[iagno] Introduce GameState.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [iagno] Introduce GameState.
- Date: Wed, 22 May 2019 12:59:17 +0000 (UTC)
commit 8f28b036d46faad90ee94b16949a693c73f37db7
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Fri Mar 22 14:39:52 2019 +0100
Introduce GameState.
A GameState object keeps all the
useful informations about a game
state. It is minimalistic and so
better when the AI deals with it
and will also be easily saved in
an history of the game not based
on a long list of flipped tiles.
Game is for now a subclass of it
but that should be changed next.
src/computer-player.vala | 42 ++---
src/game.vala | 478 ++++++++++++++++++++++++++++-------------------
src/iagno.vala | 2 +-
src/test-iagno.vala | 236 +++++++++++------------
4 files changed, 414 insertions(+), 344 deletions(-)
---
diff --git a/src/computer-player.vala b/src/computer-player.vala
index df6b51c..1a4d38e 100644
--- a/src/computer-player.vala
+++ b/src/computer-player.vala
@@ -92,7 +92,7 @@ private class ComputerPlayer : Object
private void complete_move (uint8 x, uint8 y)
{
- if (game.place_tile (x, y) == 0)
+ if (!game.place_tile (x, y))
{
critical ("Computer chose an invalid move: %d,%d\n%s", x, y, game.to_string ());
@@ -100,7 +100,7 @@ private class ComputerPlayer : Object
uint8 new_x;
uint8 new_y;
random_select (game, out new_x, out new_y);
- if (game.place_tile (new_x, new_y) == 0)
+ 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 ());
assert_not_reached ();
@@ -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. */
- Game g = new Game.copy (game);
+ GameState g = new GameState.copy_simplify (game);
int depth = difficulty_level * 2;
/* The -1 is because the search sometimes returns NEGATIVE_INFINITY. */
int a = NEGATIVE_INFINITY - 1;
@@ -230,13 +230,7 @@ private class ComputerPlayer : Object
if (move == null)
assert_not_reached ();
- Game _g = new Game.copy (g);
-
- if (_g.place_tile (((!) move).x, ((!) move).y, true) == 0)
- {
- critical ("Computer marked move (depth %d, %d,%d, %d flips) as valid, but is invalid when
checking.\n%s", depth, ((!) move).x, ((!) move).y, ((!) move).n_tiles, _g.to_string ());
- assert_not_reached ();
- }
+ GameState _g = new GameState.copy_and_move (g, ((!) move).x, ((!) move).y);
int a_new = -1 * search (_g, depth, NEGATIVE_INFINITY, -a);
if (a_new > a)
@@ -248,7 +242,7 @@ private class ComputerPlayer : Object
}
}
- private int search (Game g, int depth, int a, int b)
+ private int search (GameState g, int depth, int a, int b)
requires (a <= b)
{
/* End of the game, return a near-infinite evaluation */
@@ -276,13 +270,7 @@ private class ComputerPlayer : Object
if (move == null)
assert_not_reached ();
- Game _g = new Game.copy (g);
-
- if (_g.place_tile (((!) move).x, ((!) move).y) == 0)
- {
- critical ("Computer marked move (depth %d, %d,%d, %d flips) as valid, but is invalid
when checking.\n%s", depth, ((!) move).x, ((!) move).y, ((!) move).n_tiles, _g.to_string ());
- assert_not_reached ();
- }
+ GameState _g = new GameState.copy_and_move (g, ((!) move).x, ((!) move).y);
int a_new = -1 * search (_g, depth - 1, -b, -a);
if (a_new > a)
@@ -293,11 +281,9 @@ private class ComputerPlayer : Object
break;
}
}
- else
+ else // pass
{
- Game _g = new Game.copy (g);
-
- _g.pass ();
+ GameState _g = new GameState.copy_and_pass (g);
int a_new = -1 * search (_g, depth - 1, -b, -a);
if (a_new > a)
@@ -307,13 +293,13 @@ private class ComputerPlayer : Object
return a;
}
- private static void get_possible_moves_sorted (Game g, ref List<PossibleMove?> moves)
+ private static void get_possible_moves_sorted (GameState g, ref List<PossibleMove?> moves)
{
for (uint8 x = 0; x < g.size; x++)
{
for (uint8 y = 0; y < g.size; y++)
{
- uint8 n_tiles = g.place_tile (x, y, false);
+ uint8 n_tiles = g.test_placing_tile (x, y);
if (n_tiles == 0)
continue;
@@ -334,7 +320,7 @@ private class ComputerPlayer : Object
* * AI
\*/
- private static int calculate_heuristic (Game g, ref uint8 difficulty_level)
+ private static int calculate_heuristic (GameState g, ref uint8 difficulty_level)
{
int tile_difference = (int) g.n_current_tiles - (int) g.n_opponent_tiles;
@@ -350,7 +336,7 @@ private class ComputerPlayer : Object
return tile_difference + eval_heuristic (g) + around (g) ;
}
- private static int eval_heuristic (Game g)
+ private static int eval_heuristic (GameState g)
{
if (g.size != 8) // TODO
return 0;
@@ -369,7 +355,7 @@ private class ComputerPlayer : Object
return count;
}
- private static int around (Game g)
+ private static int around (GameState g)
{
int count = 0;
for (int8 x = 0; x < (int8) g.size; x++)
@@ -396,7 +382,7 @@ private class ComputerPlayer : Object
return count;
}
- private static int is_empty (Game g, int8 x, int8 y)
+ private static int is_empty (GameState g, int8 x, int8 y)
{
if (!g.is_valid_location_signed (x, y))
return 0;
diff --git a/src/game.vala b/src/game.vala
index 4e7436e..34022f0 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -20,36 +20,93 @@
along with GNOME Reversi. If not, see <https://www.gnu.org/licenses/>.
*/
-private class Game : Object
+private class GameState : Object
{
- /* Tiles on the board */
- private Player [,] tiles;
+ [CCode (notify = false)] public Player current_color { internal get; protected construct set; default =
Player.NONE; }
+
+ internal string to_string ()
+ {
+ string s = "\n";
+
+ for (uint8 y = 0; y < size; y++)
+ {
+ for (uint8 x = 0; x < size; x++)
+ s += " " + tiles [x, y].to_string ();
+ s += "\n";
+ }
+
+ return s;
+ }
+
+ /*\
+ * * board
+ \*/
[CCode (notify = false)] public uint8 size { internal get; protected construct; default = 8; }
- /* Undoing */
- private uint8? [] undo_stack;
- private int history_index = -1;
+ protected Player [,] tiles;
- /* Color to move next; Dark always plays first;
- * should be dark if number_of_moves % 2 == 0 */
- [CCode (notify = false)] internal Player current_color { internal get; private set; default =
Player.DARK; }
- [CCode (notify = false)] internal uint8 number_of_moves { internal get; private set; default = 0; }
+ construct
+ {
+ tiles = new Player [size, size];
+ }
- /* Indicate who's the next player who 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; private set; default = false; }
+ internal GameState.copy_simplify (Game game)
+ {
+ Object (size: game.size, current_color: game.current_color);
- /* 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);
+ for (uint8 x = 0; x < size; x++)
+ for (uint8 y = 0; y < size; y++)
+ tiles [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;
+ }
+
+ internal GameState.copy_and_pass (GameState game)
+ {
+ Object (size: game.size, current_color: Player.flip_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];
+
+ n_current_tiles = game.n_opponent_tiles;
+ n_opponent_tiles = game.n_current_tiles;
+
+ // we already know all that, it is just for checking
+ update_who_can_move ();
+ if (!current_player_can_move || is_complete)
+ assert_not_reached ();
+ }
+
+ internal GameState.copy_and_move (GameState game, uint8 move_x, uint8 move_y)
+ {
+ Player move_color = game.current_color;
+ Object (size: game.size, current_color: Player.flip_color (move_color));
+
+ 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;
+
+ 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 ();
+ }
+
+ update_who_can_move ();
+ }
/*\
- * * Number of tiles on the board
+ * * number of tiles on the board
\*/
- [CCode (notify = false)] internal uint8 initial_number_of_tiles { internal get; private set; }
[CCode (notify = false)] internal uint8 n_tiles
{
internal get { return n_dark_tiles + n_light_tiles; }
@@ -70,7 +127,7 @@ private class Game : Object
[CCode (notify = false)] internal uint8 n_current_tiles
{
internal get { return current_color == Player.LIGHT ? n_light_tiles : n_dark_tiles; }
- private set {
+ protected set {
if (current_color == Player.LIGHT)
_n_light_tiles = value;
else
@@ -81,7 +138,7 @@ private class Game : Object
[CCode (notify = false)] internal uint8 n_opponent_tiles
{
internal get { return current_color == Player.DARK ? n_light_tiles : n_dark_tiles; }
- private set {
+ protected set {
if (current_color == Player.DARK)
_n_light_tiles = value;
else
@@ -90,122 +147,20 @@ private class Game : Object
}
/*\
- * * Creation / exporting
+ * * public information
\*/
- [CCode (notify = false)] public bool alternative_start { internal get; protected construct; }
-
- construct
- {
- tiles = new Player [size, size];
- }
-
- internal Game (bool alternative_start = false, uint8 _size = 8)
- requires (_size >= 4)
- requires (_size <= 16)
- {
- Object (alternative_start: alternative_start, 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)
- {
- /* Setup board with four tiles by default */
- initial_number_of_tiles = 4;
- tiles [_size / 2 - 1, _size / 2 - 1] = alternative_start ? Player.DARK : Player.LIGHT;
- tiles [_size / 2 - 1, _size / 2] = Player.DARK;
- tiles [_size / 2, _size / 2 - 1] = alternative_start ? Player.LIGHT : Player.DARK;
- tiles [_size / 2, _size / 2] = Player.LIGHT;
- n_current_tiles = 2;
- n_opponent_tiles = 2;
- }
- else
- {
- /* Logical starting position for odd board */
- initial_number_of_tiles = 7;
- tiles [(_size - 1) / 2, (_size - 1) / 2] = Player.DARK;
- tiles [(_size + 1) / 2, (_size - 3) / 2] = alternative_start ? Player.LIGHT : Player.DARK;
- tiles [(_size - 3) / 2, (_size + 1) / 2] = alternative_start ? Player.LIGHT : Player.DARK;
- tiles [(_size - 1) / 2, (_size - 3) / 2] = Player.LIGHT;
- tiles [(_size - 3) / 2, (_size - 1) / 2] = alternative_start ? Player.DARK : Player.LIGHT;
- tiles [(_size + 1) / 2, (_size - 1) / 2] = alternative_start ? Player.DARK : Player.LIGHT;
- tiles [(_size - 1) / 2, (_size + 1) / 2] = Player.LIGHT;
- n_current_tiles = 3;
- n_opponent_tiles = 4;
- }
- }
-
- internal Game.from_strings (string [] setup, Player to_move, uint8 _size = 8)
- requires (_size >= 4)
- requires (_size <= 16)
- requires (to_move != Player.NONE)
- requires (setup.length == _size)
- {
- Object (size: _size);
-
- initial_number_of_tiles = (size % 2 == 0) ? 4 : 7;
- init_undo_stack (_size, out undo_stack);
-
- _n_dark_tiles = 0;
- _n_light_tiles = 0;
-
- 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;
- }
- }
-
- current_color = to_move;
-
- warn_if_fail (string.joinv ("\n", (string?[]) setup).strip () == to_string ().strip ());
- }
-
- internal string to_string ()
- {
- string s = "\n";
-
- for (uint8 y = 0; y < size; y++)
- {
- for (uint8 x = 0; x < size; x++)
- s += " " + tiles [x, y].to_string ();
- s += "\n";
- }
-
- return s;
- }
-
- internal Game.copy (Game game)
+ internal Player get_owner (uint8 x, uint8 y)
+ requires (is_valid_location_unsigned (x, y))
{
- Object (size: game.size);
-
- for (uint8 x = 0; x < size; x++)
- for (uint8 y = 0; y < size; y++)
- tiles [x, y] = game.tiles [x, y];
-
- number_of_moves = game.number_of_moves;
- current_color = game.current_color;
- n_current_tiles = game.n_current_tiles;
- n_opponent_tiles = game.n_opponent_tiles;
- current_player_can_move = game.current_player_can_move;
-
- init_undo_stack (_size, out undo_stack);
- /* warning: history not copied */
+ return tiles [x, y];
}
-
- /*\
- * * Public information
- \*/
+ // internal new uint8 get (uint8 x, uint8 y) // allows calling game [x, y]
+ // requires (x < size)
+ // requires (y < size)
+ // {
+ // return tiles [x, y];
+ // }
internal inline bool is_valid_location_signed (int8 x, int8 y)
{
@@ -218,80 +173,53 @@ private class Game : Object
return x < size && y < size;
}
- internal Player get_owner (uint8 x, uint8 y)
- requires (is_valid_location_unsigned (x, y))
- {
- return tiles [x, y];
- }
+ /*\
+ * * ... // completeness
+ \*/
- internal bool can_place (uint8 x, uint8 y, Player color)
- requires (is_valid_location_unsigned (x, y))
- requires (color != Player.NONE)
+ internal uint8 test_placing_tile (uint8 x, uint8 y)
{
- if (tiles [x, y] != Player.NONE)
- return false;
-
- if (can_flip_tiles (x, y, 1, 0, color) > 0) return true;
- if (can_flip_tiles (x, y, 1, 1, color) > 0) return true;
- if (can_flip_tiles (x, y, 0, 1, color) > 0) return true;
- if (can_flip_tiles (x, y, -1, 1, color) > 0) return true;
- if (can_flip_tiles (x, y, -1, 0, color) > 0) return true;
- if (can_flip_tiles (x, y, -1, -1, color) > 0) return true;
- if (can_flip_tiles (x, y, 0, -1, color) > 0) return true;
- if (can_flip_tiles (x, y, 1, -1, color) > 0) return true;
- return false;
+ return _place_tile (x, y, current_color, /* apply move */ false);
}
- /*\
- * * Actions (apart undo)
- \*/
-
- internal uint8 place_tile (uint8 x, uint8 y, bool apply = true)
+ protected uint8 _place_tile (uint8 x, uint8 y, Player color, bool apply)
requires (is_valid_location_unsigned (x, y))
{
if (tiles [x, y] != Player.NONE)
return 0;
uint8 tiles_turned = 0;
- tiles_turned += flip_tiles (x, y, 1, 0, apply);
- tiles_turned += flip_tiles (x, y, 1, 1, apply);
- tiles_turned += flip_tiles (x, y, 0, 1, apply);
- tiles_turned += flip_tiles (x, y, -1, 1, apply);
- tiles_turned += flip_tiles (x, y, -1, 0, apply);
- tiles_turned += flip_tiles (x, y, -1, -1, apply);
- tiles_turned += flip_tiles (x, y, 0, -1, apply);
- tiles_turned += flip_tiles (x, y, 1, -1, apply);
+ tiles_turned += flip_tiles (x, y, color, 1, 0, apply);
+ tiles_turned += flip_tiles (x, y, color, 1, 1, apply);
+ tiles_turned += flip_tiles (x, y, color, 0, 1, apply);
+ tiles_turned += flip_tiles (x, y, color, -1, 1, apply);
+ tiles_turned += flip_tiles (x, y, color, -1, 0, apply);
+ tiles_turned += flip_tiles (x, y, color, -1, -1, apply);
+ tiles_turned += flip_tiles (x, y, color, 0, -1, apply);
+ tiles_turned += flip_tiles (x, y, color, 1, -1, apply);
if (tiles_turned == 0)
return 0;
if (apply)
{
- set_tile (x, y);
+ set_tile (x, y, color);
end_of_turn ();
}
return tiles_turned;
}
- internal void pass ()
- requires (!current_player_can_move)
- {
- end_of_turn ();
- }
+ protected virtual void end_of_turn () {}
- private void end_of_turn ()
- requires (history_index >= -1 && history_index < undo_stack.length - 2)
- {
- current_color = Player.flip_color (current_color);
- number_of_moves++;
- history_index++;
- undo_stack [history_index] = null;
- update_who_can_move ();
- turn_ended ();
- }
+ /*\
+ * * can move
+ \*/
- private void update_who_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; }
+
+ protected void update_who_can_move ()
{
Player enemy = Player.flip_color (current_color);
bool opponent_can_move = false;
@@ -313,13 +241,31 @@ private class Game : Object
is_complete = true;
}
+ internal bool can_place (uint8 x, uint8 y, Player color)
+ requires (is_valid_location_unsigned (x, y))
+ requires (color != Player.NONE)
+ {
+ if (tiles [x, y] != Player.NONE)
+ return false;
+
+ if (can_flip_tiles (x, y, color, 1, 0) > 0) return true;
+ if (can_flip_tiles (x, y, color, 1, 1) > 0) return true;
+ if (can_flip_tiles (x, y, color, 0, 1) > 0) return true;
+ if (can_flip_tiles (x, y, color, -1, 1) > 0) return true;
+ if (can_flip_tiles (x, y, color, -1, 0) > 0) return true;
+ if (can_flip_tiles (x, y, color, -1, -1) > 0) return true;
+ if (can_flip_tiles (x, y, color, 0, -1) > 0) return true;
+ if (can_flip_tiles (x, y, color, 1, -1) > 0) return true;
+ return false;
+ }
+
/*\
- * * Flipping tiles
+ * * flipping tiles
\*/
- private uint8 flip_tiles (uint8 x, uint8 y, int8 x_step, int8 y_step, bool apply)
+ private uint8 flip_tiles (uint8 x, uint8 y, Player color, int8 x_step, int8 y_step, bool apply)
{
- uint8 enemy_count = can_flip_tiles (x, y, x_step, y_step, current_color);
+ uint8 enemy_count = can_flip_tiles (x, y, color, x_step, y_step);
if (enemy_count == 0)
return 0;
@@ -327,15 +273,18 @@ private class Game : Object
{
for (int8 i = 1; i <= enemy_count; i++)
{
- n_opponent_tiles--;
- set_tile (x + (uint8) (i * x_step),
- y + (uint8) (i * y_step));
+ if (color == Player.DARK) _n_light_tiles--;
+ else if (color == Player.LIGHT) _n_dark_tiles--;
+ else assert_not_reached ();
+ set_tile ((uint8) ((int8) x + (i * x_step)),
+ (uint8) ((int8) y + (i * y_step)),
+ color);
}
}
return enemy_count;
}
- private uint8 can_flip_tiles (uint8 x, uint8 y, int8 x_step, int8 y_step, Player color)
+ protected uint8 can_flip_tiles (uint8 x, uint8 y, Player color, int8 x_step, int8 y_step)
{
Player enemy = Player.flip_color (color);
@@ -350,24 +299,150 @@ private class Game : Object
} while (is_valid_location_signed (xt, yt) && tiles [xt, yt] == enemy);
/* Must be a line of enemy pieces then one of ours */
- if (enemy_count == 0 || !is_valid_location_signed (xt, yt) || tiles [xt, yt] != color)
+ if (enemy_count <= 0 || !is_valid_location_signed (xt, yt) || tiles [xt, yt] != color)
return 0;
return (uint8) enemy_count;
}
- private void set_tile (uint8 x, uint8 y)
+ protected virtual 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 ();
+ tiles [x, y] = color;
+ }
+}
+
+private class Game : GameState
+{
+ /* Undoing */
+ private uint8? [] undo_stack;
+ private int history_index = -1;
+
+ [CCode (notify = false)] internal uint8 number_of_moves { internal get; private set; default = 0; }
+
+ /* 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; }
+
+ /*\
+ * * creation
+ \*/
+
+ [CCode (notify = false)] public bool alternative_start { internal get; protected construct; }
+
+ 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);
+
+ 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)
+ {
+ /* Setup board with four tiles by default */
+ initial_number_of_tiles = 4;
+ tiles [_size / 2 - 1, _size / 2 - 1] = alternative_start ? Player.DARK : Player.LIGHT;
+ tiles [_size / 2 - 1, _size / 2] = Player.DARK;
+ tiles [_size / 2, _size / 2 - 1] = alternative_start ? Player.LIGHT : Player.DARK;
+ tiles [_size / 2, _size / 2] = Player.LIGHT;
+ n_current_tiles = 2;
+ n_opponent_tiles = 2;
+ }
+ else
+ {
+ /* Logical starting position for odd board */
+ initial_number_of_tiles = 7;
+ tiles [(_size - 1) / 2, (_size - 1) / 2] = Player.DARK;
+ tiles [(_size + 1) / 2, (_size - 3) / 2] = alternative_start ? Player.LIGHT : Player.DARK;
+ tiles [(_size - 3) / 2, (_size + 1) / 2] = alternative_start ? Player.LIGHT : Player.DARK;
+ tiles [(_size - 1) / 2, (_size - 3) / 2] = Player.LIGHT;
+ tiles [(_size - 3) / 2, (_size - 1) / 2] = alternative_start ? Player.DARK : Player.LIGHT;
+ tiles [(_size + 1) / 2, (_size - 1) / 2] = alternative_start ? Player.DARK : Player.LIGHT;
+ tiles [(_size - 1) / 2, (_size + 1) / 2] = Player.LIGHT;
+ n_current_tiles = 3;
+ n_opponent_tiles = 4;
+ }
+ }
+
+ internal Game.from_strings (string [] setup, Player to_move, uint8 _size = 8)
+ requires (_size >= 4)
+ requires (_size <= 16)
+ requires (to_move != Player.NONE)
+ requires (setup.length == _size)
+ {
+ Object (size: _size, current_color: to_move);
+
+ 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;
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ warn_if_fail (string.joinv ("\n", (string?[]) setup).strip () == to_string ().strip ());
+ }
+
+ /*\
+ * * actions (apart undo)
+ \*/
+
+ internal /* success */ bool place_tile (uint8 x, uint8 y)
+ {
+ return _place_tile (x, y, current_color, /* apply move */ true) != 0;
+ }
+
+ internal void pass ()
+ requires (!current_player_can_move)
+ {
+ end_of_turn ();
+ }
+
+ protected override void end_of_turn ()
requires (history_index >= -1 && history_index < undo_stack.length - 2)
{
- n_current_tiles++;
+ current_color = Player.flip_color (current_color);
+ number_of_moves++;
history_index++;
- undo_stack [history_index] = x + y * size;
- tiles [x, y] = current_color;
- square_changed (x, y, current_color);
+ undo_stack [history_index] = null;
+ update_who_can_move ();
+ turn_ended ();
}
/*\
- * * Undo
+ * * undo
\*/
internal void undo (uint8 count = 1)
@@ -411,6 +486,15 @@ private class Game : Object
}
}
+ 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--;
diff --git a/src/iagno.vala b/src/iagno.vala
index a47d478..8310014 100644
--- a/src/iagno.vala
+++ b/src/iagno.vala
@@ -515,7 +515,7 @@ private class Iagno : Gtk.Application
if ((game.current_color != player_one && computer != null) || !game.current_player_can_move)
return;
- if (game.place_tile (x, y) == 0)
+ if (!game.place_tile (x, y))
{
/* Translators: during a game, notification to display when the player tries to make an illegal
move */
window.set_subtitle (_("You can’t move there!"));
diff --git a/src/test-iagno.vala b/src/test-iagno.vala
index bc2859b..4a3bc4b 100644
--- a/src/test-iagno.vala
+++ b/src/test-iagno.vala
@@ -91,7 +91,7 @@ private class TestIagno : Object
" L L L L L L L L" };
Game game = new Game.from_strings (board, Player.DARK);
assert_true (game.number_of_moves == 0);
- assert_true (game.place_tile (7, 2) > 0);
+ assert_true (game.place_tile (7, 2));
assert_true (game.number_of_moves == 1);
assert_true (!game.current_player_can_move);
game.pass ();
@@ -99,7 +99,7 @@ private class TestIagno : Object
game.undo (2);
assert_true (game.number_of_moves == 0);
assert_true (game.to_string ().strip () == string.joinv ("\n", board).strip ());
- assert_true (game.place_tile (7, 2) > 0);
+ assert_true (game.place_tile (7, 2));
assert_true (game.number_of_moves == 1);
assert_true (!game.current_player_can_move);
game.undo (1);
@@ -111,9 +111,9 @@ private class TestIagno : Object
{
Game game = new Game ();
assert_true (game.number_of_moves == 0);
- game.place_tile (2, 3);
+ assert_true (game.place_tile (2, 3));
assert_true (game.number_of_moves == 1);
- game.place_tile (2, 2);
+ assert_true (game.place_tile (2, 2));
assert_true (game.number_of_moves == 2);
}
@@ -129,7 +129,7 @@ private class TestIagno : Object
" D D D D D D D D" };
Game game = new Game.from_strings (board, Player.DARK);
assert_true (game.current_color == Player.DARK);
- assert_true (game.place_tile (1, 2) > 0);
+ assert_true (game.place_tile (1, 2));
assert_true (game.current_color == Player.LIGHT);
assert_true (!game.current_player_can_move);
game.pass ();
@@ -235,61 +235,61 @@ private class TestIagno : Object
Game game = new Game.from_strings (board, Player.DARK);
ComputerPlayer ai = new ComputerPlayer (game, /* AI level */ 3);
- assert_true (game.place_tile (4, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 1));
assert_true (ai.force_moving (5, 5));
- assert_true (game.place_tile (4, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 5));
assert_true (ai.force_moving (3, 5));
- assert_true (game.place_tile (2, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 5));
assert_true (ai.force_moving (2, 4));
- assert_true (game.place_tile (4, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 6));
assert_true (ai.force_moving (2, 6));
- assert_true (game.place_tile (1, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 4));
assert_true (ai.force_moving (0, 4));
- assert_true (game.place_tile (2, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 3));
assert_true (ai.force_moving (1, 3));
- assert_true (game.place_tile (1, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 5));
assert_true (ai.force_moving (2, 2));
- assert_true (game.place_tile (3, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 2));
assert_true (ai.force_moving (5, 2));
- assert_true (game.place_tile (6, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 4));
assert_true (ai.force_moving (5, 6));
- assert_true (game.place_tile (3, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 0));
assert_true (ai.force_moving (0, 5));
- assert_true (game.place_tile (3, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 6));
assert_true (ai.force_moving (5, 7));
- assert_true (game.place_tile (0, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 3));
assert_true (ai.force_moving (0, 2));
- assert_true (game.place_tile (6, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 5));
assert_true (ai.force_moving (1, 2));
- assert_true (game.place_tile (3, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 7));
assert_true (ai.force_moving (2, 7));
- assert_true (game.place_tile (4, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 7));
assert_true (ai.force_moving (6, 6));
- assert_true (game.place_tile (2, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 1));
assert_true (ai.force_moving (2, 0));
- assert_true (game.place_tile (1, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 0));
assert_true (ai.force_moving (1, 1));
- assert_true (game.place_tile (6, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 2));
assert_true (ai.force_moving (4, 0));
- assert_true (game.place_tile (5, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 0));
assert_true (ai.force_moving (7, 3));
- assert_true (game.place_tile (6, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 3));
assert_true (ai.force_moving (7, 2));
- assert_true (game.place_tile (5, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 1));
assert_true (ai.force_moving (6, 1));
- assert_true (game.place_tile (7, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 4));
assert_true (ai.force_moving (7, 5));
- assert_true (game.place_tile (7, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 1));
assert_true (ai.force_moving (7, 0));
- assert_true (game.place_tile (7, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 7));
assert_true (ai.force_moving (6, 7));
- assert_true (game.place_tile (7, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 6));
assert_true (ai.force_moving (6, 0));
- assert_true (game.place_tile (0, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 0));
assert_true (ai.force_moving (0, 1));
- assert_true (game.place_tile (0, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 6));
assert_true (ai.force_moving (1, 6));
- assert_true (game.place_tile (1, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 7));
assert_true (ai.force_moving (0, 7));
}
@@ -308,61 +308,61 @@ private class TestIagno : Object
Game game = new Game.from_strings (board, Player.DARK);
ComputerPlayer ai = new ComputerPlayer (game, /* AI level */ 3);
- assert_true (game.place_tile (4, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 2));
assert_true (ai.force_moving (5, 5));
- assert_true (game.place_tile (6, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 4));
assert_true (ai.force_moving (5, 2));
- assert_true (game.place_tile (6, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 5));
assert_true (ai.force_moving (2, 2));
- assert_true (game.place_tile (3, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 1));
assert_true (ai.force_moving (4, 5));
- assert_true (game.place_tile (3, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 5));
assert_true (ai.force_moving (6, 3));
- assert_true (game.place_tile (2, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 4));
assert_true (ai.force_moving (3, 6));
- assert_true (game.place_tile (7, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 3));
assert_true (ai.force_moving (2, 5));
- assert_true (game.place_tile (3, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 7));
assert_true (ai.force_moving (7, 5));
- assert_true (game.place_tile (5, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 6));
assert_true (ai.force_moving (5, 7));
- assert_true (game.place_tile (6, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 2));
assert_true (ai.force_moving (4, 6));
- assert_true (game.place_tile (4, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 7));
assert_true (ai.force_moving (2, 7));
- assert_true (game.place_tile (1, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 3));
assert_true (ai.force_moving (1, 2));
- assert_true (game.place_tile (0, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 2));
assert_true (ai.force_moving (2, 1));
- assert_true (game.place_tile (2, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 0));
assert_true (ai.force_moving (1, 1));
- assert_true (game.place_tile (2, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 6));
assert_true (ai.force_moving (1, 7));
- assert_true (game.place_tile (4, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 1));
assert_true (ai.force_moving (7, 4));
- assert_true (game.place_tile (7, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 6));
assert_true (ai.force_moving (0, 4));
- assert_true (game.place_tile (0, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 3));
assert_true (ai.force_moving (0, 1));
- assert_true (game.place_tile (1, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 4));
assert_true (ai.force_moving (6, 6));
- assert_true (game.place_tile (1, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 6));
assert_true (ai.force_moving (4, 0));
- assert_true (game.place_tile (3, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 0));
assert_true (ai.force_moving (1, 0));
- assert_true (game.place_tile (5, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 1));
assert_true (ai.force_moving (7, 2));
- assert_true (game.place_tile (7, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 7));
assert_true (ai.force_moving (6, 0));
- assert_true (game.place_tile (6, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 1));
assert_true (ai.force_moving (6, 7));
- assert_true (game.place_tile (0, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 7));
assert_true (ai.force_moving (5, 0));
- assert_true (game.place_tile (0, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 0));
assert_true (ai.force_moving (0, 6));
- assert_true (game.place_tile (0, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 5));
assert_true (ai.force_moving (1, 5));
- assert_true (game.place_tile (7, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 0));
assert_true (ai.force_moving (7, 1));
}
@@ -382,61 +382,61 @@ private class TestIagno : Object
ComputerPlayer ai = new ComputerPlayer (game, /* AI level */ 3);
assert_true (ai.force_moving (3, 5));
- assert_true (game.place_tile (6, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 3));
assert_true (ai.force_moving (3, 2));
- assert_true (game.place_tile (2, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 3));
assert_true (ai.force_moving (1, 4));
- assert_true (game.place_tile (4, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 5));
assert_true (ai.force_moving (6, 5));
- assert_true (game.place_tile (2, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 6));
assert_true (ai.force_moving (2, 5));
- assert_true (game.place_tile (3, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 6));
assert_true (ai.force_moving (2, 2));
- assert_true (game.place_tile (0, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 3));
assert_true (ai.force_moving (6, 2));
- assert_true (game.place_tile (1, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 3));
assert_true (ai.force_moving (0, 5));
- assert_true (game.place_tile (4, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 1));
assert_true (ai.force_moving (5, 1));
- assert_true (game.place_tile (6, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 4));
assert_true (ai.force_moving (1, 5));
- assert_true (game.place_tile (5, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 0));
assert_true (ai.force_moving (7, 3));
- assert_true (game.place_tile (2, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 4));
assert_true (ai.force_moving (3, 0));
- assert_true (game.place_tile (4, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 2));
assert_true (ai.force_moving (3, 1));
- assert_true (game.place_tile (0, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 4));
assert_true (ai.force_moving (0, 2));
- assert_true (game.place_tile (1, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 2));
assert_true (ai.force_moving (4, 7));
- assert_true (game.place_tile (2, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 7));
assert_true (ai.force_moving (0, 6));
- assert_true (game.place_tile (4, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 6));
assert_true (ai.force_moving (5, 7));
- assert_true (game.place_tile (5, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 6));
assert_true (ai.force_moving (2, 1));
- assert_true (game.place_tile (2, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 0));
assert_true (ai.force_moving (1, 0));
- assert_true (game.place_tile (7, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 5));
assert_true (ai.force_moving (6, 7));
- assert_true (game.place_tile (7, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 2));
assert_true (ai.force_moving (7, 6));
- assert_true (game.place_tile (7, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 7));
assert_true (ai.force_moving (3, 7));
- assert_true (game.place_tile (7, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 4));
assert_true (ai.force_moving (1, 1));
- assert_true (game.place_tile (6, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 6));
assert_true (ai.force_moving (6, 1));
- assert_true (game.place_tile (0, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 0));
assert_true (ai.force_moving (1, 7));
- assert_true (game.place_tile (4, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 0));
assert_true (ai.force_moving (6, 0));
- assert_true (game.place_tile (0, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 7));
assert_true (ai.force_moving (0, 1));
- assert_true (game.place_tile (1, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 6));
assert_true (ai.force_moving (7, 1));
- assert_true (game.place_tile (7, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 0));
}
private static void test_complete_game_4 ()
@@ -455,63 +455,63 @@ private class TestIagno : Object
ComputerPlayer ai = new ComputerPlayer (game, /* AI level */ 3);
assert_true (ai.force_moving (5, 4));
- assert_true (game.place_tile (6, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 4));
assert_true (ai.force_moving (1, 5));
- assert_true (game.place_tile (2, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 2));
assert_true (ai.force_moving (3, 5));
- assert_true (game.place_tile (1, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 4));
assert_true (ai.force_moving (6, 3));
- assert_true (game.place_tile (4, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 2));
assert_true (ai.force_moving (4, 1));
- assert_true (game.place_tile (5, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 5));
assert_true (ai.force_moving (2, 3));
- assert_true (game.place_tile (4, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 0));
assert_true (ai.force_moving (5, 6));
- assert_true (game.place_tile (2, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 6));
assert_true (ai.force_moving (2, 5));
- assert_true (game.place_tile (0, 4, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 4));
assert_true (ai.force_moving (0, 6));
- assert_true (game.place_tile (1, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 3));
assert_true (ai.force_moving (5, 1));
- assert_true (game.place_tile (6, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 7));
assert_true (ai.force_moving (3, 6));
- assert_true (game.place_tile (3, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 7));
assert_true (ai.force_moving (5, 0));
- assert_true (game.place_tile (6, 5, /* apply */ true) != 0);
+ assert_true (game.place_tile (6, 5));
assert_true (ai.force_moving (3, 0));
- assert_true (game.place_tile (3, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (3, 1));
assert_true (ai.force_moving (0, 2));
- assert_true (game.place_tile (5, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (5, 3));
assert_true (ai.force_moving (7, 4));
- assert_true (game.place_tile (7, 2, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 2));
assert_true (ai.force_moving (7, 5));
- assert_true (game.place_tile (4, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 6));
assert_true (ai.force_moving (1, 2));
- assert_true (game.place_tile (7, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 3));
assert_true (ai.force_moving (6, 2));
- assert_true (game.place_tile (7, 6, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 6));
assert_true (ai.force_moving (2, 7));
- assert_true (game.place_tile (1, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 7));
assert_true (ai.force_moving (2, 0));
- assert_true (game.place_tile (7, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 1));
assert_true (ai.force_moving (6, 6));
- assert_true (game.place_tile (0, 3, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 3));
assert_true (ai.force_moving (0, 5));
- assert_true (game.place_tile (7, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 7));
assert_true (ai.force_moving (6, 1));
- assert_true (game.place_tile (7, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (7, 0));
assert_true (ai.force_moving (6, 0));
- assert_true (game.place_tile (1, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (1, 0));
assert_true (ai.force_moving (1, 6));
- assert_true (game.place_tile (2, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (2, 1));
game.pass ();
- assert_true (game.place_tile (0, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 7));
game.pass ();
- assert_true (game.place_tile (0, 1, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 1));
assert_true (ai.force_moving (1, 1));
- assert_true (game.place_tile (0, 0, /* apply */ true) != 0);
+ assert_true (game.place_tile (0, 0));
game.pass ();
- assert_true (game.place_tile (4, 7, /* apply */ true) != 0);
+ assert_true (game.place_tile (4, 7));
assert_true (ai.force_moving (5, 7));
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]