[iagno] Analyse the game at the end of turn.



commit b066c3db7abefaa417b8a51293ded3c15ffff96c
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Tue Sep 30 23:22:30 2014 +0200

    Analyse the game at the end of turn.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=737410

 src/computer-player.vala |   57 +++++++++++++++++++-----------------
 src/game-view.vala       |    4 +-
 src/game.vala            |   72 +++++++++++++++++++++++++---------------------
 src/iagno.vala           |   65 +++++++++++++++++++++++------------------
 src/test-iagno.vala      |    6 ++--
 5 files changed, 111 insertions(+), 93 deletions(-)
---
diff --git a/src/computer-player.vala b/src/computer-player.vala
index 4b18459..871c312 100644
--- a/src/computer-player.vala
+++ b/src/computer-player.vala
@@ -160,7 +160,7 @@ public class ComputerPlayer : Object
     }
 
     private void run_search (ref int x, ref int y)
-        requires (game.can_move (game.current_color))
+        requires (game.current_player_can_move)
     {
         /* For the first two moves play randomly so the game is not always the same */
         if (game.n_tiles < 8)
@@ -180,46 +180,40 @@ public class ComputerPlayer : Object
         requires (a <= b)
     {
         /* End of the game, return a near-infinite evaluation */
-        if (g.is_complete ())
+        if (g.is_complete)
         {
             var n_current_tiles = g.n_current_tiles;
             var n_enemy_tiles = g.n_opponent_tiles;
             return n_current_tiles > n_enemy_tiles ? POSITIVE_INFINITY - n_enemy_tiles : NEGATIVE_INFINITY + 
n_current_tiles;
         }
 
-        /* End of the search, calculate how good a result this is.
-         * Checking move_pending here is optional. It helps avoid a long unnecessary search
+        /* Checking move_pending here is optional. It helps avoid a long unnecessary search
          * if the move has been cancelled, but is expensive because it requires taking a mutex. */
-        if (depth == 0 || !move_pending)
+        if (!move_pending)
+            return 0;
+
+        /* End of the search, calculate how good a result this is. */
+        if (depth == 0)
             return calculate_heuristic (g);
 
-        /* Find all possible moves and sort from most new tiles to least new tiles */
         List<PossibleMove?> moves = null;
-        for (var x = 0; x < g.size; x++)
+        if (g.current_player_can_move)
         {
-            for (var y = 0; y < g.size; y++)
+            /* Find all possible moves and sort from most new tiles to least new tiles */
+            for (var x = 0; x < g.size; x++)
             {
-                var n_tiles = g.place_tile (x, y);
-                if (n_tiles <= 0)
-                    continue;
-
-                var move = PossibleMove (x, y, n_tiles);
-                moves.insert_sorted (move, compare_move);
-                g.undo ();
+                for (var y = 0; y < g.size; y++)
+                {
+                    var n_tiles = g.place_tile (x, y);
+                    if (n_tiles <= 0)
+                        continue;
+
+                    var move = PossibleMove (x, y, n_tiles);
+                    moves.insert_sorted (move, compare_move);
+                    g.undo ();
+                }
             }
-        }
 
-        if (moves == null)
-        {
-            /* The move.ntiles = 0 is used next to know we have to pass.
-             * The move.x = move.y = 0 is never used: move.x, move.y, move_x & move_y
-             * are only used at first iteration… and the game passes if there’s no move.
-             */
-            var move = PossibleMove (0, 0, 0);
-            moves.append (move);
-        }
-        else
-        {
             /* We use (0, 0) as our default move; if we don't change that,
              * a search could select it even if invalid at the end of the game.
              */
@@ -227,6 +221,15 @@ public class ComputerPlayer : Object
             move_x = move.x;
             move_y = move.y;
         }
+        else
+        {
+            /* The move.ntiles = 0 is used next to know we have to pass.
+             * The move.x = move.y = 0 is never used: move.x, move.y, move_x & move_y
+             * are only used at first iteration… and the game passes if there's no move.
+             */
+            var move = PossibleMove (0, 0, 0);
+            moves.append (move);
+        }
 
         /* Try each move using alpha-beta pruning to optimise finding the best branch */
         foreach (var move in moves)
diff --git a/src/game-view.vala b/src/game-view.vala
index d863ae1..3eb5bfd 100644
--- a/src/game-view.vala
+++ b/src/game-view.vala
@@ -188,7 +188,7 @@ public class GameView : Gtk.DrawingArea
         var pixmap = get_pixmap (game.get_owner (x, y));
 
         /* Show the result by laying the tiles with winning color first */
-        if (flip_final_result_now && game.is_complete ())
+        if (flip_final_result_now && game.is_complete)
         {
             var n = y * game.size + x;
             var winning_color = Player.LIGHT;
@@ -218,7 +218,7 @@ public class GameView : Gtk.DrawingArea
 
         set_square (x, y, pixmap);
 
-        if (game.is_complete () && game.n_light_tiles > 0 && game.n_dark_tiles > 0)
+        if (game.is_complete && game.n_light_tiles > 0 && game.n_dark_tiles > 0)
         {
             /*
              * Show the actual final positions of the pieces before flipping the board.
diff --git a/src/game.vala b/src/game.vala
index 41e0d96..b9fb156 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -30,12 +30,14 @@ public class Game : Object
     public Player current_color { get; private set; default = Player.DARK; }
     public int number_of_moves { get; private set; default = 0; }
 
+    /* Indicate who's the next player who can move */
+    public bool current_player_can_move { get; private set; default = true; }
+    public bool is_complete { get; private set; default = false; }
+
     /* Indicate that a player should move */
-    public signal void move ();
+    public signal void turn_ended ();
     /* Indicate a square has changed */
     public signal void square_changed (int x, int y);
-    /* Indicate the game is complete */
-    public signal void complete ();
 
     /*\
     * * Number of tiles on the board
@@ -171,26 +173,6 @@ public class Game : Object
         return tiles[x, y];
     }
 
-    public bool is_complete ()
-        ensures (result || n_tiles < size * size)
-    {
-        return !can_move (null);
-    }
-
-    public bool can_move (Player? color)
-        requires (color != Player.NONE)
-    {
-        for (var x = 0; x < size; x++)
-            for (var y = 0; y < size; y++)
-            {
-                if (color != Player.DARK && can_place (x, y, Player.LIGHT))
-                    return true;
-                if (color != Player.LIGHT && can_place (x, y, Player.DARK))
-                    return true;
-            }
-        return false;
-    }
-
     public bool can_place (int x, int y, Player color)
         requires (is_valid_location (x, y))
         requires (color != Player.NONE)
@@ -235,20 +217,13 @@ public class Game : Object
         set_tile (x, y);
         end_of_turn ();
 
-        if (is_complete ())
-            complete ();
-        else
-            move ();
-
         return tiles_turned;
     }
 
     public void pass ()
-        requires (!can_move (current_color))
+        requires (!current_player_can_move)
     {
         end_of_turn ();
-
-        move ();
     }
 
     private void end_of_turn ()
@@ -258,6 +233,30 @@ public class Game : Object
         number_of_moves++;
         history_index++;
         undo_stack[history_index] = null;
+        update_who_can_move ();
+        turn_ended ();
+    }
+
+    private void update_who_can_move ()
+    {
+        var enemy = Player.flip_color (current_color);
+        var opponent_can_move = false;
+        for (var x = 0; x < size; x++)
+        {
+            for (var y = 0; y < size; y++)
+            {
+                if (can_place (x, y, current_color))
+                {
+                    current_player_can_move = true;
+                    return;
+                }
+                if (can_place (x, y, enemy))
+                    opponent_can_move = true;
+            }
+        }
+        current_player_can_move = false;
+        if (!opponent_can_move)
+            is_complete = true;
     }
 
     /*\
@@ -337,8 +336,15 @@ public class Game : Object
             }
         }
 
-        if (count == 2)
-            undo (1);
+        if (count == 1)
+        {
+            is_complete = false;
+            update_who_can_move ();
+        }
+        else
+        {
+            undo (count - 1);
+        }
     }
 
     private void unset_tile (int tile_number, Player replacement_color)
diff --git a/src/iagno.vala b/src/iagno.vala
index 0a8c7ac..ed897fd 100644
--- a/src/iagno.vala
+++ b/src/iagno.vala
@@ -316,8 +316,7 @@ public class Iagno : Gtk.Application
         show_game_board ();
 
         game = new Game (size);
-        game.move.connect (game_move_cb);
-        game.complete.connect (game_complete_cb);
+        game.turn_ended.connect (turn_ended_cb);
         view.game = game;
         view.show ();
         game_box.pack_start (view);
@@ -367,7 +366,7 @@ public class Iagno : Gtk.Application
         if (computer == null)
         {
             game.undo (1);
-            if (!game.can_move (game.current_color))
+            if (!game.current_player_can_move)
                 game.undo (1);
         }
         else
@@ -381,11 +380,12 @@ public class Iagno : Gtk.Application
                 game.undo (2);
 
             /* If forced to pass, undo to last chosen move so the computer doesn't play next */
-            while (!game.can_move (game.current_color))
+            while (!game.current_player_can_move)
                 game.undo (2);
         }
 
-        game_move_cb ();
+        update_ui ();
+        play_sound ("flip-piece");
     }
 
     private void about_cb ()
@@ -427,27 +427,21 @@ public class Iagno : Gtk.Application
         }
     }
 
