[gnome-games] chess: Test for insufficient material, fix pawn promotion, put check/checmate suffix on SAN/LAN move



commit 9aacf79dc2c55023fdf0c4ef94882e8bfc8c3794
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Jan 25 18:37:24 2011 +1000

    chess: Test for insufficient material, fix pawn promotion, put check/checmate suffix on SAN/LAN move text, Use correct notation for castling with SAN/LAN move text, better unit tests

 glchess/src/chess-game.vala      |  202 +++++++++++++++++++++++++++++++-------
 glchess/src/test-chess-game.vala |   55 +++++++----
 2 files changed, 204 insertions(+), 53 deletions(-)
---
diff --git a/glchess/src/chess-game.vala b/glchess/src/chess-game.vala
index ea739bd..340471b 100644
--- a/glchess/src/chess-game.vala
+++ b/glchess/src/chess-game.vala
@@ -97,6 +97,13 @@ public class ChessPiece
     }
 }
 
+public enum CheckState
+{
+    NONE,
+    CHECK,
+    CHECKMATE
+}
+
 public class ChessMove
 {
     public int number;
@@ -110,24 +117,39 @@ public class ChessMove
     public int f1;
     public bool ambiguous_rank;
     public bool ambiguous_file;
+    public CheckState check_state;
 
     public string get_lan ()
     {
-        const char promotion_symbols[] = {' ', 'R', 'N', 'B', 'Q', 'K'};
-        if (promotion_piece != null)
+        if (moved_rook != null)
         {
-            if (victim != null)
-                return "%c%dx%c%d=%c".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1, promotion_symbols[promotion_piece.type]);
+            if (f1 > f0)
+                return "O-O";
             else
-                return "%c%d-%c%d=%c".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1, promotion_symbols[promotion_piece.type]);
+                return "O-O-O";
         }
+
+        var builder = new StringBuilder ();
+        if (victim != null)
+            builder.append_printf ("%c%dx%c%d", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1);
         else
+            builder.append_printf ("%c%d-%c%d", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1);
+
+        const char promotion_symbols[] = {' ', 'R', 'N', 'B', 'Q', 'K'};
+        if (promotion_piece != null)
+            builder.append_printf ("=%c", promotion_symbols[promotion_piece.type]);
+
+        switch (check_state)
         {
-            if (victim != null)
-                return "%c%dx%c%d".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1);
-            else
-                return "%c%d-%c%d".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1);
+        case CheckState.CHECK:
+            builder.append_c ('+');
+            break;
+        case CheckState.CHECKMATE:
+            builder.append_c ('#');
+            break;
         }
+
+        return builder.str;
     }
 
     public string get_san ()
@@ -148,6 +170,14 @@ public class ChessMove
 
     private string make_san (string[] piece_names)
     {
+        if (moved_rook != null)
+        {
+            if (f1 > f0)
+                return "O-O";
+            else
+                return "O-O-O";
+        }
+
         var builder = new StringBuilder ();
         builder.append (piece_names[piece.type]);
         if (ambiguous_file)
@@ -157,8 +187,20 @@ public class ChessMove
         if (victim != null)
             builder.append ("x");
         builder.append_printf ("%c%d", 'a' + f1, r1 + 1);
+
         if (promotion_piece != null)
             builder.append_printf ("=%s", piece_names[promotion_piece.type]);
+
+        switch (check_state)
+        {
+        case CheckState.CHECK:
+            builder.append_c ('+');
+            break;
+        case CheckState.CHECKMATE:
+            builder.append_c ('#');
+            break;
+        }
+
         return builder.str;
     }
 
@@ -188,18 +230,11 @@ public class ChessMove
         move.f1 = f1;
         move.ambiguous_rank = ambiguous_rank;
         move.ambiguous_file = ambiguous_file;
+        move.check_state = check_state;
         return move;
     }
 }
 
