tictactoe

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

commit 812bd5d20a182ea1b6e3cb5dbc8a06ce7b203106
parent d02b840e2bfabd7475676f9c669a535e7c91f8a5
Author: Wilson Gheen <wilson@wilsonrgheen.com>
Date:   Tue, 20 Dec 2022 16:40:21 -0600

WIP: Added the beginning of an unbeatable minimax AI. Stops moving altogether if it can't win

Diffstat:
Mtictactoe.c | 7++-----
Mtictactoe.h | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 85 insertions(+), 10 deletions(-)

diff --git a/tictactoe.c b/tictactoe.c @@ -1,9 +1,6 @@ #include <stdint.h> #include <stdio.h> -#define TB_IMPL -#include <termbox.h> -#include "../life/print_box.h" #include "tictactoe.h" #define SIDE_BY_SIDE 1 @@ -24,7 +21,7 @@ static void print_board(char *board) { char play(char (*get_move)(char*, char), char (*get_move_2)(char*, char)) { struct tb_event ev; - char board[9] = " "; + char board[10] = " "; char winner = 0; char player = 'O'; if(get_move_2 == NULL) @@ -47,7 +44,7 @@ char play(char (*get_move)(char*, char), char (*get_move_2)(char*, char)) { int main(void) { struct tb_event ev; tb_init(); - char winner = play(get_winning_move_else_block_else_random, NULL); + char winner = play(get_move_minimax, get_move_from_human); if(winner) print_msg("%c wins.", winner); else diff --git a/tictactoe.h b/tictactoe.h @@ -3,6 +3,8 @@ #include <string.h> #include <time.h> +#include "../life/print_box.h" + #define SCRATCH_BOARD "XXOOOXXOX" #define X_WINS_COLUMN "XXOXOOXOX" #define X_WINS_DIAG "XOO XOOXX" @@ -17,15 +19,18 @@ static char the_desired_board[32]; static _Bool srand_seeded = 0; +struct move { int move_ind; char winner; }; static int get_rand(int max_excl) { if (!srand_seeded) { time_t seed = time(NULL); FILE *f = fopen("seed", "w"); if(f) { - fprintf(f, "%d\n", seed); + fprintf(f, "%ld\n", seed); fclose(f); } + else + puts("Failed to open file"); srand(seed); srand_seeded = 1; } @@ -128,17 +133,21 @@ char get_move_from_human(char *board, char player) { board[n-1] = player; return get_winner_or_winning_moves(board, NULL); } -char get_random_move(char *board, char player) { - int legal_moves[9]; +static int get_legal_moves(char *board, int legal_moves[]) { int lm_ind = 0; for(int i=0; i < 9; i++) if(board[i] == ' ') legal_moves[lm_ind++] = i; - board[legal_moves[get_rand(lm_ind)]] = player; + return lm_ind; +} +char get_random_move(char *board, char player) { + int legal_moves[9]; + int lm_cnt = get_legal_moves(board, legal_moves); + board[legal_moves[get_rand(lm_cnt)]] = player; return get_winner_or_winning_moves(board, NULL); } static char try_make_winning_move(char *board, char player, _Bool block_opponent) { - char all_winning_moves[9] = ""; + char all_winning_moves[10] = ""; get_winner_or_winning_moves(board, all_winning_moves); int my_winning_moves_inds[5], his_winning_moves_inds[5]; int mwm_ind_ind = 0, hwm_ind_ind = 0; @@ -168,3 +177,72 @@ char get_winning_move_else_random(char *board, char player) { char get_winning_move_else_block_else_random(char *board, char player) { return try_make_winning_move(board, player, 1); } +static struct move minimax_best_move(char *board, char player, int *legal_moves, int lm_cnt) { + char winning_moves[10] = ""; + get_winner_or_winning_moves(board, winning_moves); + int his_wm_cnt = 0; + int i; + for(i=0; i < 9; i++) { + /* As good as won */ + if((winning_moves[i] & player) == player) { + struct move m = {.move_ind = i, .winner = player}; + return m; + } + /* `player` can't block both moves, so it's a loss */ + if(winning_moves[i] == OPPONENT && ++his_wm_cnt > 1) { + struct move m = {.move_ind = i, .winner = OPPONENT}; + return m; + } + } + i = 0; + for(int empties=0; i < 9; i++) + if(board[i] == ' ' && ++empties > 2) break; + if(i >= 9) { + /* If there's <= 2 moves remaining, and no opportunity + * to win next turn, it's a draw. (If the opponent has + * exactly one winning move, we will block it.) */ + struct move m = {.move_ind = -1, .winner = 0}; + return m; + } + + struct move *moves = malloc(sizeof(struct move) * lm_cnt); + for(i=0; i < lm_cnt; i++) { + char new_board[10]; + int *new_legal_moves = malloc(sizeof(int) * 9); + memcpy(new_board, board, 10); + memcpy(new_legal_moves, legal_moves, 9 * sizeof(int)); + new_board[legal_moves[i]] = player; + /* Just move the array to point to lm...[1] */ + if(i == 0) + new_legal_moves++; + else if(i < lm_cnt - 1) + for(int j=i+1; j < lm_cnt; j++) + new_legal_moves[j-1] = legal_moves[j]; + moves[i] = minimax_best_move(new_board, OPPONENT, new_legal_moves, lm_cnt - 1); + /* Just get first definite win */ + if(moves[i].winner == player) { + struct move ret = {legal_moves[i], player}; + free(moves); + return ret; + } + } + /* No wins -- try for a draw */ + while(lm_cnt-->0) { + if(moves[lm_cnt].winner == 0) { + struct move ret = moves[lm_cnt]; + free(moves); + ret.move_ind = legal_moves[lm_cnt]; + return ret; + } + } + struct move ret = moves[0]; //whatever + free(moves); + return ret; +} +char get_move_minimax(char *board, char player) { + int legal_moves[9]; + int lm_cnt = get_legal_moves(board, legal_moves); + int best_move = minimax_best_move(board, player, legal_moves, lm_cnt).move_ind; + board[best_move] = player; + return get_winner_or_winning_moves(board, NULL); +}