player-prepper 0.1.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.
- player_prepper-0.1.0/LICENSE +21 -0
- player_prepper-0.1.0/PKG-INFO +637 -0
- player_prepper-0.1.0/README.md +600 -0
- player_prepper-0.1.0/player_prepper/__init__.py +3 -0
- player_prepper-0.1.0/player_prepper/board.py +223 -0
- player_prepper-0.1.0/player_prepper/book.py +507 -0
- player_prepper-0.1.0/player_prepper/bridge.py +110 -0
- player_prepper-0.1.0/player_prepper/cli.py +494 -0
- player_prepper-0.1.0/player_prepper/engine.py +306 -0
- player_prepper-0.1.0/player_prepper/exploit.py +199 -0
- player_prepper-0.1.0/player_prepper/export.py +279 -0
- player_prepper-0.1.0/player_prepper/fetch.py +407 -0
- player_prepper-0.1.0/player_prepper/jobs.py +155 -0
- player_prepper-0.1.0/player_prepper/openings.py +148 -0
- player_prepper-0.1.0/player_prepper/paths.py +82 -0
- player_prepper-0.1.0/player_prepper/pipeline.py +200 -0
- player_prepper-0.1.0/player_prepper/scout.py +454 -0
- player_prepper-0.1.0/player_prepper/server.py +547 -0
- player_prepper-0.1.0/player_prepper/store.py +197 -0
- player_prepper-0.1.0/player_prepper/tree.py +363 -0
- player_prepper-0.1.0/player_prepper/web/app.js +1432 -0
- player_prepper-0.1.0/player_prepper/web/index.html +303 -0
- player_prepper-0.1.0/player_prepper/web/style.css +425 -0
- player_prepper-0.1.0/player_prepper.egg-info/PKG-INFO +637 -0
- player_prepper-0.1.0/player_prepper.egg-info/SOURCES.txt +30 -0
- player_prepper-0.1.0/player_prepper.egg-info/dependency_links.txt +1 -0
- player_prepper-0.1.0/player_prepper.egg-info/entry_points.txt +2 -0
- player_prepper-0.1.0/player_prepper.egg-info/requires.txt +11 -0
- player_prepper-0.1.0/player_prepper.egg-info/top_level.txt +1 -0
- player_prepper-0.1.0/pyproject.toml +57 -0
- player_prepper-0.1.0/setup.cfg +4 -0
- player_prepper-0.1.0/tests/test_prepper.py +742 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shubhro Dev
|
|
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,637 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: player-prepper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scout a chess opponent from their own games, and measure your preparation against them.
|
|
5
|
+
Author-email: Shubhro Dev <shubhro2004@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/spearb0lt/Lichess-Essentials
|
|
8
|
+
Project-URL: Repository, https://github.com/spearb0lt/Lichess-Essentials
|
|
9
|
+
Project-URL: Issues, https://github.com/spearb0lt/Lichess-Essentials/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/spearb0lt/Lichess-Essentials/blob/main/Player-Prepper/README.md
|
|
11
|
+
Keywords: chess,lichess,opponent,scouting,preparation,openings
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Games/Entertainment :: Board Games
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: chess>=1.11
|
|
28
|
+
Requires-Dist: requests>=2.31
|
|
29
|
+
Requires-Dist: fastapi>=0.110
|
|
30
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
31
|
+
Requires-Dist: pydantic>=2.0
|
|
32
|
+
Provides-Extra: prep
|
|
33
|
+
Requires-Dist: lichess-study-to-pdf>=0.1.0; extra == "prep"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# Player Prepper
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/player-prepper/)
|
|
41
|
+
[](https://pypi.org/project/player-prepper/)
|
|
42
|
+
[](https://pepy.tech/project/player-prepper)
|
|
43
|
+
[](https://pepy.tech/project/player-prepper)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Scout an opponent from their own games, and find out what you have no answer
|
|
48
|
+
for.
|
|
49
|
+
|
|
50
|
+
Give it a Lichess or Chess.com username. It pulls a few hundred of their
|
|
51
|
+
games, folds them into an opening tree keyed by position, and answers three
|
|
52
|
+
questions:
|
|
53
|
+
|
|
54
|
+
- **What do they play?** Per colour, counted, named, with how they score.
|
|
55
|
+
- **Where do they leak points?** Their own results, ranked by how many points
|
|
56
|
+
a move has actually cost them. No engine, no opinion.
|
|
57
|
+
- **What have you got nothing for?** Every position their games reach where it
|
|
58
|
+
is *your* move and your book is empty — weighted by how many of their games
|
|
59
|
+
would put you there.
|
|
60
|
+
- **Where should you aim?** Their real choices crossed with what the engine
|
|
61
|
+
says you get against each, ranked by an opportunity score you control.
|
|
62
|
+
|
|
63
|
+
That third one is the whole product, and it needs to know what you play. It
|
|
64
|
+
can read that from a Repertoire-Creator repertoire, a Lichess study, or your
|
|
65
|
+
own games — or all three at once.
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
cd Player-Prepper
|
|
69
|
+
& "..\.lichess\Scripts\python.exe" -m player_prepper.cli serve # port 8780
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then open <http://127.0.0.1:8780>.
|
|
73
|
+
|
|
74
|
+

|
|
75
|
+
|
|
76
|
+
*Above: this opponent meets 1.e4 with the Scandinavian in 30 of their 60 games
|
|
77
|
+
as Black, and the repertoire being measured has no move written down for it.*
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install player-prepper
|
|
85
|
+
prepper serve
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Optional.** Engine suggestions for gaps, prep-sheet PDFs and private
|
|
89
|
+
studies: `pip install "player-prepper[prep]"`.
|
|
90
|
+
|
|
91
|
+
Or take all five at once with `pip install lichess-essentials`. Installed this
|
|
92
|
+
way your files live in the usual per-user folder for your platform, and the
|
|
93
|
+
app prints the path in its startup banner. To run it from a checkout instead,
|
|
94
|
+
see [the repository README](https://github.com/spearb0lt/Lichess-Essentials/blob/main/README.md#setup-from-a-checkout).
|
|
95
|
+
|
|
96
|
+
## What a gap actually is
|
|
97
|
+
|
|
98
|
+
A gap is not "a position you have not studied" — there are billions of those.
|
|
99
|
+
It is a position that
|
|
100
|
+
|
|
101
|
+
1. **this opponent actually steers into**, in games they really played,
|
|
102
|
+
2. **your own repertoire actually reaches**, so it is a line you will get, and
|
|
103
|
+
3. **you have written nothing about**.
|
|
104
|
+
|
|
105
|
+
The count beside it is the number of their games that would have put you
|
|
106
|
+
there. That is the number worth working through in order.
|
|
107
|
+
|
|
108
|
+
Getting there means walking each of their games ply by ply, up to the scouting
|
|
109
|
+
depth, and doing one of three things at every one of your turns:
|
|
110
|
+
|
|
111
|
+
| At your turn | |
|
|
112
|
+
|---|---|
|
|
113
|
+
| Your book has a move, and they played into it | Carry on. |
|
|
114
|
+
| Your book has a move, and the game went somewhere else | **Drop the game.** Their real opponent played something you never would, so this game is about somebody else's repertoire. Counting it would punish you for a line you cannot reach. |
|
|
115
|
+
| Your book has nothing here | **A gap.** Record the position, stop the game, and add one to that position's count. |
|
|
116
|
+
|
|
117
|
+
Their own moves are never checked against anything — whatever they played, you
|
|
118
|
+
have to meet it.
|
|
119
|
+
|
|
120
|
+
So a colour's coverage comes out as three numbers that add up:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
their 60 games as Black
|
|
124
|
+
= 18 that never reach your repertoire (their opponent opened 1.d4; you play 1.e4)
|
|
125
|
+
+ 42 that do
|
|
126
|
+
= 0 that stay inside it to move 12
|
|
127
|
+
+ 42 that run into one of 6 gap positions
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The middle number is the honest denominator. A tool that measured coverage
|
|
131
|
+
against all 60 would report 70% "coverage" for a repertoire that answers
|
|
132
|
+
nothing, purely because most of their games were unreachable.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Setting up
|
|
137
|
+
|
|
138
|
+
Everything shares the one virtualenv at the repository root. From the
|
|
139
|
+
repository root:
|
|
140
|
+
|
|
141
|
+
```powershell
|
|
142
|
+
# Windows PowerShell
|
|
143
|
+
python -m venv .lichess
|
|
144
|
+
.\.lichess\Scripts\python.exe -m pip install -r Player-Prepper\requirements.txt
|
|
145
|
+
.\.lichess\Scripts\python.exe -m pip install -e Lichess-Study-to-PDF
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Git Bash on Windows
|
|
150
|
+
python -m venv .lichess
|
|
151
|
+
./.lichess/Scripts/python.exe -m pip install -r Player-Prepper/requirements.txt
|
|
152
|
+
./.lichess/Scripts/python.exe -m pip install -e Lichess-Study-to-PDF
|
|
153
|
+
|
|
154
|
+
# macOS / Linux
|
|
155
|
+
python -m venv .lichess
|
|
156
|
+
./.lichess/bin/python -m pip install -r Player-Prepper/requirements.txt
|
|
157
|
+
./.lichess/bin/python -m pip install -e Lichess-Study-to-PDF
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
That second install is the sibling app, and it is **optional**. Without it the
|
|
161
|
+
app still scouts, still measures coverage and still lists every gap; what you
|
|
162
|
+
lose is the engine's suggestion for a gap, the PDF export, and the ability to
|
|
163
|
+
read a *private* study as your book. The startup banner tells you which of
|
|
164
|
+
those are available.
|
|
165
|
+
|
|
166
|
+
A [Stockfish](https://stockfishchess.org/download/) binary in
|
|
167
|
+
`Lichess-Study-to-PDF/engine/` is what turns on the engine suggestions. A LaTeX
|
|
168
|
+
install (MiKTeX or TeX Live) unlocks the typeset book layout for the PDF.
|
|
169
|
+
|
|
170
|
+
**No API token is needed.** Every endpoint this app uses is public. A Lichess
|
|
171
|
+
token raises the rate limit when you are pulling hundreds of games, and lets a
|
|
172
|
+
private study be used as your book — paste one into the Token dialog and it is
|
|
173
|
+
kept in memory for that session only, never written to disk.
|
|
174
|
+
|
|
175
|
+
## Running it
|
|
176
|
+
|
|
177
|
+
```powershell
|
|
178
|
+
# Windows PowerShell
|
|
179
|
+
cd Player-Prepper
|
|
180
|
+
& "..\.lichess\Scripts\python.exe" -m player_prepper.cli serve
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Git Bash on Windows
|
|
185
|
+
cd Player-Prepper
|
|
186
|
+
../.lichess/Scripts/python.exe -m player_prepper.cli serve
|
|
187
|
+
|
|
188
|
+
# macOS / Linux
|
|
189
|
+
cd Player-Prepper
|
|
190
|
+
../.lichess/bin/python -m player_prepper.cli serve
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Open <http://127.0.0.1:8780>. `Ctrl+C` stops it. `--port 8781` if 8780 is
|
|
194
|
+
taken; `--host 0.0.0.0` exposes it to your network, which has no
|
|
195
|
+
authentication unless you set the password gate below — only do that on a
|
|
196
|
+
network you trust.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Your book: the three sources
|
|
201
|
+
|
|
202
|
+
Coverage is measured against a **book**, which is `{position: moves you play
|
|
203
|
+
from it}` — the same shape as Repertoire-Creator's universal mode, and for the
|
|
204
|
+
same reason. Keying on the position rather than the line is what makes a scout
|
|
205
|
+
survive move orders: if their pet order transposes into a normal Sicilian, a
|
|
206
|
+
line-keyed book would call it uncovered and simply be wrong.
|
|
207
|
+
|
|
208
|
+
Click the **book** pill in the top bar. You can stack as many sources as you
|
|
209
|
+
like; they fold into one book.
|
|
210
|
+
|
|
211
|
+
| Source | |
|
|
212
|
+
|---|---|
|
|
213
|
+
| **A Repertoire-Creator repertoire** | Read straight off `Repertoire-Creator/repertoires/`. That folder is plain PGN plus a small JSON manifest, so this reads the files rather than importing the app — the two never have to be installed together. Set `REPERTOIRE_DIR` if yours live elsewhere; it is the same variable the sibling app honours. |
|
|
214
|
+
| **A Lichess study** | Paste a study or chapter URL. With the sibling exporter installed, private studies work through its chapter-by-chapter route with no token. Without it, public studies still work. |
|
|
215
|
+
| **Your own games** | What you actually play, as opposed to what you wrote down. Only your moves are recorded, and how often you played each, so the book knows your main move from something you tried once. |
|
|
216
|
+
|
|
217
|
+
**Only your side's moves are ever recorded.** A white repertoire lists Black's
|
|
218
|
+
tries too, and counting those as your own answers would report coverage you do
|
|
219
|
+
not have — so a repertoire contributes only moves made by the colour it is
|
|
220
|
+
for, and a book built from your games contributes only the moves *you* made.
|
|
221
|
+
|
|
222
|
+
Colour is never stored, because it does not need to be: a move recorded from a
|
|
223
|
+
position where White is to move *is* a white move. Coverage asks about one
|
|
224
|
+
colour at a time and looks at whose turn it is.
|
|
225
|
+
|
|
226
|
+
The one case with no answer is a **study chapter with no `Orientation` tag** —
|
|
227
|
+
there is nothing to say which side it is for. The default reads the tag when
|
|
228
|
+
Lichess wrote one and records both sides when it did not, and the book panel
|
|
229
|
+
says so, because guessing wrong is exactly the difference between "you have an
|
|
230
|
+
answer" and "you do not". Set the colour explicitly to settle it.
|
|
231
|
+
|
|
232
|
+
**Check it** in the book dialog builds the book and reports what is in it, so
|
|
233
|
+
you can see how many positions a source actually contributes before measuring
|
|
234
|
+
anything against it.
|
|
235
|
+
|
|
236
|
+
<img src="docs/book.png" width="620" alt="The book dialog with two sources stacked -- a Repertoire-Creator repertoire and the user's own Chess.com games -- and the built book reported underneath as positions, moves and branch points">
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Reading the report
|
|
241
|
+
|
|
242
|
+
### Gaps
|
|
243
|
+
|
|
244
|
+
Ranked by how many of their games arrive there. Click one for the position,
|
|
245
|
+
their record from it, links to the games that got there, and — if you have an
|
|
246
|
+
engine — a suggested move drawn on the board.
|
|
247
|
+
|
|
248
|
+
### Where they leak points
|
|
249
|
+
|
|
250
|
+
Their own moves, ranked by **games × (0.5 − their score)**: the number of
|
|
251
|
+
points they have dropped below an even score in that line. That is the honest
|
|
252
|
+
way to combine "how badly it goes for them" with "how often it happens". A
|
|
253
|
+
move they lost with once is not a plan; a move they score 35% with over thirty
|
|
254
|
+
games is.
|
|
255
|
+
|
|
256
|
+
Two things this is not. It is **not a verdict on the move** — it is a fact
|
|
257
|
+
about their results, and you are playing them, not the move. And it is **not
|
|
258
|
+
significance-tested**: the raw record and the sample size travel with every
|
|
259
|
+
row so you can see a 100%-over-two-games for what it is, and the *smallest
|
|
260
|
+
sample* setting on the left is the only filter.
|
|
261
|
+
|
|
262
|
+

|
|
263
|
+
|
|
264
|
+
### Exploit
|
|
265
|
+
|
|
266
|
+
The other tabs answer separate questions and leave you to join them up. *Where
|
|
267
|
+
they leak* says a move has gone badly for them; *Every choice* says how often
|
|
268
|
+
they play it; neither says what to actually do about it. This tab does the
|
|
269
|
+
joining.
|
|
270
|
+
|
|
271
|
+
For each of their real choices it takes the position **after** their move —
|
|
272
|
+
where you are to move — asks the engine for the best reply, and scores it on
|
|
273
|
+
three numbers **you can switch on and off independently**:
|
|
274
|
+
|
|
275
|
+
| Factor | |
|
|
276
|
+
|---|---|
|
|
277
|
+
| **frequency** | How many of their games play into it, against their busiest choice. A brilliant refutation of something they played twice is not preparation. |
|
|
278
|
+
| **record** | How badly it has actually gone for them, as `1 − their score`. The empirical half; needs no engine. |
|
|
279
|
+
| **edge** | How good your best reply leaves you, as winning chances from *your* side, through the curve Lichess uses. |
|
|
280
|
+
|
|
281
|
+
The opportunity score is the **product** of whichever factors are enabled,
|
|
282
|
+
which is the honest combination: a product says every enabled factor has to be
|
|
283
|
+
true at once, so a common line they do fine in and a rare line you crush both
|
|
284
|
+
rank below a common line they are already losing. Turn *frequency* off and the
|
|
285
|
+
rare brilliancies rise; turn *edge* off and it becomes a pure results ranking
|
|
286
|
+
that works with no engine at all.
|
|
287
|
+
|
|
288
|
+

|
|
289
|
+
|
|
290
|
+
Every number in a row is from one point of view and it says which: *they score
|
|
291
|
+
55%* is theirs, *you get 47%* is yours. The raw evaluation in pawns stays in
|
|
292
|
+
the engine box under the board, where the White's-point-of-view convention the
|
|
293
|
+
rest of the repository uses still applies.
|
|
294
|
+
|
|
295
|
+
This is the one part of the app that needs a lot of engine time, so it runs
|
|
296
|
+
**when you first open the tab**, as a background job with a progress bar, and
|
|
297
|
+
the answers are saved into the report. Opening it again is instant. *Run
|
|
298
|
+
again* re-analyses, and `prepper exploit` does the same thing from a script.
|
|
299
|
+
|
|
300
|
+
### Their openings
|
|
301
|
+
|
|
302
|
+
Every game grouped by the deepest named opening it reached, using Lichess's own
|
|
303
|
+
openings dataset indexed by position — so a transposition is grouped with the
|
|
304
|
+
opening it became, not the one it started as.
|
|
305
|
+
|
|
306
|
+
### Every choice
|
|
307
|
+
|
|
308
|
+
One row per (position, move): what they played, how often out of the times
|
|
309
|
+
they reached that position, and how they scored. This is the raw material the
|
|
310
|
+
other tabs are ranked from.
|
|
311
|
+
|
|
312
|
+
### Explore
|
|
313
|
+
|
|
314
|
+
Walk their tree by hand from the starting position. At a position where it was
|
|
315
|
+
their move you get their choices; at a position where it was not, you get every
|
|
316
|
+
reply their opponents actually made. Both carry counts and scores, so you can
|
|
317
|
+
follow a line down to where it gets thin.
|
|
318
|
+
|
|
319
|
+

|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## The board
|
|
324
|
+
|
|
325
|
+
Whatever you click — a gap, one of their moves, an exploit row, a position in
|
|
326
|
+
Explore — lands on the board beside the report, and the board is not a picture.
|
|
327
|
+
|
|
328
|
+
<img src="docs/board.png" width="380" alt="The board panel: an eval bar down the left, the position with the engine's move drawn on it, first/back/forward/last and flip controls with the evaluation beside them, the move strip, and the engine's ranked lines underneath">
|
|
329
|
+
|
|
330
|
+
- **Play on from it.** Click a piece and its legal moves light up; click a
|
|
331
|
+
destination and you are off down your own line. Play at a point you have
|
|
332
|
+
stepped back to and the rest is rewritten, which is what taking a move back
|
|
333
|
+
and trying something else should do. *Reset* returns to the row you clicked.
|
|
334
|
+
Legality is decided on the server, so there is no chess library in the page.
|
|
335
|
+
- **The mouse wheel steps the line** — down goes forward, up goes back, and a
|
|
336
|
+
trackpad flick is one move rather than ten. Arrow keys do the same,
|
|
337
|
+
`Home`/`End` jump to the ends, and `F` flips.
|
|
338
|
+
- **The eval bar is live** and follows every move you make, including moves you
|
|
339
|
+
invent. The light block is always White's share and sits on White's side of
|
|
340
|
+
the board, so it still reads correctly after a flip. The number beside the
|
|
341
|
+
controls says the depth and whether it came from the local engine or the
|
|
342
|
+
Lichess cloud.
|
|
343
|
+
- **The move strip** shows the line you are standing in; moves you played
|
|
344
|
+
yourself are in a different colour. Click any move to jump to it.
|
|
345
|
+
- **Arrows** mean one thing each: **blue** is the engine's move, **yellow** is
|
|
346
|
+
the next move of the line you are looking at.
|
|
347
|
+
|
|
348
|
+
### The engine toggle
|
|
349
|
+
|
|
350
|
+
Asking for the engine one position at a time gets old. The **engine** pill in
|
|
351
|
+
the top bar turns it on for everything: every position you look at, step to, or
|
|
352
|
+
play into gets its ranked lines automatically, debounced so that spinning the
|
|
353
|
+
wheel is not a storm of searches. The setting is remembered.
|
|
354
|
+
|
|
355
|
+
With the toggle off, a gap still shows the suggestion computed when it was
|
|
356
|
+
scouted, and *Ask the engine* asks for one position on demand.
|
|
357
|
+
|
|
358
|
+
### On a phone
|
|
359
|
+
|
|
360
|
+
The layout collapses to one column at 860px, and the order changes with it:
|
|
361
|
+
the report first, then the position you tapped, then the scout list and
|
|
362
|
+
settings. Settings collapse into a fold on narrow screens. Everything works by
|
|
363
|
+
touch — tap a piece, tap a square — and the forward and back buttons stand in
|
|
364
|
+
for the wheel.
|
|
365
|
+
|
|
366
|
+
<img src="docs/phone.png" width="300" alt="Player Prepper on a phone: a two-row header, the report with its scrolling tab bar, then the board with its eval bar and engine lines, then the scout list and collapsed settings">
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Exporting
|
|
371
|
+
|
|
372
|
+
**PGN** gives you the whole prep sheet as a multi-chapter study: what they play
|
|
373
|
+
as each colour, where they leak points, and your gaps — each as a merged move
|
|
374
|
+
tree with the numbers in the comments. It opens in any chess GUI.
|
|
375
|
+
|
|
376
|
+
**PDF** hands that PGN to the sibling app's layout engine, so you get the same
|
|
377
|
+
four styles the study exporter has. Grid is the one to use: a scouting report
|
|
378
|
+
is a contact sheet, not a book.
|
|
379
|
+
|
|
380
|
+

|
|
381
|
+
|
|
382
|
+
Lines are merged into a tree rather than written one per chapter on purpose.
|
|
383
|
+
Twenty gaps as twenty chapters is twenty nearly-empty pages, because every
|
|
384
|
+
chapter starts a fresh one; merged, they share their opening moves and fit on
|
|
385
|
+
two pages of diagrams — which is also how you actually think about them.
|
|
386
|
+
|
|
387
|
+
Player Prepper **never writes into your repertoires folder.** It reads it and
|
|
388
|
+
nothing else, so there is no chance of two apps writing the same files and
|
|
389
|
+
Repertoire-Creator's git auto-commit stays the only writer there.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## What the two sites will and will not give you
|
|
394
|
+
|
|
395
|
+
**Lichess streams.** `/api/games/user/{u}` is one ndjson request whatever the
|
|
396
|
+
count, so the fetch reads it line by line and stops when it has enough rather
|
|
397
|
+
than asking for a number and hoping. `moves` there is SAN, so it is walked
|
|
398
|
+
through a board once to become UCI — which is what the tree wants, and a
|
|
399
|
+
fraction of the size to cache.
|
|
400
|
+
|
|
401
|
+
**Chess.com does not stream, and has no "last N games" endpoint at all** —
|
|
402
|
+
only whole months. So the fetch walks backwards through the monthly archives,
|
|
403
|
+
newest first, and stops as soon as it has enough. It is bounded at 36 months so
|
|
404
|
+
one click on a dormant account cannot turn into a hundred requests. Chess.com
|
|
405
|
+
also sits behind Cloudflare and returns a 403 to any request with no
|
|
406
|
+
`User-Agent`; that is a missing header, not a ban, and this app always sends
|
|
407
|
+
one.
|
|
408
|
+
|
|
409
|
+
Both are the documented public APIs. Neither needs a token. Variant games are
|
|
410
|
+
dropped rather than guessed at.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## What is kept on disk
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
prep/ (PREPPER_DIR, or ./prep)
|
|
418
|
+
settings.json your book sources, remembered
|
|
419
|
+
games/
|
|
420
|
+
lichess-drnykterstein.json their games, compact, reusable
|
|
421
|
+
scouts/
|
|
422
|
+
lichess-drnykterstein.json the last report and what produced it
|
|
423
|
+
books/
|
|
424
|
+
study-i7hMEq7h.pgn a study fetched as a reference book
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Games are cached as UCI move lists rather than PGN: a tenth the size, no
|
|
428
|
+
re-parsing, and all the tree ever looks at. The identifying tags are kept
|
|
429
|
+
beside them so a row still reads like a game.
|
|
430
|
+
|
|
431
|
+
A cached fetch is reused only when it was made with filters at least as wide as
|
|
432
|
+
the ones you are asking for now — otherwise "give me 500 games" would be
|
|
433
|
+
silently answered with the 50 fetched last week. Tick **Re-fetch** to force it.
|
|
434
|
+
|
|
435
|
+
`data/openings.json` is the openings index, built once from Lichess's
|
|
436
|
+
downloadable openings dataset (no token, unlike the opening explorer, which now
|
|
437
|
+
requires an authenticated request). Both `prep/` and `data/` are gitignored.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Command line
|
|
442
|
+
|
|
443
|
+
Everything the browser does is also a command, so a night-before-the-tournament
|
|
444
|
+
script can scout four opponents and print four PDFs.
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
prepper scout <username> [--site lichess|chesscom] # scout and save
|
|
448
|
+
prepper scout hikaru --site chesscom --limit 500 --speed blitz --days 180
|
|
449
|
+
prepper scout them --repertoire white-ruy-lopez --suggest 8
|
|
450
|
+
prepper scout them --study https://lichess.org/study/abcd1234
|
|
451
|
+
prepper scout them --my-games chesscom:you
|
|
452
|
+
prepper exploit <username> --color both # best counters, ranked
|
|
453
|
+
prepper exploit them --no-frequency --top 20 # same toggles as the tab
|
|
454
|
+
prepper show <username> [--json] [--verbose] # print a saved report
|
|
455
|
+
prepper list # every saved scout
|
|
456
|
+
prepper forget <username> # report and cached games
|
|
457
|
+
prepper repertoires # what the folder holds
|
|
458
|
+
prepper book --repertoire white-ruy-lopez # build a book, say what is in it
|
|
459
|
+
prepper games <username> --limit 500 # fetch and cache only
|
|
460
|
+
prepper pdf <username> --mode grid --out prep.pdf
|
|
461
|
+
prepper serve [--host H] [--port P]
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Run them through the venv the same way as `serve`, e.g.
|
|
465
|
+
`../.lichess/Scripts/python.exe -m player_prepper.cli list`.
|
|
466
|
+
|
|
467
|
+
Book sources are repeatable and combine: `--repertoire a --repertoire b
|
|
468
|
+
--study <url> --my-games lichess:you` builds one book from all four.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## How the pieces fit
|
|
473
|
+
|
|
474
|
+
| Module | |
|
|
475
|
+
|---|---|
|
|
476
|
+
| `fetch.py` | bulk games from both sites, and the SAN/PGN to UCI conversion |
|
|
477
|
+
| `store.py` | the prep folder, atomic writes, what is cached where |
|
|
478
|
+
| `book.py` | your preparation from a repertoire, a study or your own games |
|
|
479
|
+
| `tree.py` | their games as a position-keyed tree with results attached |
|
|
480
|
+
| `scout.py` | coverage, gaps, rankings, the report — no network, no engine |
|
|
481
|
+
| `exploit.py` | candidate positions, the three factors and the opportunity score |
|
|
482
|
+
| `pipeline.py` | the five steps of one scout, shared by the CLI and the server |
|
|
483
|
+
| `openings.py` | Lichess's openings dataset, indexed by position |
|
|
484
|
+
| `engine.py` | the eval bar, the suggestion for a gap, the exploit pass |
|
|
485
|
+
| `export.py` | the prep sheet as PGN, and as a PDF through the sibling app |
|
|
486
|
+
| `board.py` | board SVGs, legality, and the positions along a line |
|
|
487
|
+
| `jobs.py` | background work with progress and cancellation |
|
|
488
|
+
| `bridge.py` | what the sibling app provides, and degrading without it |
|
|
489
|
+
| `server.py` / `web/` | the HTTP layer and the browser interface |
|
|
490
|
+
|
|
491
|
+
`scout.py` is deliberately pure: everything that can fail for an external
|
|
492
|
+
reason lives in fetching, book building and the engine, which is what makes the
|
|
493
|
+
part that produces the numbers testable with no network, no engine and no disk.
|
|
494
|
+
|
|
495
|
+
## Tests
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
../.lichess/Scripts/python.exe -m pytest tests -q # 52 tests
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
No network, no engine, no Stockfish. The bias is towards things that break
|
|
502
|
+
*quietly*: a scouting report is a page of confident numbers and there is no way
|
|
503
|
+
to eyeball whether 62% was computed from the right point of view, so those get
|
|
504
|
+
exact assertions on hand-built games where the answer is countable by hand.
|
|
505
|
+
|
|
506
|
+
Two of the tests exist because the bug happened during the build:
|
|
507
|
+
|
|
508
|
+
- python-chess's `StringExporter` **accumulates**, so one exporter reused
|
|
509
|
+
across chapters emits each chapter with every earlier chapter glued in front
|
|
510
|
+
of it. The only visible symptom was a PDF with ten chapters instead of four.
|
|
511
|
+
- `all_gaps()` returning copies instead of the report's own dictionaries meant
|
|
512
|
+
engine suggestions were computed, stored nowhere, and silently missing from
|
|
513
|
+
the export.
|
|
514
|
+
|
|
515
|
+
A third came out of a test failing honestly: the coverage walk originally only
|
|
516
|
+
checked positions it was about to play a move from, so a game that *ended* on
|
|
517
|
+
your turn — someone resigned — was silently counted as covered rather than
|
|
518
|
+
checked. Every position reached inside the horizon is now checked.
|
|
519
|
+
|
|
520
|
+
A fourth is the one stylesheet assertion. `.dialog input` and `.check input`
|
|
521
|
+
have identical specificity, so whichever is written last wins; the later one
|
|
522
|
+
set `width: 100%`, which turned every checkbox in the export dialog into a
|
|
523
|
+
full-width slab that shouldered its own label out through the right-hand edge.
|
|
524
|
+
Nothing in Python could see it and the stylesheet reads correctly. The rule now
|
|
525
|
+
says `:not([type="checkbox"])` outright, and the test asks the general question
|
|
526
|
+
— does any selector setting `width: 100%` still reach a bare `input` — rather
|
|
527
|
+
than checking that today's `:not()` is still spelled right.
|
|
528
|
+
|
|
529
|
+
The exploit ranking is tested the same way — on hand-built rows where the
|
|
530
|
+
product of the factors can be worked out by hand — because "which line is the
|
|
531
|
+
best to prepare" is exactly the kind of claim nobody can check by eye. One of
|
|
532
|
+
those tests exists because *the test* was wrong first: it assumed a rare
|
|
533
|
+
disaster would outrank a common even line, and the arithmetic says otherwise.
|
|
534
|
+
That is the intended behaviour, and it is now written down as an assertion
|
|
535
|
+
rather than an assumption.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Hosting it for free
|
|
540
|
+
|
|
541
|
+
Same profile as the three sibling apps — FastAPI/Uvicorn needing a real
|
|
542
|
+
container, not a serverless host — with one thing that makes this app's setup
|
|
543
|
+
different: like Repertoire-Creator, it wants the sibling package, so the
|
|
544
|
+
Docker build context has to be the **repository root**, not this folder.
|
|
545
|
+
|
|
546
|
+
[`Dockerfile`](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Player-Prepper/Dockerfile) lives here, same as every other app in this repo,
|
|
547
|
+
but must be built with the repo root as its context so it can `COPY` the
|
|
548
|
+
sibling. Docker keeps those two concerns separate — "where is the Dockerfile"
|
|
549
|
+
and "what can `COPY` see" — which is what Render's separate **Root Directory**
|
|
550
|
+
and **Dockerfile Path** fields are for. `.dockerignore` is read from the
|
|
551
|
+
context root regardless, so it stays at [the repository root](https://github.com/spearb0lt/Lichess-Essentials/blob/main/dockerignore).
|
|
552
|
+
|
|
553
|
+
It installs Stockfish via `apt-get` and sets `STOCKFISH_PATH`, so gap
|
|
554
|
+
suggestions work on a hosted instance with nothing to configure. LaTeX is
|
|
555
|
+
deliberately left out: a full texlive install is several GB, and the grid PDF
|
|
556
|
+
does not need it.
|
|
557
|
+
|
|
558
|
+
### What does not persist
|
|
559
|
+
|
|
560
|
+
`prep/` and `data/` are gitignored locally because they are regenerated on
|
|
561
|
+
demand, and on a free container they also get wiped by every redeploy. In
|
|
562
|
+
practice: the openings index rebuilds itself on first use, and **your saved
|
|
563
|
+
scouts do not survive a restart**. That is a smaller loss here than in the
|
|
564
|
+
sibling apps — a scout is a few minutes of network away from being rebuilt, and
|
|
565
|
+
a report about somebody's recent games goes stale anyway.
|
|
566
|
+
|
|
567
|
+
### Render
|
|
568
|
+
|
|
569
|
+
1. Push this repo to GitHub.
|
|
570
|
+
2. **New Web Service** → connect the repo → **Root Directory**: leave blank
|
|
571
|
+
(repo root, so the build context can see both apps) → **Dockerfile Path**:
|
|
572
|
+
`Player-Prepper/Dockerfile` → **Free** instance.
|
|
573
|
+
3. Optional environment variables, marked **secret**: `PREPPER_AUTH_USER` /
|
|
574
|
+
`PREPPER_AUTH_PASS` — see below.
|
|
575
|
+
4. Deploy. You get a URL like `https://<name>.onrender.com`.
|
|
576
|
+
|
|
577
|
+
### Hugging Face Spaces
|
|
578
|
+
|
|
579
|
+
Spaces are their own separate git repo, so:
|
|
580
|
+
|
|
581
|
+
1. **New Space** → **SDK: Docker** → **Hardware: CPU basic (free)**.
|
|
582
|
+
2. Clone the Space's repo locally. Copy in, preserving folder names:
|
|
583
|
+
`Lichess-Study-to-PDF/` and `Player-Prepper/` — then copy
|
|
584
|
+
`Player-Prepper/Dockerfile` up to the **Space repo's own root** as well,
|
|
585
|
+
since Spaces always build whatever is literally named `Dockerfile` there.
|
|
586
|
+
3. Add this to the top of the Space's `README.md`:
|
|
587
|
+
```yaml
|
|
588
|
+
---
|
|
589
|
+
title: Player Prepper
|
|
590
|
+
sdk: docker
|
|
591
|
+
app_port: 7860
|
|
592
|
+
---
|
|
593
|
+
```
|
|
594
|
+
4. **Settings → Repository secrets**: the same variables as Render.
|
|
595
|
+
5. Commit and push (a Hugging Face access token as the git password).
|
|
596
|
+
|
|
597
|
+
### Locking it behind a password
|
|
598
|
+
|
|
599
|
+
[`server.py`](https://github.com/spearb0lt/Lichess-Essentials/blob/main/Player-Prepper/player_prepper/server.py) has an HTTP Basic Auth gate that only
|
|
600
|
+
activates when both `PREPPER_AUTH_USER` and `PREPPER_AUTH_PASS` are set —
|
|
601
|
+
leave them unset and local use is never asked for credentials. Set both as
|
|
602
|
+
secrets and every route, API included, asks for that pair first. One shared
|
|
603
|
+
credential pair, not per-user accounts, over the HTTPS both hosts terminate by
|
|
604
|
+
default.
|
|
605
|
+
|
|
606
|
+
Do **not** set `LICHESS_TOKEN` on a public deployment: it would be shared by
|
|
607
|
+
every visitor. Paste a token into the UI per session instead — nothing here
|
|
608
|
+
needs one anyway.
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## If something goes wrong
|
|
613
|
+
|
|
614
|
+
| Symptom | Cause and fix |
|
|
615
|
+
|---|---|
|
|
616
|
+
| `No module named player_prepper` | You are in the wrong directory. `cd` into `Player-Prepper` first. |
|
|
617
|
+
| `Port 8780 is already in use` | It is probably already running — open the browser, or `--port 8781`. |
|
|
618
|
+
| "No standard games found ... with those filters" | Rated-only is on by default and they may play casual; the speed filter may exclude everything they play. Widen both. |
|
|
619
|
+
| Every line says "coverage was not measured" | No book. Click the **book** pill and add a source. |
|
|
620
|
+
| Coverage is 0% and every game is a gap at move 1 | Your book is for the other colour. A white repertoire has nothing to say when *they* have White — that is a correct answer, not a bug. |
|
|
621
|
+
| Gaps have no suggested move | No engine. Install the sibling app and put Stockfish in `Lichess-Study-to-PDF/engine/`; the banner confirms it at startup. |
|
|
622
|
+
| Lines are unnamed | The openings dataset has not downloaded. It is fetched once on first use and needs no token; check the network and reload. |
|
|
623
|
+
| "That study is private" | Install the sibling exporter, or supply a token with `study:read`. |
|
|
624
|
+
| Lichess says 429 | You pulled a lot of games. Wait a minute; a token raises the limit considerably. |
|
|
625
|
+
| A scout seems stale | The games cache is reused when it is wide enough. Tick **Re-fetch** or pass `--refresh`. |
|
|
626
|
+
| The Exploit tab says there is nothing to analyse | Nothing they play meets the minimum sample. Lower *smallest sample* on the left, or scout more of their games. |
|
|
627
|
+
| Exploit rows show "no engine" | No engine, so there is no best reply to report. The ranking still works on *frequency* and *record*; turn *edge* off to stop it counting. |
|
|
628
|
+
| The eval bar sits in the middle and says nothing | That position is not in the Lichess cloud and there is no local Stockfish. Add a binary to `Lichess-Study-to-PDF/engine/`. |
|
|
629
|
+
| The board will not let me move a piece | It is not that side's turn, or the move is illegal — legality is decided on the server, and only legal destinations light up. |
|
|
630
|
+
|
|
631
|
+
## Licence
|
|
632
|
+
|
|
633
|
+
MIT — see [LICENSE](https://github.com/spearb0lt/Lichess-Essentials/blob/main/LICENSE). Chess piece artwork in the board diagrams
|
|
634
|
+
comes from python-chess (Colin M.L. Burnett's Cburnett set, CC BY-SA 3.0);
|
|
635
|
+
opening names come from
|
|
636
|
+
[lichess-org/chess-openings](https://github.com/lichess-org/chess-openings)
|
|
637
|
+
(CC0).
|