Class: Board
Instance Attribute Summary collapse
-
#checkmate ⇒ Object
readonly
Returns the value of attribute checkmate.
-
#piece_locations ⇒ Object
(also: #piece_manifest)
readonly
Returns the value of attribute piece_locations.
-
#player_turn ⇒ Object
readonly
Returns the value of attribute player_turn.
-
#taken_pieces ⇒ Object
readonly
Returns the value of attribute taken_pieces.
Instance Method Summary collapse
-
#attack_vectors(color = @player_turn, proposed_manifest = @piece_locations) ⇒ Object
Board spaces that are attackable by opposing pieces TODO: check? method should use this function.
-
#check?(color, proposed_manifest = @piece_locations, recurse_for_checkmate = false) ⇒ Boolean
Return whether the player of a specified color has their king currently in check by checking the attack vectors of all the opponents players against the king location Also, check whether king currently in check, has all of their valid moves within their opponents attack vectors, and therefore are in checkmate (@checkmate).
- #checkmate?(color, manifest) ⇒ Boolean
- #commit_board_piece_movement ⇒ Object
- #create_board_after_piece_moved(p1, p2) ⇒ Object
-
#display_board ⇒ Object
Reprint the board.
-
#get_index_from_rowcol(row_col) ⇒ Object
Convert index [A-H] => (1 - 64).
-
#get_rowcol_from_index(index) ⇒ Object
Convert index (1 - 64) => [A-H].
-
#initialize ⇒ Board
constructor
A new instance of Board.
-
#king_positions ⇒ Object
Search piece manifest for kings.
-
#move(p1, p2) ⇒ Object
Game logic.
- #opposing_color(player_color) ⇒ Object
- #place_first_row(color) ⇒ Object
- #place_pawns(color) ⇒ Object
- #place_player_pieces(color) ⇒ Object
- #player_in_checkmate(manifest = @piece_locations) ⇒ Object
- #setup_board ⇒ Object
- #setup_empty_tiles ⇒ Object
-
#valid_destinations(current_pos) ⇒ Object
Return the valid positions for piece at current_pos to move in readable format [A-H].
Methods included from Move
#castle, #constants, #get_col_from_index, #get_row_from_index, #move_diagonal, #move_knight, #move_lateral, #move_pawn, #moved?, #not_king, #piece_color, #possible_moves, #unoccupied?
Methods included from Printer
#clear_all, #piece_to_string, #print_board, #print_end_of_row, #print_footer, #print_header, #print_start_of_row, #printer, #substitute_pieces
Constructor Details
#initialize ⇒ Board
Returns a new instance of Board.
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/board.rb', line 19 def initialize @piece_locations_buffer = Hash.new @piece_locations = Hash.new @row_mappings = Hash[("A".."H").zip(1..8)] @taken_pieces = Array.new @player_turn = :black @checkmate = false setup_board end |
Instance Attribute Details
#checkmate ⇒ Object (readonly)
Returns the value of attribute checkmate.
13 14 15 |
# File 'lib/board.rb', line 13 def checkmate @checkmate end |
#piece_locations ⇒ Object (readonly) Also known as: piece_manifest
Returns the value of attribute piece_locations.
13 14 15 |
# File 'lib/board.rb', line 13 def piece_locations @piece_locations end |
#player_turn ⇒ Object (readonly)
Returns the value of attribute player_turn.
13 14 15 |
# File 'lib/board.rb', line 13 def player_turn @player_turn end |
#taken_pieces ⇒ Object (readonly)
Returns the value of attribute taken_pieces.
14 15 16 |
# File 'lib/board.rb', line 14 def taken_pieces @taken_pieces end |
Instance Method Details
#attack_vectors(color = @player_turn, proposed_manifest = @piece_locations) ⇒ Object
Board spaces that are attackable by opposing pieces
TODO: check? method should use this function
270 271 272 273 274 275 276 277 278 279 |
# File 'lib/board.rb', line 270 def attack_vectors(color = @player_turn, proposed_manifest = @piece_locations) enemy_color = opposing_color(color) kill_zone = Array.new proposed_manifest.each do |piece, details| kill_zone << possible_moves(piece, proposed_manifest) if details.fetch(:color) == enemy_color end kill_zone.flatten.uniq end |
#check?(color, proposed_manifest = @piece_locations, recurse_for_checkmate = false) ⇒ Boolean
Return whether the player of a specified color has their king currently in check by checking the attack vectors of all the opponents players against the king location Also, check whether king currently in check, has all of their valid moves within their opponents attack vectors, and therefore are in checkmate (@checkmate)
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/board.rb', line 200 def check?(color, proposed_manifest = @piece_locations, recurse_for_checkmate = false) enemy_attack_vectors = {} player_attack_vectors = {} king_loc = [] enemy_color = opposing_color(color) proposed_manifest.each do |piece, details| if details[:color] == enemy_color enemy_attack_vectors[piece] = possible_moves(piece, proposed_manifest) elsif details[:color] == color begin player_attack_vectors[piece] = possible_moves(piece, proposed_manifest) rescue # TODO: Fix possible_moves() so it doesn't throw exceptions # This happens because it is searching board for where pieces # will be, as as a result some pieces are nil end end king_loc = piece if details[:color] == color && details[:type] == :king end danger_vector = enemy_attack_vectors.values.flatten.uniq defence_vector = player_attack_vectors.values.flatten.uniq king_positions = possible_moves(king_loc, proposed_manifest) # The King is in the attackable locations by the opposing player return false unless danger_vector.include? king_loc # If all the positions the king piece can move to is also attackable by the opposing player if recurse_for_checkmate && ((king_positions - danger_vector).length == 0) is_in_check = [] player_attack_vectors.each do |piece_index, piece_valid_moves| piece_valid_moves.each do |possible_new_location| # Check if board is still in check after piece moves to its new location @new_piece_locations = @piece_locations.clone @new_piece_locations[possible_new_location] = @new_piece_locations[piece_index] @new_piece_locations[piece_index] = { :type => nil, :number => nil, :color => nil } is_in_check << check?(color, @new_piece_locations) end end if is_in_check.include?(false) return false else @checkmate = true end end true end |
#checkmate?(color, manifest) ⇒ Boolean
192 193 194 |
# File 'lib/board.rb', line 192 def checkmate?(color, manifest) check?(color, manifest, recurse_for_checkmate=true) && @checkmate end |
#commit_board_piece_movement ⇒ Object
103 104 105 106 107 |
# File 'lib/board.rb', line 103 def commit_board_piece_movement @piece_locations = @piece_locations_buffer @player_turn = opposing_color(@player_turn) display_board end |
#create_board_after_piece_moved(p1, p2) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/board.rb', line 110 def create_board_after_piece_moved(p1, p2) # Store state of board after proposed move in @piece_locations_buffer @piece_locations_buffer = @piece_locations.clone @piece_locations_buffer[p2] = @piece_locations_buffer[p1] @piece_locations_buffer[p2][:moved] = true @piece_locations_buffer[p1] = { :type => nil, :number => nil, :color => nil } end |
#display_board ⇒ Object
Reprint the board. Called after every valid piece move
283 284 285 |
# File 'lib/board.rb', line 283 def display_board print_board @piece_locations end |
#get_index_from_rowcol(row_col) ⇒ Object
Convert index [A-H] => (1 - 64)
289 290 291 |
# File 'lib/board.rb', line 289 def get_index_from_rowcol(row_col) (row_col[1].to_i - 1) * 8 + @row_mappings.fetch(row_col[0]).to_i end |
#get_rowcol_from_index(index) ⇒ Object
Convert index (1 - 64) => [A-H]
295 296 297 298 299 300 |
# File 'lib/board.rb', line 295 def get_rowcol_from_index(index) letter = get_col_from_index(index) number = get_row_from_index(index) "#{letter}#{number}" end |
#king_positions ⇒ Object
Search piece manifest for kings. Remove them from the list of positions returned from the Move module (so that players cannot take the “king” type piece)
143 144 145 146 147 148 149 150 151 |
# File 'lib/board.rb', line 143 def king_positions king_locations = [] @piece_locations.each do |piece, details| king_locations << piece if details.fetch(:type) == :king end king_locations end |
#move(p1, p2) ⇒ Object
Game logic
31 32 33 34 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 |
# File 'lib/board.rb', line 31 def move(p1, p2) manifest = piece_manifest() p1 = get_index_from_rowcol(p1.to_s) p2 = get_index_from_rowcol(p2.to_s) # Find valid positions and subtract king current position as nobody can directly take king piece valid_positions = possible_moves(p1, manifest, true) valid_positions -= king_positions # If player is moving out of turn, display message # `return p ...` is so we print value and return it from the function # this is so the unit tests can get the value directly. There are better ways to do this unless @player_turn == @piece_locations[p1][:color] return p "It is #{@player_turn}'s turn. Please move a #{@player_turn} piece." end return p "Please select a valid destination." unless valid_positions.include?(p2) create_board_after_piece_moved(p1, p2) # If player is in check with the new proposed board, disallow the movement if check?(@player_turn, @piece_locations_buffer) return p "Please move #{@player_turn} king out of check to continue" end # At this point, the move appears to be valid @taken_pieces << @piece_locations[p2] unless @piece_locations[p2][:type].nil? # Check for Pawn Promotion (if pawn reaches end of the board, promote it) if @piece_locations_buffer[p2][:type] == :pawn if p2 < 9 && @piece_locations_buffer[p2][:color] == :red promote(p2) elsif p2 > 56 && @piece_locations_buffer[p2][:color] == :black promote(p2) end end # Check for Castling - https://en.m.wikipedia.org/wiki/Castling if @piece_locations_buffer[p2][:type] == :king && (p2 - p1).abs == 2 p2 < 9 ? y_offset = 0 : y_offset = 56 if p2 > p1 @piece_locations_buffer[6+y_offset] = @piece_locations_buffer[8+y_offset] @piece_locations_buffer[8+y_offset] = { :type => nil, :number => nil, :color => nil } else @piece_locations_buffer[4+y_offset] = @piece_locations_buffer[1+y_offset] @piece_locations_buffer[1+y_offset] = { :type => nil, :number => nil, :color => nil } end end commit_board_piece_movement if (winner = player_in_checkmate(@piece_locations)) return p Messages.black_winner if winner == :black return p Messages.red_winner if winner == :red end return p Messages.piece_moved end |
#opposing_color(player_color) ⇒ Object
264 265 266 |
# File 'lib/board.rb', line 264 def opposing_color(player_color) ([:black, :red] - [player_color]).first end |
#place_first_row(color) ⇒ Object
344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/board.rb', line 344 def place_first_row(color) row_offset = color == :black ? 0 : 56 @piece_locations[row_offset + 1] = {:type => :rook, :number => 1, :color => color, :moved => false} @piece_locations[row_offset + 2] = {:type => :knight, :number => 1, :color => color, :moved => false} @piece_locations[row_offset + 3] = {:type => :bishop, :number => 1, :color => color, :moved => false} @piece_locations[row_offset + 4] = {:type => :queen, :number => 1, :color => color, :moved => false} @piece_locations[row_offset + 5] = {:type => :king, :number => 1, :color => color, :moved => false} @piece_locations[row_offset + 6] = {:type => :bishop, :number => 2, :color => color, :moved => false} @piece_locations[row_offset + 7] = {:type => :knight, :number => 2, :color => color, :moved => false} @piece_locations[row_offset + 8] = {:type => :rook, :number => 2, :color => color, :moved => false} end |
#place_pawns(color) ⇒ Object
330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/board.rb', line 330 def place_pawns(color) offset = color == :black ? 8 : 48 (1..8).each do |piece_num| @piece_locations[piece_num + offset] = { :type => :pawn, :number => piece_num, :color => color, :moved => false } end end |
#place_player_pieces(color) ⇒ Object
323 324 325 326 327 |
# File 'lib/board.rb', line 323 def place_player_pieces(color) # Place pieces on chess board place_first_row(color) place_pawns(color) end |
#player_in_checkmate(manifest = @piece_locations) ⇒ Object
187 188 189 190 |
# File 'lib/board.rb', line 187 def player_in_checkmate(manifest = @piece_locations) return :red if checkmate?(:black, manifest) return :black if checkmate?(:red, manifest) end |
#setup_board ⇒ Object
303 304 305 306 307 308 |
# File 'lib/board.rb', line 303 def setup_board # Intial setup of board. Put pieces into the expected locations setup_empty_tiles place_player_pieces(:black) place_player_pieces(:red) end |
#setup_empty_tiles ⇒ Object
311 312 313 314 315 316 317 318 319 320 |
# File 'lib/board.rb', line 311 def setup_empty_tiles # Initialize chess board tiles without any pieces (1..64).each do |tile_num| @piece_locations[tile_num] = { :type => nil, :number => nil, :color => nil } end end |
#valid_destinations(current_pos) ⇒ Object
Return the valid positions for piece at current_pos to move in readable format [A-H]
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/board.rb', line 124 def valid_destinations(current_pos) readable_positions = [] manifest = piece_manifest p1 = get_index_from_rowcol(current_pos.to_s) valid_positions = possible_moves(p1, manifest, true) valid_positions.each do |pos| grid_pos = get_rowcol_from_index(pos) # Map first string character 1-8 to [A-H], for column, and then add second string character as [1-8] readable_positions << (@row_mappings.key(grid_pos[0].to_i) + grid_pos[1].to_s) end readable_positions.sort end |