chess_validator 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 76c92b66d08e4f224ed65e0464616d2607bfb816c3549d094da30ac9329925fb
4
+ data.tar.gz: 2353327aa052a4b674fa6c9132fb73e71acad51610d89267e317de60ff7f5775
5
+ SHA512:
6
+ metadata.gz: 818c1c112a6a59e40ff4ace6f0736c91f50359a4055e72d1f0053835817a6660b17cb09c9b7af757367ca40122f21f11d1b75296c3bbc4b76505044fea70480c
7
+ data.tar.gz: b0bcfe8bc32af31dd89643124d4f0bf33c9121ae5b485c57d1ecca6fb07d1bedb06d776c756f365dc691476c3f24e368a6b70bc7adc006587c5d9efc152fcb1c
@@ -0,0 +1,24 @@
1
+ require 'piece'
2
+
3
+ module ChessValidator
4
+ class BoardLogic
5
+ def self.build_board(fen)
6
+ board = {}
7
+ square_index = 1
8
+ fen.to_s.split(' ').first.chars.each do |char|
9
+ if empty_square?(char)
10
+ square_index += char.to_i
11
+ elsif char != '/'
12
+ board[square_index] = Piece.new(char, square_index)
13
+ square_index += 1
14
+ end
15
+ end
16
+
17
+ board
18
+ end
19
+
20
+ def self.empty_square?(char)
21
+ ('1'..'8').include?(char)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1 @@
1
+ require 'move_logic'
@@ -0,0 +1,77 @@
1
+ SQUARE_KEY = {
2
+ 1 => 'a8', 2 => 'b8', 3 => 'c8', 4 => 'd8', 5 => 'e8', 6 => 'f8', 7 => 'g8', 8 => 'h8',
3
+ 9 => 'a7', 10 => 'b7', 11 => 'c7', 12 => 'd7', 13 => 'e7', 14 => 'f7', 15 => 'g7', 16 => 'h7',
4
+ 17 => 'a6', 18 => 'b6', 19 => 'c6', 20 => 'd6', 21 => 'e6', 22 => 'f6', 23 => 'g6', 24 => 'h6',
5
+ 25 => 'a5', 26 => 'b5', 27 => 'c5', 28 => 'd5', 29 => 'e5', 30 => 'f5', 31 => 'g5', 32 => 'h5',
6
+ 33 => 'a4', 34 => 'b4', 35 => 'c4', 36 => 'd4', 37 => 'e4', 38 => 'f4', 39 => 'g4', 40 => 'h4',
7
+ 41 => 'a3', 42 => 'b3', 43 => 'c3', 44 => 'd3', 45 => 'e3', 46 => 'f3', 47 => 'g3', 48 => 'h3',
8
+ 49 => 'a2', 50 => 'b2', 51 => 'c2', 52 => 'd2', 53 => 'e2', 54 => 'f2', 55 => 'g2', 56 => 'h2',
9
+ 57 => 'a1', 58 => 'b1', 59 => 'c1', 60 => 'd1', 61 => 'e1', 62 => 'f1', 63 => 'g1', 64 => 'h1'
10
+ }
11
+
12
+ INDEX_KEY = {
13
+ "a8" => 1,
14
+ "b8" => 2,
15
+ "c8" => 3,
16
+ "d8" => 4,
17
+ "e8" => 5,
18
+ "f8" => 6,
19
+ "g8" => 7,
20
+ "h8" => 8,
21
+ "a7" => 9,
22
+ "b7" => 10,
23
+ "c7" => 11,
24
+ "d7" => 12,
25
+ "e7" => 13,
26
+ "f7" => 14,
27
+ "g7" => 15,
28
+ "h7" => 16,
29
+ "a6" => 17,
30
+ "b6" => 18,
31
+ "c6" => 19,
32
+ "d6" => 20,
33
+ "e6" => 21,
34
+ "f6" => 22,
35
+ "g6" => 23,
36
+ "h6" => 24,
37
+ "a5" => 25,
38
+ "b5" => 26,
39
+ "c5" => 27,
40
+ "d5" => 28,
41
+ "e5" => 29,
42
+ "f5" => 30,
43
+ "g5" => 31,
44
+ "h5" => 32,
45
+ "a4" => 33,
46
+ "b4" => 34,
47
+ "c4" => 35,
48
+ "d4" => 36,
49
+ "e4" => 37,
50
+ "f4" => 38,
51
+ "g4" => 39,
52
+ "h4" => 40,
53
+ "a3" => 41,
54
+ "b3" => 42,
55
+ "c3" => 43,
56
+ "d3" => 44,
57
+ "e3" => 45,
58
+ "f3" => 46,
59
+ "g3" => 47,
60
+ "h3" => 48,
61
+ "a2" => 49,
62
+ "b2" => 50,
63
+ "c2" => 51,
64
+ "d2" => 52,
65
+ "e2" => 53,
66
+ "f2" => 54,
67
+ "g2" => 55,
68
+ "h2" => 56,
69
+ "a1" => 57,
70
+ "b1" => 58,
71
+ "c1" => 59,
72
+ "d1" => 60,
73
+ "e1" => 61,
74
+ "f1" => 62,
75
+ "g1" => 63,
76
+ "h1" => 64
77
+ }
@@ -0,0 +1,366 @@
1
+ require 'board_logic'
2
+ require 'pgn'
3
+ require 'pry'
4
+ module ChessValidator
5
+ class MoveLogic
6
+ class << self
7
+ def find_next_moves(fen_notatioin)
8
+ fen = PGN::FEN.new(fen_notatioin)
9
+ next_moves(fen)
10
+ end
11
+
12
+ def find_next_moves_from_moves(moves)
13
+ fen = PGN::Game.new(moves).positions.last.to_fen
14
+ next_moves(fen)
15
+ end
16
+
17
+ def next_moves(fen)
18
+ board = BoardLogic.build_board(fen)
19
+ pieces = []
20
+ board.values.each do |piece|
21
+ if piece.color == fen.active
22
+ load_valid_moves(board, piece, fen)
23
+ pieces << piece
24
+ end
25
+ end
26
+
27
+ pieces
28
+ end
29
+
30
+ def load_valid_moves(board, piece, fen)
31
+ moves_for_piece(piece).each do |move|
32
+ piece.valid_moves << move if valid_move?(piece, board, move, fen)
33
+ end
34
+ end
35
+
36
+ def valid_move?(piece, board, move, fen)
37
+ occupied_spaces = []
38
+ board.values.each { |p| occupied_spaces << p.position }
39
+ case piece.piece_type.downcase
40
+ when 'k'
41
+ handle_king(piece, board, move, fen, occupied_spaces)
42
+ when 'p'
43
+ handle_pawn(piece, board, move, fen)
44
+ else
45
+ valid_move_path?(piece, move, occupied_spaces) &&
46
+ valid_destination?(piece, board, move) &&
47
+ king_will_be_safe?(piece, board, move)
48
+ end
49
+ end
50
+
51
+ def with_next_move(piece, board, move)
52
+ index = INDEX_KEY[move]
53
+ new_board = board.clone
54
+ new_piece = Piece.new(piece.piece_type, index)
55
+ new_board.delete(piece.square_index)
56
+ new_board[index] = new_piece
57
+ new_board = handle_castle(new_board, move) if castled?(piece, move)
58
+ new_board = handle_en_passant(new_board, piece.color, move) if en_passant?(piece, move, new_board[index])
59
+ new_board
60
+ end
61
+
62
+ def handle_en_passant(board, pawn_color, move)
63
+ if pawn_color == 'w'
64
+ index = INDEX_KEY[move[0] + '5']
65
+ else
66
+ index = INDEX_KEY[move[0] + '4']
67
+ end
68
+
69
+ board.delete(index)
70
+ board
71
+ end
72
+
73
+ def handle_castle(board, move)
74
+ case move
75
+ when 'c1'
76
+ board.delete(57)
77
+ board[60] = Piece.new('R', 60)
78
+ when 'g1'
79
+ board.delete(64)
80
+ board[62] = Piece.new('R', 62)
81
+ when 'c8'
82
+ board.delete(1)
83
+ board[4] = Piece.new('r', 4)
84
+ when 'g8'
85
+ board.delete(8)
86
+ board[6] = Piece.new('r', 6)
87
+ end
88
+
89
+ board
90
+ end
91
+
92
+ def castled?(piece, move)
93
+ piece.piece_type.downcase == 'k' && (piece.position[0].ord - move[0].ord).abs == 2
94
+ end
95
+
96
+ def en_passant?(piece, move, square)
97
+ piece.piece_type.downcase == 'p' && piece.position[0] != move[0] && !square
98
+ end
99
+
100
+ def king_will_be_safe?(piece, board, move)
101
+ new_board = with_next_move(piece, board, move)
102
+ occupied_spaces = []
103
+ king = nil
104
+ new_board.values.each do |p|
105
+ king = p if p.piece_type.downcase == 'k' && p.color == piece.color
106
+ occupied_spaces << p.position
107
+ end
108
+ king_is_safe?(king.color, new_board, king.position, occupied_spaces)
109
+ end
110
+
111
+ def handle_king(king, board, move, fen, occupied_spaces)
112
+ if (king.position[0].ord - move[0].ord).abs == 2
113
+ empty_b_square = true
114
+ if move[0] == 'c'
115
+ castle_code = 'q'
116
+ between = 'd' + move[1]
117
+ empty_b_square = empty_square?(board, 'b' + move[1])
118
+ else
119
+ castle_code = 'k'
120
+ between = 'f' + move[1]
121
+ end
122
+ (fen.castling.include?(castle_code) && king.color == 'b' || fen.castling.include?(castle_code.upcase) && king.color == 'w') &&
123
+ king_is_safe?(king.color, board, king.position, occupied_spaces) &&
124
+ king_is_safe?(king.color, board, between, occupied_spaces) &&
125
+ king_is_safe?(king.color, board, move, occupied_spaces) &&
126
+ board.values.none? { |piece| [between, move].include?(piece.position) } &&
127
+ empty_b_square
128
+ else
129
+ valid_destination?(king, board, move) && king_is_safe?(king.color, board, move, occupied_spaces)
130
+ end
131
+ end
132
+
133
+ def king_is_safe?(king_color, board, king_position, occupied_spaces)
134
+ board.values.none? do |piece|
135
+ piece.color != king_color &&
136
+ moves_for_piece(piece).select do |move|
137
+ king_position == move && valid_move_path?(piece, king_position, occupied_spaces)
138
+ end.size > 0
139
+ end
140
+ end
141
+
142
+ def handle_pawn(piece, board, move, fen)
143
+ position = piece.position
144
+
145
+ if position[0] == move[0]
146
+ advance_pawn?(piece, board, move)
147
+ else
148
+ target_piece = find_piece(board, move)
149
+ (target_piece && target_piece.color != piece.color) || move == fen.en_passant
150
+ end
151
+ end
152
+
153
+ def advance_pawn?(pawn, board, move)
154
+ if (pawn.position[1].to_i - move[1].to_i).abs == 1
155
+ empty_square?(board, move)
156
+ else
157
+ occupied_spaces = []
158
+ board.values.each do |piece|
159
+ occupied_spaces << piece.position
160
+ end
161
+ valid_move_path?(pawn, move, occupied_spaces) && empty_square?(board, move)
162
+ end
163
+ end
164
+
165
+ def valid_destination?(piece, board, move)
166
+ target_piece = find_piece(board, move)
167
+ if target_piece
168
+ target_piece.color != piece.color
169
+ else
170
+ true
171
+ end
172
+ end
173
+
174
+ def find_piece(board, position)
175
+ board.values.detect { |piece| piece.position == position }
176
+ end
177
+
178
+ def empty_square?(board, move)
179
+ board.values.detect { |piece| piece.position == move }.nil?
180
+ end
181
+
182
+ def valid_move_path?(piece, move, occupied_spaces)
183
+ position = piece.position
184
+ if piece.piece_type.downcase == 'n'
185
+ true
186
+ elsif position[0] == move[0]
187
+ !collision?(piece.position, move, occupied_spaces, 1, 0)
188
+ elsif position[1] == move[1]
189
+ !collision?(piece.position, move, occupied_spaces, 0, 1)
190
+ else
191
+ !diagonal_collision?(piece.position, move, occupied_spaces)
192
+ end
193
+ end
194
+
195
+ def collision?(position, destination, occupied_spaces, i1, i2)
196
+ start_index = position[i1]
197
+ end_index = destination[i1]
198
+
199
+ if start_index > end_index
200
+ start_index = destination[i1]
201
+ end_index = position[i1]
202
+ end
203
+
204
+ occupied_spaces.select do |space|
205
+ space[i2] == position[i2] && space[i1] > start_index && space[i1] < end_index
206
+ end.size > 0
207
+ end
208
+
209
+ def diagonal_collision?(position, destination, occupied_spaces)
210
+ difference = (position[1].to_i - destination[1].to_i).abs - 1
211
+
212
+ horizontal_multiplyer = 1
213
+ horizontal_multiplyer = -1 if position[0] > destination[0]
214
+
215
+ vertical_multiplyer = 1
216
+ vertical_multiplyer = -1 if position[1] > destination[1]
217
+
218
+ move_path = []
219
+ difference.times do |n|
220
+ column = (position[0].ord + ((n + 1) * horizontal_multiplyer)).chr
221
+ row = (position[1].to_i + ((n + 1) * vertical_multiplyer)).to_s
222
+ move_path << column + row
223
+ end
224
+
225
+ !(move_path & occupied_spaces).empty?
226
+ end
227
+
228
+ def moves_for_piece(piece)
229
+ case piece.piece_type.downcase
230
+ when 'r'
231
+ moves_for_rook(piece.position)
232
+ when 'b'
233
+ moves_for_bishop(piece.position)
234
+ when 'q'
235
+ moves_for_queen(piece.position)
236
+ when 'k'
237
+ moves_for_king(piece.position)
238
+ when 'n'
239
+ moves_for_knight(piece.position)
240
+ when 'p'
241
+ moves_for_pawn(piece)
242
+ end
243
+ end
244
+
245
+ def moves_for_rook(position)
246
+ moves_horizontal(position) + moves_vertical(position)
247
+ end
248
+
249
+ def moves_for_bishop(position)
250
+ top_right = moves_diagonal('up', 'right', position)
251
+ top_left = moves_diagonal('up', 'left', position)
252
+ bottom_left = moves_diagonal('down', 'left', position)
253
+ bottom_right = moves_diagonal('down', 'right', position)
254
+
255
+ top_right + top_left + bottom_left + bottom_right
256
+ end
257
+
258
+ def moves_diagonal(vertical, horizontal, position)
259
+ column = position[0]
260
+ row = position[1]
261
+ possible_moves = []
262
+
263
+ while column >= 'a' && column <= 'h' && row >= '1' && row <= '8' do
264
+ column = horizontal == 'left' ? previous_char(column) : column.next
265
+ row = vertical == 'up' ? row.next : previous_char(row)
266
+ possible_moves << column + row
267
+ end
268
+ remove_out_of_bounds(possible_moves)
269
+ end
270
+
271
+ def previous_char(char)
272
+ (char.ord - 1).chr
273
+ end
274
+
275
+ def moves_for_queen(position)
276
+ moves_for_rook(position) + moves_for_bishop(position)
277
+ end
278
+
279
+ def spaces_near_king(position)
280
+ column = position[0].ord
281
+ row = position[1].to_i
282
+
283
+ moves = [
284
+ (column - 1).chr + row.to_s,
285
+ (column + 1).chr + row.to_s,
286
+ (column - 1).chr + (row - 1).to_s,
287
+ (column - 1).chr + (row + 1).to_s,
288
+ (column + 1).chr + (row - 1).to_s,
289
+ (column + 1).chr + (row + 1).to_s,
290
+ column.chr + (row + 1).to_s,
291
+ column.chr + (row - 1).to_s
292
+ ]
293
+ end
294
+
295
+ def moves_for_king(position)
296
+ castle_moves = [(position[0].ord - 2).chr + position[1], position[0].next.next + position[1]]
297
+ remove_out_of_bounds(spaces_near_king(position) + castle_moves)
298
+ end
299
+
300
+ def moves_for_knight(position)
301
+ column = position[0].ord
302
+ row = position[1].to_i
303
+
304
+ moves = [
305
+ (column - 2).chr + (row + 1).to_s,
306
+ (column - 2).chr + (row - 1).to_s,
307
+ (column + 2).chr + (row + 1).to_s,
308
+ (column + 2).chr + (row - 1).to_s,
309
+ (column - 1).chr + (row + 2).to_s,
310
+ (column - 1).chr + (row - 2).to_s,
311
+ (column + 1).chr + (row + 2).to_s,
312
+ (column + 1).chr + (row - 2).to_s
313
+ ]
314
+
315
+ remove_out_of_bounds(moves)
316
+ end
317
+
318
+ def remove_out_of_bounds(moves)
319
+ moves.select { |move| ('a'..'h').include?(move[0]) && ('1'..'8').include?(move[1..-1]) }
320
+ end
321
+
322
+ def moves_for_pawn(pawn)
323
+ column = pawn.position[0].ord
324
+ row = pawn.color == 'w' ? pawn.position[1].to_i + 1 : pawn.position[1].to_i - 1
325
+
326
+ moves = [
327
+ (column - 1).chr + row.to_s,
328
+ (column + 1).chr + row.to_s,
329
+ column.chr + row.to_s
330
+ ]
331
+
332
+ if pawn.color == 'w' && pawn.position[1] == '2' || pawn.color == 'b' && pawn.position[1] == '7'
333
+ two_forward = pawn.color == 'w' ? row + 1 : row - 1
334
+ moves << column.chr + two_forward.to_s
335
+ end
336
+ remove_out_of_bounds(moves)
337
+ end
338
+
339
+ def moves_horizontal(position)
340
+ possible_moves = []
341
+ column = 'a'
342
+ row = position[1]
343
+
344
+ 8.times do
345
+ possible_moves << column + row unless column == position[0]
346
+ column = column.next
347
+ end
348
+
349
+ possible_moves
350
+ end
351
+
352
+ def moves_vertical(position)
353
+ possible_moves = []
354
+ column = position[0]
355
+ row = '1'
356
+
357
+ 8.times do
358
+ possible_moves << column + row unless row == position[1]
359
+ row = row.next
360
+ end
361
+
362
+ possible_moves
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,24 @@
1
+ require_relative './constants/square_key'
2
+
3
+ module ChessValidator
4
+ class Piece
5
+ attr_accessor :position, :piece_type, :color, :square_index, :valid_moves
6
+
7
+ def initialize(char, square_index)
8
+ @piece_type = char
9
+ @square_index = square_index
10
+ @color = get_color(char)
11
+ @position = get_position(square_index)
12
+ @enemy_targets = []
13
+ @valid_moves = []
14
+ end
15
+
16
+ def get_position(square_index)
17
+ SQUARE_KEY[square_index]
18
+ end
19
+
20
+ def get_color(char)
21
+ char == char.downcase ? 'b' : 'w'
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chess_validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Charles Ellison
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'Documentation: https://github.com/chadellison/chess_validator'
14
+ email: chad.ellison0123@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/board_logic.rb
20
+ - lib/chess_validator.rb
21
+ - lib/constants/square_key.rb
22
+ - lib/move_logic.rb
23
+ - lib/piece.rb
24
+ homepage: https://rubygems.org/search?utf8=%E2%9C%93&query=chess_validator
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.1.3
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: A chess move validator
47
+ test_files: []