chess_data 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.rdoc +22 -0
- data/README.rdoc +115 -0
- data/lib/chess_data/board.rb +324 -0
- data/lib/chess_data/database.rb +83 -0
- data/lib/chess_data/game.rb +190 -0
- data/lib/chess_data/moves.rb +512 -0
- data/lib/chess_data/position-definition.rb +106 -0
- data/lib/chess_data.rb +7 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 97031a873d57187ce167a3fa0eb86b0055c2350c53debe92c0cec99e875df18f
|
4
|
+
data.tar.gz: 1f40251c1880e067836eb37c04f7514618f7252f0b5ccf6565ce4a8cf9dd8153
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 81ffd070d6b823f0e9d0d19fc22d12f64dd8a916f33e6522f72ed2b882621c3637d97f5f42c6033e1cfedb0046270316cd3be177766aa53023fef3315f6abe6f
|
7
|
+
data.tar.gz: ca62c4e5700267ff063b3d5065a9f08370c5fff204248a99f60b2238b30bdfbfec2b45506adf9b2ade549011e455dfe3152521bdad0b58569c42707f797a905a
|
data/LICENSE.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019-23, Peter Lane
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
= chess_data
|
2
|
+
|
3
|
+
Install from {RubyGems}[https://rubygems.org/gems/chess_data/]:
|
4
|
+
|
5
|
+
> gem install chess_data
|
6
|
+
|
7
|
+
source:: https://notabug.org/peterlane/chess_data/
|
8
|
+
|
9
|
+
== Description
|
10
|
+
|
11
|
+
For searching/filtering datasets of chess games.
|
12
|
+
|
13
|
+
Features
|
14
|
+
|
15
|
+
* Read and save collections of PGN files
|
16
|
+
* Filter games looking for games containing positions with certain combinations
|
17
|
+
of pieces
|
18
|
+
* Save collections of PGN games or individual positions after filtering.
|
19
|
+
* PGN lexer follows specification in https://www.chessclub.com/help/PGN-spec
|
20
|
+
* note that the lexer recognises but ignores comments and NAGs, so they will
|
21
|
+
not appear in any resaved games
|
22
|
+
|
23
|
+
== Position Definitions
|
24
|
+
|
25
|
+
Games are stored in an instance of +ChessData+::+Database+, and can be filtered using
|
26
|
+
the +search+ method. The search method takes a block defining the numbers of pieces of
|
27
|
+
each type and returns a new database containing just those games which match the
|
28
|
+
definition. For example, the following filters for 5-4 rook endings:
|
29
|
+
|
30
|
+
rook_endings_database = database.search do
|
31
|
+
exactly 1, "R", "r"
|
32
|
+
exactly 5, "P"
|
33
|
+
exactly 4, "p"
|
34
|
+
end
|
35
|
+
|
36
|
+
Filters include:
|
37
|
+
|
38
|
+
* 'exactly n, *pieces'
|
39
|
+
* 'at_least n, *pieces'
|
40
|
+
* 'at_most n, *pieces'
|
41
|
+
|
42
|
+
== Example: Extracting all 5-4 R+P endings
|
43
|
+
|
44
|
+
# Read database of games and query
|
45
|
+
|
46
|
+
require 'chess_data'
|
47
|
+
|
48
|
+
# create a database
|
49
|
+
database = ChessData::Database.new
|
50
|
+
|
51
|
+
# read in pgn files provided on command line and add them to the database
|
52
|
+
ARGV.each do |file|
|
53
|
+
puts "Reading from #{file}"
|
54
|
+
database.add_games_from file
|
55
|
+
end
|
56
|
+
|
57
|
+
# extract those games which at some point reach given position definition
|
58
|
+
selected = database.search do
|
59
|
+
exactly 1, "R", "r"
|
60
|
+
exactly 5, "P"
|
61
|
+
exactly 4, "p"
|
62
|
+
end
|
63
|
+
|
64
|
+
# report and save result
|
65
|
+
puts "Found #{selected.size} games"
|
66
|
+
selected.to_file "selected.pgn"
|
67
|
+
|
68
|
+
puts "Selected #{selected.size} out of #{database.size}"
|
69
|
+
|
70
|
+
== Example: Study a game move-by-move
|
71
|
+
|
72
|
+
The ChessData::Game#play_game method takes a block, which is passed the current
|
73
|
+
board and next move after each half move. The following example prints out the
|
74
|
+
board position and information to create a trace of the game:
|
75
|
+
|
76
|
+
$game.play_game do |board, next_move|
|
77
|
+
puts board.to_s
|
78
|
+
puts
|
79
|
+
puts "Move #{board.fullmove_number}: #{if board.to_move == "w" then "" else "... " end}#{next_move}"
|
80
|
+
end
|
81
|
+
|
82
|
+
Sample output:
|
83
|
+
|
84
|
+
Move 32: Rxd7+
|
85
|
+
........
|
86
|
+
..kR...p
|
87
|
+
....pb.Q
|
88
|
+
........
|
89
|
+
..P..Pq.
|
90
|
+
.Pn.....
|
91
|
+
......P.
|
92
|
+
......BK
|
93
|
+
|
94
|
+
Move 32: ... Kxd7
|
95
|
+
........
|
96
|
+
...k...p
|
97
|
+
....pb.Q
|
98
|
+
........
|
99
|
+
..P..Pq.
|
100
|
+
.Pn.....
|
101
|
+
......P.
|
102
|
+
......BK
|
103
|
+
|
104
|
+
Move 33: Qxf6
|
105
|
+
........
|
106
|
+
...k...p
|
107
|
+
....pQ..
|
108
|
+
........
|
109
|
+
..P..Pq.
|
110
|
+
.Pn.....
|
111
|
+
......P.
|
112
|
+
......BK
|
113
|
+
|
114
|
+
Move 33: ... 1/2-1/2
|
115
|
+
|
@@ -0,0 +1,324 @@
|
|
1
|
+
|
2
|
+
module ChessData
|
3
|
+
|
4
|
+
# Pieces are structures, made from:
|
5
|
+
# - piece: is a string "P", "p", "N", "n", etc
|
6
|
+
# - square: is square definition, either a symbol :e4 or string "E4"
|
7
|
+
PieceDefn = Struct.new(:piece, :square)
|
8
|
+
|
9
|
+
# Holds information about a chess position, including:
|
10
|
+
# - location of all pieces
|
11
|
+
# - options for castling king or queen side
|
12
|
+
# - halfmove and fullmove counts
|
13
|
+
# - possible enpassant target
|
14
|
+
#
|
15
|
+
class Board
|
16
|
+
# The next player to move, "w" or "b".
|
17
|
+
attr_accessor :to_move
|
18
|
+
# True if white king-side castling is valid
|
19
|
+
attr_accessor :white_king_side_castling
|
20
|
+
# True if white queen-side castling is valid
|
21
|
+
attr_accessor :white_queen_side_castling
|
22
|
+
# True if black king-side castling is valid
|
23
|
+
attr_accessor :black_king_side_castling
|
24
|
+
# True if black queen-side castling is valid
|
25
|
+
attr_accessor :black_queen_side_castling
|
26
|
+
# If enpassant is possible, holds the target square, or "-"
|
27
|
+
attr_accessor :enpassant_target
|
28
|
+
# Counts the number of half moves
|
29
|
+
attr_accessor :halfmove_clock
|
30
|
+
# Counts the number of full moves
|
31
|
+
attr_accessor :fullmove_number
|
32
|
+
|
33
|
+
# Creates an instance of an empty chess board
|
34
|
+
def initialize
|
35
|
+
@board = []
|
36
|
+
8.times do
|
37
|
+
@board << [nil] * 8
|
38
|
+
end
|
39
|
+
@to_move = "w"
|
40
|
+
@white_king_side_castling = false
|
41
|
+
@white_queen_side_castling = false
|
42
|
+
@black_king_side_castling = false
|
43
|
+
@black_queen_side_castling = false
|
44
|
+
@enpassant_target = "-"
|
45
|
+
@halfmove_clock = 0
|
46
|
+
@fullmove_number = 1
|
47
|
+
end
|
48
|
+
|
49
|
+
# Makes a full copy of this board instance
|
50
|
+
def clone
|
51
|
+
copy = Board.new
|
52
|
+
|
53
|
+
8.times do |row|
|
54
|
+
8.times do |col|
|
55
|
+
copy.set row, col, @board[row][col]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
copy.to_move = @to_move
|
59
|
+
copy.white_king_side_castling = @white_king_side_castling
|
60
|
+
copy.white_queen_side_castling = @white_queen_side_castling
|
61
|
+
copy.black_king_side_castling = @black_king_side_castling
|
62
|
+
copy.black_queen_side_castling = @black_queen_side_castling
|
63
|
+
copy.enpassant_target = @enpassant_target
|
64
|
+
copy.halfmove_clock = @halfmove_clock
|
65
|
+
copy.fullmove_number = @fullmove_number
|
66
|
+
|
67
|
+
return copy
|
68
|
+
end
|
69
|
+
|
70
|
+
# Provide a way of looking up items based on usual chess
|
71
|
+
# notation, i.e. :e4 or "E4".
|
72
|
+
# Raises an ArgumentError if square is not a valid chessboard position.
|
73
|
+
# @param [String, Symbol] square is location to find
|
74
|
+
# @return [String] chess on the given square
|
75
|
+
def [](square)
|
76
|
+
col, row = Board.square_to_coords square
|
77
|
+
return @board[row][col]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Change the piece on a given square.
|
81
|
+
# @param [String, Symbol] square is location to change
|
82
|
+
# @param [String] piece
|
83
|
+
# @return [String] chess on the given square
|
84
|
+
def []=(square, piece)
|
85
|
+
col, row = Board.square_to_coords square
|
86
|
+
@board[row][col] = piece
|
87
|
+
end
|
88
|
+
|
89
|
+
# Compare two boards for equality
|
90
|
+
def == board
|
91
|
+
return false unless @to_move == board.to_move &&
|
92
|
+
@white_king_side_castling == board.white_king_side_castling &&
|
93
|
+
@white_queen_side_castling == board.white_queen_side_castling &&
|
94
|
+
@black_king_side_castling == board.black_king_side_castling &&
|
95
|
+
@black_queen_side_castling == board.black_queen_side_castling &&
|
96
|
+
@enpassant_target == board.enpassant_target &&
|
97
|
+
@halfmove_clock == board.halfmove_clock &&
|
98
|
+
@fullmove_number == board.fullmove_number
|
99
|
+
|
100
|
+
8.times do |i|
|
101
|
+
8.times do |j|
|
102
|
+
square = Board.coords_to_square i, j
|
103
|
+
return false unless self[square] == board[square]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return the location of given piece on board.
|
111
|
+
# Identifier can be a letter or number, and if present the piece location must contain it
|
112
|
+
def locations_of piece, identifier=""
|
113
|
+
identifier = identifier.upcase
|
114
|
+
result = []
|
115
|
+
|
116
|
+
8.times do |row|
|
117
|
+
8.times do |col|
|
118
|
+
if @board[row][col] == piece
|
119
|
+
square = Board.coords_to_square col, row
|
120
|
+
if identifier.empty? || square.include?(identifier)
|
121
|
+
result << square
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
return result
|
128
|
+
end
|
129
|
+
|
130
|
+
# Count the number of occurrences of the given piece on the board.
|
131
|
+
def count piece
|
132
|
+
@board.flatten.count piece
|
133
|
+
end
|
134
|
+
|
135
|
+
# Creates a simple 2D board representation, suitable for printing to a terminal.
|
136
|
+
def to_s
|
137
|
+
result = ""
|
138
|
+
|
139
|
+
8.times do |i|
|
140
|
+
8.times do |j|
|
141
|
+
square = Board.coords_to_square j, i
|
142
|
+
piece = self[square]
|
143
|
+
piece = "." if piece.nil?
|
144
|
+
result += piece
|
145
|
+
end
|
146
|
+
result += "\n"
|
147
|
+
end
|
148
|
+
|
149
|
+
return result
|
150
|
+
end
|
151
|
+
|
152
|
+
# Check if the white king is in check.
|
153
|
+
def white_king_in_check?
|
154
|
+
white_king = locations_of("K").first
|
155
|
+
black_pieces.any? do |defn|
|
156
|
+
Moves.can_reach self, defn.piece, defn.square, white_king
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if the black king is in check.
|
161
|
+
def black_king_in_check?
|
162
|
+
black_king = locations_of("k").first
|
163
|
+
white_pieces.any? do |defn|
|
164
|
+
Moves.can_reach self, defn.piece, defn.square, black_king
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Creates a chessboard from a FEN description.
|
169
|
+
# The FEN description may be a single string, representing a board
|
170
|
+
# or a full six-field description.
|
171
|
+
# Raises an ArgumentError if fen is not a valid FEN description.
|
172
|
+
#
|
173
|
+
# @param [String] fen a board definition in FEN format
|
174
|
+
# @return [Board] an instance of board matching the FEN description
|
175
|
+
def Board.from_fen fen
|
176
|
+
fields = fen.split " "
|
177
|
+
unless fields.length == 1 || fields.length == 6
|
178
|
+
raise ArgumentError, "Invalid FEN description"
|
179
|
+
end
|
180
|
+
# create and populate a new instance of ChessBoard
|
181
|
+
board = Board.new
|
182
|
+
board.send(:setup_board_from_fen, fields[0])
|
183
|
+
if fields.length == 6
|
184
|
+
board.to_move = fields[1].downcase
|
185
|
+
board.white_king_side_castling = fields[2].include? "K"
|
186
|
+
board.white_queen_side_castling = fields[2].include? "Q"
|
187
|
+
board.black_king_side_castling = fields[2].include? "k"
|
188
|
+
board.black_queen_side_castling = fields[2].include? "q"
|
189
|
+
board.enpassant_target = fields[3]
|
190
|
+
board.halfmove_clock = fields[4].to_i
|
191
|
+
board.fullmove_number = fields[5].to_i
|
192
|
+
end
|
193
|
+
|
194
|
+
return board
|
195
|
+
end
|
196
|
+
|
197
|
+
# Creates a board instance representing the start position.
|
198
|
+
def Board.start_position
|
199
|
+
Board.from_fen \
|
200
|
+
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# Converts array coordinates into a square representation.
|
205
|
+
#
|
206
|
+
# > ChessBoard::Board.coords_to_square 0, 7 => "A8"
|
207
|
+
# > ChessBoard::Board.coords_to_square 7, 0 => "H1"
|
208
|
+
# > ChessBoard::Board.coords_to_square 4, 4 => "E5"
|
209
|
+
#
|
210
|
+
# The conversion is cached, for speed.
|
211
|
+
#
|
212
|
+
def Board.coords_to_square col, row
|
213
|
+
unless defined? @coords_store
|
214
|
+
@coords_store = []
|
215
|
+
8.times do
|
216
|
+
@coords_store << [nil] * 8
|
217
|
+
end
|
218
|
+
8.times do |cl|
|
219
|
+
8.times do |rw|
|
220
|
+
@coords_store[cl][rw] = Board.square_from_coords(cl, rw)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
return @coords_store[col][row]
|
226
|
+
end
|
227
|
+
|
228
|
+
# Converts a square represention into array coordinates.
|
229
|
+
#
|
230
|
+
# > ChessData::Board.square_to_coords "e4" => [4, 4]
|
231
|
+
# > ChessData::Board.square_to_coords "a8" => [0, 7]
|
232
|
+
# > ChessData::Board.square_to_coords "h1" => [7, 0]
|
233
|
+
#
|
234
|
+
# The conversion is cached, for speed.
|
235
|
+
#
|
236
|
+
def Board.square_to_coords square
|
237
|
+
unless defined? @square_hash
|
238
|
+
@square_hash = {}
|
239
|
+
8.times do |col|
|
240
|
+
8.times do |row|
|
241
|
+
@square_hash[Board.square_from_coords(col, row)] = [col, row]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
square = square.to_s.upcase # convert symbols to strings, ensure upper case
|
247
|
+
unless @square_hash.has_key? square
|
248
|
+
raise ArgumentError, "Invalid board notation -|#{square}|-"
|
249
|
+
end
|
250
|
+
|
251
|
+
return @square_hash[square]
|
252
|
+
end
|
253
|
+
|
254
|
+
# Provides a fast method to set value of board at given row/col index values
|
255
|
+
# -- used to optimise clone
|
256
|
+
def set row, col, value
|
257
|
+
@board[row][col] = value
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
# Converts a square represention into array coordinates.
|
263
|
+
#
|
264
|
+
# > ChessData::Board.square_from_coords "e4" => [4, 4]
|
265
|
+
# > ChessData::Board.square_from_coords "a1" => [0, 7]
|
266
|
+
# > ChessData::Board.square_from_coords "h8" => [7, 0]
|
267
|
+
#
|
268
|
+
def Board.square_from_coords col, row
|
269
|
+
first = (65+col).chr
|
270
|
+
second = (49+(7-row)).chr
|
271
|
+
return "#{first}#{second}"
|
272
|
+
end
|
273
|
+
|
274
|
+
# Setup the current board
|
275
|
+
def setup_board_from_fen fen
|
276
|
+
rows = fen.split "/"
|
277
|
+
unless rows.length == 8
|
278
|
+
raise ArgumentError, "Invalid FEN description"
|
279
|
+
end
|
280
|
+
8.times do |row|
|
281
|
+
col = 0
|
282
|
+
rows[row].chars.each do |i|
|
283
|
+
case i
|
284
|
+
when "K", "k", "Q", "q", "R", "r", "N", "n", "B", "b", "P", "p"
|
285
|
+
@board[row][col] = i
|
286
|
+
col += 1
|
287
|
+
when /[1-8]/
|
288
|
+
col += i.to_i
|
289
|
+
else
|
290
|
+
raise ArgumentError, "Invalid character in FEN description"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Returns the location of all white pieces and pawns.
|
297
|
+
def white_pieces
|
298
|
+
find_pieces "KQRBNP"
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns the location of all black pieces and pawns.
|
302
|
+
def black_pieces
|
303
|
+
find_pieces "kqrbnp"
|
304
|
+
end
|
305
|
+
|
306
|
+
# Returns piece+position for all pieces on the board which are in the given
|
307
|
+
# list of pieces.
|
308
|
+
def find_pieces pieces
|
309
|
+
result = []
|
310
|
+
|
311
|
+
8.times do |row|
|
312
|
+
8.times do |col|
|
313
|
+
unless @board[row][col].nil?
|
314
|
+
if pieces.include? @board[row][col]
|
315
|
+
result << PieceDefn.new(@board[row][col], Board.coords_to_square(col, row))
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
return result
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
module ChessData
|
3
|
+
|
4
|
+
# Class to store a set of chess games
|
5
|
+
#
|
6
|
+
# The usual way to create a database is directly from a filename.
|
7
|
+
# If your pgn file is called 'my_games.pgn', create a database using
|
8
|
+
#
|
9
|
+
# Database.from_file 'my_games.pgn'
|
10
|
+
#
|
11
|
+
class Database
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@games = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a given _game_ to the database.
|
19
|
+
def << game
|
20
|
+
@games << game
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the game at the given _index_ value in the database.
|
24
|
+
def [] index
|
25
|
+
@games[index]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the number of games in the database.
|
29
|
+
def size
|
30
|
+
@games.size
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a new database based on search criteria given in the block.
|
34
|
+
# Each game is scanned to see if it meets the criteria. Also see Game#search.
|
35
|
+
def search &block
|
36
|
+
db = Database.new
|
37
|
+
|
38
|
+
@games.each do |game|
|
39
|
+
db << game if game.search(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
return db
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add games from a specified filename to database.
|
46
|
+
# filename:: the name of a pgn file from which to load games
|
47
|
+
# encoding:: defaults to "iso-8859-1" (which works for TWIC files).
|
48
|
+
def add_games_from filename, encoding: "iso-8859-1"
|
49
|
+
File.open filename, mode: "r", encoding: encoding do |file|
|
50
|
+
begin
|
51
|
+
begin
|
52
|
+
game = Game.from_pgn file
|
53
|
+
@games << game unless game.nil?
|
54
|
+
rescue ArgumentError
|
55
|
+
puts "Error in #{filename}, previous game #{@games.last.white} vs #{@games.last.black}"
|
56
|
+
return
|
57
|
+
end
|
58
|
+
end until file.eof?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Save the database in PGN format to given _filename_.
|
63
|
+
# If filename exists, the games are _appended_ to the file.
|
64
|
+
def to_file filename
|
65
|
+
File.open(filename, "a") do |f|
|
66
|
+
@games.each do |game|
|
67
|
+
game.to_pgn f
|
68
|
+
f.puts
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Loads and returns a new database based on given filename.
|
74
|
+
# filename:: the name of a pgn file from which to load games
|
75
|
+
# encoding:: defaults to "iso-8859-1" (which works for TWIC files).
|
76
|
+
def Database.from_file filename, encoding: "iso-8859-1"
|
77
|
+
database = Database.new
|
78
|
+
database.add_games_from filename, encoding: encoding
|
79
|
+
return database
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|