[four-in-a-row] Allow targeting an other line length than 4.



commit 62bfefe041463e0e84422e530bed860c478908db
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Fri Jan 10 18:55:19 2020 +0100

    Allow targeting an other line length than 4.

 src/ai.vala            | 74 +++++++++++++++++++++++++-------------------------
 src/four-in-a-row.vala | 28 ++++++++++++++++---
 src/game-board.vala    | 13 +++++----
 3 files changed, 68 insertions(+), 47 deletions(-)
---
diff --git a/src/ai.vala b/src/ai.vala
index 9014133..49d603e 100644
--- a/src/ai.vala
+++ b/src/ai.vala
@@ -37,47 +37,47 @@ 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 (uint8 size, Difficulty level, string vstr)
+    internal static uint8 playgame (uint8 size, Difficulty level, string vstr, uint8 line = 4)
     {
         Player [,] 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);
+        uint8 temp = immediate_win (Player.OPPONENT, line, ref board);
         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);
+        temp = immediate_win (Player.HUMAN, line, ref board);
         if (temp < size)
             return temp;
 
         /* 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);
+        negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT, level, line, 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 static uint8 playandcheck (uint8 size, Difficulty level, string vstr)
+    internal static uint8 playandcheck (uint8 size, Difficulty level, string vstr, uint8 line = 4)
     {
         Player [,] board;
         init_board_from_string (size, vstr, out board);
 
-        uint8 temp = immediate_win (Player.OPPONENT, ref board);
+        uint8 temp = immediate_win (Player.OPPONENT, line, ref board);
         if (temp < size)
             return 100;
 
-        temp = immediate_win (Player.HUMAN, ref board);
+        temp = immediate_win (Player.HUMAN, line, ref board);
         if (temp < size)
             return temp;
 
         /* 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);
+        negamax (plies [level], NEGATIVE_INFINITY, POSITIVE_INFINITY, Player.OPPONENT, level, line, ref 
board, ref next_move_in_column);
 
         return next_move_in_column;
     }
@@ -130,16 +130,16 @@ namespace AI
 
     /* 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 static int16 negamax (uint8 height, int16 alpha, int16 beta, Player player, Difficulty level, 
ref Player [,] board, ref uint8 next_move_in_column)
+    private static int16 negamax (uint8 height, int16 alpha, int16 beta, Player player, Difficulty level, 
uint8 line, 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))
         {
             if (player == Player.OPPONENT)
-                return heurist (level, ref board);
+                return heurist (level, line, ref board);
             else if (player == Player.HUMAN)
-                return -1 * heurist (level, ref board);
+                return -1 * heurist (level, line, ref board);
             else
                 assert_not_reached ();  // do not call AI on a full board, please
         }
@@ -161,8 +161,8 @@ namespace AI
             /* victory() checks if making a move in the i'th column results in a victory for the given 
player.
                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, level, ref board, ref next_move_in_column);
+            int16 temp = victory (player, line, column, ref board) ? MAX_HEURIST_VALUE * height
+                                                                   : -1 * negamax (height - 1, -1 * beta, -1 
* alpha, player == Player.OPPONENT ? Player.HUMAN : Player.OPPONENT, level, line, ref board, ref 
next_move_in_column);
 
             unmove (column, ref board);
 
@@ -191,20 +191,20 @@ 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)
+    private static bool victory (Player player, uint8 line, 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 < n_rows && board [row, column] == Player.NOBODY; row++);
 
-        return vertical_win             (player, row, column, ref board)
-            || horizontal_win           (player, row, column, ref board)
-            || forward_diagonal_win     (player, row, column, ref board)
-            || backward_diagonal_win    (player, row, column, ref board);
+        return vertical_win             (player, line, row, column, ref board)
+            || horizontal_win           (player, line, row, column, ref board)
+            || forward_diagonal_win     (player, line, row, column, ref board)
+            || backward_diagonal_win    (player, line, row, column, ref board);
     }
 
-    private static inline bool forward_diagonal_win (Player player, uint8 _i, uint8 _j, ref Player [,] board)
+    private static inline bool forward_diagonal_win (Player player, uint8 line, uint8 _i, uint8 _j, ref 
Player [,] board)
     {
         uint8 n_rows = (uint8) board.length [0];
         uint8 n_cols = (uint8) board.length [1];
@@ -216,10 +216,10 @@ namespace AI
         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;
+        return count >= line;
     }
 
-    private static inline bool backward_diagonal_win (Player player, uint8 _i, uint8 _j, ref Player [,] 
board)
+    private static inline bool backward_diagonal_win (Player player, uint8 line, uint8 _i, uint8 _j, ref 
Player [,] board)
     {
         uint8 n_rows = (uint8) board.length [0];
         uint8 n_cols = (uint8) board.length [1];
@@ -231,10 +231,10 @@ namespace AI
         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 < n_rows && l < n_cols && board [k, l] == player; k++, l++, 
count++);
 
-        return count >= 4;
+        return count >= line;
     }
 
-    private static inline bool horizontal_win (Player player, uint8 _i, uint8 _j, ref Player [,] board)
+    private static inline bool horizontal_win (Player player, uint8 line, uint8 _i, uint8 _j, ref Player [,] 
board)
     {
         uint8 n_cols = (uint8) board.length [1];
         int8 i = (int8) _i;
@@ -245,17 +245,17 @@ namespace AI
         for (int8 k = j; k >= 0 && board [i, k] == player; k--, count++);
         for (int8 k = j + 1; k < n_cols && board [i, k] == player; k++, count++);
 
-        return count >= 4;
+        return count >= line;
     }
 
-    private static inline bool vertical_win (Player player, uint8 i, uint8 j, ref Player [,] board)
+    private static inline bool vertical_win (Player player, uint8 line, uint8 i, uint8 j, ref Player [,] 
board)
     {
         uint8 n_rows = (uint8) board.length [0];
         uint8 count = 0;
 
         for (uint8 k = i; k < n_rows && board [k, j] == player; k++, count++);
 
-        return count >= 4;
+        return count >= line;
     }
 
     /*\
@@ -300,7 +300,7 @@ namespace AI
 
     /* Check for immediate win of HUMAN or OPPONENT. It checks the current state of the board. Returns 
uint8.MAX if no immediate win for Player P.
        Otherwise returns the column number in which Player P should move to win. */
