[iagno] Introduce human opening.



commit 52e78b677aa945712013b870bd74c3346d6b0566
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Fri Sep 13 16:30:20 2019 +0200

    Introduce human opening.
    
    For two players game.
    Needs a bit love with
    perhaps explaination.

 data/themes/high_contrast.theme.desktop.in |  16 +-
 src/game.vala                              | 322 ++++++++++++++++++++++-------
 src/iagno.vala                             |  27 ++-
 src/reversi-view.vala                      |  85 +++++++-
 4 files changed, 359 insertions(+), 91 deletions(-)
---
diff --git a/data/themes/high_contrast.theme.desktop.in b/data/themes/high_contrast.theme.desktop.in
index 032451f..05c6792 100644
--- a/data/themes/high_contrast.theme.desktop.in
+++ b/data/themes/high_contrast.theme.desktop.in
@@ -53,16 +53,16 @@ Blue=0.0
 Width=3
 
 [Highlight hard]
-Red=0.7
-Green=0.7
-Blue=0.7
-Alpha=1.0
+Red=0.35
+Green=0.35
+Blue=0.35
+Alpha=0.5
 
 [Highlight soft]
-Red=0.7
-Green=0.7
-Blue=0.7
-Alpha=0.6
+Red=0.35
+Green=0.35
+Blue=0.35
+Alpha=0.3
 
 [Margin]
 #Width=0
diff --git a/src/game.vala b/src/game.vala
index 72c39bf..3053b57 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -168,6 +168,68 @@ private struct GameStateStruct
             n_opponent_tiles++;
     }
 
+    internal GameStateStruct.empty (uint8 _size, uint8 [,] _neighbor_tiles)
+    {
+        // move color
+        current_color = Player.DARK;    // Dark always starts
+        opponent_color = Player.LIGHT;
+
+        // always given
+        size = _size;
+        neighbor_tiles = _neighbor_tiles;
+
+        // tiles grid
+        tiles = new Player [_size, _size];
+        for (uint8 x = 0; x < _size; x++)
+            for (uint8 y = 0; y < _size; y++)
+                tiles [x, y] = Player.NONE;
+
+        // tiles counters
+        n_current_tiles = 0;
+        n_opponent_tiles = 0;
+        n_tiles = 0;
+
+        // empty neighbors
+        init_empty_neighbors ();    // could do better
+
+        // who can move
+        current_player_can_move = true;
+        is_complete = false;
+        x_saved = size / 2 - 2;
+        y_saved = size / 2 - 2;
+    }
+
+    internal GameStateStruct.copy_and_add (GameStateStruct game, uint8 x, uint8 y)
+    {
+        // move color
+        opponent_color = game.current_color;
+        current_color = Player.flip_color (opponent_color);
+
+        // always given
+        size = game.size;
+        neighbor_tiles = game.neighbor_tiles;
+
+        // tiles grid
+        tiles = game.tiles;
+        if (tiles [x, y] != Player.NONE)
+            assert_not_reached ();
+        tiles [x, y] = opponent_color;
+
+        // tiles counters
+        n_current_tiles = game.n_opponent_tiles;
+        n_opponent_tiles = game.n_current_tiles + 1;
+        n_tiles = n_current_tiles + n_opponent_tiles;
+
+        // empty neighbors
+        init_empty_neighbors ();    // lazyness
+
+        // who can move
+        current_player_can_move = true;
+        is_complete = false;
+        x_saved = size / 2 - 2;
+        y_saved = size / 2 - 2;
+    }
+
     /*\
     * * public information
     \*/
@@ -508,6 +570,16 @@ private class GameStateObject : Object
         _game_state_struct = GameStateStruct.from_grid (size, tiles, color, neighbor_tiles);
     }
 