-    private void game_move_cb ()
+    private void turn_ended_cb ()
     {
-        play_sound ("flip-piece");
-
-        if (!game.can_move (game.current_color))
-        {
-            game.pass ();
-            if (game.current_color == Player.DARK)
-            {
-                /* Message to display when Light has no possible moves */
-                headerbar.set_subtitle (_("Light must pass, Dark’s move"));
-            }
-            else
-            {
-                /* Message to display when Dark has no possible moves */
-                headerbar.set_subtitle (_("Dark must pass, Light’s move"));
-            }
-            return;
-        }
-
         update_ui ();
+        if (game.current_player_can_move)
+            prepare_move ();
+        else if (game.is_complete)
+            game_complete ();
+        else
+            pass ();
+    }
+
+    private void prepare_move ()
+    {
+        /* for the move that just ended */
+        play_sound ("flip-piece");
 
         /*
          * Get the computer to move after a delay, so it looks like it's
@@ -463,10 +457,26 @@ public class Iagno : Gtk.Application
         }
     }
 
-    private void game_complete_cb ()
+    private void pass ()
     {
-        update_ui ();
+        /* for the move that just ended */
+        play_sound ("flip-piece");
 
+        game.pass ();
+        if (game.current_color == Player.DARK)
+        {
+            /* Message to display when Light has no possible moves */
+            headerbar.set_subtitle (_("Light must pass, Dark’s move"));
+        }
+        else
+        {
+            /* Message to display when Dark has no possible moves */
+            headerbar.set_subtitle (_("Dark must pass, Light’s move"));
+        }
+    }
+
+    private void game_complete ()
+    {
         if (game.n_light_tiles > game.n_dark_tiles)
         {
             /* Message to display when Light has won the game */
@@ -477,12 +487,11 @@ public class Iagno : Gtk.Application
             /* Message to display when Dark has won the game */
             headerbar.set_subtitle (_("Dark wins!"));
         }
-        else if (game.n_light_tiles == game.n_dark_tiles)
+        else
         {
             /* Message to display when the game is a draw */
             headerbar.set_subtitle (_("The game is draw."));
         }
-        else assert_not_reached ();
 
         play_sound ("gameover");
     }
