├── .gitignore ├── examples ├── mcu-max-uci │ ├── .gitignore │ ├── CMakeLists.txt │ └── main.c └── arduino │ └── mcu-max-serial │ └── mcu-max-serial.ino ├── docs └── img │ ├── mcumax-media.png │ └── mcumax-title.png ├── library.properties ├── library.json ├── README.md ├── LICENSE └── src ├── mcu-max.h └── mcu-max.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /examples/mcu-max-uci/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build -------------------------------------------------------------------------------- /docs/img/mcumax-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gissio/mcu-max/HEAD/docs/img/mcumax-media.png -------------------------------------------------------------------------------- /docs/img/mcumax-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gissio/mcu-max/HEAD/docs/img/mcumax-title.png -------------------------------------------------------------------------------- /examples/mcu-max-uci/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16.0) 2 | 3 | project (mcu-max-uci) 4 | 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | add_executable (mcu-max-uci main.c ../../src/mcu-max.c) 8 | 9 | target_include_directories(mcu-max-uci PRIVATE ../../src) 10 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=mcu-max 2 | version=1.0.7 3 | author=Gissio 4 | maintainer=Gissio 5 | sentence=An MCU-optimized C-language chess game engine based on micro-Max. 6 | paragraph=mcu-max comes with an Arduino serial port example, and a UCI example for interfacing mcu-max with UCI-compatible chess programs. 7 | category=Other 8 | url=https://github.com/Gissio/mcu-max 9 | architectures=* 10 | includes=mcu-max.h 11 | license=MIT 12 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcu-max", 3 | "version": "1.0.7", 4 | "keywords": "board-game, chess, chess-engine, embedded, fen, game, mcu, microcontroller, uci, uci-chess-engine", 5 | "description": "An MCU-optimized C-language chess game engine based on micro-Max.", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Gissio/mcu-max.git" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "Gissio", 14 | "maintainer": true 15 | } 16 | ], 17 | "homepage": "https://github.com/Gissio/mcu-max", 18 | "frameworks": "*", 19 | "platforms": "*" 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Title](docs/img/mcumax-title.png) 2 | 3 | ## Overview 4 | 5 | mcu-max is an MCU-optimized C-language chess game engine based on [micro-Max][micro-max-link]. 6 | 7 | mcu-max comes with an Arduino serial port example, and a UCI chess interface example for testing mcu-max from UCI-compatible chess game GUIs. 8 | 9 | When running on devices with little memory, you might want to adjust the max depth value to avoid stack overflows. 10 | 11 | Try the [Rad Pro simulator](https://gissio.github.io/radpro-simulator/) (click the right button and select Game at the bottom of the menu) to test mcu-max. 12 | 13 | ## Features 14 | 15 | * Configurable hashing. 16 | * Configurable node limit. 17 | * Configurable max depth. 18 | * Valid move listing. 19 | * Best-move search termination. 20 | 21 | ## Terms of use 22 | 23 | mcu-max is freely available and distributed under the MIT license. 24 | 25 | [micro-max-link]: https://home.hccnet.nl/h.g.muller/max-src2.html 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2025 Gissio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mcu-max.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mcu-max 3 | * Chess game engine for low-resource MCUs 4 | * 5 | * (C) 2022-2024 Gissio 6 | * 7 | * License: MIT 8 | * 9 | * Based on micro-Max 4.8 by H.G. Muller. 10 | * Compliant with FIDE laws (except for underpromotion). 11 | */ 12 | 13 | #if !defined(MCU_MAX_H) 14 | #define MCU_MAX_H 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include 21 | #include 22 | 23 | #define MCUMAX_ID "mcu-max 1.0.6" 24 | #define MCUMAX_AUTHOR "Gissio" 25 | 26 | #define MCUMAX_SQUARE_INVALID 0x80 27 | 28 | #define MCUMAX_MOVE_INVALID \ 29 | (mcumax_move) { MCUMAX_SQUARE_INVALID, MCUMAX_SQUARE_INVALID } 30 | 31 | typedef uint8_t mcumax_square; 32 | typedef uint8_t mcumax_piece; 33 | 34 | typedef struct 35 | { 36 | mcumax_square from; 37 | mcumax_square to; 38 | } mcumax_move; 39 | 40 | typedef void (*mcumax_callback)(void *); 41 | 42 | /** 43 | * Piece types 44 | */ 45 | enum 46 | { 47 | // Bits 0-2: piece 48 | MCUMAX_EMPTY, 49 | MCUMAX_PAWN_UPSTREAM, 50 | MCUMAX_PAWN_DOWNSTREAM, 51 | MCUMAX_KNIGHT, 52 | MCUMAX_KING, 53 | MCUMAX_BISHOP, 54 | MCUMAX_ROOK, 55 | MCUMAX_QUEEN, 56 | 57 | // Bits 3: color 58 | MCUMAX_BLACK = 0x8, 59 | }; 60 | 61 | /** 62 | * @brief Resets the engine state. 63 | */ 64 | void mcumax_init(void); 65 | 66 | /** 67 | * @brief Sets position from a FEN string. 68 | * 69 | * @param value The FEN string. 70 | */ 71 | void mcumax_set_fen_position(const char *value); 72 | 73 | /** 74 | * @brief Returns the piece at the specified square. 75 | * 76 | * @param square A square coded as 0xRF, R: rank (0-7), F: file (0-7). 77 | * @return The piece. 78 | */ 79 | mcumax_piece mcumax_get_piece(mcumax_square square); 80 | 81 | /** 82 | * @brief Returns the current side. 83 | */ 84 | mcumax_piece mcumax_get_current_side(void); 85 | 86 | /** 87 | * @brief Searches valid moves. 88 | * 89 | * @param buffer A buffer for storing valid moves. 90 | * @param buffer_size The buffer size for storing valid moves. 91 | * 92 | * @return The number of valid moves. 93 | */ 94 | uint32_t mcumax_search_valid_moves(mcumax_move *buffer, uint32_t buffer_size); 95 | 96 | /** 97 | * @brief Searches the best move. 98 | * 99 | * @param node_max The maximum number of nodes to search. 100 | * @param depth_max The maximum depth to search. 101 | * 102 | * @return The best move (MCUMAX_SQUARE_INVALID, MCUMAX_SQUARE_INVALID if none found). 103 | */ 104 | mcumax_move mcumax_search_best_move(uint32_t node_max, uint32_t depth_max); 105 | 106 | /** 107 | * @brief Plays a move. 108 | * 109 | * @param move The move. 110 | * @return The move was played. 111 | */ 112 | bool mcumax_play_move(mcumax_move move); 113 | 114 | /** 115 | * @brief Sets the user callback, which is called periodically during search. 116 | */ 117 | void mcumax_set_callback(mcumax_callback callback, void *userdata); 118 | 119 | /** 120 | * @brief Stops the current search. To be called from the user callback. 121 | */ 122 | void mcumax_stop_search(void); 123 | 124 | #ifdef __cplusplus 125 | } 126 | #endif 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /examples/arduino/mcu-max-serial/mcu-max-serial.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * mcu-max serial port example 3 | * 4 | * (C) 2022-2024 Gissio 5 | * 6 | * License: MIT 7 | */ 8 | 9 | #include 10 | 11 | // Modify these values to increase the AI strength: 12 | #define MCUMAX_NODE_MAX 1000 13 | #define MCUMAX_DEPTH_MAX 3 14 | 15 | #define GAME_VALID_MOVES_NUM_MAX 181 16 | 17 | void print_board() { 18 | const char *symbols = ".PPNKBRQ.ppnkbrq"; 19 | 20 | Serial.println(""); 21 | Serial.println(" +-----------------+"); 22 | 23 | for (uint32_t y = 0; y < 8; y++) { 24 | Serial.print(8 - y); 25 | Serial.print(" | "); 26 | for (uint32_t x = 0; x < 8; x++) { 27 | Serial.print(symbols[mcumax_get_piece(0x10 * y + x)]); 28 | Serial.print(' '); 29 | } 30 | Serial.println("|"); 31 | } 32 | Serial.println(" +-----------------+"); 33 | Serial.println(" a b c d e f g h"); 34 | Serial.println(""); 35 | Serial.print("Move: "); 36 | } 37 | 38 | void print_square(mcumax_square square) { 39 | Serial.print((char)('a' + ((square & 0x07) >> 0))); 40 | Serial.print((char)('1' + 7 - ((square & 0x70) >> 4))); 41 | } 42 | 43 | void print_move(mcumax_move move) { 44 | if ((move.from == MCUMAX_SQUARE_INVALID) || (move.to == MCUMAX_SQUARE_INVALID)) 45 | Serial.print("(none)"); 46 | else { 47 | print_square(move.from); 48 | print_square(move.to); 49 | } 50 | } 51 | 52 | mcumax_square get_square(char *s) { 53 | mcumax_square rank = s[0] - 'a'; 54 | if (rank > 7) 55 | return MCUMAX_SQUARE_INVALID; 56 | 57 | mcumax_square file = '8' - s[1]; 58 | if (file > 7) 59 | return MCUMAX_SQUARE_INVALID; 60 | 61 | return 0x10 * file + rank; 62 | } 63 | 64 | char serial_input[5]; 65 | 66 | void init_serial_input() { 67 | serial_input[0] = '\0'; 68 | } 69 | 70 | bool get_serial_input() { 71 | if (Serial.available()) { 72 | char s[2]; 73 | s[0] = Serial.read(); 74 | s[1] = '\0'; 75 | 76 | if (s[0] == '\n') 77 | return true; 78 | 79 | if (s[0] == '\b') { 80 | int n = strlen(s); 81 | if (n) 82 | s[n - 1] = '\0'; 83 | } 84 | 85 | if (s[0] >= ' ') { 86 | if (strlen(serial_input) < 4) 87 | strcat(serial_input, s); 88 | 89 | Serial.print(s[0]); 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | 96 | void setup() { 97 | pinMode(LED_BUILTIN, OUTPUT); 98 | 99 | Serial.begin(9600); 100 | 101 | init_serial_input(); 102 | 103 | mcumax_init(); 104 | 105 | Serial.println("mcu-max serial port example"); 106 | Serial.println("---------------------------"); 107 | Serial.println(""); 108 | Serial.println("Enter moves as [from square][to square]. E.g.: e2e4"); 109 | 110 | print_board(); 111 | } 112 | 113 | void loop() { 114 | if (!get_serial_input()) 115 | return; 116 | 117 | digitalWrite(LED_BUILTIN, HIGH); 118 | 119 | Serial.println(""); 120 | 121 | mcumax_move move = (mcumax_move){ 122 | get_square(serial_input + 0), 123 | get_square(serial_input + 2), 124 | }; 125 | 126 | mcumax_move valid_moves[GAME_VALID_MOVES_NUM_MAX]; 127 | uint32_t valid_moves_num = mcumax_search_valid_moves(valid_moves, GAME_VALID_MOVES_NUM_MAX); 128 | bool is_valid_move = false; 129 | for (uint32_t i = 0; i < valid_moves_num; i++) 130 | if ((valid_moves[i].from == move.from) && 131 | (valid_moves[i].to == move.to)) 132 | is_valid_move = true; 133 | 134 | if (!is_valid_move || !mcumax_play_move(move)) 135 | Serial.println("Invalid move."); 136 | else { 137 | mcumax_move move = mcumax_search_best_move(MCUMAX_NODE_MAX, MCUMAX_DEPTH_MAX); 138 | if (move.from == MCUMAX_SQUARE_INVALID) 139 | Serial.println("Game over."); 140 | else if (mcumax_play_move(move)) { 141 | Serial.print("Opponent moves: "); 142 | print_move(move); 143 | Serial.println(""); 144 | } 145 | } 146 | 147 | init_serial_input(); 148 | 149 | digitalWrite(LED_BUILTIN, LOW); 150 | 151 | print_board(); 152 | } 153 | -------------------------------------------------------------------------------- /examples/mcu-max-uci/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mcu-max UCI chess interface example 3 | * 4 | * (C) 2022-2024 Gissio 5 | * 6 | * License: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mcu-max.h" 14 | 15 | #define MAIN_VALID_MOVES_NUM 512 16 | 17 | void print_board() 18 | { 19 | const char *symbols = ".PPNKBRQ.ppnkbrq"; 20 | 21 | printf(" +-----------------+\n"); 22 | 23 | for (uint32_t y = 0; y < 8; y++) 24 | { 25 | printf("%d | ", 8 - y); 26 | for (uint32_t x = 0; x < 8; x++) 27 | printf("%c ", symbols[mcumax_get_piece(0x10 * y + x)]); 28 | printf("|\n"); 29 | } 30 | 31 | printf(" +-----------------+\n"); 32 | printf(" a b c d e f g h\n"); 33 | 34 | printf("\n"); 35 | } 36 | 37 | mcumax_square get_square(char *s) 38 | { 39 | mcumax_square rank = s[0] - 'a'; 40 | if (rank > 7) 41 | return MCUMAX_SQUARE_INVALID; 42 | 43 | mcumax_square file = '8' - s[1]; 44 | if (file > 7) 45 | return MCUMAX_SQUARE_INVALID; 46 | 47 | return 0x10 * file + rank; 48 | } 49 | 50 | bool is_square_valid(char *s) 51 | { 52 | return (get_square(s) != MCUMAX_SQUARE_INVALID); 53 | } 54 | 55 | bool is_move_valid(char *s) 56 | { 57 | return is_square_valid(s) && is_square_valid(s + 2); 58 | } 59 | 60 | void print_square(mcumax_square square) 61 | { 62 | printf("%c%c", 63 | 'a' + ((square & 0x07) >> 0), 64 | '1' + 7 - ((square & 0x70) >> 4)); 65 | } 66 | 67 | void print_move(mcumax_move move) 68 | { 69 | if ((move.from == MCUMAX_SQUARE_INVALID) || 70 | (move.to == MCUMAX_SQUARE_INVALID)) 71 | printf("(none)"); 72 | else 73 | { 74 | print_square(move.from); 75 | print_square(move.to); 76 | } 77 | } 78 | 79 | bool send_uci_command(char *line) 80 | { 81 | char *token = strtok(line, " \n"); 82 | 83 | if (!token) 84 | return false; 85 | 86 | if (!strcmp(token, "uci")) 87 | { 88 | printf("id name " MCUMAX_ID "\n"); 89 | printf("id author " MCUMAX_AUTHOR "\n"); 90 | printf("uciok\n"); 91 | } 92 | else if (!strcmp(token, "uci") || 93 | !strcmp(token, "ucinewgame")) 94 | mcumax_init(); 95 | else if (!strcmp(token, "isready")) 96 | printf("readyok\n"); 97 | else if (!strcmp(token, "d")) 98 | print_board(); 99 | else if (!strcmp(token, "l")) 100 | { 101 | mcumax_move valid_moves[MAIN_VALID_MOVES_NUM]; 102 | uint32_t valid_moves_num = mcumax_search_valid_moves(valid_moves, MAIN_VALID_MOVES_NUM); 103 | 104 | for (uint32_t i = 0; i < valid_moves_num; i++) 105 | { 106 | print_move(valid_moves[i]); 107 | printf(" "); 108 | } 109 | printf("\n"); 110 | } 111 | else if (!strcmp(token, "position")) 112 | { 113 | int fen_index = 0; 114 | char fen_string[256]; 115 | 116 | while (token = strtok(NULL, " \n")) 117 | { 118 | if (fen_index) 119 | { 120 | strcat(fen_string, token); 121 | strcat(fen_string, " "); 122 | 123 | fen_index++; 124 | if (fen_index > 6) 125 | { 126 | mcumax_set_fen_position(fen_string); 127 | 128 | fen_index = 0; 129 | } 130 | } 131 | else 132 | { 133 | if (!strcmp(token, "startpos")) 134 | mcumax_init(); 135 | else if (!strcmp(token, "fen")) 136 | { 137 | fen_index = 1; 138 | strcpy(fen_string, ""); 139 | } 140 | else if (is_move_valid(token)) 141 | { 142 | mcumax_play_move((mcumax_move){ 143 | get_square(token + 0), 144 | get_square(token + 2), 145 | }); 146 | } 147 | } 148 | } 149 | } 150 | else if (!strcmp(token, "go")) 151 | { 152 | mcumax_move move = mcumax_search_best_move(1000000, 30); 153 | mcumax_play_move(move); 154 | 155 | printf("bestmove "); 156 | print_move(move); 157 | printf("\n"); 158 | } 159 | else if (!strcmp(token, "quit")) 160 | return true; 161 | else 162 | printf("Unknown command: %s\n", token); 163 | 164 | return false; 165 | } 166 | 167 | int main() 168 | { 169 | mcumax_init(); 170 | 171 | while (true) 172 | { 173 | fflush(stdout); 174 | 175 | char line[65536]; 176 | fgets(line, sizeof(line), stdin); 177 | 178 | if (send_uci_command(line)) 179 | break; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/mcu-max.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mcu-max 3 | * Chess game engine for low-resource MCUs 4 | * 5 | * (C) 2022-2024 Gissio 6 | * 7 | * License: MIT 8 | * 9 | * Based on micro-Max 4.8 by H.G. Muller. 10 | * Compliant with FIDE laws (except for underpromotion). 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mcu-max.h" 18 | 19 | // Configuration 20 | // #define MCUMAX_HASHING_ENABLED 21 | 22 | // Constants 23 | #define MCUMAX_BOARD_MASK 0x88 24 | #define MCUMAX_BOARD_WHITE 0x8 25 | #define MCUMAX_BOARD_BLACK 0x10 26 | #define MCUMAX_PIECE_MOVED 0x20 27 | #define MCUMAX_SCORE_MAX 8000 28 | #define MCUMAX_DEPTH_MAX 99 29 | 30 | enum mcumax_mode 31 | { 32 | MCUMAX_INTERNAL_NODE, 33 | MCUMAX_SEARCH_VALID_MOVES, 34 | MCUMAX_SEARCH_BEST_MOVE, 35 | MCUMAX_PLAY_MOVE, 36 | }; 37 | 38 | struct 39 | { 40 | // Board: first half of 16x8 + dummy 41 | uint8_t board[0x80 + 1]; 42 | uint8_t current_side; 43 | 44 | // Engine 45 | int32_t score; 46 | uint8_t en_passant_square; 47 | int32_t non_pawn_material; 48 | 49 | #ifdef MCUMAX_HASHING_ENABLED 50 | uint32_t hash_key; 51 | uint32_t hash_key2; 52 | #endif 53 | 54 | // Interface 55 | uint8_t square_from; // Selected move 56 | uint8_t square_to; 57 | 58 | uint32_t node_count; 59 | uint32_t node_max; 60 | uint32_t depth_max; 61 | 62 | bool stop_search; 63 | 64 | // Extra 65 | mcumax_callback user_callback; 66 | void *user_data; 67 | 68 | mcumax_move *valid_moves_buffer; 69 | uint32_t valid_moves_buffer_size; 70 | uint32_t valid_moves_num; 71 | } mcumax; 72 | 73 | static const int8_t mcumax_capture_values[] = { 74 | 0, 2, 2, 7, -1, 8, 12, 23}; 75 | 76 | static const int8_t mcumax_step_vectors_indices[] = { 77 | 0, 7, -1, 11, 6, 8, 3, 6}; 78 | 79 | static const int8_t mcumax_step_vectors[] = { 80 | // Upstream pawn 81 | -16, -15, -17, 0, 82 | // Rook 83 | 1, 16, 0, 84 | // King, queen 85 | 1, 16, 15, 17, 0, 86 | // Knight 87 | 14, 18, 31, 33, 0}; 88 | 89 | static const int8_t mcumax_board_setup[] = { 90 | MCUMAX_ROOK, 91 | MCUMAX_KNIGHT, 92 | MCUMAX_BISHOP, 93 | MCUMAX_QUEEN, 94 | MCUMAX_KING, 95 | MCUMAX_BISHOP, 96 | MCUMAX_KNIGHT, 97 | MCUMAX_ROOK, 98 | }; 99 | 100 | #ifdef MCUMAX_HASHING_ENABLED 101 | 102 | #define MCUMAX_HASH_SCRAMBLE_TABLE_SIZE 1035 103 | #define MCUMAX_HASH_TABLE_SIZE (1 << 24) 104 | 105 | #define HashScramble(A, B) \ 106 | *(uint32_t *)(mcumax_scramble_table + \ 107 | A + (B & 8) + MCUMAX_SQUARE_INVALID * (B & 0b111)) 108 | #define Hash(A) \ 109 | HashScramble(step_square_to + A, mcumax.board[step_square_to]) - \ 110 | HashScramble(scan_square_from + A, scan_piece) - \ 111 | HashScramble(capture_square + A, capture_piece) 112 | 113 | static uint8_t mcumax_scramble_table[MCUMAX_HASH_SCRAMBLE_TABLE_SIZE]; /* hash translation table */ 114 | 115 | struct HashEntry 116 | { 117 | uint32_t key2; 118 | int32_t score; 119 | uint8_t square_from; 120 | uint8_t square_to; 121 | uint8_t depth; 122 | }; 123 | 124 | static struct HashEntry mcumax_hash_table[MCUMAX_HASH_TABLE_SIZE]; 125 | 126 | #endif 127 | 128 | typedef bool (*mcumax_move_callback)(mcumax_move move); 129 | 130 | static int32_t mcumax_search(int32_t alpha, 131 | int32_t beta, 132 | int32_t score, 133 | uint8_t en_passant_square, 134 | uint8_t depth, 135 | enum mcumax_mode mode); 136 | 137 | // Recursive minimax search 138 | // (alpha,beta)=window, score=current evaluation score, en_passant_square=e.p. sqr. 139 | // depth=depth, in_root=in_root; returns score 140 | static int32_t mcumax_search(int32_t alpha, 141 | int32_t beta, 142 | int32_t score, 143 | uint8_t en_passant_square, 144 | uint8_t depth, 145 | enum mcumax_mode mode) 146 | { 147 | if (mcumax.user_callback) 148 | mcumax.user_callback(mcumax.user_data); 149 | 150 | uint8_t iter_depth; 151 | int32_t iter_score; 152 | uint8_t iter_square_from; 153 | uint8_t iter_square_to; 154 | 155 | #ifdef MCUMAX_HASHING_ENABLED 156 | int32_t hash_key; 157 | int32_t hash_key2; 158 | #endif 159 | 160 | uint8_t square_start; 161 | 162 | uint8_t square_from; 163 | uint8_t square_to; 164 | 165 | uint8_t replay_move; 166 | int32_t null_move_score; 167 | 168 | uint8_t scan_piece; 169 | uint8_t scan_piece_type; 170 | 171 | int8_t step_vector; 172 | int8_t step_vector_index; 173 | 174 | uint8_t castling_skip_square; 175 | uint8_t castling_rook_square; 176 | 177 | uint8_t capture_square; 178 | uint8_t capture_piece; 179 | int32_t capture_piece_value; 180 | 181 | uint8_t step_depth; 182 | int32_t step_alpha; 183 | int32_t step_score; 184 | int32_t step_score_new; 185 | 186 | // Adj. window: delay bonus 187 | alpha -= alpha < score; 188 | beta -= beta <= score; 189 | 190 | #ifdef MCUMAX_HASHING_ENABLED 191 | // Lookup pos. in hash table 192 | struct HashEntry *hash_entry = mcumax_hash_table + 193 | ((mcumax.hash_key + 194 | mcumax.current_side * en_passant_square) & 195 | MCUMAX_HASH_TABLE_SIZE - 1); 196 | 197 | iter_depth = hash_entry->depth; 198 | iter_score = hash_entry->score; 199 | iter_square_from = hash_entry->square_from; 200 | iter_square_to = hash_entry->square_to; 201 | 202 | // Resume at stored depth 203 | if ((hash_entry->key2 != mcumax.hash_key2) || 204 | (mode != MCUMAX_INTERNAL_NODE) || // Miss: other pos. or empty 205 | !(((iter_score <= alpha) || 206 | (iter_square_from & 0x8)) && 207 | ((iter_score >= beta) || 208 | (iter_square_from & MCUMAX_SQUARE_INVALID)))) // Or window incompatible 209 | { 210 | // Start iteration from scratch 211 | iter_depth = 212 | iter_square_to = 0; 213 | } 214 | 215 | // Start at best-move hint 216 | iter_square_from &= ~MCUMAX_BOARD_MASK; 217 | 218 | hash_key = mcumax.hash_key; 219 | hash_key2 = mcumax.hash_key2; 220 | #else 221 | iter_depth = 222 | iter_score = 223 | iter_square_from = 224 | iter_square_to = 0; 225 | #endif 226 | 227 | // Min depth = 2 iterative deepening loop 228 | // root: deepen upto time 229 | // time's up: go do best 230 | while ((iter_depth++ < depth) || 231 | (iter_depth < 3) || 232 | ((mode != MCUMAX_INTERNAL_NODE) && 233 | (mcumax.square_from == MCUMAX_SQUARE_INVALID) && 234 | (((mcumax.node_count < mcumax.node_max) && 235 | (iter_depth <= mcumax.depth_max)) || 236 | (mcumax.square_from = iter_square_from, 237 | mcumax.square_to = iter_square_to & ~MCUMAX_BOARD_MASK, 238 | iter_depth = 3)))) 239 | { 240 | if (mcumax.stop_search) 241 | break; 242 | 243 | // Start scan at previous best 244 | square_from = 245 | square_start = (mode != MCUMAX_SEARCH_VALID_MOVES) 246 | ? iter_square_from 247 | : 0; 248 | 249 | // Request try noncastling first 250 | replay_move = iter_square_to & MCUMAX_SQUARE_INVALID; 251 | 252 | // Change side 253 | mcumax.current_side ^= 0x18; 254 | 255 | // Search null move 256 | null_move_score = (iter_depth > 2) && (beta != -MCUMAX_SCORE_MAX) 257 | ? mcumax_search(-beta, 258 | 1 - beta, 259 | -score, 260 | MCUMAX_SQUARE_INVALID, 261 | iter_depth - 3, 262 | MCUMAX_INTERNAL_NODE) 263 | : MCUMAX_SCORE_MAX; 264 | 265 | // Change side 266 | mcumax.current_side ^= 0x18; 267 | 268 | // Prune if > beta unconsidered:static eval 269 | iter_score = (-null_move_score < beta) || 270 | (mcumax.non_pawn_material > 35) 271 | ? (iter_depth - 2) 272 | ? -MCUMAX_SCORE_MAX 273 | : score 274 | : -null_move_score; 275 | 276 | // Node count (for timing) 277 | mcumax.node_count++; 278 | 279 | do 280 | { 281 | // Scan board looking for 282 | scan_piece = mcumax.board[square_from]; 283 | 284 | // Own piece (inefficient!) 285 | if (scan_piece & mcumax.current_side) 286 | { 287 | // p = piece type (set r>0) 288 | step_vector = scan_piece_type = (scan_piece & 0b111); 289 | 290 | // First step vector for piece 291 | step_vector_index = mcumax_step_vectors_indices[scan_piece_type]; 292 | 293 | // Loop over directions o[] 294 | while ((step_vector = ((scan_piece_type > 2) && 295 | (step_vector < 0)) 296 | ? -step_vector 297 | : -mcumax_step_vectors[++step_vector_index])) 298 | { 299 | replay: 300 | // Resume normal after best 301 | square_to = square_from; 302 | 303 | castling_skip_square = 304 | castling_rook_square = MCUMAX_SQUARE_INVALID; 305 | 306 | // y traverses ray, or: 307 | do 308 | { 309 | // Sneak in previous best move 310 | capture_square = 311 | square_to = 312 | replay_move 313 | ? (iter_square_to ^ replay_move) 314 | : (square_to + step_vector); 315 | 316 | // Board edge hit 317 | if (square_to & MCUMAX_BOARD_MASK) 318 | break; 319 | 320 | // Bad castling 321 | if ((en_passant_square != MCUMAX_SQUARE_INVALID) && 322 | mcumax.board[en_passant_square] && 323 | ((square_to - en_passant_square) < 2) && 324 | ((en_passant_square - square_to) < 2)) 325 | iter_score = MCUMAX_SCORE_MAX; 326 | 327 | // Shift capture square if en-passant 328 | if ((scan_piece_type < 3) && 329 | (square_to == en_passant_square)) 330 | capture_square ^= 16; 331 | 332 | capture_piece = mcumax.board[capture_square]; 333 | 334 | // Capture own, bad pawn mode 335 | if ((capture_piece & mcumax.current_side) || 336 | ((scan_piece_type < 3) && 337 | !((square_to - square_from) & 0b111) - !capture_piece)) 338 | break; 339 | 340 | // Value of captured piece 341 | capture_piece_value = 37 * mcumax_capture_values[capture_piece & 0b111] + 342 | (capture_piece & 0xc0); 343 | 344 | // King capture 345 | if (capture_piece_value < 0) 346 | { 347 | iter_score = MCUMAX_SCORE_MAX; 348 | iter_depth = MCUMAX_DEPTH_MAX - 1; 349 | } 350 | 351 | // Abort on fail high 352 | if ((iter_score >= beta) && 353 | (iter_depth > 1)) 354 | goto cutoff; 355 | 356 | // MVV/LVA scoring if depth == 1 357 | step_score = (iter_depth != 1) 358 | ? score 359 | : capture_piece_value - scan_piece_type; 360 | 361 | // All captures if depth == 2 362 | if ((iter_depth - !capture_piece) > 1) 363 | { 364 | // Center positional score 365 | step_score = (scan_piece_type < 6) 366 | ? mcumax.board[square_from + 0x8] - 367 | mcumax.board[square_to + 0x8] 368 | : 0; 369 | 370 | mcumax.board[castling_rook_square] = 371 | mcumax.board[capture_square] = 372 | mcumax.board[square_from] = 0; 373 | 374 | // Do move, set non-virgin 375 | mcumax.board[square_to] = scan_piece | MCUMAX_PIECE_MOVED; 376 | 377 | // Castling: put rook & score 378 | if (!(castling_rook_square & MCUMAX_BOARD_MASK)) 379 | { 380 | mcumax.board[castling_skip_square] = mcumax.current_side + 6; 381 | step_score += 50; 382 | } 383 | 384 | // Freeze king in mid-game 385 | step_score -= ((scan_piece_type != 4) || 386 | (mcumax.non_pawn_material > 30)) 387 | ? 0 388 | : 20; 389 | 390 | // Pawns 391 | if (scan_piece_type < 3) 392 | { 393 | step_score -= 394 | 9 * ((((square_from - 2) & MCUMAX_BOARD_MASK) || 395 | mcumax.board[square_from - 2] - scan_piece) + 396 | // Structure, undefended 397 | (((square_from + 2) & MCUMAX_BOARD_MASK) || 398 | mcumax.board[square_from + 2] - scan_piece) - 399 | 1 + 400 | // Squares plus bias 401 | (mcumax.board[square_from ^ 0x10] == 402 | (mcumax.current_side + 36))) // Cling to magnetic king 403 | - (mcumax.non_pawn_material >> 2); // End-game Pawn-push bonus 404 | 405 | // Promotion / passer bonus 406 | capture_piece_value += 407 | step_alpha = 408 | (square_to + step_vector + 1) & MCUMAX_SQUARE_INVALID 409 | ? (647 - scan_piece_type) 410 | : 2 * (scan_piece & (square_to + 0x10) & 0x20); 411 | 412 | // Upgrade pawn or convert to queen 413 | mcumax.board[square_to] += step_alpha; 414 | } 415 | 416 | #ifdef MCUMAX_HASHING_ENABLED 417 | mcumax.hash_key += Hash(0); 418 | mcumax.hash_key2 += Hash(8) + castling_rook_square - MCUMAX_SQUARE_INVALID; 419 | #endif 420 | 421 | // New score & alpha 422 | step_score += score + capture_piece_value; 423 | step_alpha = iter_score > alpha 424 | ? iter_score 425 | : alpha; 426 | 427 | // New depth, reduce non-capture 428 | step_depth = iter_depth - 1 - 429 | ((iter_depth > 5) && 430 | (scan_piece_type > 2) && 431 | !capture_piece && 432 | !replay_move); 433 | 434 | // Extend 1 ply if in check 435 | if (!((mcumax.non_pawn_material > 30) || 436 | (null_move_score - MCUMAX_SCORE_MAX) || 437 | (iter_depth < 3) || 438 | (capture_piece && 439 | (scan_piece_type != 4)))) 440 | step_depth = iter_depth; 441 | 442 | // Futility, recursive evaluation of reply 443 | do 444 | { 445 | // Change side 446 | mcumax.current_side ^= 0x18; 447 | 448 | step_score_new = ((mode == MCUMAX_SEARCH_VALID_MOVES) || 449 | (step_depth > 2) || 450 | (step_score > step_alpha)) 451 | ? -mcumax_search(-beta, 452 | -step_alpha, 453 | -step_score, 454 | castling_skip_square, 455 | step_depth, 456 | MCUMAX_INTERNAL_NODE) 457 | : step_score; 458 | 459 | // Change side 460 | mcumax.current_side ^= 0x18; 461 | } while ((step_score_new > alpha) && 462 | (++step_depth < iter_depth)); 463 | 464 | // No fail: re-search unreduced 465 | step_score = step_score_new; 466 | 467 | if ((mode == MCUMAX_PLAY_MOVE) && 468 | (step_score != -MCUMAX_SCORE_MAX) && 469 | (square_from == mcumax.square_from) && 470 | (square_to == mcumax.square_to)) 471 | { 472 | // Playing move 473 | mcumax.score = -score - capture_piece_value; 474 | mcumax.en_passant_square = castling_skip_square; 475 | 476 | #ifdef MCUMAX_HASHING_ENABLED 477 | // Lock game in hash as draw 478 | hash_entry->depth = MCUMAX_DEPTH_MAX; 479 | hash_entry->score = 0; 480 | #endif 481 | 482 | // Total captured material 483 | mcumax.non_pawn_material += capture_piece_value >> 7; 484 | 485 | // Change side 486 | mcumax.current_side ^= 0x18; 487 | 488 | // Captured non-pawn material 489 | return beta; 490 | } 491 | 492 | #ifdef MCUMAX_HASHING_ENABLED 493 | mcumax.hash_key = hash_key; 494 | mcumax.hash_key2 = hash_key2; 495 | #endif 496 | 497 | // Undo move 498 | mcumax.board[castling_rook_square] = mcumax.current_side + 6; 499 | mcumax.board[castling_skip_square] = mcumax.board[square_to] = 0; 500 | mcumax.board[square_from] = scan_piece; 501 | mcumax.board[capture_square] = capture_piece; 502 | 503 | if ((mode == MCUMAX_SEARCH_BEST_MOVE) && 504 | (step_score != -MCUMAX_SCORE_MAX) && 505 | (square_from == mcumax.square_from) && 506 | (square_to == mcumax.square_to)) 507 | // Searching best move 508 | return beta; 509 | 510 | if ((mode == MCUMAX_SEARCH_VALID_MOVES) && 511 | (step_score != -MCUMAX_SCORE_MAX) && 512 | (mcumax.square_from == MCUMAX_SQUARE_INVALID) && 513 | (iter_depth == 3) && 514 | !replay_move) 515 | { 516 | // Searching valid moves 517 | mcumax_move move = {square_from, square_to}; 518 | 519 | if (mcumax.valid_moves_num < mcumax.valid_moves_buffer_size) 520 | mcumax.valid_moves_buffer[mcumax.valid_moves_num] = move; 521 | 522 | mcumax.valid_moves_num++; 523 | } 524 | } 525 | 526 | // New best, update max,best 527 | if (step_score > iter_score) 528 | { 529 | // Mark non-double 530 | iter_score = step_score; 531 | iter_square_from = square_from; 532 | iter_square_to = square_to | 533 | (castling_skip_square & MCUMAX_SQUARE_INVALID); 534 | } 535 | 536 | if (replay_move) 537 | { 538 | // Redo after doing old best 539 | replay_move = 0; 540 | 541 | goto replay; 542 | } 543 | 544 | // Not first step, moved before 545 | if ((square_from + step_vector - square_to) || 546 | (scan_piece & MCUMAX_PIECE_MOVED) || 547 | // No pawn and no lateral king move 548 | ((scan_piece_type > 2) && 549 | (((scan_piece_type != 4) || 550 | (step_vector_index != 7) || 551 | // No virgin rook in corner 552 | (mcumax.board[castling_rook_square = 553 | ((square_from + 3) ^ 554 | ((step_vector >> 1) & 0b111))] - 555 | mcumax.current_side - 6) || 556 | // No two empty squares next to rook 557 | mcumax.board[castling_rook_square ^ 1] || 558 | mcumax.board[castling_rook_square ^ 2])))) 559 | // Fake capture for nonsliding 560 | capture_piece += (scan_piece_type < 5); 561 | else 562 | // Enable en-passant 563 | castling_skip_square = square_to; 564 | 565 | // If no capture, continue ray 566 | } while (!capture_piece); 567 | } 568 | } 569 | 570 | // Next square of board, wrap 571 | } while ((square_from = ((square_from + 9) & 572 | ~MCUMAX_BOARD_MASK)) != square_start); 573 | 574 | cutoff: 575 | // Check test thru NM best loses king: (stale)mate 576 | if ((iter_score == -MCUMAX_SCORE_MAX) && 577 | (null_move_score != MCUMAX_SCORE_MAX)) 578 | iter_score = 0; 579 | 580 | #ifdef MCUMAX_HASHING_ENABLED 581 | // Protect game history 582 | if (hash_entry->depth < MCUMAX_DEPTH_MAX) 583 | { 584 | hash_entry->key2 = mcumax.hash_key2; 585 | hash_entry->score = iter_score; 586 | hash_entry->depth = iter_depth; 587 | 588 | // Move, type (bound/exact) 589 | hash_entry->square_from = iter_square_from | 590 | 8 * (iter_score > alpha) | 591 | MCUMAX_SQUARE_INVALID * (iter_score < beta); 592 | hash_entry->square_to = iter_square_to; 593 | } 594 | #endif 595 | 596 | // Kibitz 597 | // if (in_root) 598 | // printf("%2d %6d %10d %c%c%c%c\n", 599 | // iter_depth - 2, 600 | // iter_score, 601 | // mcumax.node_count, 602 | // 'a' + (iter_square_from & 0b111), 603 | // '8' - (iter_square_from >> 4), 604 | // 'a' + (iter_square_to & 0b111), 605 | // '8' - (iter_square_to >> 4 & 0b111)); 606 | } 607 | 608 | // Delayed-loss bonus 609 | return iter_score += iter_score < score; 610 | } 611 | 612 | /***************************************************************************/ 613 | 614 | void mcumax_init() 615 | { 616 | for (uint32_t x = 0; x < 8; x++) 617 | { 618 | // Setup pieces (left side) 619 | mcumax.board[0x10 * 0 + x] = MCUMAX_BOARD_BLACK | mcumax_board_setup[x]; 620 | mcumax.board[0x10 * 1 + x] = MCUMAX_BOARD_BLACK | MCUMAX_PAWN_DOWNSTREAM; 621 | for (uint32_t y = 2; y < 6; y++) 622 | mcumax.board[0x10 * y + x] = MCUMAX_EMPTY; 623 | mcumax.board[0x10 * 6 + x] = MCUMAX_BOARD_WHITE | MCUMAX_PAWN_UPSTREAM; 624 | mcumax.board[0x10 * 7 + x] = MCUMAX_BOARD_WHITE | mcumax_board_setup[x]; 625 | 626 | // Setup weights (right side) 627 | for (uint32_t y = 0; y < 8; y++) 628 | mcumax.board[16 * y + x + 8] = (x - 4) * (x - 4) + (y - 4) * (y - 3); 629 | } 630 | mcumax.current_side = MCUMAX_BOARD_WHITE; 631 | 632 | mcumax.score = 0; 633 | mcumax.en_passant_square = MCUMAX_SQUARE_INVALID; 634 | mcumax.non_pawn_material = 0; 635 | 636 | #ifdef MCUMAX_HASHING_ENABLED 637 | mcumax.hash_key = 0; 638 | mcumax.hash_key2 = 0; 639 | 640 | memset(mcumax_hash_table, 0, sizeof(mcumax_hash_table)); 641 | 642 | // for (uint32_t i = MCUMAX_HASH_SCRAMBLE_TABLE_SIZE - 1; i > MCUMAX_BOARD_MASK; i--) 643 | // mcumax_scramble_table[i] = rand() & 0xff; 644 | 645 | srand(1); 646 | for (uint32_t i = 0; i < 1035; i++) 647 | mcumax_scramble_table[i] = 648 | ((rand() & 0xff) << 0) | 649 | ((rand() & 0xff) << 8) | 650 | ((rand() & 0xff) << 16) | 651 | ((rand() & 0xff) << 24); 652 | #endif 653 | } 654 | 655 | static mcumax_square mcumax_set_piece(mcumax_square square, mcumax_piece piece) 656 | { 657 | if (square & MCUMAX_BOARD_MASK) 658 | return square; 659 | 660 | mcumax.board[square] = piece ? (piece | MCUMAX_PIECE_MOVED) : piece; 661 | 662 | return square + 1; 663 | } 664 | 665 | mcumax_piece mcumax_get_piece(mcumax_square square) 666 | { 667 | if (square & MCUMAX_BOARD_MASK) 668 | return MCUMAX_EMPTY; 669 | 670 | return (mcumax.board[square] & 0xf) ^ MCUMAX_BLACK; 671 | } 672 | 673 | void mcumax_set_fen_position(const char *fen_string) 674 | { 675 | mcumax_init(); 676 | 677 | uint32_t field_index = 0; 678 | uint32_t board_index = 0; 679 | 680 | char c; 681 | while ((c = *fen_string++)) 682 | { 683 | if (c == ' ') 684 | { 685 | if (field_index < 4) 686 | field_index++; 687 | 688 | continue; 689 | } 690 | 691 | switch (field_index) 692 | { 693 | case 0: 694 | if (board_index < 0x80) 695 | { 696 | switch (c) 697 | { 698 | case '8': 699 | case '7': 700 | case '6': 701 | case '5': 702 | case '4': 703 | case '3': 704 | case '2': 705 | case '1': 706 | for (int32_t i = 0; i < (c - '0'); i++) 707 | board_index = mcumax_set_piece(board_index, MCUMAX_EMPTY); 708 | 709 | break; 710 | 711 | case 'P': 712 | board_index = mcumax_set_piece(board_index, MCUMAX_PAWN_UPSTREAM | MCUMAX_BOARD_WHITE); 713 | 714 | break; 715 | 716 | case 'N': 717 | board_index = mcumax_set_piece(board_index, MCUMAX_KNIGHT | MCUMAX_BOARD_WHITE); 718 | 719 | break; 720 | 721 | case 'B': 722 | board_index = mcumax_set_piece(board_index, MCUMAX_BISHOP | MCUMAX_BOARD_WHITE); 723 | 724 | break; 725 | 726 | case 'R': 727 | board_index = mcumax_set_piece(board_index, MCUMAX_ROOK | MCUMAX_BOARD_WHITE); 728 | 729 | break; 730 | 731 | case 'Q': 732 | board_index = mcumax_set_piece(board_index, MCUMAX_QUEEN | MCUMAX_BOARD_WHITE); 733 | 734 | break; 735 | 736 | case 'K': 737 | board_index = mcumax_set_piece(board_index, MCUMAX_KING | MCUMAX_BOARD_WHITE); 738 | 739 | break; 740 | 741 | case 'p': 742 | board_index = mcumax_set_piece(board_index, MCUMAX_PAWN_DOWNSTREAM | MCUMAX_BOARD_BLACK); 743 | 744 | break; 745 | 746 | case 'n': 747 | board_index = mcumax_set_piece(board_index, MCUMAX_KNIGHT | MCUMAX_BOARD_BLACK); 748 | 749 | break; 750 | 751 | case 'b': 752 | board_index = mcumax_set_piece(board_index, MCUMAX_BISHOP | MCUMAX_BOARD_BLACK); 753 | 754 | break; 755 | 756 | case 'r': 757 | board_index = mcumax_set_piece(board_index, MCUMAX_ROOK | MCUMAX_BOARD_BLACK); 758 | 759 | break; 760 | 761 | case 'q': 762 | board_index = mcumax_set_piece(board_index, MCUMAX_QUEEN | MCUMAX_BOARD_BLACK); 763 | 764 | break; 765 | 766 | case 'k': 767 | board_index = mcumax_set_piece(board_index, MCUMAX_KING | MCUMAX_BOARD_BLACK); 768 | 769 | break; 770 | 771 | case '/': 772 | board_index = (board_index < 0x80) ? (board_index & 0xf0) + 0x10 : board_index; 773 | 774 | break; 775 | } 776 | } 777 | break; 778 | 779 | case 1: 780 | switch (c) 781 | { 782 | case 'w': 783 | mcumax.current_side = MCUMAX_BOARD_WHITE; 784 | 785 | break; 786 | 787 | case 'b': 788 | mcumax.current_side = MCUMAX_BOARD_BLACK; 789 | 790 | break; 791 | } 792 | break; 793 | 794 | case 2: 795 | switch (c) 796 | { 797 | case 'K': 798 | mcumax.board[0x74] &= ~MCUMAX_PIECE_MOVED; 799 | mcumax.board[0x77] &= ~MCUMAX_PIECE_MOVED; 800 | 801 | break; 802 | 803 | case 'Q': 804 | mcumax.board[0x74] &= ~MCUMAX_PIECE_MOVED; 805 | mcumax.board[0x70] &= ~MCUMAX_PIECE_MOVED; 806 | 807 | break; 808 | 809 | case 'k': 810 | mcumax.board[0x04] &= ~MCUMAX_PIECE_MOVED; 811 | mcumax.board[0x07] &= ~MCUMAX_PIECE_MOVED; 812 | 813 | break; 814 | 815 | case 'q': 816 | mcumax.board[0x04] &= ~MCUMAX_PIECE_MOVED; 817 | mcumax.board[0x00] &= ~MCUMAX_PIECE_MOVED; 818 | 819 | break; 820 | } 821 | 822 | break; 823 | 824 | case 3: 825 | switch (c) 826 | { 827 | case 'a': 828 | case 'b': 829 | case 'c': 830 | case 'd': 831 | case 'e': 832 | case 'f': 833 | case 'g': 834 | case 'h': 835 | mcumax.en_passant_square &= 0x7f; 836 | mcumax.en_passant_square |= (c - 'a'); 837 | 838 | break; 839 | 840 | case '1': 841 | case '2': 842 | case '3': 843 | case '4': 844 | case '5': 845 | case '6': 846 | case '7': 847 | case '8': 848 | mcumax.en_passant_square &= 0x7f; 849 | mcumax.en_passant_square |= 16 * ('8' - c); 850 | 851 | break; 852 | } 853 | 854 | break; 855 | } 856 | } 857 | } 858 | 859 | mcumax_piece mcumax_get_current_side(void) 860 | { 861 | return mcumax.current_side; 862 | } 863 | 864 | static int32_t mcumax_start_search(enum mcumax_mode mode, 865 | mcumax_move move, 866 | uint32_t depth_max, 867 | uint32_t node_max) 868 | { 869 | mcumax.square_from = move.from; 870 | mcumax.square_to = move.to; 871 | 872 | mcumax.node_max = node_max; 873 | mcumax.node_count = 0; 874 | mcumax.depth_max = depth_max; 875 | 876 | mcumax.stop_search = false; 877 | 878 | return mcumax_search(-MCUMAX_SCORE_MAX, 879 | MCUMAX_SCORE_MAX, 880 | mcumax.score, 881 | mcumax.en_passant_square, 882 | 3, 883 | mode); 884 | } 885 | 886 | uint32_t mcumax_search_valid_moves(mcumax_move *valid_moves_buffer, uint32_t valid_moves_buffer_size) 887 | { 888 | mcumax.valid_moves_num = 0; 889 | mcumax.valid_moves_buffer = valid_moves_buffer; 890 | mcumax.valid_moves_buffer_size = valid_moves_buffer_size; 891 | 892 | mcumax_start_search(MCUMAX_SEARCH_VALID_MOVES, MCUMAX_MOVE_INVALID, 0, 0); 893 | 894 | return mcumax.valid_moves_num; 895 | } 896 | 897 | mcumax_move mcumax_search_best_move(uint32_t node_max, uint32_t depth_max) 898 | { 899 | int32_t score = mcumax_start_search(MCUMAX_SEARCH_BEST_MOVE, 900 | MCUMAX_MOVE_INVALID, depth_max + 3, node_max); 901 | 902 | if (score == MCUMAX_SCORE_MAX) 903 | return (mcumax_move){mcumax.square_from, mcumax.square_to}; 904 | else 905 | return MCUMAX_MOVE_INVALID; 906 | } 907 | 908 | bool mcumax_play_move(mcumax_move move) 909 | { 910 | return mcumax_start_search(MCUMAX_PLAY_MOVE, move, 0, 0) == MCUMAX_SCORE_MAX; 911 | } 912 | 913 | void mcumax_set_callback(mcumax_callback callback, void *userdata) 914 | { 915 | mcumax.user_callback = callback; 916 | mcumax.user_data = userdata; 917 | } 918 | 919 | void mcumax_stop_search(void) 920 | { 921 | mcumax.stop_search = true; 922 | } 923 | --------------------------------------------------------------------------------