[four-in-a-row] Simplify history strings, part 1.



commit f31ba67a87525470fb2f9e06986c4f0910c039b4
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Wed Dec 25 18:03:35 2019 +0100

    Simplify history strings, part 1.

 src/ai.vala            |  35 ++++-----------
 src/four-in-a-row.vala |  52 ++++++++++++----------
 src/player.vala        |   7 +++
 src/test-ai.vala       | 116 ++++++++++++++++++++++++-------------------------
 4 files changed, 101 insertions(+), 109 deletions(-)
---
diff --git a/src/ai.vala b/src/ai.vala
index 3736c53..a7cdc46 100644
--- a/src/ai.vala
+++ b/src/ai.vala
@@ -20,8 +20,6 @@
 
 namespace AI
 {
-    private enum Difficulty { EASY, MEDIUM, HARD; }
-
     /* Here NEGATIVE_INFINITY is supposed to be the lowest possible value.
        Do not forget int16.MIN ≠ - int16.MAX. */
     private const int16 POSITIVE_INFINITY           =  32000;
@@ -39,11 +37,10 @@ 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 (string vstr)
+    internal static uint8 playgame (Difficulty level, string vstr)
     {
-        Difficulty level;
         Player [,] board;
-        init_from_string (vstr, out level, out board);
+        init_board_from_string (vstr, out board);
 
         /* if AI can win by making a move immediately, make that move */
         uint8 temp = immediate_win (Player.OPPONENT, ref board);
@@ -65,11 +62,10 @@ namespace AI
     }
 
     /* utility function for testing purposes */
-    internal static uint8 playandcheck (string vstr)
+    internal static uint8 playandcheck (Difficulty level, string vstr)
     {
-        Difficulty level;
         Player [,] board;
-        init_from_string (vstr, out level, out board);
+        init_board_from_string (vstr, out board);
 
         uint8 temp = immediate_win (Player.OPPONENT, ref board);
         if (temp < BOARD_COLUMNS)
@@ -91,12 +87,8 @@ namespace AI
     \*/
 
     /* vstr is the sequence of moves made until now;  */
-    private static void init_from_string (string        vstr,
-                                      out Difficulty    level,
-                                      out Player [,]    board)
+    private static void init_board_from_string (string vstr, out Player [,] board)
     {
-        set_level (vstr, out level);
-
         /* empty board */
         board = new Player [BOARD_ROWS, BOARD_COLUMNS];
         for (uint8 i = 0; i < BOARD_ROWS; i++)
@@ -104,29 +96,18 @@ namespace AI
                 board [i, j] = Player.NOBODY;
 
         /* AI will make the first move */
-        if (vstr.length == 2)
+        if (vstr.length == 1)
             return;
 
         /* update board from current string */
         update_board (vstr, ref board);
     }
 
-    private static inline void set_level (string vstr, out Difficulty level)
-    {
-        switch (vstr [0])
-        {
-            case 'a': level = Difficulty.EASY;   return;
-            case 'b': level = Difficulty.MEDIUM; return;
-            case 'c': level = Difficulty.HARD;   return;
-            default : assert_not_reached ();
-        }
-    }
-
     private static inline void update_board (string vstr, ref Player [,] board)
     {
-        Player move = vstr.length % 2 == 0 ? Player.OPPONENT : Player.HUMAN;
+        Player move = vstr.length % 2 == 0 ? Player.HUMAN : Player.OPPONENT;
 
-        for (uint8 i = 1; i < vstr.length - 1; i++)
+        for (uint8 i = 0; i < vstr.length - 1; i++)
         {
             uint8 column = (uint8) int.parse (vstr [i].to_string ()) - 1;
 
diff --git a/src/four-in-a-row.vala b/src/four-in-a-row.vala
index a25e05e..e24be25 100644
--- a/src/four-in-a-row.vala
+++ b/src/four-in-a-row.vala
@@ -27,11 +27,9 @@ private class FourInARow : Gtk.Application
 
     /* Translators: application name, as used in the window manager, the window title, the about dialog... */
     private const string PROGRAM_NAME = _("Four-in-a-row");
-    private const uint8 SIZE_VSTR = 53;
     private const uint SPEED_BLINK = 150;
     private const uint SPEED_MOVE = 35;
     private const uint SPEED_DROP = 20;
-    private const char vlevel [] = { '0','a','b','c' };
     private const uint COMPUTER_INITIAL_DELAY = 1200;
     private const uint COMPUTER_MOVE_DELAY = 600;
 
@@ -50,7 +48,7 @@ private class FourInARow : Gtk.Application
     private Player last_first_player = Player.NOBODY;
     private Board game_board = new Board ();
     private bool one_player_game;
-    private uint8 ai_level;
+    private Difficulty ai_level;
     /**
      * score:
      *
@@ -68,7 +66,7 @@ private class FourInARow : Gtk.Application
     private MenuButton history_button_2;
 
     // game state
-    private char vstr [/* SIZE_VSTR */ 53];
+    private char [] vstr;
     private uint8 moves;
     private uint8 column;
     private uint8 column_moveto;
@@ -115,11 +113,15 @@ private class FourInARow : Gtk.Application
         return new FourInARow ().run (args);
     }
 
+    construct
+    {
+        vstr = new char [BOARD_ROWS * BOARD_COLUMNS + /* last chars are '0' and '\0', for something and for 
casting as string */ 2];
+        clear_board ();
+    }
+
     private FourInARow ()
     {
         Object (application_id: "org.gnome.Four-in-a-row", flags: ApplicationFlags.FLAGS_NONE);
-
-        clear_board ();
     }
 
     protected override void startup ()
@@ -331,7 +333,13 @@ private class FourInARow : Gtk.Application
                 player = settings.get_string ("first-player") == "computer" ? Player.OPPONENT : Player.HUMAN;
                 // we keep inverting that, because it would be surprising that all people use the "next 
round" thing
                 settings.set_string ("first-player", player == Player.HUMAN ? "computer" : "human");
-                ai_level = (uint8) settings.get_int ("opponent");
+                switch (settings.get_int ("opponent"))
+                {
+                    case 1 : ai_level = Difficulty.EASY;    break;
+                    case 2 : ai_level = Difficulty.MEDIUM;  break;
+                    case 3 : ai_level = Difficulty.HARD;    break;
+                    default: assert_not_reached ();
+                }
             }
             else
                 switch_players ();
@@ -353,9 +361,8 @@ private class FourInARow : Gtk.Application
         prompt_player ();
         if (!is_player_human ())
         {
-            vstr [0] = vlevel [ai_level];
             playgame_timeout = Timeout.add (COMPUTER_INITIAL_DELAY, () => {
-                    uint8 c = AI.playgame ((string) vstr);
+                    uint8 c = AI.playgame (ai_level, (string) vstr);
                     if (c >= BOARD_COLUMNS) // c could be uint8.MAX if board is full
                         return Source.REMOVE;
                     process_move ((uint8) c);
@@ -498,8 +505,9 @@ private class FourInARow : Gtk.Application
     {
         play_sound (SoundID.DROP);
 
-        vstr [++moves] = '1' + (char) c;
-        vstr [moves + 1] = '0';
+        vstr [moves] = (char) c /* string indicates columns between 1 and BOARD_COLUMNS */ + '1';
+        moves++;
+        vstr [moves] = '0';
 
         check_game_state ();
 
@@ -517,8 +525,7 @@ private class FourInARow : Gtk.Application
             if (!is_player_human ())
             {
                 playgame_timeout = Timeout.add (COMPUTER_MOVE_DELAY, () => {
-                        vstr [0] = vlevel [ai_level];
-                        uint8 col = AI.playgame ((string) vstr);
+                        uint8 col = AI.playgame (ai_level, (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);
@@ -652,13 +659,11 @@ private class FourInARow : Gtk.Application
     private void clear_board ()
     {
         game_board.clear ();
+        moves = 0;
 
-        for (uint8 i = 0; i < SIZE_VSTR; i++)
+        vstr [0] = '0';
+        for (uint8 i = 1; i < BOARD_ROWS * BOARD_COLUMNS + 2; i++)
             vstr [i] = '\0';
-
-        vstr [0] = vlevel [/* weak */ 1];
-        vstr [1] = '0';
-        moves = 0;
     }
 
     private inline void blink_tile (uint8 row, uint8 col, Player tile, uint8 n)
@@ -779,8 +784,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…"));
 
-        vstr [0] = vlevel [/* strong */ 3];
-        uint8 c = AI.playgame ((string) vstr);
+        uint8 c = AI.playgame (Difficulty.HARD, (string) vstr);
         if (c >= BOARD_COLUMNS)
             assert_not_reached ();  // c could be uint8.MAX if the board if full
 
@@ -807,11 +811,11 @@ private class FourInARow : Gtk.Application
         if (timeout != 0)
             return;
 
-        uint8 c = vstr [moves] - '0' - 1;
+        moves--;
+        uint8 c = vstr [moves] - '0' /* string indicates columns between 1 and BOARD_COLUMNS */ - 1;
         uint8 r = game_board.first_empty_row (c) + 1;
         vstr [moves] = '0';
         vstr [moves + 1] = '\0';
-        moves--;
 
         if (gameover)
         {
@@ -831,11 +835,11 @@ private class FourInARow : Gtk.Application
          && !is_player_human ()
          && moves > 0)
         {
-            c = vstr [moves] - '0' - 1;
+            moves--;
+            c = vstr [moves] - '0' /* string indicates columns between 1 and BOARD_COLUMNS */ - 1;
             r = game_board.first_empty_row (c) + 1;
             vstr [moves] = '0';
             vstr [moves + 1] = '\0';
-            moves--;
             swap_player ();
             move_cursor (c);
             game_board [r, c] = Player.NOBODY;
diff --git a/src/player.vala b/src/player.vala
index c6e36de..82e41cd 100644
--- a/src/player.vala
+++ b/src/player.vala
@@ -30,3 +30,10 @@ private enum Player
     HUMAN,
     OPPONENT;
 }
+
+private enum Difficulty
+{
+    EASY,
+    MEDIUM,
+    HARD;
+}
diff --git a/src/test-ai.vala b/src/test-ai.vala
index 33f13d7..ac160e6 100644
--- a/src/test-ai.vala
+++ b/src/test-ai.vala
@@ -36,7 +36,7 @@ private int main (string [] args)
     Test.add_func ("/AI/Avoid Loss/Vertical Loss",          test_avoid_vertical_loss);
     Test.add_func ("/AI/Avoid Loss/Forward Diagonal Loss",  test_avoid_forward_diagonal_loss);
     Test.add_func ("/AI/Avoid Loss/Backward Diagonal Loss", test_avoid_backward_diagonal_loss);
-    // test AI relative ranking
+    // test AI relative ranking; FIXME I think these tests are crazy
     Test.add_func ("/AI/AI vs AI/Easy vs Medium",           test_easy_vs_medium);
     Test.add_func ("/AI/AI vs AI/Easy vs Hard",             test_easy_vs_hard);
     Test.add_func ("/AI/AI vs AI/Medium vs Hard",           test_medium_vs_hard);
@@ -52,104 +52,104 @@ 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 ("a1727370") == 3);
-    assert_true (AI.playgame ("a7315651311324420") == 5);
-    assert_true (AI.playgame ("a232225657223561611133440") == 3);
-    assert_true (AI.playgame ("a242215322574255543341746677453337710") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "1727370") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "7315651311324420") == 5);
+    assert_true (AI.playgame (Difficulty.EASY, "232225657223561611133440") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "242215322574255543341746677453337710") == 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 ("a1213140") == 0);
-    assert_true (AI.playgame ("a14456535526613130") == 0);
-    assert_true (AI.playgame ("a432334277752576710") == 6);
-    assert_true (AI.playgame ("a547477454544323321712116260") == 1);
+    assert_true (AI.playgame (Difficulty.EASY, "1213140") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "14456535526613130") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "432334277752576710") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "547477454544323321712116260") == 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 ("a54221164712446211622157570") == 6);
-    assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
-    assert_true (AI.playgame ("a132565522322662666775443351131113540") == 3);
-    assert_true (AI.playgame ("a4571311334541225544112245262577767733360") == 5);
+    assert_true (AI.playgame (Difficulty.EASY, "54221164712446211622157570") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "4256424426621271412117175776343330") == 2);
+    assert_true (AI.playgame (Difficulty.EASY, "132565522322662666775443351131113540") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "4571311334541225544112245262577767733360") == 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 ("a5422327343142110") == 0);
-    assert_true (AI.playgame ("a1415113315143220") == 1);
-    assert_true (AI.playgame ("a547323452213345110") == 0);
-    assert_true (AI.playgame ("a4256424426621271412117175776343330") == 2);
+    assert_true (AI.playgame (Difficulty.EASY, "5422327343142110") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "1415113315143220") == 1);
+    assert_true (AI.playgame (Difficulty.EASY, "547323452213345110") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "4256424426621271412117175776343330") == 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 ("a42563117273430") == 2);
-    assert_true (AI.playgame ("a3642571541322340") == 3);
-    assert_true (AI.playgame ("a144566264475171137750") == 4);
-    assert_true (AI.playgame ("a54747745454432332171210") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "42563117273430") == 2);
+    assert_true (AI.playgame (Difficulty.EASY, "3642571541322340") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "144566264475171137750") == 4);
+    assert_true (AI.playgame (Difficulty.EASY, "54747745454432332171210") == 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 ("a34256477331566570") == 6);
-    assert_true (AI.playgame ("a1445662644751711370") == 6);
-    assert_true (AI.playgame ("a43442235372115113340") == 3);
-    assert_true (AI.playgame ("a4143525567766443543125411170") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "34256477331566570") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "1445662644751711370") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "43442235372115113340") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "4143525567766443543125411170") == 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 ("a47465234222530") == 2);
-    assert_true (AI.playgame ("a4344223537211510") == 0);
-    assert_true (AI.playgame ("a4141311525513520") == 1);
-    assert_true (AI.playgame ("a1445662644751711377553330") == 2);
+    assert_true (AI.playgame (Difficulty.EASY, "47465234222530") == 2);
+    assert_true (AI.playgame (Difficulty.EASY, "4344223537211510") == 0);
+    assert_true (AI.playgame (Difficulty.EASY, "4141311525513520") == 1);
+    assert_true (AI.playgame (Difficulty.EASY, "1445662644751711377553330") == 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 ("a445360") == 6);
-    assert_true (AI.playgame ("a745534131117114777720") == 1);
-    assert_true (AI.playgame ("a243466431217112323350") == 4);
-    assert_true (AI.playgame ("a24147356465355111336631615240") == 3);
+    assert_true (AI.playgame (Difficulty.EASY, "445360") == 6);
+    assert_true (AI.playgame (Difficulty.EASY, "745534131117114777720") == 1);
+    assert_true (AI.playgame (Difficulty.EASY, "243466431217112323350") == 4);
+    assert_true (AI.playgame (Difficulty.EASY, "24147356465355111336631615240") == 3);
 }
 
 /* Tests if AI can detect full boards, and thus draw games. */
 private static inline void test_draw ()
 {
-    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);
+    assert_true (AI.playgame (Difficulty.EASY, "1311313113652226667224247766737374455445550") == uint8.MAX);
+    assert_true (AI.playgame (Difficulty.EASY, "6121151135432322433425566474425617635677770") == uint8.MAX);
+    assert_true (AI.playgame (Difficulty.EASY, "4226111412113275256335534443264375577676670") == uint8.MAX);
+    assert_true (AI.playgame (Difficulty.EASY, "4212116575717754775221133434432366655342660") == 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 ("a443256214350");
+    uint8 x = AI.playgame (Difficulty.EASY, "443256214350");
     assert_true (x <= 6);
 
-    x = AI.playgame ("a241473564653551113366316150");
+    x = AI.playgame (Difficulty.EASY, "241473564653551113366316150");
     assert_true (x <= 6);
 
-    x = AI.playgame ("a24357315461711177416622623350");
+    x = AI.playgame (Difficulty.EASY, "24357315461711177416622623350");
     assert_true (x <= 6);
 
-    x = AI.playgame ("a1445662644751711377553333665775446110");
+    x = AI.playgame (Difficulty.EASY, "1445662644751711377553333665775446110");
     assert_true (x <= 6);
 }
 
 /* Pits two AI's of varying difficulty levels against each other and returns the number of games won by 
easier AI.*/
-private static inline uint8 test_ai_vs_ai (string easier, string harder)
+private static inline uint8 test_ai_vs_ai (Difficulty easier_AI, Difficulty harder_AI)
 {
     uint8 easier_wins = 0;
     uint8 draw = 0;
@@ -157,15 +157,15 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
 
     for (uint8 i = 0; i < NUMBER_GAMES; i++)
     {
-        StringBuilder e = new StringBuilder ();
-        e.append (easier);
+        StringBuilder easier = new StringBuilder ();
+        easier.append ("0");
 
-        StringBuilder m = new StringBuilder ();
-        m.append (harder);
+        StringBuilder harder = new StringBuilder ();
+        harder.append ("0");
 
         while (true)
         {
-            uint8 move = AI.playandcheck (e.str);
+            uint8 move = AI.playandcheck (easier_AI, easier.str);
             if (move == uint8.MAX)
             {
                 draw++;
@@ -178,10 +178,10 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
                 break;
             }
 
-            e.insert (e.str.length - 1, (move + 1).to_string ());
-            m.insert (m.str.length - 1, (move + 1).to_string ());
+            easier.insert (easier.str.length - 1, (move + 1).to_string ());
+            harder.insert (harder.str.length - 1, (move + 1).to_string ());
 
-            move = AI.playandcheck (m.str);
+            move = AI.playandcheck (harder_AI, harder.str);
 
             if (move == uint8.MAX)
             {
@@ -194,8 +194,8 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
                 harder_wins++;
                 break;
             }
-            e.insert (e.str.length - 1, (move + 1).to_string ());
-            m.insert (m.str.length - 1, (move + 1).to_string ());
+            easier.insert (easier.str.length - 1, (move + 1).to_string ());
+            harder.insert (harder.str.length - 1, (move + 1).to_string ());
         }
     }
     return easier_wins;
@@ -203,14 +203,14 @@ private static inline uint8 test_ai_vs_ai (string easier, string harder)
 
 /* Repeatedly contest between the two AI until either easier win ratio is less than a threshold
    or maximum numbers of contests have been played.*/
-private static inline void repeat_contests (string easier, string harder, out uint8 games_contested, out 
uint8 easy_wins)
+private static inline void repeat_contests (Difficulty easier_AI, Difficulty harder_AI, out uint8 
games_contested, out uint8 easy_wins)
 {
-    easy_wins = test_ai_vs_ai (easier, harder);
+    easy_wins = test_ai_vs_ai (easier_AI, harder_AI);
     games_contested = NUMBER_GAMES;
 
     while (games_contested <= MAXIMUM_GAMES && easy_wins > games_contested / THRESHOLD_DENOMINATOR)
     {
-        easy_wins += test_ai_vs_ai (easier, harder);
+        easy_wins += test_ai_vs_ai (easier_AI, harder_AI);
         games_contested += NUMBER_GAMES;
     }
 }
@@ -219,7 +219,7 @@ private static inline void test_easy_vs_medium ()
 {
     uint8 easy_wins;
     uint8 games_contested;
-    repeat_contests ("a0", "b0", out games_contested, out easy_wins);
+    repeat_contests (Difficulty.EASY, Difficulty.MEDIUM, out games_contested, out easy_wins);
 
     assert_true (easy_wins <= games_contested / THRESHOLD_DENOMINATOR);
 }
@@ -228,7 +228,7 @@ private static inline void test_easy_vs_hard ()
 {
     uint8 easy_wins;
     uint8 games_contested;
-    repeat_contests ("a0", "c0", out games_contested, out easy_wins);
+    repeat_contests (Difficulty.EASY, Difficulty.HARD, out games_contested, out easy_wins);
 
     assert_true (easy_wins <= games_contested / THRESHOLD_DENOMINATOR);
 }
@@ -237,7 +237,7 @@ private static inline void test_medium_vs_hard ()
 {
     uint8 medium_wins;
     uint8 games_contested;
-    repeat_contests ("b0", "c0", out games_contested, out medium_wins);
+    repeat_contests (Difficulty.MEDIUM, Difficulty.HARD, out games_contested, out medium_wins);
 
     assert_true (medium_wins <= games_contested / THRESHOLD_DENOMINATOR);
 }


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