From cb4587847dcec8465cf165c458f99b705f88aaa8 Mon Sep 17 00:00:00 2001 From: woosh <> Date: Thu, 16 Nov 2023 05:07:16 +0000 Subject: [PATCH] minimax with alpha-beta pruning --- uttt.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/uttt.c b/uttt.c index ed97bc6..850a4a7 100644 --- a/uttt.c +++ b/uttt.c @@ -39,14 +39,16 @@ struct ut_game }; const char HELP_TEXT[] = "\ -Usage: uttt (--local | --host | --join)\n\ +Usage: uttt (--local | --agent | --host | --join)\n\ \n\ - --local play non-network game\n\ + --local play local game\n\ + --agent play against AI agent\n\ --host host game at 127.0.0.1:6669\n\ --join join game at 127.0.0.1:6669\n\ "; const char arg_local[] = "--local"; +const char arg_agent[] = "--agent"; const char arg_host[] = "--host"; const char arg_join[] = "--join"; @@ -676,6 +678,46 @@ int ut_join_game(struct ut_state *state, struct ut_game *game) { return ut_network_game(state, game, sock, *player, false); } +int ut_agentgetmove(const struct ut_state *state, const struct ut_game *game, struct ut_move *move); +int ut_agent_game(struct ut_state *state, struct ut_game *game) { + char player; + if ((player = ut_random_player()) < 0) {return 1;} + for(;;) + { + struct ut_move m = {4, 4}; + ut_curserase(); + ut_cursdraw(state, game); + char w = ut_winner(state); + if(w) + { + if(w == ' ') + { + ut_cursprintf("Draw!\n"); + } + else + { + ut_cursprintf("%c wins!\n", (int)w); + } + break; + } + if (state->player == player) { + if(ut_cursgetmove(state, &m)) {continue;} + } else { + ut_cursprintf("Waiting for AI agent ...\n"); + refresh(); + if(ut_agentgetmove(state, game, &m)) + { + ut_cursprintf("Agent failed - exiting\n"); + return 1; + } + } + if(ut_move(state, state, m)) {continue;} + game->moves[game->l++] = m; + } + return 0; +} + + void finish(int sig) { // putchar('\n'); @@ -718,6 +760,9 @@ int main(int argc, char **argv) { } else if (strncmp(argv[1], arg_local, sizeof(arg_local)) == 0) { begin(); waitfinish(ut_local_game(&state, &game)); + } else if (strncmp(argv[1], arg_agent, sizeof(arg_agent)) == 0) { + begin(); + waitfinish(ut_agent_game(&state, &game)); } else if (strncmp(argv[1], arg_host, sizeof(arg_host)) == 0) { begin(); waitfinish(ut_host_game(&state, &game)); @@ -730,3 +775,91 @@ int main(int argc, char **argv) { } return 0; } + + +int ut_boardValue(const char *tiles, int offset, int stride) +{ + int v = 0; + for(int r = 0; r < 3; r++) + for(int c = 0; c < 3; c++) + v += (T(r, c) == 'X') ? 1 : (T(r, c) == 'O') ? -1 : 0; + return v; +} +#define UT_VALUEMAX 81 +#define UT_VALUEMIN (-81) +int ut_value(const struct ut_state *state) +{ + int v = 0; + char winner = ut_winner(state); + if(winner != '\0') + { + return (winner == 'X') ? 81 : (winner == 'O') ? -81 : 0; + } + for(int r = 0; r < 3; r++) + for(int c = 0; c < 3; c++) + v += (state->boards[r][c] == '\0') ? ut_boardValue((char *)state->tiles, 27 * r + 3 * c, 9) : + (state->boards[r][c] == 'X') ? 9 : (state->boards[r][c] == 'O') ? -9 : 0; + return v; +} + +int ut_alphabetaq(const struct ut_state *state, struct ut_move move, int depth, int a, int b) +{ + struct ut_state next; + if(ut_move(&next, state, move)) {return (state->player == 'X') ? UT_VALUEMIN - 1 : UT_VALUEMAX + 1;} + + int q = ut_value(&next); + if(depth <= 0 || q <= UT_VALUEMIN || q >= UT_VALUEMAX) {return q;} + + + if(next.player == 'X') + { + q = UT_VALUEMIN - 1; + for(int r = 0; r < 9; r++) + for(int c = 0; c < 9; c++) + { + struct ut_move next_move = (struct ut_move){r, c}; + int next_q = ut_alphabetaq(&next, next_move, depth - 1, a, b); + if(next_q > q) {q = next_q;} + if(q > b) {return q;} + a = q > a ? q : a; + } + } + else + { + q = UT_VALUEMAX + 1; + for(int r = 0; r < 9; r++) + for(int c = 0; c < 9; c++) + { + struct ut_move next_move = (struct ut_move){r, c}; + int next_q = ut_alphabetaq(&next, next_move, depth - 1, a, b); + if(next_q < q) {q = next_q;} + if(q < a) {return q;} + b = q < b ? q : b; + } + } + return q; +} +#define UT_DEPTH 5 +struct ut_move ut_minimax(const struct ut_state *state) +{ + struct ut_move best_move = {-1, -1}; + int best_q = (state->player == 'X') ? UT_VALUEMIN - 1 : UT_VALUEMAX + 1; + for(int r = 0; r < 9; r++) + for(int c = 0; c < 9; c++) + { + struct ut_move move = (struct ut_move){r, c}; + int q = ut_alphabetaq(state, move, UT_DEPTH, UT_VALUEMIN - 1, UT_VALUEMAX + 1); + if((state->player == 'X') ? q > best_q : q < best_q) + { + best_q = q; + best_move = move; + } + } + return best_move; +} + +int ut_agentgetmove(const struct ut_state *state, const struct ut_game *game, struct ut_move *move) +{ + *move = ut_minimax(state); + return (move->r < 0 || move->r >= 9 || move->c < 0 || move->c >= 9); +}