[gnome-chess] Split chess-game.vala into multiple files
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-chess] Split chess-game.vala into multiple files
- Date: Wed, 9 Apr 2014 02:51:13 +0000 (UTC)
commit dd340f9e9e831d5e01cad787255abb2e5a04a9b8
Author: Michael Catanzaro <mcatanzaro gnome org>
Date: Mon Apr 7 20:26:54 2014 -0500
Split chess-game.vala into multiple files
This is easily justified by observing that the majority of
chess-game.vala was devoted to the ChessState class.
src/Makefile.am | 8 +
src/chess-game.vala | 1233 -------------------------------------------------
src/chess-move.vala | 141 ++++++
src/chess-piece.vala | 73 +++
src/chess-player.vala | 87 ++++
src/chess-state.vala | 972 ++++++++++++++++++++++++++++++++++++++
6 files changed, 1281 insertions(+), 1233 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index d5fb749..3ba1c88 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -21,8 +21,12 @@ gnome_chess_SOURCES = \
chess-engine-cecp.vala \
chess-engine-uci.vala \
chess-game.vala \
+ chess-move.vala \
+ chess-piece.vala \
+ chess-player.vala \
chess-pgn.vala \
chess-scene.vala \
+ chess-state.vala \
chess-view.vala \
chess-view-2d.vala \
chess-view-3d.vala
@@ -31,6 +35,10 @@ test_chess_game_SOURCES = \
chess-bitboard.vala \
chess-clock.vala \
chess-game.vala \
+ chess-move.vala \
+ chess-piece.vala \
+ chess-player.vala \
+ chess-state.vala \
test-chess-game.vala
test_chess_game_CFLAGS = \
-w \
diff --git a/src/chess-game.vala b/src/chess-game.vala
index 6d396d7..c255489 100644
--- a/src/chess-game.vala
+++ b/src/chess-game.vala
@@ -9,1239 +9,6 @@
* license.
*/
-public enum Color
-{
- WHITE,
- BLACK
-}
-
-public class ChessPlayer : Object
-{
- public Color color;
- public signal void start_turn ();
- public signal bool do_move (string move, bool apply);
- public signal void do_undo ();
- public signal bool do_resign ();
- public signal bool do_claim_draw ();
-
- private bool _local_human = false;
- public bool local_human
- {
- get { return _local_human; }
- set
- {
- _local_human = value;
- }
- }
-
- public ChessPlayer (Color color)
- {
- this.color = color;
- }
-
- public bool move (string move, bool apply = true)
- {
- return do_move (move, apply);
- }
-
- public bool move_with_coords (int r0, int f0, int r1, int f1,
- bool apply = true, PieceType promotion_type = PieceType.QUEEN)
- {
- string move = "%c%d%c%d".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1);
-
- switch (promotion_type)
- {
- case PieceType.QUEEN:
- /* Default is queen so don't add anything */
- break;
- case PieceType.KNIGHT:
- move += "=N";
- break;
- case PieceType.ROOK:
- move += "=R";
- break;
- case PieceType.BISHOP:
- move += "=B";
- break;
- default:
- break;
- }
-
- return do_move (move, apply);
- }
-
- public void undo ()
- {
- do_undo ();
- }
-
- public bool resign ()
- {
- return do_resign ();
- }
-
- public bool claim_draw ()
- {
- return do_claim_draw ();
- }
-}
-
-public enum PieceType
-{
- PAWN,
- ROOK,
- KNIGHT,
- BISHOP,
- QUEEN,
- KING
-}
-
-public class ChessPiece
-{
- public ChessPlayer player;
- public PieceType type;
-
- public signal void moved ();
- public signal void promoted ();
- public signal void died ();
-
- public Color color
- {
- get { return player.color; }
- }
-
- public unichar symbol
- {
- get
- {
- unichar c = ' ';
- switch (type)
- {
- case PieceType.PAWN:
- c = 'p';
- break;
- case PieceType.ROOK:
- c = 'r';
- break;
- case PieceType.KNIGHT:
- c = 'n';
- break;
- case PieceType.BISHOP:
- c = 'b';
- break;
- case PieceType.QUEEN:
- c = 'q';
- break;
- case PieceType.KING:
- c = 'k';
- break;
- }
- if (player.color == Color.WHITE)
- c = c.toupper ();
- return c;
- }
- }
-
- public ChessPiece (ChessPlayer player, PieceType type)
- {
- this.player = player;
- this.type = type;
- }
-}
-
-public enum CheckState
-{
- NONE,
- CHECK,
- CHECKMATE
-}
-
-public class ChessMove
-{
- public int number;
- public ChessPiece piece;
- public ChessPiece? promotion_piece;
- public ChessPiece? moved_rook;
- public ChessPiece? victim;
- public int r0;
- public int f0;
- public int r1;
- public int f1;
- public bool ambiguous_rank;
- public bool ambiguous_file;
- public CheckState check_state;
-
- public string get_lan ()
- {
- if (moved_rook != null)
- {
- if (f1 > f0)
- return "O-O";
- else
- 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)
- {
- case CheckState.CHECK:
- builder.append_c ('+');
- break;
- case CheckState.CHECKMATE:
- builder.append_c ('#');
- break;
- }
-
- return builder.str;
- }
-
- public string get_san ()
- {
- const string piece_names[] = {"", "R", "N", "B", "Q", "K"};
- return make_san ((string[]) piece_names);
- }
-
- public string get_fan ()
- {
- const string white_piece_names[] = {"", "♖", "♘", "♗", "♕", "♔"};
- const string black_piece_names[] = {"", "♜", "♞", "♝", "♛", "♚"};
- if (piece.color == Color.WHITE)
- return make_san ((string[]) white_piece_names);
- else
- return make_san ((string[]) black_piece_names);
- }
-
- 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)
- builder.append_printf ("%c", 'a' + f0);
- if (ambiguous_rank)
- builder.append_printf ("%d", r0 + 1);
- 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;
- }
-
- /* Move suitable for a chess engine (CECP/UCI) */
- public string get_engine ()
- {
- var builder = new StringBuilder ();
- const char promotion_symbols[] = {' ', 'r', 'n', 'b', 'q', ' '};
- if (promotion_piece != null)
- builder.append_printf ("%c%d%c%d%c", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1,
promotion_symbols[promotion_piece.type]);
- else
- builder.append_printf ("%c%d%c%d", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1);
- return builder.str;
- }
-
- public ChessMove copy ()
- {
- var move = new ChessMove ();
- move.number = number;
- move.piece = piece;
- move.promotion_piece = promotion_piece;
- move.moved_rook = moved_rook;
- move.victim = victim;
- move.r0 = r0;
- move.f0 = f0;
- move.r1 = r1;
- move.f1 = f1;
- move.ambiguous_rank = ambiguous_rank;
- move.ambiguous_file = ambiguous_file;
- move.check_state = check_state;
- return move;
- }
-}
-
-public class ChessState
-{
- public int number = 0;
- public ChessPlayer players[2];
- public ChessPlayer current_player;
- public ChessPlayer opponent
- {
- get { return current_player.color == Color.WHITE ? players[Color.BLACK] : players[Color.WHITE]; }
- }
- public bool can_castle_kingside[2];
- public bool can_castle_queenside[2];
- public int en_passant_index = -1;
- public CheckState check_state;
- public int halfmove_clock;
-
- public ChessPiece board[64];
- public ChessMove? last_move = null;
-
- /* Bitmap of all the pieces */
- private int64 piece_masks[2];
-
- private ChessState.empty ()
- {
- }
-
- public ChessState (string fen)
- {
- players[Color.WHITE] = new ChessPlayer (Color.WHITE);
- players[Color.BLACK] = new ChessPlayer (Color.BLACK);
- for (int i = 0; i < 64; i++)
- board[i] = null;
-
- string[] fields = fen.split (" ");
- //if (fields.length != 6)
- // throw new Error ("Invalid FEN string");
-
- /* Field 1: Piece placement */
- string[] ranks = fields[0].split ("/");
- //if (ranks.length != 8)
- // throw new Error ("Invalid piece placement");
- for (int rank = 0; rank < 8; rank++)
- {
- var rank_string = ranks[7 - rank];
- for (int file = 0, offset = 0; file < 8 && offset < rank_string.length; offset++)
- {
- var c = rank_string[offset];
- if (c >= '1' && c <= '8')
- {
- file += c - '0';
- continue;
- }
-
- PieceType type;
- var color = c.isupper () ? Color.WHITE : Color.BLACK;
- if (!decode_piece_type (c.toupper (), out type))
- ;//throw new Error ("");
-
- int index = get_index (rank, file);
- ChessPiece piece = new ChessPiece (players[color], type);
- board[index] = piece;
- int64 mask = BitBoard.set_location_masks[index];
- piece_masks[color] |= mask;
- file++;
- }
- }
-
- /* Field 2: Active color */
- if (fields[1] == "w")
- current_player = players[Color.WHITE];
- else if (fields[1] == "b")
- current_player = players[Color.BLACK];
- //else
- // throw new Error ("Unknown active color: %s", fields[1]);
-
- /* Field 3: Castling availability */
- if (fields[2] != "-")
- {
- for (int i = 0; i < fields[2].length; i++)
- {
- var c = fields[2][i];
- if (c == 'K')
- can_castle_kingside[Color.WHITE] = true;
- else if (c == 'Q')
- can_castle_queenside[Color.WHITE] = true;
- else if (c == 'k')
- can_castle_kingside[Color.BLACK] = true;
- else if (c == 'q')
- can_castle_queenside[Color.BLACK] = true;
- //else
- // throw new Error ("");
- }
- }
-
- /* Field 4: En passant target square */
- if (fields[3] != "-")
- {
- //if (fields[3].length != 2)
- // throw new Error ("");
- en_passant_index = get_index (fields[3][1] - '1', fields[3][0] - 'a');
- }
-
- /* Field 5: Halfmove clock */
- halfmove_clock = int.parse (fields[4]);
-
- /* Field 6: Fullmove number */
- number = (int.parse (fields[5]) - 1) * 2;
- if (current_player.color == Color.BLACK)
- number++;
-
- check_state = get_check_state (current_player);
- }
-
- public ChessState copy ()
- {
- ChessState state = new ChessState.empty ();
-
- state.number = number;
- state.players[Color.WHITE] = players[Color.WHITE];
- state.players[Color.BLACK] = players[Color.BLACK];
- state.current_player = current_player;
- state.can_castle_kingside[Color.WHITE] = can_castle_kingside[Color.WHITE];
- state.can_castle_queenside[Color.WHITE] = can_castle_queenside[Color.WHITE];
- state.can_castle_kingside[Color.BLACK] = can_castle_kingside[Color.BLACK];
- state.can_castle_queenside[Color.BLACK] = can_castle_queenside[Color.BLACK];
- state.en_passant_index = en_passant_index;
- state.check_state = check_state;
- if (last_move != null)
- state.last_move = last_move.copy();
- for (int i = 0; i < 64; i++)
- state.board[i] = board[i];
- state.piece_masks[Color.WHITE] = piece_masks[Color.WHITE];
- state.piece_masks[Color.BLACK] = piece_masks[Color.BLACK];
- state.halfmove_clock = halfmove_clock;
-
- return state;
- }
-
- public bool equals (ChessState state)
- {
- /*
- * Check first if there is the same layout of pieces (unlikely),
- * then that the same player is on move, then that the move castling
- * and en-passant state are the same. This follows the rules for
- * determining threefold repetition:
- *
- * https://en.wikipedia.org/wiki/Threefold_repetition
- */
- if (piece_masks[Color.WHITE] != state.piece_masks[Color.WHITE] ||
- piece_masks[Color.BLACK] != state.piece_masks[Color.BLACK] ||
- current_player.color != state.current_player.color ||
- can_castle_kingside[Color.WHITE] != state.can_castle_kingside[Color.WHITE] ||
- can_castle_queenside[Color.WHITE] != state.can_castle_queenside[Color.WHITE] ||
- can_castle_kingside[Color.BLACK] != state.can_castle_kingside[Color.BLACK] ||
- can_castle_queenside[Color.BLACK] != state.can_castle_queenside[Color.BLACK] ||
- en_passant_index != state.en_passant_index)
- return false;
-
- /* Finally check the same piece types are present */
- for (int i = 0; i < 64; i++)
- {
- if (board[i] != null && board[i].type != state.board[i].type)
- return false;
- }
-
- return true;
- }
-
- public string get_fen ()
- {
- var value = new StringBuilder ();
-
- for (int rank = 7; rank >= 0; rank--)
- {
- int skip_count = 0;
- for (int file = 0; file < 8; file++)
- {
- var p = board[get_index (rank, file)];
- if (p == null)
- skip_count++;
- else
- {
- if (skip_count > 0)
- {
- value.append_printf ("%d", skip_count);
- skip_count = 0;
- }
- value.append_printf ("%c", (int) p.symbol);
- }
- }
- if (skip_count > 0)
- value.append_printf ("%d", skip_count);
- if (rank != 0)
- value.append_c ('/');
- }
-
- value.append_c (' ');
- if (current_player.color == Color.WHITE)
- value.append_c ('w');
- else
- value.append_c ('b');
-
- value.append_c (' ');
- if (can_castle_kingside[Color.WHITE])
- value.append_c ('K');
- if (can_castle_queenside[Color.WHITE])
- value.append_c ('Q');
- if (can_castle_kingside[Color.BLACK])
- value.append_c ('k');
- if (can_castle_queenside[Color.BLACK])
- value.append_c ('q');
- if (!(can_castle_kingside[Color.WHITE] | can_castle_queenside[Color.WHITE] |
can_castle_kingside[Color.BLACK] | can_castle_queenside[Color.BLACK]))
- value.append_c ('-');
-
- value.append_c (' ');
- if (en_passant_index >= 0)
- value.append_printf ("%c%d", 'a' + get_file (en_passant_index), get_rank (en_passant_index) + 1);
- else
- value.append_c ('-');
-
- value.append_c (' ');
- value.append_printf ("%d", halfmove_clock);
-
- value.append_c (' ');
- if (current_player.color == Color.WHITE)
- value.append_printf ("%d", number / 2);
- else
- value.append_printf ("%d", number / 2 + 1);
-
- return value.str;
- }
-
- public int get_index (int rank, int file)
- {
- return rank * 8 + file;
- }
-
- public int get_rank (int index)
- {
- return index / 8;
- }
-
- public int get_file (int index)
- {
- return index % 8;
- }
-
- public bool move (string move, bool apply = true)
- {
- int r0, f0, r1, f1;
- PieceType promotion_type;
-
- if (!decode_move (current_player, move, out r0, out f0, out r1, out f1, out promotion_type))
- return false;
-
- if (!move_with_coords (current_player, r0, f0, r1, f1, promotion_type, apply))
- return false;
-
- return true;
- }
-
- public bool move_with_coords (ChessPlayer player,
- int r0, int f0, int r1, int f1,
- PieceType promotion_type = PieceType.QUEEN,
- bool apply = true, bool test_check = true)
- {
- // FIXME: Make this use indexes to be faster
- var start = get_index (r0, f0);
- var end = get_index (r1, f1);
-
- var color = player.color;
- var opponent_color = color == Color.WHITE ? Color.BLACK : Color.WHITE;
-
- /* Must be moving own piece */
- var piece = board[start];
- if (piece == null || piece.player != player)
- return false;
-
- /* Check valid move */
- int64 end_mask = BitBoard.set_location_masks[end];
- int64 move_mask = BitBoard.move_masks[color * 64*6 + piece.type * 64 + start];
- if ((end_mask & move_mask) == 0)
- return false;
-
- /* Check no pieces in the way */
- int64 over_mask = BitBoard.over_masks[start * 64 + end];
- if ((over_mask & (piece_masks[Color.WHITE] | piece_masks[Color.BLACK])) != 0)
- return false;
-
- /* Get victim of move */
- var victim = board[end];
- var victim_index = end;
-
- /* Can't take own pieces */
- if (victim != null && victim.player == player)
- return false;
-
- /* Check special moves */
- int rook_start = -1, rook_end = -1;
- bool is_promotion = false;
- bool ambiguous_rank = false;
- bool ambiguous_file = false;
- switch (piece.type)
- {
- case PieceType.PAWN:
- /* Check if taking an marched pawn */
- if (victim == null && end == en_passant_index)
- {
- victim_index = get_index (r1 == 2 ? 3 : 4, f1);
- victim = board[victim_index];
- }
-
- /* If moving diagonally there must be a victim */
- if (f0 != f1)
- {
- if (victim == null)
- return false;
- }
- else
- {
- /* If moving forward can't take enemy */
- if (victim != null)
- return false;
- }
- is_promotion = r1 == 0 || r1 == 7;
-
- /* Always show the file of a pawn capturing */
- if (victim != null)
- ambiguous_file = true;
- break;
- case PieceType.KING:
- /* If moving more than one square must be castling */
- if ((f0 - f1).abs () > 1)
- {
- /* File the rook is on */
- rook_start = get_index (r0, f1 > f0 ? 7 : 0);
- rook_end = get_index (r0, f1 > f0 ? f1 - 1 : f1 + 1);
-
- /* Check if can castle */
- if (f1 > f0)
- {
- if (!can_castle_kingside[color])
- return false;
- }
- else
- {
- if (!can_castle_queenside[color])
- return false;
- }
-
- var rook = board[rook_start];
- if (rook == null || rook.type != PieceType.ROOK || rook.color != color)
- return false;
-
- /* Check rook can move */
- int64 rook_over_mask = BitBoard.over_masks[rook_start * 64 + rook_end];
- if ((rook_over_mask & (piece_masks[Color.WHITE] | piece_masks[Color.BLACK])) != 0)
- return false;
-
- /* Can't castle when in check */
- if (check_state == CheckState.CHECK)
- return false;
-
- /* Square moved across can't be under attack */
- if (!move_with_coords (player, r0, f0, get_rank (rook_end), get_file (rook_end),
PieceType.QUEEN, false, true))
- return false;
- }
- break;
- default:
- break;
- }
-
- if (!apply && !test_check)
- return true;
-
- /* Check if other pieces of the same type can make this move - this is required for SAN notation */
- if (apply)
- {
- for (int i = 0; i < 64; i++)
- {
- /* Ignore our move */
- if (i == start)
- continue;
-
- /* Check for a friendly piece of the same type */
- var p = board[i];
- if (p == null || p.player != player || p.type != piece.type)
- continue;
-
- /* If more than one piece can move then the rank and/or file are ambiguous */
- var r = get_rank (i);
- var f = get_file (i);
- if (move_with_coords (player, r, f, r1, f1, PieceType.QUEEN, false))
- {
- if (r != r0)
- ambiguous_rank = true;
- if (f != f0)
- ambiguous_file = true;
- }
- }
- }
-
- var old_white_mask = piece_masks[Color.WHITE];
- var old_black_mask = piece_masks[Color.BLACK];
- var old_white_can_castle_kingside = can_castle_kingside[Color.WHITE];
- var old_white_can_castle_queenside = can_castle_queenside[Color.WHITE];
- var old_black_can_castle_kingside = can_castle_kingside[Color.BLACK];
- var old_black_can_castle_queenside = can_castle_queenside[Color.BLACK];
- var old_en_passant_index = en_passant_index;
- var old_halfmove_clock = halfmove_clock;
-
- /* Update board */
- board[start] = null;
- piece_masks[Color.WHITE] &= BitBoard.clear_location_masks[start];
- piece_masks[Color.BLACK] &= BitBoard.clear_location_masks[start];
- if (victim != null)
- {
- board[victim_index] = null;
- piece_masks[Color.WHITE] &= BitBoard.clear_location_masks[victim_index];
- piece_masks[Color.BLACK] &= BitBoard.clear_location_masks[victim_index];
- }
- if (is_promotion)
- board[end] = new ChessPiece (player, promotion_type);
- else
- board[end] = piece;
- piece_masks[color] |= end_mask;
- piece_masks[opponent_color] &= BitBoard.clear_location_masks[end];
- if (rook_start >= 0)
- {
- var rook = board[rook_start];
- board[rook_start] = null;
- piece_masks[color] &= BitBoard.clear_location_masks[rook_start];
- board[rook_end] = rook;
- piece_masks[color] |= BitBoard.set_location_masks[rook_end];
- }
-
- /* Can't castle once king has moved */
- if (piece.type == PieceType.KING)
- {
- can_castle_kingside[color] = false;
- can_castle_queenside[color] = false;
- }
- /* Can't castle once rooks have moved */
- else if (piece.type == PieceType.ROOK)
- {
- int base_rank = color == Color.WHITE ? 0 : 7;
- if (r0 == base_rank)
- {
- if (f0 == 0)
- can_castle_queenside[color] = false;
- else if (f0 == 7)
- can_castle_kingside[color] = false;
- }
- }
- /* Can't castle once the rooks have been captured */
- else if (victim != null && victim.type == PieceType.ROOK)
- {
- int base_rank = opponent_color == Color.WHITE ? 0 : 7;
- if (r1 == base_rank)
- {
- if (f1 == 0)
- can_castle_queenside[opponent_color] = false;
- else if (f1 == 7)
- can_castle_kingside[opponent_color] = false;
- }
- }
-
- /* Pawn square moved over is vulnerable */
- if (piece.type == PieceType.PAWN && over_mask != 0)
- en_passant_index = get_index ((r0 + r1) / 2, f0);
- else
- en_passant_index = -1;
-
- /* Reset halfmove count when pawn moved or piece taken */
- if (piece.type == PieceType.PAWN || victim != null)
- halfmove_clock = 0;
- else
- halfmove_clock++;
-
- /* Test if this move would leave that player in check */
- bool result = true;
- if (test_check && is_in_check (player))
- result = false;
-
- /* Undo move */
- if (!apply || !result)
- {
- board[start] = piece;
- board[end] = null;
- if (victim != null)
- board[victim_index] = victim;
- if (rook_start >= 0)
- {
- var rook = board[rook_end];
- board[rook_start] = rook;
- board[rook_end] = null;
- }
- piece_masks[Color.WHITE] = old_white_mask;
- piece_masks[Color.BLACK] = old_black_mask;
- can_castle_kingside[Color.WHITE] = old_white_can_castle_kingside;
- can_castle_queenside[Color.WHITE] = old_white_can_castle_queenside;
- can_castle_kingside[Color.BLACK] = old_black_can_castle_kingside;
- can_castle_queenside[Color.BLACK] = old_black_can_castle_queenside;
- en_passant_index = old_en_passant_index;
- halfmove_clock = old_halfmove_clock;
-
- return result;
- }
-
- 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;
- last_move.piece = piece;
- if (is_promotion)
- last_move.promotion_piece = board[end];
- last_move.victim = victim;
- if (rook_end >= 0)
- last_move.moved_rook = board[rook_end];
- last_move.r0 = r0;
- last_move.f0 = f0;
- last_move.r1 = r1;
- last_move.f1 = f1;
- last_move.ambiguous_rank = ambiguous_rank;
- last_move.ambiguous_file = ambiguous_file;
- last_move.check_state = check_state;
-
- return true;
- }
-
- public ChessResult get_result (out ChessRule rule)
- {
- rule = ChessRule.CHECKMATE;
- 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;
- }
- }
-
- if (!can_move (current_player))
- {
- rule = ChessRule.STALEMATE;
- return ChessResult.DRAW;
- }
-
- if (last_move != null && 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))
- {
- if (is_in_checkmate (player))
- return CheckState.CHECKMATE;
- else
- return CheckState.CHECK;
- }
- return CheckState.NONE;
- }
-
- public bool is_in_check (ChessPlayer player)
- {
- var opponent = player.color == Color.WHITE ? players[Color.BLACK] : players[Color.WHITE];
-
- /* Is in check if any piece can take the king */
- for (int king_index = 0; king_index < 64; king_index++)
- {
- var p = board[king_index];
- if (p != null && p.player == player && p.type == PieceType.KING)
- {
- /* See if any enemy pieces can take the king */
- for (int start = 0; start < 64; start++)
- {
- if (move_with_coords (opponent,
- get_rank (start), get_file (start),
- get_rank (king_index), get_file (king_index),
- PieceType.QUEEN, false, false))
- return true;
- }
- }
- }
-
- return false;
- }
-
- private bool is_in_checkmate (ChessPlayer player)
- {
- /* Is in checkmate if no pieces can move */
- for (int piece_index = 0; piece_index < 64; piece_index++)
- {
- var p = board[piece_index];
- if (p != null && p.player == player)
- {
- for (int end = 0; end < 64; end++)
- {
- if (move_with_coords (player,
- get_rank (piece_index), get_file (piece_index),
- get_rank (end), get_file (end),
- PieceType.QUEEN, false, true))
- return false;
- }
- }
- }
-
- return true;
- }
-
- private bool can_move (ChessPlayer player)
- {
- bool have_pieces = false;
-
- for (int start = 0; start < 64; start++)
- {
- var p = board[start];
- if (p != null && p.player == player)
- {
- have_pieces = true;
-
- /* See if can move anywhere */
- for (int end = 0; end < 64; end++)
- {
- if (move_with_coords (player,
- get_rank (start), get_file (start),
- get_rank (end), get_file (end),
- PieceType.QUEEN, false, true))
- return true;
- }
- }
- }
-
- /* Only mark as stalemate if have at least one piece */
- if (have_pieces)
- return false;
- else
- 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.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.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++;
- }
- }
-
- /*
- * We count the following positions as insufficient:
- *
- * 1) king versus king
- * 2) king and bishop versus king
- * 3) king and knight versus king
- * 4) king and bishop versus king and bishop with the bishops on the same color. (Any
- * number of additional bishops of either color on the same color of square due to
- * underpromotion do not affect the situation.)
- *
- * From: https://en.wikipedia.org/wiki/Draw_(chess)#Draws_in_all_games
- *
- * Note also that this follows FIDE rules, not USCF rules. E.g. K+N+N vs. K cannot be
- * forced, so it's not counted as a draw.
- *
- * This is also what CECP engines will be expecting:
- *
- * "Note that (in accordance with FIDE rules) only KK, KNK, KBK and KBKB with all
- * bishops on the same color can be claimed as draws on the basis of insufficient mating
- * material. The end-games KNNK, KBKN, KNKN and KBKB with unlike bishops do have mate
- * positions, and cannot be claimed. Complex draws based on locked Pawn chains will not
- * be recognized as draws by most interfaces, so do not claim in such positions, but
- * just offer a draw or play on."
- *
- * From: http://www.open-aurec.com/wbforum/WinBoard/engine-intf.html
- *
- * (In contrast, UCI seems to expect the interface to handle draws itself.)
- */
-
- /* Two knights versus king can checkmate (though not against an optimal opponent) */
- if (white_knight_count > 1 || black_knight_count > 1)
- 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;
-
- /* King and minor piece vs. King and knight is surprisingly not a draw */
- if ((white_bishop_count > 0 || white_knight_count > 0) && black_knight_count > 0)
- return true;
- if ((black_bishop_count > 0 || black_knight_count > 0) && white_knight_count > 0)
- return true;
-
- /* King and bishop can checkmate vs. king and bishop if bishops are on opposite colors */
- if (white_bishop_count > 0 && black_bishop_count > 0)
- {
- if (white_bishop_on_white_square && black_bishop_on_black_square)
- return true;
- else if (white_bishop_on_black_square && black_bishop_on_white_square)
- return true;
- }
- }
-
- return false;
- }
-
- private bool decode_piece_type (unichar c, out PieceType type)
- {
- type = PieceType.PAWN;
- switch (c)
- {
- case 'P':
- type = PieceType.PAWN;
- return true;
- case 'R':
- type = PieceType.ROOK;
- return true;
- case 'N':
- type = PieceType.KNIGHT;
- return true;
- case 'B':
- type = PieceType.BISHOP;
- return true;
- case 'Q':
- type = PieceType.QUEEN;
- return true;
- case 'K':
- type = PieceType.KING;
- return true;
- default:
- return false;
- }
- }
-
- private bool decode_move (ChessPlayer player, string move, out int r0, out int f0, out int r1, out int
f1, out PieceType promotion_type)
- {
- int i = 0;
-
- promotion_type = PieceType.QUEEN;
- if (move.has_prefix ("O-O-O"))
- {
- if (player.color == Color.WHITE)
- r0 = r1 = 0;
- else
- r0 = r1 = 7;
- f0 = 4;
- f1 = 2;
- i += (int) "O-O-O".length;
- }
- else if (move.has_prefix ("O-O"))
- {
- if (player.color == Color.WHITE)
- r0 = r1 = 0;
- else
- r0 = r1 = 7;
- f0 = 4;
- f1 = 6;
- i += (int) "O-O".length;
- }
- else
- {
- PieceType type = PieceType.PAWN;
- if (decode_piece_type (move[i], out type))
- i++;
-
- r0 = f0 = r1 = f1 = -1;
- if (move[i] >= 'a' && move[i] <= 'h')
- {
- f1 = (int) (move[i] - 'a');
- i++;
- }
- if (move[i] >= '1' && move[i] <= '8')
- {
- r1 = (int) (move[i] - '1');
- i++;
- }
- if (move[i] == 'x' || move[i] == '-')
- i++;
- if (move[i] >= 'a' && move[i] <= 'h')
- {
- f0 = f1;
- f1 = (int) (move[i] - 'a');
- i++;
- }
- if (move[i] >= '1' && move[i] <= '8')
- {
- r0 = r1;
- r1 = (int) (move[i] - '1');
- i++;
- }
- if (move[i] == '=')
- {
- i++;
- if (decode_piece_type (move[i], out promotion_type))
- i++;
- }
- else if (move[i] != '\0')
- {
- switch (move[i])
- {
- case 'q':
- case 'Q':
- promotion_type = PieceType.QUEEN;
- i++;
- break;
- case 'n':
- case 'N':
- promotion_type = PieceType.KNIGHT;
- i++;
- break;
- case 'r':
- case 'R':
- promotion_type = PieceType.ROOK;
- i++;
- break;
- case 'b':
- case 'B':
- promotion_type = PieceType.BISHOP;
- i++;
- break;
- }
- }
-
- /* Don't have a destination to move to */
- if (r1 < 0 || f1 < 0)
- {
- debug ("Move %s missing destination", move);
- return false;
- }
-
- /* Find source piece */
- if (r0 < 0 || f0 < 0)
- {
- int match_rank = -1, match_file = -1;
-
- for (int file = 0; file < 8; file++)
- {
- if (f0 >= 0 && file != f0)
- continue;
-
- for (int rank = 0; rank < 8; rank++)
- {
- if (r0 >= 0 && rank != r0)
- continue;
-
- /* Only check this players pieces of the correct type */
- var piece = board[get_index (rank, file)];
- if (piece == null || piece.type != type || piece.player != player)
- continue;
-
- /* See if can move here */
- if (!this.move_with_coords (player, rank, file, r1, f1, PieceType.QUEEN, false))
- continue;
-
- /* Duplicate match */
- if (match_rank >= 0)
- {
- debug ("Move %s is ambiguous", move);
- return false;
- }
-
- match_rank = rank;
- match_file = file;
- }
- }
-
- if (match_rank < 0)
- {
- debug ("Move %s has no matches", move);
- return false;
- }
-
- r0 = match_rank;
- f0 = match_file;
- }
- }
-
- if (move[i] == '+')
- i++;
- else if (move[i] == '#')
- i++;
-
- if (move[i] != '\0')
- {
- debug ("Move %s has unexpected characters", move);
- return false;
- }
-
- return true;
- }
-}
-
public enum ChessResult
{
IN_PROGRESS,
diff --git a/src/chess-move.vala b/src/chess-move.vala
new file mode 100644
index 0000000..c4efa4a
--- /dev/null
+++ b/src/chess-move.vala
@@ -0,0 +1,141 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class ChessMove
+{
+ public int number;
+ public ChessPiece piece;
+ public ChessPiece? promotion_piece;
+ public ChessPiece? moved_rook;
+ public ChessPiece? victim;
+ public int r0;
+ public int f0;
+ public int r1;
+ public int f1;
+ public bool ambiguous_rank;
+ public bool ambiguous_file;
+ public CheckState check_state;
+
+ public string get_lan ()
+ {
+ if (moved_rook != null)
+ {
+ if (f1 > f0)
+ return "O-O";
+ else
+ 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)
+ {
+ case CheckState.CHECK:
+ builder.append_c ('+');
+ break;
+ case CheckState.CHECKMATE:
+ builder.append_c ('#');
+ break;
+ }
+
+ return builder.str;
+ }
+
+ public string get_san ()
+ {
+ const string piece_names[] = {"", "R", "N", "B", "Q", "K"};
+ return make_san ((string[]) piece_names);
+ }
+
+ public string get_fan ()
+ {
+ const string white_piece_names[] = {"", "♖", "♘", "♗", "♕", "♔"};
+ const string black_piece_names[] = {"", "♜", "♞", "♝", "♛", "♚"};
+ if (piece.color == Color.WHITE)
+ return make_san ((string[]) white_piece_names);
+ else
+ return make_san ((string[]) black_piece_names);
+ }
+
+ 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)
+ builder.append_printf ("%c", 'a' + f0);
+ if (ambiguous_rank)
+ builder.append_printf ("%d", r0 + 1);
+ 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;
+ }
+
+ /* Move suitable for a chess engine (CECP/UCI) */
+ public string get_engine ()
+ {
+ var builder = new StringBuilder ();
+ const char promotion_symbols[] = {' ', 'r', 'n', 'b', 'q', ' '};
+ if (promotion_piece != null)
+ builder.append_printf ("%c%d%c%d%c", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1,
promotion_symbols[promotion_piece.type]);
+ else
+ builder.append_printf ("%c%d%c%d", 'a' + f0, r0 + 1, 'a' + f1, r1 + 1);
+ return builder.str;
+ }
+
+ public ChessMove copy ()
+ {
+ var move = new ChessMove ();
+ move.number = number;
+ move.piece = piece;
+ move.promotion_piece = promotion_piece;
+ move.moved_rook = moved_rook;
+ move.victim = victim;
+ move.r0 = r0;
+ move.f0 = f0;
+ move.r1 = r1;
+ move.f1 = f1;
+ move.ambiguous_rank = ambiguous_rank;
+ move.ambiguous_file = ambiguous_file;
+ move.check_state = check_state;
+ return move;
+ }
+}
diff --git a/src/chess-piece.vala b/src/chess-piece.vala
new file mode 100644
index 0000000..7a63844
--- /dev/null
+++ b/src/chess-piece.vala
@@ -0,0 +1,73 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum PieceType
+{
+ PAWN,
+ ROOK,
+ KNIGHT,
+ BISHOP,
+ QUEEN,
+ KING
+}
+
+public class ChessPiece
+{
+ public ChessPlayer player;
+ public PieceType type;
+
+ public signal void moved ();
+ public signal void promoted ();
+ public signal void died ();
+
+ public Color color
+ {
+ get { return player.color; }
+ }
+
+ public unichar symbol
+ {
+ get
+ {
+ unichar c = ' ';
+ switch (type)
+ {
+ case PieceType.PAWN:
+ c = 'p';
+ break;
+ case PieceType.ROOK:
+ c = 'r';
+ break;
+ case PieceType.KNIGHT:
+ c = 'n';
+ break;
+ case PieceType.BISHOP:
+ c = 'b';
+ break;
+ case PieceType.QUEEN:
+ c = 'q';
+ break;
+ case PieceType.KING:
+ c = 'k';
+ break;
+ }
+ if (player.color == Color.WHITE)
+ c = c.toupper ();
+ return c;
+ }
+ }
+
+ public ChessPiece (ChessPlayer player, PieceType type)
+ {
+ this.player = player;
+ this.type = type;
+ }
+}
diff --git a/src/chess-player.vala b/src/chess-player.vala
new file mode 100644
index 0000000..e9966c1
--- /dev/null
+++ b/src/chess-player.vala
@@ -0,0 +1,87 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum Color
+{
+ WHITE,
+ BLACK
+}
+
+public class ChessPlayer : Object
+{
+ public Color color;
+ public signal void start_turn ();
+ public signal bool do_move (string move, bool apply);
+ public signal void do_undo ();
+ public signal bool do_resign ();
+ public signal bool do_claim_draw ();
+
+ private bool _local_human = false;
+ public bool local_human
+ {
+ get { return _local_human; }
+ set
+ {
+ _local_human = value;
+ }
+ }
+
+ public ChessPlayer (Color color)
+ {
+ this.color = color;
+ }
+
+ public bool move (string move, bool apply = true)
+ {
+ return do_move (move, apply);
+ }
+
+ public bool move_with_coords (int r0, int f0, int r1, int f1,
+ bool apply = true, PieceType promotion_type = PieceType.QUEEN)
+ {
+ string move = "%c%d%c%d".printf ('a' + f0, r0 + 1, 'a' + f1, r1 + 1);
+
+ switch (promotion_type)
+ {
+ case PieceType.QUEEN:
+ /* Default is queen so don't add anything */
+ break;
+ case PieceType.KNIGHT:
+ move += "=N";
+ break;
+ case PieceType.ROOK:
+ move += "=R";
+ break;
+ case PieceType.BISHOP:
+ move += "=B";
+ break;
+ default:
+ break;
+ }
+
+ return do_move (move, apply);
+ }
+
+ public void undo ()
+ {
+ do_undo ();
+ }
+
+ public bool resign ()
+ {
+ return do_resign ();
+ }
+
+ public bool claim_draw ()
+ {
+ return do_claim_draw ();
+ }
+}
diff --git a/src/chess-state.vala b/src/chess-state.vala
new file mode 100644
index 0000000..0f01a12
--- /dev/null
+++ b/src/chess-state.vala
@@ -0,0 +1,972 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2010-2013 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public enum CheckState
+{
+ NONE,
+ CHECK,
+ CHECKMATE
+}
+
+public class ChessState
+{
+ public int number = 0;
+ public ChessPlayer players[2];
+ public ChessPlayer current_player;
+ public ChessPlayer opponent
+ {
+ get { return current_player.color == Color.WHITE ? players[Color.BLACK] : players[Color.WHITE]; }
+ }
+ public bool can_castle_kingside[2];
+ public bool can_castle_queenside[2];
+ public int en_passant_index = -1;
+ public CheckState check_state;
+ public int halfmove_clock;
+
+ public ChessPiece board[64];
+ public ChessMove? last_move = null;
+
+ /* Bitmap of all the pieces */
+ private int64 piece_masks[2];
+
+ private ChessState.empty ()
+ {
+ }
+
+ public ChessState (string fen)
+ {
+ players[Color.WHITE] = new ChessPlayer (Color.WHITE);
+ players[Color.BLACK] = new ChessPlayer (Color.BLACK);
+ for (int i = 0; i < 64; i++)
+ board[i] = null;
+
+ string[] fields = fen.split (" ");
+ //if (fields.length != 6)
+ // throw new Error ("Invalid FEN string");
+
+ /* Field 1: Piece placement */
+ string[] ranks = fields[0].split ("/");
+ //if (ranks.length != 8)
+ // throw new Error ("Invalid piece placement");
+ for (int rank = 0; rank < 8; rank++)
+ {
+ var rank_string = ranks[7 - rank];
+ for (int file = 0, offset = 0; file < 8 && offset < rank_string.length; offset++)
+ {
+ var c = rank_string[offset];
+ if (c >= '1' && c <= '8')
+ {
+ file += c - '0';
+ continue;
+ }
+
+ PieceType type;
+ var color = c.isupper () ? Color.WHITE : Color.BLACK;
+ if (!decode_piece_type (c.toupper (), out type))
+ ;//throw new Error ("");
+
+ int index = get_index (rank, file);
+ ChessPiece piece = new ChessPiece (players[color], type);
+ board[index] = piece;
+ int64 mask = BitBoard.set_location_masks[index];
+ piece_masks[color] |= mask;
+ file++;
+ }
+ }
+
+ /* Field 2: Active color */
+ if (fields[1] == "w")
+ current_player = players[Color.WHITE];
+ else if (fields[1] == "b")
+ current_player = players[Color.BLACK];
+ //else
+ // throw new Error ("Unknown active color: %s", fields[1]);
+
+ /* Field 3: Castling availability */
+ if (fields[2] != "-")
+ {
+ for (int i = 0; i < fields[2].length; i++)
+ {
+ var c = fields[2][i];
+ if (c == 'K')
+ can_castle_kingside[Color.WHITE] = true;
+ else if (c == 'Q')
+ can_castle_queenside[Color.WHITE] = true;
+ else if (c == 'k')
+ can_castle_kingside[Color.BLACK] = true;
+ else if (c == 'q')
+ can_castle_queenside[Color.BLACK] = true;
+ //else
+ // throw new Error ("");
+ }
+ }
+
+ /* Field 4: En passant target square */
+ if (fields[3] != "-")
+ {
+ //if (fields[3].length != 2)
+ // throw new Error ("");
+ en_passant_index = get_index (fields[3][1] - '1', fields[3][0] - 'a');
+ }
+
+ /* Field 5: Halfmove clock */
+ halfmove_clock = int.parse (fields[4]);
+
+ /* Field 6: Fullmove number */
+ number = (int.parse (fields[5]) - 1) * 2;
+ if (current_player.color == Color.BLACK)
+ number++;
+
+ check_state = get_check_state (current_player);
+ }
+
+ public ChessState copy ()
+ {
+ ChessState state = new ChessState.empty ();
+
+ state.number = number;
+ state.players[Color.WHITE] = players[Color.WHITE];
+ state.players[Color.BLACK] = players[Color.BLACK];
+ state.current_player = current_player;
+ state.can_castle_kingside[Color.WHITE] = can_castle_kingside[Color.WHITE];
+ state.can_castle_queenside[Color.WHITE] = can_castle_queenside[Color.WHITE];
+ state.can_castle_kingside[Color.BLACK] = can_castle_kingside[Color.BLACK];
+ state.can_castle_queenside[Color.BLACK] = can_castle_queenside[Color.BLACK];
+ state.en_passant_index = en_passant_index;
+ state.check_state = check_state;
+ if (last_move != null)
+ state.last_move = last_move.copy();
+ for (int i = 0; i < 64; i++)
+ state.board[i] = board[i];
+ state.piece_masks[Color.WHITE] = piece_masks[Color.WHITE];
+ state.piece_masks[Color.BLACK] = piece_masks[Color.BLACK];
+ state.halfmove_clock = halfmove_clock;
+
+ return state;
+ }
+
+ public bool equals (ChessState state)
+ {
+ /*
+ * Check first if there is the same layout of pieces (unlikely),
+ * then that the same player is on move, then that the move castling
+ * and en-passant state are the same. This follows the rules for
+ * determining threefold repetition:
+ *
+ * https://en.wikipedia.org/wiki/Threefold_repetition
+ */
+ if (piece_masks[Color.WHITE] != state.piece_masks[Color.WHITE] ||
+ piece_masks[Color.BLACK] != state.piece_masks[Color.BLACK] ||
+ current_player.color != state.current_player.color ||
+ can_castle_kingside[Color.WHITE] != state.can_castle_kingside[Color.WHITE] ||
+ can_castle_queenside[Color.WHITE] != state.can_castle_queenside[Color.WHITE] ||
+ can_castle_kingside[Color.BLACK] != state.can_castle_kingside[Color.BLACK] ||
+ can_castle_queenside[Color.BLACK] != state.can_castle_queenside[Color.BLACK] ||
+ en_passant_index != state.en_passant_index)
+ return false;
+
+ /* Finally check the same piece types are present */
+ for (int i = 0; i < 64; i++)
+ {
+ if (board[i] != null && board[i].type != state.board[i].type)
+ return false;
+ }
+
+ return true;
+ }
+
+ public string get_fen ()
+ {
+ var value = new StringBuilder ();
+
+ for (int rank = 7; rank >= 0; rank--)
+ {
+ int skip_count = 0;
+ for (int file = 0; file < 8; file++)
+ {
+ var p = board[get_index (rank, file)];
+ if (p == null)
+ skip_count++;
+ else
+ {
+ if (skip_count > 0)
+ {
+ value.append_printf ("%d", skip_count);
+ skip_count = 0;
+ }
+ value.append_printf ("%c", (int) p.symbol);
+ }
+ }
+ if (skip_count > 0)
+ value.append_printf ("%d", skip_count);
+ if (rank != 0)
+ value.append_c ('/');
+ }
+
+ value.append_c (' ');
+ if (current_player.color == Color.WHITE)
+ value.append_c ('w');
+ else
+ value.append_c ('b');
+
+ value.append_c (' ');
+ if (can_castle_kingside[Color.WHITE])
+ value.append_c ('K');
+ if (can_castle_queenside[Color.WHITE])
+ value.append_c ('Q');
+ if (can_castle_kingside[Color.BLACK])
+ value.append_c ('k');
+ if (can_castle_queenside[Color.BLACK])
+ value.append_c ('q');
+ if (!(can_castle_kingside[Color.WHITE] | can_castle_queenside[Color.WHITE] |
can_castle_kingside[Color.BLACK] | can_castle_queenside[Color.BLACK]))
+ value.append_c ('-');
+
+ value.append_c (' ');
+ if (en_passant_index >= 0)
+ value.append_printf ("%c%d", 'a' + get_file (en_passant_index), get_rank (en_passant_index) + 1);
+ else
+ value.append_c ('-');
+
+ value.append_c (' ');
+ value.append_printf ("%d", halfmove_clock);
+
+ value.append_c (' ');
+ if (current_player.color == Color.WHITE)
+ value.append_printf ("%d", number / 2);
+ else
+ value.append_printf ("%d", number / 2 + 1);
+
+ return value.str;
+ }
+
+ public int get_index (int rank, int file)
+ {
+ return rank * 8 + file;
+ }
+
+ public int get_rank (int index)
+ {
+ return index / 8;
+ }
+
+ public int get_file (int index)
+ {
+ return index % 8;
+ }
+
+ public bool move (string move, bool apply = true)
+ {
+ int r0, f0, r1, f1;
+ PieceType promotion_type;
+
+ if (!decode_move (current_player, move, out r0, out f0, out r1, out f1, out promotion_type))
+ return false;
+
+ if (!move_with_coords (current_player, r0, f0, r1, f1, promotion_type, apply))
+ return false;
+
+ return true;
+ }
+
+ public bool move_with_coords (ChessPlayer player,
+ int r0, int f0, int r1, int f1,
+ PieceType promotion_type = PieceType.QUEEN,
+ bool apply = true, bool test_check = true)
+ {
+ // FIXME: Make this use indexes to be faster
+ var start = get_index (r0, f0);
+ var end = get_index (r1, f1);
+
+ var color = player.color;
+ var opponent_color = color == Color.WHITE ? Color.BLACK : Color.WHITE;
+
+ /* Must be moving own piece */
+ var piece = board[start];
+ if (piece == null || piece.player != player)
+ return false;
+
+ /* Check valid move */
+ int64 end_mask = BitBoard.set_location_masks[end];
+ int64 move_mask = BitBoard.move_masks[color * 64*6 + piece.type * 64 + start];
+ if ((end_mask & move_mask) == 0)
+ return false;
+
+ /* Check no pieces in the way */
+ int64 over_mask = BitBoard.over_masks[start * 64 + end];
+ if ((over_mask & (piece_masks[Color.WHITE] | piece_masks[Color.BLACK])) != 0)
+ return false;
+
+ /* Get victim of move */
+ var victim = board[end];
+ var victim_index = end;
+
+ /* Can't take own pieces */
+ if (victim != null && victim.player == player)
+ return false;
+
+ /* Check special moves */
+ int rook_start = -1, rook_end = -1;
+ bool is_promotion = false;
+ bool ambiguous_rank = false;
+ bool ambiguous_file = false;
+ switch (piece.type)
+ {
+ case PieceType.PAWN:
+ /* Check if taking an marched pawn */
+ if (victim == null && end == en_passant_index)
+ {
+ victim_index = get_index (r1 == 2 ? 3 : 4, f1);
+ victim = board[victim_index];
+ }
+
+ /* If moving diagonally there must be a victim */
+ if (f0 != f1)
+ {
+ if (victim == null)
+ return false;
+ }
+ else
+ {
+ /* If moving forward can't take enemy */
+ if (victim != null)
+ return false;
+ }
+ is_promotion = r1 == 0 || r1 == 7;
+
+ /* Always show the file of a pawn capturing */
+ if (victim != null)
+ ambiguous_file = true;
+ break;
+ case PieceType.KING:
+ /* If moving more than one square must be castling */
+ if ((f0 - f1).abs () > 1)
+ {
+ /* File the rook is on */
+ rook_start = get_index (r0, f1 > f0 ? 7 : 0);
+ rook_end = get_index (r0, f1 > f0 ? f1 - 1 : f1 + 1);
+
+ /* Check if can castle */
+ if (f1 > f0)
+ {
+ if (!can_castle_kingside[color])
+ return false;
+ }
+ else
+ {
+ if (!can_castle_queenside[color])
+ return false;
+ }
+
+ var rook = board[rook_start];
+ if (rook == null || rook.type != PieceType.ROOK || rook.color != color)
+ return false;
+
+ /* Check rook can move */
+ int64 rook_over_mask = BitBoard.over_masks[rook_start * 64 + rook_end];
+ if ((rook_over_mask & (piece_masks[Color.WHITE] | piece_masks[Color.BLACK])) != 0)
+ return false;
+
+ /* Can't castle when in check */
+ if (check_state == CheckState.CHECK)
+ return false;
+
+ /* Square moved across can't be under attack */
+ if (!move_with_coords (player, r0, f0, get_rank (rook_end), get_file (rook_end),
PieceType.QUEEN, false, true))
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!apply && !test_check)
+ return true;
+
+ /* Check if other pieces of the same type can make this move - this is required for SAN notation */
+ if (apply)
+ {
+ for (int i = 0; i < 64; i++)
+ {
+ /* Ignore our move */
+ if (i == start)
+ continue;
+
+ /* Check for a friendly piece of the same type */
+ var p = board[i];
+ if (p == null || p.player != player || p.type != piece.type)
+ continue;
+
+ /* If more than one piece can move then the rank and/or file are ambiguous */
+ var r = get_rank (i);
+ var f = get_file (i);
+ if (move_with_coords (player, r, f, r1, f1, PieceType.QUEEN, false))
+ {
+ if (r != r0)
+ ambiguous_rank = true;
+ if (f != f0)
+ ambiguous_file = true;
+ }
+ }
+ }
+
+ var old_white_mask = piece_masks[Color.WHITE];
+ var old_black_mask = piece_masks[Color.BLACK];
+ var old_white_can_castle_kingside = can_castle_kingside[Color.WHITE];
+ var old_white_can_castle_queenside = can_castle_queenside[Color.WHITE];
+ var old_black_can_castle_kingside = can_castle_kingside[Color.BLACK];
+ var old_black_can_castle_queenside = can_castle_queenside[Color.BLACK];
+ var old_en_passant_index = en_passant_index;
+ var old_halfmove_clock = halfmove_clock;
+
+ /* Update board */
+ board[start] = null;
+ piece_masks[Color.WHITE] &= BitBoard.clear_location_masks[start];
+ piece_masks[Color.BLACK] &= BitBoard.clear_location_masks[start];
+ if (victim != null)
+ {
+ board[victim_index] = null;
+ piece_masks[Color.WHITE] &= BitBoard.clear_location_masks[victim_index];
+ piece_masks[Color.BLACK] &= BitBoard.clear_location_masks[victim_index];
+ }
+ if (is_promotion)
+ board[end] = new ChessPiece (player, promotion_type);
+ else
+ board[end] = piece;
+ piece_masks[color] |= end_mask;
+ piece_masks[opponent_color] &= BitBoard.clear_location_masks[end];
+ if (rook_start >= 0)
+ {
+ var rook = board[rook_start];
+ board[rook_start] = null;
+ piece_masks[color] &= BitBoard.clear_location_masks[rook_start];
+ board[rook_end] = rook;
+ piece_masks[color] |= BitBoard.set_location_masks[rook_end];
+ }
+
+ /* Can't castle once king has moved */
+ if (piece.type == PieceType.KING)
+ {
+ can_castle_kingside[color] = false;
+ can_castle_queenside[color] = false;
+ }
+ /* Can't castle once rooks have moved */
+ else if (piece.type == PieceType.ROOK)
+ {
+ int base_rank = color == Color.WHITE ? 0 : 7;
+ if (r0 == base_rank)
+ {
+ if (f0 == 0)
+ can_castle_queenside[color] = false;
+ else if (f0 == 7)
+ can_castle_kingside[color] = false;
+ }
+ }
+ /* Can't castle once the rooks have been captured */
+ else if (victim != null && victim.type == PieceType.ROOK)
+ {
+ int base_rank = opponent_color == Color.WHITE ? 0 : 7;
+ if (r1 == base_rank)
+ {
+ if (f1 == 0)
+ can_castle_queenside[opponent_color] = false;
+ else if (f1 == 7)
+ can_castle_kingside[opponent_color] = false;
+ }
+ }
+
+ /* Pawn square moved over is vulnerable */
+ if (piece.type == PieceType.PAWN && over_mask != 0)
+ en_passant_index = get_index ((r0 + r1) / 2, f0);
+ else
+ en_passant_index = -1;
+
+ /* Reset halfmove count when pawn moved or piece taken */
+ if (piece.type == PieceType.PAWN || victim != null)
+ halfmove_clock = 0;
+ else
+ halfmove_clock++;
+
+ /* Test if this move would leave that player in check */
+ bool result = true;
+ if (test_check && is_in_check (player))
+ result = false;
+
+ /* Undo move */
+ if (!apply || !result)
+ {
+ board[start] = piece;
+ board[end] = null;
+ if (victim != null)
+ board[victim_index] = victim;
+ if (rook_start >= 0)
+ {
+ var rook = board[rook_end];
+ board[rook_start] = rook;
+ board[rook_end] = null;
+ }
+ piece_masks[Color.WHITE] = old_white_mask;
+ piece_masks[Color.BLACK] = old_black_mask;
+ can_castle_kingside[Color.WHITE] = old_white_can_castle_kingside;
+ can_castle_queenside[Color.WHITE] = old_white_can_castle_queenside;
+ can_castle_kingside[Color.BLACK] = old_black_can_castle_kingside;
+ can_castle_queenside[Color.BLACK] = old_black_can_castle_queenside;
+ en_passant_index = old_en_passant_index;
+ halfmove_clock = old_halfmove_clock;
+
+ return result;
+ }
+
+ 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;
+ last_move.piece = piece;
+ if (is_promotion)
+ last_move.promotion_piece = board[end];
+ last_move.victim = victim;
+ if (rook_end >= 0)
+ last_move.moved_rook = board[rook_end];
+ last_move.r0 = r0;
+ last_move.f0 = f0;
+ last_move.r1 = r1;
+ last_move.f1 = f1;
+ last_move.ambiguous_rank = ambiguous_rank;
+ last_move.ambiguous_file = ambiguous_file;
+ last_move.check_state = check_state;
+
+ return true;
+ }
+
+ public ChessResult get_result (out ChessRule rule)
+ {
+ rule = ChessRule.CHECKMATE;
+ 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;
+ }
+ }
+
+ if (!can_move (current_player))
+ {
+ rule = ChessRule.STALEMATE;
+ return ChessResult.DRAW;
+ }
+
+ if (last_move != null && 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))
+ {
+ if (is_in_checkmate (player))
+ return CheckState.CHECKMATE;
+ else
+ return CheckState.CHECK;
+ }
+ return CheckState.NONE;
+ }
+
+ public bool is_in_check (ChessPlayer player)
+ {
+ var opponent = player.color == Color.WHITE ? players[Color.BLACK] : players[Color.WHITE];
+
+ /* Is in check if any piece can take the king */
+ for (int king_index = 0; king_index < 64; king_index++)
+ {
+ var p = board[king_index];
+ if (p != null && p.player == player && p.type == PieceType.KING)
+ {
+ /* See if any enemy pieces can take the king */
+ for (int start = 0; start < 64; start++)
+ {
+ if (move_with_coords (opponent,
+ get_rank (start), get_file (start),
+ get_rank (king_index), get_file (king_index),
+ PieceType.QUEEN, false, false))
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private bool is_in_checkmate (ChessPlayer player)
+ {
+ /* Is in checkmate if no pieces can move */
+ for (int piece_index = 0; piece_index < 64; piece_index++)
+ {
+ var p = board[piece_index];
+ if (p != null && p.player == player)
+ {
+ for (int end = 0; end < 64; end++)
+ {
+ if (move_with_coords (player,
+ get_rank (piece_index), get_file (piece_index),
+ get_rank (end), get_file (end),
+ PieceType.QUEEN, false, true))
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private bool can_move (ChessPlayer player)
+ {
+ bool have_pieces = false;
+
+ for (int start = 0; start < 64; start++)
+ {
+ var p = board[start];
+ if (p != null && p.player == player)
+ {
+ have_pieces = true;
+
+ /* See if can move anywhere */
+ for (int end = 0; end < 64; end++)
+ {
+ if (move_with_coords (player,
+ get_rank (start), get_file (start),
+ get_rank (end), get_file (end),
+ PieceType.QUEEN, false, true))
+ return true;
+ }
+ }
+ }
+
+ /* Only mark as stalemate if have at least one piece */
+ if (have_pieces)
+ return false;
+ else
+ 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.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.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++;
+ }
+ }
+
+ /*
+ * We count the following positions as insufficient:
+ *
+ * 1) king versus king
+ * 2) king and bishop versus king
+ * 3) king and knight versus king
+ * 4) king and bishop versus king and bishop with the bishops on the same color. (Any
+ * number of additional bishops of either color on the same color of square due to
+ * underpromotion do not affect the situation.)
+ *
+ * From: https://en.wikipedia.org/wiki/Draw_(chess)#Draws_in_all_games
+ *
+ * Note also that this follows FIDE rules, not USCF rules. E.g. K+N+N vs. K cannot be
+ * forced, so it's not counted as a draw.
+ *
+ * This is also what CECP engines will be expecting:
+ *
+ * "Note that (in accordance with FIDE rules) only KK, KNK, KBK and KBKB with all
+ * bishops on the same color can be claimed as draws on the basis of insufficient mating
+ * material. The end-games KNNK, KBKN, KNKN and KBKB with unlike bishops do have mate
+ * positions, and cannot be claimed. Complex draws based on locked Pawn chains will not
+ * be recognized as draws by most interfaces, so do not claim in such positions, but
+ * just offer a draw or play on."
+ *
+ * From: http://www.open-aurec.com/wbforum/WinBoard/engine-intf.html
+ *
+ * (In contrast, UCI seems to expect the interface to handle draws itself.)
+ */
+
+ /* Two knights versus king can checkmate (though not against an optimal opponent) */
+ if (white_knight_count > 1 || black_knight_count > 1)
+ 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;
+
+ /* King and minor piece vs. King and knight is surprisingly not a draw */
+ if ((white_bishop_count > 0 || white_knight_count > 0) && black_knight_count > 0)
+ return true;
+ if ((black_bishop_count > 0 || black_knight_count > 0) && white_knight_count > 0)
+ return true;
+
+ /* King and bishop can checkmate vs. king and bishop if bishops are on opposite colors */
+ if (white_bishop_count > 0 && black_bishop_count > 0)
+ {
+ if (white_bishop_on_white_square && black_bishop_on_black_square)
+ return true;
+ else if (white_bishop_on_black_square && black_bishop_on_white_square)
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool decode_piece_type (unichar c, out PieceType type)
+ {
+ type = PieceType.PAWN;
+ switch (c)
+ {
+ case 'P':
+ type = PieceType.PAWN;
+ return true;
+ case 'R':
+ type = PieceType.ROOK;
+ return true;
+ case 'N':
+ type = PieceType.KNIGHT;
+ return true;
+ case 'B':
+ type = PieceType.BISHOP;
+ return true;
+ case 'Q':
+ type = PieceType.QUEEN;
+ return true;
+ case 'K':
+ type = PieceType.KING;
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private bool decode_move (ChessPlayer player, string move, out int r0, out int f0, out int r1, out int
f1, out PieceType promotion_type)
+ {
+ int i = 0;
+
+ promotion_type = PieceType.QUEEN;
+ if (move.has_prefix ("O-O-O"))
+ {
+ if (player.color == Color.WHITE)
+ r0 = r1 = 0;
+ else
+ r0 = r1 = 7;
+ f0 = 4;
+ f1 = 2;
+ i += (int) "O-O-O".length;
+ }
+ else if (move.has_prefix ("O-O"))
+ {
+ if (player.color == Color.WHITE)
+ r0 = r1 = 0;
+ else
+ r0 = r1 = 7;
+ f0 = 4;
+ f1 = 6;
+ i += (int) "O-O".length;
+ }
+ else
+ {
+ PieceType type = PieceType.PAWN;
+ if (decode_piece_type (move[i], out type))
+ i++;
+
+ r0 = f0 = r1 = f1 = -1;
+ if (move[i] >= 'a' && move[i] <= 'h')
+ {
+ f1 = (int) (move[i] - 'a');
+ i++;
+ }
+ if (move[i] >= '1' && move[i] <= '8')
+ {
+ r1 = (int) (move[i] - '1');
+ i++;
+ }
+ if (move[i] == 'x' || move[i] == '-')
+ i++;
+ if (move[i] >= 'a' && move[i] <= 'h')
+ {
+ f0 = f1;
+ f1 = (int) (move[i] - 'a');
+ i++;
+ }
+ if (move[i] >= '1' && move[i] <= '8')
+ {
+ r0 = r1;
+ r1 = (int) (move[i] - '1');
+ i++;
+ }
+ if (move[i] == '=')
+ {
+ i++;
+ if (decode_piece_type (move[i], out promotion_type))
+ i++;
+ }
+ else if (move[i] != '\0')
+ {
+ switch (move[i])
+ {
+ case 'q':
+ case 'Q':
+ promotion_type = PieceType.QUEEN;
+ i++;
+ break;
+ case 'n':
+ case 'N':
+ promotion_type = PieceType.KNIGHT;
+ i++;
+ break;
+ case 'r':
+ case 'R':
+ promotion_type = PieceType.ROOK;
+ i++;
+ break;
+ case 'b':
+ case 'B':
+ promotion_type = PieceType.BISHOP;
+ i++;
+ break;
+ }
+ }
+
+ /* Don't have a destination to move to */
+ if (r1 < 0 || f1 < 0)
+ {
+ debug ("Move %s missing destination", move);
+ return false;
+ }
+
+ /* Find source piece */
+ if (r0 < 0 || f0 < 0)
+ {
+ int match_rank = -1, match_file = -1;
+
+ for (int file = 0; file < 8; file++)
+ {
+ if (f0 >= 0 && file != f0)
+ continue;
+
+ for (int rank = 0; rank < 8; rank++)
+ {
+ if (r0 >= 0 && rank != r0)
+ continue;
+
+ /* Only check this players pieces of the correct type */
+ var piece = board[get_index (rank, file)];
+ if (piece == null || piece.type != type || piece.player != player)
+ continue;
+
+ /* See if can move here */
+ if (!this.move_with_coords (player, rank, file, r1, f1, PieceType.QUEEN, false))
+ continue;
+
+ /* Duplicate match */
+ if (match_rank >= 0)
+ {
+ debug ("Move %s is ambiguous", move);
+ return false;
+ }
+
+ match_rank = rank;
+ match_file = file;
+ }
+ }
+
+ if (match_rank < 0)
+ {
+ debug ("Move %s has no matches", move);
+ return false;
+ }
+
+ r0 = match_rank;
+ f0 = match_file;
+ }
+ }
+
+ if (move[i] == '+')
+ i++;
+ else if (move[i] == '#')
+ i++;
+
+ if (move[i] != '\0')
+ {
+ debug ("Move %s has unexpected characters", move);
+ return false;
+ }
+
+ return true;
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]