tictactoe.h (8985B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 #include "./print_box.h" 7 8 #define SCRATCH_BOARD "XXOOOXXOX" 9 #define X_WINS_COLUMN "XXOXOOXOX" 10 #define X_WINS_DIAG "XOO XOOXX" 11 #define X_WINS_ROW " OOOXOXXX" 12 #define O_WINS_COLUMN "OOXOXXOX " 13 #define O_WINS_DIAG "OXXOOXX O" 14 #define O_WINS_ROW " XXXOXOOO" 15 16 #define scase(s1, s2) if (!strcmp(s1, s2)) 17 18 #define OPPONENT (player == 'O' ? 'X' : 'O') 19 20 static char the_desired_board[32]; 21 static _Bool srand_seeded = 0; 22 struct move { int move_ind; char winner; }; 23 24 static int get_rand(int max_excl) { 25 #ifndef UNIT_TEST 26 FILE *urandom = fopen("/dev/urandom", "r"); 27 if(urandom) { 28 int rnd = 0; 29 size_t z = fread(&rnd, sizeof(int), 1, urandom); 30 fclose(urandom); 31 if(z) { 32 if(rnd < 0) rnd *= -1; 33 return rnd % max_excl; 34 } 35 } 36 #endif 37 /* fallback if somehow we can't open urandom or read 4 bytes from it */ 38 if (!srand_seeded) { 39 time_t seed = time(NULL); 40 /* save seed for later in case of bugs, so they're reproducible */ 41 FILE *seed_file = fopen("seed", "w"); 42 if(seed_file) { 43 fprintf(seed_file, "%ld\n", seed); 44 fclose(seed_file); 45 } 46 srand(seed); 47 srand_seeded = 1; 48 } 49 return rand() % max_excl; 50 } 51 char get_winner_or_winning_moves(char *b, char *winning_moves) { 52 char center = b[4]; 53 _Bool add_to_wm = 0; 54 /* Check status of squares on either side of center: row, column and diagonal */ 55 for(int i=-4; i < 0; i++) { 56 int sq1 = 4+i; 57 int sq3 = 4-i; 58 if(b[sq1] == b[sq3] && b[sq1] != ' ') { 59 if(b[sq1] == center) 60 return center; 61 if(center == ' ') 62 add_to_wm = 1; 63 } 64 if(winning_moves == NULL) continue; 65 if(add_to_wm) { 66 /* Normally means `w...[4] = b[s...]`, but allows us 67 * to detect later on if it is a winning move for both */ 68 winning_moves[4] |= b[sq1]; 69 add_to_wm = 0; 70 } 71 else if(center == ' ') continue; 72 else if((b[sq1] == center) ^ (b[sq3] == center)) { 73 if(b[sq1] == ' ') 74 winning_moves[sq1] |= center; 75 else if(b[sq3] == ' ') 76 winning_moves[sq3] |= center; 77 } 78 } 79 for(int i=1; i <= 7; i+=2){ 80 /* (i+1)&4 -> side column, else upper or lower row */ 81 int distance = ((i+1) & 4) ? 3 : 1; 82 int sq1 = i - distance; 83 int sq3 = i + distance; 84 if(b[sq1] == b[sq3] && b[sq1] != ' ') { 85 if(b[sq1] == b[i]) 86 return b[i]; 87 if(b[i] == ' ') 88 add_to_wm = 1; 89 } 90 if(winning_moves == NULL) continue; 91 if(add_to_wm) { 92 winning_moves[i] |= b[sq1]; 93 add_to_wm = 0; 94 } 95 else if(b[i] == ' ') continue; 96 else if((b[sq1] == b[i]) ^ (b[sq3] == b[i])) { 97 if(b[sq1] == ' ') 98 winning_moves[sq1] |= b[i]; 99 else if(b[sq3] == ' ') 100 winning_moves[sq3] |= b[i]; 101 } 102 } 103 return 0; 104 } 105 106 void set_board(char *board_name) { 107 scase(board_name, "scratch_board") 108 strcpy(the_desired_board, SCRATCH_BOARD); 109 scase(board_name, "x_wins_column") 110 strcpy(the_desired_board, X_WINS_COLUMN); 111 scase(board_name, "x_wins_diag") 112 strcpy(the_desired_board, X_WINS_DIAG); 113 scase(board_name, "x_wins_row") 114 strcpy(the_desired_board, X_WINS_ROW); 115 scase(board_name, "o_wins_column") 116 strcpy(the_desired_board, O_WINS_COLUMN); 117 scase(board_name, "o_wins_diag") 118 strcpy(the_desired_board, O_WINS_DIAG); 119 scase(board_name, "o_wins_row") 120 strcpy(the_desired_board, O_WINS_ROW); 121 } 122 123 char get_move_preset_board(char *board, char player) { 124 char *desired_board = the_desired_board; 125 for(int i=0; i < 9; i++) { 126 if(desired_board[i] == player && board[i] == ' ') { 127 board[i] = player; 128 return get_winner_or_winning_moves(board, NULL); 129 } 130 } 131 return 0; 132 } 133 char get_these_moves(char *board, char player) { 134 /* Automate any specific sequence of moves, for debugging */ 135 static int moves[9] = {1, 2, 5, 6, 7,}; 136 static int m = 0; 137 board[moves[m++]] = player; 138 return 0; 139 } 140 char get_move_from_human(char *board, char player) { 141 int n; 142 print_msg("%c's move? [1-9]: ", player); 143 tb_present(); 144 scanf("%d", &n); 145 board[n-1] = player; 146 return get_winner_or_winning_moves(board, NULL); 147 } 148 static int get_legal_moves(char *board, int legal_moves[]) { 149 int lm_ind = 0; 150 for(int i=0; i < 9; i++) 151 if(board[i] == ' ') 152 legal_moves[lm_ind++] = i; 153 return lm_ind; 154 } 155 char get_random_move(char *board, char player) { 156 int legal_moves[9]; 157 int lm_cnt = get_legal_moves(board, legal_moves); 158 board[legal_moves[get_rand(lm_cnt)]] = player; 159 return get_winner_or_winning_moves(board, NULL); 160 } 161 static char try_make_winning_move(char *board, char player, _Bool block_opponent) { 162 char all_winning_moves[10] = ""; 163 get_winner_or_winning_moves(board, all_winning_moves); 164 int my_winning_moves_inds[5], his_winning_moves_inds[5]; 165 int mwm_ind_ind = 0, hwm_ind_ind = 0; 166 for(int i=0; i < 9; i++) { 167 /* Get all possible winning moves, including optionally the opponent's; 168 * add indices of each to an array ... */ 169 if((all_winning_moves[i] & player) == player) 170 my_winning_moves_inds[mwm_ind_ind++] = i; 171 else if(block_opponent && all_winning_moves[i] == OPPONENT) 172 his_winning_moves_inds[hwm_ind_ind++] = i; 173 } 174 /* ...then choose one at random, mine if possible, else the opponent's (if opted for) */ 175 if(mwm_ind_ind) { 176 board[my_winning_moves_inds[get_rand(mwm_ind_ind)]] = player; 177 return player; 178 } 179 if(hwm_ind_ind) { 180 board[his_winning_moves_inds[get_rand(hwm_ind_ind)]] = player; 181 /* N.B. This will not be reached if the move is a winning move for both players */ 182 return 0; 183 } 184 return get_random_move(board, player); 185 } 186 char get_winning_move_else_random(char *board, char player) { 187 return try_make_winning_move(board, player, 0); 188 } 189 char get_winning_move_else_block_else_random(char *board, char player) { 190 return try_make_winning_move(board, player, 1); 191 } 192 static struct move minimax_best_move(char *board, char player, int *legal_moves, int lm_cnt) { 193 char winning_moves[10] = ""; 194 int i; 195 get_winner_or_winning_moves(board, winning_moves); 196 int his_wm_cnt = 0; 197 int blocking_move = -1; 198 for(i=0; i < 9; i++) { 199 /* As good as won */ 200 if((winning_moves[i] & player) == player) { 201 struct move m = {.move_ind = i, .winner = player}; 202 return m; 203 } 204 if(winning_moves[i] == OPPONENT) { 205 his_wm_cnt++; 206 blocking_move = i; 207 } 208 } 209 if(lm_cnt <= 2) { 210 /* If there's <= 2 moves remaining, and no opportunity 211 * to win next turn, it's a draw. If the opponent has 212 * exactly one winning move, we will block it. 213 * If more than one, opponent wins. */ 214 struct move m; 215 m.winner = his_wm_cnt > 1 ? OPPONENT : 0; 216 m.move_ind = his_wm_cnt > 0 ? blocking_move : legal_moves[0]; 217 return m; 218 } 219 220 struct move *moves = malloc(sizeof(struct move) * lm_cnt); 221 for(i=0; i < lm_cnt; i++) { 222 char new_board[10]; 223 int *new_legal_moves = malloc(sizeof(int) * 9); 224 memcpy(new_board, board, 10); 225 memcpy(new_legal_moves, legal_moves, 9 * sizeof(int)); 226 new_board[legal_moves[i]] = player; 227 /* Just move the array to point to lm...[1] */ 228 if(i == 0) 229 new_legal_moves++; 230 else if(i < lm_cnt - 1) 231 for(int j=i+1; j < lm_cnt; j++) 232 new_legal_moves[j-1] = legal_moves[j]; 233 moves[i] = minimax_best_move(new_board, OPPONENT, new_legal_moves, lm_cnt - 1); 234 /* Just get first definite win */ 235 if(moves[i].winner == player) { 236 struct move ret = {legal_moves[i], player}; 237 free(moves); 238 return ret; 239 } 240 } 241 /* No wins -- try for a draw */ 242 int scratch_moves[9]; 243 int sm_cnt = 0; 244 while(lm_cnt-->0) 245 if(moves[lm_cnt].winner == 0) 246 scratch_moves[sm_cnt++] = legal_moves[lm_cnt]; 247 struct move ret; 248 if(sm_cnt) { 249 ret.winner = 0; 250 ret.move_ind = scratch_moves[get_rand(sm_cnt)]; 251 } 252 else { 253 ret.winner = OPPONENT; 254 ret.move_ind = legal_moves[0]; //whatever 255 } 256 free(moves); 257 return ret; 258 } 259 char get_move_minimax(char *board, char player) { 260 int legal_moves[9]; 261 int lm_cnt = get_legal_moves(board, legal_moves); 262 int best_move = minimax_best_move(board, player, legal_moves, lm_cnt).move_ind; 263 board[best_move] = player; 264 return get_winner_or_winning_moves(board, NULL); 265 }