Class: JustChess::GameState

Inherits:
Object
  • Object
show all
Defined in:
lib/just_chess/game_state.rb

Overview

Game State

Represents a game of Chess in progress.

Constant Summary collapse

PROMOTABLE_PIECE_TYPES =

They piece types that a pawn can promote to.

['queen', 'knight', 'bishop', 'rook']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(current_player_number:, squares: [], last_double_step_pawn_id: nil) ⇒ GameState

Returns a new instance of GameState.



19
20
21
22
23
24
25
26
27
28
29
# 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

Instance Attribute Details

#current_player_numberObject (readonly)

Returns the value of attribute current_player_number.



31
32
33
# File 'lib/just_chess/game_state.rb', line 31

def current_player_number
  @current_player_number
end

#errorsObject (readonly)

Returns the value of attribute errors.



31
32
33
# File 'lib/just_chess/game_state.rb', line 31

def errors
  @errors
end

#last_changeObject (readonly)

Returns the value of attribute last_change.



31
32
33
# File 'lib/just_chess/game_state.rb', line 31

def last_change
  @last_change
end

#last_double_step_pawn_idObject (readonly)

Returns the value of attribute last_double_step_pawn_id.



31
32
33
# File 'lib/just_chess/game_state.rb', line 31

def last_double_step_pawn_id
  @last_double_step_pawn_id
end

#squaresObject (readonly)

Returns the value of attribute squares.



31
32
33
# File 'lib/just_chess/game_state.rb', line 31

def squares
  @squares
end

Class Method Details

.defaultGameState

Instantiates a new GameState object in the starting position

Returns:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 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

Instance Method Details

#as_jsonHash

serializes the game state as a hash

Returns:

  • (Hash)


118
119
120
121
122
123
124
# 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

#cloneGameState

deep clone of the game state

Returns:



129
130
131
# File 'lib/just_chess/game_state.rb', line 129

def clone
  self.class.new(**as_json)
end

#in_check?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
244
245
# 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

#in_checkmate?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


247
248
249
# 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

#king_cannot_move?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


255
256
257
258
259
260
261
262
263
# 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

#move(player_number, from_id, to_id, promote_to = nil) ⇒ Boolean

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')

Parameters:

  • player_number (Fixnum)

    the player number, 1 or 2.

  • from_id (String)

    the id of the from square

  • to_id (String)

    the id of the to square

  • [String] (Hash)

    a customizable set of options

Returns:

  • (Boolean)


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 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

#non_king_pieces_cannot_move?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


251
252
253
# 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

#perform_complete_move(player_number, from_id, to_id, promote_to = nil) ⇒ Boolean

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')

Parameters:

  • player_number (Fixnum)

    the player number, 1 or 2.

  • from_id (String)

    the id of the from square

  • to_id (String)

    the id of the to square

  • [String] (Hash)

    a customizable set of options

Returns:

  • (Boolean)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# 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

#winnerFixnum, NilClass

The player number of the winner. It returns nil if there is no winner.

Returns:

  • (Fixnum, NilClass)


188
189
190
191
192
193
194
195
196
197
# 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