[four-in-a-row] Make negamax method static.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [four-in-a-row] Make negamax method static.
- Date: Thu, 26 Dec 2019 17:13:05 +0000 (UTC)
commit 28a8b459b90597ed1f4b1a5287556e9026fcff5a
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Wed Dec 25 15:09:18 2019 +0100
Make negamax method static.
src/ai.vala | 77 ++++++++++++++++++--------------------------
src/four-in-a-row.vala | 6 ++--
src/test-ai.vala | 86 ++++++++++++++++++++++++--------------------------
3 files changed, 76 insertions(+), 93 deletions(-)
---
diff --git a/src/ai.vala b/src/ai.vala
index a0e4057..3736c53 100644
--- a/src/ai.vala
+++ b/src/ai.vala
@@ -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
\*/
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index eda16bf..a25e05e 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -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
diff --git a/src/test-ai.vala b/src/test-ai.vala
index b87a65e..33f13d7 100644
--- a/src/test-ai.vala
+++ b/src/test-ai.vala
@@ -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)
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]