gnome-chess/lib/chess-game.vala

408 lines
10 KiB
Vala
Raw Normal View History

2014-01-07 02:19:07 +00:00
/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
2016-03-15 17:12:58 +00:00
* Copyright (C) 2010-2014 Robert Ancell
* Copyright (C) 2015-2016 Sahil Sareen
2013-07-07 21:14:29 +00:00
*
* 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 3 of the License, or (at your option) any later
2013-07-07 21:14:29 +00:00
* version. See http://www.gnu.org/copyleft/gpl.html the full text of the
* license.
*/
public enum ChessResult
{
IN_PROGRESS,
WHITE_WON,
BLACK_WON,
DRAW,
BUG
}
public enum ChessRule
{
UNKNOWN,
CHECKMATE,
STALEMATE,
FIFTY_MOVES,
SEVENTY_FIVE_MOVES,
TIMEOUT,
THREE_FOLD_REPETITION,
FIVE_FOLD_REPETITION,
INSUFFICIENT_MATERIAL,
RESIGN,
ABANDONMENT,
DEATH,
BUG
}
2014-06-23 02:54:09 +00:00
public class ChessGame : Object
{
public bool is_started;
public ChessResult result;
public ChessRule rule;
2011-01-01 01:42:50 +00:00
public List<ChessState> move_stack;
2011-02-19 06:54:50 +00:00
private int hold_count = 0;
2011-01-18 22:45:59 +00:00
public const string STANDARD_SETUP = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
public signal void turn_started (ChessPlayer player);
public signal void moved (ChessMove move);
public signal void paused ();
public signal void unpaused ();
2011-01-25 11:44:15 +00:00
public signal void undo ();
public signal void ended ();
public bool is_paused { get; private set; default = false; }
public bool should_show_paused_overlay { get; private set; default = false; }
2011-02-19 06:54:50 +00:00
public ChessState current_state
{
get { return move_stack.data; }
}
public ChessPlayer white
{
2011-02-19 06:54:50 +00:00
get { return current_state.players[Color.WHITE]; }
}
public ChessPlayer black
{
2011-02-19 06:54:50 +00:00
get { return current_state.players[Color.BLACK]; }
}
public ChessPlayer current_player
{
2011-02-19 06:54:50 +00:00
get { return current_state.current_player; }
}
2011-01-25 11:44:15 +00:00
public ChessPlayer opponent
{
2011-02-19 06:54:50 +00:00
get { return current_state.opponent; }
2011-01-25 11:44:15 +00:00
}
2011-01-09 03:25:33 +00:00
private ChessClock? _clock;
public ChessClock? clock
{
get { return _clock; }
set
{
if (is_started)
return;
_clock = value;
}
}
public ChessGame (string fen = STANDARD_SETUP, string[]? moves = null) throws PGNError
{
is_started = false;
2010-12-28 04:38:09 +00:00
move_stack.prepend (new ChessState (fen));
result = ChessResult.IN_PROGRESS;
2011-01-18 22:45:59 +00:00
if (moves != null)
{
for (var i = 0; i < moves.length; i++)
{
if (!do_move (current_player, moves[i], true))
{
/* Message when the game cannot be loaded due to an invalid move in the file. */
throw new PGNError.LOAD_ERROR (_("Failed to load PGN: move %s is invalid."), moves[i]);
}
2011-01-18 22:45:59 +00:00
}
}
white.do_move.connect (move_cb);
2011-01-25 11:44:15 +00:00
white.do_undo.connect (undo_cb);
white.do_resign.connect (resign_cb);
white.do_claim_draw.connect (claim_draw_cb);
black.do_move.connect (move_cb);
2011-01-25 11:44:15 +00:00
black.do_undo.connect (undo_cb);
black.do_resign.connect (resign_cb);
black.do_claim_draw.connect (claim_draw_cb);
}
~ChessGame ()
{
if (_clock != null)
_clock.stop ();
}
private bool move_cb (ChessPlayer player, string move, bool apply)
{
2011-01-18 22:45:59 +00:00
if (!is_started)
return false;
2011-01-18 22:45:59 +00:00
return do_move (player, move, apply);
}
2011-01-18 22:45:59 +00:00
private bool do_move (ChessPlayer player, string? move, bool apply)
{
if (player != current_player)
return false;
2011-02-19 06:54:50 +00:00
var state = current_state.copy ();
state.number++;
2011-01-18 22:45:59 +00:00
if (!state.move (move, apply))
return false;
if (!apply)
return true;
move_stack.prepend (state);
if (state.last_move.victim != null)
state.last_move.victim.died ();
state.last_move.piece.moved ();
if (state.last_move.castling_rook != null)
state.last_move.castling_rook.moved ();
moved (state.last_move);
2011-02-19 06:54:50 +00:00
complete_move ();
2011-02-19 06:54:50 +00:00
return true;
}
public void add_hold ()
{
hold_count++;
}
public void remove_hold ()
{
return_if_fail (hold_count > 0);
hold_count--;
if (hold_count == 0)
complete_move ();
}
private void complete_move ()
{
/* Wait until the hold is removed */
if (hold_count > 0)
return;
if (!is_started)
return;
ChessRule rule;
2011-02-19 06:54:50 +00:00
var result = current_state.get_result (out rule);
if (result != ChessResult.IN_PROGRESS)
2011-01-25 05:15:08 +00:00
{
stop (result, rule);
return;
2011-01-25 05:15:08 +00:00
}
if (is_five_fold_repeat ())
2011-01-19 21:54:14 +00:00
{
stop (ChessResult.DRAW, ChessRule.FIVE_FOLD_REPETITION);
return;
2011-01-19 21:54:14 +00:00
}
/* Note this test must occur after the test for checkmate in current_state.get_result (). */
if (is_seventy_five_move_rule_fulfilled ())
{
stop (ChessResult.DRAW, ChessRule.SEVENTY_FIVE_MOVES);
return;
}
if (_clock != null)
_clock.active_color = current_player.color;
turn_started (current_player);
}
2011-01-25 11:44:15 +00:00
private void undo_cb (ChessPlayer player)
{
2011-01-25 12:10:00 +00:00
/* If this players turn undo their opponents move first */
2011-01-25 11:44:15 +00:00
if (player == current_player)
undo_cb (opponent);
/* Don't pop off starting move */
if (move_stack.next == null)
return;
/* Pop off the move state */
2011-01-25 11:44:15 +00:00
move_stack.remove_link (move_stack);
/* Restart the game if undo was done after end of the game */
if (result != ChessResult.IN_PROGRESS)
{
result = ChessResult.IN_PROGRESS;
start ();
}
/* Notify */
2011-01-25 11:44:15 +00:00
undo ();
}
private bool resign_cb (ChessPlayer player)
{
if (!is_started)
return false;
2010-12-27 01:05:47 +00:00
if (player.color == Color.WHITE)
stop (ChessResult.BLACK_WON, ChessRule.RESIGN);
else
stop (ChessResult.WHITE_WON, ChessRule.RESIGN);
return true;
}
private int state_repeated_times (ChessState s1)
{
var count = 1;
foreach (var s2 in move_stack)
{
if (s1 != s2 && s1.equals (s2))
count++;
}
return count;
}
public bool is_three_fold_repeat ()
{
var repeated = state_repeated_times (current_state);
return repeated == 3 || repeated == 4;
}
public bool is_five_fold_repeat ()
{
return state_repeated_times (current_state) >= 5;
}
public bool is_fifty_move_rule_fulfilled ()
{
/* Fifty moves *per player* without capture or pawn advancement */
return current_state.halfmove_clock >= 100 && current_state.halfmove_clock < 150;
}
public bool is_seventy_five_move_rule_fulfilled ()
{
/* 75 moves *per player* without capture or pawn advancement */
return current_state.halfmove_clock >= 150;
}
public bool can_claim_draw ()
{
return is_fifty_move_rule_fulfilled () || is_three_fold_repeat ();
}
private void claim_draw_cb ()
requires (can_claim_draw ())
{
if (is_fifty_move_rule_fulfilled ())
stop (ChessResult.DRAW, ChessRule.FIFTY_MOVES);
else if (is_three_fold_repeat ())
stop (ChessResult.DRAW, ChessRule.THREE_FOLD_REPETITION);
}
public void start ()
{
2011-01-18 22:45:59 +00:00
if (result != ChessResult.IN_PROGRESS)
return;
if (is_started)
return;
is_started = true;
2011-01-09 03:25:33 +00:00
if (_clock != null)
{
_clock.expired.connect (clock_expired_cb);
_clock.active_color = current_player.color;
}
2011-01-07 04:20:27 +00:00
turn_started (current_player);
}
2011-01-07 04:20:27 +00:00
private void clock_expired_cb (ChessClock clock)
{
if (clock.white_remaining_seconds <= 0)
2011-01-07 04:20:27 +00:00
stop (ChessResult.BLACK_WON, ChessRule.TIMEOUT);
else if (clock.black_remaining_seconds <= 0)
2011-01-07 04:20:27 +00:00
stop (ChessResult.WHITE_WON, ChessRule.TIMEOUT);
else
assert_not_reached ();
2011-01-07 04:20:27 +00:00
}
public ChessPiece? get_piece (int rank, int file, int move_number = -1)
{
if (move_number < 0)
move_number += (int) move_stack.length ();
var state = move_stack.nth_data (move_stack.length () - move_number - 1);
2010-12-24 22:05:44 +00:00
return state.board[state.get_index (rank, file)];
}
public uint n_moves
{
2015-02-19 15:19:36 +00:00
get { return move_stack.length () - 1; }
}
public void pause (bool show_overlay = true)
{
if (clock != null && result == ChessResult.IN_PROGRESS && !is_paused)
{
clock.pause ();
is_paused = true;
should_show_paused_overlay = show_overlay;
paused ();
}
}
public void unpause ()
{
if (clock != null && result == ChessResult.IN_PROGRESS && is_paused)
{
clock.unpause ();
is_paused = false;
should_show_paused_overlay = false;
unpaused ();
}
}
public void stop (ChessResult result, ChessRule rule)
{
if (!is_started)
return;
this.result = result;
this.rule = rule;
is_started = false;
2013-05-22 02:23:51 +00:00
if (_clock != null)
2015-02-19 15:19:36 +00:00
_clock.stop ();
ended ();
}
public bool is_king_under_attack_at_position (int rank, int file)
{
if (!current_state.is_in_check (current_player))
return false;
var piece = get_piece (rank, file);
if (piece == null || piece.type != PieceType.KING)
return false;
if (piece.player.color == Color.WHITE && current_player.color == Color.WHITE)
return true;
if (piece.player.color == Color.BLACK && current_player.color == Color.BLACK)
return true;
return false;
}
public bool is_piece_at_position_threatening_check (int rank, int file)
{
int[] threatening_rank, threatening_file;
if (current_state.get_positions_threatening_king (current_player, out threatening_rank, out threatening_file))
{
assert (threatening_rank.length == threatening_file.length);
for (int i = 0; i < threatening_rank.length; i++)
{
if (threatening_rank[i] == rank && threatening_file[i] == file)
return true;
}
}
return false;
}
}