Commit 26ee7b48 authored by Arnaud B.'s avatar Arnaud B.

Add --size command-line support.

parent 3fedbd5d
Pipeline #139697 passed with stage
in 1 minute and 54 seconds
......@@ -37,20 +37,20 @@ namespace AI
\*/
/* returns the column number in which the next move has to be made. Returns uint8.MAX if the board is full. */
internal static uint8 playgame (Difficulty level, string vstr)
internal static uint8 playgame (uint8 size, Difficulty level, string vstr)
{
Player [,] board;
init_board_from_string (vstr, out board);
init_board_from_string (size, vstr, out board);
/* if AI can win by making a move immediately, make that move */
uint8 temp = immediate_win (Player.OPPONENT, ref board);
if (temp < BOARD_COLUMNS)
if (temp < size)
return temp;
/* if HUMAN can win by making a move immediately,
we make AI move in that column so as avoid loss */
temp = immediate_win (Player.HUMAN, ref board);
if (temp < BOARD_COLUMNS)
if (temp < size)
return temp;
/* call negamax tree on the current state of the board */
......@@ -62,17 +62,17 @@ namespace AI
}
/* utility function for testing purposes */
internal static uint8 playandcheck (Difficulty level, string vstr)
internal static uint8 playandcheck (uint8 size, Difficulty level, string vstr)
{
Player [,] board;
init_board_from_string (vstr, out board);
init_board_from_string (size, vstr, out board);
uint8 temp = immediate_win (Player.OPPONENT, ref board);
if (temp < BOARD_COLUMNS)
if (temp < size)
return 100;
temp = immediate_win (Player.HUMAN, ref board);
if (temp < BOARD_COLUMNS)
if (temp < size)
return temp;
/* call negamax tree on the current state of the board */
......@@ -87,12 +87,14 @@ namespace AI
\*/
/* vstr is the sequence of moves made until now; */
private static void init_board_from_string (string vstr, out Player [,] board)
private static void init_board_from_string (uint8 size, string vstr, out Player [,] board)
{
uint8 n_rows = size - 1;
uint8 n_cols = size;
/* empty board */
board = new Player [BOARD_ROWS, BOARD_COLUMNS];
for (uint8 i = 0; i < BOARD_ROWS; i++)
for (uint8 j = 0; j < BOARD_COLUMNS; j++)
board = new Player [n_rows, n_cols];
for (uint8 i = 0; i < n_rows; i++)
for (uint8 j = 0; j < n_cols; j++)
board [i, j] = Player.NOBODY;
/* AI will make the first move */
......@@ -105,6 +107,7 @@ namespace AI
private static inline void update_board (string vstr, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
Player move = vstr.length % 2 == 0 ? Player.OPPONENT : Player.HUMAN;
for (uint8 i = 0; i < vstr.length; i++)
......@@ -113,7 +116,7 @@ namespace AI
/* find the cell on which the move is made */
int8 row;
for (row = BOARD_ROWS - 1; row >= 0 && board [row, column] != Player.NOBODY; row--);
for (row = n_rows - 1; row >= 0 && board [row, column] != Player.NOBODY; row--);
board [row, column] = move;
......@@ -129,6 +132,7 @@ namespace AI
It returns the value of the current node. For nodes at height == 0, the value is determined by a heuristic function. */
private static int16 negamax (uint8 height, int16 alpha, int16 beta, Player player, Difficulty level, ref Player [,] board, ref uint8 next_move_in_column)
{
uint8 n_cols = (uint8) board.length [1];
/* 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))
{
......@@ -149,7 +153,7 @@ namespace AI
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++)
for (uint8 column = 0; column < n_cols; column++)
{
if (!move (player, column, ref board))
continue;
......@@ -189,9 +193,10 @@ namespace AI
/* all these functions return true if the given player wins, or false */
private static bool victory (Player player, uint8 column, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
/* find the cell on which the last move was made */
uint8 row;
for (row = 0; row < BOARD_ROWS && board [row, column] == Player.NOBODY; row++);
for (row = 0; row < n_rows && board [row, column] == Player.NOBODY; row++);
return vertical_win (player, row, column, ref board)
|| horizontal_win (player, row, column, ref board)
......@@ -201,48 +206,54 @@ namespace AI
private static inline bool forward_diagonal_win (Player player, uint8 _i, uint8 _j, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
uint8 n_cols = (uint8) board.length [1];
int8 i = (int8) _i;
int8 j = (int8) _j;
uint8 count = 0;
for (int8 k = i, l = j; k >= 0 && l < BOARD_COLUMNS && board [k, l] == player; k--, l++, count++);
for (int8 k = i + 1, l = j - 1; k < BOARD_ROWS && l >= 0 && board [k, l] == player; k++, l--, count++);
for (int8 k = i, l = j; k >= 0 && l < n_cols && board [k, l] == player; k--, l++, count++);
for (int8 k = i + 1, l = j - 1; k < n_rows && l >= 0 && board [k, l] == player; k++, l--, count++);
return count >= 4;
}
private static inline bool backward_diagonal_win (Player player, uint8 _i, uint8 _j, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
uint8 n_cols = (uint8) board.length [1];
int8 i = (int8) _i;
int8 j = (int8) _j;
uint8 count = 0;
for (int8 k = i, l = j; k >= 0 && l >= 0 && board [k, l] == player; k--, l--, count++);
for (int8 k = i + 1, l = j + 1; k < BOARD_ROWS && l < BOARD_COLUMNS && board [k, l] == player; k++, l++, count++);
for (int8 k = i + 1, l = j + 1; k < n_rows && l < n_cols && board [k, l] == player; k++, l++, count++);
return count >= 4;
}
private static inline bool horizontal_win (Player player, uint8 _i, uint8 _j, ref Player [,] board)
{
uint8 n_cols = (uint8) board.length [1];
int8 i = (int8) _i;
int8 j = (int8) _j;
uint8 count = 0;
for (int8 k = j; k >= 0 && board [i, k] == player; k--, count++);
for (int8 k = j + 1; k < BOARD_COLUMNS && board [i, k] == player; k++, count++);
for (int8 k = j + 1; k < n_cols && board [i, k] == player; k++, count++);
return count >= 4;
}
private static inline bool vertical_win (Player player, uint8 i, uint8 j, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
uint8 count = 0;
for (uint8 k = i; k < BOARD_ROWS && board [k, j] == player; k++, count++);
for (uint8 k = i; k < n_rows && board [k, j] == player; k++, count++);
return count >= 4;
}
......@@ -254,7 +265,8 @@ namespace AI
/* returns true if the board is full, false if not */
private static bool board_full (ref Player [,] board)
{
for (uint8 i = 0 ; i < BOARD_COLUMNS ; i++)
uint8 n_cols = (uint8) board.length [1];
for (uint8 i = 0 ; i < n_cols; i++)
if (board [0, i] == Player.NOBODY)
return false;
return true;
......@@ -263,9 +275,10 @@ namespace AI
/* makes a move into the column'th column. Returns true if the move was succesful, false if it wasn't */
private static bool move (Player player, uint8 column, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
/* find the cell on which to move */
int8 row;
for (row = BOARD_ROWS - 1; row >= 0 && board [row, column] != Player.NOBODY; row--);
for (row = n_rows - 1; row >= 0 && board [row, column] != Player.NOBODY; row--);
if (row < 0)
return false;
......@@ -277,9 +290,10 @@ namespace AI
/* unmove the last move made in the column'th column */
private static void unmove (uint8 column, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
/* find the cell on which the last move was made */
uint8 row;
for (row = 0; row < BOARD_ROWS && board [row, column] == Player.NOBODY; row++);
for (row = 0; row < n_rows && board [row, column] == Player.NOBODY; row++);
board [row, column] = Player.NOBODY;
}
......@@ -288,7 +302,8 @@ namespace AI
Otherwise returns the column number in which Player P should move to win. */
private static uint8 immediate_win (Player player, ref Player [,] board)
{
for (uint8 i = 0; i < BOARD_COLUMNS; i++)
uint8 n_cols = (uint8) board.length [1];
for (uint8 i = 0; i < n_cols; i++)
{
if (!move (player, i, ref board))
continue;
......@@ -307,9 +322,11 @@ namespace AI
/* 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++)
// uint8 n_rows = (uint8) board.length [0];
// uint8 n_cols = (uint8) board.length [1];
// for (uint8 i = 0; i < n_rows; i++)
// {
// for (uint8 j = 0; j < BOARD_COLUMNS; j++)
// for (uint8 j = 0; j < n_cols; j++)
// stdout.printf ("%d\t", board [i, j]);
// stdout.printf ("\n");
// }
......@@ -352,11 +369,13 @@ namespace AI
which have an empty cell in the vicinity to make it four in a row. */
private static int8 count_3_in_a_row (Player player, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
uint8 n_cols = (uint8) board.length [1];
int8 count = 0;
for (uint8 j = 0; j < BOARD_COLUMNS; j++)
for (uint8 j = 0; j < n_cols; j++)
{
for (uint8 i = 0; i < BOARD_ROWS; i++)
for (uint8 i = 0; i < n_rows; i++)
{
if (board [i, j] != Player.NOBODY)
break;
......@@ -383,6 +402,8 @@ namespace AI
/* checks if all adjacent cells to board [i, j] are empty */
private static inline bool all_adjacent_empty (uint8 _i, uint8 _j, ref Player [,] board)
{
uint8 n_rows = (uint8) board.length [0];
uint8 n_cols = (uint8) board.length [1];
int8 i = (int8) _i;
int8 j = (int8) _j;
......@@ -392,7 +413,7 @@ namespace AI
{
if (k == 0 && l == 0)
continue;
if (i + k >= 0 && i + k < BOARD_ROWS && j + l >= 0 && j + l < BOARD_COLUMNS && board [i + k, j + l] != Player.NOBODY)
if (i + k >= 0 && i + k < n_rows && j + l >= 0 && j + l < n_cols && board [i + k, j + l] != Player.NOBODY)
return false;
}
}
......
......@@ -46,7 +46,7 @@ private class FourInARow : Gtk.Application
private Player player = Player.NOBODY;
private Player winner = Player.NOBODY;
private Player last_first_player = Player.NOBODY;
private Board game_board = new Board ();
private Board game_board;
private bool one_player_game;
private Difficulty ai_level;
/**
......@@ -90,6 +90,7 @@ private class FourInARow : Gtk.Application
private uint8 theme_id;
private static string? level = null;
private static int size = 7;
private static bool? sound = null;
private const OptionEntry [] option_entries =
......@@ -103,6 +104,12 @@ private class FourInARow : Gtk.Application
/* Translators: command-line option description, see 'four-in-a-row --help' */
{ "mute", 0, OptionFlags.NONE, OptionArg.NONE, null, N_("Turn off the sound"), null },
/* Translators: command-line option description, see 'four-in-a-row --help' */
{ "size", 's', OptionFlags.NONE, OptionArg.INT, ref size, N_("Size of the board"),
/* Translators: in the command-line options description, text to indicate the user should specify a size, see 'four-in-a-row --help' */
N_("SIZE") },
/* Translators: command-line option description, see 'four-in-a-row --help' */
{ "unmute", 0, OptionFlags.NONE, OptionArg.NONE, null, N_("Turn on the sound"), null },
......@@ -135,11 +142,6 @@ private class FourInARow : Gtk.Application
return new FourInARow ().run (args);
}
construct
{
clear_board ();
}
private FourInARow ()
{
Object (application_id: "org.gnome.Four-in-a-row", flags: ApplicationFlags.FLAGS_NONE);
......@@ -156,6 +158,19 @@ private class FourInARow : Gtk.Application
return Posix.EXIT_SUCCESS;
}
if (size < 4)
{
/* Translators: command-line error message, displayed for an incorrect game size request; try 'four-in-a-row -s 2' */
stderr.printf ("%s\n", _("Size must be at least 4."));
return Posix.EXIT_FAILURE;
}
if (size > 16)
{
/* Translators: command-line error message, displayed for an incorrect game size request; try 'four-in-a-row -s 17' */
stderr.printf ("%s\n", _("Size must not be more than 16."));
return Posix.EXIT_FAILURE;
}
if (options.contains ("mute"))
sound = false;
else if (options.contains ("unmute"))
......@@ -202,6 +217,9 @@ private class FourInARow : Gtk.Application
settings.apply ();
}
game_board = new Board ((uint8) size);
clear_board ();
if (settings.get_boolean ("sound"))
init_sound ();
......@@ -425,7 +443,8 @@ private class FourInARow : Gtk.Application
switch_players ();
winner = NOBODY;
column = (BOARD_COLUMNS % 2 == 0 && get_locale_direction () == TextDirection.RTL) ? BOARD_COLUMNS / 2 - 1 : BOARD_COLUMNS / 2;
column = (/* BOARD_COLUMNS */ size % 2 == 0 && get_locale_direction () == TextDirection.RTL) ? /* BOARD_COLUMNS */ (uint8) size / 2 - 1
: /* BOARD_COLUMNS */ (uint8) size / 2;
column_moveto = column;
row = 0;
row_dropto = 0;
......@@ -439,8 +458,8 @@ private class FourInARow : Gtk.Application
if (!is_player_human ())
{
playgame_timeout = Timeout.add (COMPUTER_INITIAL_DELAY, () => {
uint8 c = AI.playgame (ai_level, vstr);
if (c >= BOARD_COLUMNS) // c could be uint8.MAX if board is full
uint8 c = AI.playgame ((uint8) size, ai_level, vstr);
if (c >= /* BOARD_COLUMNS */ size) // c could be uint8.MAX if board is full
return Source.REMOVE;
process_move (c);
playgame_timeout = 0;
......@@ -601,8 +620,8 @@ private class FourInARow : Gtk.Application
if (!is_player_human ())
{
playgame_timeout = Timeout.add (COMPUTER_MOVE_DELAY, () => {
uint8 col = AI.playgame (ai_level, vstr);
if (col >= BOARD_COLUMNS) // c could be uint8.MAX if the board is full
uint8 col = AI.playgame ((uint8) size, ai_level, vstr);
if (col >= /* BOARD_COLUMNS */ size) // c could be uint8.MAX if the board is full
set_gameover (true);
var nm = new NextMove (col, this);
Timeout.add (SPEED_DROP, nm.exec);
......@@ -624,7 +643,7 @@ private class FourInARow : Gtk.Application
play_sound (SoundID.PLAYER_WIN);
window.allow_hint (false);
}
else if (moves == BOARD_ROWS * BOARD_COLUMNS)
else if (moves == /* BOARD_ROWS */ (size - 1) * /* BOARD_COLUMNS */ size)
{
set_gameover (true);
winner = NOBODY;
......@@ -857,8 +876,8 @@ private class FourInARow : Gtk.Application
/* Translators: text *briefly* displayed in the headerbar/actionbar, when a hint is requested */
set_status_message (_("I’m Thinking…"));
uint8 c = AI.playgame (Difficulty.HARD, vstr);
if (c >= BOARD_COLUMNS)
uint8 c = AI.playgame ((uint8) size, Difficulty.HARD, vstr);
if (c >= /* BOARD_COLUMNS */ size)
assert_not_reached (); // c could be uint8.MAX if the board if full
column_moveto = c;
......@@ -1010,7 +1029,7 @@ private class FourInARow : Gtk.Application
}
else if (key == "Right" || event.keyval == keypress_right)
{
if (column >= BOARD_COLUMNS_MINUS_ONE)
if (column >= /* BOARD_COLUMNS_MINUS_ONE */ size - 1)
return false;
column_moveto++;
move_cursor (column_moveto);
......
......@@ -78,8 +78,8 @@ private class GameBoardView : Gtk.DrawingArea
int allocated_width = get_allocated_width ();
int allocated_height = get_allocated_height ();
int size = int.min (allocated_width, allocated_height);
tile_size = size / BOARD_SIZE;
board_size = tile_size * BOARD_SIZE;
tile_size = size / game_board.size;
board_size = tile_size * game_board.size;
board_x = (allocated_width - board_size) / 2;
board_y = (allocated_height - board_size) / 2;
......@@ -109,8 +109,8 @@ private class GameBoardView : Gtk.DrawingArea
cr.restore ();
/* tiles */
for (uint8 row = 0; row < BOARD_ROWS_PLUS_ONE; row++)
for (uint8 col = 0; col < BOARD_COLUMNS; col++)
for (uint8 row = 0; row < /* BOARD_ROWS_PLUS_ONE */ game_board.size; row++)
for (uint8 col = 0; col < /* BOARD_COLUMNS */ game_board.size; col++)
paint_tile (cr, row, col);
/* grid */
......@@ -169,7 +169,7 @@ private class GameBoardView : Gtk.DrawingArea
cr.set_dash (dashes, /* offset */ 0.0);
/* draw the grid on the background pixmap */
for (uint8 i = 1; i < BOARD_SIZE; i++)
for (uint8 i = 1; i < /* BOARD_SIZE */ game_board.size; i++)
{
double line_offset = i * tile_size + 0.5;
// vertical lines
......@@ -253,15 +253,15 @@ private class GameBoardView : Gtk.DrawingArea
{
int raw_tile_size = pb_tileset_raw.get_height ();
pb_bground_raw = new Gdk.Pixbuf (Gdk.Colorspace.RGB, /* alpha */ true, /* bits per sample */ 8, raw_tile_size * BOARD_COLUMNS, raw_tile_size * BOARD_ROWS_PLUS_ONE);
for (int i = 0; i < BOARD_COLUMNS; i++)
pb_bground_raw = new Gdk.Pixbuf (Gdk.Colorspace.RGB, /* alpha */ true, /* bits per sample */ 8, raw_tile_size * /* BOARD_COLUMNS */ game_board.size, raw_tile_size * /* BOARD_ROWS_PLUS_ONE */ game_board.size);
for (int i = 0; i < /* BOARD_COLUMNS */ game_board.size; i++)
{
pb_tileset_raw.copy_area (raw_tile_size * 3, 0,
raw_tile_size, raw_tile_size,
pb_bground_raw,
i * raw_tile_size, 0);
for (int j = 1; j < BOARD_ROWS_PLUS_ONE; j++)
for (int j = 1; j < /* BOARD_ROWS_PLUS_ONE */ game_board.size; j++)
pb_tileset_raw.copy_area (raw_tile_size * 2, 0,
raw_tile_size, raw_tile_size,
pb_bground_raw,
......@@ -303,7 +303,7 @@ private class GameBoardView : Gtk.DrawingArea
private inline bool get_column (int x, int y, out uint8 col)
{
int _col = (x - board_x) / tile_size;
if (x < board_x || y < board_y || _col < 0 || _col > BOARD_COLUMNS_MINUS_ONE)
if (x < board_x || y < board_y || _col < 0 || _col > /* BOARD_COLUMNS_MINUS_ONE */ game_board.size - 1)
{
col = 0;
return false;
......@@ -311,7 +311,7 @@ private class GameBoardView : Gtk.DrawingArea
col = (uint8) _col;
int row = (y - board_y) / tile_size;
if (row < 0 || row > BOARD_ROWS)
if (row < 0 || row > /* BOARD_ROWS */ game_board.size - 1)
return false;
return true;
......
......@@ -20,11 +20,18 @@
private class Board : Object
{
[CCode (notify = false)] public uint8 size { internal get; protected construct; default = 7; }
private static Player [,] gboard;
internal Board ()
construct
{
gboard = new Player [/* BOARD_COLUMNS */ size, /* BOARD_ROWS_PLUS_ONE */ size];
}
internal Board (uint8 size)
{
gboard = new Player [BOARD_COLUMNS, BOARD_ROWS_PLUS_ONE];
Object (size: size);
}
internal new void @set (uint8 x, uint8 y, Player tile)
......@@ -39,8 +46,8 @@ private class Board : Object
internal void clear ()
{
for (uint8 row = 0; row < BOARD_ROWS_PLUS_ONE; row++)
for (uint8 col = 0; col < BOARD_COLUMNS; col++)
for (uint8 row = 0; row < /* BOARD_ROWS_PLUS_ONE */ size; row++)
for (uint8 col = 0; col < /* BOARD_COLUMNS */ size; col++)
gboard [row, col] = Player.NOBODY;
}
......@@ -48,7 +55,7 @@ private class Board : Object
{
uint8 row = 1;
while (row < BOARD_ROWS_PLUS_ONE && gboard [row, col] == Player.NOBODY)
while (row < /* BOARD_ROWS_PLUS_ONE */ size && gboard [row, col] == Player.NOBODY)
row++;
return row - 1;
}
......@@ -100,7 +107,7 @@ private class Board : Object
col_2 = col;
while (col_1 > 0 && gboard [row, col_1 - 1] == tile)
col_1 = col_1 - 1;
while (col_2 < BOARD_ROWS && gboard [row, col_2 + 1] == tile)
while (col_2 < /* BOARD_ROWS */ size - 1 && gboard [row, col_2 + 1] == tile)
col_2 = col_2 + 1;
return col_2 - col_1 >= 3;
}
......@@ -115,7 +122,7 @@ private class Board : Object
col_2 = col;
while (row_1 > 1 && gboard [row_1 - 1, col] == tile)
row_1 = row_1 - 1;
while (row_2 < BOARD_ROWS && gboard [row_2 + 1, col] == tile)
while (row_2 < /* BOARD_ROWS */ size - 1 && gboard [row_2 + 1, col] == tile)
row_2 = row_2 + 1;
return row_2 - row_1 >= 3;
}
......@@ -134,7 +141,7 @@ private class Board : Object
row_1 = row_1 - 1;
col_1 = col_1 - 1;
}
while (col_2 < BOARD_COLUMNS_MINUS_ONE && row_2 < BOARD_ROWS && gboard [row_2 + 1, col_2 + 1] == tile)
while (col_2 < /* BOARD_COLUMNS_MINUS_ONE */ size - 1 && row_2 < /* BOARD_ROWS */ size - 1 && gboard [row_2 + 1, col_2 + 1] == tile)
{
row_2 = row_2 + 1;
col_2 = col_2 + 1;
......@@ -151,12 +158,12 @@ private class Board : Object
row_2 = row;
col_1 = col;
col_2 = col;
while (col_1 < BOARD_COLUMNS_MINUS_ONE && row_1 > 1 && gboard [row_1 - 1, col_1 + 1] == tile)
while (col_1 < /* BOARD_COLUMNS_MINUS_ONE */ size - 1 && row_1 > 1 && gboard [row_1 - 1, col_1 + 1] == tile)
{
row_1 = row_1 - 1;
col_1 = col_1 + 1;
}
while (col_2 > 0 && row_2 < BOARD_ROWS && gboard [row_2 + 1, col_2 - 1] == tile)
while (col_2 > 0 && row_2 < /* BOARD_ROWS */ size - 1 && gboard [row_2 + 1, col_2 - 1] == tile)
{
row_2 = row_2 + 1;
col_2 = col_2 - 1;
......
......@@ -18,11 +18,11 @@
with GNOME Four-in-a-row. If not, see <https://www.gnu.org/licenses/>.
*/
private const uint8 BOARD_COLUMNS = 7;
private const uint8 BOARD_COLUMNS_MINUS_ONE = 6;
private const uint8 BOARD_ROWS = 6;
private const uint8 BOARD_ROWS_PLUS_ONE = 7;
private const uint8 BOARD_SIZE = 7; // as long as that is needed, impossible to have n_rows != n_cols - 1
// private const uint8 BOARD_COLUMNS = 7;
// private const uint8 BOARD_COLUMNS_MINUS_ONE = 6;
// private const uint8 BOARD_ROWS = 6;
// private const uint8 BOARD_ROWS_PLUS_ONE = 7;
// private const uint8 BOARD_SIZE = 7; // as long as that is needed, impossible to have n_rows != n_cols - 1
private enum Player
{
......
......@@ -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 (AI.playgame (Difficulty.EASY, "172737") == 3);
assert_true (AI.playgame (Difficulty.EASY, "731565131132442") == 5);
assert_true (AI.playgame (Difficulty.EASY, "23222565722356161113344") == 3);
assert_true (AI.playgame (Difficulty.EASY, "24221532257425554334174667745333771") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "172737") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "731565131132442") == 5);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "23222565722356161113344") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "24221532257425554334174667745333771") == 0);
}
/* Tests if the AI makes moves so as to take up immediate vertical wins.*/
private static inline void test_vertical_win ()
{
assert_true (AI.playgame (Difficulty.EASY, "121314") == 0);
assert_true (AI.playgame (Difficulty.EASY, "1445653552661313") == 0);
assert_true (AI.playgame (Difficulty.EASY, "43233427775257671") == 6);
assert_true (AI.playgame (Difficulty.EASY, "54747745454432332171211626") == 1);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "121314") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "1445653552661313") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "43233427775257671") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "54747745454432332171211626") == 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 (AI.playgame (Difficulty.EASY, "5422116471244621162215757") == 6);
assert_true (AI.playgame (Difficulty.EASY, "425642442662127141211717577634333") == 2);
assert_true (AI.playgame (Difficulty.EASY, "13256552232266266677544335113111354") == 3);
assert_true (AI.playgame (Difficulty.EASY, "457131133454122554411224526257776773336") == 5);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "5422116471244621162215757") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "425642442662127141211717577634333") == 2);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "13256552232266266677544335113111354") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "457131133454122554411224526257776773336") == 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 (AI.playgame (Difficulty.EASY, "542232734314211") == 0);
assert_true (AI.playgame (Difficulty.EASY, "141511331514322") == 1);
assert_true (AI.playgame (Difficulty.EASY, "54732345221334511") == 0);
assert_true (AI.playgame (Difficulty.EASY, "425642442662127141211717577634333") == 2);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "542232734314211") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "141511331514322") == 1);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "54732345221334511") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "425642442662127141211717577634333") == 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 (AI.playgame (Difficulty.EASY, "4256311727343") == 2);
assert_true (AI.playgame (Difficulty.EASY, "364257154132234") == 3);
assert_true (AI.playgame (Difficulty.EASY, "14456626447517113775") == 4);
assert_true (AI.playgame (Difficulty.EASY, "5474774545443233217121") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "4256311727343") == 2);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "364257154132234") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "14456626447517113775") == 4);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "5474774545443233217121") == 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 (AI.playgame (Difficulty.EASY, "3425647733156657") == 6);
assert_true (AI.playgame (Difficulty.EASY, "144566264475171137") == 6);
assert_true (AI.playgame (Difficulty.EASY, "4344223537211511334") == 3);
assert_true (AI.playgame (Difficulty.EASY, "414352556776644354312541117") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "3425647733156657") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "144566264475171137") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "4344223537211511334") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "414352556776644354312541117") == 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 (AI.playgame (Difficulty.EASY, "4746523422253") == 2);
assert_true (AI.playgame (Difficulty.EASY, "434422353721151") == 0);
assert_true (AI.playgame (Difficulty.EASY, "414131152551352") == 1);
assert_true (AI.playgame (Difficulty.EASY, "144566264475171137755333") == 2);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "4746523422253") == 2);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "434422353721151") == 0);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "414131152551352") == 1);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "144566264475171137755333") == 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 (AI.playgame (Difficulty.EASY, "44536") == 6);
assert_true (AI.playgame (Difficulty.EASY, "74553413111711477772") == 1);
assert_true (AI.playgame (Difficulty.EASY, "24346643121711232335") == 4);
assert_true (AI.playgame (Difficulty.EASY, "2414735646535511133663161524") == 3);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "44536") == 6);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "74553413111711477772") == 1);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "24346643121711232335") == 4);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "2414735646535511133663161524") == 3);
}
/* Tests if AI can detect full boards, and thus draw games. */
private static inline void test_draw ()
{
assert_true (AI.playgame (Difficulty.EASY, "131131311365222666722424776673737445544555") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "612115113543232243342556647442561763567777") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "422611141211327525633553444326437557767667") == uint8.MAX);
assert_true (AI.playgame (Difficulty.EASY, "421211657571775477522113343443236665534266") == uint8.MAX);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "131131311365222666722424776673737445544555") == uint8.MAX);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "612115113543232243342556647442561763567777") == uint8.MAX);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "422611141211327525633553444326437557767667") == uint8.MAX);
assert_true (AI.playgame (/* size */ 7, Difficulty.EASY, "421211657571775477522113343443236665534266") == uint8.MAX);
}
/* Tests if AI makes valid moves, i.e., between column 1 and column 7. */
private static inline void test_random ()
{
uint8 x = AI.playgame (Difficulty.EASY, "44325621435");
uint8 x = AI.playgame (/* size */ 7, Difficulty.EASY, "44325621435");
assert_true (x <= 6);
x = AI.playgame (Difficulty.EASY, "24147356465355111336631615");
x = AI.playgame (/* size */ 7, Difficulty.EASY, "24147356465355111336631615");
assert_true (x <= 6);
x = AI.playgame (Difficulty.EASY, "2435731546171117741662262335");
x = AI.playgame (/* size */ 7, Difficulty.EASY, "2435731546171117741662262335");
assert_true (x <= 6);
x = AI.playgame (Difficulty.EASY, "144566264475171137755333366577544611");