gammonview 3.1.0__tar.gz → 3.2.0__tar.gz
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.
- {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/publish.yml +1 -1
- {gammonview-3.1.0 → gammonview-3.2.0}/CHANGELOG.md +25 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/CONTRIBUTING.md +1 -1
- {gammonview-3.1.0 → gammonview-3.2.0}/PKG-INFO +8 -8
- {gammonview-3.1.0 → gammonview-3.2.0}/README.md +4 -4
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/CLI.md +2 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/position.py +87 -4
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/README.md +1 -1
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/package.json +3 -3
- {gammonview-3.1.0 → gammonview-3.2.0}/pyproject.toml +3 -3
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_analyze_position.py +64 -1
- {gammonview-3.1.0 → gammonview-3.2.0}/.gitattributes +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/ci.yml +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/engine.yml +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/.gitignore +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/.python-version +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/CLAUDE.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/LICENSE +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/THIRD-PARTY-NOTICES.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/assets/logo.png +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/METHODOLOGY.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_COMPUTED_FIELDS.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_JSON_SPEC_GAMMONVIEW.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_V2_PROFILE.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/PRESET_ACCURACY.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/upstream-bgsage.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/docs/v1/OGXM_FORMAT_SPEC_GAMMONVIEW.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-16-checker-mid-tier.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-17-checker-mid-tier-followup.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-19-cross-machine-costs.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-20-corpus-rerun.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-10-21-wc2-corpus.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/experiments/bench.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/__init__.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/batch.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/checker_eval.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/compare_gva.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/game_eval.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/loader.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/luck.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/match.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/ogxm_reconstructor.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/parentwatch.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/presets.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/progress.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/reconstruct_mat.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/share.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/__init__.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/basefill.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/bgf.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/binary.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/export.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/game_reconstructor.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/legality.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/mat.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/mat_parser.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/merge.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/met.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/notation.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogid.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2_passthrough.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2_writer.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/place.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/reader.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/share.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/stats.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/xg.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/.gitignore +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/LICENSE +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/THIRD-PARTY-NOTICES.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/README.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/bgf2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/mat2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/xg2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/package-lock.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/basefill.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/bgf2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/binary.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/constants.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/export.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/index.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/legality.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/mat2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/merge.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/met.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/notation.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/og2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogid.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2_passthrough.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2_writer.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/place.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/reader.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/sha256.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/share.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/stats.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/xg2gva.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/3WNK_g1Z-PLsh_HyvQ5j4a.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/5nqfGw9bWG3deTaU.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/B4_SrGcsKAQmoTyHlgJCbM.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/C07OQCKyw4Pe-CnjFiAMYO.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/MYdvw1qGuyaRUH__.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/Sz-2PgKnvb6aFls69-qFXg.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/UL-CV6F9jMM-E1Pu.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/XCu3RJ0UvDg_Tldl.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/baR-U643iUDpvzTC.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/eXBNG5wZHS5Alu3P.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/hQ8sVn2LbTdF4wRm.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/rK7pXm4TqLb9NzWd.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/uwvEbd63UvdX7SDin5o_Iw.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-docs.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-edits-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/bgf-writer-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields-docs.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/foreign.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/gen.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/match.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/match.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/passthrough-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/resign.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/resign.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/set-up.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/set-up.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.expected.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.ogxm +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/v1-chunks.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/writer-sha256.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-alternative-move-steps.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-basefill.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-alternatives.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-embedded-cube.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-move-steps.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-chunk-passthrough.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-equity-loss.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-illegal-move.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-illegal-play-steps.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-legality.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-mat.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-merge.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-met.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-og.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogid.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-annos.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-blocks.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-fields.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-limits.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-passthrough.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-writer.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-place.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-robustness.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-roundtrip.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-share.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-stats.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-cube.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-eval-levels.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-move-steps.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-zero-win-eval.js +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/README.md +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/3WNK_g1Z-PLsh_HyvQ5j4a.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/5nqfGw9bWG3deTaU.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/B4_SrGcsKAQmoTyHlgJCbM.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/C07OQCKyw4Pe-CnjFiAMYO.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/MYdvw1qGuyaRUH__.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/Sz-2PgKnvb6aFls69-qFXg.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/UL-CV6F9jMM-E1Pu.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/XCu3RJ0UvDg_Tldl.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/baR-U643iUDpvzTC.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/eXBNG5wZHS5Alu3P.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/uwvEbd63UvdX7SDin5o_Iw.bgf +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/3WNK_g1Z-PLsh_HyvQ5j4a.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/5nqfGw9bWG3deTaU.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/C07OQCKyw4Pe-CnjFiAMYO.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/MYdvw1qGuyaRUH__.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/Sz-2PgKnvb6aFls69-qFXg.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/UL-CV6F9jMM-E1Pu.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/XCu3RJ0UvDg_Tldl.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/baR-U643iUDpvzTC.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/eXBNG5wZHS5Alu3P.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/uwvEbd63UvdX7SDin5o_Iw.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/3WNK_g1Z-PLsh_HyvQ5j4a.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/5nqfGw9bWG3deTaU.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/B4_SrGcsKAQmoTyHlgJCbM.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/C07OQCKyw4Pe-CnjFiAMYO.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/MYdvw1qGuyaRUH__.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/Sz-2PgKnvb6aFls69-qFXg.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/UL-CV6F9jMM-E1Pu.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/XCu3RJ0UvDg_Tldl.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/baR-U643iUDpvzTC.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/eXBNG5wZHS5Alu3P.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/hQ8sVn2LbTdF4wRm.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/rK7pXm4TqLb9NzWd.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/uwvEbd63UvdX7SDin5o_Iw.mat +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/3WNK_g1Z-PLsh_HyvQ5j4a.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/5nqfGw9bWG3deTaU.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/B4_SrGcsKAQmoTyHlgJCbM.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/C07OQCKyw4Pe-CnjFiAMYO.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/MYdvw1qGuyaRUH__.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/Sz-2PgKnvb6aFls69-qFXg.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/UL-CV6F9jMM-E1Pu.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/XCu3RJ0UvDg_Tldl.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/baR-U643iUDpvzTC.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/eXBNG5wZHS5Alu3P.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/hQ8sVn2LbTdF4wRm.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/rK7pXm4TqLb9NzWd.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/uwvEbd63UvdX7SDin5o_Iw.xg +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/audit_corpus.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_js_v2_fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_annos_fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_blocks_fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_fields_fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_passthrough_fixtures.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/5nqfGw9bWG3deTaU.fast.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/5nqfGw9bWG3deTaU.fast.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/MYdvw1qGuyaRUH__.fast.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/MYdvw1qGuyaRUH__.fast.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/PROVENANCE.json +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/baR-U643iUDpvzTC.fast.gva +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/baR-U643iUDpvzTC.fast.gvab +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_annos_cases.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_blocks_cases.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_fields_cases.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_oracle.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/passthrough_cases.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/regen_golden.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/run_all.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_alternative_move_steps.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_analysis_orientation.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_basefill.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_alternatives.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_cube_response.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_move_steps.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_chunk_passthrough.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_count_illegal.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_cube_tiers.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_docs_presets.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_equity_loss.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_gvab_writer.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_illegal_move.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_illegal_play_steps.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_legality.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_loader_formats.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_luck_fill.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_merge_resign.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_met.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_annos.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_blocks.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_blocks_analysis.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_fields.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_fields_analysis.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_limits.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_passthrough.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_reference.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_writer.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_conformance.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_export.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_pipeline.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_stats.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_parent_watch.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_position_id.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_presets.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_read_gvab.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_reconstruct_mat.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_resign_actor.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_resign_errors.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_restated_play_analysis.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_screened_checker.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_share_link.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_cube.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_eval_levels.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_move_steps.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_zero_win_eval.py +0 -0
- {gammonview-3.1.0 → gammonview-3.2.0}/uv.lock +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# must be done before the first run, because the project does not exist yet:
|
|
8
8
|
#
|
|
9
9
|
# PyPI project name : gammonview
|
|
10
|
-
# Owner :
|
|
10
|
+
# Owner : gammonview
|
|
11
11
|
# Repository name : gammonview-analysis
|
|
12
12
|
# Workflow name : publish.yml
|
|
13
13
|
# Environment name : pypi
|
|
@@ -13,6 +13,31 @@ when the repository was opened; they are kept because they record why things
|
|
|
13
13
|
are the way they are — particularly the breaking changes and the measurements
|
|
14
14
|
behind several design decisions. Dates are the tag dates.
|
|
15
15
|
|
|
16
|
+
## 3.2.0 — 2026-11-04
|
|
17
|
+
|
|
18
|
+
**`analyze_position` takes the money rules: Jacoby and beavers** *(added)*
|
|
19
|
+
|
|
20
|
+
`analyze_position(..., jacoby=None, beaver=None)` and `gvan-position
|
|
21
|
+
--jacoby/--no-jacoby --beaver/--no-beaver`. Until now every money position was
|
|
22
|
+
analysed under bgsage's defaults, Jacoby on and beavers allowed, with no way to
|
|
23
|
+
ask otherwise. A bool chooses the rule; left unset it is the id's own. The reply
|
|
24
|
+
gains `jacoby` and `beaver`, the rules actually applied, both `False` at any
|
|
25
|
+
match score, where bgsage ignores them. With beavers allowed, a wrong double is
|
|
26
|
+
answered with a beaver and the cube's `double_take_equity` is the beaver's
|
|
27
|
+
(twice the take's), with `is_beaver` set, as it always was.
|
|
28
|
+
|
|
29
|
+
**A money XGID's rules field is read as the rules** *(fixed)*
|
|
30
|
+
|
|
31
|
+
At money, the XGID field that holds the Crawford flag in a match holds the
|
|
32
|
+
rules instead -- "Jacoby+2xBeaver", in XG's own spec (eXtreme Gammon 2 Help,
|
|
33
|
+
"XGID"), now quoted in `parse_xgid`'s docstring. It was read as Crawford in both
|
|
34
|
+
cases, so a money "1" claimed a Crawford game and the Jacoby rule it stated was
|
|
35
|
+
lost, and so was the beaver bit. `PositionState` gains `jacoby` and `beaver`
|
|
36
|
+
(None for an OGID, which cannot say, and at a match score). Behaviour change:
|
|
37
|
+
unset, both follow the XGID, so a money XGID with "0" there -- which states
|
|
38
|
+
"No Jacoby, no Beaver" -- is now analysed with neither, where it was analysed
|
|
39
|
+
with both. An OGID is still analysed with both.
|
|
40
|
+
|
|
16
41
|
## 3.1.0 — 2026-10-22
|
|
17
42
|
|
|
18
43
|
**`engine_build` names the gammonview version that ran the analysis** *(changed)*
|
|
@@ -6,7 +6,7 @@ you need to know before sending one.
|
|
|
6
6
|
## Getting set up
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
git clone https://github.com/
|
|
9
|
+
git clone https://github.com/gammonview/gammonview-analysis
|
|
10
10
|
cd gammonview-analysis
|
|
11
11
|
uv sync # gvformat + gvanalysis + bgsage + everything the tests need
|
|
12
12
|
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gammonview
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Backgammon match analysis — the pure-Python OGXM/GVA codec (`gvformat`) plus bgsage-powered match and position analysis (`gvanalysis`).
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
-
Project-URL: Issues, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/gammonview/gammonview-analysis
|
|
6
|
+
Project-URL: Repository, https://github.com/gammonview/gammonview-analysis
|
|
7
|
+
Project-URL: Issues, https://github.com/gammonview/gammonview-analysis/issues
|
|
8
8
|
Author: Nicholas Vlamis
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
@@ -28,15 +28,15 @@ Requires-Dist: pyyaml>=6.0.3; extra == 'engine'
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
<p align="center">
|
|
31
|
-
<img src="https://raw.githubusercontent.com/
|
|
31
|
+
<img src="https://raw.githubusercontent.com/gammonview/gammonview-analysis/main/assets/logo.png"
|
|
32
32
|
alt="" width="104" height="104">
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
35
|
<h1 align="center">GammonView</h1>
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
38
|
-
<a href="https://github.com/
|
|
39
|
-
<a href="https://github.com/
|
|
38
|
+
<a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
39
|
+
<a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
|
|
40
40
|
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
|
|
41
41
|
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-green.svg" alt="Licence: MIT"></a>
|
|
42
42
|
</p>
|
|
@@ -82,7 +82,7 @@ the environment — it installs both packages in editable mode, plus bgsage and
|
|
|
82
82
|
the test dependencies:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
git clone https://github.com/
|
|
85
|
+
git clone https://github.com/gammonview/gammonview-analysis.git
|
|
86
86
|
cd gammonview-analysis
|
|
87
87
|
uv sync
|
|
88
88
|
uv run python tests/run_all.py --fast # ~3s, no engine needed
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/gammonview/gammonview-analysis/main/assets/logo.png"
|
|
3
3
|
alt="" width="104" height="104">
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
<h1 align="center">GammonView</h1>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="https://github.com/
|
|
10
|
-
<a href="https://github.com/
|
|
9
|
+
<a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
|
|
11
11
|
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-green.svg" alt="Licence: MIT"></a>
|
|
13
13
|
</p>
|
|
@@ -53,7 +53,7 @@ the environment — it installs both packages in editable mode, plus bgsage and
|
|
|
53
53
|
the test dependencies:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
git clone https://github.com/
|
|
56
|
+
git clone https://github.com/gammonview/gammonview-analysis.git
|
|
57
57
|
cd gammonview-analysis
|
|
58
58
|
uv sync
|
|
59
59
|
uv run python tests/run_all.py --fast # ~3s, no engine needed
|
|
@@ -174,6 +174,8 @@ position shape is matched (an XGID never matches it).
|
|
|
174
174
|
| Flag | Effect |
|
|
175
175
|
|---|---|
|
|
176
176
|
| `--level LEVEL` | `1ply`, `2ply`, `3ply`, `4ply`, `truncated1/2/3`, `rollout`. Default `3ply`. |
|
|
177
|
+
| `--jacoby` / `--no-jacoby` | Money games: analyse with or without the Jacoby rule (gammons count single while the cube is centred). Default: what the id says — an XGID's field 8 — and on for an OGID, which has no field for it. Ignored at a match score. |
|
|
178
|
+
| `--beaver` / `--no-beaver` | Money games: allow or forbid beavers. Default as for `--jacoby`: the XGID's field 8, allowed for an OGID. Ignored at a match score. |
|
|
177
179
|
| `--no-progress` | Suppress the rollout progress bar. |
|
|
178
180
|
|
|
179
181
|
At a rollout level a progress bar is drawn on **stderr**, so piping the report
|
|
@@ -58,6 +58,12 @@ class PositionState:
|
|
|
58
58
|
#: perspective either way; this is what maps a move back to the absolute
|
|
59
59
|
#: point numbering the OGXM step form (and every consumer of it) uses.
|
|
60
60
|
mover_is_white: bool = True
|
|
61
|
+
#: The money-game Jacoby rule, when the id says: an XGID's rules field
|
|
62
|
+
#: does at money, an OGID never does (None). Always None at a match score.
|
|
63
|
+
jacoby: bool | None = None
|
|
64
|
+
#: Whether beavers are allowed, the other half of that field; None in the
|
|
65
|
+
#: same cases.
|
|
66
|
+
beaver: bool | None = None
|
|
61
67
|
|
|
62
68
|
|
|
63
69
|
#: Back-compat alias -- this was XGID-only before OGID input was added.
|
|
@@ -65,6 +71,32 @@ XGIDState = PositionState
|
|
|
65
71
|
|
|
66
72
|
|
|
67
73
|
def parse_xgid(xgid: str) -> PositionState:
|
|
74
|
+
"""Parse an XGID into the mover-perspective state bgsage analyses.
|
|
75
|
+
|
|
76
|
+
The XGID is eXtreme Gammon's, specified in its own help ("XGID", eXtreme
|
|
77
|
+
Gammon 2 Help rev. 1.01, 2011, pp. 145-146) as ten colon-separated fields:
|
|
78
|
+
|
|
79
|
+
1. Position 26 chars: the top player's bar, points 1-24 from the
|
|
80
|
+
bottom player's side, the bottom player's bar. '-' empty,
|
|
81
|
+
'A'-'P' 1-16 bottom-player checkers, 'a'-'p' top-player.
|
|
82
|
+
2. Cube the cube is 2**value.
|
|
83
|
+
3. Cube pos. 1 bottom player owns it, 0 centred, -1 top player.
|
|
84
|
+
4. Turn 1 the bottom player's turn, -1 the top player's.
|
|
85
|
+
5. Dice "00" to roll or double, "D" doubled (take or drop), "B"
|
|
86
|
+
beavered, "R" raccooned, or the two dice ("35").
|
|
87
|
+
6. Score 1 the bottom player's.
|
|
88
|
+
7. Score 2 the top player's.
|
|
89
|
+
8. Crawford, Jacoby
|
|
90
|
+
"Match play: 1 means the current game is Crawford, 0 means
|
|
91
|
+
the current game is not played with the Crawford rule.
|
|
92
|
+
Unlimited games: the value is a Jacoby+2xBeaver" --
|
|
93
|
+
0 none, 1 Jacoby, 2 beaver, 3 both.
|
|
94
|
+
9. Match length 0 for unlimited games (money).
|
|
95
|
+
10. Max cube 2**value.
|
|
96
|
+
|
|
97
|
+
(GNU Backgammon's ``SetXGID`` reads field 8 the same way.) Field 10 is
|
|
98
|
+
optional here, as it is in ids found in the wild.
|
|
99
|
+
"""
|
|
68
100
|
s = xgid.strip()
|
|
69
101
|
if s.upper().startswith("XGID="):
|
|
70
102
|
s = s[5:]
|
|
@@ -120,7 +152,13 @@ def parse_xgid(xgid: str) -> PositionState:
|
|
|
120
152
|
die2 = int(dice_str[1]) if len(dice_str) > 1 and dice_str[1].isdigit() else 0
|
|
121
153
|
|
|
122
154
|
match_len = int(parts[8]) if parts[8].lstrip("-").isdigit() else 0
|
|
123
|
-
|
|
155
|
+
# Field 8 is two fields in one (see the docstring): the Crawford flag in a
|
|
156
|
+
# match, Jacoby + 2 x Beaver at money -- so a money "1" is the Jacoby rule,
|
|
157
|
+
# not a Crawford game money cannot have.
|
|
158
|
+
rules = int(parts[7]) if parts[7].isdigit() else 0
|
|
159
|
+
is_crawford = match_len > 0 and rules == 1
|
|
160
|
+
jacoby = bool(rules & 1) if match_len <= 0 else None
|
|
161
|
+
beaver = bool(rules & 2) if match_len <= 0 else None
|
|
124
162
|
|
|
125
163
|
if match_len > 0:
|
|
126
164
|
# Scores are written player 1 (White) first; `away1` is the MOVER's.
|
|
@@ -141,7 +179,7 @@ def parse_xgid(xgid: str) -> PositionState:
|
|
|
141
179
|
|
|
142
180
|
return PositionState(
|
|
143
181
|
board, die1, die2, cube_value, cube_owner, away1, away2, is_crawford,
|
|
144
|
-
mover_is_white=mover_is_white,
|
|
182
|
+
mover_is_white=mover_is_white, jacoby=jacoby, beaver=beaver,
|
|
145
183
|
)
|
|
146
184
|
|
|
147
185
|
|
|
@@ -232,6 +270,8 @@ def analyze_position(
|
|
|
232
270
|
max_alternatives: int = MAX_ALTERNATIVES,
|
|
233
271
|
analyzer=None,
|
|
234
272
|
on_progress: "Callable[[int, int, str], None] | None" = None,
|
|
273
|
+
jacoby: bool | None = None,
|
|
274
|
+
beaver: bool | None = None,
|
|
235
275
|
) -> dict:
|
|
236
276
|
"""Analyze one position and return the result as an OGXM-shaped dict.
|
|
237
277
|
|
|
@@ -253,6 +293,22 @@ def analyze_position(
|
|
|
253
293
|
one costs ~40ms, worth reusing across requests); it must have been built at
|
|
254
294
|
``level``, which is reported back as ``eval_level``.
|
|
255
295
|
|
|
296
|
+
``jacoby`` is the money-game rule the position is played under: gammons
|
|
297
|
+
and backgammons count single while the cube is centred. Left as ``None``
|
|
298
|
+
it is the id's own: a money XGID states it in its rules field, and an OGID,
|
|
299
|
+
which cannot, is read under the rule (bgsage's own default). A bool
|
|
300
|
+
overrides the id. It means nothing at a match score, where bgsage ignores
|
|
301
|
+
it, and the reply's ``jacoby`` reports the rule actually applied -- so it
|
|
302
|
+
is ``False`` for every match position.
|
|
303
|
+
|
|
304
|
+
``beaver`` is the same for beavers: whether a player doubled may redouble
|
|
305
|
+
at once and keep the cube. Left as ``None`` it is the XGID's own beaver bit,
|
|
306
|
+
and allowed for an OGID (bgsage's default). Allowed, a wrong double --
|
|
307
|
+
double/take below zero -- is answered with a beaver, and the cube's
|
|
308
|
+
``double_take_equity`` is that beaver's (twice the take's), with
|
|
309
|
+
``is_beaver`` set. The reply's ``beaver`` is the rule applied, ``False`` at
|
|
310
|
+
a match score.
|
|
311
|
+
|
|
256
312
|
``on_progress(completed, total, phase)`` reports a rollout's progress; it is
|
|
257
313
|
never called at the full-width levels, which return too fast to report.
|
|
258
314
|
``phase`` is ``"trials"`` for the planned work and ``"finalizing"`` for the
|
|
@@ -280,6 +336,13 @@ def analyze_position(
|
|
|
280
336
|
away2=state.away2,
|
|
281
337
|
is_crawford=state.is_crawford,
|
|
282
338
|
)
|
|
339
|
+
money = not state.away1 and not state.away2
|
|
340
|
+
if jacoby is None:
|
|
341
|
+
jacoby = state.jacoby if state.jacoby is not None else True
|
|
342
|
+
if beaver is None:
|
|
343
|
+
beaver = state.beaver if state.beaver is not None else True
|
|
344
|
+
match_kwargs["jacoby"] = jacoby
|
|
345
|
+
match_kwargs["beaver"] = beaver
|
|
283
346
|
|
|
284
347
|
started = time.perf_counter()
|
|
285
348
|
result: dict = {
|
|
@@ -292,6 +355,8 @@ def analyze_position(
|
|
|
292
355
|
"away1": state.away1,
|
|
293
356
|
"away2": state.away2,
|
|
294
357
|
"is_crawford": state.is_crawford,
|
|
358
|
+
"jacoby": bool(jacoby) and money,
|
|
359
|
+
"beaver": bool(beaver) and money,
|
|
295
360
|
"dice": [state.die1, state.die2] if is_checker else None,
|
|
296
361
|
}
|
|
297
362
|
|
|
@@ -431,6 +496,17 @@ def main() -> None:
|
|
|
431
496
|
parser.add_argument(
|
|
432
497
|
"--level", default="3ply", choices=EVAL_LEVELS,
|
|
433
498
|
help="Eval level (default: 3ply)")
|
|
499
|
+
parser.add_argument(
|
|
500
|
+
"--jacoby", action=argparse.BooleanOptionalAction, default=None,
|
|
501
|
+
help="Money games: analyse with or without the Jacoby rule, gammons "
|
|
502
|
+
"counting single while the cube is centred. Default: what the id "
|
|
503
|
+
"says -- an XGID's rules field -- and on for an OGID, which cannot "
|
|
504
|
+
"say. Ignored at a match score")
|
|
505
|
+
parser.add_argument(
|
|
506
|
+
"--beaver", action=argparse.BooleanOptionalAction, default=None,
|
|
507
|
+
help="Money games: allow or forbid beavers. Default: what the id says "
|
|
508
|
+
"-- an XGID's rules field -- and allowed for an OGID. Ignored at a "
|
|
509
|
+
"match score")
|
|
434
510
|
parser.add_argument(
|
|
435
511
|
"--no-progress", action="store_true",
|
|
436
512
|
help="Suppress the rollout progress bar (it is stderr-only and already "
|
|
@@ -439,7 +515,13 @@ def main() -> None:
|
|
|
439
515
|
|
|
440
516
|
fmt = "OGID" if looks_like_ogid(args.position_id) else "XGID"
|
|
441
517
|
state = parse_position_id(args.position_id)
|
|
442
|
-
|
|
518
|
+
jacoby = args.jacoby if args.jacoby is not None else (
|
|
519
|
+
state.jacoby if state.jacoby is not None else True)
|
|
520
|
+
beaver = args.beaver if args.beaver is not None else (
|
|
521
|
+
state.beaver if state.beaver is not None else True)
|
|
522
|
+
context = ("money game, " + ("Jacoby" if jacoby else "no Jacoby")
|
|
523
|
+
+ (", beavers" if beaver else ", no beavers")
|
|
524
|
+
if state.away1 == 0 else f"match {state.away1}-away vs {state.away2}-away")
|
|
443
525
|
print(f"Position ({fmt}): cube={state.cube_value} ({state.cube_owner}) {context}"
|
|
444
526
|
+ (" [Crawford]" if state.is_crawford else ""))
|
|
445
527
|
|
|
@@ -468,7 +550,8 @@ def main() -> None:
|
|
|
468
550
|
|
|
469
551
|
try:
|
|
470
552
|
result = analyze_position(args.position_id, level=args.level,
|
|
471
|
-
on_progress=_progress
|
|
553
|
+
on_progress=_progress, jacoby=jacoby,
|
|
554
|
+
beaver=beaver)
|
|
472
555
|
finally:
|
|
473
556
|
if bar is not None:
|
|
474
557
|
bar.clear()
|
|
@@ -7,7 +7,7 @@ OGIDs. **No engine, no WebAssembly, no build step.** One runtime
|
|
|
7
7
|
dependency (`pako`, for zlib).
|
|
8
8
|
|
|
9
9
|
This is the browser half of
|
|
10
|
-
[gammonview-analysis](https://github.com/
|
|
10
|
+
[gammonview-analysis](https://github.com/gammonview/gammonview-analysis): Python
|
|
11
11
|
writes `.gvab` server-side, this reads it client-side. It is an ESM mirror of the
|
|
12
12
|
Python `gvformat` package and **the definitive copy of the JavaScript** — the
|
|
13
13
|
port is maintained here, not downstream.
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"description": "OGXM binary codec — read/write .gvab match files",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/
|
|
13
|
+
"url": "git+https://github.com/gammonview/gammonview-analysis.git",
|
|
14
14
|
"directory": "gvformat-js"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://github.com/
|
|
16
|
+
"homepage": "https://github.com/gammonview/gammonview-analysis/tree/main/gvformat-js",
|
|
17
17
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/
|
|
18
|
+
"url": "https://github.com/gammonview/gammonview-analysis/issues"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
@@ -31,9 +31,9 @@ classifiers = [
|
|
|
31
31
|
dependencies = []
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
34
|
-
Homepage = "https://github.com/
|
|
35
|
-
Repository = "https://github.com/
|
|
36
|
-
Issues = "https://github.com/
|
|
34
|
+
Homepage = "https://github.com/gammonview/gammonview-analysis"
|
|
35
|
+
Repository = "https://github.com/gammonview/gammonview-analysis"
|
|
36
|
+
Issues = "https://github.com/gammonview/gammonview-analysis/issues"
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
39
39
|
# The bgsage-powered analysis layer (`gvanalysis`: game_eval, match, position
|
|
@@ -29,7 +29,7 @@ from pathlib import Path
|
|
|
29
29
|
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
30
30
|
sys.path.insert(0, str(_REPO_ROOT))
|
|
31
31
|
|
|
32
|
-
from gvanalysis.position import EVAL_LEVELS, analyze_position
|
|
32
|
+
from gvanalysis.position import EVAL_LEVELS, analyze_position, parse_position_id
|
|
33
33
|
|
|
34
34
|
_checks = 0
|
|
35
35
|
_failures: list[str] = []
|
|
@@ -227,6 +227,69 @@ def main() -> int:
|
|
|
227
227
|
== [a["notation"] for a in many["alternatives"][:5]],
|
|
228
228
|
"a smaller cap truncates the same ranking, it does not reorder it")
|
|
229
229
|
|
|
230
|
+
print("11. the Jacoby rule is the caller's, and money-only")
|
|
231
|
+
# A blitz: three checkers on the bar against a closed board. Gammons are
|
|
232
|
+
# most of the wins, so with the cube centred the rule decides the cube:
|
|
233
|
+
# under Jacoby a gammon counts single and holding is worth at most a point,
|
|
234
|
+
# without it holding is worth far more than the point a pass gives.
|
|
235
|
+
blitz = "XGID=cBBBBBB-C----------cccc---:0:0:1:00:0:0:0:0:10"
|
|
236
|
+
jac = analyze_position(blitz, level="1ply", jacoby=True)
|
|
237
|
+
nojac = analyze_position(blitz, level="1ply", jacoby=False)
|
|
238
|
+
check(jac["jacoby"] is True and nojac["jacoby"] is False,
|
|
239
|
+
"the reply says which rule was applied")
|
|
240
|
+
check(jac["cube"]["no_double_equity"] <= 1.0 + 1e-9,
|
|
241
|
+
f"under Jacoby, holding is worth at most a point (got {jac['cube']['no_double_equity']})")
|
|
242
|
+
check(nojac["cube"]["no_double_equity"] > 1.2,
|
|
243
|
+
f"without it, gammons make holding worth more (got {nojac['cube']['no_double_equity']})")
|
|
244
|
+
match = analyze_position(
|
|
245
|
+
"XGID=cBBBBBB-C----------cccc---:0:0:1:00:0:0:0:7:10", level="1ply")
|
|
246
|
+
check(match["jacoby"] is False, "a match position reports no Jacoby, however asked")
|
|
247
|
+
|
|
248
|
+
print("12. a money XGID's rules field is the Jacoby rule, not Crawford")
|
|
249
|
+
# Field 8 at money is "Jacoby+2xBeaver" (XG's own spec; see parse_xgid).
|
|
250
|
+
# An OGID has no such field.
|
|
251
|
+
said_on = parse_position_id(blitz.replace(":0:0:0:0:10", ":0:0:1:0:10"))
|
|
252
|
+
said_off = parse_position_id(blitz)
|
|
253
|
+
check(said_on.jacoby is True and said_on.is_crawford is False,
|
|
254
|
+
"a money '1' is Jacoby, and no Crawford game")
|
|
255
|
+
check(parse_position_id(blitz.replace(":0:0:0:0:10", ":0:0:3:0:10")).jacoby is True,
|
|
256
|
+
"Jacoby and beavers together ('3') is still Jacoby")
|
|
257
|
+
check(said_off.jacoby is False, "a money '0' states the rule is off")
|
|
258
|
+
check(parse_position_id(OPENING_31_OGID).jacoby is None, "an OGID says nothing")
|
|
259
|
+
m = parse_position_id("XGID=-b----E-C---eE---c-e----B-:0:0:1:31:0:0:1:7:10")
|
|
260
|
+
check(m.is_crawford is True and m.jacoby is None, "at a match score '1' is still Crawford")
|
|
261
|
+
check(analyze_position(blitz.replace(":0:0:0:0:10", ":0:0:1:0:10"), level="1ply")["jacoby"] is True
|
|
262
|
+
and analyze_position(blitz, level="1ply")["jacoby"] is False,
|
|
263
|
+
"left unset, the analysis follows what the XGID says")
|
|
264
|
+
check(analyze_position(OPENING_CUBE_XGID.replace("XGID=", ""), level="1ply")["jacoby"] is False,
|
|
265
|
+
"(an XGID written with '0' there, as most are, says off)")
|
|
266
|
+
|
|
267
|
+
print("13. beavers: the caller's, or the XGID's, and money-only")
|
|
268
|
+
# The blitz from the other side: three checkers on the bar against a
|
|
269
|
+
# closed board, and it is their turn. A double here is a gift, so if the
|
|
270
|
+
# rules allow it the opponent beavers -- which doubles the take's equity.
|
|
271
|
+
victim = "XGID=cBBBBBB-C----------cccc---:0:0:-1:00:0:0:0:0:10"
|
|
272
|
+
allowed = analyze_position(victim, level="1ply", beaver=True)
|
|
273
|
+
forbidden = analyze_position(victim, level="1ply", beaver=False)
|
|
274
|
+
check(allowed["beaver"] is True and forbidden["beaver"] is False,
|
|
275
|
+
"the reply says which rule was applied")
|
|
276
|
+
check(allowed["cube"]["is_beaver"] and not forbidden["cube"]["is_beaver"],
|
|
277
|
+
"a wrong double is beavered only when beavers are allowed")
|
|
278
|
+
check(abs(allowed["cube"]["double_take_equity"]
|
|
279
|
+
- 2 * forbidden["cube"]["double_take_equity"]) < 1e-3,
|
|
280
|
+
f"and the beaver doubles the take's equity (got {allowed['cube']['double_take_equity']} "
|
|
281
|
+
f"vs {forbidden['cube']['double_take_equity']})")
|
|
282
|
+
check(parse_position_id(victim.replace(":0:0:0:0:10", ":0:0:2:0:10")).beaver is True
|
|
283
|
+
and parse_position_id(victim).beaver is False
|
|
284
|
+
and parse_position_id(OPENING_31_OGID).beaver is None,
|
|
285
|
+
"an XGID's beaver bit is read; an OGID says nothing")
|
|
286
|
+
check(analyze_position(victim.replace(":0:0:0:0:10", ":0:0:2:0:10"), level="1ply")["cube"]["is_beaver"]
|
|
287
|
+
and not analyze_position(victim, level="1ply")["cube"]["is_beaver"],
|
|
288
|
+
"left unset, the analysis follows the XGID")
|
|
289
|
+
check(analyze_position(
|
|
290
|
+
"XGID=cBBBBBB-C----------cccc---:0:0:-1:00:0:0:0:7:10", level="1ply")["beaver"] is False,
|
|
291
|
+
"a match position reports no beavers")
|
|
292
|
+
|
|
230
293
|
print(f"\n{_checks - len(_failures)}/{_checks} checks passed.")
|
|
231
294
|
if _failures:
|
|
232
295
|
print("FAILURES:")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|