+    internal GameStateObject.empty (uint8 size, uint8 [,] neighbor_tiles)
+    {
+        _game_state_struct = GameStateStruct.empty (size, neighbor_tiles);
+    }
+
+    internal GameStateObject.copy_and_add (GameStateObject game, uint8 x, uint8 y)
+    {
+        _game_state_struct = GameStateStruct.copy_and_add (game.game_state_struct, x, y);
+    }
+
     /*\
     * * public information
     \*/
@@ -556,7 +628,7 @@ private class Game : Object
 
     [CCode (notify = false)] public uint8           size                    { internal get; protected 
construct;     }
     [CCode (notify = false)] public bool            reverse                 { internal get; protected 
construct;     }
-    [CCode (notify = false)] public Opening         opening                 { internal get; protected 
construct;     }
+    [CCode (notify = false)] public Opening         opening                 { internal get; protected 
construct set; }
     [CCode (notify = false)] public GameStateObject current_state           { internal get; protected 
construct set; }
     [CCode (notify = false)] public uint8           initial_number_of_tiles { internal get; protected 
construct;     }
 
@@ -570,78 +642,32 @@ private class Game : Object
         requires (_size >= 4)
         requires (_size <= 16)
     {
-        bool even_board = (_size % 2 == 0);
-
-        Player [,] tiles = new Player [_size, _size];
-
-        for (uint8 x = 0; x < _size; x++)
-            for (uint8 y = 0; y < _size; y++)
-                tiles [x, y] = Player.NONE;
+        uint8 [,] _neighbor_tiles;
+        init_neighbor_tiles (_size, out _neighbor_tiles);
 
+        GameStateObject _current_state;
         uint8 _initial_number_of_tiles;
-        if (even_board)
+        if (_opening == Opening.HUMANS)
         {
-            /* setup board with four tiles by default */
-            uint8 half_size = _size / 2;
-            _initial_number_of_tiles = 4;
-            Player [,] start_position;
-            switch (_opening)
-            {
-                case Opening.REVERSI:      start_position = {{ Player.LIGHT, Player.DARK  }, { Player.DARK , 
Player.LIGHT }}; break;
-                case Opening.INVERTED:     start_position = {{ Player.DARK , Player.LIGHT }, { Player.LIGHT, 
Player.DARK  }}; break;
-                case Opening.ALTER_TOP:    start_position = {{ Player.DARK , Player.DARK  }, { Player.LIGHT, 
Player.LIGHT }}; break;
-                case Opening.ALTER_LEFT:   start_position = {{ Player.DARK , Player.LIGHT }, { Player.DARK , 
Player.LIGHT }}; break;
-                case Opening.ALTER_RIGHT:  start_position = {{ Player.LIGHT, Player.DARK  }, { Player.LIGHT, 
Player.DARK  }}; break;
-                case Opening.ALTER_BOTTOM: start_position = {{ Player.LIGHT, Player.LIGHT }, { Player.DARK , 
Player.DARK  }}; break;
-                default: assert_not_reached ();
-            }
-            tiles [half_size - 1, half_size - 1] = start_position [0, 0];
-            tiles [half_size    , half_size - 1] = start_position [0, 1];
-            tiles [half_size - 1, half_size    ] = start_position [1, 0];
-            tiles [half_size    , half_size    ] = start_position [1, 1];
+            _initial_number_of_tiles = 0;
+
+            _current_state = new GameStateObject.empty (_size, _neighbor_tiles);
         }
         else
         {
-            /* logical starting position for odd board */
-            uint8 mid_board = (_size - 1) / 2;
-            _initial_number_of_tiles = 7;
-            Player [,] start_position;
-            switch (_opening)
-            {
-                case Opening.REVERSI:      start_position = {{ Player.NONE , Player.LIGHT, Player.DARK  },
-                                                             { Player.LIGHT, Player.DARK , Player.LIGHT },
-                                                             { Player.DARK , Player.LIGHT, Player.NONE  }}; 
break;
-                case Opening.INVERTED:     start_position = {{ Player.DARK , Player.LIGHT, Player.NONE  },
-                                                             { Player.LIGHT, Player.DARK , Player.LIGHT },
-                                                             { Player.NONE , Player.LIGHT, Player.DARK  }}; 
break;
-                case Opening.ALTER_TOP:    start_position = {{ Player.NONE , Player.DARK , Player.LIGHT },
-                                                             { Player.LIGHT, Player.DARK , Player.LIGHT },
-                                                             { Player.LIGHT, Player.DARK , Player.NONE  }}; 
break;
-                case Opening.ALTER_LEFT:   start_position = {{ Player.LIGHT, Player.LIGHT, Player.NONE  },
-                                                             { Player.DARK , Player.DARK , Player.DARK  },
-                                                             { Player.NONE , Player.LIGHT, Player.LIGHT }}; 
break;
-                case Opening.ALTER_RIGHT:  start_position = {{ Player.NONE , Player.LIGHT, Player.LIGHT },
-                                                             { Player.DARK , Player.DARK , Player.DARK  },
-                                                             { Player.LIGHT, Player.LIGHT, Player.NONE  }}; 
break;
-                case Opening.ALTER_BOTTOM: start_position = {{ Player.LIGHT, Player.DARK , Player.NONE  },
-                                                             { Player.LIGHT, Player.DARK , Player.LIGHT },
-                                                             { Player.NONE , Player.DARK , Player.LIGHT }}; 
break;
-                default: assert_not_reached ();
-            }
-            tiles [mid_board - 1, mid_board - 1] = start_position [0, 0];
-            tiles [mid_board    , mid_board - 1] = start_position [0, 1];
-            tiles [mid_board + 1, mid_board - 1] = start_position [0, 2];
-            tiles [mid_board - 1, mid_board    ] = start_position [1, 0];
-            tiles [mid_board    , mid_board    ] = Player.DARK;
-            tiles [mid_board + 1, mid_board    ] = start_position [1, 2];
-            tiles [mid_board - 1, mid_board + 1] = start_position [2, 0];
-            tiles [mid_board    , mid_board + 1] = start_position [2, 1];
-            tiles [mid_board + 1, mid_board + 1] = start_position [2, 2];
-        }
+            Player [,] tiles = new Player [_size, _size];
 
-        uint8 [,] _neighbor_tiles;
-        init_neighbor_tiles (_size, out _neighbor_tiles);
-        GameStateObject _current_state = new GameStateObject.from_grid (_size, tiles, /* Dark always starts 
*/ Player.DARK, _neighbor_tiles);
+            for (uint8 x = 0; x < _size; x++)
+                for (uint8 y = 0; y < _size; y++)
+                    tiles [x, y] = Player.NONE;
+
+            if (_size % 2 == 0)
+                setup_even_board (_size, _opening, ref tiles, out _initial_number_of_tiles);
+            else
+                setup_odd_board  (_size, _opening, ref tiles, out _initial_number_of_tiles);
+
+            _current_state = new GameStateObject.from_grid (_size, tiles, /* Dark always starts */ 
Player.DARK, _neighbor_tiles);
+        }
 
         Object (size                    : _size,
                 reverse                 : _reverse,
@@ -650,6 +676,65 @@ private class Game : Object
                 initial_number_of_tiles : _initial_number_of_tiles);
         neighbor_tiles = (owned) _neighbor_tiles;
     }
