tictactoe

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 }