pgn 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|