chessmate 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|