Class: ChessEngine::Game

Inherits:
Object
  • Object
show all
Includes:
MoveValidator
Defined in:
lib/chess_engine/game.rb

Overview

This class provides all the rules for the chess game. It recognizes check, checkmate and stalemate. Move validations logic can be found in the MoveValidator module, which is included in this class

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MoveValidator

#safe_moves, #valid_moves

Constructor Details

#initializeGame

Returns a new instance of Game.



18
19
20
21
22
23
24
25
# File 'lib/chess_engine/game.rb', line 18

def initialize
  @board = Board.new
  @board.set_default
  @current_color = :white
  @last_piece = nil
  @name = nil
  @promotion_coord = false
end

Instance Attribute Details

#current_colorObject (readonly)

Returns the value of attribute current_color.



15
16
17
# File 'lib/chess_engine/game.rb', line 15

def current_color
  @current_color
end

#nameObject

Returns the value of attribute name.



14
15
16
# File 'lib/chess_engine/game.rb', line 14

def name
  @name
end

Instance Method Details

#[](str) ⇒ Object

Returns a piece (or nil if the square is empty) at given coordinates

Example

g = Game.new
g["e2"] #=> <Pawn ...>
g["e4"] #=> nil


70
71
72
73
74
# File 'lib/chess_engine/game.rb', line 70

def [](str)
  letters = ("a".."h").to_a
  return nil unless /[a-h][1-8]/.match?(str)
  @board.at([letters.find_index(str[0]), str[1].to_i - 1])
end

#castling(length) ⇒ Object

Accepts a length sybmol :short or :long. Ensures that castling is possible and commits appropriate moves. Otherwise, raises InvalidMove

Raises:



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/chess_engine/game.rb', line 104

def castling(length)
  row = @current_color == :white ? 0 : 7
  king = @board.at([4, row])
  if length == :short
    rook = @board.at([7, row])
    line = [5, 6]
    moves = [Move.new(@board, [4, row], [6, row]),
      Move.new(@board, [7, row], [5, row])]
  else
    rook = @board.at([0, row])
    line = [1, 2, 3]
    moves = [Move.new(@board, [4, row], [2, row]),
      Move.new(@board, [0, row], [3, row])]
  end
  raise InvalidMove, "Invalid castling" unless
    king && rook && king.moves_count == 0 && rook.moves_count == 0 &&
    line.all? { |x| @board.at([x, row]).nil? }

  moves.each { |move| move.commit }
  if king_attacked?
    moves.each { |move| move.rollback }
    raise InvalidMove, "Fatal move"
  end
  @last_piece = nil
  next_player
end

#check?Boolean

Returns true if current king is attacked

Returns:

  • (Boolean)


150
151
152
# File 'lib/chess_engine/game.rb', line 150

def check?
  king_attacked?
end

#drawObject

Returns the board in the nice-looking string



79
80
81
# File 'lib/chess_engine/game.rb', line 79

def draw
  @board.to_s
end

#move(string) ⇒ Object

Accepts the move string in algebraic notation, e.g. “e2e4”, and applies it to the board. Raises InvalidMove if:

  • Game is already over

  • Pawn promotion should be executed first

  • Empty square is chosen

  • Player tries to move piece of the opponent

  • Move is invalid (checks via the MoveValidator module)

  • Move is fatal (king is attacked after the move)

After successfull move, the method changes the current player or goes into “A pawn needs promotion” state, which can be checked by #needs_promotion? method

Raises:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/chess_engine/game.rb', line 42

def move(string)
  from, to = Game.string_to_move(string)
  piece = @board.at(from)
  raise InvalidMove, "Game is over" if over?
  raise InvalidMove, "#{@current_color} player should execute pawn promotion first" if needs_promotion?
  raise InvalidMove, "Empty square is chosen" if piece.nil?
  raise InvalidMove, "This is not your piece" unless piece.color == @current_color
  raise InvalidMove, "Invalid move" unless valid_moves(from).include?(to)
  move = Move.new(@board, from, to)
  move.commit
  if king_attacked?
    move.rollback
    raise InvalidMove, "Fatal move"
  end

  @last_piece = piece
  piece.moves_count += 1
  @promotion_coord = to and return if piece.pawn? && [7, 0].include?(to[1])
  next_player
end

#needs_promotion?Boolean

Checks if pawn promotion is needed

Returns:

  • (Boolean)


143
144
145
# File 'lib/chess_engine/game.rb', line 143

def needs_promotion?
  !!@promotion_coord
end

#over?Boolean

Returns true if game is over

Returns:

  • (Boolean)


134
135
136
137
138
# File 'lib/chess_engine/game.rb', line 134

def over?
  @board.piece_coordinates(@current_color).all? do |coord|
    safe_moves(coord).empty?
  end
end

#promotion(class_name) ⇒ Object

Accepts a string with name of the piece. Promotes a pawn and changes the current player. Raises InvalidMove if promotion is not needed or invalid class_name has been passed

Example

game.promotion("queen")


91
92
93
94
95
96
97
98
# File 'lib/chess_engine/game.rb', line 91

def promotion(class_name)
  unless needs_promotion? && ["rook", "knight", "elephant", "queen"].include?(class_name.downcase)
    raise InvalidMove, "Invalid promotion"
  end
  @board.set_at(@promotion_coord, Module.const_get("ChessEngine::#{class_name.capitalize}").new(@current_color))
  @promotion_coord = nil
  next_player
end