Class: ChessValidator::MoveLogic

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

Class Method Summary collapse

Class Method Details

.advance_pawn?(pawn, board, move) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
199
200
201
# File 'lib/move_logic.rb', line 191

def advance_pawn?(pawn, board, move)
  if (pawn.position[1].to_i - move[1].to_i).abs == 1
    empty_square?(board, move)
  else
    occupied_spaces = []
    board.values.each do |piece|
      occupied_spaces << piece.position
    end
    valid_move_path?(pawn, move, occupied_spaces) && empty_square?(board, move)
  end
end

.castled?(piece, move) ⇒ Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/move_logic.rb', line 120

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

Returns:

  • (Boolean)


233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/move_logic.rb', line 233

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

.diagonal_collision?(position, destination, occupied_spaces) ⇒ Boolean

Returns:

  • (Boolean)


247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/move_logic.rb', line 247

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

Returns:

  • (Boolean)


216
217
218
# File 'lib/move_logic.rb', line 216

def empty_square?(board, move)
  board.values.detect { |piece| piece.position == move }.nil?
end

.en_passant?(piece, move, square) ⇒ Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/move_logic.rb', line 124

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



134
135
136
137
138
139
140
141
142
# File 'lib/move_logic.rb', line 134

def find_king_and_spaces(board, color)
  occupied_spaces = []
  king = nil
  board.values.each do |p|
    king = p if p.piece_type.downcase == 'k' && p.color == color
    occupied_spaces << p.position
  end
  [king, occupied_spaces]
end

.find_piece(board, position) ⇒ Object



212
213
214
# File 'lib/move_logic.rb', line 212

def find_piece(board, position)
  board.values.detect { |piece| piece.position == position }
end

.find_target(board, piece, move) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/move_logic.rb', line 32

def find_target(board, piece, move)
  if board[INDEX_KEY[move]]
    board[INDEX_KEY[move]]
  elsif piece.piece_type.downcase == 'p' && piece.position[0] != move[0]
    en_passant_position = piece.color == 'w' ? move[0] + '5' : move[0] + '4'
    board[INDEX_KEY[en_passant_position]]
  end
end

.handle_castle(board, move) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/move_logic.rb', line 87

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



76
77
78
79
80
81
82
83
84
85
# File 'lib/move_logic.rb', line 76

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, occupied_spaces) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/move_logic.rb', line 144

def handle_king(king, board, move, fen, occupied_spaces)
  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
    (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
    valid_destination?(king, board, move) && king_is_safe?(king.color, board, move, occupied_spaces)
  end
end

.handle_pawn(piece, board, move, fen) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/move_logic.rb', line 179

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

Returns:

  • (Boolean)


166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/move_logic.rb', line 166

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

Returns:

  • (Boolean)


128
129
130
131
132
# File 'lib/move_logic.rb', line 128

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)
  king_is_safe?(king.color, new_board, king.position, occupied_spaces)
end

.load_move_data(board, piece, fen) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/move_logic.rb', line 20

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)
      piece.targets << target if target
    else
      piece.move_potential << move
    end
  end
end

.make_move(piece, move, fen_notation) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/move_logic.rb', line 112

def make_move(piece, move, fen_notation)
  fen = PGN::FEN.new(fen_notation)
  board = BoardLogic.build_board(fen)
  new_board = with_next_move(piece, board, move)

  BoardLogic.to_fen_notation(new_board, fen, piece, move)
end

.make_random_move(fen_notation, pieces_with_moves) ⇒ Object



106
107
108
109
110
# File 'lib/move_logic.rb', line 106

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



296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/move_logic.rb', line 296

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



287
288
289
290
291
292
293
294
# File 'lib/move_logic.rb', line 287

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



334
335
336
337
# File 'lib/move_logic.rb', line 334

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



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/move_logic.rb', line 339

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



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/move_logic.rb', line 361

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



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/move_logic.rb', line 266

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



313
314
315
# File 'lib/move_logic.rb', line 313

def moves_for_queen(position)
  moves_for_rook(position) + moves_for_bishop(position)
end

.moves_for_rook(position) ⇒ Object



283
284
285
# File 'lib/move_logic.rb', line 283

def moves_for_rook(position)
  moves_horizontal(position) + moves_vertical(position)
end

.moves_horizontal(position) ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/move_logic.rb', line 378

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



391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/move_logic.rb', line 391

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



7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/move_logic.rb', line 7

def next_moves(fen)
  board = BoardLogic.build_board(fen)
  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

  pieces
end

.previous_char(char) ⇒ Object



309
310
311
# File 'lib/move_logic.rb', line 309

def previous_char(char)
  (char.ord - 1).chr
end

.remove_out_of_bounds(moves) ⇒ Object



357
358
359
# File 'lib/move_logic.rb', line 357

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



68
69
70
71
72
73
74
# File 'lib/move_logic.rb', line 68

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



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/move_logic.rb', line 317

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

Returns:

  • (Boolean)


203
204
205
206
207
208
209
210
# File 'lib/move_logic.rb', line 203

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

Returns:

  • (Boolean)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/move_logic.rb', line 41

def valid_move?(piece, board, move, fen)
  occupied_spaces = []
  board.values.each { |p| occupied_spaces << p.position }
  case piece.piece_type.downcase
  when 'k'
    handle_king(piece, board, move, fen, occupied_spaces)
  when 'p'
    handle_pawn(piece, board, move, fen)
  else
    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

Returns:

  • (Boolean)


220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/move_logic.rb', line 220

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



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/move_logic.rb', line 56

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