-public enum CheckState
-{
-    NONE,
-    CHECK,
-    CHECKMATE,
-    STALEMATE
-}
-
 public class ChessState
 {
     public int number = 0;
@@ -665,6 +700,7 @@ public class ChessState
         }
 
         current_player = color == Color.WHITE ? players[Color.BLACK] : players[Color.WHITE];
+        check_state = get_check_state (current_player);
 
         last_move = new ChessMove ();
         last_move.number = number;
@@ -680,12 +716,40 @@ public class ChessState
         last_move.f1 = f1;
         last_move.ambiguous_rank = ambiguous_rank;
         last_move.ambiguous_file = ambiguous_file;
-
-        check_state = get_check_state (current_player);
+        last_move.check_state = check_state;
 
         return true;
     }
 
+    public ChessResult get_result (out ChessRule rule)
+    {
+        if (check_state == CheckState.CHECKMATE)
+        {
+            if (current_player.color == Color.WHITE)
+            {
+                rule = ChessRule.CHECKMATE;
+                return ChessResult.BLACK_WON;
+            }
+            else
+            {
+                rule = ChessRule.CHECKMATE;
+                return ChessResult.WHITE_WON;
+            }
+        }
+        else if (!can_move (current_player))
+        {
+            rule = ChessRule.STALEMATE;
+            return ChessResult.DRAW;
+        }
+        else if (last_move.victim != null && !have_sufficient_material ())
+        {
+            rule = ChessRule.INSUFFICIENT_MATERIAL;
+            return ChessResult.DRAW;
+        }
+
+        return ChessResult.IN_PROGRESS;
+    }
+
     private CheckState get_check_state (ChessPlayer player)
     {
         if (is_in_check (player))
@@ -695,11 +759,6 @@ public class ChessState
             else
                 return CheckState.CHECK;
         }
-        else
-        {
-            if (!can_move (player))
-                return CheckState.STALEMATE;
-        }
         return CheckState.NONE;
     }
 
@@ -779,6 +838,80 @@ public class ChessState
             return true;
     }
 
+    public bool have_sufficient_material ()
+    {
+        var white_knight_count = 0;
+        var white_bishop_count = 0;
+        var white_bishop_on_white_square = false;
+        var white_bishop_on_black_square = false;
+        var black_knight_count = 0;
+        var black_bishop_count = 0;
+        var black_bishop_on_white_square = false;
+        var black_bishop_on_black_square = false;
+
+        for (int i = 0; i < 64; i++)
+        {
+            var p = board[i];
+            if (p == null)
+                continue;
+
+            /* Any pawns, rooks or queens can perform checkmate */
+            if (p.type == PieceType.PAWN || p.type == PieceType.ROOK || p.type == PieceType.QUEEN)
+                return true;
+
+            /* Otherwise, count the minor pieces for each colour... */
+            if (p.type == PieceType.KNIGHT)
+            {
+                if (p.player.color == Color.WHITE)
+                    white_knight_count++;
+                else
+                    black_knight_count++;
+            }
+
+            if (p.type == PieceType.BISHOP)
+            {
+                var color = Color.BLACK;
+                if ((i + i/8) % 2 != 0)
+                    color = Color.WHITE;
+
+                if (p.player.color == Color.WHITE)
+                {
+                    if (color == Color.WHITE)
+                        white_bishop_on_white_square = true;
+                    else
+                        white_bishop_on_black_square = true;
+                    white_bishop_count++;
+                }
+                else
+                {
+                    if (color == Color.WHITE)
+                        black_bishop_on_white_square = true;
+                    else
+                        black_bishop_on_black_square = true;
+                    black_bishop_count++;
+                }
+            }
+
+            /* Three knights versus king can checkmate */
+            if (white_knight_count > 2 || black_knight_count > 2)
+                return true;
+
+            /* Bishop and knight versus king can checkmate */
+            if (white_bishop_count > 0 && white_knight_count > 0)
+                return true;
+            if (black_bishop_count > 0 && black_knight_count > 0)
+                return true;
+
+            /* King and bishops versus king can checkmate as long as the bishops are on both colours */
+            if (white_bishop_on_white_square && white_bishop_on_black_square)
+                return true;
+            if (black_bishop_on_white_square && black_bishop_on_black_square)
+                return true;
+        }
+
+        return false;
+    }
+
     private bool decode_piece_type (unichar c, out PieceType type)
     {
         switch (c)
@@ -863,9 +996,11 @@ public class ChessState
                 i++;
             }
             if (move[i] == '=')