+    private static inline void setup_even_board (uint8 size, Opening opening, ref Player [,] tiles, out 
uint8 initial_number_of_tiles)
+    {
+        /* setup board with four tiles by default */
+        uint8 half_size = size / 2;
+        initial_number_of_tiles = 4;
+        Player [,] start_position;
+        switch (opening)
+        {
+            case Opening.REVERSI:      start_position = {{ Player.LIGHT, Player.DARK  }, { Player.DARK , 
Player.LIGHT }}; break;
+            case Opening.INVERTED:     start_position = {{ Player.DARK , Player.LIGHT }, { Player.LIGHT, 
Player.DARK  }}; break;
+            case Opening.ALTER_TOP:    start_position = {{ Player.DARK , Player.DARK  }, { Player.LIGHT, 
Player.LIGHT }}; break;
+            case Opening.ALTER_LEFT:   start_position = {{ Player.DARK , Player.LIGHT }, { Player.DARK , 
Player.LIGHT }}; break;
+            case Opening.ALTER_RIGHT:  start_position = {{ Player.LIGHT, Player.DARK  }, { Player.LIGHT, 
Player.DARK  }}; break;
+            case Opening.ALTER_BOTTOM: start_position = {{ Player.LIGHT, Player.LIGHT }, { Player.DARK , 
Player.DARK  }}; break;
+            default: assert_not_reached ();
+        }
+        tiles [half_size - 1, half_size - 1] = start_position [0, 0];
+        tiles [half_size    , half_size - 1] = start_position [0, 1];
+        tiles [half_size - 1, half_size    ] = start_position [1, 0];
+        tiles [half_size    , half_size    ] = start_position [1, 1];
+    }
+    private static inline void setup_odd_board (uint8 size, Opening opening, ref Player [,] tiles, out uint8 
initial_number_of_tiles)
+    {
+        /* logical starting position for odd board */
+        uint8 mid_board = (size - 1) / 2;
+        initial_number_of_tiles = 7;
+        Player [,] start_position;
+        switch (opening)
+        {
+            case Opening.REVERSI:      start_position = {{ Player.NONE , Player.LIGHT, Player.DARK  },
+                                                         { Player.LIGHT, Player.DARK , Player.LIGHT },
+                                                         { Player.DARK , Player.LIGHT, Player.NONE  }}; 
break;
+            case Opening.INVERTED:     start_position = {{ Player.DARK , Player.LIGHT, Player.NONE  },
+                                                         { Player.LIGHT, Player.DARK , Player.LIGHT },
+                                                         { Player.NONE , Player.LIGHT, Player.DARK  }}; 
break;
+            case Opening.ALTER_TOP:    start_position = {{ Player.NONE , Player.DARK , Player.LIGHT },
+                                                         { Player.LIGHT, Player.DARK , Player.LIGHT },
+                                                         { Player.LIGHT, Player.DARK , Player.NONE  }}; 
break;
+            case Opening.ALTER_LEFT:   start_position = {{ Player.LIGHT, Player.LIGHT, Player.NONE  },
+                                                         { Player.DARK , Player.DARK , Player.DARK  },
+                                                         { Player.NONE , Player.LIGHT, Player.LIGHT }}; 
break;
+            case Opening.ALTER_RIGHT:  start_position = {{ Player.NONE , Player.LIGHT, Player.LIGHT },
+                                                         { Player.DARK , Player.DARK , Player.DARK  },
+                                                         { Player.LIGHT, Player.LIGHT, Player.NONE  }}; 
break;
+            case Opening.ALTER_BOTTOM: start_position = {{ Player.LIGHT, Player.DARK , Player.NONE  },
+                                                         { Player.LIGHT, Player.DARK , Player.LIGHT },
+                                                         { Player.NONE , Player.DARK , Player.LIGHT }}; 
break;
+            default: assert_not_reached ();
+        }
+        tiles [mid_board - 1, mid_board - 1] = start_position [0, 0];
+        tiles [mid_board    , mid_board - 1] = start_position [0, 1];
+        tiles [mid_board + 1, mid_board - 1] = start_position [0, 2];
+        tiles [mid_board - 1, mid_board    ] = start_position [1, 0];
+        tiles [mid_board    , mid_board    ] = Player.DARK;
+        tiles [mid_board + 1, mid_board    ] = start_position [1, 2];
+        tiles [mid_board - 1, mid_board + 1] = start_position [2, 0];
+        tiles [mid_board    , mid_board + 1] = start_position [2, 1];
+        tiles [mid_board + 1, mid_board + 1] = start_position [2, 2];
+    }
 
     internal Game.from_strings (string [] setup, Player to_move, bool _reverse = false, uint8 _size = 8)
         requires (_size >= 4)
