Skip to content

Commit bcb5919

Browse files
authored
Merge pull request #20 from SKOHscripts/claude/anki-guid-puzzle-id-4pdvG
fix: flip board coordinates for Black-to-move and prevent coord overflow
2 parents fe4f194 + b54fdce commit bcb5919

5 files changed

Lines changed: 56 additions & 32 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,13 @@ The script downloads the **complete Lichess database** (several million puzzles)
179179
**Fundamental principle:** Instead of simply taking the most popular puzzles (which would create redundancies), the script applies a **maximum coverage algorithm by theme**:
180180

181181
```python
182-
def sample_by_themes(tranche, target_per_theme=20, popularity_threshold=90):
182+
def sample_by_themes(tranche, target_per_theme=17, popularity_threshold=90):
183183
```
184184

185185
**Selection steps:**
186186
1. **Theme identification**: Extract all tactical themes (fork, pin, discovered attack, etc.)
187187
2. **Quality filtering**: Priority selection of puzzles with Popularity ≥ 90%
188-
3. **Balanced distribution**: Maximum 20 puzzles per theme to avoid overrepresentation
188+
3. **Balanced distribution**: Maximum 17 puzzles per theme to avoid overrepresentation
189189
4. **Intelligent complement**: Add puzzles with lower popularity for rare themes
190190

191191
### **3. Exhaustive Coverage Guarantee 📊**

build_apkg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ def build_full(csv_dir: str, output: str) -> None:
326326
import lichess_optimized_puzzles_datasets as ld # pylint: disable=import-outside-toplevel
327327
ld.download_puzzle_db()
328328
ld.decompress_zst()
329-
stats = ld.extract_tranches(ld.CSV_FILE, target_per_theme=20, popularity_threshold=90)
329+
stats = ld.extract_tranches(ld.CSV_FILE, target_per_theme=17, popularity_threshold=90)
330330
build_from_csvs(csv_dir, output, deck_stats=stats)
331331

332332

lichess_optimized_puzzles_datasets.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def uci_seq_to_san(fen: str, uci_moves: str) -> str:
172172