+            {
                 i++;
-            if (decode_piece_type (move[i], out promotion_type))
-                i++;
+                if (decode_piece_type (move[i], out promotion_type))
+                    i++;
+            }
 
             /* Don't have a destination to move to */
             if (r1 < 0 || f1 < 0)
@@ -1047,16 +1182,11 @@ public class ChessGame
             state.last_move.moved_rook.moved ();
         moved (state.last_move);
 
-        if (state.check_state == CheckState.CHECKMATE)
-        {
-            if (current_player.color == Color.WHITE)
-                stop (ChessResult.BLACK_WON, ChessRule.CHECKMATE);
-            else
-                stop (ChessResult.WHITE_WON, ChessRule.CHECKMATE);
-        }
-        else if (state.check_state == CheckState.STALEMATE)
+        ChessRule rule;
+        var result = state.get_result (out rule);
+        if (result != ChessResult.IN_PROGRESS)
         {
-            stop (ChessResult.DRAW, ChessRule.STALEMATE);
+            stop (result, rule);
         }
         else
         {
@@ -1089,6 +1219,8 @@ public class ChessGame
 
         if (move_stack.data.halfmove_clock >= 50)
             stop (ChessResult.DRAW, ChessRule.FIFTY_MOVES);
+        //else if () // FIXME: Check for three-fold-repetition
+        //    ;
         else
             return false;
 
diff --git a/glchess/src/test-chess-game.vala b/glchess/src/test-chess-game.vala
index ae3283d..b175c4e 100644
--- a/glchess/src/test-chess-game.vala
+++ b/glchess/src/test-chess-game.vala
@@ -3,32 +3,43 @@ class GlChess
     static int test_count = 0;
     static int failure_count = 0;
 
-    private static void test_good_move (string fen, string move, string result_fen, CheckState check_state = CheckState.NONE)
+    private static void test_good_move (string fen, string move, string result_fen,
+                                        ChessResult result = ChessResult.IN_PROGRESS,
+                                        ChessRule rule = ChessRule.CHECKMATE)
     {
         ChessState state = new ChessState (fen);
         test_count++;
         if (!state.move (move))
         {
-            stderr.printf ("%d. Not allowed to do valid move: %s : %s\n", test_count, fen, move);
+            stderr.printf ("%d. FAIL %s + %s is a valid move\n", test_count, fen, move);
             failure_count++;
             return;
         }
 
         if (state.get_fen () != result_fen)
         {
-            stderr.printf ("%d. Move led to invalid state: %s : %s -> %s, not %s\n", test_count, fen, move, state.get_fen (), result_fen);
+            stderr.printf ("%d. FAIL %s + %s has state %s not %s\n", test_count, fen, move, state.get_fen (), result_fen);
             failure_count++;
             return;
         }
 
-        if (state.check_state != check_state)
+        if (state.last_move.get_san () != move)
         {
-            stderr.printf ("%d. Move led to invalid check state: %s : %s -> %d, not %d\n", test_count, fen, move, state.check_state, check_state);
+            stderr.printf ("%d. FAIL %s + %s has SAN move %s\n", test_count, fen, move, state.last_move.get_san ());
             failure_count++;
             return;
         }
 
-        stderr.printf ("%d. %s + %s -> %s OK\n", test_count, fen, move, result_fen);
+        ChessRule move_rule;
+        var move_result = state.get_result (out move_rule);
+        if (move_result != result || move_rule != rule)
+        {
+            stderr.printf ("%d. FAIL %s + %s has result %d not %d\n", test_count, fen, move, move_result, result);
+            failure_count++;
+            return;
+        }
+
+        stderr.printf ("%d. PASS %s + %s is valid\n", test_count, fen, move);
     }
 
     private static void test_bad_move (string fen, string move)
@@ -37,29 +48,33 @@ class GlChess
         test_count++;
         if (state.move (move, false))
         {
-            stderr.printf ("%d. Allowed to do invalid move: %s : %s\n", test_count, fen, move);
+            stderr.printf ("%d. FAIL %s + %s is valid\n", test_count, fen, move);
             failure_count++;
             return;
         }
 
-        stderr.printf ("%d. %s + %s -> invalid OK\n", test_count, fen, move);
+        stderr.printf ("%d. PASS %s + %s is invalid\n", test_count, fen, move);
     }
 
     public static int main (string[] args)
     {
         /* Pawn move */
-        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a2a3",
+        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a3",
                         "8/8/8/8/8/P7/8/8 b - - 0 1");
 
         /* Pawn march */