@@ -657,6 +742,9 @@ private class Game : Object
         requires (to_move != Player.NONE)
         requires (setup.length == _size)
     {
+        uint8 [,] _neighbor_tiles;
+        init_neighbor_tiles (_size, out _neighbor_tiles);
+
         Player [,] tiles = new Player [_size, _size];
 
         for (uint8 y = 0; y < _size; y++)
@@ -667,8 +755,6 @@ private class Game : Object
                 tiles [x, y] = Player.from_char (setup [y] [x * 2 + 1]);
         }
 
-        uint8 [,] _neighbor_tiles;
-        init_neighbor_tiles (_size, out _neighbor_tiles);
         GameStateObject _current_state = new GameStateObject.from_grid (_size, tiles, to_move, 
_neighbor_tiles);
 
         Object (size                    : _size,
@@ -709,6 +795,48 @@ private class Game : Object
 
     internal /* success */ bool place_tile (uint8 x, uint8 y)
     {
+        if (opening == Opening.HUMANS)
+        {
+            uint8 half_game_size = size / 2;
+            if (x < half_game_size - 1 || x > half_game_size
+             || y < half_game_size - 1 || y > half_game_size)
+                return false;
+
+            if (current_color == Player.LIGHT && n_light_tiles == 0)
+            {
+                uint8 opposite_x = x == half_game_size ? half_game_size - 1 : half_game_size;
+                uint8 opposite_y = y == half_game_size ? half_game_size - 1 : half_game_size;
+                if (get_owner (opposite_x, opposite_y) == Player.DARK)
+                    return false;
+            }
+
+            if (current_state.n_dark_tiles == 2)
+            {
+                if (get_owner (half_game_size - 1, half_game_size - 1) == Player.DARK)
+                {
+                    if (get_owner (half_game_size, half_game_size - 1) == Player.DARK)  opening = 
Opening.ALTER_TOP;
+                    else if (get_owner (half_game_size, half_game_size) == Player.DARK) opening = 
Opening.REVERSI;
+                    else                                                                opening = 
Opening.ALTER_LEFT;
+                }
+                else
+                {
+                    if (get_owner (half_game_size, half_game_size - 1) == Player.LIGHT) opening = 
Opening.ALTER_BOTTOM;
+                    else if (get_owner (half_game_size, half_game_size) == Player.DARK) opening = 
Opening.ALTER_RIGHT;
+                    else                                                                opening = 
Opening.INVERTED;
+                }
+            }
+            current_state = new GameStateObject.copy_and_add (current_state, x, y);
+
+            if (n_light_tiles == 2)
+            {
+                undo_stack.remove (0);
+                undo_stack.append (current_state);
+            }
+
+            end_of_turn (/* undoing */ false, /* no_draw */ false);
+            return true;
+        }
+
         PossibleMove move;
         if (!current_state.test_placing_tile (x, y, out move))
             return false;
@@ -808,6 +936,28 @@ private class Game : Object
 
     internal bool test_placing_tile (uint8 x, uint8 y, out unowned PossibleMove move)
     {
+        if (opening == Opening.HUMANS)
+        {
+            move = PossibleMove (x, y); // good enough
+
+            uint8 half_game_size = size / 2;
+            if (x < half_game_size - 1 || x > half_game_size
+             || y < half_game_size - 1 || y > half_game_size)
+                return false;
+
+            if (get_owner (x, y) != Player.NONE)
+                return false;
+
+            if (current_color == Player.LIGHT && n_light_tiles == 0)
+            {
+                uint8 opposite_x = x == half_game_size ? half_game_size - 1 : half_game_size;
+                uint8 opposite_y = y == half_game_size ? half_game_size - 1 : half_game_size;
+                if (get_owner (opposite_x, opposite_y) == Player.DARK)
+                    return false;
+            }
+            return true;
+        }
+
         unowned SList<PossibleMove?>? test_move = possible_moves.nth (0);
         while (test_move != null)
         {
@@ -830,11 +980,45 @@ private class Game : Object
 
     private inline void update_possible_moves ()
     {
-        current_state.get_possible_moves (out possible_moves);
+        if (opening == Opening.HUMANS)
+        {
+            possible_moves = new SList<PossibleMove?> ();
+
+            uint8 half_game_size = size / 2;
+
+            bool top_left;
+            bool top_right;
+            bool bottom_left;
+            bool bottom_right;
+
+            if (current_color == Player.LIGHT && n_light_tiles == 0)
+            {
+                top_left = get_owner (half_game_size - 1, half_game_size - 1) == Player.NONE
+                        && get_owner (half_game_size    , half_game_size    ) == Player.NONE;
+                top_right    = !top_left;
+                bottom_left  = !top_left;
+                bottom_right = top_left;
+            }
+            else
+            {
+                top_left     = get_owner (half_game_size - 1, half_game_size - 1) == Player.NONE;
+                top_right    = get_owner (half_game_size    , half_game_size - 1) == Player.NONE;
+                bottom_left  = get_owner (half_game_size - 1, half_game_size    ) == Player.NONE;
+                bottom_right = get_owner (half_game_size    , half_game_size    ) == Player.NONE;
+            }
+
+            if (top_left)     possible_moves.prepend (PossibleMove (half_game_size - 1, half_game_size - 1));
+            if (top_right)    possible_moves.prepend (PossibleMove (half_game_size    , half_game_size - 1));
+            if (bottom_left)  possible_moves.prepend (PossibleMove (half_game_size - 1, half_game_size    ));
+            if (bottom_right) possible_moves.prepend (PossibleMove (half_game_size    , half_game_size    ));
+        }
+        else
+            current_state.get_possible_moves (out possible_moves);
     }
 }
 
 private enum Opening {
+    HUMANS,
     REVERSI,
     INVERTED,
     ALTER_TOP,
diff --git a/src/iagno.vala b/src/iagno.vala
index f1103b3..fd919a7 100644
--- a/src/iagno.vala
+++ b/src/iagno.vala
@@ -266,12 +266,6 @@ private class Iagno : Gtk.Application, BaseApplication
         size_menu.append_section (null, section);
 
         section = new GLib.Menu ();
-        if (!alternative_start && !random_start && !usual_start)
-        {
-            /* Translators: when configuring a new game, in the first menubutton's menu, label of the entry 
to choose to use randomly an alternative start position (with a mnemonic that appears pressing Alt) */
-            section.append (_("_Random start position"), "app.random-start-position");
-        }
-
         /* Translators: when configuring a new game, in the first menubutton's menu, label of the entry to 
choose to alternate who starts between human and AI (with a mnemonic that appears pressing Alt) */
         section.append (_("_Alternate who starts"), "app.alternate-who-starts");
         section.freeze ();
@@ -280,6 +274,7 @@ private class Iagno : Gtk.Application, BaseApplication
         size_menu.freeze ();
 
         GLib.Menu theme_menu = new GLib.Menu ();
+        section = new GLib.Menu ();
         /* Translators: when configuring a new game, in the second menubutton's menu, label of the entry to 
choose an easy-level computer adversary (with a mnemonic that appears pressing Alt) */
         theme_menu.append (_("_Easy"),   "app.change-level('1')");
 
@@ -290,6 +285,17 @@ private class Iagno : Gtk.Application, BaseApplication
 
         /* Translators: when configuring a new game, in the second menubutton's menu, label of the entry to 
choose an hard-level computer adversary (with a mnemonic that appears pressing Alt) */
         theme_menu.append (_("_Hard"),   "app.change-level('3')");
+        section.freeze ();
+        theme_menu.append_section (null, section);
+
+        if (!alternative_start && !random_start && !usual_start)
+        {
+            section = new GLib.Menu ();
+            /* Translators: when configuring a new game, in the first menubutton's menu, label of the entry 
to choose to use randomly an alternative start position (with a mnemonic that appears pressing Alt) */
+            section.append (_("_Vary start position"), "app.random-start-position");
+            section.freeze ();
+            theme_menu.append_section (null, section);
+        }
         theme_menu.freeze ();
 
         /* Translators: when configuring a new game, label of the first big button; name of the usual 
reversi game, where you try to have more pieces */
@@ -616,18 +622,23 @@ private class Iagno : Gtk.Application, BaseApplication
             ((!) computer).cancel_move ();
 
         bool two_players = settings.get_int ("num-players") == 2;
+        bool even_board = size % 2 == 0;
 
         Opening opening;
         if (alternative_start)
         {
-            if (size % 2 == 0)
+            if (even_board)
                 opening = get_locale_direction () == TextDirection.LTR ? Opening.ALTER_LEFT : 
Opening.ALTER_RIGHT;
             else
                 opening = get_locale_direction () == TextDirection.LTR ? Opening.ALTER_RIGHT : 
Opening.ALTER_LEFT;
         }
         else if (usual_start)
             opening = Opening.REVERSI;
-        else if (random_start || settings.get_boolean ("random-start-position"))
+        else if (two_players && even_board && !random_start) // TODO make work on odd board
+            opening = Opening.HUMANS;
+        else if (random_start
+              || settings.get_boolean ("random-start-position")
+              || two_players /* && !even_board */)
         {
             switch (Random.int_range (0, 8))
             {
diff --git a/src/reversi-view.vala b/src/reversi-view.vala
index c8a94c1..f9331dd 100644
--- a/src/reversi-view.vala
+++ b/src/reversi-view.vala
@@ -103,6 +103,10 @@ private class ReversiView : Gtk.DrawingArea
     /* Animation timer */
     private uint animate_timeout = 0;
 
+    /* Humans opening */
+    private const uint8 HUMANS_OPENING_INTENSITY_MAX = 15;
+    private uint8 humans_opening_intensity = 0;
+
     internal signal void move (uint8 x, uint8 y);
     internal signal void clear_impossible_to_move_here_warning ();
 
@@ -125,6 +129,7 @@ private class ReversiView : Gtk.DrawingArea
 
             if (game_is_set)
                 SignalHandler.disconnect_by_func (_game, null, this);
+
             _game = value;
             game_size = _game.size;
             if (!game_is_set)
@@ -142,16 +147,17 @@ private class ReversiView : Gtk.DrawingArea
             _game.turn_ended.connect (turn_ended_cb);
 
             show_highlight = false;
-            if (game_size % 2 != 0) // always start on center on odd games
-            {
-                highlight_set = true;
-                highlight_x = (uint8) (game_size / 2);
-            }
-            else
+            bool even_board = game_size % 2 == 0;
+            if (even_board)
             {
                 highlight_set = false;
                 highlight_x = (uint8) (game_size / 2 - 1);
             }
+            else    // always start on center on odd games
+            {
+                highlight_set = true;
+                highlight_x = (uint8) (game_size / 2);
+            }
             highlight_y = highlight_x;
             old_highlight_x = uint8.MAX;
             old_highlight_y = uint8.MAX;
@@ -164,6 +170,15 @@ private class ReversiView : Gtk.DrawingArea
             mouse_position_x = uint8.MAX;
             mouse_position_y = uint8.MAX;
 
+            if (_game.opening == Opening.HUMANS)
+            {
+                if (!even_board)
+                    assert_not_reached ();
+                humans_opening_intensity = HUMANS_OPENING_INTENSITY_MAX;
+            }
+            else
+                humans_opening_intensity = 0;
+
             queue_draw ();
         }
     }
@@ -359,6 +374,9 @@ private class ReversiView : Gtk.DrawingArea
         // draw tiles (and highlight)
         cr.translate (border_width, border_width);
 
+        if (humans_opening_intensity != 0)
+            draw_overture (cr);
+
         draw_highlight (cr);
         add_highlights (cr);
         draw_playables (cr);
@@ -453,6 +471,27 @@ private class ReversiView : Gtk.DrawingArea
         cr.fill ();
     }
 
+    private inline void draw_overture (Cairo.Context cr)
+     // requires (game_size % 2 == 0)
+    {
+        uint8 half_game_size = game_size / 2;
+        for (uint8 x = 0; x < game_size; x++)
+            for (uint8 y = 0; y < game_size; y++)
+            {
+                if ((x == half_game_size || x == half_game_size - 1)
+                 && (y == half_game_size || y == half_game_size - 1))
+                    continue;
+
+                darken_tile (cr, x, y);
+            }
+
+        if (game.opening != Opening.HUMANS)
+        {
+            humans_opening_intensity--;
+            queue_draw ();
+        }
+    }
+
     private inline void draw_highlight (Cairo.Context cr)
     {
         if (game.is_complete)   // TODO highlight last played tile on game.is_complete, even if it's the 
opponent one...
@@ -660,6 +699,20 @@ private class ReversiView : Gtk.DrawingArea
         cr.fill ();
     }
 
+    private void darken_tile (Cairo.Context cr, uint8 x, uint8 y)
+    {
+        double alpha = highlight_hard_alpha * 1.6 * (double) humans_opening_intensity / (double) 
HUMANS_OPENING_INTENSITY_MAX;
+        cr.set_source_rgba (highlight_hard_red, highlight_hard_green, highlight_hard_blue, alpha);
+        rounded_square (cr,
+                        // TODO odd/even sizes problem
+                        tile_xs [x, y],
+                        tile_ys [x, y],
+                        tile_size,
+                        0,
+                        background_radius);
+        cr.fill ();
+    }
+
     private void queue_draw_tile (uint8 x, uint8 y)
         requires (x < game_size)
         requires (y < game_size)
@@ -1262,6 +1315,7 @@ private class ReversiView : Gtk.DrawingArea
         if (iagno_instance.computer != null && iagno_instance.player_one == Player.LIGHT)
         {
             /* This section is for when computer starts but player moves before.
+               It is similar to the case of a two-players game, with an opening.
                The starting [highlight_x, highlight_y] tile is the top-left one.
                The target tile is the one that will give correct highlight after
                a move in the given direction, not directly the one to highlight. */
@@ -1292,6 +1346,25 @@ private class ReversiView : Gtk.DrawingArea
                The target tile is the one that will give correct highlight after
                a move in the given direction, not directly the one to highlight. */
 
+            case Opening.HUMANS:
+                if (Gtk.get_locale_direction () == Gtk.TextDirection.LTR)
+                    switch (direction)
+                    {
+                        case Direction.TOP:                       highlight_y++;    break;
+                        case Direction.LEFT:    highlight_x++;    highlight_y++;    break;
+                        case Direction.RIGHT:                                       break;
+                        case Direction.BOTTOM:  highlight_x++;                      break;
+                    }
+                else
+                    switch (direction)
+                    {
+                        case Direction.TOP:     highlight_x++;    highlight_y++;    break;
+                        case Direction.LEFT:    highlight_x++;                      break;
+                        case Direction.RIGHT:                     highlight_y++;    break;
+                        case Direction.BOTTOM:                                      break;
+                    }
+                return;
+
             case Opening.REVERSI:
                 switch (direction)
                 {


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