pgn2fen 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +63 -0
- data/README.md +42 -0
- data/Rakefile +14 -0
- data/TODO +4 -0
- data/VERSION +1 -0
- data/lib/pgn2fen.rb +1053 -0
- data/pgn2fen.gemspec +24 -0
- data/test/pgn2fen_test.rb +143 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 45d55e81c471a04b01d22f91d842f4add77328a9
|
4
|
+
data.tar.gz: 60686f31b714c74a3c54885a5316fd4b0fb05fb0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7349dce60140d45dc6b8b3a333b9068ebfb8dec6b66d4db7f230ba22923698300af70f25b21d234bc11a3c35cb9c83ea4cd2f0921d3d9b86ff75e892bbb5d007
|
7
|
+
data.tar.gz: 30dee78b92f12148fcc05dde633d7926b478a41b72bd4d9659b1c3ca36473611c6e0305dc1bf998ab6fc14278b19cddd2866bf927d264310de971c72a9e34040
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
== Pgn2Fen License
|
2
|
+
|
3
|
+
Pgn2Fen is copyright Vinay Doma (vinay.doma@gmail.com) and made available
|
4
|
+
under the terms of Ruby's license, included below for your convenience.
|
5
|
+
|
6
|
+
== Ruby's License
|
7
|
+
|
8
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
9
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
10
|
+
(see the file GPL), or the conditions below:
|
11
|
+
|
12
|
+
1. You may make and give away verbatim copies of the source form of the
|
13
|
+
software without restriction, provided that you duplicate all of the
|
14
|
+
original copyright notices and associated disclaimers.
|
15
|
+
|
16
|
+
2. You may modify your copy of the software in any way, provided that
|
17
|
+
you do at least ONE of the following:
|
18
|
+
|
19
|
+
a) place your modifications in the Public Domain or otherwise
|
20
|
+
make them Freely Available, such as by posting said
|
21
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
22
|
+
the author to include your modifications in the software.
|
23
|
+
|
24
|
+
b) use the modified software only within your corporation or
|
25
|
+
organization.
|
26
|
+
|
27
|
+
c) give non-standard binaries non-standard names, with
|
28
|
+
instructions on where to get the original software distribution.
|
29
|
+
|
30
|
+
d) make other distribution arrangements with the author.
|
31
|
+
|
32
|
+
3. You may distribute the software in object code or binary form,
|
33
|
+
provided that you do at least ONE of the following:
|
34
|
+
|
35
|
+
a) distribute the binaries and library files of the software,
|
36
|
+
together with instructions (in the manual page or equivalent)
|
37
|
+
on where to get the original distribution.
|
38
|
+
|
39
|
+
b) accompany the distribution with the machine-readable source of
|
40
|
+
the software.
|
41
|
+
|
42
|
+
c) give non-standard binaries non-standard names, with
|
43
|
+
instructions on where to get the original software distribution.
|
44
|
+
|
45
|
+
d) make other distribution arrangements with the author.
|
46
|
+
|
47
|
+
4. You may modify and include the part of the software into any other
|
48
|
+
software (possibly commercial). But some files in the distribution
|
49
|
+
are not written by the author, so that they are not under these terms.
|
50
|
+
|
51
|
+
For the list of those files and their copying conditions, see the
|
52
|
+
file LEGAL.
|
53
|
+
|
54
|
+
5. The scripts and library files supplied as input to or produced as
|
55
|
+
output from the software do not automatically fall under the
|
56
|
+
copyright of the software, but belong to whomever generated them,
|
57
|
+
and may be sold commercially, and may be aggregated with this
|
58
|
+
software.
|
59
|
+
|
60
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
61
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
62
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
63
|
+
PURPOSE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# pgn2fen
|
2
|
+
|
3
|
+
Converts a single game chess PGN to an array of FEN strings.
|
4
|
+
The FEN follows the specification as listed on [Forsyth–Edwards Notation](http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation).
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
require 'pgn2fen'
|
10
|
+
fen_array = Pgn2Fen::Game.new(pgn_string).parse_pgn().fen_array
|
11
|
+
```
|
12
|
+
|
13
|
+
PGN header information is available in the Game object.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'pgn2fen'
|
17
|
+
game = Pgn2Fen::Game.new(pgn_string)
|
18
|
+
game.parse_pgn()
|
19
|
+
#FEN Array
|
20
|
+
puts game.fen_array
|
21
|
+
|
22
|
+
#PGN Header
|
23
|
+
puts game.event
|
24
|
+
puts game.site
|
25
|
+
puts game.date
|
26
|
+
puts game.eventdate
|
27
|
+
puts game.round
|
28
|
+
puts game.white
|
29
|
+
puts game.black
|
30
|
+
puts game.whiteelo
|
31
|
+
puts game.blackelo
|
32
|
+
puts game.result
|
33
|
+
puts game.eco
|
34
|
+
puts game.plycount
|
35
|
+
puts game.fen
|
36
|
+
```
|
37
|
+
|
38
|
+
## Notes
|
39
|
+
|
40
|
+
All side lines are ignored - only main game is converted.
|
41
|
+
Only a single game PGN is supported right now.
|
42
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
# Test --------------------------------------------------------------------
|
5
|
+
desc 'Run the test suite'
|
6
|
+
task :test do
|
7
|
+
Rake::TestTask.new do |t|
|
8
|
+
t.verbose = true
|
9
|
+
t.warning = true
|
10
|
+
t.pattern = 'test/**/*_test.rb'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: :test
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/lib/pgn2fen.rb
ADDED
@@ -0,0 +1,1053 @@
|
|
1
|
+
require 'set'
|
2
|
+
#require 'byebug'
|
3
|
+
|
4
|
+
module Pgn2Fen
|
5
|
+
|
6
|
+
class Game
|
7
|
+
# constants
|
8
|
+
START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
9
|
+
HEADER_KEY_REGEX = /^\[([A-Z][A-Za-z]*)\s.*\]$/
|
10
|
+
HEADER_VALUE_REGEX = /^\[[A-Za-z]+\s"(.*)"\]$/
|
11
|
+
OPEN_PAREN = "("
|
12
|
+
CLOSE_PAREN = ")"
|
13
|
+
OPEN_BRACE = "{"
|
14
|
+
CLOSE_BRACE = "}"
|
15
|
+
|
16
|
+
# attr_accessors
|
17
|
+
# attr_accessor :event, :site, :date, :eventdate, :round, :white, :black, :whiteelo, :blackelo, :result, :eco, :plycount, :fen
|
18
|
+
attr_accessor :pgn, :fen_array
|
19
|
+
attr_accessor :can_white_castle_kingside, :can_black_castle_kingside, :can_black_castle_queenside, :potential_enpassent_ply, :halfmove, :fullmove, :promotion, :promotion_piece, :board_color_from_fen
|
20
|
+
|
21
|
+
# Board Representation
|
22
|
+
# (a8, 0) (b8, 1) (c8, 2) (d8, 3) (e8, 4) (f8, 5) (g8, 6) (h8, 7)
|
23
|
+
# (a7, 8) (b7, 9) (c7,10) (d7,11) (e7,12) (f7,13) (g7,14) (h7,15)
|
24
|
+
# (a6,16) (b6,17) (c6,18) (d6,19) (e6,20) (f6,21) (g6,22) (h6,23)
|
25
|
+
# (a5,24) (b5,25) (c5,26) (d5,27) (e5,28) (f5,29) (g5,30) (h5,31)
|
26
|
+
# (a4,32) (b4,33) (c4,34) (d4,35) (e4,36) (f4,37) (g4,38) (h4,39)
|
27
|
+
# (a3,40) (b3,41) (c3,42) (d3,43) (e3,44) (f3,45) (g3,46) (h3,47)
|
28
|
+
# (a2,48) (b2,49) (c2,50) (d2,51) (e2,52) (f2,53) (g2,54) (h2,55)
|
29
|
+
# (a1,56) (b1,57) (c1,58) (d1,59) (e1,60) (f1,61) (g1,62) (h1,63)
|
30
|
+
|
31
|
+
# static initializers
|
32
|
+
@@pgn_squares = ["a8","b8","c8","d8","e8","f8","g8","h8","a7","b7","c7","d7","e7","f7","g7","h7","a6","b6","c6","d6","e6","f6","g6","h6","a5","b5","c5","d5","e5","f5","g5","h5","a4","b4","c4","d4","e4","f4","g4","h4","a3","b3","c3","d3","e3","f3","g3","h3","a2","b2","c2","d2","e2","f2","g2","h2","a1","b1","c1","d1","e1","f1","g1","h1"];
|
33
|
+
|
34
|
+
@@number_squares = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
|
35
|
+
|
36
|
+
@@pgn_number_hash = Hash[@@pgn_squares.zip(@@number_squares)]
|
37
|
+
@@number_pgn_hash = Hash[@@number_squares.zip(@@pgn_squares)]
|
38
|
+
|
39
|
+
@@aFile = Set.new; @@bFile = Set.new; @@cFile = Set.new; @@dFile = Set.new;
|
40
|
+
@@eFile = Set.new; @@fFile = Set.new; @@gFile = Set.new; @@hFile = Set.new;
|
41
|
+
|
42
|
+
(0...8).each {|i|
|
43
|
+
@@aFile.add(i * 8 + 0)
|
44
|
+
@@bFile.add(i * 8 + 1)
|
45
|
+
@@cFile.add(i * 8 + 2)
|
46
|
+
@@dFile.add(i * 8 + 3)
|
47
|
+
@@eFile.add(i * 8 + 4)
|
48
|
+
@@fFile.add(i * 8 + 5)
|
49
|
+
@@gFile.add(i * 8 + 6)
|
50
|
+
@@hFile.add(i * 8 + 7)
|
51
|
+
}
|
52
|
+
|
53
|
+
@@firstRank = Set.new; @@secondRank = Set.new; @@thirdRank = Set.new; @@fourthRank = Set.new;
|
54
|
+
@@fifthRank = Set.new; @@sixthRank = Set.new; @@seventhRank = Set.new; @@eighthRank = Set.new;
|
55
|
+
|
56
|
+
(0...8).each {|i|
|
57
|
+
@@firstRank.add(8 * 7 + i)
|
58
|
+
@@secondRank.add(8 * 6 + i)
|
59
|
+
@@thirdRank.add(8 * 5 + i)
|
60
|
+
@@fourthRank.add(8 * 4 + i)
|
61
|
+
@@fifthRank.add(8 * 3 + i)
|
62
|
+
@@sixthRank.add(8 * 2 + i)
|
63
|
+
@@seventhRank.add(8 * 1 + i)
|
64
|
+
@@eighthRank.add(8 * 0 + i)
|
65
|
+
}
|
66
|
+
|
67
|
+
@@light_squares = Set.new
|
68
|
+
@@dark_squares = Set.new
|
69
|
+
|
70
|
+
[ 0, 2, 4, 6,
|
71
|
+
9, 11, 13, 15,
|
72
|
+
16, 18, 20, 22,
|
73
|
+
25, 27, 29, 31,
|
74
|
+
32, 34, 36, 38,
|
75
|
+
41, 43, 45, 47,
|
76
|
+
48, 50, 52, 54,
|
77
|
+
57, 59, 61, 63 ].each {|i|
|
78
|
+
@@light_squares.add(i)
|
79
|
+
}
|
80
|
+
|
81
|
+
[ 1, 3, 5, 7,
|
82
|
+
8, 10, 12, 14,
|
83
|
+
17, 19, 21, 23,
|
84
|
+
24, 26, 28, 30,
|
85
|
+
33, 35, 37, 39,
|
86
|
+
40, 42, 44, 46,
|
87
|
+
49, 51, 53, 55,
|
88
|
+
56, 58, 60, 62 ].each {|i|
|
89
|
+
@@dark_squares.add(i)
|
90
|
+
}
|
91
|
+
|
92
|
+
@@one_thru_eight = Set.new
|
93
|
+
@@a_thru_h = Set.new
|
94
|
+
|
95
|
+
('1'..'8').each {|i| @@one_thru_eight.add(i)}
|
96
|
+
('a'..'h').each {|i| @@a_thru_h.add(i)}
|
97
|
+
|
98
|
+
def initialize(pgn_data)
|
99
|
+
@pgn_data = pgn_data
|
100
|
+
@board = []
|
101
|
+
@board_start_fen = START_FEN
|
102
|
+
@fen = nil
|
103
|
+
@potential_enpassent_ply = nil
|
104
|
+
@halfmove = 0
|
105
|
+
@fullmove = 1
|
106
|
+
@promotion = false
|
107
|
+
@promotion_piece = nil
|
108
|
+
@board_color_from_fen = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset_castle_options(option)
|
112
|
+
@can_white_castle_kingside = option
|
113
|
+
@can_white_castle_queenside = option
|
114
|
+
@can_black_castle_kingside = option
|
115
|
+
@can_black_castle_queenside = option
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def parse_headers(headers)
|
120
|
+
#puts headers
|
121
|
+
headers.each {|header|
|
122
|
+
key = HEADER_KEY_REGEX.match(header)[1]
|
123
|
+
value = HEADER_VALUE_REGEX.match(header)[1]
|
124
|
+
instance_variable_set(:"@#{key.downcase.to_sym}", value)
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse_pgn
|
129
|
+
unless is_single_game
|
130
|
+
raise Pgn2FenError, 'Only a single game PGN is supported right now.'
|
131
|
+
end
|
132
|
+
headers = []
|
133
|
+
@pgn_data.strip!
|
134
|
+
pgn_data_array = @pgn_data.split("\n")
|
135
|
+
pgn_data_array.each {|line|
|
136
|
+
if line.index("[") == 0
|
137
|
+
headers << line
|
138
|
+
else
|
139
|
+
break
|
140
|
+
end
|
141
|
+
}
|
142
|
+
parse_headers(headers)
|
143
|
+
pgn_data_array = pgn_data_array[headers.size..-1]
|
144
|
+
# get pgn
|
145
|
+
@pgn = ""
|
146
|
+
pgn_data_array.each {|line|
|
147
|
+
@pgn = pgn.concat(line).concat(" ")
|
148
|
+
}
|
149
|
+
@pgn = clean_pgn(@pgn)
|
150
|
+
|
151
|
+
#if fen does exist, use that as start board
|
152
|
+
unless @fen.nil?
|
153
|
+
tokens = @fen.split
|
154
|
+
unless tokens.size == 6
|
155
|
+
raise Pgn2FenError, "Invalid FEN header #{@fen}"
|
156
|
+
end
|
157
|
+
@board_start_fen = @fen
|
158
|
+
@board_color_from_fen = tokens[1]
|
159
|
+
@fullmove = tokens[5].to_i
|
160
|
+
castle_options = tokens[3]
|
161
|
+
update_castle_options_from_fen(castle_options)
|
162
|
+
else
|
163
|
+
reset_castle_options(true)
|
164
|
+
end
|
165
|
+
|
166
|
+
init_board
|
167
|
+
@plies = plies_from_pgn(@pgn)
|
168
|
+
@fen_array = fen_array_from_plies(@plies)
|
169
|
+
self # allow chaining
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_s
|
173
|
+
str = ""
|
174
|
+
str << "Event: #{@event}\n"
|
175
|
+
str << "Site: #{@site}\n"
|
176
|
+
str << "Date: #{@date}\n"
|
177
|
+
str << "EventDate: #{@eventdate}\n"
|
178
|
+
str << "Round: #{@round}\n"
|
179
|
+
str << "White: #{@white}\n"
|
180
|
+
str << "Black: #{@black}\n"
|
181
|
+
str << "WhiteElo: #{@whiteElo}\n"
|
182
|
+
str << "BlackElo: #{@blackElo}\n"
|
183
|
+
str << "Result: #{@result}\n"
|
184
|
+
str << "Eco: #{@eco}\n"
|
185
|
+
str << "Plycount: #{@plycount}\n"
|
186
|
+
str << "FEN: #{@fen}\n"
|
187
|
+
|
188
|
+
str << "PGN: #{@pgn}\n"
|
189
|
+
str << "Plies: #{@plies}\n"
|
190
|
+
str << "FEN Array:\n"
|
191
|
+
unless @fen_array.nil?
|
192
|
+
@fen_array.each {|fen|
|
193
|
+
str << fen << "\n"
|
194
|
+
}
|
195
|
+
end
|
196
|
+
str
|
197
|
+
end
|
198
|
+
|
199
|
+
def rank_for_hint(hint)
|
200
|
+
case hint
|
201
|
+
when '1'
|
202
|
+
return @@firstRank
|
203
|
+
when '2'
|
204
|
+
return @@secondRank
|
205
|
+
when '3'
|
206
|
+
return @@thirdRank
|
207
|
+
when '4'
|
208
|
+
return @@fourthRank
|
209
|
+
when '5'
|
210
|
+
return @@fifthRank
|
211
|
+
when '6'
|
212
|
+
return @@sixthRank
|
213
|
+
when '7'
|
214
|
+
return @@seventhRank
|
215
|
+
when '8'
|
216
|
+
return @@eighthRank
|
217
|
+
else
|
218
|
+
return nil
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def file_for_hint(hint)
|
223
|
+
case hint
|
224
|
+
when 'a'
|
225
|
+
return @@aFile
|
226
|
+
when 'b'
|
227
|
+
return @@bFile
|
228
|
+
when 'c'
|
229
|
+
return @@cFile
|
230
|
+
when 'd'
|
231
|
+
return @@dFile
|
232
|
+
when 'e'
|
233
|
+
return @@eFile
|
234
|
+
when 'f'
|
235
|
+
return @@fFile
|
236
|
+
when 'g'
|
237
|
+
return @@gFile
|
238
|
+
when 'h'
|
239
|
+
return @@hFile
|
240
|
+
else
|
241
|
+
return nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def light_square_by_pgn square_pgn
|
246
|
+
@@light_squares.include?(@@pgn_number_hash[square_pgn])
|
247
|
+
end
|
248
|
+
|
249
|
+
def dark_square_by_pgn square_pgn
|
250
|
+
@@dark_squares.include?(@@pgn_number_hash[square_pgn])
|
251
|
+
end
|
252
|
+
|
253
|
+
def light_square_by_number square_number
|
254
|
+
@@light_squares.include?(square_number)
|
255
|
+
end
|
256
|
+
|
257
|
+
def dark_square_by_number square_number
|
258
|
+
@@dark_squares.include?(square_number)
|
259
|
+
end
|
260
|
+
|
261
|
+
def init_board
|
262
|
+
@board_start_fen.split(" ").first.each_char {|c|
|
263
|
+
case c
|
264
|
+
when 'r', 'n', 'b', 'q', 'k', 'p'
|
265
|
+
@board << c
|
266
|
+
when 'R', 'N', 'B', 'Q', 'K', 'P'
|
267
|
+
@board << c
|
268
|
+
when '1', '2', '3', '4', '5', '6', '7', '8'
|
269
|
+
(0...c.to_i).each {|n| @board << "" }
|
270
|
+
else
|
271
|
+
# do nothing
|
272
|
+
end
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
def games_from_pgn(pgn)
|
277
|
+
games = []
|
278
|
+
pos_array = pgn.enum_for(:scan, /\[Event/).map{Regexp.last_match.begin(0)}
|
279
|
+
pos_array.each_with_index {|pos, index|
|
280
|
+
if (pos_array.size > index + 1) # all elements but last
|
281
|
+
single_game_pgn = pgn[pos..pos_array[index+1]-1]
|
282
|
+
else # last element
|
283
|
+
single_game_pgn = pgn[pos..-1]
|
284
|
+
end
|
285
|
+
games << game_from_pgn(single_game_pgn)
|
286
|
+
}
|
287
|
+
games
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# check to verify PGN includes only a single game
|
292
|
+
def is_single_game
|
293
|
+
count = @pgn_data.scan(/\[Event /).count
|
294
|
+
#puts "Event count:#{count}"
|
295
|
+
if count > 1; return false; end
|
296
|
+
|
297
|
+
# result header also includes result, hence compare with 2
|
298
|
+
count = @pgn_data.scan(/1-0/).count
|
299
|
+
#puts "1-0 count:#{count}"
|
300
|
+
if count > 2; return false; end
|
301
|
+
|
302
|
+
count = @pgn_data.scan(/0-1/).count
|
303
|
+
#puts "0-1 count:#{count}"
|
304
|
+
if count > 2; return false; end
|
305
|
+
|
306
|
+
count = @pgn_data.scan(/1\/2-1\/2/).count
|
307
|
+
#puts "1/2-1/2 count:#{count}"
|
308
|
+
if count > 2; return false; end
|
309
|
+
|
310
|
+
true
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# get plies from pgn
|
315
|
+
def plies_from_pgn(pgn)
|
316
|
+
moves = pgn.split(/[0-9]+\./)
|
317
|
+
moves.shift # remove first ""
|
318
|
+
plies = []
|
319
|
+
moves.each {|move|
|
320
|
+
move.strip!
|
321
|
+
move.gsub!(".", "")
|
322
|
+
move.split(" ").each {|ply|
|
323
|
+
plies << ply
|
324
|
+
}
|
325
|
+
}
|
326
|
+
plies
|
327
|
+
end
|
328
|
+
|
329
|
+
##
|
330
|
+
# clean pgn - remove comments and other unrequired text
|
331
|
+
def clean_pgn(pgn)
|
332
|
+
pgn.strip!
|
333
|
+
# clean result
|
334
|
+
pgn.sub!("1-0", ""); pgn.sub!("0-1", ""); pgn.sub!("1/2-1/2", "")
|
335
|
+
# clean mate and incomplete game marker
|
336
|
+
pgn.gsub!("#", ""); pgn.gsub!("*", "")
|
337
|
+
#remove all chessbase $ characters
|
338
|
+
pgn.gsub!(/\$(\w+)/, "")
|
339
|
+
#remove all comments - content within {}
|
340
|
+
#pgn.gsub!(/(\{[^}]+\})+?/, "")
|
341
|
+
pgn = clean_comments(pgn)
|
342
|
+
#remove all subvariations - content within ()
|
343
|
+
#pgn.gsub!(/(\([^}]+\))+?/, "")
|
344
|
+
pgn = clean_subvariations(pgn)
|
345
|
+
end
|
346
|
+
|
347
|
+
def clean_comments(pgn)
|
348
|
+
remove_text_between_tokens_inclusive(pgn, OPEN_BRACE, CLOSE_BRACE)
|
349
|
+
end
|
350
|
+
|
351
|
+
def clean_subvariations(pgn)
|
352
|
+
remove_text_between_tokens_inclusive(pgn, OPEN_PAREN, CLOSE_PAREN)
|
353
|
+
end
|
354
|
+
|
355
|
+
def remove_text_between_tokens_inclusive(text, open_token, close_token)
|
356
|
+
open_token_count = 0
|
357
|
+
new_text = ""
|
358
|
+
text.split("").each {|c|
|
359
|
+
if c == open_token
|
360
|
+
open_token_count += 1
|
361
|
+
elsif c == close_token
|
362
|
+
open_token_count -= 1
|
363
|
+
else
|
364
|
+
if open_token_count == 0
|
365
|
+
new_text << c
|
366
|
+
end
|
367
|
+
end
|
368
|
+
}
|
369
|
+
new_text
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# get FEN array from plies
|
374
|
+
def fen_array_from_plies(plies)
|
375
|
+
fen_array = []
|
376
|
+
fen_array << @board_start_fen
|
377
|
+
ply_number = 1 #ply_number starts at 1 for regular game
|
378
|
+
unless @fen.nil?
|
379
|
+
if @board_color_from_fen == "w"
|
380
|
+
ply_number = (@fullmove.to_i * 2) + 1
|
381
|
+
else
|
382
|
+
ply_number = (@fullmove.to_i * 2)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
plies.each{|ply|
|
386
|
+
#puts "ply=#{ply}, ply_number=#{ply_number}, move_number=#{ply_number/2 + 1}"
|
387
|
+
fen_array << fen_for_ply(ply, ply_number)
|
388
|
+
ply_number += 1
|
389
|
+
}
|
390
|
+
fen_array
|
391
|
+
end
|
392
|
+
|
393
|
+
##
|
394
|
+
# get FEN from a ply and ply number
|
395
|
+
def fen_for_ply(ply, ply_number)
|
396
|
+
#puts ply
|
397
|
+
is_white = (ply_number % 2 != 0)
|
398
|
+
@halfmove = @halfmove + 1
|
399
|
+
long_ply = short_ply_to_long_ply(ply, is_white)
|
400
|
+
|
401
|
+
if long_ply.eql? "O-O"
|
402
|
+
if is_white
|
403
|
+
make_ply_on_board("e1g1")
|
404
|
+
make_ply_on_board("h1f1")
|
405
|
+
@can_white_castle_kingside = false
|
406
|
+
@can_white_castle_queenside = false
|
407
|
+
else
|
408
|
+
make_ply_on_board("e8g8")
|
409
|
+
make_ply_on_board("h8f8")
|
410
|
+
@can_black_castle_kingside = false
|
411
|
+
@can_black_castle_queenside = false
|
412
|
+
end
|
413
|
+
elsif long_ply.eql? "O-O-O"
|
414
|
+
if is_white
|
415
|
+
make_ply_on_board("e1c1")
|
416
|
+
make_ply_on_board("a1d1")
|
417
|
+
@can_white_castle_kingside = false
|
418
|
+
@can_white_castle_queenside = false
|
419
|
+
else
|
420
|
+
make_ply_on_board("e8c8")
|
421
|
+
make_ply_on_board("a8d8")
|
422
|
+
@can_black_castle_kingside = false
|
423
|
+
@can_black_castle_queenside = false
|
424
|
+
end
|
425
|
+
else # rest of moves
|
426
|
+
if is_white
|
427
|
+
if @can_white_castle_kingside && long_ply[0,2] == "h1"; @can_white_castle_kingside = false; end
|
428
|
+
if @can_white_castle_queenside && long_ply[0,2] == "a1"; @can_white_castle_queenside = false; end
|
429
|
+
else
|
430
|
+
if @can_black_castle_kingside && long_ply[0,2] == "h8"; @can_white_castle_kingside = false; end
|
431
|
+
if @can_black_castle_queenside && long_ply[0,2] == "a8"; @can_white_castle_queenside = false; end
|
432
|
+
end
|
433
|
+
make_ply_on_board(long_ply)
|
434
|
+
end
|
435
|
+
|
436
|
+
fen = board_to_fen
|
437
|
+
fen.concat(" ").concat(is_white ? "b": "w")
|
438
|
+
fen.concat(" ").concat(fen_castle_text)
|
439
|
+
#enpassent square
|
440
|
+
unless @potential_enpassent_ply.nil?
|
441
|
+
fen.concat(" ").concat(@potential_enpassent_ply)
|
442
|
+
@potential_enpassent_ply = nil
|
443
|
+
else
|
444
|
+
fen.concat(" ").concat("-")
|
445
|
+
end
|
446
|
+
fen.concat(" ").concat(@halfmove.to_s)
|
447
|
+
if is_white
|
448
|
+
fen.concat(" ").concat((@fullmove).to_s)
|
449
|
+
else
|
450
|
+
fen.concat(" ").concat((@fullmove += 1).to_s)
|
451
|
+
end
|
452
|
+
#pp_board
|
453
|
+
fen
|
454
|
+
end
|
455
|
+
|
456
|
+
##
|
457
|
+
# get FEN castle text based on castling availability
|
458
|
+
def fen_castle_text
|
459
|
+
text = ""
|
460
|
+
if @can_white_castle_kingside; text.concat("K"); end
|
461
|
+
if @can_white_castle_queenside; text.concat("Q"); end
|
462
|
+
if @can_black_castle_kingside; text.concat("k"); end
|
463
|
+
if @can_black_castle_queenside; text.concat("q"); end
|
464
|
+
if text.empty?; text = "-"; end
|
465
|
+
text
|
466
|
+
end
|
467
|
+
|
468
|
+
##
|
469
|
+
# update castling options from FEN header
|
470
|
+
def update_castle_options_from_fen castle_options
|
471
|
+
reset_castle_options(false)
|
472
|
+
if castle_options == "-"
|
473
|
+
return
|
474
|
+
end
|
475
|
+
tokens = castle_options.split("")
|
476
|
+
tokens.each {|token|
|
477
|
+
case token
|
478
|
+
when "K"
|
479
|
+
@can_white_castle_kingside = true
|
480
|
+
when "Q"
|
481
|
+
@can_white_castle_queenside = true
|
482
|
+
when "k"
|
483
|
+
@can_black_castle_kingside = true
|
484
|
+
when "q"
|
485
|
+
@can_black_castle_queenside = true
|
486
|
+
end
|
487
|
+
}
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# make a ply on the board
|
492
|
+
def make_ply_on_board(long_ply)
|
493
|
+
from_pgn = long_ply[0,2]
|
494
|
+
to_pgn = long_ply[2,2]
|
495
|
+
from_idx = @@pgn_number_hash[from_pgn]
|
496
|
+
to_idx = @@pgn_number_hash[to_pgn]
|
497
|
+
#puts "from_pgn:#{from_pgn}, to_pgn:#{to_pgn}, from_idx:#{from_idx}, to_idx:#{to_idx}"
|
498
|
+
if @promotion
|
499
|
+
if "P" == @board[from_idx] #white
|
500
|
+
@board[to_idx] = @promotion_piece.upcase
|
501
|
+
else #black
|
502
|
+
@board[to_idx] = @promotion_piece.downcase
|
503
|
+
end
|
504
|
+
@promotion = false
|
505
|
+
@promotion_piece = nil
|
506
|
+
else #general case
|
507
|
+
@board[to_idx] = @board[from_idx]
|
508
|
+
end
|
509
|
+
@board[from_idx] = ""
|
510
|
+
end
|
511
|
+
|
512
|
+
##
|
513
|
+
# return fen representation from board
|
514
|
+
def board_to_fen
|
515
|
+
fen = ""
|
516
|
+
empty_square_counter = 0;
|
517
|
+
@board.each_with_index { |tok, idx|
|
518
|
+
if (idx % 8 == 0 && idx > 0)
|
519
|
+
if empty_square_counter != 0
|
520
|
+
fen.concat(empty_square_counter.to_s)
|
521
|
+
empty_square_counter = 0
|
522
|
+
end
|
523
|
+
fen.concat("/")
|
524
|
+
end
|
525
|
+
if tok.empty?
|
526
|
+
empty_square_counter = empty_square_counter + 1
|
527
|
+
else
|
528
|
+
if empty_square_counter != 0
|
529
|
+
fen.concat(empty_square_counter.to_s)
|
530
|
+
empty_square_counter = 0
|
531
|
+
end
|
532
|
+
fen.concat(tok)
|
533
|
+
end
|
534
|
+
}
|
535
|
+
# last squares could be empty
|
536
|
+
if empty_square_counter != 0
|
537
|
+
fen.concat(empty_square_counter.to_s)
|
538
|
+
empty_square_counter = 0
|
539
|
+
end
|
540
|
+
fen
|
541
|
+
end
|
542
|
+
|
543
|
+
def pp_board
|
544
|
+
str = ""
|
545
|
+
@board.each_with_index { |sq, idx|
|
546
|
+
if sq.empty?; sq = "*"; end
|
547
|
+
if (idx % 8 == 0 && idx != 0); str << "\n"; end
|
548
|
+
str << sq << " "
|
549
|
+
}
|
550
|
+
puts "= = = = = = = ="
|
551
|
+
puts str
|
552
|
+
puts "= = = = = = = =\n\n\n"
|
553
|
+
end
|
554
|
+
|
555
|
+
##
|
556
|
+
# convert short ply to long ply
|
557
|
+
def short_ply_to_long_ply(ply, is_white)
|
558
|
+
from_idx = -1
|
559
|
+
to_idx = -1
|
560
|
+
from_pgn = ""
|
561
|
+
to_pgn = ""
|
562
|
+
hint = nil
|
563
|
+
|
564
|
+
if ply.eql?("O-O"); return ply; end
|
565
|
+
if ply.eql?("O-O-O"); return ply; end
|
566
|
+
unless ply.index('+').nil?; ply.sub!("+", ""); end
|
567
|
+
unless ply.index('x').nil?; ply.sub!("x", ""); @halfmove = 0; end
|
568
|
+
unless ply.index('=').nil?
|
569
|
+
@promotion = true
|
570
|
+
@promotion_piece = ply[-1]
|
571
|
+
ply = ply.chop.chop
|
572
|
+
end
|
573
|
+
|
574
|
+
if ply.length == 2 # pawn non-capture
|
575
|
+
to_pgn = ply
|
576
|
+
to_idx = @@pgn_number_hash[to_pgn]
|
577
|
+
if is_white
|
578
|
+
if !@board[to_idx+8].empty?
|
579
|
+
from_idx = to_idx+8
|
580
|
+
else
|
581
|
+
from_idx = to_idx+16
|
582
|
+
# if @board[to_idx-1].eql?"p" or @board[to_idx+1].eql?"p"
|
583
|
+
@potential_enpassent_ply = @@number_pgn_hash[from_idx-8]
|
584
|
+
# end
|
585
|
+
end
|
586
|
+
else # isBlack
|
587
|
+
if !@board[to_idx-8].empty?
|
588
|
+
from_idx = to_idx-8
|
589
|
+
else
|
590
|
+
from_idx = to_idx-16
|
591
|
+
# if @board[to_idx-1].eql?"P" or @board[to_idx+1].eql?"P"
|
592
|
+
@potential_enpassent_ply = @@number_pgn_hash[from_idx+8]
|
593
|
+
# end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
597
|
+
@halfmove = 0
|
598
|
+
return from_pgn + to_pgn
|
599
|
+
end
|
600
|
+
|
601
|
+
if ('a'..'h').include?(ply[0]) && ply.length == 3 #pawn capture, non-enpassent
|
602
|
+
to_pgn = ply[1..-1]
|
603
|
+
to_idx = @@pgn_number_hash[to_pgn]
|
604
|
+
if is_white
|
605
|
+
if @board[to_idx+7].eql?("P") && file_for_hint(ply[0]).include?(to_idx+7)
|
606
|
+
from_idx = to_idx+7
|
607
|
+
elsif @board[to_idx+9].eql?("P")&& file_for_hint(ply[0]).include?(to_idx+9)
|
608
|
+
from_idx = to_idx+9
|
609
|
+
end
|
610
|
+
else #is_black
|
611
|
+
if @board[to_idx-7].eql?("p") && file_for_hint(ply[0]).include?(to_idx-7)
|
612
|
+
from_idx = to_idx-7
|
613
|
+
elsif @board[to_idx-9].eql?("p") && file_for_hint(ply[0]).include?(to_idx-9)
|
614
|
+
from_idx = to_idx-9
|
615
|
+
end
|
616
|
+
end
|
617
|
+
if from_idx == -1; raise Pgn2FenError, "Error parsing pawn capture at ply #{ply}"; end
|
618
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
619
|
+
@halfmove = 0
|
620
|
+
return from_pgn + to_pgn
|
621
|
+
end
|
622
|
+
|
623
|
+
if ply.length == 3; to_pgn = ply[1,2]; end
|
624
|
+
if ply.length() == 4
|
625
|
+
to_pgn = ply[2,2]
|
626
|
+
hint = ply[1]
|
627
|
+
end
|
628
|
+
to_idx = @@pgn_number_hash[to_pgn]
|
629
|
+
|
630
|
+
if ply[0].downcase.eql?('r'); return short_ply_to_long_ply_for_rook(to_idx, to_pgn, hint, is_white); end
|
631
|
+
if ply[0].downcase.eql?('n'); return short_ply_to_long_ply_for_knight(to_idx, to_pgn, hint, is_white); end
|
632
|
+
if ply[0].downcase.eql?('b'); return short_ply_to_long_ply_for_bishop(to_idx, to_pgn, hint, is_white); end
|
633
|
+
if ply[0].downcase.eql?('q'); return short_ply_to_long_ply_for_queen(to_idx, to_pgn, hint, is_white); end
|
634
|
+
if ply[0].downcase.eql?('k'); return short_ply_to_long_ply_for_king(to_idx, to_pgn, hint, is_white); end
|
635
|
+
return from_pgn + to_pgn
|
636
|
+
end
|
637
|
+
|
638
|
+
def short_ply_to_long_ply_for_rook(to_idx, to_pgn, hint, is_white)
|
639
|
+
from_idx = -1
|
640
|
+
from_pgn = ""
|
641
|
+
piece = is_white ? "R" : "r"
|
642
|
+
if !hint.nil?
|
643
|
+
if @@one_thru_eight.include?(hint)
|
644
|
+
from_pgn = to_pgn[0,1] + hint
|
645
|
+
end
|
646
|
+
if @@a_thru_h.include?(hint)
|
647
|
+
from_pgn = hint + to_pgn[1,1]
|
648
|
+
end
|
649
|
+
return from_pgn + to_pgn
|
650
|
+
else # no hint
|
651
|
+
# check file
|
652
|
+
up = to_idx
|
653
|
+
while up > -1 do
|
654
|
+
up = up - 8
|
655
|
+
if (up < 0)
|
656
|
+
break
|
657
|
+
end
|
658
|
+
if @board[up].eql?(piece)
|
659
|
+
from_idx = up
|
660
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
661
|
+
return from_pgn + to_pgn
|
662
|
+
elsif @board[up].eql?("")
|
663
|
+
next
|
664
|
+
else
|
665
|
+
break
|
666
|
+
end
|
667
|
+
end
|
668
|
+
down = to_idx
|
669
|
+
while down < 64 do
|
670
|
+
down = down + 8
|
671
|
+
if down > 63
|
672
|
+
break
|
673
|
+
end
|
674
|
+
if @board[down].eql?(piece)
|
675
|
+
from_idx = down
|
676
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
677
|
+
return from_pgn + to_pgn
|
678
|
+
elsif @board[down].eql?("")
|
679
|
+
next
|
680
|
+
else
|
681
|
+
break
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# check rank
|
686
|
+
left = to_idx
|
687
|
+
while left > -1 do
|
688
|
+
left = left - 1
|
689
|
+
if left % 8 == 7
|
690
|
+
break
|
691
|
+
end
|
692
|
+
if @board[left].eql?(piece)
|
693
|
+
from_idx = left
|
694
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
695
|
+
return from_pgn + to_pgn
|
696
|
+
elsif @board[left].eql?("")
|
697
|
+
next
|
698
|
+
else
|
699
|
+
break
|
700
|
+
end
|
701
|
+
end
|
702
|
+
right = to_idx
|
703
|
+
while right < 64 do
|
704
|
+
right = right + 1
|
705
|
+
if right % 8 == 0
|
706
|
+
break
|
707
|
+
end
|
708
|
+
if @board[right].eql?(piece)
|
709
|
+
from_idx = right
|
710
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
711
|
+
return from_pgn + to_pgn
|
712
|
+
elsif @board[right].eql?("")
|
713
|
+
next
|
714
|
+
else
|
715
|
+
break
|
716
|
+
end
|
717
|
+
end
|
718
|
+
end
|
719
|
+
from_pgn + to_pgn
|
720
|
+
end
|
721
|
+
|
722
|
+
def short_ply_to_long_ply_for_knight(to_idx, to_pgn, hint, is_white)
|
723
|
+
from_idx = -1
|
724
|
+
from_pgn = ""
|
725
|
+
piece = is_white ? "N" : "n"
|
726
|
+
if !hint.nil?
|
727
|
+
# -17,-15, 10, -6, 6, 10, 15, 17 are possible knight moves
|
728
|
+
knight_moves = [17+to_idx, 17+to_idx, -15+to_idx, 15+to_idx, -10+to_idx, 10+to_idx, -6+to_idx, 6+to_idx]
|
729
|
+
if @@one_thru_eight.include?(hint)
|
730
|
+
rank = rank_for_hint(hint)
|
731
|
+
knight_moves = knight_moves & rank.to_a # intersection
|
732
|
+
end
|
733
|
+
if @@a_thru_h.include?(hint)
|
734
|
+
file = file_for_hint(hint)
|
735
|
+
knight_moves = knight_moves & file.to_a #intersection
|
736
|
+
end
|
737
|
+
knight_moves.each {|idx|
|
738
|
+
if @board[idx].eql?(piece)
|
739
|
+
from_idx = idx
|
740
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
741
|
+
return from_pgn + to_pgn
|
742
|
+
end
|
743
|
+
}
|
744
|
+
else
|
745
|
+
# -17,-15, 10, -6, 6, 10, 15, 17 are possible knight moves
|
746
|
+
knight_moves = [-17, 17, -15, 15, -10, 10, -6, 6]
|
747
|
+
knight_moves.each {|i|
|
748
|
+
idx = to_idx + i
|
749
|
+
if (idx < 0 && idx > 63)
|
750
|
+
next
|
751
|
+
end
|
752
|
+
if @board[idx].eql?(piece)
|
753
|
+
from_idx = idx
|
754
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
755
|
+
return from_pgn + to_pgn
|
756
|
+
end
|
757
|
+
}
|
758
|
+
end
|
759
|
+
#return from_pgn + to_pgn
|
760
|
+
raise Pgn2FenError, "Error parsing knight move to square #{to_pgn}"
|
761
|
+
end
|
762
|
+
|
763
|
+
def short_ply_to_long_ply_for_bishop(to_idx, to_pgn, hint, is_white)
|
764
|
+
from_idx = -1
|
765
|
+
from_pgn = ""
|
766
|
+
piece = is_white ? "B" : "b"
|
767
|
+
is_light = light_square_by_number(to_idx) ? true : false
|
768
|
+
if (!hint.nil?)
|
769
|
+
if @@one_thru_eight.include?(hint)
|
770
|
+
from_pgn = to_pgn[0,1] + h
|
771
|
+
end
|
772
|
+
if @@a_thru_h.include?(hint)
|
773
|
+
from_pgn = h + to_pgn[1,1]
|
774
|
+
end
|
775
|
+
return from_pgn + to_pgn
|
776
|
+
else
|
777
|
+
# check nw direction
|
778
|
+
nw = to_idx
|
779
|
+
while(nw > -1) do
|
780
|
+
nw = nw - 9
|
781
|
+
if (nw < 0)
|
782
|
+
break
|
783
|
+
end
|
784
|
+
# puts "nw=#{nw}"
|
785
|
+
if light_square_by_number(nw) != is_light # square colors don't match - overflow
|
786
|
+
break
|
787
|
+
elsif @board[nw].eql?(piece)
|
788
|
+
from_idx = nw
|
789
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
790
|
+
return from_pgn + to_pgn
|
791
|
+
elsif @board[nw].eql?("")
|
792
|
+
next
|
793
|
+
else
|
794
|
+
break
|
795
|
+
end
|
796
|
+
end
|
797
|
+
# check ne direction
|
798
|
+
ne = to_idx
|
799
|
+
while(ne > 0) do
|
800
|
+
ne = ne - 7
|
801
|
+
if (ne < 1)
|
802
|
+
break
|
803
|
+
end
|
804
|
+
# puts "ne=#{ne}"
|
805
|
+
if light_square_by_number(ne) != is_light # square colors don't match - overflow
|
806
|
+
break
|
807
|
+
elsif @board[ne].eql?(piece)
|
808
|
+
from_idx = ne
|
809
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
810
|
+
return from_pgn + to_pgn
|
811
|
+
elsif @board[ne].eql?("")
|
812
|
+
next
|
813
|
+
else
|
814
|
+
break
|
815
|
+
end
|
816
|
+
end
|
817
|
+
# check sw direction
|
818
|
+
sw = to_idx
|
819
|
+
while(sw < 63) do
|
820
|
+
sw = sw + 7
|
821
|
+
if (sw > 62)
|
822
|
+
break
|
823
|
+
end
|
824
|
+
# puts "sw=#{sw}"
|
825
|
+
if light_square_by_number(sw) != is_light # square colors don't match - overflow
|
826
|
+
break
|
827
|
+
elsif @board[sw].eql?(piece)
|
828
|
+
from_idx = sw
|
829
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
830
|
+
return from_pgn + to_pgn
|
831
|
+
elsif @board[sw].eql?("")
|
832
|
+
next
|
833
|
+
else
|
834
|
+
break
|
835
|
+
end
|
836
|
+
end
|
837
|
+
# check se direction
|
838
|
+
se = to_idx
|
839
|
+
while(se < 64) do
|
840
|
+
se = se + 9
|
841
|
+
if (se > 63)
|
842
|
+
break
|
843
|
+
end
|
844
|
+
# puts "se=#{se}"
|
845
|
+
if light_square_by_number(se) != is_light # square colors don't match - overflow
|
846
|
+
break
|
847
|
+
elsif @board[se].eql?(piece)
|
848
|
+
from_idx = se
|
849
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
850
|
+
return from_pgn + to_pgn
|
851
|
+
elsif @board[se].eql?("")
|
852
|
+
next
|
853
|
+
else
|
854
|
+
break
|
855
|
+
end
|
856
|
+
end
|
857
|
+
end
|
858
|
+
return from_pgn + to_pgn
|
859
|
+
end
|
860
|
+
|
861
|
+
def short_ply_to_long_ply_for_queen(to_idx, to_pgn, hint, is_white)
|
862
|
+
# check bishop type moves
|
863
|
+
from_idx = -1
|
864
|
+
from_pgn = ""
|
865
|
+
piece = is_white ? "Q" : "q"
|
866
|
+
is_light = light_square_by_number(to_idx) ? true : false
|
867
|
+
if (!hint.nil?)
|
868
|
+
if @@one_thru_eight.include?(hint)
|
869
|
+
from_pgn = to_pgn[0,1] + hint
|
870
|
+
end
|
871
|
+
if @@a_thru_h.include?(hint)
|
872
|
+
from_pgn = hint + to_pgn[1,1]
|
873
|
+
end
|
874
|
+
return from_pgn + to_pgn
|
875
|
+
else
|
876
|
+
# check nw direction
|
877
|
+
nw = to_idx
|
878
|
+
while(nw > -1) do
|
879
|
+
nw = nw - 9
|
880
|
+
if (nw < 0)
|
881
|
+
break
|
882
|
+
end
|
883
|
+
if light_square_by_number(nw) != is_light # square colors don't match - overflow
|
884
|
+
break
|
885
|
+
elsif @board[nw].eql?(piece)
|
886
|
+
from_idx = nw
|
887
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
888
|
+
return from_pgn + to_pgn
|
889
|
+
elsif @board[nw].eql?("")
|
890
|
+
next
|
891
|
+
else
|
892
|
+
break
|
893
|
+
end
|
894
|
+
end
|
895
|
+
# check ne direction
|
896
|
+
ne = to_idx
|
897
|
+
while(ne > 0) do
|
898
|
+
ne = ne - 7
|
899
|
+
if (ne < 1)
|
900
|
+
break
|
901
|
+
end
|
902
|
+
if light_square_by_number(ne) != is_light # square colors don't match - overflow
|
903
|
+
break
|
904
|
+
elsif @board[ne].eql?(piece)
|
905
|
+
from_idx = ne
|
906
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
907
|
+
return from_pgn + to_pgn
|
908
|
+
elsif @board[ne].eql?("")
|
909
|
+
next
|
910
|
+
else
|
911
|
+
break
|
912
|
+
end
|
913
|
+
end
|
914
|
+
# check sw direction
|
915
|
+
sw = to_idx
|
916
|
+
while(sw < 63) do
|
917
|
+
sw = sw + 7
|
918
|
+
if (sw > 62)
|
919
|
+
break
|
920
|
+
end
|
921
|
+
if light_square_by_number(sw) != is_light # square colors don't match - overflow
|
922
|
+
break
|
923
|
+
elsif @board[sw].eql?(piece)
|
924
|
+
from_idx = sw
|
925
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
926
|
+
return from_pgn + to_pgn
|
927
|
+
elsif @board[sw].eql?("")
|
928
|
+
next
|
929
|
+
else
|
930
|
+
break
|
931
|
+
end
|
932
|
+
end
|
933
|
+
# check se direction
|
934
|
+
se = to_idx
|
935
|
+
while(se < 64) do
|
936
|
+
se = se + 9
|
937
|
+
if (se > 63)
|
938
|
+
break
|
939
|
+
end
|
940
|
+
if light_square_by_number(se) != is_light # square colors don't match - overflow
|
941
|
+
break
|
942
|
+
elsif @board[se].eql?(piece)
|
943
|
+
from_idx = se
|
944
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
945
|
+
return from_pgn + to_pgn
|
946
|
+
elsif @board[se].eql?("")
|
947
|
+
next
|
948
|
+
else
|
949
|
+
break
|
950
|
+
end
|
951
|
+
end
|
952
|
+
end
|
953
|
+
if (from_pgn.length == 2)
|
954
|
+
return from_pgn + to_pgn
|
955
|
+
end
|
956
|
+
# check rook type moves
|
957
|
+
if !hint.nil?
|
958
|
+
if @@one_thru_eight.include?(hint)
|
959
|
+
from_pgn = to_pgn[0,1] + hint
|
960
|
+
end
|
961
|
+
if @@a_thru_h.include?(hint)
|
962
|
+
from_pgn = hint + to_pgn[1,1]
|
963
|
+
end
|
964
|
+
return from_pgn + to_pgn
|
965
|
+
else # no hint
|
966
|
+
# check file
|
967
|
+
up = to_idx
|
968
|
+
while up > -1 do
|
969
|
+
up = up - 8
|
970
|
+
if (up < 0)
|
971
|
+
break
|
972
|
+
end
|
973
|
+
if @board[up].eql?(piece)
|
974
|
+
from_idx = up
|
975
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
976
|
+
return from_pgn + to_pgn
|
977
|
+
elsif @board[up].eql?("")
|
978
|
+
next
|
979
|
+
else
|
980
|
+
break
|
981
|
+
end
|
982
|
+
end
|
983
|
+
down = to_idx
|
984
|
+
while down < 64 do
|
985
|
+
down = down + 8
|
986
|
+
if down > 63
|
987
|
+
break
|
988
|
+
end
|
989
|
+
if @board[down].eql?(piece)
|
990
|
+
from_idx = down
|
991
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
992
|
+
return from_pgn + to_pgn
|
993
|
+
elsif @board[down].eql?("")
|
994
|
+
next
|
995
|
+
else
|
996
|
+
break
|
997
|
+
end
|
998
|
+
end
|
999
|
+
|
1000
|
+
# check rank
|
1001
|
+
left = to_idx
|
1002
|
+
while left > -1 do
|
1003
|
+
left = left - 1
|
1004
|
+
if left % 8 == 7
|
1005
|
+
break
|
1006
|
+
end
|
1007
|
+
if @board[left].eql?(piece)
|
1008
|
+
from_idx = left
|
1009
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
1010
|
+
return from_pgn + to_pgn
|
1011
|
+
elsif @board[left].eql?("")
|
1012
|
+
next
|
1013
|
+
else
|
1014
|
+
break
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
right = to_idx
|
1018
|
+
while right < 64 do
|
1019
|
+
right = right + 1
|
1020
|
+
if right % 8 == 0
|
1021
|
+
break
|
1022
|
+
end
|
1023
|
+
if @board[right].eql?(piece)
|
1024
|
+
from_idx = right
|
1025
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
1026
|
+
return from_pgn + to_pgn
|
1027
|
+
elsif @board[right].eql?("")
|
1028
|
+
next
|
1029
|
+
else
|
1030
|
+
break
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
from_pgn + to_pgn
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
def short_ply_to_long_ply_for_king(to_idx, to_pgn, hint, is_white)
|
1038
|
+
from_idx = -1
|
1039
|
+
from_pgn = ""
|
1040
|
+
if is_white
|
1041
|
+
@board.reverse.each_with_index {|i,idx| if i.eql?("K"); from_idx = 63 - idx; break; end }
|
1042
|
+
else
|
1043
|
+
@board.each_with_index {|i,idx| if i.eql?("k"); from_idx = idx; break; end }
|
1044
|
+
end
|
1045
|
+
from_pgn = @@number_pgn_hash[from_idx]
|
1046
|
+
return from_pgn + to_pgn
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
class Pgn2FenError < StandardError; end
|
1052
|
+
|
1053
|
+
end #end module
|