-        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a2a4",
+        test_good_move ("8/8/8/8/8/8/P7/8 w - - 0 1", "a4",
                         "8/8/8/8/P7/8/8/8 b - a3 0 1");
 
         /* Pawn march only allowed from baseline */
         test_bad_move ("8/8/8/8/8/P7/8/8 w - - 0 1", "a2a5");
-        
+
+        /* Pawn promotion */
+        test_good_move ("8/P7/8/8/8/8/8/8 w - - 0 1", "a8=Q",
+                        "Q7/8/8/8/8/8/8/8 b - - 0 1");
+
         /* En passant */
-        test_good_move ("8/8/8/pP6/8/8/8/8 w - a6 0 1", "b5a6",
+        test_good_move ("8/8/8/pP6/8/8/8/8 w - a6 0 1", "bxa6",
                         "8/8/P7/8/8/8/8/8 b - - 0 1");
 
         /* Can't en passant if wasn't allowed */
@@ -89,16 +104,20 @@ class GlChess
         test_bad_move ("4r3/8/8/8/8/8/4R3/4K3 w - - 0 1", "e2f2");
 
         /* Check */
-        test_good_move ("k7/8/8/8/8/8/8/1R6 w - - 0 1", "b1a1",
-                        "k7/8/8/8/8/8/8/R7 b - - 1 1", CheckState.CHECK);
+        test_good_move ("k7/8/8/8/8/8/8/1R6 w - - 0 1", "Ra1+",
+                        "k7/8/8/8/8/8/8/R7 b - - 1 1");
 
         /* Checkmate */
-        test_good_move ("k7/8/8/8/8/8/1R6/1R6 w - - 0 1", "b1a1",
-                        "k7/8/8/8/8/8/1R6/R7 b - - 1 1", CheckState.CHECKMATE);
+        test_good_move ("k7/8/8/8/8/8/1R6/1R6 w - - 0 1", "Ra1#",
+                        "k7/8/8/8/8/8/1R6/R7 b - - 1 1", ChessResult.WHITE_WON);
 
         /* Stalemate */
-        test_good_move ("k7/8/7R/8/8/8/8/1R6 w - - 0 1", "h6h7",
-                        "k7/7R/8/8/8/8/8/1R6 b - - 1 1", CheckState.STALEMATE);
+        test_good_move ("k7/8/7R/8/8/8/8/1R6 w - - 0 1", "Rh7",
+                        "k7/7R/8/8/8/8/8/1R6 b - - 1 1", ChessResult.DRAW, ChessRule.STALEMATE);
+
+        /* Insufficient material */
+        test_good_move ("k7/7p/7K/8/8/8/8/8 w - - 0 1", "Kxh7",
+                        "k7/7K/8/8/8/8/8/8 b - - 0 1", ChessResult.DRAW, ChessRule.INSUFFICIENT_MATERIAL);
 
         stdout.printf ("%d/%d tests successful\n", test_count - failure_count, test_count);
 



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