diff --git a/src/test-iagno.vala b/src/test-iagno.vala
index 73fc4fa..8f9759a 100644
--- a/src/test-iagno.vala
+++ b/src/test-iagno.vala
@@ -34,7 +34,7 @@ public class TestIagno : Object
         assert (game.number_of_moves == 0);
         assert (game.place_tile (7, 2) > 0);
         assert (game.number_of_moves == 1);
-        assert (!game.can_move (Player.LIGHT));
+        assert (!game.current_player_can_move);
         game.pass ();
         assert (game.number_of_moves == 2);
         game.undo (2);
@@ -42,7 +42,7 @@ public class TestIagno : Object
         assert (game.to_string ().strip () == string.joinv ("\n", board).strip ());
         assert (game.place_tile (7, 2) > 0);
         assert (game.number_of_moves == 1);
-        assert (!game.can_move (Player.LIGHT));
+        assert (!game.current_player_can_move);
         game.undo (1);
         assert (game.number_of_moves == 0);
         assert (game.to_string ().strip () == string.joinv ("\n", board).strip ());
@@ -72,7 +72,7 @@ public class TestIagno : Object
         assert (game.current_color == Player.DARK);
         assert (game.place_tile (1, 2) > 0);
         assert (game.current_color == Player.LIGHT);
-        assert (!game.can_move (Player.LIGHT));
+        assert (!game.current_player_can_move);
         game.pass ();
         assert (game.current_color == Player.DARK);
     }


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