A little app to simulate every possible move in Connect Four.
at main 14 kB view raw
1#include "core.h" 2#include "helpers.h" 3 4#include <cassert> 5#include <math.h> 6#include "SDL.h" 7#include <stdio.h> 8 9struct WinningBoardCollection 10{ 11 uint8_t num_boards; 12 binary_board* boards; 13}; 14 15MakeMoveResult make_move_table_partial[2][MAKE_MOVE_TABLE_PARTIAL_SIZE]; 16MakeMoveResult make_move_table_full[2][MAKE_MOVE_TABLE_FULL_SIZE]; 17 18const uint8_t CHECK_FOR_WIN_PROCESSED_COLUMN_TABLE_SIZE = MAX_FULL_COLUMN + 1; 19uint8_t check_for_win_processed_column_table[2][CHECK_FOR_WIN_PROCESSED_COLUMN_TABLE_SIZE]; 20WinningBoardCollection check_for_win_processed_winning_boards_table[NUM_ROWS][NUM_COLUMNS]; 21 22void init_board(BoardState state) 23{ 24 for (uint8_t col = 0; col < NUM_COLUMNS; ++col) 25 { 26 state[col] = 1; 27 } 28} 29 30inline int8_t get_board_value(BoardState state, uint8_t row, uint8_t col) 31{ 32 assert(row < NUM_ROWS && col < NUM_COLUMNS); 33 34 return state[col] < (1 << (row + 1)) ? -1 : ((state[col] & (1 << row)) >> row); 35} 36 37int8_t make_move_column(uint8_t player, uint8_t* column_state) 38{ 39 if (*column_state >= MIN_FULL_COLUMN) 40 return -1; 41 42 uint8_t mask = *column_state; // 0011 0000 (example) 43 mask |= mask >> 1; // 0011 1000 44 mask |= mask >> 2; // 0011 1110 45 mask |= mask >> 4; // 0011 1111 46 ++mask; // 0100 0000 47 48 // add 1 to top of stack 49 *column_state |= mask; // 0111 0000 50 if (player == 0) 51 { 52 // clear top piece of the stack for player 0 53 *column_state &= ~(mask >> 1); // 0101 0000 54 } 55 56 switch (mask) 57 { 58 case 0b00000010: 59 return 0; 60 case 0b00000100: 61 return 1; 62 case 0b00001000: 63 return 2; 64 case 0b00010000: 65 return 3; 66 case 0b00100000: 67 return 4; 68 case 0b01000000: 69 return 5; 70 default: 71 assert(false); 72 return -1; 73 } 74} 75 76inline binary_board bit_set(binary_board num, uint8_t row, uint8_t col) 77{ 78 return num | ((binary_board)1 << (col * 8 + (row))); 79} 80 81void init_core() 82{ 83 SDL_Init(0); 84 85 // Building make_move partial lookup table (skips full columns) 86 for (uint8_t player = 0; player < 2; ++player) 87 { 88 make_move_table_partial[player][0] = MakeMoveResult { -1, 0 }; 89 for (uint8_t column_state = 1; column_state < MAKE_MOVE_TABLE_PARTIAL_SIZE; ++column_state) 90 { 91 MakeMoveResult result; 92 result.column_state = column_state; 93 result.row = make_move_column(player, &result.column_state); 94 make_move_table_partial[player][column_state] = result; 95 } 96 } 97 98 // Building make_move full lookup table (includes full columns) 99 for (uint8_t player = 0; player < 2; ++player) 100 { 101 make_move_table_full[player][0] = MakeMoveResult { -1, 0 }; 102 for (uint8_t column_state = 1; column_state < MAKE_MOVE_TABLE_FULL_SIZE; ++column_state) 103 { 104 MakeMoveResult result; 105 result.column_state = column_state; 106 result.row = make_move_column(player, &result.column_state); 107 make_move_table_full[player][column_state] = result; 108 } 109 } 110 111 // Building check_for_win processed column lookup table 112 for (uint8_t player = 0; player < 2; ++player) 113 { 114 check_for_win_processed_column_table[player][0] = 0; 115 for (uint8_t in_column = 1; in_column < CHECK_FOR_WIN_PROCESSED_COLUMN_TABLE_SIZE; ++in_column) 116 { 117 // Remove top-of-stack bit 118 uint8_t mask = in_column; // 0011 0000 (example) 119 mask |= mask >> 1; // 0011 1000 120 mask |= mask >> 2; // 0011 1110 121 mask |= mask >> 4; // 0011 1111 122 mask >>= 1; // 0001 1111 123 124 uint8_t out_column = in_column; 125 if (player == 0) 126 { 127 out_column = ~out_column; // 1100 1111 128 out_column &= mask; // 0000 1111 129 } 130 else 131 { 132 out_column &= mask; // 0001 0000 133 } 134 135 check_for_win_processed_column_table[player][in_column] = out_column; 136 } 137 } 138 139 // Building check_for_win winning board lookup table 140 for (uint8_t table_row = 0; table_row < NUM_ROWS; ++table_row) 141 { 142 for (uint8_t table_col = 0; table_col < NUM_COLUMNS; ++table_col) 143 { 144 binary_board boards[32]; 145 uint8_t num_boards = 0; 146 147 // Vertical 148 for (uint8_t row_start = 0; row_start <= (NUM_ROWS - NUM_TO_WIN); ++row_start) 149 { 150 binary_board winning_board_state = 0; 151 for (uint8_t row_offset = 0; row_offset < NUM_TO_WIN; ++row_offset) 152 { 153 winning_board_state = bit_set(winning_board_state, row_start + row_offset, table_col); 154 } 155 156 boards[num_boards] = winning_board_state; 157 ++num_boards; 158 } 159 160 // Horizontal 161 for (uint8_t col_start = 0; col_start <= (NUM_COLUMNS - NUM_TO_WIN); ++col_start) 162 { 163 binary_board winning_board_state = 0; 164 for (uint8_t col_offset = 0; col_offset < NUM_TO_WIN; ++col_offset) 165 { 166 winning_board_state = bit_set(winning_board_state, table_row, col_start + col_offset); 167 } 168 169 boards[num_boards] = winning_board_state; 170 ++num_boards; 171 } 172 173 // Diagonal (bottom-left to top-right) 174 { 175 uint8_t diag_row = table_row; 176 uint8_t diag_col = table_col; 177 while (diag_row > 0 && diag_col > 0) 178 { 179 --diag_row; 180 --diag_col; 181 } 182 183 while (diag_row + NUM_TO_WIN <= NUM_ROWS && diag_col + NUM_TO_WIN <= NUM_COLUMNS) 184 { 185 binary_board winning_board_state = 0; 186 for (uint8_t i = 0; i < NUM_TO_WIN; ++i) 187 { 188 winning_board_state = bit_set(winning_board_state, diag_row + i, diag_col + i); 189 } 190 191 boards[num_boards] = winning_board_state; 192 ++num_boards; 193 194 ++diag_row; 195 ++diag_col; 196 } 197 } 198 199 // Diagonal (top-left to bottom-right) 200 { 201 int8_t diag_row = table_row; 202 uint8_t diag_col = table_col; 203 while (diag_row + 1 < NUM_ROWS && diag_col > 0) 204 { 205 ++diag_row; 206 --diag_col; 207 } 208 209 while ((diag_row + 1) - NUM_TO_WIN >= 0 && diag_col + NUM_TO_WIN <= NUM_COLUMNS) 210 { 211 binary_board winning_board_state = 0; 212 for (uint8_t i = 0; i < NUM_TO_WIN; ++i) 213 { 214 winning_board_state = bit_set(winning_board_state, diag_row - i, diag_col + i); 215 } 216 217 boards[num_boards] = winning_board_state; 218 ++num_boards; 219 220 --diag_row; 221 ++diag_col; 222 } 223 } 224 225 WinningBoardCollection board_collection; 226 board_collection.num_boards = num_boards; 227 board_collection.boards = (binary_board*)malloc(num_boards * sizeof(binary_board)); 228 memcpy(board_collection.boards, boards, num_boards * sizeof(binary_board)); 229 230 check_for_win_processed_winning_boards_table[table_row][table_col] = board_collection; 231 } 232 } 233} 234 235int8_t make_move(uint8_t player, uint8_t column, BoardState state) 236{ 237 assert(column >= 0 && column < NUM_COLUMNS); 238 239 return make_move_column(player, &state[column]); 240} 241 242bool check_for_win(BoardState state, uint8_t last_move_player, uint8_t last_move_row, uint8_t last_move_col) 243{ 244 assert(last_move_player <= 1); 245 assert(last_move_row < NUM_ROWS && last_move_col < NUM_COLUMNS); 246 247 uint8_t processed_board_state[NUM_COLUMNS_PROCESSED] = { 0 }; 248 for (uint8_t col = 0; col < NUM_COLUMNS; ++col) 249 { 250 assert(state[col] < MAX_FULL_COLUMN + 1); 251 processed_board_state[col] = check_for_win_processed_column_table[last_move_player][state[col]]; 252 } 253 254 binary_board* processed_board_state_binary = (binary_board*)processed_board_state; 255 256 WinningBoardCollection winning_boards = check_for_win_processed_winning_boards_table[last_move_row][last_move_col]; 257 for (uint8_t i = 0; i < winning_boards.num_boards; ++i) 258 { 259 binary_board winning_board_state = winning_boards.boards[i]; 260 if ((winning_board_state & *processed_board_state_binary) == winning_board_state) 261 { 262 return true; 263 } 264 } 265 266 return false; 267} 268 269uint64_t total_moves_evaluated; 270uint64_t wins_found; 271uint64_t dead_ends_found; 272uint8_t min_depth; 273uint64_t time_of_last_print; 274uint64_t get_move_score_start_time; 275 276struct MoveEvalNode 277{ 278 uint8_t curr_move_column = 0; 279 BoardState state; 280 int64_t score = 0; 281}; 282 283void get_move_scores(int64_t final_scores[NUM_COLUMNS]) 284{ 285 MoveEvalNode nodes[44]; 286 init_board(nodes[0].state); 287 288 get_move_score_start_time = SDL_GetTicks(); 289 time_of_last_print = get_move_score_start_time; 290 total_moves_evaluated = 0; 291 wins_found = 0; 292 dead_ends_found = 0; 293 min_depth = 50; 294 295 int8_t depth = 0; 296 uint8_t player = 0; 297 uint8_t player_this_turn = 0; 298 while (depth >= 0) 299 { 300 // Print status update every 100 ms. 301 uint64_t time_current = SDL_GetTicks(); 302 if (time_current - time_of_last_print >= 1000) 303 { 304 print_board(nodes[depth].state); 305 306 time_of_last_print = time_current; 307 308 double_t percent_completed = 100.0 * total_moves_evaluated / 4531985219092; 309 uint64_t time_elapsed = time_current - get_move_score_start_time; 310 double_t time_to_complete = (time_elapsed / 1000.0) * (100.0 / percent_completed); 311 312 printf("Total moves: %lu (%f%%), wins found: %lu, dead-ends found: %lu, depth: %d\n", total_moves_evaluated, percent_completed, wins_found, dead_ends_found, min_depth); 313 314 char friendly_time_string[256]; 315 sprint_friendly_time(time_to_complete, friendly_time_string); 316 printf("- Time to complete: %s\n", friendly_time_string); 317 } 318 319 // Make move from current node to next node, based on curr_move_column. 320 memcpy(nodes[depth + 1].state, nodes[depth].state, sizeof(BoardState)); 321 int8_t row = make_move_lookup_full(player_this_turn, nodes[depth].curr_move_column, nodes[depth + 1].state); 322 323 bool move_to_next_column = false; 324 325 if (row < 0) 326 { 327 // Move not possible. 328 ++dead_ends_found; 329 330 move_to_next_column = true; 331 } 332 else 333 { 334 ++total_moves_evaluated; 335 if (check_for_win(nodes[depth + 1].state, player_this_turn, row, nodes[depth].curr_move_column)) 336 { 337 ++wins_found; 338 339 // Dumb little score function just to get the functionality right. 340 nodes[depth].score += player_this_turn == player ? 1 : -1; 341 342 move_to_next_column = true; 343 } 344 else 345 { 346 // If this is not a win or dead end, we'll move down to the next node. 347 ++depth; 348 nodes[depth].curr_move_column = 0; 349 nodes[depth].score = 0; 350 player_this_turn = 1 - player_this_turn; 351 } 352 } 353 354 // Move to next column, and backtrack if needed. 355 if (move_to_next_column) 356 { 357 // Next column. 358 ++nodes[depth].curr_move_column; 359 360 // If we've checked every column... 361 while (nodes[depth].curr_move_column >= NUM_COLUMNS) 362 { 363 min_depth = depth < min_depth ? depth : min_depth; 364 365 // Go back up a node (and exit out if we're going past the top). 366 --depth; 367 if (depth < 0) 368 break; 369 370 // If we're at the top... 371 if (depth == 0) 372 { 373 // Record this move's score. 374 final_scores[nodes[depth].curr_move_column] = nodes[depth + 1].score; 375 } 376 // Otherwise... 377 else 378 { 379 // Add score from child node. 380 nodes[depth].score += nodes[depth + 1].score; 381 } 382 383 // Reset and go to next column. 384 nodes[depth + 1].curr_move_column = 0; 385 nodes[depth + 1].score = 0; 386 ++nodes[depth].curr_move_column; 387 player_this_turn = 1 - player_this_turn; 388 } 389 } 390 } 391} 392 393void sprint_board(BoardState state, char* buffer) 394{ 395 for (uint8_t i = 0; i < NUM_COLUMNS; ++i) 396 { 397 snprintf(buffer, 256, "_%d", i + 1); 398 buffer += 2; 399 } 400 401 snprintf(buffer, 256, "_\n"); 402 buffer += 2; 403 for (int8_t row = NUM_ROWS - 1; row >= 0; --row) 404 { 405 snprintf(buffer, 256, "|"); 406 buffer += 1; 407 for (uint8_t col = 0; col < NUM_COLUMNS; ++col) 408 { 409 switch (get_board_value(state, row, col)) 410 { 411 case 0: 412 snprintf(buffer, 256, "O"); 413 buffer += 1; 414 break; 415 case 1: 416 snprintf(buffer, 256, "X"); 417 buffer += 1; 418 break; 419 default: 420 snprintf(buffer, 256, " "); 421 buffer += 1; 422 break; 423 } 424 425 snprintf(buffer, 256, "|"); 426 buffer += 1; 427 } 428 snprintf(buffer, 256, "\n"); 429 buffer += 1; 430 } 431 for (uint8_t i = 0; i < NUM_COLUMNS; ++i) 432 { 433 snprintf(buffer, 256, "--"); 434 buffer += 2; 435 } 436 snprintf(buffer, 256, "\n"); 437} 438 439void print_board(BoardState state) 440{ 441 char* board = (char*)malloc(sizeof(char) * 256); 442 sprint_board(state, board); 443 printf("%s", board); 444 free(board); 445}