pgn2 1.5.0 → 2.0.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 +4 -4
- data/.github/workflows/ci.yml +34 -3
- data/.github/workflows/native.yml +32 -0
- data/.github/workflows/publish.yml +44 -2
- data/.github/workflows/release-gems.yml +40 -0
- data/.github/workflows/release.yml +52 -5
- data/.gitignore +9 -1
- data/.rubocop.yml +46 -6
- data/CHANGELOG.md +148 -0
- data/Gemfile +3 -0
- data/NOTICE.md +21 -0
- data/README.md +107 -5
- data/Rakefile +35 -10
- data/TODO.md +64 -0
- data/bench/baseline_moves.txt +38 -4
- data/bench/baseline_parse.txt +4 -4
- data/bench/cross_check.rb +61 -0
- data/bench/legal_moves.rb +38 -0
- data/bench/perft.rb +32 -0
- data/bench/profile_moves.rb +93 -0
- data/docs/superpowers/plans/2026-08-13-attack-masks-plan.md +51 -0
- data/docs/superpowers/plans/2026-08-13-perf-internals-plan.md +883 -0
- data/docs/superpowers/plans/2026-08-13-rust-bitboard-perft-plan.md +2442 -0
- data/docs/superpowers/plans/2026-08-14-chessie-migration.md +722 -0
- data/docs/superpowers/plans/2026-08-14-rust-integration-plan.md +444 -0
- data/docs/superpowers/specs/2026-08-13-attack-masks-design.md +57 -0
- data/docs/superpowers/specs/2026-08-13-perf-internals-design.md +111 -0
- data/docs/superpowers/specs/2026-08-13-rust-bitboard-perft-design.md +270 -0
- data/docs/superpowers/specs/2026-08-14-rust-integration-design.md +217 -0
- data/ext/pgn2_native/Cargo.lock +321 -0
- data/ext/pgn2_native/Cargo.toml +19 -0
- data/ext/pgn2_native/extconf.rb +8 -0
- data/ext/pgn2_native/pgn2-bitboard/Cargo.toml +10 -0
- data/ext/pgn2_native/pgn2-bitboard/src/board.rs +32 -0
- data/ext/pgn2_native/pgn2-bitboard/src/lib.rs +12 -0
- data/ext/pgn2_native/pgn2-bitboard/src/moves.rs +121 -0
- data/ext/pgn2_native/pgn2-bitboard/src/perft.rs +81 -0
- data/ext/pgn2_native/pgn2_native/Cargo.toml +11 -0
- data/ext/pgn2_native/pgn2_native/src/lib.rs +54 -0
- data/lib/pgn/bitboard.rb +13 -0
- data/lib/pgn/board.rb +64 -0
- data/lib/pgn/fen.rb +35 -46
- data/lib/pgn/game.rb +22 -13
- data/lib/pgn/move.rb +19 -15
- data/lib/pgn/move_calculator.rb +13 -1
- data/lib/pgn/notation.rb +24 -29
- data/lib/pgn/position.rb +60 -19
- data/lib/pgn/serializer.rb +13 -18
- data/lib/pgn/version.rb +1 -1
- data/lib/pgn/zobrist.rb +53 -0
- data/lib/pgn.rb +2 -0
- data/pgn2.gemspec +17 -10
- data/spec/bitboard_spec.rb +54 -0
- data/spec/board_spec.rb +53 -0
- data/spec/fen_spec.rb +65 -65
- data/spec/game_spec.rb +52 -15
- data/spec/lexer_spec.rb +5 -5
- data/spec/notation_spec.rb +5 -0
- data/spec/parser_spec.rb +8 -1
- data/spec/position_spec.rb +128 -27
- data/spec/serializer_spec.rb +4 -4
- data/spec/zobrist_spec.rb +46 -0
- metadata +97 -35
data/spec/lexer_spec.rb
CHANGED
|
@@ -17,7 +17,7 @@ RSpec.describe PGN::Lexer do
|
|
|
17
17
|
|
|
18
18
|
it 'discards % rest-of-line comments' do
|
|
19
19
|
toks = lex("% a comment line\n[Event \"X\"]")
|
|
20
|
-
expect(toks.map(&:type)).to eq([
|
|
20
|
+
expect(toks.map(&:type)).to eq(%i[lbracket tag_name string rbracket])
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it 'tokenizes the four single-character literals' do
|
|
@@ -57,8 +57,8 @@ RSpec.describe PGN::Lexer do
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
it 'tokenizes nested brace comments recursively' do
|
|
60
|
-
toks = lex(
|
|
61
|
-
expect(toks[0].value).to eq(
|
|
60
|
+
toks = lex('{outer {inner} tail}')
|
|
61
|
+
expect(toks[0].value).to eq('{outer {inner} tail}')
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
it 'tokenizes all game terminations' do
|
|
@@ -116,8 +116,8 @@ RSpec.describe PGN::Lexer do
|
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
it 'records byte offsets on every token' do
|
|
119
|
-
toks = lex(
|
|
120
|
-
expect(toks[0].offset).to eq(0)
|
|
119
|
+
toks = lex('1. e4 e5 1-0')
|
|
120
|
+
expect(toks[0].offset).to eq(0) # "1."
|
|
121
121
|
expect(toks[1].offset).to eq(3) # "e4"
|
|
122
122
|
expect(toks[2].offset).to eq(6) # "e5"
|
|
123
123
|
expect(toks[3].offset).to eq(9) # "1-0"
|
data/spec/notation_spec.rb
CHANGED
|
@@ -22,6 +22,11 @@ describe PGN::Notation do
|
|
|
22
22
|
'g1', 'f3')).to eq('Nf3')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
it 'generates a non-castling king walk' do
|
|
26
|
+
fen = '4k3/8/8/8/8/8/8/4K3 w - - 0 1'
|
|
27
|
+
expect(PGN::Notation.san(pos(fen), 'e1', 'e2')).to eq('Ke2')
|
|
28
|
+
end
|
|
29
|
+
|
|
25
30
|
it 'generates a pawn capture with the origin file' do
|
|
26
31
|
fen = 'rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2'
|
|
27
32
|
expect(PGN::Notation.san(pos(fen), 'e4', 'd5')).to eq('exd5')
|
data/spec/parser_spec.rb
CHANGED
|
@@ -107,7 +107,14 @@ describe PGN do
|
|
|
107
107
|
it 'returns original game pgn' do
|
|
108
108
|
games = PGN.parse(File.read('./spec/pgn_files/fen.pgn'))
|
|
109
109
|
game = games.first
|
|
110
|
-
|
|
110
|
+
expected = <<~PGN.chomp
|
|
111
|
+
[Event "Event 1"]
|
|
112
|
+
[FEN "4brkn/4bp1p/3q2pP/8/2B3N1/1P4N1/2PP3P/1K2Q3 w - - 0 1"]
|
|
113
|
+
[PlyCount "7"]
|
|
114
|
+
|
|
115
|
+
1. Qxe7 Qxe7 2. Ne4 Bc6 3. Nef6+ Qxf6 4. Nxf6# 1-0
|
|
116
|
+
PGN
|
|
117
|
+
expect(game.pgn).to eq expected
|
|
111
118
|
end
|
|
112
119
|
|
|
113
120
|
it 'returns original game pgn for second game' do
|
data/spec/position_spec.rb
CHANGED
|
@@ -1,53 +1,50 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe PGN::Position do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
it "should have fullmove 1" do
|
|
4
|
+
describe 'start' do
|
|
5
|
+
it 'should have fullmove 1' do
|
|
8
6
|
pos = PGN::Position.start
|
|
9
7
|
pos.fullmove.should == 1
|
|
10
8
|
end
|
|
11
|
-
|
|
12
9
|
end
|
|
13
|
-
|
|
14
|
-
context
|
|
15
|
-
describe
|
|
16
|
-
pos = PGN::FEN.new(
|
|
17
|
-
next_pos = pos.move(
|
|
18
|
-
|
|
19
|
-
it
|
|
20
|
-
next_pos.board.at(
|
|
10
|
+
|
|
11
|
+
context 'disambiguating moves' do
|
|
12
|
+
describe 'using SAN square disambiguation' do
|
|
13
|
+
pos = PGN::FEN.new('r1bqkb1r/pp1p1ppp/2n1pn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 3 6').to_position
|
|
14
|
+
next_pos = pos.move('Ndb5')
|
|
15
|
+
|
|
16
|
+
it 'should move the specified piece' do
|
|
17
|
+
next_pos.board.at('d4').should be_nil
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
it
|
|
24
|
-
next_pos.board.at(
|
|
20
|
+
it 'should not move the other piece' do
|
|
21
|
+
next_pos.board.at('c3').should == 'N'
|
|
25
22
|
end
|
|
26
23
|
end
|
|
27
24
|
|
|
28
|
-
describe
|
|
29
|
-
pos = PGN::FEN.new(
|
|
30
|
-
next_pos = pos.move(
|
|
25
|
+
describe 'using discovered check' do
|
|
26
|
+
pos = PGN::FEN.new('rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 5').to_position
|
|
27
|
+
next_pos = pos.move('Ne2')
|
|
31
28
|
|
|
32
29
|
it "should move the piece that doesn't give discovered check" do
|
|
33
|
-
next_pos.board.at(
|
|
30
|
+
next_pos.board.at('g1').should be_nil
|
|
34
31
|
end
|
|
35
32
|
|
|
36
33
|
it "shouldn't move the other piece" do
|
|
37
|
-
next_pos.board.at(
|
|
34
|
+
next_pos.board.at('c3').should == 'N'
|
|
38
35
|
end
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
describe
|
|
42
|
-
pos = PGN::FEN.new(
|
|
43
|
-
next_pos = pos.move(
|
|
38
|
+
describe 'with two pawns on the same file' do
|
|
39
|
+
pos = PGN::FEN.new('r2q1rk1/4bppp/p3n3/1p2n3/4N3/1B2BP2/PP3P1P/R2Q1RK1 w - - 4 19').to_position
|
|
40
|
+
next_pos = pos.move('f4')
|
|
44
41
|
|
|
45
|
-
it
|
|
46
|
-
next_pos.board.at(
|
|
42
|
+
it 'should move the pawn in front' do
|
|
43
|
+
next_pos.board.at('f3').should be_nil
|
|
47
44
|
end
|
|
48
45
|
|
|
49
|
-
it
|
|
50
|
-
next_pos.board.at(
|
|
46
|
+
it 'should not move the other pawn' do
|
|
47
|
+
next_pos.board.at('f2').should == 'P'
|
|
51
48
|
end
|
|
52
49
|
end
|
|
53
50
|
end
|
|
@@ -124,4 +121,108 @@ describe PGN::Position do
|
|
|
124
121
|
expect(parsed.to_fen.to_s).to eq(fen)
|
|
125
122
|
end
|
|
126
123
|
end
|
|
124
|
+
|
|
125
|
+
describe '#zobrist and equality' do
|
|
126
|
+
it 'seeds the start position with a stable hash' do
|
|
127
|
+
expect(PGN::Position.start.zobrist).to be_an(Integer)
|
|
128
|
+
expect(PGN::Position.start.zobrist).to eq(PGN::Position.start.zobrist)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'updates the hash after a move (and matches a fresh seed)' do
|
|
132
|
+
start = PGN::Position.start
|
|
133
|
+
after = start.move('e4')
|
|
134
|
+
expect(after.zobrist).to eq(
|
|
135
|
+
PGN::Zobrist.seed(after.board, after.player, after.castling, after.en_passant)
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'is equal to an independently built equivalent position' do
|
|
140
|
+
a = PGN::Position.start.move('e4').move('e5')
|
|
141
|
+
b = PGN::Position.start.move('e4').move('e5')
|
|
142
|
+
expect(a).to eq(b)
|
|
143
|
+
expect(a.hash).to eq(b.hash)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'is not equal when only the side to move differs' do
|
|
147
|
+
a = PGN::Position.start
|
|
148
|
+
b = PGN::Position.start.move('e4')
|
|
149
|
+
expect(a).not_to eq(b)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'is not equal when castling rights differ' do
|
|
153
|
+
fen = 'r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1'
|
|
154
|
+
a = PGN::FEN.new(fen).to_position
|
|
155
|
+
b = a.move('O-O')
|
|
156
|
+
expect(a).not_to eq(b)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'ignores halfmove and fullmove counters for equality' do
|
|
160
|
+
a = PGN::Position.new(PGN::Board.start, :white, %w[K Q k q], nil, 0, 1)
|
|
161
|
+
b = PGN::Position.new(PGN::Board.start, :white, %w[K Q k q], nil, 7, 9)
|
|
162
|
+
expect(a).to eq(b)
|
|
163
|
+
expect(a.hash).to eq(b.hash)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'keeps the replay hash in sync with a fresh seed for a whole game' do
|
|
167
|
+
moves = %w[e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be2 e6]
|
|
168
|
+
pos = PGN::Position.start
|
|
169
|
+
moves.each do |m|
|
|
170
|
+
pos = pos.move(m)
|
|
171
|
+
expect(pos.zobrist).to eq(
|
|
172
|
+
PGN::Zobrist.seed(pos.board, pos.player, pos.castling, pos.en_passant)
|
|
173
|
+
), "drift after #{m}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
RSpec.describe PGN::Position, '#perft' do
|
|
180
|
+
it 'returns 1 at depth 0 for the start position' do
|
|
181
|
+
expect(PGN::Position.start.perft(0)).to eq(1)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'matches published startpos perft values' do
|
|
185
|
+
p = PGN::Position.start
|
|
186
|
+
expect(p.perft(1)).to eq(20)
|
|
187
|
+
expect(p.perft(2)).to eq(400)
|
|
188
|
+
expect(p.perft(3)).to eq(8_902)
|
|
189
|
+
expect(p.perft(4)).to eq(197_281)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'matches published Kiwipete perft values' do
|
|
193
|
+
fen = 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'
|
|
194
|
+
p = PGN::FEN.new(fen).to_position
|
|
195
|
+
expect(p.perft(1)).to eq(48)
|
|
196
|
+
expect(p.perft(2)).to eq(2_039)
|
|
197
|
+
expect(p.perft(3)).to eq(97_862)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'raises ArgumentError on negative depth' do
|
|
201
|
+
expect { PGN::Position.start.perft(-1) }.to raise_error(ArgumentError)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
RSpec.describe PGN::Position, '#legal_moves' do
|
|
206
|
+
it 'lists 20 legal moves from the start position' do
|
|
207
|
+
expect(PGN::Position.start.legal_moves.length).to eq(20)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'returns sorted UCI strings matching the UCI format' do
|
|
211
|
+
moves = PGN::Position.start.legal_moves
|
|
212
|
+
expect(moves).to eq(moves.sort)
|
|
213
|
+
expect(moves).to all(match(/\A[a-h][1-8][a-h][1-8][qrbn]?\z/))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'matches the engine direct output for the same FEN (delegation equivalence)' do
|
|
217
|
+
fen = 'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1'
|
|
218
|
+
pos = PGN::FEN.new(fen).to_position
|
|
219
|
+
expect(pos.legal_moves).to eq(PGN::Bitboard::Engine.new(fen).legal_moves)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'includes a promotion UCI when one is legal' do
|
|
223
|
+
# White king e1, black king h1, white pawn e7 — e7e8q must be legal.
|
|
224
|
+
fen = '8/4P3/8/8/8/8/8/4K2k w - - 0 1'
|
|
225
|
+
pos = PGN::FEN.new(fen).to_position
|
|
226
|
+
expect(pos.legal_moves).to include('e7e8q')
|
|
227
|
+
end
|
|
127
228
|
end
|
data/spec/serializer_spec.rb
CHANGED
|
@@ -62,10 +62,10 @@ describe PGN::Serializer do
|
|
|
62
62
|
it 'reproduces the variations.pgn movetext shape, including 2... after variations' do
|
|
63
63
|
game = PGN.parse(File.read('./spec/pgn_files/variations.pgn')).first
|
|
64
64
|
expected = "[Black \"Petrov\"]\n[White \"Somebody\"]\n\n" \
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
'1. e4 e5 2. Nf3 { comment } ' \
|
|
66
|
+
'(2. f4 exf4 { final variation }) ' \
|
|
67
|
+
'(2. Nc3 { other } 2... d5 (2... f5) 3. exd5) ' \
|
|
68
|
+
"2... Nf6 *\n"
|
|
69
69
|
expect(PGN::Serializer.new(game).to_s).to eq(expected)
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PGN::Zobrist do
|
|
6
|
+
it 'exposes table, side, castling, and ep_file constants' do
|
|
7
|
+
expect(PGN::Zobrist::TABLE).to be_a(Hash)
|
|
8
|
+
expect(PGN::Zobrist::TABLE.keys.sort).to eq(%w[B K N P Q R b k n p q r].sort)
|
|
9
|
+
expect(PGN::Zobrist::TABLE['P'].length).to eq(128)
|
|
10
|
+
expect(PGN::Zobrist::SIDE).to be_an(Integer)
|
|
11
|
+
expect(PGN::Zobrist::CASTLING).to be_a(Hash)
|
|
12
|
+
expect(PGN::Zobrist::CASTLING.keys.sort).to eq(%w[K Q k q].sort)
|
|
13
|
+
expect(PGN::Zobrist::EP_FILE.length).to eq(8)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'seeds the starting position to a stable integer' do
|
|
17
|
+
first = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
|
|
18
|
+
second = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
|
|
19
|
+
expect(first).to eq(second)
|
|
20
|
+
expect(first).to be_an(Integer)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'differs when the side to move changes' do
|
|
24
|
+
white_to_move = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
|
|
25
|
+
black_to_move = PGN::Zobrist.seed(PGN::Board.start, :black, %w[K Q k q], nil)
|
|
26
|
+
expect(white_to_move).not_to eq(black_to_move)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'differs when castling rights change' do
|
|
30
|
+
full = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
|
|
31
|
+
no_k = PGN::Zobrist.seed(PGN::Board.start, :white, %w[Q k q], nil)
|
|
32
|
+
expect(full).not_to eq(no_k)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'differs when an en-passant file is present vs absent' do
|
|
36
|
+
with_ep = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], 'e3')
|
|
37
|
+
no_ep = PGN::Zobrist.seed(PGN::Board.start, :white, %w[K Q k q], nil)
|
|
38
|
+
expect(with_ep).not_to eq(no_ep)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'is deterministic across processes (frozen constants)' do
|
|
42
|
+
expect(PGN::Zobrist::TABLE).to be_frozen
|
|
43
|
+
expect(PGN::Zobrist::CASTLING).to be_frozen
|
|
44
|
+
expect(PGN::Zobrist::EP_FILE).to be_frozen
|
|
45
|
+
end
|
|
46
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgn2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stacey Touset
|
|
@@ -9,8 +9,64 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-08-
|
|
12
|
+
date: 2026-08-14 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: rb_sys
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: 0.9.39
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: 0.9.39
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: rake-compiler
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '1.2'
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '1.2'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: rake-compiler-dock
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '1.6'
|
|
49
|
+
type: :development
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '1.6'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: benchmark-ips
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
14
70
|
- !ruby/object:Gem::Dependency
|
|
15
71
|
name: bundler
|
|
16
72
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -26,7 +82,7 @@ dependencies:
|
|
|
26
82
|
- !ruby/object:Gem::Version
|
|
27
83
|
version: '2.3'
|
|
28
84
|
- !ruby/object:Gem::Dependency
|
|
29
|
-
name:
|
|
85
|
+
name: bundler-audit
|
|
30
86
|
requirement: !ruby/object:Gem::Requirement
|
|
31
87
|
requirements:
|
|
32
88
|
- - ">="
|
|
@@ -40,7 +96,7 @@ dependencies:
|
|
|
40
96
|
- !ruby/object:Gem::Version
|
|
41
97
|
version: '0'
|
|
42
98
|
- !ruby/object:Gem::Dependency
|
|
43
|
-
name:
|
|
99
|
+
name: memory_profiler
|
|
44
100
|
requirement: !ruby/object:Gem::Requirement
|
|
45
101
|
requirements:
|
|
46
102
|
- - ">="
|
|
@@ -54,7 +110,7 @@ dependencies:
|
|
|
54
110
|
- !ruby/object:Gem::Version
|
|
55
111
|
version: '0'
|
|
56
112
|
- !ruby/object:Gem::Dependency
|
|
57
|
-
name:
|
|
113
|
+
name: pry
|
|
58
114
|
requirement: !ruby/object:Gem::Requirement
|
|
59
115
|
requirements:
|
|
60
116
|
- - ">="
|
|
@@ -82,7 +138,7 @@ dependencies:
|
|
|
82
138
|
- !ruby/object:Gem::Version
|
|
83
139
|
version: '0'
|
|
84
140
|
- !ruby/object:Gem::Dependency
|
|
85
|
-
name:
|
|
141
|
+
name: rake
|
|
86
142
|
requirement: !ruby/object:Gem::Requirement
|
|
87
143
|
requirements:
|
|
88
144
|
- - ">="
|
|
@@ -96,7 +152,7 @@ dependencies:
|
|
|
96
152
|
- !ruby/object:Gem::Version
|
|
97
153
|
version: '0'
|
|
98
154
|
- !ruby/object:Gem::Dependency
|
|
99
|
-
name:
|
|
155
|
+
name: rspec
|
|
100
156
|
requirement: !ruby/object:Gem::Requirement
|
|
101
157
|
requirements:
|
|
102
158
|
- - ">="
|
|
@@ -128,11 +184,14 @@ email:
|
|
|
128
184
|
- stacey@touset.org
|
|
129
185
|
- muriloime@gmail.com
|
|
130
186
|
executables: []
|
|
131
|
-
extensions:
|
|
187
|
+
extensions:
|
|
188
|
+
- ext/pgn2_native/extconf.rb
|
|
132
189
|
extra_rdoc_files: []
|
|
133
190
|
files:
|
|
134
191
|
- ".github/workflows/ci.yml"
|
|
192
|
+
- ".github/workflows/native.yml"
|
|
135
193
|
- ".github/workflows/publish.yml"
|
|
194
|
+
- ".github/workflows/release-gems.yml"
|
|
136
195
|
- ".github/workflows/release.yml"
|
|
137
196
|
- ".gitignore"
|
|
138
197
|
- ".rspec"
|
|
@@ -140,6 +199,7 @@ files:
|
|
|
140
199
|
- CHANGELOG.md
|
|
141
200
|
- Gemfile
|
|
142
201
|
- LICENSE.txt
|
|
202
|
+
- NOTICE.md
|
|
143
203
|
- README.md
|
|
144
204
|
- Rakefile
|
|
145
205
|
- TODO.md
|
|
@@ -152,16 +212,39 @@ files:
|
|
|
152
212
|
- bench/baseline_parse.pre-quickwins.txt
|
|
153
213
|
- bench/baseline_parse.racc.txt
|
|
154
214
|
- bench/baseline_parse.txt
|
|
215
|
+
- bench/cross_check.rb
|
|
216
|
+
- bench/legal_moves.rb
|
|
217
|
+
- bench/perft.rb
|
|
155
218
|
- bench/profile_moves.rb
|
|
156
219
|
- bench/profile_parse.rb
|
|
157
220
|
- docs/superpowers/plans/2026-08-12-efficiency-optimizations.md
|
|
158
221
|
- docs/superpowers/plans/2026-08-12-efficiency-tests-and-profiling.md
|
|
159
222
|
- docs/superpowers/plans/2026-08-12-to-pgn-serialization.md
|
|
223
|
+
- docs/superpowers/plans/2026-08-13-attack-masks-plan.md
|
|
224
|
+
- docs/superpowers/plans/2026-08-13-perf-internals-plan.md
|
|
225
|
+
- docs/superpowers/plans/2026-08-13-rust-bitboard-perft-plan.md
|
|
160
226
|
- docs/superpowers/plans/2026-08-13-whittle-to-racc-migration.md
|
|
227
|
+
- docs/superpowers/plans/2026-08-14-chessie-migration.md
|
|
228
|
+
- docs/superpowers/plans/2026-08-14-rust-integration-plan.md
|
|
161
229
|
- docs/superpowers/specs/2026-08-12-to-pgn-serialization-design.md
|
|
230
|
+
- docs/superpowers/specs/2026-08-13-attack-masks-design.md
|
|
231
|
+
- docs/superpowers/specs/2026-08-13-perf-internals-design.md
|
|
162
232
|
- docs/superpowers/specs/2026-08-13-pgn-performance-quick-wins-design.md
|
|
233
|
+
- docs/superpowers/specs/2026-08-13-rust-bitboard-perft-design.md
|
|
234
|
+
- docs/superpowers/specs/2026-08-14-rust-integration-design.md
|
|
163
235
|
- examples/immortal_game.pgn
|
|
236
|
+
- ext/pgn2_native/Cargo.lock
|
|
237
|
+
- ext/pgn2_native/Cargo.toml
|
|
238
|
+
- ext/pgn2_native/extconf.rb
|
|
239
|
+
- ext/pgn2_native/pgn2-bitboard/Cargo.toml
|
|
240
|
+
- ext/pgn2_native/pgn2-bitboard/src/board.rs
|
|
241
|
+
- ext/pgn2_native/pgn2-bitboard/src/lib.rs
|
|
242
|
+
- ext/pgn2_native/pgn2-bitboard/src/moves.rs
|
|
243
|
+
- ext/pgn2_native/pgn2-bitboard/src/perft.rs
|
|
244
|
+
- ext/pgn2_native/pgn2_native/Cargo.toml
|
|
245
|
+
- ext/pgn2_native/pgn2_native/src/lib.rs
|
|
164
246
|
- lib/pgn.rb
|
|
247
|
+
- lib/pgn/bitboard.rb
|
|
165
248
|
- lib/pgn/board.rb
|
|
166
249
|
- lib/pgn/fen.rb
|
|
167
250
|
- lib/pgn/game.rb
|
|
@@ -175,7 +258,9 @@ files:
|
|
|
175
258
|
- lib/pgn/position.rb
|
|
176
259
|
- lib/pgn/serializer.rb
|
|
177
260
|
- lib/pgn/version.rb
|
|
261
|
+
- lib/pgn/zobrist.rb
|
|
178
262
|
- pgn2.gemspec
|
|
263
|
+
- spec/bitboard_spec.rb
|
|
179
264
|
- spec/board_spec.rb
|
|
180
265
|
- spec/fen_spec.rb
|
|
181
266
|
- spec/game_spec.rb
|
|
@@ -202,14 +287,17 @@ files:
|
|
|
202
287
|
- spec/position_spec.rb
|
|
203
288
|
- spec/serializer_spec.rb
|
|
204
289
|
- spec/spec_helper.rb
|
|
290
|
+
- spec/zobrist_spec.rb
|
|
205
291
|
homepage: https://github.com/muriloime/pgn
|
|
206
292
|
licenses:
|
|
207
293
|
- MIT
|
|
294
|
+
- MPL-2.0
|
|
208
295
|
metadata:
|
|
209
296
|
homepage_uri: https://github.com/muriloime/pgn
|
|
210
297
|
source_code_uri: https://github.com/muriloime/pgn
|
|
211
298
|
bug_tracker_uri: https://github.com/muriloime/pgn/issues
|
|
212
299
|
changelog_uri: https://github.com/muriloime/pgn/blob/main/CHANGELOG.md
|
|
300
|
+
rubygems_mfa_required: 'true'
|
|
213
301
|
post_install_message:
|
|
214
302
|
rdoc_options: []
|
|
215
303
|
require_paths:
|
|
@@ -229,30 +317,4 @@ rubygems_version: 3.5.22
|
|
|
229
317
|
signing_key:
|
|
230
318
|
specification_version: 4
|
|
231
319
|
summary: A PGN parser for Ruby
|
|
232
|
-
test_files:
|
|
233
|
-
- spec/board_spec.rb
|
|
234
|
-
- spec/fen_spec.rb
|
|
235
|
-
- spec/game_spec.rb
|
|
236
|
-
- spec/lexer_spec.rb
|
|
237
|
-
- spec/move_calculator_spec.rb
|
|
238
|
-
- spec/move_spec.rb
|
|
239
|
-
- spec/notation_spec.rb
|
|
240
|
-
- spec/parser_explicit_spec.rb
|
|
241
|
-
- spec/parser_spec.rb
|
|
242
|
-
- spec/pgn_files/alternate_castling.pgn
|
|
243
|
-
- spec/pgn_files/annotations.pgn
|
|
244
|
-
- spec/pgn_files/comments.pgn
|
|
245
|
-
- spec/pgn_files/doublequotes.pgn
|
|
246
|
-
- spec/pgn_files/empty_variation_move.pgn
|
|
247
|
-
- spec/pgn_files/fen.pgn
|
|
248
|
-
- spec/pgn_files/multiline_comments.pgn
|
|
249
|
-
- spec/pgn_files/nested_comments.pgn
|
|
250
|
-
- spec/pgn_files/no_moves.pgn
|
|
251
|
-
- spec/pgn_files/specialcharacters.pgn
|
|
252
|
-
- spec/pgn_files/test.pgn
|
|
253
|
-
- spec/pgn_files/two_annotations.pgn
|
|
254
|
-
- spec/pgn_files/two_games.pgn
|
|
255
|
-
- spec/pgn_files/variations.pgn
|
|
256
|
-
- spec/position_spec.rb
|
|
257
|
-
- spec/serializer_spec.rb
|
|
258
|
-
- spec/spec_helper.rb
|
|
320
|
+
test_files: []
|