class JustChess::GameState
Game State¶ ↑
Represents a game of Chess in progress.
Constants
- PROMOTABLE_PIECE_TYPES
They piece types that a pawn can promote to.
Attributes
Public Class Methods
Instantiates a new GameState
object in the starting position
@return [GameState]
# File lib/just_chess/game_state.rb, line 36 def self.default new( current_player_number: 1, squares: [ { id: 'a8', x: 0, y: 0, piece: { id: 1, player_number: 2, type: 'rook' } }, { id: 'b8', x: 1, y: 0, piece: { id: 2, player_number: 2, type: 'knight' } }, { id: 'c8', x: 2, y: 0, piece: { id: 3, player_number: 2, type: 'bishop' } }, { id: 'd8', x: 3, y: 0, piece: { id: 4, player_number: 2, type: 'queen' } }, { id: 'e8', x: 4, y: 0, piece: { id: 5, player_number: 2, type: 'king' } }, { id: 'f8', x: 5, y: 0, piece: { id: 6, player_number: 2, type: 'bishop' } }, { id: 'g8', x: 6, y: 0, piece: { id: 7, player_number: 2, type: 'knight' } }, { id: 'h8', x: 7, y: 0, piece: { id: 8, player_number: 2, type: 'rook' } }, { id: 'a7', x: 0, y: 1, piece: { id: 9, player_number: 2, type: 'pawn' } }, { id: 'b7', x: 1, y: 1, piece: { id: 10, player_number: 2, type: 'pawn' } }, { id: 'c7', x: 2, y: 1, piece: { id: 11, player_number: 2, type: 'pawn' } }, { id: 'd7', x: 3, y: 1, piece: { id: 12, player_number: 2, type: 'pawn' } }, { id: 'e7', x: 4, y: 1, piece: { id: 13, player_number: 2, type: 'pawn' } }, { id: 'f7', x: 5, y: 1, piece: { id: 14, player_number: 2, type: 'pawn' } }, { id: 'g7', x: 6, y: 1, piece: { id: 15, player_number: 2, type: 'pawn' } }, { id: 'h7', x: 7, y: 1, piece: { id: 16, player_number: 2, type: 'pawn' } }, { id: 'a6', x: 0, y: 2, piece: nil }, { id: 'b6', x: 1, y: 2, piece: nil }, { id: 'c6', x: 2, y: 2, piece: nil }, { id: 'd6', x: 3, y: 2, piece: nil }, { id: 'e6', x: 4, y: 2, piece: nil }, { id: 'f6', x: 5, y: 2, piece: nil }, { id: 'g6', x: 6, y: 2, piece: nil }, { id: 'h6', x: 7, y: 2, piece: nil }, { id: 'a5', x: 0, y: 3, piece: nil }, { id: 'b5', x: 1, y: 3, piece: nil }, { id: 'c5', x: 2, y: 3, piece: nil }, { id: 'd5', x: 3, y: 3, piece: nil }, { id: 'e5', x: 4, y: 3, piece: nil }, { id: 'f5', x: 5, y: 3, piece: nil }, { id: 'g5', x: 6, y: 3, piece: nil }, { id: 'h5', x: 7, y: 3, piece: nil }, { id: 'a4', x: 0, y: 4, piece: nil }, { id: 'b4', x: 1, y: 4, piece: nil }, { id: 'c4', x: 2, y: 4, piece: nil }, { id: 'd4', x: 3, y: 4, piece: nil }, { id: 'e4', x: 4, y: 4, piece: nil }, { id: 'f4', x: 5, y: 4, piece: nil }, { id: 'g4', x: 6, y: 4, piece: nil }, { id: 'h4', x: 7, y: 4, piece: nil }, { id: 'a3', x: 0, y: 5, piece: nil }, { id: 'b3', x: 1, y: 5, piece: nil }, { id: 'c3', x: 2, y: 5, piece: nil }, { id: 'd3', x: 3, y: 5, piece: nil }, { id: 'e3', x: 4, y: 5, piece: nil }, { id: 'f3', x: 5, y: 5, piece: nil }, { id: 'g3', x: 6, y: 5, piece: nil }, { id: 'h3', x: 7, y: 5, piece: nil }, { id: 'a2', x: 0, y: 6, piece: { id: 17, player_number: 1, type: 'pawn' } }, { id: 'b2', x: 1, y: 6, piece: { id: 18, player_number: 1, type: 'pawn' } }, { id: 'c2', x: 2, y: 6, piece: { id: 19, player_number: 1, type: 'pawn' } }, { id: 'd2', x: 3, y: 6, piece: { id: 20, player_number: 1, type: 'pawn' } }, { id: 'e2', x: 4, y: 6, piece: { id: 21, player_number: 1, type: 'pawn' } }, { id: 'f2', x: 5, y: 6, piece: { id: 22, player_number: 1, type: 'pawn' } }, { id: 'g2', x: 6, y: 6, piece: { id: 23, player_number: 1, type: 'pawn' } }, { id: 'h2', x: 7, y: 6, piece: { id: 24, player_number: 1, type: 'pawn' } }, { id: 'a1', x: 0, y: 7, piece: { id: 25, player_number: 1, type: 'rook' } }, { id: 'b1', x: 1, y: 7, piece: { id: 26, player_number: 1, type: 'knight' } }, { id: 'c1', x: 2, y: 7, piece: { id: 27, player_number: 1, type: 'bishop' } }, { id: 'd1', x: 3, y: 7, piece: { id: 28, player_number: 1, type: 'queen' } }, { id: 'e1', x: 4, y: 7, piece: { id: 29, player_number: 1, type: 'king' } }, { id: 'f1', x: 5, y: 7, piece: { id: 30, player_number: 1, type: 'bishop' } }, { id: 'g1', x: 6, y: 7, piece: { id: 31, player_number: 1, type: 'knight' } }, { id: 'h1', x: 7, y: 7, piece: { id: 32, player_number: 1, type: 'rook' } }, ] ) end
# File lib/just_chess/game_state.rb, line 19 def initialize(current_player_number: , squares: [], last_double_step_pawn_id: nil) @current_player_number = current_player_number @squares = if squares.is_a?(SquareSet) squares else SquareSet.new(squares: squares) end @last_double_step_pawn_id = last_double_step_pawn_id @last_change = {} @errors = [] end
Public Instance Methods
serializes the game state as a hash
@return [Hash]
# File lib/just_chess/game_state.rb, line 118 def as_json { current_player_number: current_player_number, squares: squares.as_json, last_double_step_pawn_id: last_double_step_pawn_id } end
deep clone of the game state
@return [GameState]
# File lib/just_chess/game_state.rb, line 129 def clone self.class.new(**as_json) end
# File lib/just_chess/game_state.rb, line 241 def in_check?(player_number) king_square = squares.find_king_for_player(player_number) threatened_by = squares.threatened_by(opposing_player_number(player_number), self) threatened_by.include?(king_square) end
# File lib/just_chess/game_state.rb, line 247 def in_checkmate?(player_number) (in_check?(player_number) || non_king_pieces_cannot_move?(player_number)) && king_cannot_move?(player_number) end
# File lib/just_chess/game_state.rb, line 255 def king_cannot_move?(player_number) king_square = squares.find_king_for_player(player_number) destinations = king_square.piece.destinations(king_square, self) destinations.all? do |destination| duplicate = self.clone duplicate.perform_complete_move(player_number, king_square.id, destination.id) duplicate.in_check?(player_number) end end
Moves a piece owned by the player, from one square, to another, with an optional promotion.
It moves the piece and returns true if the move is valid and it's the player's turn. It returns false otherwise.
Example:¶ ↑
# Moves a piece from a square to perform a move game_state.move(1, 'h7', 'h6')
@param [Fixnum] player_number
the player number, 1 or 2
@param [String] from_id
the id of the from square
@param [String] to_id
the id of the to square
@option [String] promote_to
the type of the piece that the pawn will promote to
@return [Boolean]
# File lib/just_chess/game_state.rb, line 155 def move(player_number, from_id, to_id, promote_to=nil) @errors = [] from = squares.find_by_id(from_id) to = squares.find_by_id(to_id) if current_player_number != player_number @errors.push JustChess::NotPlayersTurnError.new elsif from.unoccupied? @errors.push JustChess::NoPieceError.new elsif !((0..7).include?(to.x) && (0..7).include?(to.y)) @errors.push JustChess::OffBoardError.new elsif !promote_to.nil? && !PROMOTABLE_PIECE_TYPES.include?(promote_to) @errors.push JustChess::InvalidPromotion.new elsif from.piece.can_move?(from, to, self) duplicate = self.clone duplicate.perform_complete_move(player_number, from_id, to_id, promote_to) if duplicate.in_check?(current_player_number) @errors.push JustChess::MovedIntoCheckError.new else perform_complete_move(player_number, from_id, to_id, promote_to) end else @errors.push JustChess::InvalidMoveError.new end @errors.empty? end
# File lib/just_chess/game_state.rb, line 251 def non_king_pieces_cannot_move?(player_number) squares.occupied_by_player(player_number).excluding_piece(JustChess::King).all? { |s| s.piece.destinations(s, self).empty? } end
Moves a piece owned by the player, from one square, to another, with an optional promotion without validation
It handles castling, en passant and promotion. It moves the piece and returns true if the move is valid and it's the player's turn. It returns false otherwise.
Example:¶ ↑
# Moves a piece from a square to perform a move game_state.move(1, 'h7', 'h6')
@param [Fixnum] player_number
the player number, 1 or 2
@param [String] from_id
the id of the from square
@param [String] to_id
the id of the to square
@option [String] promote_to
the type of the piece that the pawn will promote to
@return [Boolean]
# File lib/just_chess/game_state.rb, line 222 def perform_complete_move(player_number, from_id, to_id, promote_to=nil) from = squares.find_by_id(from_id) to = squares.find_by_id(to_id) captured = captured_square(from, to) double_step_pawn = from.piece.is_a?(JustChess::Pawn) && BoardGameGrid::Vector.new(from,to).magnitude == 2 @last_double_step_pawn_id = double_step_pawn ? from.piece.id : nil @last_change = { type: 'move', data: {player_number: player_number, from: from_id, to: to_id} } rook_castle = rook_castle_move(from, to) perform_move(rook_castle.from, rook_castle.to, nil) if rook_castle perform_move(from, to, captured) promote(to, promote_to) if pawn_moved_to_last_rank(to) pass_turn end
The player number of the winner. It returns nil if there is no winner.
@return [Fixnum,NilClass]
# File lib/just_chess/game_state.rb, line 188 def winner case when in_checkmate?(1) 2 when in_checkmate?(2) 1 else nil end end
Private Instance Methods
# File lib/just_chess/game_state.rb, line 267 def captured_square(from, to) if to.occupied? to else en_passant_square = from.piece.is_a?(Pawn) && from.piece.en_passant_square(from, self) if en_passant_square && en_passant_square.x == to.x squares.find_by_piece_id(last_double_step_pawn_id) else nil end end end
# File lib/just_chess/game_state.rb, line 306 def opposing_player_number(player_number=nil) if player_number other_player_number(player_number) else other_player_number(@current_player_number) end end
# File lib/just_chess/game_state.rb, line 314 def other_player_number(player_number) (player_number == 1) ? 2 : 1 end
# File lib/just_chess/game_state.rb, line 302 def pass_turn @current_player_number = opposing_player_number end
# File lib/just_chess/game_state.rb, line 298 def pawn_moved_to_last_rank(square) square.piece && square.piece.is_a?(Pawn) && square.last_rank(square.piece.player_number) end
# File lib/just_chess/game_state.rb, line 318 def perform_move(from, to, captured) captured.piece = nil if captured to.piece = from.piece from.piece = nil to.piece.moved end
# File lib/just_chess/game_state.rb, line 325 def promote(square, promote_to) new_piece = PieceFactory.new(type: promote_to, id: square.piece.id, player_number: square.piece.player_number).build square.piece = new_piece end
# File lib/just_chess/game_state.rb, line 280 def rook_castle_move(from, to) if from.occupied? && from.piece.is_a?(King) && from.piece.castle(from, self).include?(to) vector = BoardGameGrid::Vector.new(from, to) rook_from_x = vector.direction.x > 0 ? 7 : 0 rook_from_y = from.y rook_from = squares.find_by_x_and_y(rook_from_x, rook_from_y) rook_to_x = vector.direction.x > 0 ? (from.x + 1) : (from.x - 1) rook_to_y = from.y rook_to = squares.find_by_x_and_y(rook_to_x, rook_to_y) Struct.new(:from, :to).new(rook_from, rook_to) else nil end end