[gnome-games] chess: Test for insufficient material, fix pawn promotion, put check/checmate suffix on SAN/LAN move
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] chess: Test for insufficient material, fix pawn promotion, put check/checmate suffix on SAN/LAN move
- Date: Tue, 25 Jan 2011 08:37:40 +0000 (UTC)
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]