-    private static uint8 immediate_win (Player player, ref Player [,] board)
+    private static uint8 immediate_win (Player player, uint8 line, ref Player [,] board)
     {
         uint8 n_cols = (uint8) board.length [1];
         for (uint8 i = 0; i < n_cols; i++)
@@ -308,7 +308,7 @@ namespace AI
             if (!move (player, i, ref board))
                 continue;
 
-            bool player_wins = victory (player, i, ref board);
+            bool player_wins = victory (player, line, i, ref board);
             unmove (i, ref board);
 
             if (player_wins)
@@ -338,20 +338,20 @@ namespace AI
     \*/
 
     /* The evaluation function to be called when we have reached the maximum depth in the DecisionTree */
-    private static inline int16 heurist (Difficulty level, ref Player [,] board)
+    private static inline int16 heurist (Difficulty level, uint8 line, ref Player [,] board)
     {
         switch (level)
         {
-            case Difficulty.EASY  : return heurist_easy (ref board);
+            case Difficulty.EASY  : return heurist_easy (line, ref board);
             case Difficulty.MEDIUM: return heurist_medium ();
-            case Difficulty.HARD  : return heurist_hard (ref board);
+            case Difficulty.HARD  : return heurist_hard (line, ref board);
             default: assert_not_reached ();
         }
     }
 
-    private static int16 heurist_easy (ref Player [,] board)
+    private static int16 heurist_easy (uint8 line, ref Player [,] board)
     {
-        return -1 * heurist_hard (ref board);
+        return -1 * heurist_hard (line, ref board);
     }
 
     private static inline int16 heurist_medium ()
@@ -359,15 +359,15 @@ namespace AI
         return (int16) Random.int_range (1, 49);
     }
 
-    private static int16 heurist_hard (ref Player [,] board)
+    private static int16 heurist_hard (uint8 line, ref Player [,] board)
     {
-        int8 count = count_3_in_a_row (Player.OPPONENT, ref board) - count_3_in_a_row (Player.HUMAN, ref 
board);
+        int8 count = count_3_in_a_row (Player.OPPONENT, line, ref board) - count_3_in_a_row (Player.HUMAN, 
line, ref board);
         return count == 0 ? (int16) Random.int_range (1, 49) : (int16) count * 100;
     }
 
     /* Count the number of threes in a row for Player P. It counts all those 3
        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)
+    private static int8 count_3_in_a_row (Player player, uint8 line, ref Player [,] board)
     {
         uint8 n_rows = (uint8) board.length [0];
         uint8 n_cols = (uint8) board.length [1];
@@ -385,7 +385,7 @@ namespace AI
 
                 board [i, j] = player;
 
-                if (victory (player, j, ref board))
+                if (victory (player, line, j, ref board))
                 {
                     if (count < int8.MAX)
                         count++;
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index 5f702e2..622c314 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -86,6 +86,7 @@ private class FourInARow : Gtk.Application
 
     private static string? level = null;
     private static int size = 7;
+    private static int target = 4;
     private static bool? sound = null;
 
     private const OptionEntry [] option_entries =
@@ -105,6 +106,12 @@ private class FourInARow : Gtk.Application
         /* 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' */
+        { "target", 't', OptionFlags.NONE, OptionArg.INT, ref target,           N_("Length of a winning 
line"),
+
+        /* Translators: in the command-line options description, text to indicate the user should specify 
the line length, see 'four-in-a-row --help' */
+                                                                                N_("TARGET") },
+
         /* Translators: command-line option description, see 'four-in-a-row --help' */
         { "unmute", 0, OptionFlags.NONE, OptionArg.NONE, null,                  N_("Turn on the sound"), 
null },
 
@@ -166,6 +173,19 @@ private class FourInARow : Gtk.Application
             return Posix.EXIT_FAILURE;
         }
 
