chess_data 1.0.6
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/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
|
+
|