173173
def sample_by_themes(
174174
tranche: pandas.DataFrame,
175-
target_per_theme: int = 30,
175+
target_per_theme: int = 17,
176176
popularity_threshold: int = 90,
177177
) -> List:
178178
"""
@@ -185,7 +185,7 @@ def sample_by_themes(
185185
----------
186186
tranche : pandas.DataFrame
187187
DataFrame containing puzzles for a specific ELO range
188-
target_per_theme : int, default=30
188+
target_per_theme : int, default=17
189189
Maximum number of puzzles to select per theme
190190
popularity_threshold : int, default=90
191191
Minimum popularity score for initial selection
@@ -225,7 +225,7 @@ def sample_by_themes(
225225

226226
def extract_tranches(
227227
csv_file: str,
228-
target_per_theme: int = 30,
228+
target_per_theme: int = 17,
229229
popularity_threshold: int = 90,
230230
) -> Dict[str, Dict]:
231231
"""
@@ -240,7 +240,7 @@ def extract_tranches(
240240
----------
241241
csv_file : str
242242
Path to the decompressed puzzle database CSV file
243-
target_per_theme : int, default=30
243+
target_per_theme : int, default=17
244244
Maximum puzzles per theme for balanced sampling
245245
popularity_threshold : int, default=90
246246
Minimum popularity threshold for quality filtering
@@ -413,7 +413,7 @@ def main() -> None:
413413
"""
414414
download_puzzle_db()
415415
decompress_zst()
416-
extract_tranches(CSV_FILE, target_per_theme=20, popularity_threshold=90)
416+
extract_tranches(CSV_FILE, target_per_theme=17, popularity_threshold=90)
417417

418418

419419
if __name__ == "__main__":

templates/back.html

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,15 @@ <h1 class="clean-pill">{{Themes}}</h1>
145145
}
146146

147147
/* My Board object */
148-
function ChessBoard(_fenCode, _attrBorder, _colorMode, _attrStyle, _attrClass) {
148+
function ChessBoard(_fenCode, _attrBorder, _colorMode, _attrStyle, _attrClass, _flip) {
149149
this.fenCode = _fenCode;
150150
this.attrBorder = _attrBorder.split(' ');
151151
this.colorMode = _colorMode;
152152
this.attrStyle = _attrStyle;
153153
this.attrClass = _attrClass;
154+
// When the board is shown from Black's side (180° rotation), the coordinate
155+
// labels must be mirrored: bottom-left becomes h1's mirror, not a1.
156+
this.flip = !!_flip;
154157

155158
this.rowCount = 0;
156159
this.colCount = 0;
@@ -321,6 +324,13 @@ <h1 class="clean-pill">{{Themes}}</h1>
321324
var generatedHTML = '';
322325
this.attrClass = VALUE_CLASS_COLOR_TABLE + (this.attrClass == '' ? '' : ' ' + this.attrClass);
323326

327+
var colsLabels = MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount);
328+
var rowsLabels = MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
329+
if (this.flip) {
330+
colsLabels = colsLabels.split('').reverse().join('');
331+
rowsLabels = rowsLabels.split('<br/>').filter(function (s) { return s !== ''; }).reverse().join('<br/>') + '<br/>';
332+
}
333+
324334
/* ----- Chess table ---------------------------------------------------------------------------------------------------- */
325335
generatedHTML +=
326336
'<table class="' + this.attrClass + '"' + (this.attrStyle != '' ? ' style="' + this.attrStyle + '"' : '') + '>';
@@ -332,7 +342,7 @@ <h1 class="clean-pill">{{Themes}}</h1>
332342
generatedHTML +=
333343
(this.isCoordShownLeft || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
334344
'<td class="cols">' +
335-
MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount) +
345+
colsLabels +
336346
'</td>' +
337347
(this.isCoordShownRight || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
338348
'';
@@ -354,7 +364,7 @@ <h1 class="clean-pill">{{Themes}}</h1>
354364

355365
if (this.isCoordShownLeft) {
356366
generatedHTML += '<td class="rows">';
357-
generatedHTML += MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
367+
generatedHTML += rowsLabels;
358368
generatedHTML += '</td>';
359369
} else if (this.isCoordPadded) {
360370
generatedHTML += '<td class="rows">';
@@ -433,7 +443,7 @@ <h1 class="clean-pill">{{Themes}}</h1>
433443

434444
if (this.isCoordShownRight) {
435445
generatedHTML += '<td class="rows">';
436-
generatedHTML += MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
446+
generatedHTML += rowsLabels;
437447
generatedHTML += '</td>';
438448
} else if (this.isCoordPadded) {
439449
generatedHTML += '<td class="rows">';
@@ -450,7 +460,7 @@ <h1 class="clean-pill">{{Themes}}</h1>
450460
generatedHTML +=
451461
(this.isCoordShownLeft || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
452462
'<td class="cols">' +
453-
MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount) +
463+
colsLabels +
454464
'</td>' +
455465
(this.isCoordShownRight || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
456466
'';
@@ -668,7 +678,8 @@ <h1 class="clean-pill">{{Themes}}</h1>
668678
boardFenCode = chessElt[idx].fenCode;
669679
}
670680

671-
var board = new ChessBoard(boardFenCode, attrBorder, attrMode, attrStyle, attrClass);
681+
var attrFlip = chessElt[idx].getAttribute('data-flip') === '1';
682+
var board = new ChessBoard(boardFenCode, attrBorder, attrMode, attrStyle, attrClass, attrFlip);
672683
var generatedHTML = board.generateHTML();
673684
if (generatedHTML.length == 0) {
674685
/* No board position given. Color mode is kept for upcoming chess tags. */
@@ -728,7 +739,10 @@ <h1 class="clean-pill">{{Themes}}</h1>
728739
// 5) Minimal DOM updates (textContent avoids accidental HTML parsing)
729740
const fig = document.getElementById('fen_fig');
730741
const toMove = document.getElementById('to_move');
731-
if (fig) fig.textContent = oriented;
742+
if (fig) {
743+
fig.textContent = oriented;
744+
fig.setAttribute('data-flip', side === 'w' ? '0' : '1');
745+
}
732746
if (toMove) toMove.textContent = side === 'w' ? dict.white : dict.black;
733747

734748
// 6) coords button label (shared from same dict, avoids a second lang lookup)
@@ -742,9 +756,6 @@ <h1 class="clean-pill">{{Themes}}</h1>
742756
const table = document.querySelector('table.chess, table.bwchess');
743757
if (!table) return;
744758

745-
// Measure the real board box (more reliable than the table alone)
746-
const board = table.querySelector('td.board > div.board') || table;
747-
748759
// Reset any previous fitting
749760
table.style.zoom = '';
750761
table.style.transform = '';
@@ -764,8 +775,9 @@ <h1 class="clean-pill">{{Themes}}</h1>
764775
const maxW = Math.max(50, vw - margin * 2);
765776
const maxH = Math.max(50, vh - overhead - margin * 2);
766777

767-
// Measure actual current board size
768-
const rect = board.getBoundingClientRect();
778+
// Measure the full table (board + coordinate cells) so the right-hand
779+
// coordinates never push the board off-screen when shown.
780+
const rect = table.getBoundingClientRect();
769781

770782
// Fit factor (never upscale; only shrink)
771783
let z = Math.min(maxW / rect.width, maxH / rect.height, 1);

templates/front.html

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,15 @@ <h2 id="to_move" class="clean-pill"></h2>
133133
}
134134

135135
/* My Board object */
136-
function ChessBoard(_fenCode, _attrBorder, _colorMode, _attrStyle, _attrClass) {
136+
function ChessBoard(_fenCode, _attrBorder, _colorMode, _attrStyle, _attrClass, _flip) {
137137
this.fenCode = _fenCode;
138138
this.attrBorder = _attrBorder.split(' ');
139139
this.colorMode = _colorMode;
140140
this.attrStyle = _attrStyle;
141141
this.attrClass = _attrClass;
142+
// When the board is shown from Black's side (180° rotation), the coordinate
143+
// labels must be mirrored: bottom-left becomes h1's mirror, not a1.
144+
this.flip = !!_flip;
142145

143146
this.rowCount = 0;
144147
this.colCount = 0;
@@ -309,6 +312,13 @@ <h2 id="to_move" class="clean-pill"></h2>
309312
var generatedHTML = '';
310313
this.attrClass = VALUE_CLASS_COLOR_TABLE + (this.attrClass == '' ? '' : ' ' + this.attrClass);
311314

315+
var colsLabels = MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount);
316+
var rowsLabels = MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
317+
if (this.flip) {
318+
colsLabels = colsLabels.split('').reverse().join('');
319+
rowsLabels = rowsLabels.split('<br/>').filter(function (s) { return s !== ''; }).reverse().join('<br/>') + '<br/>';
320+
}
321+
312322
/* ----- Chess table -------------------------------------------------------- */
313323
generatedHTML +=
314324
'<table class="' + this.attrClass + '"' + (this.attrStyle != '' ? ' style="' + this.attrStyle + '"' : '') + '>';
@@ -320,7 +330,7 @@ <h2 id="to_move" class="clean-pill"></h2>
320330
generatedHTML +=
321331
(this.isCoordShownLeft || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
322332
'<td class="cols">' +
323-
MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount) +
333+
colsLabels +
324334
'</td>' +
325335
(this.isCoordShownRight || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
326336
'';
@@ -342,7 +352,7 @@ <h2 id="to_move" class="clean-pill"></h2>
342352

343353
if (this.isCoordShownLeft) {
344354
generatedHTML += '<td class="rows">';
345-
generatedHTML += MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
355+
generatedHTML += rowsLabels;
346356
generatedHTML += '</td>';
347357
} else if (this.isCoordPadded) {
348358
generatedHTML += '<td class="rows">';
@@ -421,7 +431,7 @@ <h2 id="to_move" class="clean-pill"></h2>
421431

422432
if (this.isCoordShownRight) {
423433
generatedHTML += '<td class="rows">';
424-
generatedHTML += MERIDA_COORD_ROWS.substr((8 - this.coordOriginRow - this.rowCount) * 6, this.rowCount * 6);
434+
generatedHTML += rowsLabels;
425435
generatedHTML += '</td>';
426436
} else if (this.isCoordPadded) {
427437
generatedHTML += '<td class="rows">';
@@ -438,7 +448,7 @@ <h2 id="to_move" class="clean-pill"></h2>
438448
generatedHTML +=
439449
(this.isCoordShownLeft || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
440450
'<td class="cols">' +
441-
MERIDA_COORD_COLS.substr(this.coordOriginCol, this.colCount) +
451+
colsLabels +
442452
'</td>' +
443453
(this.isCoordShownRight || this.isCoordPadded ? '<td>&nbsp;</td>' : '') +
444454
'';
@@ -656,7 +666,8 @@ <h2 id="to_move" class="clean-pill"></h2>
656666
boardFenCode = chessElt[idx].fenCode;
657667
}
658668

659-
var board = new ChessBoard(boardFenCode, attrBorder, attrMode, attrStyle, attrClass);
669+
var attrFlip = chessElt[idx].getAttribute('data-flip') === '1';
670+
var board = new ChessBoard(boardFenCode, attrBorder, attrMode, attrStyle, attrClass, attrFlip);
660671
var generatedHTML = board.generateHTML();
661672
if (generatedHTML.length == 0) {
662673
/* No board position given. Color mode is kept for upcoming chess tags. */
@@ -716,7 +727,10 @@ <h2 id="to_move" class="clean-pill"></h2>
716727
// 5) Minimal DOM updates (textContent avoids accidental HTML parsing)
717728
const fig = document.getElementById('fen_fig');
718729
const toMove = document.getElementById('to_move');
719-
if (fig) fig.textContent = oriented;
730+
if (fig) {
731+
fig.textContent = oriented;
732+
fig.setAttribute('data-flip', side === 'w' ? '0' : '1');
733+
}
720734
if (toMove) toMove.textContent = side === 'w' ? dict.white : dict.black;
721735

722736
// 6) display moves string
@@ -734,9 +748,6 @@ <h2 id="to_move" class="clean-pill"></h2>
734748
const table = document.querySelector('table.chess, table.bwchess');
735749
if (!table) return;
736750

737-
// Measure the real board box (more reliable than the table alone)
738-
const board = table.querySelector('td.board > div.board') || table;
739-
740751
// Reset any previous fitting
741752
table.style.zoom = '';
742753
table.style.transform = '';
@@ -756,8 +767,9 @@ <h2 id="to_move" class="clean-pill"></h2>
756767
const maxW = Math.max(50, vw - margin * 2);
757768
const maxH = Math.max(50, vh - overhead - margin * 2);
758769

759-
// Measure actual current board size
760-
const rect = board.getBoundingClientRect();
770+
// Measure the full table (board + coordinate cells) so the right-hand
771+
// coordinates never push the board off-screen when shown.
772+
const rect = table.getBoundingClientRect();
761773

762774
// Fit factor (never upscale; only shrink)
763775
let z = Math.min(maxW / rect.width, maxH / rect.height, 1);

0 commit comments

Comments
 (0)