-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate agents from game Logic (#30)
The C source file is split into several ones to facilitate the addition of new agents. Several changes were implemented to achieve this: 1. 'move_t' was introduced to transform negamax into a dedicated func. 2. qsort was relocated from available_moves to incorporate it into the game logic. The readability of the code also increased with the following changes: 1. 'table' and 'move_record' were declared as static arrays. 2. The condition is_consecutive(score, -1) was replaced with score < 0, and vice versa.
- Loading branch information
Showing
6 changed files
with
242 additions
and
229 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
#include <assert.h> | ||
#include <ctype.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "game.h" | ||
#include "negamax.h" | ||
|
||
#define MAX_SEARCH_DEPTH 6 | ||
|
||
static int history_score_sum[N_GRIDS]; | ||
static int history_count[N_GRIDS]; | ||
|
||
static int cmp_moves(const void *a, const void *b) | ||
{ | ||
int *_a = (int *) a, *_b = (int *) b; | ||
int score_a = 0, score_b = 0; | ||
|
||
if (history_count[*_a]) | ||
score_a = history_score_sum[*_a] / history_count[*_a]; | ||
if (history_count[*_b]) | ||
score_b = history_score_sum[*_b] / history_count[*_b]; | ||
return score_b - score_a; | ||
} | ||
|
||
static int eval_line_segment_score(const char *table, | ||
char player, | ||
int i, | ||
int j, | ||
line_t line) | ||
{ | ||
int score = 0; | ||
for (int k = 0; k < GOAL; k++) { | ||
char curr = | ||
table[GET_INDEX(i + k * line.i_shift, j + k * line.j_shift)]; | ||
if (curr == player) { | ||
if (score < 0) | ||
return 0; | ||
if (score) | ||
score *= 10; | ||
else | ||
score = 1; | ||
} else if (curr != ' ') { | ||
if (score > 0) | ||
return 0; | ||
if (score) | ||
score *= 10; | ||
else | ||
score = -1; | ||
} | ||
} | ||
return score; | ||
} | ||
|
||
static int get_score(const char *table, char player) | ||
{ | ||
int score = 0; | ||
for (int i_line = 0; i_line < 4; ++i_line) { | ||
line_t line = lines[i_line]; | ||
for (int i = line.i_lower_bound; i < line.i_upper_bound; ++i) { | ||
for (int j = line.j_lower_bound; j < line.j_upper_bound; ++j) { | ||
score += eval_line_segment_score(table, player, i, j, line); | ||
} | ||
} | ||
} | ||
return score; | ||
} | ||
|
||
static move_t negamax(char *table, int depth, char player, int alpha, int beta) | ||
{ | ||
if (check_win(table) != ' ' || depth == 0) { | ||
move_t result = {get_score(table, player), -1}; | ||
return result; | ||
} | ||
|
||
int score; | ||
move_t best_move = {-10000, -1}; | ||
int *moves = available_moves(table); | ||
int n_moves = 0; | ||
while (n_moves < N_GRIDS && moves[n_moves] != -1) | ||
++n_moves; | ||
qsort(moves, n_moves, sizeof(int), cmp_moves); | ||
for (int i = 0; i < n_moves; i++) { | ||
table[moves[i]] = player; | ||
if (!i) // do a full search on the first move | ||
score = -negamax(table, depth - 1, player == 'X' ? 'O' : 'X', -beta, | ||
-alpha) | ||
.score; | ||
else { | ||
// do a null-window search on the rest of the moves | ||
score = -negamax(table, depth - 1, player == 'X' ? 'O' : 'X', | ||
-alpha - 1, -alpha) | ||
.score; | ||
if (alpha < score && score < beta) // do a full re-search | ||
score = -negamax(table, depth - 1, player == 'X' ? 'O' : 'X', | ||
-beta, -score) | ||
.score; | ||
} | ||
history_count[moves[i]]++; | ||
history_score_sum[moves[i]] += score; | ||
if (score > best_move.score) { | ||
best_move.score = score; | ||
best_move.move = moves[i]; | ||
} | ||
table[moves[i]] = ' '; | ||
if (score > alpha) | ||
alpha = score; | ||
if (alpha >= beta) | ||
break; | ||
} | ||
|
||
free((char *) moves); | ||
return best_move; | ||
} | ||
|
||
move_t negamax_predict(char *table, char player) | ||
{ | ||
memset(history_score_sum, 0, sizeof(history_score_sum)); | ||
memset(history_count, 0, sizeof(history_count)); | ||
move_t result; | ||
for (int depth = 2; depth <= MAX_SEARCH_DEPTH; depth += 2) | ||
result = negamax(table, depth, player, -100000, 100000); | ||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#pragma once | ||
|
||
typedef struct { | ||
int score, move; | ||
} move_t; | ||
|
||
move_t negamax_predict(char *table, char player); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#include <assert.h> | ||
#include <ctype.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "game.h" | ||
|
||
#define LOOKUP(table, i, j, else_value) \ | ||
((i) < 0 || (j) < 0 || (i) > BOARD_SIZE || (j) > BOARD_SIZE \ | ||
? (else_value) \ | ||
: (table)[GET_INDEX(i, j)]) | ||
|
||
_Static_assert(BOARD_SIZE <= 26, "Board size must not be greater than 26"); | ||
_Static_assert(BOARD_SIZE > 0, "Board size must be greater than 0"); | ||
_Static_assert(GOAL <= BOARD_SIZE, "Goal must not be greater than board size"); | ||
_Static_assert(GOAL > 0, "Goal must be greater than 0"); | ||
_Static_assert(ALLOW_EXCEED == 0 || ALLOW_EXCEED == 1, | ||
"ALLOW_EXCEED must be a boolean that is 0 or 1"); | ||
|
||
const line_t lines[4] = { | ||
{1, 0, 0, 0, BOARD_SIZE - GOAL + 1, BOARD_SIZE}, // ROW | ||
{0, 1, 0, 0, BOARD_SIZE, BOARD_SIZE - GOAL + 1}, // COL | ||
{1, 1, 0, 0, BOARD_SIZE - GOAL + 1, BOARD_SIZE - GOAL + 1}, // PRIMARY | ||
{1, -1, 0, GOAL - 1, BOARD_SIZE - GOAL + 1, BOARD_SIZE}, // SECONDARY | ||
}; | ||
|
||
static char check_line_segment_win(const char *t, int i, int j, line_t line) | ||
{ | ||
char last = t[GET_INDEX(i, j)]; | ||
if (last == ' ') | ||
return ' '; | ||
for (int k = 1; k < GOAL; k++) { | ||
if (last != t[GET_INDEX(i + k * line.i_shift, j + k * line.j_shift)]) { | ||
return ' '; | ||
} | ||
} | ||
#if !ALLOW_EXCEED | ||
if (last == LOOKUP(t, i - line.i_shift, j - line.j_shift, ' ') || | ||
last == | ||
LOOKUP(t, i + GOAL * line.i_shift, j + GOAL * line.j_shift, ' ')) | ||
return ' '; | ||
#endif | ||
return last; | ||
} | ||
|
||
char check_win(char *t) | ||
{ | ||
for (int i_line = 0; i_line < 4; ++i_line) { | ||
line_t line = lines[i_line]; | ||
for (int i = line.i_lower_bound; i < line.i_upper_bound; ++i) { | ||
for (int j = line.j_lower_bound; j < line.j_upper_bound; ++j) { | ||
char win = check_line_segment_win(t, i, j, line); | ||
if (win != ' ') | ||
return win; | ||
} | ||
} | ||
} | ||
for (int i = 0; i < N_GRIDS; i++) | ||
if (t[i] == ' ') | ||
return ' '; | ||
return 'D'; | ||
} | ||
|
||
int *available_moves(char *table) | ||
{ | ||
int *moves = malloc(N_GRIDS * sizeof(int)); | ||
int m = 0; | ||
for (int i = 0; i < N_GRIDS; i++) | ||
if (table[i] == ' ') | ||
moves[m++] = i; | ||
if (m < N_GRIDS) | ||
moves[m] = -1; | ||
return moves; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#pragma once | ||
|
||
#define BOARD_SIZE 4 | ||
#define GOAL 3 | ||
#define ALLOW_EXCEED 1 | ||
#define N_GRIDS (BOARD_SIZE * BOARD_SIZE) | ||
#define GET_INDEX(i, j) ((i) * (BOARD_SIZE) + (j)) | ||
#define GET_COL(x) ((x) % BOARD_SIZE) | ||
#define GET_ROW(x) ((x) / BOARD_SIZE) | ||
|
||
typedef struct { | ||
int i_shift, j_shift; | ||
int i_lower_bound, j_lower_bound, i_upper_bound, j_upper_bound; | ||
} line_t; | ||
|
||
extern const line_t lines[4]; | ||
|
||
int *available_moves(char *table); | ||
char check_win(char *t); |
Oops, something went wrong.