Commit 28a8b459 authored by Arnaud B.'s avatar Arnaud B.

Make negamax method static.

parent 768c92b1
......@@ -18,16 +18,10 @@
with GNOME Four-in-a-row. If not, see <https://www.gnu.org/licenses/>.
*/
private enum Difficulty { EASY, MEDIUM, HARD; }
private uint8 playgame (string moves_until_now)
namespace AI
{
DecisionTree t = new DecisionTree ();
return t.playgame (moves_until_now);
}
private enum Difficulty { EASY, MEDIUM, HARD; }
private class DecisionTree
{
/* Here NEGATIVE_INFINITY is supposed to be the lowest possible value.
Do not forget int16.MIN ≠ - int16.MAX. */
private const int16 POSITIVE_INFINITY = 32000;
......@@ -40,28 +34,16 @@ private class DecisionTree
/* Use plies [level]; EASY/MEDIUM/HARD */
private const uint8 [] plies = { 4, 7, 7 };
/* to mantain the status of the board, to be used by the heuristic function, the top left cell is [0, 0] */
private Player [,] board;
/* next_move_in_column - stores the column number in which next move is to be made */
private uint8 next_move_in_column;
/* stores the difficulty level of the game */
private Difficulty level;
/* Initializes an empty board */
internal DecisionTree ()
{
// sanity init from a string without any move (and with level easy)
init_from_string ("a", out level, out next_move_in_column, out board);
}
/*\
* * internal methods
\*/
/* returns the column number in which the next move has to be made. Returns uint8.MAX if the board is full. */
internal uint8 playgame (string vstr)
internal static uint8 playgame (string vstr)
{
init_from_string (vstr, out level, out next_move_in_column, out board);
Difficulty level;
Player [,] board;
init_from_string (vstr, out level, out board);
/* if AI can win by making a move immediately, make that move */
uint8 temp = immediate_win (Player.OPPONENT, ref board);
......@@ -75,16 +57,19 @@ private class DecisionTree
return temp;
/* call negamax tree on the current state of the board */
negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT);
uint8 next_move_in_column = uint8.MAX;
negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT, level, ref board, ref next_move_in_column);
/* return the column number in which next move should be made */
return next_move_in_column;
}
/* utility function for testing purposes */
internal uint8 playandcheck (string vstr)
internal static uint8 playandcheck (string vstr)
{
init_from_string (vstr, out level, out next_move_in_column, out board);
Difficulty level;
Player [,] board;
init_from_string (vstr, out level, out board);
uint8 temp = immediate_win (Player.OPPONENT, ref board);
if (temp < BOARD_COLUMNS)
......@@ -94,23 +79,13 @@ private class DecisionTree
if (temp < BOARD_COLUMNS)
return temp;
negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT);
/* call negamax tree on the current state of the board */
uint8 next_move_in_column = uint8.MAX;
negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT, level, ref board, ref next_move_in_column);
return next_move_in_column;
}
/* utility function for debugging purposes, prints a snapshot of current status of the board */
internal void print_board ()
{
for (uint8 i = 0; i < BOARD_ROWS; i++)
{
for (uint8 j = 0; j < BOARD_COLUMNS; j++)
stdout.printf ("%d\t", board [i, j]);
stdout.printf ("\n");
}
stdout.printf ("\n");
}
/*\
* * setting defaults
\*/
......@@ -118,11 +93,9 @@ private class DecisionTree
/* vstr is the sequence of moves made until now; */
private static void init_from_string (string vstr,
out Difficulty level,
out uint8 next_move_in_column,
out Player [,] board)
{
set_level (vstr, out level);
next_move_in_column = uint8.MAX;
/* empty board */
board = new Player [BOARD_ROWS, BOARD_COLUMNS];
......@@ -173,7 +146,7 @@ private class DecisionTree
/* Recursively implements a negamax tree in memory with alpha-beta pruning. The function is first called for the root node.
It returns the value of the current node. For nodes at height == 0, the value is determined by a heuristic function. */
private int16 negamax (uint8 height, int16 alpha, int16 beta, Player player)
private static int16 negamax (uint8 height, int16 alpha, int16 beta, Player player, Difficulty level, ref Player [,] board, ref uint8 next_move_in_column)
{
/* base case of recursive function, returns if we have reached the lowest depth of DecisionTree or the board if full */
if (height == 0 || board_full (ref board))
......@@ -192,7 +165,7 @@ private class DecisionTree
int16 max = LESS_THAN_NEGATIVE_INFINITY;
/* Local variable that stores the column number in which next move is to be made.
Initialized with -1 because we do not know the column number yet. */
Initialized with uint8.MAX because we do not know the column number yet. */
uint8 next = uint8.MAX;
for (uint8 column = 0; column < BOARD_COLUMNS; column++)
......@@ -204,7 +177,7 @@ private class DecisionTree
If so, multiply MAX_HEURIST_VALUE by a height factor to avoid closer threats first.
Or, we need to go further down the negamax tree. */
int16 temp = victory (player, column, ref board) ? MAX_HEURIST_VALUE * height
: -1 * negamax (height - 1, -1 * beta, -1 * alpha, player == Player.OPPONENT ? Player.HUMAN : Player.OPPONENT);
: -1 * negamax (height - 1, -1 * beta, -1 * alpha, player == Player.OPPONENT ? Player.HUMAN : Player.OPPONENT, level, ref board, ref next_move_in_column);
unmove (column, ref board);
......@@ -221,7 +194,7 @@ private class DecisionTree
break;
}
/* If it's the root node, asign the value of next to next_move_in_column */
/* hackish: if it's the root node, return the value of the column where to play */
if (height == plies [level])
next_move_in_column = next;
......@@ -350,6 +323,18 @@ private class DecisionTree
return uint8.MAX;
}
/* utility function for debugging purposes, prints a snapshot of current status of the board */
// private static void print_board (ref Player [,] board)
// {
// for (uint8 i = 0; i < BOARD_ROWS; i++)
// {
// for (uint8 j = 0; j < BOARD_COLUMNS; j++)
// stdout.printf ("%d\t", board [i, j]);
// stdout.printf ("\n");
// }
// stdout.printf ("\n");
// }
/*\
* * heuristics
\*/
......
......@@ -355,7 +355,7 @@ private class FourInARow : Gtk.Application
{
vstr [0] = vlevel [ai_level];
playgame_timeout = Timeout.add (COMPUTER_INITIAL_DELAY, () => {
uint8 c = playgame ((string) vstr);
uint8 c = AI.playgame ((string) vstr);
if (c >= BOARD_COLUMNS) // c could be uint8.MAX if board is full
return Source.REMOVE;
process_move ((uint8) c);
......@@ -518,7 +518,7 @@ private class FourInARow : Gtk.Application
{
playgame_timeout = Timeout.add (COMPUTER_MOVE_DELAY, () => {
vstr [0] = vlevel [ai_level];
uint8 col = playgame ((string) vstr);
uint8 col = AI.playgame ((string) vstr);
if (col >= BOARD_COLUMNS) // c could be uint8.MAX if the board is full
set_gameover (true);
var nm = new NextMove ((uint8) col, this);
......@@ -780,7 +780,7 @@ private class FourInARow : Gtk.Application
set_status_message (_("I’m Thinking…"));
vstr [0] = vlevel [/* strong */ 3];
uint8 c = playgame ((string) vstr);
uint8 c = AI.playgame ((string) vstr);
if (c >= BOARD_COLUMNS)
assert_not_reached (); // c could be uint8.MAX if the board if full
......
......@@ -52,99 +52,99 @@ private int main (string [] args)
private static inline void test_horizontal_win ()
{
/*In the first statement below, the AI has made moves into the 1st, 2nd and 3rd columns. To win, AI must move in the 4th column.*/
assert_true (playgame ("a1727370") == 3);
assert_true (playgame ("a7315651311324420") == 5);
assert_true (playgame ("a232225657223561611133440") == 3);
assert_true (playgame ("a242215322574255543341746677453337710") == 0);
assert_true (AI.playgame ("a1727370") == 3);
assert_true (AI.playgame ("a7315651311324420") == 5);
assert_true (AI.playgame ("a232225657223561611133440") == 3);
assert_true (AI.playgame ("a242215322574255543341746677453337710") == 0);
}
/* Tests if the AI makes moves so as to take up immediate vertical wins.*/
private static inline void test_vertical_win ()
{
assert_true (playgame ("a1213140") == 0);
assert_true (playgame ("a14456535526613130") == 0);
assert_true (playgame ("a432334277752576710") == 6);
assert_true (playgame ("a547477454544323321712116260") == 1);
assert_true (AI.playgame ("a1213140") == 0);
assert_true (AI.playgame ("a14456535526613130") == 0);
assert_true (AI.playgame ("a432334277752576710") == 6);
assert_true (AI.playgame ("a547477454544323321712116260") == 1);
}
/* Tests if the AI makes moves so as to take up immediate forward diagonal wins.*/
private static inline void test_forward_diagonal_win ()
{
assert_true (playgame ("a54221164712446211622157570") == 6);
assert_true (playgame ("a4256424426621271412117175776343330") == 2);
assert_true (playgame ("a132565522322662666775443351131113540") == 3);
assert_true (playgame ("a4571311334541225544112245262577767733360") == 5);
assert_true (AI.playgame ("a54221164712446211622157570") == 6);
assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
assert_true (AI.playgame ("a132565522322662666775443351131113540") == 3);
assert_true (AI.playgame ("a4571311334541225544112245262577767733360") == 5);
}
/* Tests if the AI makes moves so as to take up immediate backward diagonal wins.*/
private static inline void test_backward_diagonal_win ()
{
assert_true (playgame ("a5422327343142110") == 0);
assert_true (playgame ("a1415113315143220") == 1);
assert_true (playgame ("a547323452213345110") == 0);
assert_true (playgame ("a4256424426621271412117175776343330") == 2);
assert_true (AI.playgame ("a5422327343142110") == 0);
assert_true (AI.playgame ("a1415113315143220") == 1);
assert_true (AI.playgame ("a547323452213345110") == 0);
assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate vertical victories. Consider that a HUMAN has 3 balls in the
first column. The AI's next move should be in the 1st column or else, HUMAN will claim victory on his next turn.*/
private static inline void test_avoid_vertical_loss ()
{
assert_true (playgame ("a42563117273430") == 2);
assert_true (playgame ("a3642571541322340") == 3);
assert_true (playgame ("a144566264475171137750") == 4);
assert_true (playgame ("a54747745454432332171210") == 0);
assert_true (AI.playgame ("a42563117273430") == 2);
assert_true (AI.playgame ("a3642571541322340") == 3);
assert_true (AI.playgame ("a144566264475171137750") == 4);
assert_true (AI.playgame ("a54747745454432332171210") == 0);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate forward diagonal victories*/
private static inline void test_avoid_forward_diagonal_loss ()
{
assert_true (playgame ("a34256477331566570") == 6);
assert_true (playgame ("a1445662644751711370") == 6);
assert_true (playgame ("a43442235372115113340") == 3);
assert_true (playgame ("a4143525567766443543125411170") == 6);
assert_true (AI.playgame ("a34256477331566570") == 6);
assert_true (AI.playgame ("a1445662644751711370") == 6);
assert_true (AI.playgame ("a43442235372115113340") == 3);
assert_true (AI.playgame ("a4143525567766443543125411170") == 6);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate backward diagonal victories*/
private static inline void test_avoid_backward_diagonal_loss ()
{
assert_true (playgame ("a47465234222530") == 2);
assert_true (playgame ("a4344223537211510") == 0);
assert_true (playgame ("a4141311525513520") == 1);
assert_true (playgame ("a1445662644751711377553330") == 2);
assert_true (AI.playgame ("a47465234222530") == 2);
assert_true (AI.playgame ("a4344223537211510") == 0);
assert_true (AI.playgame ("a4141311525513520") == 1);
assert_true (AI.playgame ("a1445662644751711377553330") == 2);
}
/* Tests if the AI makes moves which prevents HUMAN from taking immediate horizontal victories*/
private static inline void test_avoid_horizontal_loss ()
{
assert_true (playgame ("a445360") == 6);
assert_true (playgame ("a745534131117114777720") == 1);
assert_true (playgame ("a243466431217112323350") == 4);
assert_true (playgame ("a24147356465355111336631615240") == 3);
assert_true (AI.playgame ("a445360") == 6);
assert_true (AI.playgame ("a745534131117114777720") == 1);
assert_true (AI.playgame ("a243466431217112323350") == 4);
assert_true (AI.playgame ("a24147356465355111336631615240") == 3);
}
/* Tests if AI can detect full boards, and thus draw games. */
private static inline void test_draw ()
{
assert_true (playgame ("a1311313113652226667224247766737374455445550") == uint8.MAX);
assert_true (playgame ("a6121151135432322433425566474425617635677770") == uint8.MAX);
assert_true (playgame ("a4226111412113275256335534443264375577676670") == uint8.MAX);
assert_true (playgame ("a4212116575717754775221133434432366655342660") == uint8.MAX);
assert_true (AI.playgame ("a1311313113652226667224247766737374455445550") == uint8.MAX);
assert_true (AI.playgame ("a6121151135432322433425566474425617635677770") == uint8.MAX);
assert_true (AI.playgame ("a4226111412113275256335534443264375577676670") == uint8.MAX);
assert_true (AI.playgame ("a4212116575717754775221133434432366655342660") == uint8.MAX);
}
/* Tests if AI makes valid moves, i.e., between column 1 and column 7. */
private static inline void test_random ()
{
uint8 x = playgame ("a443256214350");
uint8 x = AI.playgame ("a443256214350");
assert_true (x <= 6);
x = playgame ("a241473564653551113366316150");
x = AI.playgame ("a241473564653551113366316150");
assert_true (x <= 6);
x = playgame ("a24357315461711177416622623350");
x = AI.playgame ("a24357315461711177416622623350");
assert_true (x <= 6);
x = playgame ("a1445662644751711377553333665775446110");
x = AI.playgame ("a1445662644751711377553333665775446110");
assert_true (x <= 6);
}
......@@ -165,8 +165,7 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
while (true)
{
DecisionTree t = new DecisionTree ();
uint8 move = t.playandcheck (e.str);
uint8 move = AI.playandcheck (e.str);
if (move == uint8.MAX)
{
draw++;
......@@ -182,8 +181,7 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
e.insert (e.str.length - 1, (move + 1).to_string ());
m.insert (m.str.length - 1, (move + 1).to_string ());
DecisionTree d = new DecisionTree ();
move = d.playandcheck (m.str);
move = AI.playandcheck (m.str);
if (move == uint8.MAX)
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment