[iagno] Redo Undo.



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]