Class: ChessValidator::MoveLogic
- Inherits:
-
Object
- Object
- ChessValidator::MoveLogic
- Defined in:
- lib/move_logic.rb
Constant Summary collapse
- MAX_CACHE_SIZE =
1000
Class Method Summary collapse
- .advance_pawn?(pawn, board, move) ⇒ Boolean
- .boards ⇒ Object
- .can_defend?(piece, board, position, fen) ⇒ Boolean
- .castled?(piece, move) ⇒ Boolean
- .collision?(position, destination, occupied_spaces, i1, i2) ⇒ Boolean
- .defended_pieces_by_square(fen_notation) ⇒ Object
- .defended_sets ⇒ Object
- .diagonal_collision?(position, destination, occupied_spaces) ⇒ Boolean
- .empty_square?(board, move) ⇒ Boolean
- .en_passant?(piece, move, square) ⇒ Boolean
- .find_king_and_spaces(board, color) ⇒ Object
- .find_piece(board, position) ⇒ Object
- .find_target(board, piece, move, en_passant) ⇒ Object
- .forward_by(piece, count) ⇒ Object
- .get_board(fen_string) ⇒ Object
- .get_occupied_spaces(fen_string) ⇒ Object
- .handle_castle(board, move) ⇒ Object
- .handle_en_passant(board, pawn_color, move) ⇒ Object
- .handle_king(king, board, move, fen) ⇒ Object
- .handle_pawn(piece, board, move, fen) ⇒ Object
- .king_is_safe?(king_color, board, king_position, occupied_spaces) ⇒ Boolean
- .king_will_be_safe?(piece, board, move) ⇒ Boolean
- .load_move_data(board, piece, fen) ⇒ Object
- .make_move(piece, move, fen_notation) ⇒ Object
- .make_random_move(fen_notation, pieces_with_moves) ⇒ Object
- .moves_diagonal(vertical, horizontal, position) ⇒ Object
- .moves_for_bishop(position) ⇒ Object
- .moves_for_king(position) ⇒ Object
- .moves_for_knight(position) ⇒ Object
- .moves_for_pawn(pawn) ⇒ Object
- .moves_for_piece(piece) ⇒ Object
- .moves_for_queen(position) ⇒ Object
- .moves_for_rook(position) ⇒ Object
- .moves_horizontal(position) ⇒ Object
- .moves_vertical(position) ⇒ Object
- .next_moves(fen) ⇒ Object
- .next_moves_for ⇒ Object
- .occupied_square_sets ⇒ Object
- .previous_char(char) ⇒ Object
- .remove_out_of_bounds(moves) ⇒ Object
- .resolve_piece_type(piece_type, move) ⇒ Object
- .spaces_near_king(position) ⇒ Object
- .valid_destination?(piece, board, move) ⇒ Boolean
- .valid_move?(piece, board, move, fen) ⇒ Boolean
- .valid_move_path?(piece, move, occupied_spaces) ⇒ Boolean
- .with_next_move(piece, board, move) ⇒ Object
Class Method Details
.advance_pawn?(pawn, board, move) ⇒ Boolean
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/move_logic.rb', line 254 def advance_pawn?(pawn, board, move) if empty_square?(board, forward_by(pawn, 1)) if (pawn.position[1].to_i - move[1].to_i).abs == 2 empty_square?(board, forward_by(pawn, 2)) else true end else false end end |
.boards ⇒ Object
27 28 29 30 31 32 33 |
# File 'lib/move_logic.rb', line 27 def boards if @boards.nil? || @boards.size > MAX_CACHE_SIZE @boards = {} end @boards end |
.can_defend?(piece, board, position, fen) ⇒ Boolean
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/move_logic.rb', line 334 def can_defend?(piece, board, position, fen) case piece.piece_type.downcase when 'k' handle_king(piece, board, position, fen) when 'p' if piece.position[0] != position[0] target_piece = find_piece(board, position) valid = (target_piece && target_piece.color != piece.color) || position == fen.en_passant valid && king_will_be_safe?(piece, board, position) end else occupied_spaces = get_occupied_spaces(fen.to_s) valid_move_path?(piece, position, occupied_spaces) && king_will_be_safe?(piece, board, position) end end |
.castled?(piece, move) ⇒ Boolean
180 181 182 |
# File 'lib/move_logic.rb', line 180 def castled?(piece, move) piece.piece_type.downcase == 'k' && (piece.position[0].ord - move[0].ord).abs == 2 end |
.collision?(position, destination, occupied_spaces, i1, i2) ⇒ Boolean
301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/move_logic.rb', line 301 def collision?(position, destination, occupied_spaces, i1, i2) start_index = position[i1] end_index = destination[i1] if start_index > end_index start_index = destination[i1] end_index = position[i1] end occupied_spaces.select do |space| space[i2] == position[i2] && space[i1] > start_index && space[i1] < end_index end.size > 0 end |
.defended_pieces_by_square(fen_notation) ⇒ Object
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/move_logic.rb', line 350 def defended_pieces_by_square(fen_notation) return defended_sets[fen_notation] if defended_sets[fen_notation] board = get_board(fen_notation) fen = PGN::FEN.new(fen_notation) space_index = {} pieces = board.values.map do |piece| space_index[piece.position] = piece.piece_type + piece.color piece end defended = Hash.new([]) pieces.each do |piece| moves_for_piece(piece).each do |move| if space_index[move] && space_index[move][-1] == piece.color && can_defend?(piece, board, move, fen) defended[move] += [piece] end end end defended_sets[fen_notation] = defended defended end |
.defended_sets ⇒ Object
51 52 53 54 55 56 57 |
# File 'lib/move_logic.rb', line 51 def defended_sets if @defended_sets.nil? || @defended_sets.size > MAX_CACHE_SIZE @defended_sets = {} end @defended_sets end |
.diagonal_collision?(position, destination, occupied_spaces) ⇒ Boolean
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/move_logic.rb', line 315 def diagonal_collision?(position, destination, occupied_spaces) difference = (position[1].to_i - destination[1].to_i).abs - 1 horizontal_multiplyer = 1 horizontal_multiplyer = -1 if position[0] > destination[0] vertical_multiplyer = 1 vertical_multiplyer = -1 if position[1] > destination[1] move_path = [] difference.times do |n| column = (position[0].ord + ((n + 1) * horizontal_multiplyer)).chr row = (position[1].to_i + ((n + 1) * vertical_multiplyer)).to_s move_path << column + row end !(move_path & occupied_spaces).empty? end |
.empty_square?(board, move) ⇒ Boolean
284 285 286 |
# File 'lib/move_logic.rb', line 284 def empty_square?(board, move) board.values.none? { |piece| piece.position == move } end |
.en_passant?(piece, move, square) ⇒ Boolean
184 185 186 |
# File 'lib/move_logic.rb', line 184 def en_passant?(piece, move, square) piece.piece_type.downcase == 'p' && piece.position[0] != move[0] && !square end |
.find_king_and_spaces(board, color) ⇒ Object
195 196 197 198 199 200 201 202 203 |
# File 'lib/move_logic.rb', line 195 def find_king_and_spaces(board, color) king = nil occupied_spaces = board.values.map do |p| king = p if p.piece_type.downcase == 'k' && p.color == color p.position end [king, occupied_spaces] end |
.find_piece(board, position) ⇒ Object
280 281 282 |
# File 'lib/move_logic.rb', line 280 def find_piece(board, position) board.values.detect { |piece| piece.position == position } end |
.find_target(board, piece, move, en_passant) ⇒ Object
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/move_logic.rb', line 92 def find_target(board, piece, move, en_passant) if piece.piece_type.downcase == 'p' && piece.position[0] == move[0] nil elsif board[INDEX_KEY[move]] board[INDEX_KEY[move]] elsif piece.piece_type.downcase == 'p' && move == en_passant en_passant_position = piece.color == 'w' ? move[0] + '5' : move[0] + '4' board[INDEX_KEY[en_passant_position]] end end |
.forward_by(piece, count) ⇒ Object
266 267 268 269 |
# File 'lib/move_logic.rb', line 266 def forward_by(piece, count) position = piece.position piece.color == 'w' ? position[0] + (position[1].to_i + count).to_s : position[0] + (position[1].to_i - count).to_s end |
.get_board(fen_string) ⇒ Object
59 60 61 62 63 64 65 |
# File 'lib/move_logic.rb', line 59 def get_board(fen_string) return boards[fen_string] if boards[fen_string] board = BoardLogic.build_board_from_string(fen_string.split.first) boards[fen_string] = board board end |
.get_occupied_spaces(fen_string) ⇒ Object
68 69 70 71 72 73 74 75 76 77 |
# File 'lib/move_logic.rb', line 68 def get_occupied_spaces(fen_string) return occupied_square_sets[fen_string] if occupied_square_sets[fen_string] board = get_board(fen_string) occupied_spaces = [] board.values.each { |p| occupied_spaces << p.position } occupied_square_sets[fen_string] = occupied_spaces occupied_spaces end |
.handle_castle(board, move) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/move_logic.rb', line 148 def handle_castle(board, move) case move when 'c1' board.delete(57) board[60] = Piece.new('R', 60) when 'g1' board.delete(64) board[62] = Piece.new('R', 62) when 'c8' board.delete(1) board[4] = Piece.new('r', 4) when 'g8' board.delete(8) board[6] = Piece.new('r', 6) end board end |
.handle_en_passant(board, pawn_color, move) ⇒ Object
137 138 139 140 141 142 143 144 145 146 |
# File 'lib/move_logic.rb', line 137 def handle_en_passant(board, pawn_color, move) if pawn_color == 'w' index = INDEX_KEY[move[0] + '5'] else index = INDEX_KEY[move[0] + '4'] end board.delete(index) board end |
.handle_king(king, board, move, fen) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/move_logic.rb', line 205 def handle_king(king, board, move, fen) if (king.position[0].ord - move[0].ord).abs == 2 empty_b_square = true if move[0] == 'c' castle_code = 'q' between = 'd' + move[1] empty_b_square = empty_square?(board, 'b' + move[1]) else castle_code = 'k' between = 'f' + move[1] end occupied_spaces = get_occupied_spaces(fen.to_s) (fen.castling.include?(castle_code) && king.color == 'b' || fen.castling.include?(castle_code.upcase) && king.color == 'w') && king_is_safe?(king.color, board, king.position, occupied_spaces) && king_is_safe?(king.color, board, between, occupied_spaces) && king_is_safe?(king.color, board, move, occupied_spaces) && board.values.none? { |piece| [between, move].include?(piece.position) } && empty_b_square else king_will_be_safe?(king, board, move) end end |
.handle_pawn(piece, board, move, fen) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/move_logic.rb', line 242 def handle_pawn(piece, board, move, fen) position = piece.position if position[0] == move[0] valid = advance_pawn?(piece, board, move) else target_piece = find_piece(board, move) valid = (target_piece && target_piece.color != piece.color) || move == fen.en_passant end valid && king_will_be_safe?(piece, board, move) end |
.king_is_safe?(king_color, board, king_position, occupied_spaces) ⇒ Boolean
229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/move_logic.rb', line 229 def king_is_safe?(king_color, board, king_position, occupied_spaces) board.values.none? do |piece| piece.color != king_color && moves_for_piece(piece).any? do |move| if piece.piece_type.downcase == 'p' king_position == move && piece.position[0] != king_position[0] else king_position == move && valid_move_path?(piece, king_position, occupied_spaces) end end end end |
.king_will_be_safe?(piece, board, move) ⇒ Boolean
188 189 190 191 192 193 |
# File 'lib/move_logic.rb', line 188 def king_will_be_safe?(piece, board, move) new_board = with_next_move(piece, board, move) king, occupied_spaces = find_king_and_spaces(new_board, piece.color) return false if king.nil? king_is_safe?(king.color, new_board, king.position, occupied_spaces) end |
.load_move_data(board, piece, fen) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/move_logic.rb', line 80 def load_move_data(board, piece, fen) moves_for_piece(piece).each do |move| if valid_move?(piece, board, move, fen) piece.valid_moves << move target = find_target(board, piece, move, fen.en_passant) piece.targets << target if target else piece.move_potential << move end end end |
.make_move(piece, move, fen_notation) ⇒ Object
173 174 175 176 177 178 |
# File 'lib/move_logic.rb', line 173 def make_move(piece, move, fen_notation) board = get_board(fen_notation) new_board = with_next_move(piece, board, move) fen = PGN::FEN.new(fen_notation) BoardLogic.to_fen_notation(new_board, fen, piece, move, board[INDEX_KEY[move]]) end |
.make_random_move(fen_notation, pieces_with_moves) ⇒ Object
167 168 169 170 171 |
# File 'lib/move_logic.rb', line 167 def make_random_move(fen_notation, pieces_with_moves) piece_to_move = pieces_with_moves.sample move = piece_to_move.valid_moves.sample make_move(piece_to_move, move, fen_notation) end |
.moves_diagonal(vertical, horizontal, position) ⇒ Object
407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/move_logic.rb', line 407 def moves_diagonal(vertical, horizontal, position) column = position[0] row = position[1] possible_moves = [] while column >= 'a' && column <= 'h' && row >= '1' && row <= '8' do column = horizontal == 'left' ? previous_char(column) : column.next row = vertical == 'up' ? row.next : previous_char(row) possible_moves << column + row end remove_out_of_bounds(possible_moves) end |
.moves_for_bishop(position) ⇒ Object
398 399 400 401 402 403 404 405 |
# File 'lib/move_logic.rb', line 398 def moves_for_bishop(position) top_right = moves_diagonal('up', 'right', position) top_left = moves_diagonal('up', 'left', position) bottom_left = moves_diagonal('down', 'left', position) bottom_right = moves_diagonal('down', 'right', position) top_right + top_left + bottom_left + bottom_right end |
.moves_for_king(position) ⇒ Object
445 446 447 448 |
# File 'lib/move_logic.rb', line 445 def moves_for_king(position) castle_moves = [(position[0].ord - 2).chr + position[1], position[0].next.next + position[1]] remove_out_of_bounds(spaces_near_king(position) + castle_moves) end |
.moves_for_knight(position) ⇒ Object
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/move_logic.rb', line 450 def moves_for_knight(position) column = position[0].ord row = position[1].to_i moves = [ (column - 2).chr + (row + 1).to_s, (column - 2).chr + (row - 1).to_s, (column + 2).chr + (row + 1).to_s, (column + 2).chr + (row - 1).to_s, (column - 1).chr + (row + 2).to_s, (column - 1).chr + (row - 2).to_s, (column + 1).chr + (row + 2).to_s, (column + 1).chr + (row - 2).to_s ] remove_out_of_bounds(moves) end |
.moves_for_pawn(pawn) ⇒ Object
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/move_logic.rb', line 472 def moves_for_pawn(pawn) column = pawn.position[0].ord row = pawn.color == 'w' ? pawn.position[1].to_i + 1 : pawn.position[1].to_i - 1 moves = [ (column - 1).chr + row.to_s, (column + 1).chr + row.to_s, column.chr + row.to_s ] if pawn.color == 'w' && pawn.position[1] == '2' || pawn.color == 'b' && pawn.position[1] == '7' two_forward = pawn.color == 'w' ? row + 1 : row - 1 moves << column.chr + two_forward.to_s end remove_out_of_bounds(moves) end |
.moves_for_piece(piece) ⇒ Object
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'lib/move_logic.rb', line 377 def moves_for_piece(piece) case piece.piece_type.downcase when 'r' moves_for_rook(piece.position) when 'b' moves_for_bishop(piece.position) when 'q' moves_for_queen(piece.position) when 'k' moves_for_king(piece.position) when 'n' moves_for_knight(piece.position) when 'p' moves_for_pawn(piece) end end |
.moves_for_queen(position) ⇒ Object
424 425 426 |
# File 'lib/move_logic.rb', line 424 def moves_for_queen(position) moves_for_rook(position) + moves_for_bishop(position) end |
.moves_for_rook(position) ⇒ Object
394 395 396 |
# File 'lib/move_logic.rb', line 394 def moves_for_rook(position) moves_horizontal(position) + moves_vertical(position) end |
.moves_horizontal(position) ⇒ Object
489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/move_logic.rb', line 489 def moves_horizontal(position) possible_moves = [] column = 'a' row = position[1] 8.times do possible_moves << column + row unless column == position[0] column = column.next end possible_moves end |
.moves_vertical(position) ⇒ Object
502 503 504 505 506 507 508 509 510 511 512 513 |
# File 'lib/move_logic.rb', line 502 def moves_vertical(position) possible_moves = [] column = position[0] row = '1' 8.times do possible_moves << column + row unless row == position[1] row = row.next end possible_moves end |
.next_moves(fen) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/move_logic.rb', line 9 def next_moves(fen) fen_notation = fen.to_s return next_moves_for[fen_notation] if next_moves_for[fen_notation] board = get_board(fen.to_s) pieces = [] board.values.each do |piece| if piece.color == fen.active load_move_data(board, piece, fen) pieces << piece if piece.valid_moves.size > 0 end end next_moves_for[fen_notation] = pieces pieces end |
.next_moves_for ⇒ Object
35 36 37 38 39 40 41 |
# File 'lib/move_logic.rb', line 35 def next_moves_for if @next_moves_for.nil? || @next_moves_for.size > MAX_CACHE_SIZE @next_moves_for = {} end @next_moves_for end |
.occupied_square_sets ⇒ Object
43 44 45 46 47 48 49 |
# File 'lib/move_logic.rb', line 43 def occupied_square_sets if @occupied_square_sets.nil? || @occupied_square_sets.size > MAX_CACHE_SIZE @occupied_square_sets = {} end @occupied_square_sets end |
.previous_char(char) ⇒ Object
420 421 422 |
# File 'lib/move_logic.rb', line 420 def previous_char(char) (char.ord - 1).chr end |
.remove_out_of_bounds(moves) ⇒ Object
468 469 470 |
# File 'lib/move_logic.rb', line 468 def remove_out_of_bounds(moves) moves.select { |move| ('a'..'h').include?(move[0]) && ('1'..'8').include?(move[1..-1]) } end |
.resolve_piece_type(piece_type, move) ⇒ Object
129 130 131 132 133 134 135 |
# File 'lib/move_logic.rb', line 129 def resolve_piece_type(piece_type, move) if piece_type.downcase == 'p' && ['1', '8'].include?(move[1]) move[1] == '8' ? 'Q' : 'q' else piece_type end end |
.spaces_near_king(position) ⇒ Object
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 |
# File 'lib/move_logic.rb', line 428 def spaces_near_king(position) column = position[0].ord row = position[1].to_i moves = [ (column - 1).chr + row.to_s, (column + 1).chr + row.to_s, (column - 1).chr + (row - 1).to_s, (column - 1).chr + (row + 1).to_s, (column + 1).chr + (row - 1).to_s, (column + 1).chr + (row + 1).to_s, column.chr + (row + 1).to_s, column.chr + (row - 1).to_s ] remove_out_of_bounds(moves) end |
.valid_destination?(piece, board, move) ⇒ Boolean
271 272 273 274 275 276 277 278 |
# File 'lib/move_logic.rb', line 271 def valid_destination?(piece, board, move) target_piece = find_piece(board, move) if target_piece target_piece.color != piece.color else true end end |
.valid_move?(piece, board, move, fen) ⇒ Boolean
103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/move_logic.rb', line 103 def valid_move?(piece, board, move, fen) case piece.piece_type.downcase when 'k' handle_king(piece, board, move, fen) && valid_destination?(piece, board, move) when 'p' handle_pawn(piece, board, move, fen) else occupied_spaces = get_occupied_spaces(fen.to_s) valid_move_path?(piece, move, occupied_spaces) && valid_destination?(piece, board, move) && king_will_be_safe?(piece, board, move) end end |
.valid_move_path?(piece, move, occupied_spaces) ⇒ Boolean
288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/move_logic.rb', line 288 def valid_move_path?(piece, move, occupied_spaces) position = piece.position if piece.piece_type.downcase == 'n' true elsif position[0] == move[0] !collision?(piece.position, move, occupied_spaces, 1, 0) elsif position[1] == move[1] !collision?(piece.position, move, occupied_spaces, 0, 1) else !diagonal_collision?(piece.position, move, occupied_spaces) end end |
.with_next_move(piece, board, move) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/move_logic.rb', line 117 def with_next_move(piece, board, move) index = INDEX_KEY[move] new_board = board.clone piece_type = resolve_piece_type(piece.piece_type, move) new_piece = Piece.new(piece_type, index) new_board.delete(piece.square_index) new_board[index] = new_piece new_board = handle_castle(new_board, move) if castled?(piece, move) new_board = handle_en_passant(new_board, piece.color, move) if en_passant?(piece, move, new_board[index]) new_board end |