pgn 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +1 -0
- data/TODO.md +4 -0
- data/examples/immortal_game.pgn +17 -0
- data/lib/pgn.rb +22 -0
- data/lib/pgn/board.rb +183 -0
- data/lib/pgn/fen.rb +146 -0
- data/lib/pgn/game.rb +82 -0
- data/lib/pgn/move.rb +167 -0
- data/lib/pgn/move_calculator.rb +339 -0
- data/lib/pgn/parser.rb +119 -0
- data/lib/pgn/position.rb +129 -0
- data/lib/pgn/version.rb +3 -0
- data/pgn.gemspec +26 -0
- data/spec/fen_spec.rb +87 -0
- data/spec/game_spec.rb +13 -0
- data/spec/parser_spec.rb +14 -0
- data/spec/position_spec.rb +44 -0
- data/spec/spec_helper.rb +19 -0
- metadata +129 -0
data/lib/pgn/game.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module PGN
|
2
|
+
# {PGN::Game} holds all of the information about a game. It is either
|
3
|
+
# the result of parsing a PGN file, or created by hand.
|
4
|
+
#
|
5
|
+
# A {PGN::Game} has an interactive {#play} method, and can also return
|
6
|
+
# a list of positions in {PGN::Position} format or FEN.
|
7
|
+
#
|
8
|
+
# @!attribute tags
|
9
|
+
# @return [Hash<String, String>] metadata about the game
|
10
|
+
# @example
|
11
|
+
# game.tags #=> {"White" => "Kasparov", "Black" => "Deep Blue"}
|
12
|
+
#
|
13
|
+
# @!attribute moves
|
14
|
+
# @return [Array<String>] a list of the moves in standard algebraic
|
15
|
+
# notation
|
16
|
+
# @example
|
17
|
+
# game.moves #=> ["e4", "c5", "Nf3", "d6", "d4", "cxd4"]
|
18
|
+
#
|
19
|
+
# @!attribute result
|
20
|
+
# @return [String] the outcome of the game
|
21
|
+
# @example
|
22
|
+
# game.result #=> "1-0"
|
23
|
+
#
|
24
|
+
class Game
|
25
|
+
attr_accessor :tags, :moves, :result
|
26
|
+
|
27
|
+
LEFT = "a"
|
28
|
+
RIGHT = "d"
|
29
|
+
EXIT = "\u{0003}"
|
30
|
+
|
31
|
+
# @param moves [Array<String>] a list of moves in SAN
|
32
|
+
# @param tags [Hash<String, String>] metadata about the game
|
33
|
+
# @param result [String] the outcome of the game
|
34
|
+
#
|
35
|
+
def initialize(moves, tags = nil, result = nil)
|
36
|
+
self.moves = moves
|
37
|
+
self.tags = tags
|
38
|
+
self.result = result
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Array<PGN::Position>] list of the {PGN::Position}s in the game
|
42
|
+
#
|
43
|
+
def positions
|
44
|
+
@positions ||= begin
|
45
|
+
position = PGN::Position.start
|
46
|
+
arr = [position]
|
47
|
+
self.moves.each do |move|
|
48
|
+
new_pos = position.move(move)
|
49
|
+
arr << new_pos
|
50
|
+
position = new_pos
|
51
|
+
end
|
52
|
+
arr
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Array<String>] list of the fen representations of the positions
|
57
|
+
#
|
58
|
+
def fen_list
|
59
|
+
self.positions.map {|p| p.to_fen.inspect }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Interactively step through the game
|
63
|
+
#
|
64
|
+
# Use +d+ to move forward, +a+ to move backward, and +^C+ to exit.
|
65
|
+
#
|
66
|
+
def play
|
67
|
+
index = 0
|
68
|
+
loop do
|
69
|
+
puts "\e[H\e[2J"
|
70
|
+
puts self.positions[index].inspect
|
71
|
+
case STDIN.getch
|
72
|
+
when LEFT
|
73
|
+
index -= 1 if index > 0
|
74
|
+
when RIGHT
|
75
|
+
index += 1 if index < self.moves.length
|
76
|
+
when EXIT
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/pgn/move.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
module PGN
|
2
|
+
# {PGN::Move} knows how to parse a move string in standard algebraic
|
3
|
+
# notation to extract all relevant information.
|
4
|
+
#
|
5
|
+
# @see http://en.wikipedia.org/wiki/Algebraic_notation_(chess) Standard
|
6
|
+
# Algebraic Notation
|
7
|
+
#
|
8
|
+
# @!attribute san
|
9
|
+
# @return [String] the move string
|
10
|
+
# @example
|
11
|
+
# move1.san #=> "O-O-O+"
|
12
|
+
# move2.san #=> "Raxe1"
|
13
|
+
# move3.san #=> "e8=Q#"
|
14
|
+
#
|
15
|
+
# @!attribute player
|
16
|
+
# @return [Symbol] the current player
|
17
|
+
# @example
|
18
|
+
# move.player #=> :white
|
19
|
+
#
|
20
|
+
# @!attribute piece
|
21
|
+
# @return [String, nil] the piece being moved
|
22
|
+
# @example
|
23
|
+
# move1.piece #=> "Q"
|
24
|
+
# move2.piece #=> "r"
|
25
|
+
# @note this is nil for castling
|
26
|
+
# @note uppercase represents white, lowercase represents black
|
27
|
+
#
|
28
|
+
# @!attribute destination
|
29
|
+
# @return [String, nil] the destination square of the piece
|
30
|
+
# @example
|
31
|
+
# move.destination #=> "e4"
|
32
|
+
#
|
33
|
+
# @!attribute promotion
|
34
|
+
# @return [String, nil] the promotion piece, if applicable
|
35
|
+
#
|
36
|
+
# @!attribute check
|
37
|
+
# @return [String, nil] whether the move results in check or mate
|
38
|
+
# @example
|
39
|
+
# move1.check #=> "+"
|
40
|
+
# move2.check #=> "#"
|
41
|
+
#
|
42
|
+
# @!attribute capture
|
43
|
+
# @return [String, nil] whether the move is a capture
|
44
|
+
# @example
|
45
|
+
# move.capture #=> "x"
|
46
|
+
#
|
47
|
+
# @!attribute disambiguation
|
48
|
+
# @return [String, nil] the disambiguation string if there is one
|
49
|
+
# @example
|
50
|
+
# move.disambiguation #=> "3"
|
51
|
+
#
|
52
|
+
# @!attribute castle
|
53
|
+
# @return [String, nil] the castle string if applicable
|
54
|
+
# @example
|
55
|
+
# move1.castle #=> "O-O-O"
|
56
|
+
# move2.castle #=> "O-O"
|
57
|
+
#
|
58
|
+
class Move
|
59
|
+
attr_accessor :san, :player
|
60
|
+
attr_accessor :piece, :destination, :promotion, :check, :capture, :disambiguation, :castle
|
61
|
+
|
62
|
+
# A regular expression for matching moves in standard algebraic
|
63
|
+
# notation
|
64
|
+
#
|
65
|
+
SAN_REGEX = %r{
|
66
|
+
(?<piece> [BKNQR] ){0}
|
67
|
+
(?<destination> [a-h][1-8] ){0}
|
68
|
+
(?<promotion> =[BNQR] ){0}
|
69
|
+
(?<check> [#+] ){0}
|
70
|
+
(?<capture> x ){0}
|
71
|
+
(?<disambiguation> [a-h]?[1-8]? ){0}
|
72
|
+
|
73
|
+
(?<castle> O-O(-O)? ){0}
|
74
|
+
|
75
|
+
(?<normal>
|
76
|
+
\g<piece>?
|
77
|
+
\g<disambiguation>
|
78
|
+
\g<capture>?
|
79
|
+
\g<destination>
|
80
|
+
\g<promotion>?
|
81
|
+
){0}
|
82
|
+
|
83
|
+
\A (\g<castle> | \g<normal>) \g<check>? \z
|
84
|
+
}x
|
85
|
+
|
86
|
+
# @param move [String] the move in SAN
|
87
|
+
# @param player [Symbol] the player making the move
|
88
|
+
# @example
|
89
|
+
# PGN::Move.new("e4", :white)
|
90
|
+
#
|
91
|
+
def initialize(move, player)
|
92
|
+
self.player = player
|
93
|
+
self.san = move
|
94
|
+
|
95
|
+
match = move.match(SAN_REGEX)
|
96
|
+
|
97
|
+
match.names.each do |name|
|
98
|
+
if self.respond_to?(name)
|
99
|
+
self.send("#{name}=", match[name])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def piece=(val)
|
105
|
+
return if san.match("O-O")
|
106
|
+
|
107
|
+
val ||= "P"
|
108
|
+
@piece = self.black? ?
|
109
|
+
val.downcase :
|
110
|
+
val
|
111
|
+
end
|
112
|
+
|
113
|
+
def promotion=(val)
|
114
|
+
if val
|
115
|
+
val.downcase! if self.black?
|
116
|
+
@promotion = val.delete("=")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def capture=(val)
|
121
|
+
@capture = !!val
|
122
|
+
end
|
123
|
+
|
124
|
+
def disambiguation=(val)
|
125
|
+
@disambiguation = (val == "" ? nil : val)
|
126
|
+
end
|
127
|
+
|
128
|
+
def castle=(val)
|
129
|
+
if val
|
130
|
+
@castle = "K" if val == "O-O"
|
131
|
+
@castle = "Q" if val == "O-O-O"
|
132
|
+
@castle.downcase! if self.black?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Boolean] whether the move results in check
|
137
|
+
#
|
138
|
+
def check?
|
139
|
+
self.check == "+"
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Boolean] whether the move results in checkmate
|
143
|
+
#
|
144
|
+
def checkmate?
|
145
|
+
self.check == "#"
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Boolean] whether it's white's turn
|
149
|
+
#
|
150
|
+
def white?
|
151
|
+
self.player == :white
|
152
|
+
end
|
153
|
+
|
154
|
+
# @return [Boolean] whether it's black's turn
|
155
|
+
#
|
156
|
+
def black?
|
157
|
+
self.player == :black
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return [Boolean] whether the piece being moved is a pawn
|
161
|
+
#
|
162
|
+
def pawn?
|
163
|
+
['P', 'p'].include?(self.piece)
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
module PGN
|
2
|
+
# {PGN::MoveCalculator} is responsible for computing all of the ways that a
|
3
|
+
# specific move changes the current position. This includes which squares on
|
4
|
+
# the board need to be updated, new castling restrictions, the en passant
|
5
|
+
# square and whether to update fullmove and halfmove counters.
|
6
|
+
#
|
7
|
+
# @!attribute board
|
8
|
+
# @return [PGN::Board] the current board
|
9
|
+
#
|
10
|
+
# @!attribute move
|
11
|
+
# @return [PGN::Move] the current move
|
12
|
+
#
|
13
|
+
# @!attribute origin
|
14
|
+
# @return [String, nil] the origin square in SAN
|
15
|
+
#
|
16
|
+
class MoveCalculator
|
17
|
+
# Specifies the movement of pieces who are allowed to move in a
|
18
|
+
# given direction until they reach an obstacle or the end of the
|
19
|
+
# board.
|
20
|
+
#
|
21
|
+
DIRECTIONS = {
|
22
|
+
'b' => [[ 1, 1], [-1, 1], [-1, -1], [ 1, -1]],
|
23
|
+
'r' => [[-1, 0], [ 1, 0], [ 0, -1], [ 0, 1]],
|
24
|
+
'q' => [[ 1, 1], [-1, 1], [-1, -1], [ 1, -1],
|
25
|
+
[-1, 0], [ 1, 0], [ 0, -1], [ 0, 1]],
|
26
|
+
}
|
27
|
+
|
28
|
+
# Specifies the movement of pieces that have a limited set of moves
|
29
|
+
# they are allowed to make.
|
30
|
+
#
|
31
|
+
MOVES = {
|
32
|
+
'k' => [[-1, -1], [ 0, -1], [ 1, -1], [ 1, 0],
|
33
|
+
[ 1, 1], [ 0, 1], [-1, 1], [-1, 0]],
|
34
|
+
'n' => [[-1, -2], [-1, 2], [ 1, -2], [ 1, 2],
|
35
|
+
[-2, -1], [ 2, -1], [-2, 1], [ 2, 1]],
|
36
|
+
}
|
37
|
+
|
38
|
+
# Specifies possible pawn movements. It may seem backwards since it is
|
39
|
+
# used to compute the origin square and not the destination.
|
40
|
+
#
|
41
|
+
PAWN_MOVES = {
|
42
|
+
'P' => {
|
43
|
+
capture: [[-1, -1], [ 1, -1]],
|
44
|
+
normal: [[ 0, -1]],
|
45
|
+
double: [[ 0, -2]],
|
46
|
+
},
|
47
|
+
'p' => {
|
48
|
+
capture: [[-1, 1], [ 1, 1]],
|
49
|
+
normal: [[ 0, 1]],
|
50
|
+
double: [[ 0, 2]],
|
51
|
+
},
|
52
|
+
}
|
53
|
+
|
54
|
+
# The squares to update for each possible castling move.
|
55
|
+
#
|
56
|
+
CASTLING = {
|
57
|
+
"Q" => {
|
58
|
+
"a1" => nil,
|
59
|
+
"c1" => "K",
|
60
|
+
"d1" => "R",
|
61
|
+
"e1" => nil,
|
62
|
+
},
|
63
|
+
"K" => {
|
64
|
+
"e1" => nil,
|
65
|
+
"f1" => "R",
|
66
|
+
"g1" => "K",
|
67
|
+
"h1" => nil,
|
68
|
+
},
|
69
|
+
"q" => {
|
70
|
+
"a8" => nil,
|
71
|
+
"c8" => "k",
|
72
|
+
"d8" => "r",
|
73
|
+
"e8" => nil,
|
74
|
+
},
|
75
|
+
"k" => {
|
76
|
+
"e8" => nil,
|
77
|
+
"f8" => "r",
|
78
|
+
"g8" => "k",
|
79
|
+
"h8" => nil,
|
80
|
+
},
|
81
|
+
}
|
82
|
+
|
83
|
+
attr_accessor :board
|
84
|
+
attr_accessor :move
|
85
|
+
attr_accessor :origin
|
86
|
+
|
87
|
+
# @param board [PGN::Board] the current board
|
88
|
+
# @param move [PGN::Move] the current move
|
89
|
+
#
|
90
|
+
def initialize(board, move)
|
91
|
+
self.board = board
|
92
|
+
self.move = move
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [PGN::Board] the board after the move is made
|
96
|
+
#
|
97
|
+
def result_board
|
98
|
+
compute_origin
|
99
|
+
|
100
|
+
new_board = self.board.dup
|
101
|
+
new_board.change!(changes)
|
102
|
+
|
103
|
+
new_board
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Array<String>] which castling moves are no longer available
|
107
|
+
#
|
108
|
+
def castling_restrictions
|
109
|
+
compute_origin
|
110
|
+
|
111
|
+
restrict = case self.move.piece
|
112
|
+
when "K" then "KQ"
|
113
|
+
when "k" then "kq"
|
114
|
+
when "R"
|
115
|
+
{"a1" => "Q", "h1" => "K"}[self.origin]
|
116
|
+
when "r"
|
117
|
+
{"a8" => "q", "h8" => "k"}[self.origin]
|
118
|
+
end
|
119
|
+
|
120
|
+
restrict = "KQ" if ['K', 'Q'].include? move.castle
|
121
|
+
restrict = "kq" if ['k', 'q'].include? move.castle
|
122
|
+
|
123
|
+
restrict ||= ''
|
124
|
+
|
125
|
+
restrict.split('')
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Boolean] whether to increment the halfmove clock
|
129
|
+
#
|
130
|
+
def increment_halfmove?
|
131
|
+
!(self.move.capture || self.move.pawn?)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [Boolean] whether to increment the fullmove counter
|
135
|
+
#
|
136
|
+
def increment_fullmove?
|
137
|
+
self.move.black?
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [String, nil] the en passant square if applicable
|
141
|
+
#
|
142
|
+
def en_passant_square
|
143
|
+
compute_origin
|
144
|
+
|
145
|
+
return nil if move.castle
|
146
|
+
|
147
|
+
if self.move.pawn? && (self.origin[1].to_i - self.move.destination[1].to_i).abs == 2
|
148
|
+
self.move.white? ?
|
149
|
+
self.origin[0] + '3' :
|
150
|
+
self.origin[0] + '6'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def changes
|
157
|
+
compute_origin
|
158
|
+
|
159
|
+
changes = {}
|
160
|
+
changes.merge!(CASTLING[self.move.castle]) if self.move.castle
|
161
|
+
changes.merge!(
|
162
|
+
self.origin => nil,
|
163
|
+
self.move.destination => self.move.piece,
|
164
|
+
en_passant_capture => nil,
|
165
|
+
)
|
166
|
+
if self.move.promotion
|
167
|
+
changes[self.move.destination] = self.move.promotion
|
168
|
+
end
|
169
|
+
|
170
|
+
changes.reject! {|key, _| key.nil? }
|
171
|
+
|
172
|
+
changes
|
173
|
+
end
|
174
|
+
|
175
|
+
# Using the current position and move, figure out where the piece
|
176
|
+
# came from.
|
177
|
+
#
|
178
|
+
def compute_origin
|
179
|
+
return nil if move.castle
|
180
|
+
|
181
|
+
@origin ||= begin
|
182
|
+
possibilities = case move.piece
|
183
|
+
when /[brq]/i then direction_origins
|
184
|
+
when /[kn]/i then move_origins
|
185
|
+
when /p/i then pawn_origins
|
186
|
+
end
|
187
|
+
|
188
|
+
if possibilities.length > 1
|
189
|
+
possibilities = disambiguate(possibilities)
|
190
|
+
end
|
191
|
+
|
192
|
+
self.board.position_for(possibilities.first)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# From the destination square, move in each direction stopping if we
|
197
|
+
# reach the end of the board. If we encounter a piece, add it to the
|
198
|
+
# list of origin possibilities if it is the moving piece, or else
|
199
|
+
# check the next direction.
|
200
|
+
#
|
201
|
+
def direction_origins
|
202
|
+
directions = DIRECTIONS[move.piece.downcase]
|
203
|
+
possibilities = []
|
204
|
+
|
205
|
+
directions.each do |dir|
|
206
|
+
piece, square = first_piece(destination_coords, dir)
|
207
|
+
possibilities << square if piece == self.move.piece
|
208
|
+
end
|
209
|
+
|
210
|
+
possibilities
|
211
|
+
end
|
212
|
+
|
213
|
+
# From the destination square, make each move. If it is a valid
|
214
|
+
# square and matches the moving piece, add it to the list of origin
|
215
|
+
# possibilities.
|
216
|
+
#
|
217
|
+
def move_origins(moves = nil)
|
218
|
+
moves ||= MOVES[move.piece.downcase]
|
219
|
+
possibilities = []
|
220
|
+
file, rank = destination_coords
|
221
|
+
|
222
|
+
moves.each do |i, j|
|
223
|
+
f = file + i
|
224
|
+
r = rank + j
|
225
|
+
|
226
|
+
if valid_square?(f, r) && self.board.at(f, r) == move.piece
|
227
|
+
possibilities << [f, r]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
possibilities
|
232
|
+
end
|
233
|
+
|
234
|
+
# Computes the possbile pawn origins based on the destination square
|
235
|
+
# and whether or not the move is a capture.
|
236
|
+
#
|
237
|
+
def pawn_origins
|
238
|
+
_, rank = destination_coords
|
239
|
+
double_rank = (rank == 3 && self.move.white?) || (rank == 4 && self.move.black?)
|
240
|
+
|
241
|
+
pawn_moves = PAWN_MOVES[self.move.piece]
|
242
|
+
|
243
|
+
moves = self.move.capture ? pawn_moves[:capture] : pawn_moves[:normal]
|
244
|
+
moves += pawn_moves[:double] if double_rank
|
245
|
+
|
246
|
+
move_origins(moves)
|
247
|
+
end
|
248
|
+
|
249
|
+
def disambiguate(possibilities)
|
250
|
+
possibilities = disambiguate_san(possibilities)
|
251
|
+
possibilities = disambiguate_pawns(possibilities) if possibilities.length > 1
|
252
|
+
possibilities = disambiguate_discovered_check(possibilities) if possibilities.length > 1
|
253
|
+
|
254
|
+
possibilities
|
255
|
+
end
|
256
|
+
|
257
|
+
# Try to disambiguate based on the standard algebraic notation.
|
258
|
+
#
|
259
|
+
def disambiguate_san(possibilities)
|
260
|
+
move.disambiguation ?
|
261
|
+
possibilities.select {|p| self.board.position_for(p).match(move.disambiguation) } :
|
262
|
+
possibilities
|
263
|
+
end
|
264
|
+
|
265
|
+
# A pawn can't move two spaces if there is a pawn in front of it.
|
266
|
+
#
|
267
|
+
def disambiguate_pawns(possibilities)
|
268
|
+
self.move.piece.match(/p/i) && !self.move.capture ?
|
269
|
+
possibilities.reject {|p| self.board.position_for(p).match(/2|7/) } :
|
270
|
+
possibilities
|
271
|
+
end
|
272
|
+
|
273
|
+
# A piece can't move if it would result in a discovered check.
|
274
|
+
#
|
275
|
+
def disambiguate_discovered_check(possibilities)
|
276
|
+
DIRECTIONS.each do |attacking_piece, directions|
|
277
|
+
attacking_piece = attacking_piece.upcase if self.move.black?
|
278
|
+
|
279
|
+
directions.each do |dir|
|
280
|
+
piece, square = first_piece(king_position, dir)
|
281
|
+
next unless piece == self.move.piece && possibilities.include?(square)
|
282
|
+
|
283
|
+
piece, _ = first_piece(square, dir)
|
284
|
+
possibilities.reject! {|p| p == square } if piece == attacking_piece
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
possibilities
|
289
|
+
end
|
290
|
+
|
291
|
+
def first_piece(from, direction)
|
292
|
+
file, rank = from
|
293
|
+
i, j = direction
|
294
|
+
|
295
|
+
piece = nil
|
296
|
+
|
297
|
+
while valid_square?(file += i, rank += j)
|
298
|
+
break if piece = self.board.at(file, rank)
|
299
|
+
end
|
300
|
+
|
301
|
+
[piece, [file, rank]]
|
302
|
+
end
|
303
|
+
|
304
|
+
# If the move is a capture and there is no piece on the
|
305
|
+
# destination square, it must be an en passant capture.
|
306
|
+
#
|
307
|
+
def en_passant_capture
|
308
|
+
return nil if self.move.castle
|
309
|
+
|
310
|
+
if !self.board.at(self.move.destination) && self.move.capture
|
311
|
+
self.move.destination[0] + self.origin[1]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def king_position
|
316
|
+
king = self.move.white? ? 'K' : 'k'
|
317
|
+
|
318
|
+
coords = nil
|
319
|
+
0.upto(7) do |file|
|
320
|
+
0.upto(7) do |rank|
|
321
|
+
if self.board.at(file, rank) == king
|
322
|
+
coords = [file, rank]
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
coords
|
328
|
+
end
|
329
|
+
|
330
|
+
def valid_square?(file, rank)
|
331
|
+
(0..7) === file && (0..7) === rank
|
332
|
+
end
|
333
|
+
|
334
|
+
def destination_coords
|
335
|
+
self.board.coordinates_for(self.move.destination)
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
end
|