gammonview 1.0.0__py3-none-any.whl
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-1.0.0.dist-info/METADATA +319 -0
- gammonview-1.0.0.dist-info/RECORD +38 -0
- gammonview-1.0.0.dist-info/WHEEL +4 -0
- gammonview-1.0.0.dist-info/entry_points.txt +8 -0
- gammonview-1.0.0.dist-info/licenses/LICENSE +21 -0
- gammonview-1.0.0.dist-info/licenses/THIRD-PARTY-NOTICES.md +80 -0
- gvanalysis/__init__.py +118 -0
- gvanalysis/batch.py +232 -0
- gvanalysis/checker_eval.py +378 -0
- gvanalysis/compare_gva.py +323 -0
- gvanalysis/game_eval.py +1010 -0
- gvanalysis/loader.py +67 -0
- gvanalysis/luck.py +143 -0
- gvanalysis/match.py +832 -0
- gvanalysis/ogxm_reconstructor.py +225 -0
- gvanalysis/position.py +474 -0
- gvanalysis/presets.py +344 -0
- gvanalysis/progress.py +98 -0
- gvanalysis/reconstruct_mat.py +306 -0
- gvanalysis/share.py +30 -0
- gvformat/__init__.py +65 -0
- gvformat/basefill.py +300 -0
- gvformat/bgf.py +1569 -0
- gvformat/binary.py +956 -0
- gvformat/export.py +1351 -0
- gvformat/game_reconstructor.py +311 -0
- gvformat/legality.py +243 -0
- gvformat/mat.py +144 -0
- gvformat/mat_parser.py +323 -0
- gvformat/merge.py +133 -0
- gvformat/met.py +196 -0
- gvformat/notation.py +212 -0
- gvformat/ogid.py +849 -0
- gvformat/place.py +54 -0
- gvformat/reader.py +1061 -0
- gvformat/share.py +50 -0
- gvformat/stats.py +450 -0
- gvformat/xg.py +1413 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gammonview
|
|
3
|
+
Version: 1.0.0
|
|
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/ngvlamis/gammonview-analysis
|
|
6
|
+
Project-URL: Repository, https://github.com/ngvlamis/gammonview-analysis
|
|
7
|
+
Project-URL: Issues, https://github.com/ngvlamis/gammonview-analysis/issues
|
|
8
|
+
Author: Nicholas Vlamis
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: THIRD-PARTY-NOTICES.md
|
|
12
|
+
Keywords: analysis,backgammon,bgsage,gvab,match-equity,ogxm,xg
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Games/Entertainment :: Board Games
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Provides-Extra: engine
|
|
26
|
+
Requires-Dist: bgsage>=2.0.20260907; extra == 'engine'
|
|
27
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'engine'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="https://raw.githubusercontent.com/ngvlamis/gammonview-analysis/main/assets/logo.png"
|
|
32
|
+
alt="" width="104" height="104">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<h1 align="center">GammonView</h1>
|
|
36
|
+
|
|
37
|
+
<p align="center">
|
|
38
|
+
<a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
39
|
+
<a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
|
|
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
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-green.svg" alt="Licence: MIT"></a>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
The backgammon analysis backend behind
|
|
45
|
+
**[gammonview.com](https://gammonview.com)**: read and write the site's
|
|
46
|
+
`.gvab`/`.gva` match files, import `.mat`/`.xg`/`.bgf`, analyze a match or a
|
|
47
|
+
single position with the [bgsage](https://pypi.org/project/bgsage/) engine, and
|
|
48
|
+
compute per-decision error rate (PR), luck and cube statistics.
|
|
49
|
+
|
|
50
|
+
Those files are based on **OGXM** — the chunked match format from
|
|
51
|
+
[HedgeHog](https://gitlab.com/eranlambooij/hedgehog-public), not ours — extended
|
|
52
|
+
to carry what bgsage's analysis produces. This repo reads OGXM; what it writes is
|
|
53
|
+
that extended form. See [The format](#the-format).
|
|
54
|
+
|
|
55
|
+
It ships as two importable Python packages, plus a JavaScript port of the codec
|
|
56
|
+
for the browser:
|
|
57
|
+
|
|
58
|
+
- **`gvformat`** — the pure-stdlib codec: read/write the `.gvab` binary, `.gva`
|
|
59
|
+
JSON conversion, `.xg`/`.bgf` import, compute-on-read stats, OGID. **Zero
|
|
60
|
+
third-party deps** — `pip install gammonview` and `import gvformat` anywhere,
|
|
61
|
+
no engine required. This is the format layer: everything else here, Python and
|
|
62
|
+
JavaScript alike, reads and writes through it.
|
|
63
|
+
- **`gvanalysis`** — the [bgsage](https://pypi.org/project/bgsage/)-powered
|
|
64
|
+
analysis: `analyze_match` (a match file in, analyzed `.gvab` bytes out),
|
|
65
|
+
`analyze_file` (the same work stopping at the OGXM document), and single-position
|
|
66
|
+
analysis. Depends on `gvformat` + bgsage (the `[engine]` extra). Dependencies
|
|
67
|
+
flow one way: `gvanalysis` → `gvformat`, never back.
|
|
68
|
+
- **`@gammonview/gvformat`** — the same codec in JavaScript, on npm. What reads
|
|
69
|
+
the analysis in the browser. See [In the browser](#in-the-browser).
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
To use the packages, install from PyPI — no clone needed:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install gammonview # the codec alone (gvformat), no engine
|
|
77
|
+
pip install "gammonview[engine]" # adds bgsage, for gvanalysis
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To work on them, clone the repo and let [uv](https://docs.astral.sh/uv/) build
|
|
81
|
+
the environment — it installs both packages in editable mode, plus bgsage and
|
|
82
|
+
the test dependencies:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/ngvlamis/gammonview-analysis.git
|
|
86
|
+
cd gammonview-analysis
|
|
87
|
+
uv sync
|
|
88
|
+
uv run python tests/run_all.py --fast # ~3s, no engine needed
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every command below is written `uv run …` for that checkout; from a plain
|
|
92
|
+
`pip install`, drop the prefix.
|
|
93
|
+
|
|
94
|
+
## Quickstart
|
|
95
|
+
|
|
96
|
+
Server one-liner — analyze a dropped `.mat` and get the analyzed `.gvab` back,
|
|
97
|
+
ready to store and ship to the client:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from gvanalysis import analyze_match
|
|
101
|
+
data = analyze_match("match.mat", preset="world_class") # analyzed .gvab bytes
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It runs the engine over every decision, and the bytes carry the match *and* the
|
|
105
|
+
analysis. Any input the codec reads works — `.mat`, `.gva`, `.gvab` — and one
|
|
106
|
+
that already carries analysis keeps it, with ours appended.
|
|
107
|
+
|
|
108
|
+
It returns `.gvab` (binary), always — that is the artifact to store and ship.
|
|
109
|
+
For the JSON, `read_gvab(data)` is the `.gva`; to stop a step earlier and work
|
|
110
|
+
with the document itself, `analyze_file` returns it directly. (The CLI is the
|
|
111
|
+
other way round: `gvan-match` writes `.gva` unless asked for `--gvab`, because
|
|
112
|
+
a file you open by hand is likelier to want to be readable.)
|
|
113
|
+
|
|
114
|
+
## The format
|
|
115
|
+
|
|
116
|
+
The files this repo reads and writes — `.gvab` (binary) and `.gva` (JSON) — are
|
|
117
|
+
based directly on **OGXM** (OpenGammon eXtensible Match), the chunked match
|
|
118
|
+
format from [HedgeHog](https://gitlab.com/eranlambooij/hedgehog-public), and
|
|
119
|
+
extend it with what GammonView's viewer shows and OGXM has no field for: luck,
|
|
120
|
+
per-decision PR and illegal-move flags, and per-alternative eval levels — all in
|
|
121
|
+
one `GVAN` chunk — plus match metadata like `event`, `site` and `cube_limit`.
|
|
122
|
+
|
|
123
|
+
**We read OGXM.** `read_gvab` takes a file this repo never wrote, including one
|
|
124
|
+
carrying another engine's analysis, and `gvan-match` appends its own analysis
|
|
125
|
+
block rather than replacing what is already there.
|
|
126
|
+
|
|
127
|
+
We make no claim in the other direction, and that is about us, not about OGXM.
|
|
128
|
+
One of our choices is a genuine departure: in match play we store **normalized
|
|
129
|
+
equity** in the three cube equity fields, where the base spec specifies **raw
|
|
130
|
+
MWC**. We do it for quantization — these fields are an `int16` at 1e-4, which
|
|
131
|
+
gives normalized equity about 20,000 usable steps at any score, while raw MWC
|
|
132
|
+
has as few as **16** at a lopsided one — and we recover MWC on read from the
|
|
133
|
+
shipped MET. It is the right trade for us and it is still a difference a reader
|
|
134
|
+
cannot see: same fields, same layout, different numbers.
|
|
135
|
+
|
|
136
|
+
So: treat these as GammonView files that an OGXM reader may well be able to make
|
|
137
|
+
partial sense of, not as OGXM files with extras. The two spec documents describe
|
|
138
|
+
**what this repo writes** — the base spec with every addition marked
|
|
139
|
+
`[GammonView extension]` and every divergence marked, including where a base
|
|
140
|
+
reader would go wrong. The upstream specs they track are in HedgeHog's `docs/`
|
|
141
|
+
and remain the authority on the base format.
|
|
142
|
+
|
|
143
|
+
## In the browser
|
|
144
|
+
|
|
145
|
+
The codec has a JavaScript twin, published to npm as
|
|
146
|
+
**[`@gammonview/gvformat`](https://www.npmjs.com/package/@gammonview/gvformat)**:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm install @gammonview/gvformat
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import { read_gvab, convert_mat, parse_ogid } from '@gammonview/gvformat';
|
|
154
|
+
|
|
155
|
+
const match = read_gvab(bytes); // .gvab -> .gva JSON
|
|
156
|
+
const dropped = convert_mat(matText); // .mat -> .gva JSON, no analysis
|
|
157
|
+
const pos = parse_ogid(match.games[0].plies[0].ogid_before);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
An ESM port of `gvformat` — the same reads, writes, source converters and OGID
|
|
161
|
+
handling, with **no engine, no WebAssembly and no build step**. It is how
|
|
162
|
+
gammonview.com parses a dropped `.xg`/`.bgf`/`.mat` client-side and renders the
|
|
163
|
+
`.gvab` the Python side produced.
|
|
164
|
+
|
|
165
|
+
The port lives in [`gvformat-js/`](gvformat-js/) and is maintained here rather
|
|
166
|
+
than downstream. Python and JavaScript must agree byte-for-byte — the same
|
|
167
|
+
`.gvab` in gives the same `.gva` JSON out — and the two test suites check that
|
|
168
|
+
from either side.
|
|
169
|
+
|
|
170
|
+
There is no JavaScript port of `gvanalysis`: analysis needs the engine, and the
|
|
171
|
+
engine is Python-side only.
|
|
172
|
+
|
|
173
|
+
## Command-line tools
|
|
174
|
+
|
|
175
|
+
Entry points are defined in `pyproject.toml` and run with `uv run`:
|
|
176
|
+
|
|
177
|
+
| CLI | What it does |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `gvan-match` | Analyze a match (`.mat`, `.gva`/`.ogxm`, `.gvab`), compute per-move PR → `.gva`/`.gvab` |
|
|
180
|
+
| `gvan-batch` | The same over many files or a directory, one output each |
|
|
181
|
+
| `gvan-position` | Analyze a single position from an **XGID or OGID** |
|
|
182
|
+
| `xg2gva` | Convert an eXtreme Gammon `.xg` file to `.gva` JSON (no engine) |
|
|
183
|
+
| `bgf2gva` | Convert a BGBlitz `.bgf` file to `.gva` JSON (no engine) |
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
uv run gvan-match match.mat --preset world_class --gvab # binary out, not .gva
|
|
187
|
+
uv run gvan-match match.gvab --preset fast # re-analyze a binary, append
|
|
188
|
+
uv run gvan-match match.mat --link # print a shareable URL
|
|
189
|
+
uv run gvan-batch matches/ --preset fast # a dir -> its match files
|
|
190
|
+
uv run gvan-position "XGID=-b----E-C---eE---c-e----B-:0:-1:1:31:0:0:0:0"
|
|
191
|
+
uv run gvan-position "11ccccchhhjjjjj:66666888dddddoo:N0N:13:B:R:0:0:0:0"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
An OGXM input keeps any analysis it already carries — ours is appended as a new
|
|
195
|
+
block rather than replacing it. `--link` encodes the whole match into a
|
|
196
|
+
`gammonview.com` URL fragment, so it needs no hosted file; `--browser` opens it.
|
|
197
|
+
|
|
198
|
+
### Analysis presets
|
|
199
|
+
|
|
200
|
+
`--preset` selects an eXtreme Gammon–style scheme: a cheap first pass screens
|
|
201
|
+
every decision, and a stronger pass runs only where it is needed. The six
|
|
202
|
+
built-ins:
|
|
203
|
+
|
|
204
|
+
| Preset (aliases) | 1st pass | Middle tier | 2nd pass | XG equivalent |
|
|
205
|
+
|---|---|---|---|---|
|
|
206
|
+
| `very_quick` (`vq`) | `2ply` | — | — | Very quick |
|
|
207
|
+
| `fast` (`f`) | `2ply` | — | `3ply` | Fast |
|
|
208
|
+
| `deep` (`d`) | `3ply` | — | — | Deep |
|
|
209
|
+
| `balanced` (`b`) | `2ply` | `3ply` (both kinds) | `truncated2` | — (quality/speed) |
|
|
210
|
+
| `world_class` (`wc`) | `4ply` | — | `truncated2` | World Class (XG Roller+) |
|
|
211
|
+
| `world_class_fast` (`wcf`) | `3ply` | `truncated2` (**cube only**) | `truncated2` | World Class (3-tier) |
|
|
212
|
+
|
|
213
|
+
The second pass runs on an **error**; the middle tier runs on a **borderline**
|
|
214
|
+
decision the screen would otherwise have settled. `fast` is the default.
|
|
215
|
+
|
|
216
|
+
#### Which one to use
|
|
217
|
+
|
|
218
|
+
493 matches were analyzed by XG at World Class and by each of the three strong
|
|
219
|
+
presets, then compared decision by decision:
|
|
220
|
+
|
|
221
|
+
| | mean gap to XG's match PR | same checker play as XG | PR within 1 of XG | relative cost |
|
|
222
|
+
|---|---|---|---|---|
|
|
223
|
+
| `world_class` | 0.434 | 90.3% | 91.8% | 45× |
|
|
224
|
+
| `world_class_fast` | 0.441 | 90.0% | 90.7% | 24× |
|
|
225
|
+
| `balanced` | 0.540 | 89.2% | 86.7% | 6× |
|
|
226
|
+
|
|
227
|
+
**Mean gap** is the average distance between the two *ratings* of the same
|
|
228
|
+
player-match, in either direction: rate a match with `world_class` and XG rates
|
|
229
|
+
it 0.434 PR away, typically. It is not what following the preset costs you —
|
|
230
|
+
priced by XG's own equities, a player who made `world_class`'s recommended
|
|
231
|
+
checker play every time would post a PR of 0.333, `world_class_fast` 0.353,
|
|
232
|
+
`balanced` 0.412. All three are inside what XG itself calls world-class play.
|
|
233
|
+
|
|
234
|
+
Cost is mean wall clock relative to `fast` over four matches, and varies with
|
|
235
|
+
the match — see [the timings](docs/CLI.md#what-they-cost).
|
|
236
|
+
|
|
237
|
+
- **`world_class_fast` is the recommended analysis.** It lands 0.007 PR behind
|
|
238
|
+
the deepest preset for a little under half the time. The two sit three times
|
|
239
|
+
closer to each other (0.136) than either does to XG, because what separates
|
|
240
|
+
them from XG is the *engine*, not the search depth — 95% of
|
|
241
|
+
`world_class_fast`'s squared PR error against XG is that engine floor, which
|
|
242
|
+
no preset can spend its way past.
|
|
243
|
+
- **`world_class` is for reproducing XG specifically**, not for being more
|
|
244
|
+
accurate. Its only real edge is on borderline cubes, and it gets there by
|
|
245
|
+
rolling them out *less* often than `world_class_fast` does — which is
|
|
246
|
+
XG-like, not demonstrably better. It costs roughly twice as much for that.
|
|
247
|
+
- **`balanced` is the quality/speed option**, and the only one of the three with
|
|
248
|
+
real headroom: upgrading it recovers eight times as much error as upgrading
|
|
249
|
+
`world_class_fast` does.
|
|
250
|
+
|
|
251
|
+
Every number above measures *similarity to XG*, which is not the same as being
|
|
252
|
+
right — where the two engines differ there is no arbiter here to say which is
|
|
253
|
+
correct. **[`docs/PRESET_ACCURACY.md`](docs/PRESET_ACCURACY.md)** is the full
|
|
254
|
+
experiment: how the presets split on checker play versus the cube, why rolling
|
|
255
|
+
out *costs* agreement with XG, and the evidence that the residual disagreement
|
|
256
|
+
is systematic rather than noise.
|
|
257
|
+
|
|
258
|
+
Presets live in `gvanalysis/presets.py` and are overridable via `presets.yaml`.
|
|
259
|
+
See **[`docs/CLI.md`](docs/CLI.md)** for the full reference — every flag, how the
|
|
260
|
+
tiers fire, parallelism, and preset overrides.
|
|
261
|
+
|
|
262
|
+
## Documentation
|
|
263
|
+
|
|
264
|
+
| Doc | What it covers |
|
|
265
|
+
|---|---|
|
|
266
|
+
| [`docs/CLI.md`](docs/CLI.md) | **Full command-line reference** — every flag, presets, parallelism |
|
|
267
|
+
| [`gvformat-js/README.md`](gvformat-js/README.md) | The JavaScript package — API, layout, parity rules |
|
|
268
|
+
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | How to run the tests, the conventions, what a good change looks like |
|
|
269
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | Release history, including the measurements behind several design decisions |
|
|
270
|
+
| [`CLAUDE.md`](CLAUDE.md) | Maintainer and coding-agent notes: design rationale, measurements, and the rituals around an engine upgrade |
|
|
271
|
+
| [`docs/OGXM_JSON_SPEC_GAMMONVIEW.md`](docs/OGXM_JSON_SPEC_GAMMONVIEW.md) | The JSON shape this repo writes (match → game → ply → analysis) — base spec plus every GammonView addition, marked |
|
|
272
|
+
| [`docs/OGXM_FORMAT_SPEC_GAMMONVIEW.md`](docs/OGXM_FORMAT_SPEC_GAMMONVIEW.md) | The binary `.gvab` chunk layout, including the `GVAN` extension chunk |
|
|
273
|
+
| [`docs/OGXM_COMPUTED_FIELDS.md`](docs/OGXM_COMPUTED_FIELDS.md) | Fields the reader computes rather than stores (PR, luck-MWC, MWC, classification) |
|
|
274
|
+
| [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) | PR / luck / cube-decision methodology and filters |
|
|
275
|
+
| [`docs/PRESET_ACCURACY.md`](docs/PRESET_ACCURACY.md) | What each preset costs in accuracy, measured against XG over 493 matches |
|
|
276
|
+
|
|
277
|
+
## Licence
|
|
278
|
+
|
|
279
|
+
[MIT](LICENSE). Every source file carries an SPDX header; `LICENSE` holds the
|
|
280
|
+
full text.
|
|
281
|
+
|
|
282
|
+
MIT because what this repo mainly offers is a **format**, and a format wants to
|
|
283
|
+
be copied in pieces — one routine lifted into another project, or the codec
|
|
284
|
+
ported to another language. It also stays compatible in both directions at once,
|
|
285
|
+
which matters in this ecosystem: GNU Backgammon is GPL, eXtreme Gammon and
|
|
286
|
+
BGBlitz are proprietary, and all of them are welcome to interoperate.
|
|
287
|
+
[HedgeHog](https://gitlab.com/eranlambooij/hedgehog-public), whose format this
|
|
288
|
+
implements, is MIT too.
|
|
289
|
+
|
|
290
|
+
[bgsage](https://pypi.org/project/bgsage/), the engine behind the optional
|
|
291
|
+
`[engine]` extra, is MPL-2.0, which constrains nothing here. MPL is *file-level*
|
|
292
|
+
copyleft with no linking trigger: it attaches to its own files, and §3.3
|
|
293
|
+
expressly permits distributing a larger work under other terms. bgsage resolves
|
|
294
|
+
from PyPI at install time and is never modified or redistributed here, and
|
|
295
|
+
`gvformat` does not touch the engine at all.
|
|
296
|
+
|
|
297
|
+
The JavaScript mirror ([`gvformat-js/`](gvformat-js/), published as
|
|
298
|
+
`@gammonview/gvformat`) is under the same licence and ships its own copy.
|
|
299
|
+
|
|
300
|
+
The **GammonView logo** (`assets/logo.png`) is excluded: it identifies the site,
|
|
301
|
+
so it is not part of the MIT grant. Fork the code freely; use your own mark.
|
|
302
|
+
|
|
303
|
+
### Third-party material
|
|
304
|
+
|
|
305
|
+
Two pieces of third-party material are incorporated, both permissively licensed.
|
|
306
|
+
Their notices must be preserved — see **[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)**,
|
|
307
|
+
which reproduces them in full:
|
|
308
|
+
|
|
309
|
+
- **HedgeHog** (MIT, © 2026 Eran Lambooij) — OGXM is HedgeHog's format. Most of
|
|
310
|
+
`gvformat` is an independent implementation, but `binary.py` and `export.py`
|
|
311
|
+
(and their JS mirrors) are genuine ports of its C++ sources: the chunk struct
|
|
312
|
+
layouts and `parse_ply_analysis_json` from `ogxm_io.cpp`/`ogxm_json.cpp`, and
|
|
313
|
+
`_TurnState` from `ogxm_replay.cpp`'s `replay_game()`. Those four files say so
|
|
314
|
+
in their headers.
|
|
315
|
+
- **Kazaross-XG2 MET** (© 2011 Neil Kazaross; transcribed for GNUbg by Michael
|
|
316
|
+
Petch) — the match-equity table embedded in `gvformat/met.py` and
|
|
317
|
+
`gvformat-js/src/met.js`. It ships with GNU Backgammon but is *not* under its
|
|
318
|
+
GPL; its own terms require only that the notice be preserved, and it is
|
|
319
|
+
reproduced at the top of both files.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
gvanalysis/__init__.py,sha256=S9O17fPn54wZrTT2qA_JLFUPzAywkDVNFwRNqJ8EAkU,5185
|
|
2
|
+
gvanalysis/batch.py,sha256=ZqFGyimIL2Vf8lvkLnzbwHkomgqj51pTmWl_ZIaJoBg,8205
|
|
3
|
+
gvanalysis/checker_eval.py,sha256=y38kmFk8MqDZj6h1Rn-m4GLhYPdQVvoY3mobldg3TSQ,17138
|
|
4
|
+
gvanalysis/compare_gva.py,sha256=Os-NA_Jjy-dBU61Bl7EXVprurO2y1sw8iSyMw1nhFqo,12358
|
|
5
|
+
gvanalysis/game_eval.py,sha256=AMhctfXq0gxnddSxxYUpl4K2pqqPJxUbBLyzMBf_Bcw,46745
|
|
6
|
+
gvanalysis/loader.py,sha256=Uh7wklmW9hYSIFFUv98Z5zRsblaoPP566ispILB0PDE,2448
|
|
7
|
+
gvanalysis/luck.py,sha256=CCafBrb58jYTap8wx2rCck47p8sCe4tOV_5A8TMQ_QQ,6260
|
|
8
|
+
gvanalysis/match.py,sha256=EoC0jhCAf_hkdl8qctllJD48FKAy86JcOQhJan3OEnU,36579
|
|
9
|
+
gvanalysis/ogxm_reconstructor.py,sha256=IWuP6BmwJ3gVh_5AGu5KIagg7TX1Eh5I-COFg_tMxIk,9717
|
|
10
|
+
gvanalysis/position.py,sha256=_Rzrq8Hjh1hYSD6IP-msnA4oOEdmBhjHVdIb0trjQ-4,19103
|
|
11
|
+
gvanalysis/presets.py,sha256=-y0DwAfdCt0z9eNwrPlXn4iIeyQtDC7inni6zBccLCA,17124
|
|
12
|
+
gvanalysis/progress.py,sha256=SlNBJwFI3EyAvo1_CcwiOfpuxtA2pu7WyvTxhDL1jvk,3341
|
|
13
|
+
gvanalysis/reconstruct_mat.py,sha256=IA3IaZ4EfTf0vbSksXZcx3EntIlbkvjckixQrhPrpTo,10358
|
|
14
|
+
gvanalysis/share.py,sha256=60hjWWSBZWx1lduBgyx2T3_--zKTiAGXL3GdQoG55J4,1312
|
|
15
|
+
gvformat/__init__.py,sha256=0b1FwWAAfzv-_Ja8iCVB88nwqx5PW6ZrqwDKj7KggEo,2186
|
|
16
|
+
gvformat/basefill.py,sha256=nQp7xYC_co0iO1uPZ32lHYAmk_v9PdO-VuJvJk5YNwM,12477
|
|
17
|
+
gvformat/bgf.py,sha256=TDy97AD5zzcQvTB0afVDj44MDxsl41DG5eY_M6wdocM,61346
|
|
18
|
+
gvformat/binary.py,sha256=OoVVJEwTxf_JVOLv0IeHsjJUtTRhRYP2B3PKDpbgejM,40839
|
|
19
|
+
gvformat/export.py,sha256=kGwi6h23lb9KQ5X3QQlnZHA8gM_sgo7Mtn6mRnP-6RI,59291
|
|
20
|
+
gvformat/game_reconstructor.py,sha256=rs6qD8pJjFKVC2w3Kl14guXOcYYGn1Gp-y8pIY6ydbw,12133
|
|
21
|
+
gvformat/legality.py,sha256=eRU3MyNx_J2U_seS6KB7aILebaoK_yJW35XympnscyI,9198
|
|
22
|
+
gvformat/mat.py,sha256=JRtJFWsTnV3eNG6LGUWIFqTRn5cOE7pSXSaic8G-J2I,6140
|
|
23
|
+
gvformat/mat_parser.py,sha256=v1QgSnBqdwWXcmWhwHrAe0xWdzGwVGMCZbWK3hC8fjM,11794
|
|
24
|
+
gvformat/merge.py,sha256=B-M-MIyzsPFQyJSh56Nb3YMsbVJjnue8WLpoLtzUEoU,5800
|
|
25
|
+
gvformat/met.py,sha256=QYjycKZatVWo1F3M8IxQngE8qCbcmhXy-qrrFkXn9T4,13769
|
|
26
|
+
gvformat/notation.py,sha256=s69yhh-5IlXVAXK1OKMNiZDrrABPoElzAdFK_skIHGI,8294
|
|
27
|
+
gvformat/ogid.py,sha256=rn5rOPxXjqa9wLu9sRcE3k7aZhkczmd9S0Niy0Vq67g,35673
|
|
28
|
+
gvformat/place.py,sha256=CfoiYndZg08NGqVK2r1HuYbgBYIT2gKVSpzSFDVXasE,2185
|
|
29
|
+
gvformat/reader.py,sha256=jkQolPbQp4bcltrdl0EZ7D8777kn7ygRoN6FVHgFUKQ,46464
|
|
30
|
+
gvformat/share.py,sha256=ZjHmbhWRbnZhH-MDqbC8zPWXuktzmMFwhjhLbOdghn0,1944
|
|
31
|
+
gvformat/stats.py,sha256=5qLR2okawClhcMZ96b06sOq8jO36YfWUTgU8iS0K9TM,19811
|
|
32
|
+
gvformat/xg.py,sha256=QV2qUNWQ5PXUKo7akS40bf2nINDuywaz0S_RjmRXkms,62957
|
|
33
|
+
gammonview-1.0.0.dist-info/METADATA,sha256=5zX8d1dABVXNwZB_nN44fhlpukmCIPweHSRiwVCRUoA,16692
|
|
34
|
+
gammonview-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
35
|
+
gammonview-1.0.0.dist-info/entry_points.txt,sha256=6jzfIHRiRLHQOe6tryjxj_JOkBrlkVW7kXJnLxfxfOw,275
|
|
36
|
+
gammonview-1.0.0.dist-info/licenses/LICENSE,sha256=alJKWw6YN2SqgNVH3WHiBoE610YzS716wiR8qQSIcBc,1072
|
|
37
|
+
gammonview-1.0.0.dist-info/licenses/THIRD-PARTY-NOTICES.md,sha256=79PZed48nGcRAWkYUWQ2IVDgKmXCIz0eFmR-5Nya6uw,3472
|
|
38
|
+
gammonview-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
bgf2gva = gvformat.bgf:main
|
|
3
|
+
compare-gva = gvanalysis.compare_gva:main
|
|
4
|
+
gvan-batch = gvanalysis.batch:main
|
|
5
|
+
gvan-match = gvanalysis.match:main
|
|
6
|
+
gvan-position = gvanalysis.position:main
|
|
7
|
+
reconstruct-mat = gvanalysis.reconstruct_mat:main
|
|
8
|
+
xg2gva = gvformat.xg:main
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nicholas Vlamis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This project is MIT-licensed (see `LICENSE`). It incorporates the third-party
|
|
4
|
+
material below, each under its own terms. **These notices must be preserved in
|
|
5
|
+
any copy or substantial portion of this software.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## HedgeHog — the OGXM format and reference codec
|
|
10
|
+
|
|
11
|
+
OGXM is HedgeHog's format. `gvformat` is an independent implementation of it,
|
|
12
|
+
but several modules are direct ports of HedgeHog's C++ sources rather than
|
|
13
|
+
clean-room implementations of the published spec:
|
|
14
|
+
|
|
15
|
+
| This repo | Ported from |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `gvformat/binary.py`, `gvformat-js/src/binary.js` | `src/match/ogxm_io.cpp`, `ogxm_format.hpp`, `ogxm_json.cpp` — chunk struct layouts, `encode_ply`, fixed-point probability encoding, and `parse_ply_analysis_json` field-for-field |
|
|
18
|
+
| `gvformat/export.py`, `gvformat-js/src/export.js` | `src/match/ogxm_replay.cpp` — `_TurnState` is a port of `replay_game()`; the OGID state/action string constants are taken byte-for-byte from `src/ogid.h` |
|
|
19
|
+
|
|
20
|
+
Source: <https://gitlab.com/eranlambooij/hedgehog-public>
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
MIT License
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2026 Eran Lambooij
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Kazaross-XG2 match-equity table
|
|
49
|
+
|
|
50
|
+
The MET data shipped in `gvformat/met.py` and `gvformat-js/src/met.js` (the
|
|
51
|
+
25x25 pre-Crawford table and the post-Crawford vector) is transcribed from GNU
|
|
52
|
+
Backgammon's `met/Kazaross-XG2.xml`. The table is separately licensed under the
|
|
53
|
+
permissive notice below — it is **not** covered by GNU Backgammon's GPL.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Copyright (C) 2011 Neil Kazaross
|
|
57
|
+
|
|
58
|
+
Table rolled up to 9 point match by eXtreme Gammon. Then uses R/K MET
|
|
59
|
+
which was rolled up to 15 and extrapolated to 25 points.
|
|
60
|
+
|
|
61
|
+
Transcribed for use by GNUbg by Michael Petch <mpetch@capp-sysware.com>.
|
|
62
|
+
|
|
63
|
+
This file is distributed as a part of the GNU Backgammon program.
|
|
64
|
+
Copying and distribution of this file, with or without modification, are
|
|
65
|
+
permitted in any medium without royalty provided the copyright notice and
|
|
66
|
+
this notice are preserved. This file is offered as-is, without any warranty.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Not bundled
|
|
72
|
+
|
|
73
|
+
For completeness, since both are commonly assumed to be vendored here:
|
|
74
|
+
|
|
75
|
+
- **bgsage** (MPL-2.0) — the analysis engine. An optional `[engine]` extra
|
|
76
|
+
resolved from PyPI at install time; no bgsage source is included in this
|
|
77
|
+
repository, and none of its files are modified.
|
|
78
|
+
- **pako** (MIT) — used by the JS mirror for zlib deflate. A declared npm
|
|
79
|
+
dependency, installed by `npm install`; not vendored, and excluded from the
|
|
80
|
+
Python sdist.
|
gvanalysis/__init__.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (C) 2026 Nicholas Vlamis
|
|
3
|
+
|
|
4
|
+
"""GammonView Analysis — bgsage-powered backgammon analysis.
|
|
5
|
+
|
|
6
|
+
This is the *analysis* layer: it drives the bgsage engine to evaluate a match
|
|
7
|
+
or position, and hands the result to the pure-stdlib ``gvformat`` codec for
|
|
8
|
+
serialization. It depends on ``gvformat`` + ``bgsage`` (the ``[engine]`` extra);
|
|
9
|
+
``gvformat`` on its own has no such dependency.
|
|
10
|
+
|
|
11
|
+
Typical server use — analyze a dropped match file and get the analyzed
|
|
12
|
+
``.gvab`` bytes back, in one call::
|
|
13
|
+
|
|
14
|
+
from gvanalysis import analyze_match
|
|
15
|
+
data = analyze_match("match.mat", preset="world_class") # bytes to send to the client
|
|
16
|
+
|
|
17
|
+
Or the pieces:
|
|
18
|
+
|
|
19
|
+
from gvanalysis import analyze_mat
|
|
20
|
+
from gvformat import to_ogxm_json, write_gvab, read_gvab
|
|
21
|
+
result = analyze_mat("match.mat", preset="fast")
|
|
22
|
+
data = write_gvab(to_ogxm_json(result))
|
|
23
|
+
|
|
24
|
+
A single position is a different scale of problem -- fast enough to answer in
|
|
25
|
+
a request, so it returns a plain OGXM-shaped dict rather than a file::
|
|
26
|
+
|
|
27
|
+
from gvanalysis import analyze_position
|
|
28
|
+
result = analyze_position("XGID=...", level="3ply") # ~0.07s at 3ply
|
|
29
|
+
|
|
30
|
+
CLIs: ``gvan-match`` / ``gvan-position`` (or ``python -m gvanalysis.match``).
|
|
31
|
+
|
|
32
|
+
Note: ``analyze_mat`` / ``analyze_match`` (and the ``match`` / ``position`` /
|
|
33
|
+
``game_eval`` submodules) require the bgsage engine — the ``[engine]`` extra.
|
|
34
|
+
Importing ``gvanalysis`` itself, and the engine-free tool submodules
|
|
35
|
+
(``reconstruct_mat``, ``compare_gva``), works without it; the engine
|
|
36
|
+
requirement only surfaces — with a clear message — when you actually reach for
|
|
37
|
+
the analyzer.
|
|
38
|
+
|
|
39
|
+
The ``.mat`` parser/converter is *not* here: it needs no engine, so it lives in
|
|
40
|
+
the codec layer beside the other source-format converters — ``gvformat``'s
|
|
41
|
+
``convert_mat``/``mat_to_ogxm`` (and ``mat_parser`` / ``game_reconstructor``),
|
|
42
|
+
mirroring ``convert_xg`` and ``convert_bgf``.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
_ENGINE_HINT = (
|
|
46
|
+
"the bgsage engine is required for this, but isn't installed. Add the "
|
|
47
|
+
"analysis extra:\n"
|
|
48
|
+
" pip install 'gammonview[engine]' (or, in this repo: uv sync)\n"
|
|
49
|
+
"The pure-codec layer `gvformat` needs no engine."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _load_match():
|
|
54
|
+
"""Import the engine-backed ``match`` module, or raise a clear, actionable
|
|
55
|
+
error when the bgsage engine (the ``[engine]`` extra) isn't installed."""
|
|
56
|
+
try:
|
|
57
|
+
from . import match
|
|
58
|
+
except ModuleNotFoundError as e:
|
|
59
|
+
if (e.name or "") in ("bgsage", "bgbot_cpp") or (e.name or "").startswith("bgsage."):
|
|
60
|
+
raise ModuleNotFoundError(f"gvanalysis: {_ENGINE_HINT}") from e
|
|
61
|
+
raise
|
|
62
|
+
return match
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def analyze_match(input_path, *, preset: str | None = None, quiet: bool = True, **kwargs) -> bytes:
|
|
66
|
+
"""Analyze a match file and return the analyzed ``.gvab`` bytes.
|
|
67
|
+
|
|
68
|
+
Returns **bytes**, not a dict: this is the one call that hands back the
|
|
69
|
+
storable artifact. ``analyze_file`` is the same work stopping one step
|
|
70
|
+
earlier, at the OGXM document -- ``analyze_match(p)`` is exactly
|
|
71
|
+
``write_gvab(analyze_file(p))``, and ``read_gvab`` of these bytes is that
|
|
72
|
+
document back.
|
|
73
|
+
|
|
74
|
+
One call for the common server path: a match in (``.mat``, ``.gva``/
|
|
75
|
+
``.ogxm``, or ``.gvab`` — OGXM is the internal representation, so ``.mat``
|
|
76
|
+
is converted first), canonical ``.gvab`` bytes out, carrying the match
|
|
77
|
+
*and* the analysis this call just ran (ready to store and ship to the
|
|
78
|
+
client). An OGXM input's existing analysis blocks are preserved and ours is
|
|
79
|
+
appended. Equivalent to ``write_gvab(analyze_file(...))``. Requires the
|
|
80
|
+
``[engine]`` extra; without it, raises a ``ModuleNotFoundError`` explaining
|
|
81
|
+
how to install.
|
|
82
|
+
|
|
83
|
+
Extra keyword arguments go to ``analyze_ogxm``; notably
|
|
84
|
+
``on_progress=lambda done, total: ...`` reports decision-level progress, for
|
|
85
|
+
a server driving a progress bar in a client.
|
|
86
|
+
"""
|
|
87
|
+
analyze_file = _load_match().analyze_file
|
|
88
|
+
from gvformat import write_gvab
|
|
89
|
+
return write_gvab(analyze_file(input_path, preset=preset, quiet=quiet, **kwargs))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
#: The former name. It read as a format conversion and named an input format
|
|
93
|
+
#: this call never required, so the analysis it runs was invisible in it; kept
|
|
94
|
+
#: working because callers exist.
|
|
95
|
+
mat_to_gvab = analyze_match
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def __getattr__(name):
|
|
99
|
+
# PEP 562: expose the engine-backed entry points lazily so `import
|
|
100
|
+
# gvanalysis` (and the engine-free tool submodules) load without bgsage; the
|
|
101
|
+
# engine requirement only bites — with a clear message — when the analyzer
|
|
102
|
+
# is actually used.
|
|
103
|
+
if name in ("analyze_mat", "analyze_ogxm", "analyze_file"):
|
|
104
|
+
return getattr(_load_match(), name)
|
|
105
|
+
# `position` imports no engine at module scope (it reaches for bgsage only
|
|
106
|
+
# inside the call), so it needs none of _load_match's guarding — but it is
|
|
107
|
+
# still lazy, to keep `import gvanalysis` as cheap as it has always been.
|
|
108
|
+
if name == "analyze_position":
|
|
109
|
+
from . import position
|
|
110
|
+
return position.analyze_position
|
|
111
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
__all__ = [
|
|
115
|
+
"analyze_mat", "analyze_ogxm", "analyze_file",
|
|
116
|
+
"analyze_match", "mat_to_gvab",
|
|
117
|
+
"analyze_position",
|
|
118
|
+
]
|