chessmate 0.1.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/lib/chessmate.rb +226 -0
- data/lib/helpers/notation_parser.rb +24 -0
- data/lib/pieces/bishop.rb +12 -0
- data/lib/pieces/king.rb +9 -0
- data/lib/pieces/knight.rb +12 -0
- data/lib/pieces/pawn.rb +34 -0
- data/lib/pieces/piece.rb +76 -0
- data/lib/pieces/queen.rb +17 -0
- data/lib/pieces/rook.rb +16 -0
- data/rspec.txt +88826 -0
- data/spec/chessmate_spec.rb +975 -0
- data/spec/notation_parser_spec.rb +44 -0
- data/spec/piece_spec.rb +96 -0
- data/spec/spec_helper.rb +101 -0
- metadata +33 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6be20b4d6b07a0f81f64db1d90580456b3f9c8cfc516ede8a669139f473640d5
|
|
4
|
+
data.tar.gz: fc4eee51ad0a83400cc605c111a012ca37b02f54cf9eafb4dc3b9b054c72a917
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77431e006f69ea5c8aaa3788d2faaa8a08e919dc6462c6e621f7086d60ca5589dc2ceea3b3f056e6ae57ff6931425be7d912c87da886a416cdff55055b7efd0e
|
|
7
|
+
data.tar.gz: 9081e8d1c6f7d846c4f9a95696e981c9e18c0683ff4c39830a1853b80982b15f19787b6f690192e72dc311db14aa227f63411e400fa5825444299de4ffe2f588
|
data/lib/chessmate.rb
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
class ChessMate
|
|
2
|
+
require 'helpers/notation_parser'
|
|
3
|
+
require 'pieces/pawn'
|
|
4
|
+
require 'pieces/rook'
|
|
5
|
+
require 'pieces/bishop'
|
|
6
|
+
require 'pieces/knight'
|
|
7
|
+
require 'pieces/queen'
|
|
8
|
+
require 'pieces/king'
|
|
9
|
+
|
|
10
|
+
attr_reader :board, :turn, :in_check
|
|
11
|
+
|
|
12
|
+
def initialize(board=nil,turn=nil)
|
|
13
|
+
if board.nil?
|
|
14
|
+
@board =
|
|
15
|
+
[
|
|
16
|
+
['BR', 'BN', 'BB', 'BQ', 'BK', 'BB', 'BN', 'BR'],
|
|
17
|
+
['BP', 'BP', 'BP', 'BP', 'BP', 'BP', 'BP', 'BP'],
|
|
18
|
+
[nil, nil, nil, nil, nil, nil, nil, nil],
|
|
19
|
+
[nil, nil, nil, nil, nil, nil, nil, nil],
|
|
20
|
+
[nil, nil, nil, nil, nil, nil, nil, nil],
|
|
21
|
+
[nil, nil, nil, nil, nil, nil, nil, nil],
|
|
22
|
+
['WP', 'WP', 'WP', 'WP', 'WP', 'WP', 'WP', 'WP'],
|
|
23
|
+
['WR', 'WN', 'WB', 'WQ', 'WK', 'WB', 'WN', 'WR']
|
|
24
|
+
]
|
|
25
|
+
else
|
|
26
|
+
@board = board
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if turn.nil?
|
|
30
|
+
@turn = 1
|
|
31
|
+
else
|
|
32
|
+
@turn = turn
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@in_check = {
|
|
36
|
+
"white": false,
|
|
37
|
+
"black": false
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update(orig, dest=nil)
|
|
42
|
+
orig_y = orig[0]
|
|
43
|
+
orig_x = orig[1]
|
|
44
|
+
dest_y = dest[0]
|
|
45
|
+
dest_x = dest[1]
|
|
46
|
+
piece_type = @board[orig_y][orig_x]
|
|
47
|
+
|
|
48
|
+
# Hacky way of doing this. Will fail for en passant and castling.
|
|
49
|
+
@board[orig_y][orig_x] = nil
|
|
50
|
+
@board[dest_y][dest_x] = piece_type
|
|
51
|
+
|
|
52
|
+
@turn += 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def in_check?(board=nil)
|
|
56
|
+
board = board.nil? ? @board : board
|
|
57
|
+
wk_coords = bk_coords = nil
|
|
58
|
+
|
|
59
|
+
board.each_with_index do |row, y|
|
|
60
|
+
if row.include?("WK")
|
|
61
|
+
wk_coords = [y, row.index("WK")]
|
|
62
|
+
end
|
|
63
|
+
if row.include?("BK")
|
|
64
|
+
bk_coords = [y, row.index("BK")]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if wk_coords.nil? || bk_coords.nil?
|
|
69
|
+
return { "white": false, "black": false }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
wk_pos = NotationParser.encode_notation(wk_coords)
|
|
73
|
+
bk_pos = NotationParser.encode_notation(bk_coords)
|
|
74
|
+
|
|
75
|
+
white_in_check = black_in_check = false
|
|
76
|
+
board.each_with_index do |row, y|
|
|
77
|
+
row.each_with_index do |col, x|
|
|
78
|
+
if !col.nil?
|
|
79
|
+
piece_pos = NotationParser.encode_notation([y,x])
|
|
80
|
+
if col[0] == "W"
|
|
81
|
+
if move(piece_pos, bk_pos, true)
|
|
82
|
+
black_in_check = true
|
|
83
|
+
end
|
|
84
|
+
elsif col[0] == "B"
|
|
85
|
+
if move(piece_pos, wk_pos, true)
|
|
86
|
+
white_in_check = true
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
{ "white": white_in_check, "black": black_in_check }
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def move(orig, dest, test=false, test_board=nil)
|
|
98
|
+
orig_pos = NotationParser.parse_notation(orig)
|
|
99
|
+
dest_pos = NotationParser.parse_notation(dest)
|
|
100
|
+
|
|
101
|
+
if orig_pos.nil? || dest_pos.nil?
|
|
102
|
+
return false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
orig_y = orig_pos[0]
|
|
106
|
+
orig_x = orig_pos[1]
|
|
107
|
+
|
|
108
|
+
piece = @board[orig_y][orig_x]
|
|
109
|
+
piece_type = piece[1]
|
|
110
|
+
|
|
111
|
+
if piece[0].downcase() == "w"
|
|
112
|
+
piece_color = :white
|
|
113
|
+
elsif piece[0].downcase() == "b"
|
|
114
|
+
piece_color = :black
|
|
115
|
+
else
|
|
116
|
+
piece_color = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
valid_move = nil
|
|
120
|
+
board = test_board.nil? ? @board : test_board
|
|
121
|
+
case piece_type
|
|
122
|
+
when "P"
|
|
123
|
+
valid_move = Pawn.move_is_valid?(orig_pos,dest_pos,board)
|
|
124
|
+
when "R"
|
|
125
|
+
valid_move = Rook.move_is_valid?(orig_pos,dest_pos,board)
|
|
126
|
+
when "B"
|
|
127
|
+
valid_move = Bishop.move_is_valid?(orig_pos,dest_pos,board)
|
|
128
|
+
when "N"
|
|
129
|
+
valid_move = Knight.move_is_valid?(orig_pos,dest_pos,board)
|
|
130
|
+
when "Q"
|
|
131
|
+
valid_move = Queen.move_is_valid?(orig_pos,dest_pos,board)
|
|
132
|
+
when "K"
|
|
133
|
+
valid_move = King.move_is_valid?(orig_pos,dest_pos,board)
|
|
134
|
+
else
|
|
135
|
+
valid_move = false
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if !test
|
|
139
|
+
@in_check = self.in_check?
|
|
140
|
+
in_check_after_move = in_check_after_move?(orig_pos,dest_pos)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if valid_move && !test && !in_check_after_move
|
|
144
|
+
self.update(orig_pos, dest_pos)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
valid_move && !@in_check[piece_color] && !in_check_after_move
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def in_check_after_move?(orig, dest)
|
|
151
|
+
test_board = @board.map(&:dup)
|
|
152
|
+
|
|
153
|
+
orig_y = orig[0]
|
|
154
|
+
orig_x = orig[1]
|
|
155
|
+
dest_y = dest[0]
|
|
156
|
+
dest_x = dest[1]
|
|
157
|
+
piece = test_board[orig_y][orig_x]
|
|
158
|
+
|
|
159
|
+
# Hacky way of doing this. Will fail for en passant and castling.
|
|
160
|
+
test_board[orig_y][orig_x] = nil
|
|
161
|
+
test_board[dest_y][dest_x] = piece
|
|
162
|
+
|
|
163
|
+
piece_color = piece[0]
|
|
164
|
+
king = piece_color + "K"
|
|
165
|
+
|
|
166
|
+
king_coords = nil
|
|
167
|
+
test_board.each_with_index do |row, y|
|
|
168
|
+
if row.include?(king)
|
|
169
|
+
king_coords = [y, row.index(king)]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if king_coords.nil?
|
|
174
|
+
return false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
king_pos = NotationParser.encode_notation(king_coords)
|
|
178
|
+
|
|
179
|
+
test_board.each_with_index do |row, y|
|
|
180
|
+
row.each_with_index do |col, x|
|
|
181
|
+
if !col.nil? && col[0] != piece_color
|
|
182
|
+
piece_pos = NotationParser.encode_notation([y,x])
|
|
183
|
+
if move(piece_pos, king_pos, true, test_board)
|
|
184
|
+
return true
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def any_valid_moves?(color)
|
|
194
|
+
test_board = @board.map(&:dup)
|
|
195
|
+
|
|
196
|
+
test_board.each_with_index do |row, orig_y|
|
|
197
|
+
row.each_with_index do |col, orig_x|
|
|
198
|
+
if !col.nil? && col[0] == color
|
|
199
|
+
orig_pos = NotationParser.encode_notation([orig_y,orig_x])
|
|
200
|
+
8.times do |dest_x|
|
|
201
|
+
8.times do |dest_y|
|
|
202
|
+
dest_pos = NotationParser.encode_notation([dest_y, dest_x])
|
|
203
|
+
if move(orig_pos, dest_pos, true)
|
|
204
|
+
if !in_check_after_move?([orig_y,orig_x],[dest_y,dest_x])
|
|
205
|
+
return true
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
false
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def checkmate?(color)
|
|
218
|
+
piece_color = color.downcase == "w" ? :white : :black
|
|
219
|
+
!any_valid_moves?(color) && in_check?[piece_color]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def draw?(color)
|
|
223
|
+
piece_color = color.downcase == "w" ? :white : :black
|
|
224
|
+
!any_valid_moves?(color) && in_check?[piece_color] == false
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module NotationParser
|
|
2
|
+
def self.parse_notation(square)
|
|
3
|
+
col = square[0].downcase().ord - 97
|
|
4
|
+
row = 7 - ( square[1].to_i - 1 )
|
|
5
|
+
|
|
6
|
+
if col >= 0 && col < 8 && row >= 0 && row < 8
|
|
7
|
+
[row,col]
|
|
8
|
+
else
|
|
9
|
+
return nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.encode_notation(coords)
|
|
14
|
+
row, col = coords
|
|
15
|
+
|
|
16
|
+
if col >= 0 && col < 8 && row >= 0 && row < 8
|
|
17
|
+
col = (col + 97).chr
|
|
18
|
+
row = (8 - row).to_s
|
|
19
|
+
return col + row
|
|
20
|
+
else
|
|
21
|
+
return nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'pieces/piece'
|
|
2
|
+
|
|
3
|
+
class Bishop < Piece
|
|
4
|
+
def self.move_is_valid?(orig, dest, board)
|
|
5
|
+
not_obstructed = !self.is_obstructed?(orig, dest, board)
|
|
6
|
+
|
|
7
|
+
not_obstructed &&
|
|
8
|
+
( !self.destination_occupied?(dest, board) || self.is_capturable?(orig, dest, board) ) &&
|
|
9
|
+
(orig[0] - dest[0]).abs == (orig[1] - dest[1]).abs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
data/lib/pieces/king.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'pieces/piece'
|
|
2
|
+
|
|
3
|
+
class Knight < Piece
|
|
4
|
+
def self.move_is_valid?(orig, dest, board)
|
|
5
|
+
x_offset = (orig[0] - dest[0]).abs
|
|
6
|
+
y_offset = (orig[1] - dest[1]).abs
|
|
7
|
+
|
|
8
|
+
( !self.destination_occupied?(dest, board) || self.is_capturable?(orig, dest, board) ) &&
|
|
9
|
+
(x_offset == 2 && y_offset == 1) || (x_offset == 1 && y_offset == 2)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
data/lib/pieces/pawn.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'pieces/piece'
|
|
2
|
+
|
|
3
|
+
class Pawn < Piece
|
|
4
|
+
def self.move_is_valid?(orig, dest, board)
|
|
5
|
+
orig_y = orig[0]
|
|
6
|
+
orig_x = orig[1]
|
|
7
|
+
dest_y = dest[0]
|
|
8
|
+
dest_x = dest[1]
|
|
9
|
+
piece_type = board[orig_y][orig_x]
|
|
10
|
+
piece_color = piece_type[0].downcase()
|
|
11
|
+
|
|
12
|
+
if piece_color == "w"
|
|
13
|
+
direction = 1
|
|
14
|
+
elsif piece_color == "b"
|
|
15
|
+
direction = -1
|
|
16
|
+
else
|
|
17
|
+
return false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if self.is_capturable?(orig,dest,board) &&
|
|
21
|
+
(orig_x - dest_x).abs == 1 &&
|
|
22
|
+
(orig_y - dest_y) * direction == 1
|
|
23
|
+
return true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
not_obstructed = !self.is_obstructed?(orig, dest, board)
|
|
27
|
+
|
|
28
|
+
basic_move = ( (orig_y - dest_y) * direction == 1 && orig_x == dest_x )
|
|
29
|
+
move_double_on_first_turn = ( orig_y - dest_y == (2 * direction) ) && ( orig_x == dest_x )
|
|
30
|
+
|
|
31
|
+
move_double_on_first_turn && not_obstructed || basic_move
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
data/lib/pieces/piece.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
class Piece
|
|
2
|
+
def self.is_obstructed?(orig, dest, board)
|
|
3
|
+
orig_y = orig[0]
|
|
4
|
+
orig_x = orig[1]
|
|
5
|
+
dest_y = dest[0]
|
|
6
|
+
dest_x = dest[1]
|
|
7
|
+
|
|
8
|
+
if orig_y == dest_y
|
|
9
|
+
|
|
10
|
+
direction = orig_x > dest_x ? 1 : -1
|
|
11
|
+
( (orig_x - dest_x).abs - 1 ).times do |x|
|
|
12
|
+
test_pos = orig_x - (x * direction) - direction
|
|
13
|
+
if !board[orig_y][test_pos].nil?
|
|
14
|
+
return true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
return false
|
|
18
|
+
|
|
19
|
+
elsif orig_x == dest_x
|
|
20
|
+
|
|
21
|
+
direction = orig_y > dest_y ? 1 : -1
|
|
22
|
+
( (orig_y - dest_y).abs - 1 ).times do |y|
|
|
23
|
+
test_pos = orig_y - (y * direction) - direction
|
|
24
|
+
if !board[test_pos][orig_x].nil?
|
|
25
|
+
return true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return false
|
|
30
|
+
|
|
31
|
+
elsif (orig_y - dest_y).abs == (orig_x - dest_x).abs
|
|
32
|
+
|
|
33
|
+
x_direction = orig_x > dest_x ? 1 : -1
|
|
34
|
+
y_direction = orig_y > dest_y ? 1 : -1
|
|
35
|
+
|
|
36
|
+
( (orig_y - dest_y).abs - 1 ).times do |v|
|
|
37
|
+
test_y_pos = orig_y - (v * y_direction) - y_direction
|
|
38
|
+
test_x_pos = orig_x - (v * x_direction) - x_direction
|
|
39
|
+
if !board[test_y_pos][test_x_pos].nil?
|
|
40
|
+
return true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return false
|
|
45
|
+
|
|
46
|
+
else
|
|
47
|
+
return nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.is_capturable?(orig, dest, board)
|
|
52
|
+
orig_y = orig[0]
|
|
53
|
+
orig_x = orig[1]
|
|
54
|
+
dest_y = dest[0]
|
|
55
|
+
dest_x = dest[1]
|
|
56
|
+
orig_piece = board[orig_y][orig_x]
|
|
57
|
+
dest_piece = board[dest_y][dest_x]
|
|
58
|
+
|
|
59
|
+
if orig_piece && dest_piece
|
|
60
|
+
orig_piece_color = orig_piece[0]
|
|
61
|
+
dest_piece_color = dest_piece[0]
|
|
62
|
+
else
|
|
63
|
+
return false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
orig_piece_color != dest_piece_color
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.destination_occupied?(dest, board)
|
|
71
|
+
dest_y = dest[0]
|
|
72
|
+
dest_x = dest[1]
|
|
73
|
+
|
|
74
|
+
!board[dest_y][dest_x].nil?
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/pieces/queen.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'pieces/piece'
|
|
2
|
+
|
|
3
|
+
class Queen < Piece
|
|
4
|
+
def self.move_is_valid?(orig, dest, board)
|
|
5
|
+
|
|
6
|
+
not_obstructed = !self.is_obstructed?(orig, dest, board)
|
|
7
|
+
|
|
8
|
+
not_obstructed &&
|
|
9
|
+
( !self.destination_occupied?(dest, board) || self.is_capturable?(orig, dest, board) ) &&
|
|
10
|
+
(
|
|
11
|
+
(orig[0] - dest[0]).abs == (orig[1] - dest[1]).abs ||
|
|
12
|
+
orig[0] == dest[0] || orig[1] == dest[1]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
data/lib/pieces/rook.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'pieces/piece'
|
|
2
|
+
|
|
3
|
+
class Rook < Piece
|
|
4
|
+
def self.move_is_valid?(orig, dest, board)
|
|
5
|
+
|
|
6
|
+
not_obstructed = !self.is_obstructed?(orig, dest, board)
|
|
7
|
+
|
|
8
|
+
not_obstructed &&
|
|
9
|
+
( !self.destination_occupied?(dest, board) || self.is_capturable?(orig, dest, board) ) &&
|
|
10
|
+
(
|
|
11
|
+
orig[0] == dest[0] || orig[1] == dest[1]
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|