pgn2 1.2.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a5ce857277684a9f89c02d35941d7c455df2ca8a18f557985f3ca92eed414b3
4
- data.tar.gz: 5f126a3da7a6f314748495919168336bb7b97d07e5ad0c9f2ac9b9956a8bb4be
3
+ metadata.gz: 963f48df8fbfe5d69097030a9434a05b82e211c235772273f03bd11178afb8a5
4
+ data.tar.gz: 6bc38e0387e7061e3649fe52d22e5f01dabdc7029f8c27294d56b62a3779a76c
5
5
  SHA512:
6
- metadata.gz: '079c4e3c1dd292c58fac802349c8d9d8054efa4a01288090bd0a38472029b8848a162cd4264fe83135c80d14fb9ea65d531009ddc6dbd6237e30c4f4bb9efda8'
7
- data.tar.gz: e3ec9dbcf8dad46e7e8ec623642d468253a4ee6d5899dd9d1ac7486e60416b9069a49e34de092e08744439b9c40dce84074a6ddcaed91a584d88a65be6646b9a
6
+ metadata.gz: e81b88377f5d1ab6102a5b15d4d006feb4201647125b5b3d434b826dfa55cac60c82048fc8d4d2a4c8aa7d1600d6ba4edd472f27d063d258703d8e225c0832cc
7
+ data.tar.gz: 06cb00152b81f2124749fbc743356a25a7ee44e049c7779fb397660a84ebfefee8afcf4679f86ac633ff12b771db007123308cd5c65520ae8329e7334b340ab9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1 (2026-08-13)
4
+
5
+ ### Summary
6
+
7
+ Replay (move-application) throughput quick win on the `MoveCalculator`
8
+ hot path. No public API changes; serialized PGN/FEN output stays
9
+ byte-identical. See `bench/IMPROVEMENTS.md` for the per-step deltas.
10
+
11
+ ### Changed
12
+ - **`MoveCalculator#valid_square?`**: integer compares (`file >= 0 &&
13
+ file < 8`) instead of `(0..7).include?(file)`, which both allocates a
14
+ `Range` per call and is ~3.4x slower. This predicate sits inside the
15
+ `first_piece` / `move_origins` / `king_position` inner board-scanning
16
+ loops — the dominant replay compute site (~46% of replay CPU via
17
+ per-method timing, not the SAN regex parse as had been assumed).
18
+ - **`MoveCalculator#compute_origin`**: string `case` (`when 'B','R','Q',...`)
19
+ instead of `/[brq]/i` regex `===` matches, removing 4 `MatchData`
20
+ allocations per move.
21
+ - **`MoveCalculator#first_piece`**: returns only the `[file, rank]` square
22
+ (`nil` when the scan runs off the edge) instead of a `[piece, square]`
23
+ wrapper tuple; callers read the piece via a new `piece_at` helper. Removes
24
+ one array allocation per direction scan.
25
+ - Performance vs 1.2.0 (immortal game, 45 plies, fresh process each):
26
+ replay throughput 814.65 us/i -> 752.54 us/i (+8.2%); replay allocations
27
+ 1709 -> 1571 objects (-138, -8.1%) and 103720 -> 92440 bytes (-10.9%).
28
+ 182 specs pass; byte-identical FEN/PGN output; zero new rubocop offenses
29
+ vs 1.2.0. The piece-location-index rewrite (the real ~46% ceiling,
30
+ deferred "Approach B") is unchanged.
31
+
3
32
  ## 1.2.0 (2026-08-13)
4
33
 
5
34
  ### Summary
@@ -185,9 +185,9 @@ module PGN
185
185
  return nil if move.castle
186
186
 
187
187
  possibilities = case move.piece
188
- when /[brq]/i then direction_origins
189
- when /[kn]/i then move_origins
190
- when /p/i then pawn_origins
188
+ when 'B', 'R', 'Q', 'b', 'r', 'q' then direction_origins
189
+ when 'K', 'N', 'k', 'n' then move_origins
190
+ when 'P', 'p' then pawn_origins
191
191
  else # don't care move, used in variations
192
192
  return nil
193
193
  end
@@ -207,8 +207,8 @@ module PGN
207
207
  possibilities = []
208
208
 
209
209
  directions.each do |dir|
210
- piece, square = first_piece(destination_coords, dir)
211
- possibilities << square if piece == move.piece
210
+ square = first_piece(destination_coords, dir)
211
+ possibilities << square if piece_at(square) == move.piece
212
212
  end
213
213
 
214
214
  possibilities
@@ -285,28 +285,44 @@ module PGN
285
285
  attacking_piece = attacking_piece.upcase if move.black?
286
286
 
287
287
  directions.each do |dir|
288
- piece, square = first_piece(king_pos, dir)
289
- next unless piece == move.piece && possibilities.include?(square)
288
+ square = first_piece(king_pos, dir)
289
+ next unless piece_at(square) == move.piece && possibilities.include?(square)
290
290
 
291
- piece, = first_piece(square, dir)
292
- possibilities.reject! { |p| p == square } if piece == attacking_piece
291
+ next_square = first_piece(square, dir)
292
+ possibilities.reject! { |p| p == square } if piece_at(next_square) == attacking_piece
293
293
  end
294
294
  end
295
295
 
296
296
  possibilities
297
297
  end
298
298
 
299
+ # Walks from `from` in `direction` until it reaches the edge of the board
300
+ # or the first occupied square. Returns that square's `[file, rank]`
301
+ # coordinates, or `nil` if no piece was encountered before the edge. The
302
+ # caller reads the piece off the board itself, so this avoids allocating
303
+ # the `[piece, square]` wrapper tuple per direction scan.
304
+ #
299
305
  def first_piece(from, direction)
300
306
  file, rank = from
301
307
  i, j = direction
302
308
 
303
- piece = nil
309
+ loop do
310
+ file += i
311
+ rank += j
312
+ return nil if file.negative? || file > 7 || rank.negative? || rank > 7
304
313
 
305
- while valid_square?(file += i, rank += j)
306
- break if (piece = board.at(file, rank))
314
+ square = [file, rank]
315
+ return square if board.at(file, rank)
307
316
  end
317
+ end
308
318
 
309
- [piece, [file, rank]]
319
+ # Reads the piece on a square, tolerating a nil square (returned by
320
+ # {#first_piece} when the scan ran off the edge). Kept as a helper so the
321
+ # callers read the piece once instead of unpacking a `[piece, square]`
322
+ # tuple per direction scan.
323
+ #
324
+ def piece_at(square)
325
+ square && board.at(square[0], square[1])
310
326
  end
311
327
 
312
328
  # If the move is a capture and there is no piece on the
@@ -331,7 +347,7 @@ module PGN
331
347
  end
332
348
 
333
349
  def valid_square?(file, rank)
334
- (0..7).include?(file) && (0..7).include?(rank)
350
+ file >= 0 && file < 8 && rank >= 0 && rank < 8
335
351
  end
336
352
 
337
353
  def destination_coords
data/lib/pgn/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module PGN
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.2.1'.freeze
3
3
  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: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stacey Touset