+        if (target < 3)
+        {
+            /* Translators: command-line error message, displayed for an incorrect line length request; try 
'four-in-a-row -t 2' */
+            stderr.printf ("%s\n", _("Lines must be at least 3 tiles."));
+            return Posix.EXIT_FAILURE;
+        }
+        if (target > size - 1)
+        {
+            /* Translators: command-line error message, displayed for an incorrect line length request; try 
'four-in-a-row -t 8' */
+            stderr.printf ("%s\n", _("Lines cannot be longer than board height or width."));
+            return Posix.EXIT_FAILURE;
+        }
+
         if (options.contains ("mute"))
             sound = false;
         else if (options.contains ("unmute"))
@@ -212,7 +232,7 @@ private class FourInARow : Gtk.Application
             settings.apply ();
         }
 
-        game_board = new Board ((uint8) size);
+        game_board = new Board ((uint8) size, (uint8) target);
         clear_board ();
 
         if (settings.get_boolean ("sound"))
@@ -450,7 +470,7 @@ private class FourInARow : Gtk.Application
         if (!is_player_human ())
         {
             playgame_timeout = Timeout.add (COMPUTER_INITIAL_DELAY, () => {
-                    uint8 c = AI.playgame ((uint8) size, ai_level, vstr);
+                    uint8 c = AI.playgame ((uint8) size, ai_level, vstr, (uint8) target);
                     if (c >= /* BOARD_COLUMNS */ size) // c could be uint8.MAX if board is full
                         return Source.REMOVE;
                     process_move (c);
@@ -631,7 +651,7 @@ private class FourInARow : Gtk.Application
             if (!is_player_human ())
             {
                 playgame_timeout = Timeout.add (COMPUTER_MOVE_DELAY, () => {
-                        uint8 col = AI.playgame ((uint8) size, ai_level, vstr);
+                        uint8 col = AI.playgame ((uint8) size, ai_level, vstr, (uint8) target);
                         if (col >= /* BOARD_COLUMNS */ size)   // c could be uint8.MAX if the board is full
                             set_gameover (true);
                         var nm = new NextMove (col, this);
@@ -887,7 +907,7 @@ 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 ((uint8) size, Difficulty.HARD, vstr);
+        uint8 c = AI.playgame ((uint8) size, Difficulty.HARD, vstr, (uint8) target);
         if (c >= /* BOARD_COLUMNS */ size)
             assert_not_reached ();  // c could be uint8.MAX if the board if full
 
diff --git a/src/game-board.vala b/src/game-board.vala
index 2212296..a3c2a76 100644
--- a/src/game-board.vala
+++ b/src/game-board.vala
@@ -20,6 +20,7 @@
 
 private class Board : Object
 {
+    [CCode (notify = false)] public uint8 line { internal get; protected construct; default = 4; }
     [CCode (notify = false)] public uint8 size { internal get; protected construct; default = 7; }
 
     private static Player [,] gboard;
@@ -29,9 +30,9 @@ private class Board : Object
         gboard = new Player [/* BOARD_COLUMNS */ size, /* BOARD_ROWS_PLUS_ONE */ size];
     }
 
-    internal Board (uint8 size)
+    internal Board (uint8 size, uint8 line)
     {
-        Object (size: size);
+        Object (size: size, line: line);
     }
 
     internal new void @set (uint8 x, uint8 y, Player tile)
@@ -109,7 +110,7 @@ private class Board : Object
             col_1 = col_1 - 1;
         while (col_2 < /* BOARD_ROWS */ size - 1 && gboard [row, col_2 + 1] == tile)
             col_2 = col_2 + 1;
-        return col_2 - col_1 >= 3;
+        return col_2 - col_1 >= line - 1;
     }
 
     private inline bool is_vline_at (Player tile,     uint8 row,       uint8 col,
@@ -124,7 +125,7 @@ private class Board : Object
             row_1 = row_1 - 1;
         while (row_2 < /* BOARD_ROWS */ size - 1 && gboard [row_2 + 1, col] == tile)
             row_2 = row_2 + 1;
-        return row_2 - row_1 >= 3;
+        return row_2 - row_1 >= line - 1;
     }
 
     private inline bool is_dline1_at (Player tile,     uint8 row,       uint8 col,
@@ -146,7 +147,7 @@ private class Board : Object
             row_2 = row_2 + 1;
             col_2 = col_2 + 1;
         }
-        return row_2 - row_1 >= 3;
+        return row_2 - row_1 >= line - 1;
     }
 
     private inline bool is_dline2_at (Player tile,     uint8 row,       uint8 col,
@@ -168,6 +169,6 @@ private class Board : Object
             row_2 = row_2 + 1;
             col_2 = col_2 - 1;
         }
-        return row_2 - row_1 >= 3;
+        return row_2 - row_1 >= line - 1;
     }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]