/* micro-Max 4.8 Web (Core) */ /* Browser (WebAssembly) integration by 64Logic */ /* Canonical micro-Max 4.8 source: umax4_8.c by H.G. Muller */ /* */ /* Copyright (c) 2026 64Logic */ /* SPDX-License-Identifier: MIT */ /* */ /* This file is the browser counterpart of the UCI integration in */ /* micro-Max 4.8 UCI (Core). It drives the canonical search function D() */ /* directly, the same way Core does, and exports a few small functions to */ /* JavaScript instead of speaking UCI over standard input and output. */ /* */ /* umax4_8.c is compiled byte for byte unchanged, with -Dmain=umax_main and */ /* signed char semantics, exactly as in Core. Its own main() is never called. */ /* */ /* The module is freestanding: no C library is linked. The only library */ /* routines the engine needs are rand(), memset() and memcpy(), provided */ /* below. rand() reproduces the GNU C Library generator so the hash */ /* translation table T[] matches a native Linux build of Core. */ #include /* Canonical micro-Max 4.8 must be compiled with -fsigned-char. */ #if CHAR_MIN == 0 #error "use -fsigned-char when compiling all source files, including umax4_8.c" #endif #define EXPORT(name) __attribute__((export_name(name))) typedef __SIZE_TYPE__ size_t; typedef int i32; typedef unsigned int u32; /* Canonical micro-Max 4.8 interface (identical to Core) */ extern int M, S, I, Q, O, K, N, R, J, Z, k; extern char L; extern char b[129]; extern char T[1035]; extern char o[]; extern int D(int q, int l, int e, int E, int z, int n); #define U (1 << 24) extern struct _ { int K, V; char X, Y, D; } A[U]; /* ------------------------------------------------------------------------ */ /* Freestanding library routines */ /* ------------------------------------------------------------------------ */ /* Built with -mbulk-memory, these lower to the WebAssembly memory.fill and */ /* memory.copy instructions rather than calling themselves. */ void *memset(void *dst, int c, size_t n) { return __builtin_memset(dst, c, n); } void *memcpy(void *dst, const void *src, size_t n) { return __builtin_memcpy(dst, src, n); } /* GNU C Library rand(): additive feedback generator (TYPE_3, degree 31, */ /* separation 3) in its default unseeded state, which equals srand(1). */ static i32 rand_tbl[31]; static i32 *rand_front, *rand_rear; static int rand_ready = 0; static i32 rand_step(void) { u32 val; *rand_front += (u32)*rand_rear; val = (u32)*rand_front; ++rand_front; if (rand_front >= rand_tbl + 31) { rand_front = rand_tbl; ++rand_rear; } else { ++rand_rear; if (rand_rear >= rand_tbl + 31) rand_rear = rand_tbl; } return (i32)(val >> 1); } static void rand_seed(u32 seed) { i32 word, hi, lo; int i; if (seed == 0) seed = 1; rand_tbl[0] = word = (i32)seed; for (i = 1; i < 31; i++) { hi = word / 127773; lo = word % 127773; word = 16807 * lo - 2836 * hi; if (word < 0) word += 2147483647; rand_tbl[i] = word; } rand_front = rand_tbl + 3; rand_rear = rand_tbl; for (i = 0; i < 310; i++) rand_step(); rand_ready = 1; } int rand(void) { if (!rand_ready) rand_seed(1); return rand_step(); } /* ------------------------------------------------------------------------ */ /* Engine state (same procedures as Core) */ /* ------------------------------------------------------------------------ */ static int game_started = 0; static int last_nodes = 0; /* Fill T[136..1034] once per module instance, exactly as the original */ /* initialization loop does, leaving L == (char)-1. */ static void engine_init(void) { N = 1035; while (N-- > M) T[N] = rand() >> 9; L = (char)-1; } /* Restore fresh canonical game state. See game_init() in Core for the */ /* reasoning behind each step; this is the same procedure. */ static void game_init(void) { int i, kk, ll; for (i = 0; i < 129; i++) b[i] = 0; kk = 8; while (kk--) { b[kk] = (b[kk+112] = o[kk+24] + 8) + 8; b[kk+16] = 18; b[kk+96] = 9; } ll = 8; while (ll--) { kk = 8; while (kk--) b[16*ll + kk + 8] = (kk-4)*(kk-4) + (ll-3.5)*(ll-3.5); } if (game_started) memset(A, 0, sizeof A); Q = 0; O = 0; R = 0; J = 0; Z = 0; k = 16; K = I; L = (char)-1; N = 128; game_started = 1; } /* Compare only the 64 real 0x88 board squares; b[128] is the dummy square. */ static int real_board_changed(const char before[129]) { int i; for (i = 0; i < 128; i++) if (!(i & M) && before[i] != b[i]) return 1; return 0; } /* ------------------------------------------------------------------------ */ /* Exports */ /* ------------------------------------------------------------------------ */ /* Call once after instantiation. */ EXPORT("mm_init") void mm_init(void) { engine_init(); game_init(); } /* Fresh canonical game state (Core's 'ucinewgame'). */ EXPORT("mm_new_game") void mm_new_game(void) { game_init(); } /* Play one move given as four ASCII codes, e.g. 'e','2','e','4', through */ /* D()'s own validation path. Converted with the original micro-Max formula. */ /* Returns 1 if D() committed the move, 0 if it rejected it. */ EXPORT("mm_play") int mm_play(int f0, int r0, int f1, int r1) { char before[129]; if (f0 < 'a' || f0 > 'h' || r0 < '1' || r0 > '8' || f1 < 'a' || f1 > 'h' || r1 < '1' || r1 > '8') return 0; memcpy(before, b, sizeof before); K = f0 - 16*r0 + 799; L = f1 - 16*r1 + 799; N = 128; D(-I, I, Q, O, 1, 3); return real_board_changed(before); } /* Search and commit micro-Max's move from the current position. */ /* Returns from | to << 8 | promotion << 16 (0x88 squares), or -1 when no */ /* move was committed. */ EXPORT("mm_search") int mm_search(void) { char before[129]; int promo; memcpy(before, b, sizeof before); K = I; N = 128; D(-I, I, Q, O, 1, 3); last_nodes = N - S; if (!real_board_changed(before)) return -1; promo = ((before[K] & 7) < 3) && ((L & 0x70) == 0 || (L & 0x70) == 0x70); return (K & 0x7f) | ((L & 0x7f) << 8) | (promo << 16); } /* Nodes searched by the last mm_search() call. */ EXPORT("mm_nodes") int mm_nodes(void) { return last_nodes; } /* Raw board byte, for verification tools. */ EXPORT("mm_square") int mm_square(int i) { return (i >= 0 && i < 129) ? b[i] : 0; }