pgn2fen 0.9.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 +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
|