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.



18
19
20
21
22
23
24
25
26
27
28
# File 'lib/just_chess/game_state.rb', line 18

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.



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

def current_player_number
  @current_player_number
end

#errorsObject (readonly)

Returns the value of attribute errors.



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

def errors
  @errors
end

#last_changeObject (readonly)

Returns the value of attribute last_change.



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

def last_change
  @last_change
end

#last_double_step_pawn_idObject (readonly)

Returns the value of attribute last_double_step_pawn_id.



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

def last_double_step_pawn_id
  @last_double_step_pawn_id
end

#squaresObject (readonly)

Returns the value of attribute squares.



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

def squares
  @squares
end

Class Method Details

.defaultGameState

Instantiates a new GameState object in the starting position

Returns:



35
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
# File 'lib/just_chess/game_state.rb', line 35

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)


117
118
119
120
121
122
123
# File 'lib/just_chess/game_state.rb', line 117

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:



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

def clone
  self.class.new(as_json)
end

#in_check?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


240
241
242
243
244
# File 'lib/just_chess/game_state.rb', line 240

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)


246
247
248
# File 'lib/just_chess/game_state.rb', line 246

def in_checkmate?(player_number)
  in_check?(player_number) && king_cannot_move?(player_number)
end

#king_cannot_move?(player_number) ⇒ Boolean

Returns:

  • (Boolean)


251
252
253
254
255
256
257
# File 'lib/just_chess/game_state.rb', line 251

def king_cannot_move?(player_number)
  king_square = squares.find_king_for_player(player_number)
  threatened_by = squares.threatened_by(opposing_player_number(player_number), self)
  destinations = king_square.piece.destinations(king_square, self)
  remove_threats = destinations - threatened_by
  remove_threats.none?
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)


154
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
# File 'lib/just_chess/game_state.rb', line 154

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

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


221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/just_chess/game_state.rb', line 221

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


187
188
189
190
191
192
193
194
195
196
# File 'lib/just_chess/game_state.rb', line 187

def winner
  case
  when in_checkmate?(1)
    2
  when in_checkmate?(2)
    1
  else
    nil
  end
end