[iagno] Redo Undo.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [iagno] Redo Undo.
- Date: Wed, 22 May 2019 12:59:27 +0000 (UTC)
commit d0d65bfcdeb885a5814f5e5ea7d958723de48ec4
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Sat Mar 23 14:55:49 2019 +0100
Redo Undo.
Game is not anymore a GameState.
src/computer-player.vala | 10 +-
src/game-view.vala | 48 ++++++--
src/game.vala | 296 +++++++++++++++++++++--------------------------
src/iagno.vala | 5 +-
4 files changed, 177 insertions(+), 182 deletions(-)
---
diff --git a/src/computer-player.vala b/src/computer-player.vala
index 4b10032..412f2df 100644
--- a/src/computer-player.vala
+++ b/src/computer-player.vala
@@ -99,7 +99,7 @@ private class ComputerPlayer : Object
/* Has been reached, once. So let's have a fallback. */
uint8 new_x;
uint8 new_y;
- random_select (game, out new_x, out new_y);
+ random_select (game.current_state, out new_x, out new_y);
if (!game.place_tile (new_x, new_y))
{
critical ("Computer chose an invalid move for the second time: %d,%d\n%s", new_x, new_y,
game.to_string ());
@@ -204,9 +204,9 @@ private class ComputerPlayer : Object
requires (game.current_player_can_move)
{
/* For the first/first two moves play randomly so the game is not always the same */
- if (game.n_tiles < game.initial_number_of_tiles + (game.size < 6 ? 2 : 4))
+ if (game.current_state.n_tiles < game.initial_number_of_tiles + (game.size < 6 ? 2 : 4))
{
- random_select (game, out x, out y);
+ random_select (game.current_state, out x, out y);
return;
}
@@ -216,7 +216,7 @@ private class ComputerPlayer : Object
/* Choose a location to place by building the tree of possible moves and
* using the minimax algorithm to pick the best branch with the chosen
* strategy. */
- GameState g = new GameState.copy_simplify (game);
+ GameState g = new GameState.copy (game.current_state);
int depth = difficulty_level * 2;
/* The -1 is because the search sometimes returns NEGATIVE_INFINITY. */
int a = NEGATIVE_INFINITY - 1;
@@ -396,7 +396,7 @@ private class ComputerPlayer : Object
* * First random moves
\*/
- private static void random_select (Game g, out uint8 move_x, out uint8 move_y)
+ private static void random_select (GameState g, out uint8 move_x, out uint8 move_y)
{
List<uint8> moves = new List<uint8> ();
uint8 size = g.size;
diff --git a/src/game-view.vala b/src/game-view.vala
index e102ecd..d7e2f20 100644
--- a/src/game-view.vala
+++ b/src/game-view.vala
@@ -128,8 +128,8 @@ private class GameView : Gtk.DrawingArea
for (uint8 x = 0; x < game_size; x++)
for (uint8 y = 0; y < game_size; y++)
pixmaps [x, y] = get_pixmap (_game.get_owner (x, y));
- _game.notify ["is-complete"].connect (game_is_complete_cb);
- _game.square_changed.connect (square_changed_cb);
+ _game.completeness_updated.connect (game_is_complete_cb);
+ _game.turn_ended.connect (turn_ended_cb);
show_highlight = false;
bool odd_game = game_size % 2 != 0; // always start on center on odd games
@@ -455,8 +455,17 @@ private class GameView : Gtk.DrawingArea
* * turning tiles
\*/
- private inline void update_highlight_after_undo (uint8 x, uint8 y)
+ private bool last_state_set = false;
+ private GameState last_state;
+
+ private inline void update_highlight_after_undo ()
+ requires (last_state_set)
{
+ // get the tile that was played on and is now empty
+ uint8 x;
+ uint8 y;
+ get_missing_tile (out x, out y);
+
// clear the previous highlight (if any)
if (show_highlight)
{
@@ -476,13 +485,34 @@ private class GameView : Gtk.DrawingArea
highlight_x = x;
highlight_y = y;
}
+ private void get_missing_tile (out uint8 x, out uint8 y)
+ {
+ y = 0; // avoids a warning
+
+ for (x = 0; x < game_size; x++)
+ {
+ for (y = 0; y < game_size; y++)
+ {
+ if (game.get_owner (x, y) != Player.NONE)
+ continue;
+ if (last_state.get_owner (x, y) == game.current_color)
+ return;
+ }
+ }
+ assert_not_reached ();
+ }
- private void square_changed_cb (uint8 x, uint8 y, Player replacement)
+ private void turn_ended_cb (bool undoing, bool no_draw)
{
- if (replacement == Player.NONE)
- update_highlight_after_undo (x, y);
+ if (!no_draw)
+ {
+ update_squares ();
+ if (undoing)
+ update_highlight_after_undo ();
+ }
- update_square (x, y);
+ last_state = game.current_state;
+ last_state_set = true;
}
private inline void update_squares ()
@@ -576,7 +606,7 @@ private class GameView : Gtk.DrawingArea
private void game_is_complete_cb ()
{
- if (!game.is_complete) // we're connecting to a property change, not a signal
+ if (!game.is_complete)
return;
if (game.n_light_tiles == 0 || game.n_dark_tiles == 0) // complete win
@@ -656,7 +686,7 @@ private class GameView : Gtk.DrawingArea
{
int8 x = (int8) ((event.x - board_x) / paving_size);
int8 y = (int8) ((event.y - board_y) / paving_size);
- if (game.is_valid_location_signed (x, y))
+ if (game.current_state.is_valid_location_signed (x, y))
{
show_highlight = false;
queue_draw ();
diff --git a/src/game.vala b/src/game.vala
index bd7be36..268dcb5 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -22,7 +22,7 @@
private class GameState : Object
{
- [CCode (notify = false)] public Player current_color { internal get; protected construct set; default =
Player.NONE; }
+ [CCode (notify = false)] public Player current_color { internal get; protected construct; default =
Player.NONE; }
internal string to_string ()
{
@@ -42,27 +42,27 @@ private class GameState : Object
* * board
\*/
- [CCode (notify = false)] public uint8 size { internal get; protected construct; default = 8; }
+ [CCode (notify = false)] public uint8 size { internal get; protected construct; default = 0; }
- protected Player [,] tiles;
+ private Player [,] tiles;
construct
{
tiles = new Player [size, size];
}
- internal GameState.copy_simplify (Game game)
+ internal GameState.copy (GameState game)
{
Object (size: game.size, current_color: game.current_color);
for (uint8 x = 0; x < size; x++)
for (uint8 y = 0; y < size; y++)
- tiles [x, y] = game.tiles [x, y];
+ set_tile (x, y, game.tiles [x, y]);
- current_player_can_move = game.current_player_can_move;
- is_complete = game.is_complete;
- n_current_tiles = game.n_current_tiles;
- n_opponent_tiles = game.n_opponent_tiles;
+ update_who_can_move ();
+ if (current_player_can_move != game.current_player_can_move
+ || is_complete != game.is_complete)
+ assert_not_reached ();
}
internal GameState.copy_and_pass (GameState game)
@@ -71,10 +71,7 @@ private class GameState : Object
for (uint8 x = 0; x < size; x++)
for (uint8 y = 0; y < size; y++)
- tiles [x, y] = game.tiles [x, y];
-
- n_current_tiles = game.n_opponent_tiles;
- n_opponent_tiles = game.n_current_tiles;
+ set_tile (x, y, game.tiles [x, y]);
// we already know all that, it is just for checking
update_who_can_move ();
@@ -89,12 +86,9 @@ private class GameState : Object
for (uint8 x = 0; x < size; x++)
for (uint8 y = 0; y < size; y++)
- tiles [x, y] = game.tiles [x, y];
+ set_tile (x, y, game.tiles [x, y]);
- n_current_tiles = game.n_opponent_tiles;
- n_opponent_tiles = game.n_current_tiles;
-
- if (_place_tile (move_x, move_y, move_color, /* apply move */ true) == 0)
+ if (place_tile (move_x, move_y, move_color, /* apply move */ true) == 0)
{
critical ("Computer marked move (%d, %d) as valid, but is invalid when checking.\n%s", move_x,
move_y, to_string ());
assert_not_reached ();
@@ -103,12 +97,23 @@ private class GameState : Object
update_who_can_move ();
}
+ internal GameState.from_grid (uint8 _size, Player [,] _tiles, Player color)
+ {
+ Object (size: _size, current_color: color);
+
+ for (uint8 x = 0; x < _size; x++)
+ for (uint8 y = 0; y < _size; y++)
+ set_tile (x, y, _tiles [x, y]);
+
+ update_who_can_move ();
+ }
+
/*\
* * number of tiles on the board
\*/
- private uint8 _n_light_tiles = 2;
- private uint8 _n_dark_tiles = 2;
+ private uint8 _n_light_tiles = 0;
+ private uint8 _n_dark_tiles = 0;
[CCode (notify = false)] internal uint8 n_tiles
{ internal get { return _n_dark_tiles + _n_light_tiles; }}
@@ -116,27 +121,25 @@ private class GameState : Object
{ internal get { return _n_light_tiles; }}
[CCode (notify = false)] internal uint8 n_dark_tiles
{ internal get { return _n_dark_tiles; }}
-
[CCode (notify = false)] internal uint8 n_current_tiles
+ { internal get { return current_color == Player.LIGHT ?
_n_light_tiles : _n_dark_tiles; }}
+ [CCode (notify = false)] internal uint8 n_opponent_tiles
+ { internal get { return current_color == Player.DARK ?
_n_light_tiles : _n_dark_tiles; }}
+
+ internal void add_tile_of_color (Player color)
{
- internal get { return current_color == Player.LIGHT ? n_light_tiles : n_dark_tiles; }
- protected set {
- if (current_color == Player.LIGHT)
- _n_light_tiles = value;
- else
- _n_dark_tiles = value;
- }
+ if (color == Player.DARK)
+ _n_dark_tiles++;
+ else if (color == Player.LIGHT)
+ _n_light_tiles++;
}
- [CCode (notify = false)] internal uint8 n_opponent_tiles
+ private void remove_tile_of_opponent_color (Player color)
{
- internal get { return current_color == Player.DARK ? n_light_tiles : n_dark_tiles; }
- protected set {
- if (current_color == Player.DARK)
- _n_light_tiles = value;
- else
- _n_dark_tiles = value;
- }
+ if (color == Player.LIGHT)
+ _n_dark_tiles--;
+ else if (color == Player.DARK)
+ _n_light_tiles--;
}
/*\
@@ -172,10 +175,10 @@ private class GameState : Object
internal uint8 test_placing_tile (uint8 x, uint8 y)
{
- return _place_tile (x, y, current_color, /* apply move */ false);
+ return place_tile (x, y, current_color, /* apply move */ false);
}
- protected uint8 _place_tile (uint8 x, uint8 y, Player color, bool apply)
+ private uint8 place_tile (uint8 x, uint8 y, Player color, bool apply)
requires (is_valid_location_unsigned (x, y))
{
if (tiles [x, y] != Player.NONE)
@@ -195,24 +198,19 @@ private class GameState : Object
return 0;
if (apply)
- {
set_tile (x, y, color);
- end_of_turn ();
- }
return tiles_turned;
}
- protected virtual void end_of_turn () {}
-
/*\
* * can move
\*/
[CCode (notify = false)] internal bool current_player_can_move { internal get; private set; default =
true; }
- [CCode (notify = true)] internal bool is_complete { internal get; protected set; default = false; }
+ [CCode (notify = true)] internal bool is_complete { internal get; private set; default = false; }
- protected void update_who_can_move ()
+ private void update_who_can_move ()
{
Player enemy = Player.flip_color (current_color);
bool opponent_can_move = false;
@@ -266,9 +264,7 @@ private class GameState : Object
{
for (int8 i = 1; i <= enemy_count; i++)
{
- if (color == Player.DARK) _n_light_tiles--;
- else if (color == Player.LIGHT) _n_dark_tiles--;
- else assert_not_reached ();
+ remove_tile_of_opponent_color (color);
set_tile ((uint8) ((int8) x + (i * x_step)),
(uint8) ((int8) y + (i * y_step)),
color);
@@ -277,7 +273,7 @@ private class GameState : Object
return enemy_count;
}
- protected uint8 can_flip_tiles (uint8 x, uint8 y, Player color, int8 x_step, int8 y_step)
+ private uint8 can_flip_tiles (uint8 x, uint8 y, Player color, int8 x_step, int8 y_step)
{
Player enemy = Player.flip_color (color);
@@ -298,64 +294,64 @@ private class GameState : Object
return (uint8) enemy_count;
}
- protected virtual void set_tile (uint8 x, uint8 y, Player color)
+ private void set_tile (uint8 x, uint8 y, Player color)
{
- if (color == Player.DARK) _n_dark_tiles++;
- else if (color == Player.LIGHT) _n_light_tiles++;
- else assert_not_reached ();
+ add_tile_of_color (color);
tiles [x, y] = color;
}
}
-private class Game : GameState
+private class Game : Object
{
- /* Undoing */
- private uint8? [] undo_stack;
- private int history_index = -1;
-
- [CCode (notify = false)] internal uint8 number_of_moves { internal get; private set; default = 0; }
+ private GLib.ListStore undo_stack = new GLib.ListStore (typeof (GameState));
+ [CCode (notify = false)] internal uint8 number_of_moves
+ {
+ internal get
+ {
+ uint n_items = undo_stack.get_n_items ();
+ if (n_items == 0 || n_items >= 256)
+ assert_not_reached ();
+ return (uint8) (n_items - 1);
+ }
+ }
/* Indicate that a player should move */
- internal signal void turn_ended ();
- /* Indicate a square has changed */
- internal signal void square_changed (uint8 x, uint8 y, Player new_color);
-
- [CCode (notify = false)] internal uint8 initial_number_of_tiles { internal get; private set; }
+ internal signal void turn_ended (bool undoing, bool no_draw);
/*\
* * creation
\*/
- [CCode (notify = false)] public bool alternative_start { internal get; protected construct; }
+ [CCode (notify = false)] public GameState current_state { internal get; private set;
}
+ [CCode (notify = false)] public uint8 initial_number_of_tiles { internal get; protected construct;
}
+ [CCode (notify = false)] public uint8 size { internal get; protected construct;
}
+ [CCode (notify = false)] public bool alternative_start { internal get; protected construct;
}
- internal Game (bool alternative_start = false, uint8 _size = 8)
+ internal Game (bool _alternative_start = false, uint8 _size = 8)
requires (_size >= 4)
requires (_size <= 16)
{
- Object (alternative_start: alternative_start, size: _size, current_color: /* Dark always starts */
Player.DARK);
+ bool even_board = (_size % 2 == 0);
+ Object (alternative_start: _alternative_start, size: _size, initial_number_of_tiles: (even_board ? 4
: 7));
+
+ 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;
- init_undo_stack (_size, out undo_stack);
-
- if (_size % 2 == 0)
+ if (even_board)
{
/* setup board with four tiles by default */
- initial_number_of_tiles = 4;
uint8 half_size = _size / 2;
tiles [half_size - 1, half_size - 1] = alternative_start ? Player.DARK : Player.LIGHT;
tiles [half_size - 1, half_size ] = Player.DARK;
tiles [half_size , half_size - 1] = alternative_start ? Player.LIGHT : Player.DARK;
tiles [half_size , half_size ] = Player.LIGHT;
- n_current_tiles = 2;
- n_opponent_tiles = 2;
}
else
{
/* logical starting position for odd board */
- initial_number_of_tiles = 7;
uint8 mid_board = (_size - 1) / 2;
tiles [mid_board , mid_board ] = Player.DARK;
tiles [mid_board + 1, mid_board - 1] = alternative_start ? Player.LIGHT : Player.DARK;
@@ -364,9 +360,11 @@ private class Game : GameState
tiles [mid_board - 1, mid_board ] = alternative_start ? Player.DARK : Player.LIGHT;
tiles [mid_board + 1, mid_board ] = alternative_start ? Player.DARK : Player.LIGHT;
tiles [mid_board , mid_board + 1] = Player.LIGHT;
- n_current_tiles = 3;
- n_opponent_tiles = 4;
}
+
+ current_state = new GameState.from_grid (_size, tiles, /* Dark always starts */ Player.DARK);
+ undo_stack.append (current_state);
+ completeness_updated (current_state.is_complete);
}
internal Game.from_strings (string [] setup, Player to_move, uint8 _size = 8)
@@ -375,39 +373,45 @@ private class Game : GameState
requires (to_move != Player.NONE)
requires (setup.length == _size)
{
- Object (size: _size, current_color: to_move);
+ Object (alternative_start: /* garbage */ false, size: _size, initial_number_of_tiles: (_size % 2 ==
0) ? 4 : 7);
- initial_number_of_tiles = (_size % 2 == 0) ? 4 : 7;
- init_undo_stack (_size, out undo_stack);
-
- uint8 n_dark_tiles = 0;
- uint8 n_light_tiles = 0;
+ Player [,] tiles = new Player [_size, _size];
for (uint8 y = 0; y < _size; y++)
{
if (setup [y].length != _size * 2)
warn_if_reached ();
for (uint8 x = 0; x < _size; x++)
- {
- Player player = Player.from_char (setup [y][x * 2 + 1]);
- if (player == Player.DARK) n_dark_tiles++;
- else if (player == Player.LIGHT) n_light_tiles++;
- tiles [x, y] = player;
- }
+ tiles [x, y] = Player.from_char (setup [y] [x * 2 + 1]);
}
- if (to_move == Player.DARK)
- {
- n_current_tiles = n_dark_tiles;
- n_opponent_tiles = n_light_tiles;
- }
- else
- {
- n_current_tiles = n_light_tiles;
- n_opponent_tiles = n_dark_tiles;
- }
+ current_state = new GameState.from_grid (_size, tiles, to_move);
+ undo_stack.append (current_state);
+ completeness_updated (current_state.is_complete);
+
+ warn_if_fail (string.joinv ("\n", (string? []) setup).strip () == to_string ().strip ());
+ }
+
+ /*\
+ * * informations
+ \*/
+
+ internal Player get_owner (uint8 x, uint8 y)
+ {
+ return current_state.get_owner (x, y);
+ }
+
+ internal signal void completeness_updated (bool completeness);
- warn_if_fail (string.joinv ("\n", (string?[]) setup).strip () == to_string ().strip ());
+ [CCode (notify = false)] internal bool is_complete { get { return
current_state.is_complete; }}
+ [CCode (notify = false)] internal uint8 n_light_tiles { get { return
current_state.n_light_tiles; }}
+ [CCode (notify = false)] internal uint8 n_dark_tiles { get { return
current_state.n_dark_tiles; }}
+ [CCode (notify = false)] internal bool current_player_can_move { get { return
current_state.current_player_can_move; }}
+ [CCode (notify = false)] internal Player current_color { get { return
current_state.current_color; }}
+
+ internal string to_string ()
+ {
+ return @"$current_state";
}
/*\
@@ -416,24 +420,28 @@ private class Game : GameState
internal /* success */ bool place_tile (uint8 x, uint8 y)
{
- return _place_tile (x, y, current_color, /* apply move */ true) != 0;
+ uint8 n_tiles = current_state.test_placing_tile (x, y);
+ if (n_tiles == 0)
+ return false;
+
+ current_state = new GameState.copy_and_move (current_state, x, y);
+ undo_stack.append (current_state);
+ end_of_turn (/* undoing */ false, /* no_draw */ false);
+ return true;
}
internal void pass ()
requires (!current_player_can_move)
{
- end_of_turn ();
+ current_state = new GameState.copy_and_pass (current_state);
+ undo_stack.append (current_state);
+ end_of_turn (/* undoing */ false, /* no_draw */ true);
}
- protected override void end_of_turn ()
- requires (history_index >= -1 && history_index < undo_stack.length - 2)
+ private void end_of_turn (bool undoing, bool no_draw)
{
- current_color = Player.flip_color (current_color);
- number_of_moves++;
- history_index++;
- undo_stack [history_index] = null;
- update_who_can_move ();
- turn_ended ();
+ completeness_updated (current_state.is_complete);
+ turn_ended (undoing, no_draw);
}
/*\
@@ -443,69 +451,23 @@ private class Game : GameState
internal void undo (uint8 count = 1)
requires (count == 1 || count == 2)
requires (number_of_moves >= count)
- requires (history_index < undo_stack.length)
{
- Player enemy = current_color;
- current_color = Player.flip_color (current_color);
- number_of_moves--;
+ uint undo_stack_n_items = undo_stack.get_n_items ();
+ Object? tmp_current_state = undo_stack.get_object (undo_stack_n_items - 2);
+ if (tmp_current_state == null || !((!) tmp_current_state is GameState))
+ assert_not_reached ();
+ current_state = (GameState) (!) tmp_current_state;
- /* pass the end of turn mark of the history */
- history_index--;
+ /* for now, we forget about this undone move in the undo stack
+ TODO keep an index of current state instead, and allow redo */
+ undo_stack.remove (undo_stack_n_items - 1);
- /* if not pass */
- uint8? undo_item = undo_stack [history_index];
- if (undo_item != null)
- {
- /* last log entry is the placed tile, previous are flipped tiles */
- unset_tile ((!) undo_item, Player.NONE);
- undo_item = undo_stack [history_index];
- while (history_index > -1 && undo_item != null)
- {
- n_opponent_tiles++;
- unset_tile ((!) undo_item, enemy);
- if (history_index > -1)
- undo_item = undo_stack [history_index];
- else
- undo_item = null;
- }
- }
-
- if (count == 1)
- {
- is_complete = false;
- update_who_can_move ();
- }
- else
+ if (count == 1 && current_player_can_move)
+ end_of_turn (/* undoing */ true, /* no_draw */ false);
+ else if (count == 2)
{
+ end_of_turn (/* undoing */ true, /* no_draw */ true);
undo (count - 1);
}
}
-
- protected override void set_tile (uint8 x, uint8 y, Player color)
- requires (history_index >= -1 && history_index < undo_stack.length - 2)
- {
- history_index++;
- undo_stack [history_index] = x + y * size;
- base.set_tile (x, y, color);
- square_changed (x, y, color);
- }
-
- private void unset_tile (uint8 tile_number, Player replacement_color)
- {
- n_current_tiles--;
- history_index--;
- uint8 x = tile_number % size;
- uint8 y = tile_number / size;
- tiles [x, y] = replacement_color;
- square_changed (x, y, replacement_color);
- }
-
- private static void init_undo_stack (uint8 size, out uint8? [] undo_stack)
- {
- // Stack is oversized: there are (size * size - initial tiles) turns,
- // each adds one piece, a null marking the end of turn, then possibly
- // another null marking the opponent passing, and it is impossible to
- // flip (size - 2) enemy pieces in each of the 4 possible directions.
- undo_stack = new uint8? [(size * size - 4) * (3 + (size - 2) * 4)];
- }
}
diff --git a/src/iagno.vala b/src/iagno.vala
index 8310014..b143fd9 100644
--- a/src/iagno.vala
+++ b/src/iagno.vala
@@ -434,9 +434,12 @@ private class Iagno : Gtk.Application
view.update_scoreboard ();
}
- private void turn_ended_cb ()
+ private void turn_ended_cb (bool undoing, bool no_draw)
requires (game_is_set)
{
+ if (undoing && no_draw)
+ return;
+
update_ui ();
if (game.current_player_can_move)
prepare_move ();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]