├── .gitignore ├── LICENSE ├── README.md ├── bad-puzzles.txt ├── bin ├── copy-to-play.js ├── deploy-db.sh ├── download-supergm-games.sh ├── fix-prod-players.js ├── generator-vote.js ├── import-more.sh ├── load-puzzles.sh ├── migrate-ids.js ├── retag.sh ├── set-players.js └── super-gms-list.js ├── generator ├── README.md ├── cassettes │ ├── TestGenerator.test_not_puzzle_tb_1.yaml │ ├── TestTbChecker.test_correct_best_move.yaml │ ├── TestTbChecker.test_correct_best_move_promotion.yaml │ └── TestTbChecker.test_multiple_winning_moves.yaml ├── diskettes │ ├── 1176704234.py │ ├── 1178014960.py │ ├── 1287068348.py │ ├── 1297553427.py │ ├── 1349195776.py │ ├── 1385043965.py │ ├── 1519785268.py │ ├── 1521489201.py │ ├── 1580538584.py │ ├── 1594038342.py │ ├── 1710889002.py │ ├── 1736840525.py │ ├── 1912019082.py │ ├── 1935743088.py │ ├── 2112822054.py │ ├── 2241337505.py │ ├── 2274433818.py │ ├── 2561939647.py │ ├── 2747930855.py │ ├── 2757959804.py │ ├── 2841779116.py │ ├── 3124893989.py │ ├── 3193641271.py │ ├── 3273070181.py │ ├── 3346864465.py │ ├── 3512671251.py │ ├── 3582072466.py │ ├── 3672841238.py │ ├── 3738769095.py │ ├── 3829668910.py │ ├── 388831774.py │ ├── 3912244286.py │ ├── 4117174029.py │ ├── 691672986.py │ ├── 696195689.py │ ├── 696260506.py │ ├── 728045472.py │ ├── 730666912.py │ ├── 837031090.py │ ├── 945691296.py │ └── 949688249.py ├── generator.py ├── model.py ├── requirements.txt ├── server.py ├── tb.py ├── test.py ├── test_pgn_3fold_uDMCM.pgn └── util.py ├── project └── build.properties ├── pyrightconfig.json ├── tagger ├── README.md ├── cook.py ├── model.py ├── requirements.txt ├── tagger.py ├── test.py ├── util.py └── zugzwang.py └── validator ├── .editorconfig ├── README.md ├── back ├── config │ └── dev.json ├── package.json ├── pnpm-lock.yaml ├── public │ ├── images │ │ ├── board │ │ │ ├── 3d │ │ │ │ └── woodi.1024.png │ │ │ └── blue.svg │ │ └── pieces │ │ │ ├── merida │ │ │ ├── bB.svg │ │ │ ├── bK.svg │ │ │ ├── bN.svg │ │ │ ├── bP.svg │ │ │ ├── bQ.svg │ │ │ ├── bR.svg │ │ │ ├── wB.svg │ │ │ ├── wK.svg │ │ │ ├── wN.svg │ │ │ ├── wP.svg │ │ │ ├── wQ.svg │ │ │ └── wR.svg │ │ │ └── staunton │ │ │ └── basic │ │ │ ├── Black-Bishop-Flipped.png │ │ │ ├── Black-Bishop.png │ │ │ ├── Black-King.png │ │ │ ├── Black-Knight-Flipped.png │ │ │ ├── Black-Knight.png │ │ │ ├── Black-Pawn.png │ │ │ ├── Black-Queen.png │ │ │ ├── Black-Rook.png │ │ │ ├── White-Bishop-Flipped.png │ │ │ ├── White-Bishop.png │ │ │ ├── White-King.png │ │ │ ├── White-Knight-Flipped.png │ │ │ ├── White-Knight.png │ │ │ ├── White-Pawn.png │ │ │ ├── White-Queen.png │ │ │ └── White-Rook.png │ └── style.css ├── src │ ├── config.ts │ ├── env.ts │ ├── index.ts │ ├── mongo.ts │ ├── puzzle.ts │ └── router.ts └── tsconfig.json └── front ├── dist └── puzzle-validator.dev.js ├── package.json ├── rollup.config.js ├── src ├── ctrl.ts ├── main.ts ├── types.ts └── view.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | build/ 4 | validator/back/config/prod.json 5 | validator/back/config/local.json 6 | data/ 7 | dist/ 8 | venv/ 9 | target/ 10 | __pycache__/ 11 | .mypy_cache 12 | .vim 13 | puzzler-dump 14 | dump/ 15 | prod-dump/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 lichess.org 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lichess puzzler 2 | --------------- 3 | 4 | Let's renew the puzzle collection. 5 | We'll produce a collection of new puzzles out of the lichess game database. 6 | 7 | We need a program that generates puzzles, 8 | and one that allows manual validation and categorization of each and every puzzle. 9 | 10 | ## Generator 11 | 12 | Use stockfish and database.lichess.org to produce puzzle candidates. 13 | Python is probably the language of choice because of 14 | https://github.com/niklasf/python-chess 15 | 16 | The generator posts candidates to the validator. 17 | 18 | ## Validator 19 | 20 | Stores puzzle candidates and lets people review them with a web UI. 21 | 22 | mongodb puzzle: 23 | ``` 24 | { 25 | _id: 1, // incremental 26 | createdAt: date, 27 | gameId: string, 28 | fen: string, 29 | ply: number, 30 | moves: [uci], 31 | review: { // after a review was done 32 | at: date, 33 | score: 0-5, // quality 34 | rating: 1200 // estimated rating 35 | topics: [string], 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /bin/copy-to-play.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const buildColl = db.puzzle2; 4 | const playColl = db.puzzle2_puzzle; 5 | const blockColl = db.puzzle2_blocklist; 6 | 7 | const blocklist = makeBlocklist('bad-puzzles.txt'); 8 | 9 | buildColl.deleteMany({ _id: { $in: blocklist } }); 10 | playColl.deleteMany({ _id: { $in: blocklist } }); 11 | blockColl.drop(); 12 | blocklist.forEach(_id => blockColl.updateOne({ _id }, { $set: { _id } }, { upsert: true })); 13 | 14 | buffer = []; 15 | 16 | function processBuffer(buf) { 17 | const existingIds = new Set(playColl.distinct('_id', { _id: { $in: buf.map(p => p._id) } })); 18 | const missing = buf 19 | .filter(p => !existingIds.has(p._id)) 20 | .map(p => ({ 21 | _id: p._id, 22 | gameId: p.gameId, 23 | fen: p.fen, 24 | themes: [], 25 | glicko: { 26 | r: 1500, 27 | d: 500, 28 | v: 0.09, 29 | }, 30 | plays: NumberInt(0), 31 | vote: 1, 32 | vu: NumberInt(10), 33 | vd: NumberInt(0), 34 | line: p.moves.join(' '), 35 | cp: p.cp, 36 | tagMe: true, 37 | })); 38 | print(`Inserting ${missing.length}/${buf.length}`) 39 | if (missing.length) playColl.insertMany(missing, { ordered: false }); 40 | } 41 | 42 | buildColl 43 | .find({ _id: { $nin: blocklist }, createdAt: { $gt: new Date(Date.now() - 1000 * 3600 * 24 * 7) } }) 44 | .forEach(p => { 45 | if (p.moves.length < 2) return; 46 | buffer.push(p); 47 | if (buffer.length >= 1000) { 48 | processBuffer(buffer); 49 | buffer = []; 50 | } 51 | }); 52 | 53 | processBuffer(buffer); 54 | 55 | function makeBlocklist(file) { 56 | return fs 57 | .readFileSync(file) 58 | .toString('UTF8') 59 | .split('\n') 60 | .map(l => l.trim()) 61 | .filter(l => l); 62 | } 63 | -------------------------------------------------------------------------------- /bin/deploy-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TARGET=rubik-nokey 4 | DIR=puzzler-dump 5 | 6 | echo "Dumping collections to $DIR" 7 | rm -rf $DIR 8 | mongodump --db puzzler --collection puzzle2_puzzle --out $DIR 9 | mongodump --db puzzler --collection puzzle2_round --out $DIR 10 | mongodump --db puzzler --collection puzzle2 --out $DIR 11 | mongodump --db puzzler --collection puzzle2_blocklist --out $DIR 12 | 13 | echo "Sending $DIR to $TARGET" 14 | rsync -av $DIR $TARGET:/home/puzzler 15 | 16 | ssh $TARGET 'cd /home/puzzler/puzzler-dump && ./load-puzzles.sh' 17 | -------------------------------------------------------------------------------- /bin/download-supergm-games.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | perf=blitz 4 | OUT=data/supergm-games-$perf.pgn 5 | rm $OUT 6 | 7 | supergms='DrNykterstein DrDrunkenstein DannyTheDonkey ManWithaVan Bombegranate avalongamemaster Konevlad alireza2003 Azerichessss Moose959 AnishGiri AnishOnYoutube GMVallejo Vladimirovich9000 EvilGenius94 BakukaDaku87 Wesley_So STL_So gmwesleyso1993 RealDavidNavara Dr-BassemAmin gmluke howitzer14 Benefactorr IGMGataKamsky dalmatinac101 Vladimir201701 LiviuDieterNisipeanu jitanu76 ArkadijNaiditsch R-Ponomariov athena-pallada Chepursias muisback VerdeNotte KeyzerSose NeverEnough Sasha jlhammer' 8 | 9 | for u in $supergms; do 10 | echo $u 11 | curl https://lichess.org/api/games/user/$u\?perfType\=$perf\&rated\=true\&analysed\=true\&clocks=false\&evals\=true >> $OUT 12 | done 13 | -------------------------------------------------------------------------------- /bin/fix-prod-players.js: -------------------------------------------------------------------------------- 1 | // mongosh ~/.mongoshrc.js bin/fix-prod-players.js 2 | 3 | mainSec = sec(); 4 | local = connect('localhost:27017/lichess'); 5 | puzzler = prod(ports.rubik, 'puzzler'); 6 | load('bin/super-gms-list.js'); 7 | 8 | const titledUsers = new Set(mainSec.user4.distinct('_id', { title: { $exists: 1, $ne: 'BOT' } })); 9 | 10 | const count = puzzler.puzzle2_puzzle.estimatedDocumentCount(); 11 | const it = 0; 12 | 13 | const batchUpdate = []; 14 | 15 | const flushBatchUpdate = async () => { 16 | if (!batchUpdate.length) return; 17 | console.log(`Flushing ${batchUpdate.length} updates`); 18 | const writes = batchUpdate.map(update => ({ updateOne: update })) 19 | await puzzler.puzzle2_puzzle.bulkWrite(writes); 20 | batchUpdate.length = 0; 21 | } 22 | 23 | const scheduleUpdate = async (id, update) => { 24 | batchUpdate.push({ filter: { _id: id }, update }); 25 | if (batchUpdate.length >= 200) await flushBatchUpdate(); 26 | } 27 | 28 | // puzzler.puzzle2_puzzle.find({ users: { $exists: false } }, { gameId: 1 }).forEach(p => { 29 | puzzler.puzzle2_puzzle.find({}, { gameId: 1, users: 1 }).forEach(async p => { 30 | game = local.game5.findOne({ _id: p.gameId }, { us: 1 }); 31 | if (!game) { 32 | console.log(`Fetching game ${p.gameId} for puzzle ${p._id}`); 33 | game = mainSec.game5.findOne({ _id: p.gameId }, { us: 1 }); 34 | } 35 | if (!game) throw `Missing game ${p.gameId} for puzzle ${p._id}`; 36 | const users = game.us; 37 | if (!users || users.length !== 2) { 38 | console.error(`Invalid users for puzzle ${p._id} and game ${JSON.stringify(game)}: ${users}`); 39 | return; 40 | } 41 | 42 | const masters = users.filter(u => titledUsers.has(u)).length; 43 | const t = []; 44 | if (masters > 0) t.push('master'); 45 | if (masters > 1) t.push('masterVsMaster'); 46 | if (users.find(u => supergms.has(u))) t.push('superGM'); 47 | 48 | const update = { 49 | $set: { users }, 50 | ...(t.length ? { $addToSet: { themes: { $each: t } } } : {}) 51 | } 52 | 53 | if (t.length || users.join('') !== (p.users || []).join('')) { 54 | await scheduleUpdate(p._id, update); 55 | } 56 | 57 | if (t.length) { 58 | await puzzler.puzzle2_round.updateOne({ _id: 'lichess:' + p._id }, { $addToSet: { t: { $each: t.map(t => '+' + t) } } }); 59 | } 60 | 61 | it++; 62 | if (it % 1000 == 0) console.log(`${it}/${count} ${p._id} -> ${users} | ${t.join('+')}`); 63 | }); 64 | 65 | flushBatchUpdate(); 66 | -------------------------------------------------------------------------------- /bin/generator-vote.js: -------------------------------------------------------------------------------- 1 | db.puzzle2_puzzle.find({vote:1, cp: {$exists:1}},{_id:1, vote:1, cp:1}).forEach(p => { 2 | 3 | const build = db.puzzle2.findOne({_id:p._id},{generator:1}); 4 | 5 | if (!build) { 6 | print('Missing build for ' + p._id); 7 | return; 8 | } 9 | 10 | const generatorVote = 11 | // possible rejected mate in X when mate in one available 12 | build.generator < 24 && p.cp == 999999999 ? -15 : ( 13 | // 40 meganodes 14 | build.generator < 13 ? -10 : ( 15 | // 0.64 win diff 16 | build.generator < 22 ? -5 : 2 17 | ) 18 | ); 19 | 20 | print(p._id + ': ' + p.vote + ' -> ' + generatorVote); 21 | 22 | if (p.vote != generatorVote) db.puzzle2_puzzle.update({_id: p._id},{$set:{vote: NumberInt(generatorVote)}}); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /bin/import-more.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # source=192.168.1.2 5 | # 6 | # echo "Tag new puzzles" 7 | # mongosh $source:27017/puzzler --eval 'db.puzzle2.updateMany({recent:true},{$unset:{recent:true}});db.puzzle2.updateMany({createdAt:{$gt:new Date(Date.now() - 1000 * 3600 * 24 * 7)}},{$set:{recent:true}})' 8 | # 9 | # echo "Download" 10 | # mongodump --db=puzzler --collection=puzzle2 --host=$source --gzip --archive --query '{"recent":true}' | mongorestore --gzip --archive --drop 11 | 12 | # -------------------------------------------------------- 13 | 14 | cd ~/lichess-puzzler 15 | echo "Copy" 16 | mongosh puzzler bin/copy-to-play.js 17 | 18 | echo "Games" 19 | cd ~/lichess-mongo-import 20 | pnpm run puzzle-game-all 21 | 22 | echo "Users" 23 | cd ~/lichess-mongo-import 24 | pnpm run puzzle-game-user 25 | 26 | echo "Themes" 27 | cd ~/lichess-puzzler 28 | ./bin/retag.sh 29 | 30 | echo "Players" 31 | cd ~/lichess-puzzler 32 | mongosh ./bin/set-players.js 33 | 34 | echo "Deploy" 35 | cd ~/lichess-puzzler 36 | ./bin/deploy-db.sh 37 | -------------------------------------------------------------------------------- /bin/load-puzzles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script belongs on rubik:/home/puzzler/puzzler-dump/load-puzzles.sh 3 | # And is called by deploy-db.sh 4 | 5 | mongorestore mongodb://localhost:27017 --db puzzler --collection puzzle2_puzzle puzzler/puzzle2_puzzle.bson --writeConcern '{w:0}' --noIndexRestore 6 | mongorestore mongodb://localhost:27017 --db puzzler --collection puzzle2_round puzzler/puzzle2_round.bson --writeConcern '{w:0}' --noIndexRestore 7 | mongorestore mongodb://localhost:27017 --db puzzler --collection puzzle2_blocklist puzzler/puzzle2_blocklist.bson --writeConcern '{w:0}' --noIndexRestore 8 | mongosh puzzler --eval 'db.puzzle2_blocklist.find().forEach(p => db.puzzle2_puzzle.deleteOne(p))' 9 | -------------------------------------------------------------------------------- /bin/migrate-ids.js: -------------------------------------------------------------------------------- 1 | db.puzzle2.ensureIndex({gameId:1},{unique:true}); 2 | db.puzzle2.ensureIndex({fen:1,'moves.0':1},{unique:true}); 3 | 4 | const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 5 | function randomId() { 6 | let result = ''; 7 | for (let i = 5; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; 8 | return result; 9 | } 10 | 11 | db.puzzle.find().forEach(p => { 12 | p._id = randomId() 13 | db.puzzle2.insert(p); 14 | }); 15 | -------------------------------------------------------------------------------- /bin/retag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd ~/lichess-puzzler/tagger 5 | . venv/bin/activate 6 | echo "Themes" 7 | python tagger.py --threads=1 8 | echo "Zug" 9 | python tagger.py --zug --threads=2 10 | 11 | echo "Themes denormalize" 12 | mongosh puzzler ~/lila/cron/mongodb-puzzle-denormalize-themes.js 13 | 14 | # echo "Paths" 15 | # mongo puzzler ~/lichess-sysadmin/cron/mongodb-puzzle-regen-paths.js 16 | -------------------------------------------------------------------------------- /bin/set-players.js: -------------------------------------------------------------------------------- 1 | conn = Mongo(); 2 | lichess = conn.getDB('lichess'); 3 | puzzler = conn.getDB('puzzler'); 4 | load('bin/super-gms-list.js'); 5 | 6 | const titledUsers = new Set(lichess.user4.distinct('_id', { title: { $exists: 1, $ne: 'BOT' } })); 7 | 8 | gms = 0; 9 | titled = 0; 10 | puzzler.puzzle2_puzzle.find({ users: { $exists: false } }, { gameId: 1, users: 1 }).forEach(p => { 11 | if (!p.users) { 12 | const game = lichess.game5.findOne({ _id: p.gameId }); 13 | if (!game) throw `Missing game ${p.gameId} for puzzle ${p._id}`; 14 | p.users = game && game.us; 15 | if (p.users.length != 2) throw `Invalid users for puzzle ${p._id}: ${game}`; 16 | puzzler.puzzle2_puzzle.updateOne({ _id: p._id }, { $set: { users: p.users } }); 17 | } 18 | 19 | const masters = p.users.filter(u => titledUsers.has(u)).length; 20 | const t = []; 21 | if (masters > 0) t.push('master'); 22 | if (masters > 1) t.push('masterVsMaster'); 23 | if (p.users.find(u => supergms.has(u))) { 24 | gms++; 25 | t.push('superGM'); 26 | } 27 | if (t.length) { 28 | titled++; 29 | puzzler.puzzle2_puzzle.updateOne({ _id: p._id }, { $addToSet: { themes: { $each: t } } }); 30 | puzzler.puzzle2_round.updateOne({ _id: 'lichess:' + p._id }, { $addToSet: { t: { $each: t.map(t => '+' + t) } } }); 31 | } 32 | }); 33 | 34 | print('titled: ', titled); 35 | print('gms: ', gms); 36 | -------------------------------------------------------------------------------- /bin/super-gms-list.js: -------------------------------------------------------------------------------- 1 | const supergms = new Set( 2 | [ 3 | 'DrNykterstein', 4 | 'DrDrunkenstein', 5 | 'DrGrekenstein', 6 | 'DannyTheDonkey', 7 | 'ManWithaVan', 8 | 'Bombegranate', 9 | 'avalongamemaster', 10 | 'Konevlad', 11 | 'alireza2003', 12 | 'Azerichessss', 13 | 'Moose959', 14 | 'AnishGiri', 15 | 'AnishOnYoutube', 16 | 'GMVallejo', 17 | 'Vladimirovich9000', 18 | 'EvilGenius94', 19 | 'BakukaDaku87', 20 | 'Wesley_So', 21 | 'STL_So', 22 | 'gmwesleyso1993', 23 | 'RealDavidNavara', 24 | 'Dr-BassemAmin', 25 | 'gmluke', 26 | 'howitzer14', 27 | 'Benefactorr', 28 | 'IGMGataKamsky', 29 | 'dalmatinac101', 30 | 'Vladimir201701', 31 | 'LiviuDieterNisipeanu', 32 | 'jitanu76', 33 | 'ArkadijNaiditsch', 34 | 'R-Ponomariov', 35 | 'athena-pallada', 36 | 'Chepursias', 37 | 'muisback', 38 | 'VerdeNotte', 39 | 'KeyzerSose', 40 | 'NeverEnough', 41 | 'Sasha', 42 | 'jlhammer', 43 | ].map(n => n.toLowerCase()) 44 | ); 45 | -------------------------------------------------------------------------------- /generator/README.md: -------------------------------------------------------------------------------- 1 | Generate puzzles from a game database. 2 | 3 | ``` 4 | python3 -m venv venv 5 | . venv/bin/activate 6 | pip install -r requirements.txt 7 | python3 generator.py -f file.pgn -t 6 -v -u http://localhost:8000/puzzle 8 | ``` 9 | 10 | prod: 11 | 12 | ``` 13 | sudo apt update 14 | sudo apt install software-properties-common 15 | sudo add-apt-repository ppa:deadsnakes/ppa 16 | sudo apt install -y python3.8 17 | sudo apt install -y python3.8-venv 18 | cd 19 | git clone https://github.com/ornicar/lichess-puzzler 20 | cd lichess-puzzler/generator 21 | python3.8 -m venv venv 22 | . venv/bin/activate 23 | python3.8 -m pip install -r requirements.txt 24 | nice -n19 python3.8 generator.py -t 4 -v --url=http://knarr:9371 --token=*** -e /root/fishnet-nv8Icl/stockfish-x86-64-avx512 -f /root/lichess-puzzler/data/lichess_db_standard_rated_2022-08.pgn.zst --parts 2 --part 1 --skip 0 25 | ``` 26 | -------------------------------------------------------------------------------- /generator/cassettes/TestGenerator.test_not_puzzle_tb_1.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate, zstd 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.32.3 13 | method: GET 14 | uri: http://tablebase.lichess.ovh/standard?fen=8/8/5p2/4p3/p3K1P1/k4P2/8/8%20b%20-%20-%201%2047 15 | response: 16 | body: 17 | string: '{"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":3,"precise_dtz":null,"dtm":null,"dtw":null,"category":"win","moves":[{"uci":"a3b2","san":"Kb2","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"a3b3","san":"Kb3","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"a3b4","san":"Kb4","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"a3a2","san":"Ka2","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-4,"precise_dtz":null,"dtm":null,"dtw":null,"category":"loss"},{"uci":"f6f5","san":"f5+","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":null,"dtw":null,"category":"win"}]}' 18 | headers: 19 | Access-Control-Allow-Headers: 20 | - Accept,If-Modified-Since,Cache-Control,X-Requested-With 21 | Access-Control-Allow-Methods: 22 | - GET,OPTIONS 23 | Access-Control-Allow-Origin: 24 | - '*' 25 | Cache-Control: 26 | - max-age=1209600 27 | - public 28 | Connection: 29 | - keep-alive 30 | Content-Encoding: 31 | - gzip 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Mon, 03 Mar 2025 14:50:29 GMT 36 | Expires: 37 | - Mon, 17 Mar 2025 14:50:29 GMT 38 | Server: 39 | - nginx 40 | Transfer-Encoding: 41 | - chunked 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /generator/cassettes/TestTbChecker.test_correct_best_move.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate, zstd 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.32.3 13 | method: GET 14 | uri: http://tablebase.lichess.ovh/standard?fen=5K2/8/7p/6P1/1p5P/k7/8/8%20w%20-%20-%200%2049 15 | response: 16 | body: 17 | string: '{"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":null,"dtw":null,"category":"win","moves":[{"uci":"g5h6","san":"gxh6","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":-40,"dtw":null,"category":"loss"},{"uci":"h4h5","san":"h5","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":null,"dtw":null,"category":"draw"},{"uci":"g5g6","san":"g6","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":null,"dtw":null,"category":"draw"},{"uci":"f8e7","san":"Ke7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":null,"dtw":null,"category":"draw"},{"uci":"f8f7","san":"Kf7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":null,"dtw":null,"category":"draw"},{"uci":"f8e8","san":"Ke8","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":null,"dtw":null,"category":"draw"},{"uci":"f8g7","san":"Kg7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":null,"dtw":null,"category":"win"},{"uci":"f8g8","san":"Kg8","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":null,"dtw":null,"category":"win"}]}' 18 | headers: 19 | Access-Control-Allow-Headers: 20 | - Accept,If-Modified-Since,Cache-Control,X-Requested-With 21 | Access-Control-Allow-Methods: 22 | - GET,OPTIONS 23 | Access-Control-Allow-Origin: 24 | - '*' 25 | Cache-Control: 26 | - max-age=1209600 27 | - public 28 | Connection: 29 | - keep-alive 30 | Content-Encoding: 31 | - gzip 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Mon, 03 Mar 2025 14:50:12 GMT 36 | Expires: 37 | - Mon, 17 Mar 2025 14:50:12 GMT 38 | Server: 39 | - nginx 40 | Transfer-Encoding: 41 | - chunked 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /generator/cassettes/TestTbChecker.test_correct_best_move_promotion.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate, zstd 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.32.3 13 | method: GET 14 | uri: http://tablebase.lichess.ovh/standard?fen=5K2/7P/8/8/7P/k7/1p6/8%20w%20-%20-%200%2051 15 | response: 16 | body: 17 | string: '{"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":37,"dtw":null,"category":"win","moves":[{"uci":"h7h8q","san":"h8=Q","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":-36,"dtw":null,"category":"loss"},{"uci":"h4h5","san":"h5","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":0,"dtw":null,"category":"draw"},{"uci":"f8e7","san":"Ke7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":0,"dtw":null,"category":"draw"},{"uci":"f8f7","san":"Kf7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":0,"dtw":null,"category":"draw"},{"uci":"f8g7","san":"Kg7","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":0,"dtw":null,"category":"draw"},{"uci":"f8g8","san":"Kg8","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":0,"precise_dtz":0,"dtm":0,"dtw":null,"category":"draw"},{"uci":"h7h8r","san":"h8=R","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":75,"dtw":null,"category":"win"},{"uci":"h7h8n","san":"h8=N","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":35,"dtw":null,"category":"win"},{"uci":"h7h8b","san":"h8=B","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":27,"dtw":null,"category":"win"},{"uci":"f8e8","san":"Ke8","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":23,"dtw":null,"category":"win"}]}' 18 | headers: 19 | Access-Control-Allow-Headers: 20 | - Accept,If-Modified-Since,Cache-Control,X-Requested-With 21 | Access-Control-Allow-Methods: 22 | - GET,OPTIONS 23 | Access-Control-Allow-Origin: 24 | - '*' 25 | Cache-Control: 26 | - max-age=1209600 27 | - public 28 | Connection: 29 | - keep-alive 30 | Content-Encoding: 31 | - gzip 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Mon, 03 Mar 2025 14:50:18 GMT 36 | Expires: 37 | - Mon, 17 Mar 2025 14:50:18 GMT 38 | Server: 39 | - nginx 40 | Transfer-Encoding: 41 | - chunked 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /generator/cassettes/TestTbChecker.test_multiple_winning_moves.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate, zstd 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.32.3 13 | method: GET 14 | uri: http://tablebase.lichess.ovh/standard?fen=4k3/8/8/8/8/8/3PPPPP/4K3%20w%20-%20-%200%201 15 | response: 16 | body: 17 | string: '{"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":1,"precise_dtz":1,"dtm":null,"dtw":null,"category":"win","moves":[{"uci":"d2d3","san":"d3","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"e2e3","san":"e3","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"f2f3","san":"f3","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"g2g3","san":"g3","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"h2h3","san":"h3","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"d2d4","san":"d4","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"e2e4","san":"e4","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"f2f4","san":"f4","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"g2g4","san":"g4","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"h2h4","san":"h4","zeroing":true,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"e1d1","san":"Kd1","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"},{"uci":"e1f1","san":"Kf1","zeroing":false,"checkmate":false,"stalemate":false,"variant_win":false,"variant_loss":false,"insufficient_material":false,"dtz":-2,"precise_dtz":-2,"dtm":null,"dtw":null,"category":"loss"}]}' 18 | headers: 19 | Access-Control-Allow-Headers: 20 | - Accept,If-Modified-Since,Cache-Control,X-Requested-With 21 | Access-Control-Allow-Methods: 22 | - GET,OPTIONS 23 | Access-Control-Allow-Origin: 24 | - '*' 25 | Cache-Control: 26 | - max-age=1209600 27 | - public 28 | Connection: 29 | - keep-alive 30 | Content-Encoding: 31 | - gzip 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Mon, 03 Mar 2025 14:50:18 GMT 36 | Expires: 37 | - Mon, 17 Mar 2025 14:50:18 GMT 38 | Server: 39 | - nginx 40 | Transfer-Encoding: 41 | - chunked 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /generator/diskettes/1176704234.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/3kp3/p5P1/P3KP2/8/8 b - - 1 44 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 36, 'seldepth': 66, 'multipv': 1, 'score': PovScore(Cp(+714), BLACK), 'nodes': 25001057, 'nps': 5388158, 'hashfull': 845, 'tbhits': 0, 'time': 4.64, 'pv': [Move.from_uci('d5c4')], 'lowerbound': True}, {'depth': 36, 'seldepth': 13, 'multipv': 2, 'score': PovScore(Cp(0), BLACK), 'nodes': 25001057, 'nps': 5388158, 'hashfull': 845, 'tbhits': 0, 'time': 4.64, 'pv': [Move.from_uci('d5d6'), Move.from_uci('e3e4'), Move.from_uci('d6e6'), Move.from_uci('f3f4'), Move.from_uci('e5f4'), Move.from_uci('e4f4'), Move.from_uci('e6e7'), Move.from_uci('f4e4'), Move.from_uci('e7f7'), Move.from_uci('e4e3'), Move.from_uci('f7g7'), Move.from_uci('e3e4')]}] -------------------------------------------------------------------------------- /generator/diskettes/1178014960.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/4pK2/p5P1/Pk3P2/8/8 b - - 5 46 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 24, 'seldepth': 63, 'multipv': 1, 'score': PovScore(Cp(+3868), BLACK), 'nodes': 25000816, 'nps': 5718393, 'hashfull': 870, 'tbhits': 0, 'time': 4.372, 'pv': [Move.from_uci('b3a3'), Move.from_uci('f5f6'), Move.from_uci('e5e4'), Move.from_uci('f3f4'), Move.from_uci('e4e3'), Move.from_uci('g4g5'), Move.from_uci('e3e2'), Move.from_uci('f4f5'), Move.from_uci('e2e1q'), Move.from_uci('f6f7'), Move.from_uci('e1e5'), Move.from_uci('f5f6'), Move.from_uci('e5g5'), Move.from_uci('f7e6'), Move.from_uci('g5g6'), Move.from_uci('e6e7'), Move.from_uci('g6f5'), Move.from_uci('f6f7'), Move.from_uci('a3b4'), Move.from_uci('f7f8q'), Move.from_uci('f5f8'), Move.from_uci('e7f8'), Move.from_uci('a4a3'), Move.from_uci('f8g7'), Move.from_uci('b4c4'), Move.from_uci('g7f7')]}, {'depth': 24, 'seldepth': 44, 'multipv': 2, 'score': PovScore(Cp(-9), BLACK), 'nodes': 25000816, 'nps': 5718393, 'hashfull': 870, 'tbhits': 0, 'time': 4.372, 'pv': [Move.from_uci('e5e4'), Move.from_uci('f3e4'), Move.from_uci('b3a3'), Move.from_uci('g4g5'), Move.from_uci('f6g5'), Move.from_uci('e4e5'), Move.from_uci('a3b3'), Move.from_uci('e5e6'), Move.from_uci('a4a3'), Move.from_uci('e6e7'), Move.from_uci('a3a2'), Move.from_uci('e7e8q'), Move.from_uci('a2a1q'), Move.from_uci('f5g5'), Move.from_uci('a1g1'), Move.from_uci('g5f6'), Move.from_uci('g1f1'), Move.from_uci('f6g7'), Move.from_uci('f1g1'), Move.from_uci('g7f8')]}] -------------------------------------------------------------------------------- /generator/diskettes/1287068348.py: -------------------------------------------------------------------------------- 1 | #b'r1b3k1/pp3p1p/2pp2p1/8/2P5/2N4R/PP1qBPP1/R4K2 w - - 0 19 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 37, 'multipv': 1, 'score': PovScore(Cp(-412), WHITE), 'nodes': 25000948, 'nps': 1501378, 'hashfull': 999, 'tbhits': 0, 'time': 16.652, 'pv': [Move.from_uci('h3e3'), Move.from_uci('c8e6'), Move.from_uci('b2b3'), Move.from_uci('d2b2'), Move.from_uci('a1b1'), Move.from_uci('b2a3'), Move.from_uci('b1d1'), Move.from_uci('a8d8'), Move.from_uci('e2f3'), Move.from_uci('g8g7'), Move.from_uci('f1g1'), Move.from_uci('a3a5'), Move.from_uci('g2g3'), Move.from_uci('e6f5'), Move.from_uci('d1d4'), Move.from_uci('h7h5'), Move.from_uci('d4f4'), Move.from_uci('d8d7'), Move.from_uci('c3e2'), Move.from_uci('g6g5'), Move.from_uci('f4d4'), Move.from_uci('f5g6'), Move.from_uci('f3e4'), Move.from_uci('f7f5'), Move.from_uci('e4c2')]}, {'depth': 25, 'seldepth': 40, 'multipv': 2, 'score': PovScore(Cp(-422), WHITE), 'nodes': 25000948, 'nps': 1501378, 'hashfull': 999, 'tbhits': 0, 'time': 16.652, 'pv': [Move.from_uci('a1d1'), Move.from_uci('d2b2'), Move.from_uci('h3e3'), Move.from_uci('g8g7'), Move.from_uci('d1b1'), Move.from_uci('b2a3'), Move.from_uci('f1g1'), Move.from_uci('a8b8'), Move.from_uci('c3e4'), Move.from_uci('a3a5'), Move.from_uci('e4d6'), Move.from_uci('c8e6'), Move.from_uci('e2f3'), Move.from_uci('a5c5'), Move.from_uci('b1d1'), Move.from_uci('h7h5'), Move.from_uci('e3b3'), Move.from_uci('b7b5'), Move.from_uci('d6e4'), Move.from_uci('c5b6'), Move.from_uci('e4d6'), Move.from_uci('b5b4'), Move.from_uci('b3d3'), Move.from_uci('b6c7'), Move.from_uci('d3e3')]}] -------------------------------------------------------------------------------- /generator/diskettes/1297553427.py: -------------------------------------------------------------------------------- 1 | #b'3R4/1Q2nk2/4p2p/8/BP3pnP/P5PK/6P1/2r5 b - - 2 41 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25000892, 'nps': 4142649, 'hashfull': 1000, 'tbhits': 0, 'time': 6.035, 'pv': [Move.from_uci('g4f2'), Move.from_uci('h3h2'), Move.from_uci('c1h1')]}, {'depth': 26, 'seldepth': 40, 'multipv': 2, 'score': PovScore(Cp(-713), BLACK), 'nodes': 25000892, 'nps': 4142649, 'hashfull': 1000, 'tbhits': 0, 'time': 6.035, 'pv': [Move.from_uci('g4e3'), Move.from_uci('g3f4')], 'upperbound': True}] -------------------------------------------------------------------------------- /generator/diskettes/1349195776.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/4p3/6K1/2P2P2/P6P/5q2 b - - 3 39 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 36, 'seldepth': 6, 'multipv': 1, 'score': PovScore(Mate(+3), BLACK), 'nodes': 25000693, 'nps': 1216105, 'hashfull': 998, 'tbhits': 0, 'time': 20.558, 'pv': [Move.from_uci('f1g2'), Move.from_uci('g4h4'), Move.from_uci('b6f2'), Move.from_uci('h4h5'), Move.from_uci('g2g6')]}, {'depth': 35, 'seldepth': 67, 'multipv': 2, 'score': PovScore(Cp(+7), BLACK), 'nodes': 25000693, 'nps': 1216105, 'hashfull': 998, 'tbhits': 0, 'time': 20.558, 'pv': [Move.from_uci('f6f5'), Move.from_uci('c8f5'), Move.from_uci('f1g2'), Move.from_uci('g4h5'), Move.from_uci('g2h2'), Move.from_uci('h5g4'), Move.from_uci('h2g2'), Move.from_uci('g4h5'), Move.from_uci('g2a2'), Move.from_uci('f5e5'), Move.from_uci('a2e6'), Move.from_uci('e5e6'), Move.from_uci('e7e6'), Move.from_uci('h5g4'), Move.from_uci('b6a5'), Move.from_uci('c3c4'), Move.from_uci('e6e5'), Move.from_uci('f3f4'), Move.from_uci('e5d4'), Move.from_uci('f4f5'), Move.from_uci('d4c4'), Move.from_uci('g4f3'), Move.from_uci('a5d8'), Move.from_uci('c6e8'), Move.from_uci('a7a5'), Move.from_uci('e8f7'), Move.from_uci('c4c3'), Move.from_uci('f7e8'), Move.from_uci('c3b3'), Move.from_uci('e8f7'), Move.from_uci('b3a3'), Move.from_uci('f7e8'), Move.from_uci('a5a4'), Move.from_uci('e8a4'), Move.from_uci('a3a4'), Move.from_uci('f3e4')]}] -------------------------------------------------------------------------------- /generator/diskettes/1385043965.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/4p1q1/4K3/2P2P2/P6P/8 b - - 7 41 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 49, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25001634, 'nps': 961897, 'hashfull': 999, 'tbhits': 0, 'time': 25.992, 'pv': [Move.from_uci('g5e3'), Move.from_uci('e4f5'), Move.from_uci('e3f4')]}, {'depth': 48, 'seldepth': 21, 'multipv': 2, 'score': PovScore(Cp(0), BLACK), 'nodes': 25001634, 'nps': 961897, 'hashfull': 999, 'tbhits': 0, 'time': 25.992, 'pv': [Move.from_uci('g5g6'), Move.from_uci('c8f5'), Move.from_uci('b6a5'), Move.from_uci('c3c4'), Move.from_uci('g6h6'), Move.from_uci('f5d7'), Move.from_uci('e7f8'), Move.from_uci('d7c8'), Move.from_uci('f8g7'), Move.from_uci('c8g4'), Move.from_uci('g7f8')]}] -------------------------------------------------------------------------------- /generator/diskettes/1519785268.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/4p3/p3K1P1/Pk3P2/8/8 w - - 4 46 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 19, 'seldepth': 66, 'multipv': 1, 'score': PovScore(Cp(-7353), WHITE), 'nodes': 25000620, 'nps': 6268961, 'hashfull': 792, 'tbhits': 0, 'time': 3.988, 'pv': [Move.from_uci('e4f5'), Move.from_uci('b3a3'), Move.from_uci('f5f6'), Move.from_uci('e5e4'), Move.from_uci('f3e4'), Move.from_uci('a3b4'), Move.from_uci('e4e5'), Move.from_uci('a4a3'), Move.from_uci('e5e6'), Move.from_uci('a3a2'), Move.from_uci('e6e7'), Move.from_uci('a2a1q'), Move.from_uci('f6f7'), Move.from_uci('a1a2'), Move.from_uci('f7f8'), Move.from_uci('a2f2'), Move.from_uci('f8g7'), Move.from_uci('f2d4'), Move.from_uci('g7f7'), Move.from_uci('d4f4'), Move.from_uci('f7e8'), Move.from_uci('b4c5'), Move.from_uci('e8d8'), Move.from_uci('f4d6'), Move.from_uci('d8e8'), Move.from_uci('d6g6'), Move.from_uci('e8d8'), Move.from_uci('g6f6'), Move.from_uci('d8d7'), Move.from_uci('f6f7'), Move.from_uci('g4g5'), Move.from_uci('f7d5'), Move.from_uci('d7c8'), Move.from_uci('d5g8'), Move.from_uci('c8d7'), Move.from_uci('g8f7'), Move.from_uci('g5g6'), Move.from_uci('f7f5'), Move.from_uci('d7d8'), Move.from_uci('f5d3'), Move.from_uci('d8c8'), Move.from_uci('d3g6'), Move.from_uci('c8d7'), Move.from_uci('g6g4'), Move.from_uci('d7d8'), Move.from_uci('g4d4'), Move.from_uci('d8c8'), Move.from_uci('d4h8'), Move.from_uci('c8d7'), Move.from_uci('h8g7'), Move.from_uci('d7e6'), Move.from_uci('g7g8'), Move.from_uci('e6e5'), Move.from_uci('g8d5'), Move.from_uci('e5f4')]}, {'depth': 18, 'seldepth': 58, 'multipv': 2, 'score': PovScore(Cp(-7970), WHITE), 'nodes': 25000620, 'nps': 6267390, 'hashfull': 792, 'tbhits': 0, 'time': 3.989, 'pv': [Move.from_uci('e4d3'), Move.from_uci('b3a3'), Move.from_uci('d3c3'), Move.from_uci('a3a2'), Move.from_uci('c3c4'), Move.from_uci('a2b2'), Move.from_uci('c4d5'), Move.from_uci('b2b3'), Move.from_uci('d5e4'), Move.from_uci('a4a3'), Move.from_uci('e4f5'), Move.from_uci('b3b4'), Move.from_uci('f5f6'), Move.from_uci('e5e4'), Move.from_uci('f3e4'), Move.from_uci('a3a2'), Move.from_uci('f6e5'), Move.from_uci('a2a1q'), Move.from_uci('e5f5'), Move.from_uci('a1f1'), Move.from_uci('f5g6'), Move.from_uci('f1f4'), Move.from_uci('g6h5'), Move.from_uci('f4e4'), Move.from_uci('h5h4'), Move.from_uci('b4c5'), Move.from_uci('h4g5'), Move.from_uci('e4e7'), Move.from_uci('g5h5'), Move.from_uci('e7f6'), Move.from_uci('g4g5'), Move.from_uci('f6f5'), Move.from_uci('h5h4'), Move.from_uci('f5g6'), Move.from_uci('h4g4'), Move.from_uci('g6f7'), Move.from_uci('g4h4'), Move.from_uci('f7f3')]}] -------------------------------------------------------------------------------- /generator/diskettes/1521489201.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/4p3/p1k3P1/P3KP2/8/8 w - - 2 45 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 20, 'seldepth': 42, 'multipv': 1, 'score': PovScore(Cp(-980), WHITE), 'nodes': 25001095, 'nps': 5925834, 'hashfull': 701, 'tbhits': 0, 'time': 4.219, 'pv': [Move.from_uci('e3e4'), Move.from_uci('c4b3'), Move.from_uci('e4f5'), Move.from_uci('b3a3'), Move.from_uci('f5g6'), Move.from_uci('a3b4'), Move.from_uci('g6f6'), Move.from_uci('e5e4'), Move.from_uci('f3f4'), Move.from_uci('e4e3'), Move.from_uci('f6f5'), Move.from_uci('e3e2'), Move.from_uci('f5e5'), Move.from_uci('e2e1q'), Move.from_uci('e5f6'), Move.from_uci('a4a3'), Move.from_uci('f6g7'), Move.from_uci('e1e4'), Move.from_uci('g4g5')]}, {'depth': 20, 'seldepth': 45, 'multipv': 2, 'score': PovScore(Cp(-1013), WHITE), 'nodes': 25001095, 'nps': 5925834, 'hashfull': 701, 'tbhits': 0, 'time': 4.219, 'pv': [Move.from_uci('e3e2'), Move.from_uci('c4b3'), Move.from_uci('e2d3'), Move.from_uci('b3a3'), Move.from_uci('d3c4'), Move.from_uci('a3b2'), Move.from_uci('c4d5'), Move.from_uci('a4a3'), Move.from_uci('d5e6'), Move.from_uci('b2b3'), Move.from_uci('e6f5'), Move.from_uci('b3b4'), Move.from_uci('f5g6'), Move.from_uci('a3a2'), Move.from_uci('g6f7'), Move.from_uci('a2a1q'), Move.from_uci('f7g7'), Move.from_uci('b4b3'), Move.from_uci('g7f7'), Move.from_uci('a1d4'), Move.from_uci('f7g8')]}] -------------------------------------------------------------------------------- /generator/diskettes/1580538584.py: -------------------------------------------------------------------------------- 1 | #b'r1b3k1/pp3p1p/2pp2p1/8/2P5/2N1R3/PP1qBPP1/R4K2 b - - 1 19 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 36, 'multipv': 1, 'score': PovScore(Cp(+417), BLACK), 'nodes': 25001800, 'nps': 1635922, 'hashfull': 999, 'tbhits': 0, 'time': 15.283, 'pv': [Move.from_uci('g8f8'), Move.from_uci('e2f3'), Move.from_uci('c8e6'), Move.from_uci('f1g1'), Move.from_uci('d6d5'), Move.from_uci('c4d5'), Move.from_uci('c6d5'), Move.from_uci('e3e2'), Move.from_uci('d2g5'), Move.from_uci('a1d1'), Move.from_uci('d5d4'), Move.from_uci('d1d4'), Move.from_uci('a8d8'), Move.from_uci('d4d8'), Move.from_uci('g5d8'), Move.from_uci('f3b7'), Move.from_uci('e6a2'), Move.from_uci('b7d5'), Move.from_uci('a2d5'), Move.from_uci('e2d2'), Move.from_uci('d8g5'), Move.from_uci('d2d5'), Move.from_uci('g5c1'), Move.from_uci('c3d1')]}, {'depth': 26, 'seldepth': 43, 'multipv': 2, 'score': PovScore(Cp(+417), BLACK), 'nodes': 25001800, 'nps': 1635922, 'hashfull': 999, 'tbhits': 0, 'time': 15.283, 'pv': [Move.from_uci('c8f5'), Move.from_uci('a1d1'), Move.from_uci('d2b2'), Move.from_uci('g2g4'), Move.from_uci('f5c2'), Move.from_uci('d1e1'), Move.from_uci('b2b4'), Move.from_uci('a2a3'), Move.from_uci('b4a5'), Move.from_uci('e2f3'), Move.from_uci('c2b3'), Move.from_uci('c3e4'), Move.from_uci('b3c4'), Move.from_uci('f1g2'), Move.from_uci('g8g7'), Move.from_uci('e4d6'), Move.from_uci('c4d5'), Move.from_uci('e1d1'), Move.from_uci('a5a4'), Move.from_uci('d1d5'), Move.from_uci('c6d5'), Move.from_uci('f3d5')]}] -------------------------------------------------------------------------------- /generator/diskettes/1594038342.py: -------------------------------------------------------------------------------- 1 | #b'3R4/1Q2nk2/4p2p/8/BP3p1P/P5P1/5nPK/2r5 b - - 4 42 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 57263, 'nps': 8180428, 'hashfull': 3, 'tbhits': 0, 'time': 0.007, 'pv': [Move.from_uci('c1h1')]}, {'depth': 50, 'seldepth': 6, 'multipv': 2, 'score': PovScore(Mate(+3), BLACK), 'nodes': 57263, 'nps': 8180428, 'hashfull': 3, 'tbhits': 0, 'time': 0.007, 'pv': [Move.from_uci('f2g4'), Move.from_uci('h2h3'), Move.from_uci('g4f2'), Move.from_uci('h3h2'), Move.from_uci('c1h1')]}] -------------------------------------------------------------------------------- /generator/diskettes/1710889002.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/2q1p3/4K3/2P2P2/P6P/8 b - - 11 43 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25741, 'nps': 6435250, 'hashfull': 0, 'tbhits': 0, 'time': 0.004, 'pv': [Move.from_uci('c5e3'), Move.from_uci('e4f5'), Move.from_uci('e3f4')]}, {'depth': 50, 'seldepth': 4, 'multipv': 2, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25741, 'nps': 6435250, 'hashfull': 0, 'tbhits': 0, 'time': 0.004, 'pv': [Move.from_uci('c5c4'), Move.from_uci('e4f5'), Move.from_uci('c4f4')]}] -------------------------------------------------------------------------------- /generator/diskettes/1736840525.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/4p3/p1k1K1P1/P4P2/8/8 b - - 3 45 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 19, 'seldepth': 21, 'multipv': 1, 'score': PovScore(Cp(+3206), BLACK), 'nodes': 25000207, 'nps': 6292526, 'hashfull': 569, 'tbhits': 0, 'time': 3.973, 'pv': [Move.from_uci('c4b3')], 'lowerbound': True}, {'depth': 19, 'seldepth': 47, 'multipv': 2, 'score': PovScore(Cp(-216), BLACK), 'nodes': 25000207, 'nps': 6292526, 'hashfull': 569, 'tbhits': 0, 'time': 3.973, 'pv': [Move.from_uci('c4c3'), Move.from_uci('e4f5'), Move.from_uci('c3b3'), Move.from_uci('f5f6'), Move.from_uci('b3a3'), Move.from_uci('g4g5'), Move.from_uci('e5e4'), Move.from_uci('f3e4'), Move.from_uci('a3b4'), Move.from_uci('g5g6'), Move.from_uci('a4a3'), Move.from_uci('g6g7'), Move.from_uci('a3a2'), Move.from_uci('g7g8q'), Move.from_uci('a2a1q'), Move.from_uci('e4e5'), Move.from_uci('a1h1'), Move.from_uci('g8b8'), Move.from_uci('b4c4'), Move.from_uci('b8c8'), Move.from_uci('c4b4'), Move.from_uci('c8e6'), Move.from_uci('b4b5'), Move.from_uci('f6f7'), Move.from_uci('h1f3'), Move.from_uci('f7e7'), Move.from_uci('f3g2'), Move.from_uci('e6d7'), Move.from_uci('b5b6')]}] -------------------------------------------------------------------------------- /generator/diskettes/1912019082.py: -------------------------------------------------------------------------------- 1 | #b'3R4/1Q2nk2/4p2p/4n3/BP3ppP/P7/5PPK/2r5 b - - 3 39 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 8, 'multipv': 1, 'score': PovScore(Mate(+4), BLACK), 'nodes': 25004334, 'nps': 3994940, 'hashfull': 998, 'tbhits': 0, 'time': 6.259, 'pv': [Move.from_uci('g4g3'), Move.from_uci('f2g3'), Move.from_uci('e5g4'), Move.from_uci('h2h3'), Move.from_uci('g4f2'), Move.from_uci('h3h2'), Move.from_uci('c1h1')]}, {'depth': 25, 'seldepth': 42, 'multipv': 2, 'score': PovScore(Cp(-648), BLACK), 'nodes': 25004334, 'nps': 3994940, 'hashfull': 998, 'tbhits': 0, 'time': 6.259, 'pv': [Move.from_uci('c1c3'), Move.from_uci('h2g1'), Move.from_uci('c3a3'), Move.from_uci('d8d1'), Move.from_uci('a3a4'), Move.from_uci('b7b5'), Move.from_uci('f4f3'), Move.from_uci('b5e5'), Move.from_uci('a4b4'), Move.from_uci('d1e1'), Move.from_uci('b4b6'), Move.from_uci('g2f3'), Move.from_uci('g4f3'), Move.from_uci('g1h1'), Move.from_uci('f7e8'), Move.from_uci('e5h5'), Move.from_uci('e8f8'), Move.from_uci('h5f3'), Move.from_uci('f8e8'), Move.from_uci('f3h5'), Move.from_uci('e8f8'), Move.from_uci('h5e5'), Move.from_uci('b6c6'), Move.from_uci('e1g1')]}] -------------------------------------------------------------------------------- /generator/diskettes/1935743088.py: -------------------------------------------------------------------------------- 1 | #b'3R4/1Q2nk2/4p2p/4n3/BP3p1P/P5P1/6PK/2r5 b - - 0 40 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 24, 'seldepth': 6, 'multipv': 1, 'score': PovScore(Mate(+3), BLACK), 'nodes': 25000604, 'nps': 3799483, 'hashfull': 1000, 'tbhits': 0, 'time': 6.58, 'pv': [Move.from_uci('e5g4'), Move.from_uci('h2h3'), Move.from_uci('g4f2'), Move.from_uci('h3h2'), Move.from_uci('c1h1')]}, {'depth': 23, 'seldepth': 46, 'multipv': 2, 'score': PovScore(Cp(-709), BLACK), 'nodes': 25000604, 'nps': 3799483, 'hashfull': 1000, 'tbhits': 0, 'time': 6.58, 'pv': [Move.from_uci('f4g3'), Move.from_uci('h2g3'), Move.from_uci('c1c3'), Move.from_uci('g3h2'), Move.from_uci('e5g4'), Move.from_uci('h2g1'), Move.from_uci('c3a3'), Move.from_uci('a4e8'), Move.from_uci('f7f6'), Move.from_uci('d8d1'), Move.from_uci('g4e3'), Move.from_uci('d1e1'), Move.from_uci('a3c3'), Move.from_uci('b4b5'), Move.from_uci('e3d5'), Move.from_uci('b7a7'), Move.from_uci('c3c4'), Move.from_uci('a7a2'), Move.from_uci('c4c3'), Move.from_uci('a2e2'), Move.from_uci('c3e3'), Move.from_uci('e2b2'), Move.from_uci('e6e5'), Move.from_uci('e1e3'), Move.from_uci('d5e3'), Move.from_uci('b2c3')]}] -------------------------------------------------------------------------------- /generator/diskettes/2112822054.py: -------------------------------------------------------------------------------- 1 | #b'r1b3k1/pp3p1p/2pp2p1/8/2P2q2/2N4R/PP1QBPP1/R4K2 b - - 0 18 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 29, 'seldepth': 45, 'multipv': 1, 'score': PovScore(Cp(+416), BLACK), 'nodes': 25005397, 'nps': 1847598, 'hashfull': 999, 'tbhits': 0, 'time': 13.534, 'pv': [Move.from_uci('f4d2'), Move.from_uci('h3e3'), Move.from_uci('c8e6'), Move.from_uci('b2b3'), Move.from_uci('d2b2'), Move.from_uci('a1b1'), Move.from_uci('b2a3'), Move.from_uci('f1g1'), Move.from_uci('a8d8'), Move.from_uci('b1d1'), Move.from_uci('g8g7'), Move.from_uci('e2f3'), Move.from_uci('a3a5'), Move.from_uci('g2g3'), Move.from_uci('e6f5'), Move.from_uci('d1d4'), Move.from_uci('h7h5'), Move.from_uci('g1g2'), Move.from_uci('d8d7'), Move.from_uci('d4d1'), Move.from_uci('a5d8'), Move.from_uci('d1e1'), Move.from_uci('h5h4'), Move.from_uci('f3e4'), Move.from_uci('f5g4'), Move.from_uci('g3h4'), Move.from_uci('d6d5'), Move.from_uci('e3g3'), Move.from_uci('f7f5'), Move.from_uci('c4d5'), Move.from_uci('c6d5'), Move.from_uci('c3d5'), Move.from_uci('d7d5'), Move.from_uci('e4d5'), Move.from_uci('d8d5'), Move.from_uci('g2g1')]}, {'depth': 29, 'seldepth': 34, 'multipv': 2, 'score': PovScore(Cp(-727), BLACK), 'nodes': 25005397, 'nps': 1847461, 'hashfull': 999, 'tbhits': 0, 'time': 13.535, 'pv': [Move.from_uci('f4f6'), Move.from_uci('h3h1'), Move.from_uci('c8e6'), Move.from_uci('d2h6'), Move.from_uci('f6g7'), Move.from_uci('h6g7'), Move.from_uci('g8g7'), Move.from_uci('c3e4'), Move.from_uci('d6d5'), Move.from_uci('e4g5'), Move.from_uci('h7h5'), Move.from_uci('g5e6'), Move.from_uci('f7e6'), Move.from_uci('a1d1'), Move.from_uci('b7b5'), Move.from_uci('c4b5'), Move.from_uci('c6b5'), Move.from_uci('d1c1'), Move.from_uci('a8b8'), Move.from_uci('h1h3'), Move.from_uci('e6e5'), Move.from_uci('h3d3'), Move.from_uci('d5d4'), Move.from_uci('e2f3'), Move.from_uci('e5e4'), Move.from_uci('f3e4')]}] -------------------------------------------------------------------------------- /generator/diskettes/2241337505.py: -------------------------------------------------------------------------------- 1 | #b'1r5k/7p/pr1p1bp1/q2B4/2P5/P5PP/KB1R4/8 w - - 0 34 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), WHITE), 'nodes': 796784, 'nps': 4888245, 'hashfull': 133, 'tbhits': 0, 'time': 0.163, 'pv': [Move.from_uci('b2f6')]}, {'depth': 50, 'seldepth': 7, 'multipv': 2, 'score': PovScore(Mate(-3), WHITE), 'nodes': 796784, 'nps': 4888245, 'hashfull': 133, 'tbhits': 0, 'time': 0.163, 'pv': [Move.from_uci('d2f2'), Move.from_uci('b6b2'), Move.from_uci('f2b2'), Move.from_uci('b8b2'), Move.from_uci('a2a1'), Move.from_uci('a5a3')]}] -------------------------------------------------------------------------------- /generator/diskettes/2274433818.py: -------------------------------------------------------------------------------- 1 | #b'2kr3r/ppp2pp1/1b6/1P2p3/4P3/P2B2P1/2P2PP1/R2R2K1 b - - 1 18 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 32, 'seldepth': 8, 'multipv': 1, 'score': PovScore(Mate(+4), BLACK), 'nodes': 25001088, 'nps': 1139936, 'hashfull': 1000, 'tbhits': 0, 'time': 21.932, 'pv': [Move.from_uci('h8h1'), Move.from_uci('g1h1'), Move.from_uci('b6f2'), Move.from_uci('d3e2'), Move.from_uci('d8h8'), Move.from_uci('e2h5'), Move.from_uci('h8h5')]}, {'depth': 31, 'seldepth': 62, 'multipv': 2, 'score': PovScore(Cp(+286), BLACK), 'nodes': 25001088, 'nps': 1139936, 'hashfull': 1000, 'tbhits': 0, 'time': 21.932, 'pv': [Move.from_uci('b6d4'), Move.from_uci('d3e2'), Move.from_uci('d4a1'), Move.from_uci('d1a1'), Move.from_uci('d8d2'), Move.from_uci('e2d3'), Move.from_uci('c7c6'), Move.from_uci('b5c6'), Move.from_uci('b7c6'), Move.from_uci('a1f1'), Move.from_uci('f7f6'), Move.from_uci('f2f4'), Move.from_uci('h8e8'), Move.from_uci('g1h2'), Move.from_uci('c8c7'), Move.from_uci('h2h3'), Move.from_uci('e8h8'), Move.from_uci('h3g4'), Move.from_uci('e5f4'), Move.from_uci('f1f4'), Move.from_uci('c7d6'), Move.from_uci('f4f5'), Move.from_uci('d2g2'), Move.from_uci('f5a5')]}] -------------------------------------------------------------------------------- /generator/diskettes/2561939647.py: -------------------------------------------------------------------------------- 1 | #b'5b1k/p3r2P/1p1r3q/4p3/2PBB3/4P3/P3K3/6R1 w - - 0 40 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 25, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), WHITE), 'nodes': 25003133, 'nps': 3599126, 'hashfull': 999, 'tbhits': 0, 'time': 6.947, 'pv': [Move.from_uci('g1g8')]}, {'depth': 25, 'seldepth': 48, 'multipv': 2, 'score': PovScore(Cp(-749), WHITE), 'nodes': 25003133, 'nps': 3599126, 'hashfull': 999, 'tbhits': 0, 'time': 6.947, 'pv': [Move.from_uci('d4c3'), Move.from_uci('e7g7'), Move.from_uci('g1g7'), Move.from_uci('f8g7'), Move.from_uci('e4d5'), Move.from_uci('h6h2'), Move.from_uci('e2d1'), Move.from_uci('d6f6'), Move.from_uci('c3d2'), Move.from_uci('e5e4'), Move.from_uci('d5e4'), Move.from_uci('f6f2'), Move.from_uci('d1c2'), Move.from_uci('f2d2'), Move.from_uci('c2b3'), Move.from_uci('d2b2'), Move.from_uci('b3a4'), Move.from_uci('b2a2'), Move.from_uci('a4b5'), Move.from_uci('h2e5'), Move.from_uci('e4d5')]}] -------------------------------------------------------------------------------- /generator/diskettes/2747930855.py: -------------------------------------------------------------------------------- 1 | #b'7k/p3r1bP/1p1r3q/4p3/2PBB3/4P3/P3KQ2/6R1 w - - 0 39 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), WHITE), 'nodes': 17804810, 'nps': 1179595, 'hashfull': 997, 'tbhits': 0, 'time': 15.094, 'pv': [Move.from_uci('f2f8'), Move.from_uci('g7f8'), Move.from_uci('g1g8')]}, {'depth': 50, 'seldepth': 12, 'multipv': 2, 'score': PovScore(Cp(0), WHITE), 'nodes': 17804810, 'nps': 1179595, 'hashfull': 997, 'tbhits': 0, 'time': 15.094, 'pv': [Move.from_uci('d4c5'), Move.from_uci('d6f6'), Move.from_uci('c5e7'), Move.from_uci('f6f2'), Move.from_uci('e2f2'), Move.from_uci('h6e6'), Move.from_uci('e7g5'), Move.from_uci('e6f7'), Move.from_uci('f2g3'), Move.from_uci('f7h5'), Move.from_uci('g3f2')]}] -------------------------------------------------------------------------------- /generator/diskettes/2757959804.py: -------------------------------------------------------------------------------- 1 | #b'rn2k1nr/ppp2p1p/3p1qp1/2b1p3/2B1P1b1/2NP1Q2/PPP2PPP/R1B1K1NR w KQkq - 2 7 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 58, 'multipv': 1, 'score': PovScore(Cp(+461), WHITE), 'nodes': 25005007, 'nps': 1115647, 'hashfull': 998, 'tbhits': 0, 'time': 22.413, 'pv': [Move.from_uci('f3g4'), Move.from_uci('f6f2'), Move.from_uci('e1d1'), Move.from_uci('f2f1'), Move.from_uci('d1d2'), Move.from_uci('b8c6'), Move.from_uci('g4e2'), Move.from_uci('f1e2'), Move.from_uci('g1e2'), Move.from_uci('f7f5'), Move.from_uci('e4f5'), Move.from_uci('g6f5'), Move.from_uci('a2a3'), Move.from_uci('c6e7'), Move.from_uci('b2b4'), Move.from_uci('c5b6'), Move.from_uci('c3d5'), Move.from_uci('e7d5'), Move.from_uci('c4d5'), Move.from_uci('c7c6'), Move.from_uci('d5f3'), Move.from_uci('e8e7'), Move.from_uci('c2c3'), Move.from_uci('g8f6')]}, {'depth': 26, 'seldepth': 38, 'multipv': 2, 'score': PovScore(Cp(+85), WHITE), 'nodes': 25005007, 'nps': 1115647, 'hashfull': 998, 'tbhits': 0, 'time': 22.413, 'pv': [Move.from_uci('f3g3'), Move.from_uci('g4e6'), Move.from_uci('c3d5'), Move.from_uci('e6d5'), Move.from_uci('c4d5'), Move.from_uci('b8c6'), Move.from_uci('c1g5'), Move.from_uci('f6g7'), Move.from_uci('g1e2'), Move.from_uci('f7f6'), Move.from_uci('d5c6'), Move.from_uci('b7c6'), Move.from_uci('g5d2'), Move.from_uci('g8e7'), Move.from_uci('f2f4'), Move.from_uci('e5f4'), Move.from_uci('g3f4'), Move.from_uci('h8f8'), Move.from_uci('e1c1'), Move.from_uci('e8c8'), Move.from_uci('f4h4'), Move.from_uci('g7f7'), Move.from_uci('c1b1'), Move.from_uci('c8b7'), Move.from_uci('h1f1'), Move.from_uci('d6d5')]}] -------------------------------------------------------------------------------- /generator/diskettes/2841779116.py: -------------------------------------------------------------------------------- 1 | #b'r1b3k1/pp3p1p/2pp2p1/8/2P2q2/2N4r/PP1QBPP1/R4K1R w - - 0 18 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 41, 'multipv': 1, 'score': PovScore(Cp(-421), WHITE), 'nodes': 25000796, 'nps': 1675656, 'hashfull': 999, 'tbhits': 0, 'time': 14.92, 'pv': [Move.from_uci('h1h3'), Move.from_uci('f4d2'), Move.from_uci('h3e3'), Move.from_uci('c8e6'), Move.from_uci('b2b3'), Move.from_uci('d2b2'), Move.from_uci('a1b1'), Move.from_uci('b2a3'), Move.from_uci('f1g1'), Move.from_uci('g8g7'), Move.from_uci('b3b4'), Move.from_uci('e6f5'), Move.from_uci('b1e1'), Move.from_uci('a3b4'), Move.from_uci('g2g4'), Move.from_uci('a8e8'), Move.from_uci('e3e8'), Move.from_uci('b4c3'), Move.from_uci('e1d1'), Move.from_uci('f5e6'), Move.from_uci('d1d6'), Move.from_uci('c3e5'), Move.from_uci('e8e6'), Move.from_uci('f7e6'), Move.from_uci('d6d7'), Move.from_uci('g7f8'), Move.from_uci('e2f1'), Move.from_uci('e5b2'), Move.from_uci('d7h7')]}, {'depth': 26, 'seldepth': 51, 'multipv': 2, 'score': PovScore(Cp(-533), WHITE), 'nodes': 25000796, 'nps': 1675656, 'hashfull': 999, 'tbhits': 0, 'time': 14.92, 'pv': [Move.from_uci('g2h3'), Move.from_uci('f4d2'), Move.from_uci('a1e1'), Move.from_uci('d2b2'), Move.from_uci('c3e4'), Move.from_uci('d6d5'), Move.from_uci('e4g3'), Move.from_uci('c8e6'), Move.from_uci('e2g4'), Move.from_uci('a8f8'), Move.from_uci('f1g2'), Move.from_uci('h7h5'), Move.from_uci('g4e6'), Move.from_uci('f7e6'), Move.from_uci('h1f1'), Move.from_uci('e6e5'), Move.from_uci('e1e2'), Move.from_uci('b2d4'), Move.from_uci('f1e1'), Move.from_uci('e5e4'), Move.from_uci('c4d5'), Move.from_uci('c6d5'), Move.from_uci('h3h4'), Move.from_uci('d4d3'), Move.from_uci('e2b2'), Move.from_uci('d3f3'), Move.from_uci('g2g1'), Move.from_uci('g8h8')]}] -------------------------------------------------------------------------------- /generator/diskettes/3124893989.py: -------------------------------------------------------------------------------- 1 | #b'5k2/5ppp/2r1p3/1p6/1b1R4/p1N1P1P1/B4PKP/8 b - - 0 34 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 28, 'seldepth': 44, 'multipv': 1, 'score': PovScore(Cp(+468), BLACK), 'nodes': 25000208, 'nps': 1139324, 'hashfull': 998, 'tbhits': 0, 'time': 21.943, 'pv': [Move.from_uci('b4c3'), Move.from_uci('d4d8'), Move.from_uci('f8e7'), Move.from_uci('d8a8'), Move.from_uci('b5b4'), Move.from_uci('a8a7'), Move.from_uci('e7f6'), Move.from_uci('h2h4'), Move.from_uci('h7h5'), Move.from_uci('a7a4'), Move.from_uci('c6d6'), Move.from_uci('a2c4'), Move.from_uci('d6d2'), Move.from_uci('g2f1'), Move.from_uci('d2c2'), Move.from_uci('c4b3'), Move.from_uci('c2b2'), Move.from_uci('b3c4'), Move.from_uci('b2d2'), Move.from_uci('a4a7'), Move.from_uci('g7g6'), Move.from_uci('c4b3'), Move.from_uci('c3e5'), Move.from_uci('f2f4'), Move.from_uci('e5c3'), Move.from_uci('e3e4'), Move.from_uci('c3d4'), Move.from_uci('e4e5'), Move.from_uci('f6f5'), Move.from_uci('a7f7'), Move.from_uci('f5e4'), Move.from_uci('b3e6'), Move.from_uci('a3a2'), Move.from_uci('e6a2'), Move.from_uci('d2a2'), Move.from_uci('e5e6'), Move.from_uci('a2a8'), Move.from_uci('f7b7')]}, {'depth': 28, 'seldepth': 30, 'multipv': 2, 'score': PovScore(Cp(+5), BLACK), 'nodes': 25000208, 'nps': 1139324, 'hashfull': 998, 'tbhits': 0, 'time': 21.943, 'pv': [Move.from_uci('c6c3'), Move.from_uci('d4b4'), Move.from_uci('c3c2'), Move.from_uci('a2b1'), Move.from_uci('a3a2'), Move.from_uci('b1a2'), Move.from_uci('c2a2'), Move.from_uci('b4b5'), Move.from_uci('a2a4'), Move.from_uci('h2h3'), Move.from_uci('h7h6'), Move.from_uci('g3g4'), Move.from_uci('g7g6'), Move.from_uci('f2f4'), Move.from_uci('a4a2'), Move.from_uci('g2f3'), Move.from_uci('a2a7'), Move.from_uci('f3g3'), Move.from_uci('a7c7'), Move.from_uci('h3h4'), Move.from_uci('f7f5'), Move.from_uci('h4h5')]}] -------------------------------------------------------------------------------- /generator/diskettes/3193641271.py: -------------------------------------------------------------------------------- 1 | #b'kr6/p5pp/Q4np1/3p4/6P1/2P1qP2/P5P1/K2R3R b - - 2 26 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 27, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 25002204, 'nps': 1244571, 'hashfull': 997, 'tbhits': 0, 'time': 20.089, 'pv': [Move.from_uci('e3c3')]}, {'depth': 26, 'seldepth': 52, 'multipv': 2, 'score': PovScore(Cp(-421), BLACK), 'nodes': 25002204, 'nps': 1244571, 'hashfull': 997, 'tbhits': 0, 'time': 20.089, 'pv': [Move.from_uci('e3c5'), Move.from_uci('a6d3'), Move.from_uci('h7h6'), Move.from_uci('d1b1'), Move.from_uci('b8b6'), Move.from_uci('h1c1'), Move.from_uci('c5c8'), Move.from_uci('d3d4'), Move.from_uci('c8c7'), Move.from_uci('c3c4'), Move.from_uci('b6c6'), Move.from_uci('c4c5'), Move.from_uci('g6g5'), Move.from_uci('d4c3'), Move.from_uci('f6d7'), Move.from_uci('c3g7'), Move.from_uci('a7a6'), Move.from_uci('c1e1'), Move.from_uci('d7c5')]}] -------------------------------------------------------------------------------- /generator/diskettes/3273070181.py: -------------------------------------------------------------------------------- 1 | #b'5k2/p6K/1p2Q2p/5Pq1/1P4P1/P7/8/8 b - - 8 58 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 25, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 25000602, 'nps': 6199008, 'hashfull': 990, 'tbhits': 0, 'time': 4.033, 'pv': [Move.from_uci('g5g7')]}, {'depth': 24, 'seldepth': 54, 'multipv': 2, 'score': PovScore(Cp(-909), BLACK), 'nodes': 25000602, 'nps': 6199008, 'hashfull': 990, 'tbhits': 0, 'time': 4.033, 'pv': [Move.from_uci('g5e7'), Move.from_uci('e6e7'), Move.from_uci('f8e7'), Move.from_uci('h7h6'), Move.from_uci('e7d6'), Move.from_uci('f5f6'), Move.from_uci('d6c6'), Move.from_uci('f6f7'), Move.from_uci('c6b5'), Move.from_uci('f7f8q'), Move.from_uci('b5a4'), Move.from_uci('g4g5'), Move.from_uci('a7a6'), Move.from_uci('b4b5'), Move.from_uci('a4b5'), Move.from_uci('g5g6'), Move.from_uci('b5c6'), Move.from_uci('g6g7'), Move.from_uci('c6d7'), Move.from_uci('h6h7'), Move.from_uci('a6a5'), Move.from_uci('f8f6'), Move.from_uci('b6b5'), Move.from_uci('f6d4')]}] -------------------------------------------------------------------------------- /generator/diskettes/3346864465.py: -------------------------------------------------------------------------------- 1 | #b'1r5k/5Q1p/pr1p2p1/q2B4/2P5/P1b3PP/KB1R4/8 w - - 1 33 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 25, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), WHITE), 'nodes': 25003073, 'nps': 4682223, 'hashfull': 1000, 'tbhits': 0, 'time': 5.34, 'pv': [Move.from_uci('f7f6'), Move.from_uci('c3f6'), Move.from_uci('b2f6')]}, {'depth': 24, 'seldepth': 52, 'multipv': 2, 'score': PovScore(Cp(-879), WHITE), 'nodes': 25003073, 'nps': 4682223, 'hashfull': 1000, 'tbhits': 0, 'time': 5.34, 'pv': [Move.from_uci('f7f2'), Move.from_uci('b6b2'), Move.from_uci('d2b2'), Move.from_uci('b8b2'), Move.from_uci('f2b2'), Move.from_uci('c3b2'), Move.from_uci('a2b2'), Move.from_uci('a5d2'), Move.from_uci('b2b1'), Move.from_uci('h8g7'), Move.from_uci('d5b7'), Move.from_uci('d2d1'), Move.from_uci('b1b2'), Move.from_uci('d1d4'), Move.from_uci('b2c1'), Move.from_uci('d4c4'), Move.from_uci('c1d2'), Move.from_uci('g7f6'), Move.from_uci('b7f3'), Move.from_uci('f6e5'), Move.from_uci('d2e3'), Move.from_uci('c4c5'), Move.from_uci('e3e2'), Move.from_uci('e5d4'), Move.from_uci('h3h4'), Move.from_uci('c5a3')]}] -------------------------------------------------------------------------------- /generator/diskettes/3512671251.py: -------------------------------------------------------------------------------- 1 | #b'r1bq1r2/pp1nbppk/4p3/3pP3/8/1P6/PBP2PPP/RN1Q1RK1 w - - 0 12 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 25, 'seldepth': 39, 'multipv': 1, 'score': PovScore(Cp(-346), WHITE), 'nodes': 25003380, 'nps': 1216887, 'hashfull': 999, 'tbhits': 0, 'time': 20.547, 'pv': [Move.from_uci('c2c4'), Move.from_uci('d5c4'), Move.from_uci('b1d2'), Move.from_uci('f8h8'), Move.from_uci('d1f3'), Move.from_uci('d7e5'), Move.from_uci('b2e5'), Move.from_uci('d8d2'), Move.from_uci('a1d1'), Move.from_uci('d2g5'), Move.from_uci('f1e1'), Move.from_uci('f7f6'), Move.from_uci('e5d6'), Move.from_uci('e7d6'), Move.from_uci('d1d6'), Move.from_uci('e6e5'), Move.from_uci('b3c4'), Move.from_uci('c8f5'), Move.from_uci('f3d5'), Move.from_uci('h8f8'), Move.from_uci('e1e3'), Move.from_uci('a8e8'), Move.from_uci('d5d1'), Move.from_uci('g5g6'), Move.from_uci('d1b3'), Move.from_uci('g6h5'), Move.from_uci('d6d5'), Move.from_uci('b7b6'), Move.from_uci('e3f3'), Move.from_uci('f5e6')]}, {'depth': 25, 'seldepth': 36, 'multipv': 2, 'score': PovScore(Cp(-353), WHITE), 'nodes': 25003380, 'nps': 1216887, 'hashfull': 999, 'tbhits': 0, 'time': 20.547, 'pv': [Move.from_uci('d1e2'), Move.from_uci('h7g8'), Move.from_uci('b1d2'), Move.from_uci('f7f5'), Move.from_uci('e5f6'), Move.from_uci('d7f6'), Move.from_uci('d2f3'), Move.from_uci('d8e8'), Move.from_uci('a1e1'), Move.from_uci('e8h5'), Move.from_uci('e2b5'), Move.from_uci('h5g6'), Move.from_uci('f3e5'), Move.from_uci('g6c2'), Move.from_uci('b2d4'), Move.from_uci('e7d6'), Move.from_uci('e1e3'), Move.from_uci('d6e5'), Move.from_uci('d4e5'), Move.from_uci('f6g4')]}] -------------------------------------------------------------------------------- /generator/diskettes/3582072466.py: -------------------------------------------------------------------------------- 1 | #b'3q1k2/p7/1p2Q1Kp/5P2/1P4P1/P7/8/8 b - - 6 57 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 24, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25002018, 'nps': 2699710, 'hashfull': 988, 'tbhits': 0, 'time': 9.261, 'pv': [Move.from_uci('d8g5'), Move.from_uci('g6h7'), Move.from_uci('g5g7')]}, {'depth': 23, 'seldepth': 58, 'multipv': 2, 'score': PovScore(Cp(-771), BLACK), 'nodes': 25002018, 'nps': 2699710, 'hashfull': 988, 'tbhits': 0, 'time': 9.261, 'pv': [Move.from_uci('d8c7'), Move.from_uci('e6f6'), Move.from_uci('f8g8'), Move.from_uci('b4b5'), Move.from_uci('h6h5'), Move.from_uci('f6e6'), Move.from_uci('g8f8'), Move.from_uci('g6h5'), Move.from_uci('c7d8'), Move.from_uci('h5g6'), Move.from_uci('d8c7'), Move.from_uci('e6f6'), Move.from_uci('f8g8'), Move.from_uci('g6h6'), Move.from_uci('a7a5'), Move.from_uci('b5a6'), Move.from_uci('b6b5'), Move.from_uci('f6e6'), Move.from_uci('g8f8'), Move.from_uci('f5f6'), Move.from_uci('c7h2'), Move.from_uci('h6g6'), Move.from_uci('h2c2'), Move.from_uci('e6f5'), Move.from_uci('c2c6'), Move.from_uci('f5d3'), Move.from_uci('c6e8'), Move.from_uci('g6h6'), Move.from_uci('e8a8'), Move.from_uci('g4g5'), Move.from_uci('b5b4'), Move.from_uci('a3b4'), Move.from_uci('f8e8'), Move.from_uci('h6g6'), Move.from_uci('a8b8'), Move.from_uci('d3e4'), Move.from_uci('e8d7'), Move.from_uci('e4d5'), Move.from_uci('d7c7'), Move.from_uci('d5e5'), Move.from_uci('c7c8'), Move.from_uci('e5b8'), Move.from_uci('c8b8'), Move.from_uci('f6f7')]}] -------------------------------------------------------------------------------- /generator/diskettes/3672841238.py: -------------------------------------------------------------------------------- 1 | #b'r1b3k1/pp3p1p/2pp2p1/8/2P2q2/2N1r2P/PP1QBPP1/R4K1R b - - 1 17 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 25, 'seldepth': 39, 'multipv': 1, 'score': PovScore(Cp(+416), BLACK), 'nodes': 25000353, 'nps': 1532259, 'hashfull': 1000, 'tbhits': 0, 'time': 16.316, 'pv': [Move.from_uci('e3h3'), Move.from_uci('h1h3'), Move.from_uci('f4d2'), Move.from_uci('h3e3'), Move.from_uci('c8e6'), Move.from_uci('f1g1'), Move.from_uci('a8d8'), Move.from_uci('b2b3'), Move.from_uci('d6d5'), Move.from_uci('a1d1'), Move.from_uci('d2b2'), Move.from_uci('c4d5'), Move.from_uci('c6d5'), Move.from_uci('e3d3'), Move.from_uci('d5d4'), Move.from_uci('d1d2'), Move.from_uci('b2c1'), Move.from_uci('d2d1'), Move.from_uci('c1g5'), Move.from_uci('e2f3'), Move.from_uci('d4c3'), Move.from_uci('d3d8'), Move.from_uci('g8g7'), Move.from_uci('f3e4'), Move.from_uci('b7b5'), Move.from_uci('e4c2'), Move.from_uci('e6h3'), Move.from_uci('g2g3'), Move.from_uci('b5b4'), Move.from_uci('d8d5'), Move.from_uci('g5e7')]}, {'depth': 25, 'seldepth': 31, 'multipv': 2, 'score': PovScore(Cp(-491), BLACK), 'nodes': 25000353, 'nps': 1532259, 'hashfull': 1000, 'tbhits': 0, 'time': 16.316, 'pv': [Move.from_uci('e3f3'), Move.from_uci('d2e1'), Move.from_uci('f3g3'), Move.from_uci('e1c1'), Move.from_uci('f4c1'), Move.from_uci('a1c1'), Move.from_uci('g3g5'), Move.from_uci('f2f3'), Move.from_uci('c8e6'), Move.from_uci('f1f2'), Move.from_uci('d6d5'), Move.from_uci('h3h4'), Move.from_uci('g5e5'), Move.from_uci('c4d5'), Move.from_uci('e6d5'), Move.from_uci('c3d5'), Move.from_uci('e5d5'), Move.from_uci('h1d1'), Move.from_uci('d5h5'), Move.from_uci('d1d7'), Move.from_uci('h5h4'), Move.from_uci('d7b7'), Move.from_uci('h4h5'), Move.from_uci('c1d1'), Move.from_uci('h5c5'), Move.from_uci('d1d2'), Move.from_uci('a8e8'), Move.from_uci('g2g4')]}] -------------------------------------------------------------------------------- /generator/diskettes/3738769095.py: -------------------------------------------------------------------------------- 1 | #b'8/5p1k/4p1p1/8/3PQ1Kp/4P2P/5qP1/8 b - - 0 44 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 218099, 'nps': 6231400, 'hashfull': 10, 'tbhits': 0, 'time': 0.035, 'pv': [Move.from_uci('f2g3')]}, {'depth': 50, 'seldepth': 8, 'multipv': 2, 'score': PovScore(Mate(+4), BLACK), 'nodes': 218099, 'nps': 6231400, 'hashfull': 10, 'tbhits': 0, 'time': 0.035, 'pv': [Move.from_uci('f7f5'), Move.from_uci('g4g5'), Move.from_uci('f5e4'), Move.from_uci('d4d5'), Move.from_uci('h7g7'), Move.from_uci('d5e6'), Move.from_uci('f2g3')]}] -------------------------------------------------------------------------------- /generator/diskettes/3829668910.py: -------------------------------------------------------------------------------- 1 | #b'r1bq1r2/pp1nbppk/4p3/3pP3/2P5/1P6/PB3PPP/RN1Q1RK1 b - - 0 12 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 26, 'seldepth': 43, 'multipv': 1, 'score': PovScore(Cp(+338), BLACK), 'nodes': 25000821, 'nps': 1241968, 'hashfull': 998, 'tbhits': 0, 'time': 20.13, 'pv': [Move.from_uci('d5c4'), Move.from_uci('b1d2'), Move.from_uci('f8h8'), Move.from_uci('a1c1'), Move.from_uci('d7b6'), Move.from_uci('c1c3'), Move.from_uci('h7g8'), Move.from_uci('c3g3'), Move.from_uci('h8h7'), Move.from_uci('d1e2'), Move.from_uci('c8d7'), Move.from_uci('d2e4'), Move.from_uci('d7c6'), Move.from_uci('e4f6'), Move.from_uci('e7f6'), Move.from_uci('e5f6'), Move.from_uci('d8d5'), Move.from_uci('f1d1'), Move.from_uci('d5e4'), Move.from_uci('e2d2'), Move.from_uci('c4c3'), Move.from_uci('b2c3'), Move.from_uci('b6d5'), Move.from_uci('g1f1')]}, {'depth': 26, 'seldepth': 46, 'multipv': 2, 'score': PovScore(Cp(+338), BLACK), 'nodes': 25000821, 'nps': 1241968, 'hashfull': 998, 'tbhits': 0, 'time': 20.13, 'pv': [Move.from_uci('f7f6'), Move.from_uci('e5f6'), Move.from_uci('d7f6'), Move.from_uci('b1d2'), Move.from_uci('h7g8'), Move.from_uci('f1e1'), Move.from_uci('d5c4'), Move.from_uci('b3c4'), Move.from_uci('e7b4'), Move.from_uci('e1e2'), Move.from_uci('d8d3'), Move.from_uci('d2e4'), Move.from_uci('d3d1'), Move.from_uci('a1d1'), Move.from_uci('e6e5'), Move.from_uci('b2e5'), Move.from_uci('f6e4'), Move.from_uci('e2e4'), Move.from_uci('b4c5')]}] -------------------------------------------------------------------------------- /generator/diskettes/388831774.py: -------------------------------------------------------------------------------- 1 | #b'1r4k1/5p1p/pr1p2p1/q2B4/2P5/P1b3PP/KB1R1Q2/8 w - - 0 32 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 23, 'seldepth': 6, 'multipv': 1, 'score': PovScore(Mate(+3), WHITE), 'nodes': 25000029, 'nps': 4906777, 'hashfull': 997, 'tbhits': 0, 'time': 5.095, 'pv': [Move.from_uci('f2f7'), Move.from_uci('g8h8'), Move.from_uci('f7f6'), Move.from_uci('c3f6'), Move.from_uci('b2f6')]}, {'depth': 22, 'seldepth': 49, 'multipv': 2, 'score': PovScore(Cp(-805), WHITE), 'nodes': 25000029, 'nps': 4906777, 'hashfull': 997, 'tbhits': 0, 'time': 5.095, 'pv': [Move.from_uci('g3g4'), Move.from_uci('b6b2'), Move.from_uci('d2b2'), Move.from_uci('b8b2'), Move.from_uci('f2b2'), Move.from_uci('c3b2'), Move.from_uci('a2b2'), Move.from_uci('g6g5'), Move.from_uci('b2b3'), Move.from_uci('g8f8'), Move.from_uci('a3a4'), Move.from_uci('a5d2'), Move.from_uci('a4a5'), Move.from_uci('d2a5'), Move.from_uci('d5e4'), Move.from_uci('a5b6'), Move.from_uci('b3c2'), Move.from_uci('b6f2'), Move.from_uci('c2d3'), Move.from_uci('a6a5'), Move.from_uci('e4f5'), Move.from_uci('a5a4')]}] -------------------------------------------------------------------------------- /generator/diskettes/3912244286.py: -------------------------------------------------------------------------------- 1 | #b'r1bq1rk1/pp1nbppB/4p3/3pP3/8/1P6/PBP2PPP/RN1Q1RK1 b - - 0 11 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 27, 'seldepth': 42, 'multipv': 1, 'score': PovScore(Cp(+325), BLACK), 'nodes': 25000514, 'nps': 1389535, 'hashfull': 998, 'tbhits': 0, 'time': 17.992, 'pv': [Move.from_uci('g8h7'), Move.from_uci('c2c4'), Move.from_uci('d5c4'), Move.from_uci('b1d2'), Move.from_uci('f8h8'), Move.from_uci('d1f3'), Move.from_uci('c4c3'), Move.from_uci('f3c3'), Move.from_uci('d7c5'), Move.from_uci('b2a3'), Move.from_uci('b7b6'), Move.from_uci('f1d1'), Move.from_uci('c8b7'), Move.from_uci('d2c4'), Move.from_uci('b7d5'), Move.from_uci('d1d4'), Move.from_uci('h7g8'), Move.from_uci('a1d1'), Move.from_uci('d8f8'), Move.from_uci('c3c2'), Move.from_uci('a8c8'), Move.from_uci('c2e2'), Move.from_uci('d5c4'), Move.from_uci('d4c4'), Move.from_uci('a7a5'), Move.from_uci('g2g3'), Move.from_uci('c8d8'), Move.from_uci('d1d8'), Move.from_uci('f8d8')]}, {'depth': 27, 'seldepth': 49, 'multipv': 2, 'score': PovScore(Cp(-619), BLACK), 'nodes': 25000514, 'nps': 1389535, 'hashfull': 998, 'tbhits': 0, 'time': 17.992, 'pv': [Move.from_uci('g8h8'), Move.from_uci('d1h5'), Move.from_uci('e7g5'), Move.from_uci('b2c1'), Move.from_uci('g5c1'), Move.from_uci('f1c1'), Move.from_uci('f8e8'), Move.from_uci('h7g6'), Move.from_uci('h8g8'), Move.from_uci('g6f7'), Move.from_uci('g8f8'), Move.from_uci('f7g6'), Move.from_uci('d7e5'), Move.from_uci('g6e8'), Move.from_uci('d8f6'), Move.from_uci('c1e1'), Move.from_uci('e5f3'), Move.from_uci('h5f3'), Move.from_uci('f6f3'), Move.from_uci('g2f3'), Move.from_uci('f8e8'), Move.from_uci('g1g2')]}] -------------------------------------------------------------------------------- /generator/diskettes/4117174029.py: -------------------------------------------------------------------------------- 1 | #b'8/Pkp3pp/8/4p3/1P2b3/4Kr2/1P5P/R7 w - - 2 31 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 30, 'seldepth': 48, 'multipv': 1, 'score': PovScore(Cp(0), WHITE), 'nodes': 25000122, 'nps': 1675386, 'hashfull': 999, 'tbhits': 0, 'time': 14.922, 'pv': [Move.from_uci('e3e4'), Move.from_uci('f3f8'), Move.from_uci('a7a8r'), Move.from_uci('f8a8'), Move.from_uci('a1a8'), Move.from_uci('b7a8'), Move.from_uci('e4e5'), Move.from_uci('a8a7'), Move.from_uci('e5e6'), Move.from_uci('a7a6'), Move.from_uci('e6f7'), Move.from_uci('g7g5'), Move.from_uci('f7g7'), Move.from_uci('a6b5'), Move.from_uci('g7h7'), Move.from_uci('b5b4'), Move.from_uci('h7g6'), Move.from_uci('g5g4'), Move.from_uci('g6f5'), Move.from_uci('b4b3'), Move.from_uci('f5g4'), Move.from_uci('b3b2'), Move.from_uci('h2h4'), Move.from_uci('c7c5'), Move.from_uci('h4h5'), Move.from_uci('c5c4'), Move.from_uci('h5h6'), Move.from_uci('c4c3'), Move.from_uci('h6h7'), Move.from_uci('c3c2'), Move.from_uci('h7h8q'), Move.from_uci('b2b1'), Move.from_uci('h8h1')]}, {'depth': 30, 'seldepth': 47, 'multipv': 2, 'score': PovScore(Cp(-515), WHITE), 'nodes': 25000122, 'nps': 1675386, 'hashfull': 999, 'tbhits': 0, 'time': 14.922, 'pv': [Move.from_uci('e3d2'), Move.from_uci('b7a8'), Move.from_uci('h2h4'), Move.from_uci('g7g6'), Move.from_uci('b4b5'), Move.from_uci('e4b7'), Move.from_uci('d2e2'), Move.from_uci('h7h5'), Move.from_uci('a1g1'), Move.from_uci('a8a7'), Move.from_uci('g1g6'), Move.from_uci('f3f4'), Move.from_uci('g6e6'), Move.from_uci('f4h4'), Move.from_uci('b5b6'), Move.from_uci('c7b6'), Move.from_uci('e6e5'), Move.from_uci('h4e4'), Move.from_uci('e5e4'), Move.from_uci('b7e4'), Move.from_uci('e2e3'), Move.from_uci('e4f5'), Move.from_uci('e3d2'), Move.from_uci('f5d7')]}] -------------------------------------------------------------------------------- /generator/diskettes/691672986.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/4p3/7K/2P2P2/P5qP/8 b - - 5 40 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 339789, 'nps': 4924478, 'hashfull': 22, 'tbhits': 0, 'time': 0.069, 'pv': [Move.from_uci('b6f2'), Move.from_uci('h4h5'), Move.from_uci('g2g5')]}, {'depth': 50, 'seldepth': 8, 'multipv': 2, 'score': PovScore(Mate(+4), BLACK), 'nodes': 339789, 'nps': 4924478, 'hashfull': 22, 'tbhits': 0, 'time': 0.069, 'pv': [Move.from_uci('g2h2'), Move.from_uci('h4g4'), Move.from_uci('h2g2'), Move.from_uci('g4h4'), Move.from_uci('b6f2'), Move.from_uci('h4h5'), Move.from_uci('g2g5')]}] -------------------------------------------------------------------------------- /generator/diskettes/696195689.py: -------------------------------------------------------------------------------- 1 | #b'5rk1/pp3p2/1q1R3p/6p1/5pB1/2P3PP/PPQ3PK/3Rr3 b - - 0 27 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 1241507, 'nps': 1924817, 'hashfull': 358, 'tbhits': 0, 'time': 0.645, 'pv': [Move.from_uci('b6g1')]}, {'depth': 50, 'seldepth': 8, 'multipv': 2, 'score': PovScore(Cp(0), BLACK), 'nodes': 1241507, 'nps': 1924817, 'hashfull': 358, 'tbhits': 0, 'time': 0.645, 'pv': [Move.from_uci('f4g3'), Move.from_uci('h2g3'), Move.from_uci('b6e3'), Move.from_uci('g3h2'), Move.from_uci('e3g1'), Move.from_uci('h2g3'), Move.from_uci('g1e3')]}] -------------------------------------------------------------------------------- /generator/diskettes/696260506.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/4pK2/8/2P2P2/P5qP/8 b - - 5 40 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 156430, 'nps': 9201764, 'hashfull': 18, 'tbhits': 0, 'time': 0.017, 'pv': [Move.from_uci('g2g6')]}, {'depth': 50, 'seldepth': 6, 'multipv': 2, 'score': PovScore(Mate(+3), BLACK), 'nodes': 156430, 'nps': 9201764, 'hashfull': 18, 'tbhits': 0, 'time': 0.017, 'pv': [Move.from_uci('g2g5'), Move.from_uci('f5e4'), Move.from_uci('g5e3'), Move.from_uci('e4f5'), Move.from_uci('e3f4')]}] -------------------------------------------------------------------------------- /generator/diskettes/728045472.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/3Kp3/8/2P1qP2/P6P/8 b - - 9 42 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 35069, 'nps': 5844833, 'hashfull': 0, 'tbhits': 0, 'time': 0.006, 'pv': [Move.from_uci('e3d3')]}, {'depth': 50, 'seldepth': 6, 'multipv': 2, 'score': PovScore(Mate(+3), BLACK), 'nodes': 35069, 'nps': 5844833, 'hashfull': 0, 'tbhits': 0, 'time': 0.006, 'pv': [Move.from_uci('e3c5'), Move.from_uci('d5e4'), Move.from_uci('c5e3'), Move.from_uci('e4f5'), Move.from_uci('e3f4')]}] -------------------------------------------------------------------------------- /generator/diskettes/730666912.py: -------------------------------------------------------------------------------- 1 | #b'2Q5/p3kp2/1bB2p2/4pK2/8/2P1qP2/P6P/8 b - - 9 42 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 50, 'seldepth': 2, 'multipv': 1, 'score': PovScore(Mate(+1), BLACK), 'nodes': 35563, 'nps': 5927166, 'hashfull': 0, 'tbhits': 0, 'time': 0.006, 'pv': [Move.from_uci('e3f4')]}, {'depth': 50, 'seldepth': 6, 'multipv': 2, 'score': PovScore(Mate(+3), BLACK), 'nodes': 35563, 'nps': 5927166, 'hashfull': 0, 'tbhits': 0, 'time': 0.006, 'pv': [Move.from_uci('e3g5'), Move.from_uci('f5e4'), Move.from_uci('g5e3'), Move.from_uci('e4f5'), Move.from_uci('e3f4')]}] -------------------------------------------------------------------------------- /generator/diskettes/837031090.py: -------------------------------------------------------------------------------- 1 | #b'8/8/5p2/4pK2/p5P1/k4P2/8/8 w - - 0 47 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 21, 'seldepth': 59, 'multipv': 1, 'score': PovScore(Cp(-7201), WHITE), 'nodes': 25002042, 'nps': 6414069, 'hashfull': 777, 'tbhits': 0, 'time': 3.898, 'pv': [Move.from_uci('f5e4'), Move.from_uci('a3b3')], 'upperbound': True}, {'depth': 20, 'seldepth': 56, 'multipv': 2, 'score': PovScore(Cp(-1476), WHITE), 'nodes': 25002042, 'nps': 6414069, 'hashfull': 777, 'tbhits': 0, 'time': 3.898, 'pv': [Move.from_uci('f5f6'), Move.from_uci('e5e4'), Move.from_uci('f3e4'), Move.from_uci('a3b4'), Move.from_uci('g4g5'), Move.from_uci('a4a3'), Move.from_uci('g5g6'), Move.from_uci('a3a2'), Move.from_uci('g6g7'), Move.from_uci('a2a1q'), Move.from_uci('f6f7'), Move.from_uci('a1a2'), Move.from_uci('f7f8'), Move.from_uci('a2f2'), Move.from_uci('f8e7'), Move.from_uci('f2g3'), Move.from_uci('e7f7'), Move.from_uci('g3f4'), Move.from_uci('f7g6'), Move.from_uci('f4e4'), Move.from_uci('g6h6'), Move.from_uci('e4e6'), Move.from_uci('h6h7'), Move.from_uci('e6f5'), Move.from_uci('h7h8'), Move.from_uci('f5h3'), Move.from_uci('h8g8'), Move.from_uci('b4c5'), Move.from_uci('g8f7'), Move.from_uci('h3f3'), Move.from_uci('f7e7'), Move.from_uci('f3e4'), Move.from_uci('e7f6'), Move.from_uci('e4e8'), Move.from_uci('f6f5'), Move.from_uci('e8f7'), Move.from_uci('f5g5'), Move.from_uci('f7g7'), Move.from_uci('g5f4'), Move.from_uci('g7a1'), Move.from_uci('f4f5'), Move.from_uci('c5d5'), Move.from_uci('f5f4'), Move.from_uci('a1g7'), Move.from_uci('f4f3'), Move.from_uci('d5d6'), Move.from_uci('f3f4'), Move.from_uci('g7f6'), Move.from_uci('f4g4'), Move.from_uci('f6f8'), Move.from_uci('g4g3')]}] -------------------------------------------------------------------------------- /generator/diskettes/945691296.py: -------------------------------------------------------------------------------- 1 | #b'5rk1/pp3p2/1q1R3p/6p1/5pBb/2P4P/PPQ2PPK/3Rr3 b - - 7 26 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 30, 'seldepth': 4, 'multipv': 1, 'score': PovScore(Mate(+2), BLACK), 'nodes': 25002162, 'nps': 1093947, 'hashfull': 996, 'tbhits': 0, 'time': 22.855, 'pv': [Move.from_uci('h4g3'), Move.from_uci('f2g3'), Move.from_uci('b6g1')]}, {'depth': 29, 'seldepth': 51, 'multipv': 2, 'score': PovScore(Cp(+47), BLACK), 'nodes': 25002162, 'nps': 1093947, 'hashfull': 996, 'tbhits': 0, 'time': 22.855, 'pv': [Move.from_uci('b6f2'), Move.from_uci('c2f2'), Move.from_uci('h4f2'), Move.from_uci('d1e1'), Move.from_uci('f2e1'), Move.from_uci('h2g1'), Move.from_uci('f8e8'), Move.from_uci('d6d1'), Move.from_uci('e1g3'), Move.from_uci('g1f1'), Move.from_uci('b7b6'), Move.from_uci('c3c4'), Move.from_uci('a7a5'), Move.from_uci('g4e2'), Move.from_uci('g8g7'), Move.from_uci('d1d5'), Move.from_uci('e8e6'), Move.from_uci('a2a4'), Move.from_uci('e6c6'), Move.from_uci('e2d3'), Move.from_uci('g7f6'), Move.from_uci('d5f5'), Move.from_uci('f6e7'), Move.from_uci('f5d5'), Move.from_uci('c6e6'), Move.from_uci('d3e2'), Move.from_uci('f7f6'), Move.from_uci('c4c5'), Move.from_uci('h6h5'), Move.from_uci('c5b6'), Move.from_uci('e6b6'), Move.from_uci('e2h5')]}] -------------------------------------------------------------------------------- /generator/diskettes/949688249.py: -------------------------------------------------------------------------------- 1 | #b'2R2b2/N4k1p/8/5pp1/1n2p2P/4P1K1/3P4/8 b - - 2 47 2 Limit(time=30, depth=50, nodes=25000000)' 2 | [{'string': 'NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1))', 'depth': 30, 'seldepth': 50, 'multipv': 1, 'score': PovScore(Cp(-21), BLACK), 'nodes': 25000816, 'nps': 1029263, 'hashfull': 1000, 'tbhits': 0, 'time': 24.29, 'pv': [Move.from_uci('f8d6'), Move.from_uci('g3h3'), Move.from_uci('g5g4'), Move.from_uci('h3g2'), Move.from_uci('f5f4'), Move.from_uci('e3f4'), Move.from_uci('d6f4'), Move.from_uci('a7b5'), Move.from_uci('h7h5'), Move.from_uci('c8c4'), Move.from_uci('f4d2'), Move.from_uci('b5d6'), Move.from_uci('f7e6'), Move.from_uci('d6e4'), Move.from_uci('d2e1'), Move.from_uci('e4f2'), Move.from_uci('e6f6'), Move.from_uci('g2f1'), Move.from_uci('e1f2'), Move.from_uci('f1f2'), Move.from_uci('b4d5'), Move.from_uci('c4c6'), Move.from_uci('f6f7'), Move.from_uci('c6c5'), Move.from_uci('f7e6'), Move.from_uci('f2g3'), Move.from_uci('e6d6'), Move.from_uci('c5c8'), Move.from_uci('d5e7'), Move.from_uci('c8h8'), Move.from_uci('e7f5'), Move.from_uci('g3f4'), Move.from_uci('f5h4'), Move.from_uci('h8h6'), Move.from_uci('d6d7'), Move.from_uci('h6h5'), Move.from_uci('h4g6'), Move.from_uci('f4g4'), Move.from_uci('g6f8'), Move.from_uci('g4f5')]}, {'depth': 30, 'seldepth': 46, 'multipv': 2, 'score': PovScore(Cp(-39), BLACK), 'nodes': 25000816, 'nps': 1029263, 'hashfull': 1000, 'tbhits': 0, 'time': 24.29, 'pv': [Move.from_uci('h7h6')], 'lowerbound': True}] -------------------------------------------------------------------------------- /generator/model.py: -------------------------------------------------------------------------------- 1 | from chess.pgn import GameNode, ChildNode 2 | from chess import Move, Color 3 | from chess.engine import Score 4 | from dataclasses import dataclass 5 | from typing import Tuple, List, Optional 6 | 7 | @dataclass 8 | class Puzzle: 9 | node: ChildNode 10 | moves: List[Move] 11 | cp: int 12 | 13 | @dataclass 14 | class Line: 15 | nb: Tuple[int, int] 16 | letter: str 17 | password: str 18 | 19 | @dataclass 20 | class EngineMove: 21 | move: Move 22 | score: Score 23 | 24 | @dataclass 25 | class NextMovePair: 26 | node: GameNode 27 | winner: Color 28 | best: EngineMove 29 | second: Optional[EngineMove] 30 | 31 | # More than just a TB probing result, since checking if other moves are also winning 32 | @dataclass 33 | class TbPair(NextMovePair): 34 | # `True` if the position is winning and only one move wins 35 | only_winning_move: bool -------------------------------------------------------------------------------- /generator/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2025.1.31 2 | chardet==3.0.4 3 | charset-normalizer==3.4.1 4 | chess==1.3.0 5 | idna==2.10 6 | multidict==6.1.0 7 | propcache==0.3.0 8 | PyYAML==6.0.2 9 | requests==2.32.3 10 | urllib3==2.3.0 11 | vcrpy==7.0.0 12 | wrapt==1.17.2 13 | yarl==1.18.3 14 | zstandard==0.19.0 15 | -------------------------------------------------------------------------------- /generator/server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from chess.pgn import Game, GameNode, ChildNode 3 | from model import Puzzle 4 | import requests 5 | import urllib.parse 6 | from requests.adapters import HTTPAdapter 7 | from requests.packages.urllib3.util.retry import Retry 8 | 9 | retry_strategy = Retry( 10 | total=999999999, 11 | backoff_factor=0.1, 12 | status_forcelist=[429, 500, 502, 503, 504], 13 | allowed_methods=frozenset(['GET', 'POST']) 14 | ) 15 | adapter = HTTPAdapter(max_retries=retry_strategy) 16 | http = requests.Session() 17 | http.mount("https://", adapter) 18 | http.mount("http://", adapter) 19 | 20 | TIMEOUT = 5 21 | 22 | class Server: 23 | 24 | def __init__(self, logger: logging.Logger, url: str, token: str, version: int) -> None: 25 | self.logger = logger 26 | self.url = url 27 | self.token = token 28 | self.version = version 29 | 30 | def is_seen(self, id: str) -> bool: 31 | if not self.url: 32 | return False 33 | try: 34 | status = http.get(self._seen_url(id), timeout = TIMEOUT).status_code 35 | return status == 200 36 | except Exception as e: 37 | self.logger.error(e) 38 | return False 39 | 40 | def set_seen(self, game: Game) -> None: 41 | try: 42 | if self.url: 43 | http.post(self._seen_url(game.headers.get("Site", "?")[20:]), timeout = TIMEOUT) 44 | except Exception as e: 45 | self.logger.error(e) 46 | 47 | def is_seen_pos(self, node: ChildNode) -> bool: 48 | if not self.url: 49 | return False 50 | id = urllib.parse.quote(f"{node.parent.board().fen()}:{node.uci()}") 51 | try: 52 | status = http.get(self._seen_url(id), timeout = TIMEOUT).status_code 53 | return status == 200 54 | except Exception as e: 55 | self.logger.error(e) 56 | return False 57 | 58 | def _seen_url(self, id: str) -> str: 59 | return "{}/seen?token={}&id={}".format(self.url, self.token, id) 60 | 61 | def post(self, game_id: str, puzzle: Puzzle) -> None: 62 | parent = puzzle.node.parent 63 | assert parent 64 | json = { 65 | 'game_id': game_id, 66 | 'fen': parent.board().fen(), 67 | 'ply': parent.ply(), 68 | 'moves': [puzzle.node.uci()] + list(map(lambda m : m.uci(), puzzle.moves)), 69 | 'cp': puzzle.cp, 70 | 'generator_version': self.version, 71 | } 72 | if not self.url: 73 | print(json) 74 | return None 75 | try: 76 | r = http.post("{}/puzzle?token={}".format(self.url, self.token), json=json) 77 | self.logger.info(r.text if r.ok else "FAILURE {}".format(r.text)) 78 | except Exception as e: 79 | self.logger.error("Couldn't post puzzle: {}".format(e)) 80 | -------------------------------------------------------------------------------- /generator/tb.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import time 4 | 5 | import chess 6 | import requests 7 | 8 | from typing import Optional, Literal, Dict, Any 9 | 10 | from chess import Color 11 | from chess.pgn import GameNode 12 | from requests.adapters import HTTPAdapter 13 | from requests.packages.urllib3.util.retry import Retry 14 | 15 | from model import NextMovePair, TbPair, EngineMove 16 | 17 | TB_API = "http://tablebase.lichess.ovh/standard?fen={}" 18 | 19 | 20 | WDL = Literal["win", "draw", "loss", "unknown", "maybe-win", "blessed-loss", "maybe-loss", "cursed-win"] 21 | 22 | RETRY_STRAT = Retry( 23 | total=5, 24 | backoff_factor=1, 25 | status_forcelist=[429, 500, 502, 503, 504], 26 | allowed_methods=["GET"] 27 | ) 28 | ADAPTER = HTTPAdapter(max_retries=RETRY_STRAT) 29 | 30 | class TbChecker: 31 | 32 | def __init__(self, log: logging.Logger) -> None: 33 | self.session = requests.Session() 34 | self.session.mount("http://", ADAPTER) 35 | self.session.mount("https://", ADAPTER) 36 | self.log = log 37 | self.last_req: Optional[datetime.datetime] = None 38 | 39 | def _probe(self, fen: str) -> Dict[str, Any]: 40 | if self.last_req is not None: 41 | wait = (self.last_req + datetime.timedelta(milliseconds=550) - datetime.datetime.now()).total_seconds() 42 | if wait > 0: 43 | time.sleep(wait) 44 | resp = self.session.get(TB_API.format(fen),timeout=5).json() 45 | # conservative to take last_req after the end of the request 46 | self.last_req = datetime.datetime.now() 47 | return resp 48 | 49 | # `*` is used to force kwarg only for `looking_for_mate` 50 | def get_only_winning_move(self, node: GameNode, winner: Color, *,looking_for_mate: bool) -> Optional[TbPair]: 51 | """ 52 | Returns `None` if the check is not applicable: 53 | - The position has more than 7 pieces. 54 | - The puzzle is a mate puzzle. DTZ does not garantee the fastest mate. Also a mate in N puzzle 55 | can be correct even if there also exists a N+1 mate. 56 | - It's not `winner`'s turn. 57 | - There is no legal moves 58 | - There is an error processing the API result, or if the API is unreachable. 59 | """ 60 | if looking_for_mate: 61 | return None 62 | board = node.board() 63 | if len(chess.SquareSet(board.occupied)) > 7 or board.turn != winner: 64 | return None 65 | fen = board.fen() 66 | try: 67 | rep = self._probe(fen) 68 | except requests.exceptions.RequestException as e: 69 | self.log.warning(f"req error while checking tb for fen {fen}: {e}") 70 | return None 71 | # The API return results in descending order (best move firsts) 72 | # So only checking for the first two moves should be enough to know 73 | # if there are more than one winning move. 74 | moves = rep.get("moves", []) 75 | if not moves: 76 | # No legal moves 77 | return None 78 | best = to_engine_move(moves[0], turn=not board.turn, winner=winner) 79 | second = None 80 | second_winning = False 81 | if len(moves) > 1: 82 | # from opponent's perspective 83 | second_winning = moves[1]["category"] in ["loss", "maybe-loss"] 84 | second = to_engine_move(moves[1], turn=not board.turn, winner=winner) 85 | only_winning_move = rep["category"] == "win" and not second_winning 86 | self.log.debug(f"tb check for {fen}, best move: {best}, second move: {second}, only winning move: {only_winning_move}") 87 | return TbPair(node=node, winner=winner, best=best, second=second,only_winning_move=only_winning_move) 88 | 89 | def to_engine_move(move: Dict[str, Any], *,turn: Color, winner: Color) -> EngineMove: 90 | pov_score = chess.engine.PovScore(relative=wdl_to_cp(move["category"]),turn=turn) 91 | return EngineMove(chess.Move.from_uci(move["uci"]), pov_score.pov(winner)) 92 | 93 | # conservative, because considering maybe-win as a draw, and maybe-loss as a loss 94 | def wdl_to_cp(wdl: WDL) -> chess.engine.Cp: 95 | if wdl == "win": 96 | return chess.engine.Cp(999999998) 97 | # using `or` for mypy 98 | elif wdl == "maybe-win" or wdl == "cursed-win" or wdl == "draw" or wdl == "blessed-loss": 99 | return chess.engine.Cp(0) 100 | # using `or` for mypy 101 | elif wdl == "unknown" or wdl == "maybe-loss" or wdl == "loss": 102 | return chess.engine.Cp(-999999998) 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /generator/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import logging 3 | import zlib 4 | import chess 5 | from model import Puzzle, NextMovePair, EngineMove, TbPair 6 | from pathlib import Path 7 | from generator import logger 8 | from server import Server 9 | from tb import TbChecker, TB_API 10 | from chess.engine import SimpleEngine, Mate, Cp, Score, PovScore, InfoDict 11 | from chess import Move, Color, Board, WHITE, BLACK 12 | from chess.pgn import Game, GameNode 13 | from vcr.unittest import VCRTestCase # type: ignore 14 | from typing import List, Optional, Tuple, Literal, Union 15 | 16 | from generator import Generator, Server, make_engine 17 | 18 | class CachedEngine(SimpleEngine): 19 | 20 | 21 | def __init__(self, *args, **kwargs): 22 | super().__init__(*args, **kwargs) 23 | self.used_checksums = set() 24 | # named after cassettes in VCR 25 | self.diskette_dir = Path("diskettes") 26 | self.diskette_dir.mkdir(exist_ok=True) 27 | 28 | # a more general implementation should use the `inspect` module and `Signature.bind` 29 | def analyse(self, board: Board, multipv: int, limit: chess.engine.Limit) -> Union[List[InfoDict], InfoDict]: 30 | checksum_arg = f"{board.fen()} {multipv} {limit}".encode() 31 | checksum = zlib.adler32(checksum_arg) 32 | self.used_checksums.add(checksum) 33 | path = self.diskette_dir / f"{checksum}.py" 34 | print(f"checksum of args {checksum_arg}, is {checksum}") 35 | if path.exists(): 36 | with open(path) as f: 37 | return eval(f.read()) 38 | res = super().analyse(board=board,multipv=multipv,limit=limit) 39 | with open(path, "w") as f: 40 | f.write(f"#{checksum_arg}\n") 41 | f.write(str(res)) 42 | return res 43 | 44 | def list_unused_evals(self) -> List[int]: 45 | # list all files in the diskette directory 46 | return [int(x.stem) for x in self.diskette_dir.iterdir() if int(x.stem) not in self.used_checksums] 47 | 48 | class TestGenerator(VCRTestCase): 49 | 50 | @classmethod 51 | def setUpClass(cls): 52 | super().setUpClass() 53 | cls.engine = CachedEngine.popen_uci("stockfish") 54 | cls.engine.configure({'Threads': 6}) # don't use more than 6 threads! it fails at finding mates 55 | cls.server = Server(logger, "", "", 0) 56 | cls.gen = Generator(cls.engine, cls.server) 57 | logger.setLevel(logging.DEBUG) 58 | 59 | def test_puzzle_1(self) -> None: 60 | # https://lichess.org/analysis/standard/3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8_w_-_-_5_57#112 61 | self.get_puzzle("3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8 w - - 5 57", 62 | Cp(-1000), "h5g6", Mate(2), "d8g5 g6h7 g5g7") 63 | 64 | def test_puzzle_3(self) -> None: 65 | # https://lichess.org/wQptHOX6/white#61 66 | self.get_puzzle("1r4k1/5p1p/pr1p2p1/q2Bb3/2P5/P1R3PP/KB1R1Q2/8 b - - 1 31", 67 | Cp(-4), "e5c3", Mate(3), "f2f7 g8h8 f7f6 c3f6 b2f6") 68 | 69 | # can't be done because there are 2 possible defensive moves 70 | def test_puzzle_5(self) -> None: 71 | # https://lichess.org/2YRgIXwk/black#32 72 | self.get_puzzle("r1b3k1/pp3p1p/2pp2p1/8/2P2q2/2N1r2P/PP2BPP1/R2Q1K1R w - - 0 17", 73 | Cp(-520), "d1d2", Cp(410), "e3h3 h1h3 f4d2") 74 | 75 | # https://lichess.org/YCjcYuK6#81 76 | # def test_puzzle_7(self) -> None: 77 | # self.get_puzzle("7r/1k6/pPp1qp2/2Q3p1/P4p2/5P2/5KP1/1RR4r b - - 5 41", 78 | # Cp(-1500), "e6a2", Cp(530), "c1c2 a2e6 b1h1 h8h1 c2e2 e6d7 e2e7 d7e7 c5e7") 79 | 80 | # r1bq3r/pppp1kpp/2n5/2b1P1N1/3p2n1/2P5/P4PPP/RNBQ1RK1 b - - 1 10 81 | # def test_puzzle_8(self) -> None: 82 | # self.get_puzzle("r1bq3r/pppp1kpp/2n5/2b1P1N1/3p2n1/2P5/P4PPP/RNBQ1RK1 b - - 1 10", 83 | # Cp(0), "f7g8", Mate(4), "d1b3 d7d5 e5d6 c8e6 b3e6 g8f8 e6f7") 84 | 85 | def test_puzzle_9(self) -> None: 86 | self.get_puzzle("7k/p3r1bP/1p1rp2q/8/2PBB3/4P3/P3KQ2/6R1 b - - 0 38", 87 | Cp(-110), "e6e5", Mate(2), "f2f8 g7f8 g1g8") 88 | 89 | # https://lichess.org/ejvEklSH/black#50 90 | def test_puzzle_10(self) -> None: 91 | self.get_puzzle("5rk1/pp3p2/1q1R3p/6p1/5pBb/2P4P/PPQ2PP1/3Rr1K1 w - - 6 26", 92 | Cp(-450), "g1h2", Mate(2), "h4g3 f2g3 b6g1") 93 | 94 | def test_puzzle_16(self): 95 | self.get_puzzle("kr6/p5pp/Q4np1/3p4/6P1/2P1qP2/PK4P1/3R3R w - - 1 26", 96 | Cp(-30), "b2a1", Mate(1), "e3c3") 97 | 98 | # one mover 99 | # def test_puzzle_17(self): 100 | # self.get_puzzle("6k1/Q4pp1/8/6p1/3pr3/4q2P/P1P3P1/3R3K w - - 0 31", 101 | # Cp(0), "d1d3", Cp(2000), "e3c1") 102 | 103 | def test_not_puzzle_1(self) -> None: 104 | # https://lichess.org/LywqL7uc#32 105 | self.not_puzzle("r2q1rk1/1pp2pp1/p4n1p/b1pP4/4PB2/P3RQ2/1P3PPP/RN4K1 w - - 1 17", 106 | Cp(-230), "b1c3", Cp(160)) 107 | 108 | def test_not_puzzle_2(self) -> None: 109 | # https://lichess.org/TIH1K2BQ#51 110 | self.not_puzzle("5b1r/kpQ2ppp/4p3/4P3/1P4q1/8/P3N3/1nK2B2 b - - 0 26", 111 | Cp(-1520), "b1a3", Cp(0)) 112 | 113 | # def test_not_puzzle_3(self) -> None: 114 | # https://lichess.org/StRzB2gY#59 115 | # self.not_puzzle("7k/p6p/4p1p1/8/1q1p3P/2r1P1P1/P4Q2/5RK1 b - - 1 30", 116 | # Cp(0), "d4e3", Cp(580)) 117 | 118 | def test_not_puzzle_4(self) -> None: 119 | self.not_puzzle("r2qk2r/p1p1bppp/1p1ppn2/8/2PP1B2/3Q1N2/PP3PPP/3RR1K1 b kq - 6 12", 120 | Cp(-110), "h7h6", Cp(150)) 121 | 122 | # https://lichess.org/ynAkXFBr/black#92 123 | def test_not_puzzle_5(self) -> None: 124 | self.not_puzzle("4Rb2/N4k1p/8/5pp1/1n2p2P/4P1K1/3P4/8 w - - 1 47", 125 | Cp(-40), "e8c8", Cp(610)) 126 | 127 | def test_not_puzzle_6(self) -> None: 128 | self.not_puzzle("5r1k/1Q3p2/5q1p/8/2P4p/1P4P1/P4P2/R4RK1 w - - 0 29", 129 | Cp(-1020), "g3h4", Cp(0)) 130 | 131 | # https://lichess.org/N99i0nfU#11 132 | def test_not_puzzle_7(self): 133 | self.not_puzzle("rnb1k1nr/ppp2p1p/3p1qp1/2b1p3/2B1P3/2NP1Q2/PPP2PPP/R1B1K1NR b KQkq - 1 6", 134 | Cp(-50), "c8g4", Cp(420)) 135 | 136 | def test_not_puzzle_8(self): 137 | self.not_puzzle("r1bq1rk1/pp1nbppp/4p3/3pP3/8/1P1B4/PBP2PPP/RN1Q1RK1 w - - 1 11", 138 | Cp(-40), "d3h7", Cp(380)) 139 | 140 | def test_not_puzzle_9(self): 141 | self.not_puzzle("5k2/5ppp/2r1p3/1p6/1b1R4/p1n1P1P1/B4PKP/1N6 w - - 2 34", 142 | Cp(0), "b1c3", Cp(520)) 143 | 144 | def test_not_puzzle_10(self): 145 | self.not_puzzle("2Qr3k/p2P2p1/2p1n3/4n1p1/8/4q1P1/PP2P2P/R4R1K w - - 0 33", 146 | Cp(100), "c8d8", Cp(500)) 147 | 148 | def test_not_puzzle_11(self) -> None: 149 | self.not_puzzle("2kr3r/ppp2pp1/1b6/1P2p3/4P3/P2B2P1/2P2PP1/R4RK1 w - - 0 18", 150 | Cp(20), "f1d1", Mate(4)) 151 | 152 | def test_not_puzzle_12(self): 153 | self.not_puzzle("5r1k/1Q3p2/5q1p/8/2P4p/1P4P1/P4P2/R4RK1 w - - 0 29", 154 | Cp(-1010), "g3h4", Cp(0)) 155 | 156 | # https://lichess.org/oKiQW6Wn/black#86 157 | def test_not_puzzle_13(self): 158 | self.not_puzzle("8/5p1k/4p1p1/4Q3/3Pp1Kp/4P2P/5qP1/8 w - - 2 44", 159 | Cp(-6360), "e5e4", Mate(1)) 160 | 161 | def test_not_puzzle_14(self) -> None: 162 | # https://lichess.org/nq1x9tln/black#76 163 | self.not_puzzle("3R4/1Q2nk2/4p2p/4n3/BP3ppP/P7/5PP1/2r3K1 w - - 2 39", 164 | Cp(-1000), "g1h2", Mate(4)) 165 | 166 | def test_not_puzzle_15(self) -> None: 167 | # https://lichess.org/nq1x9tln/black#76 168 | self.not_puzzle("3r4/8/2p2n2/7k/P1P4p/1P6/2K5/6R1 w - - 0 43", 169 | Cp(-1000), "b3b4", Mate(4)) 170 | 171 | def test_not_puzzle_16(self) -> None: 172 | self.not_puzzle("8/Pkp3pp/8/4p3/1P2b3/4K3/1P3r1P/R7 b - - 1 30", 173 | Cp(0), "f2f3", Cp(5000)) 174 | 175 | def test_not_puzzle_17(self) -> None: 176 | with open("test_pgn_3fold_uDMCM.pgn") as pgn: 177 | game = chess.pgn.read_game(pgn) 178 | puzzle = self.gen.analyze_game(game, tier=10) # type: ignore 179 | self.assertEqual(puzzle, None) 180 | 181 | def test_not_puzzle_tb_1(self) -> None: 182 | # Adapted from 183 | # sewAW,8/8/5p2/3kp3/p5P1/P2K1P2/8/8 w - - 0 44,d3e3 d5c4 e3e4 c4b3 e4f5 b3a3 f5f6 e5e4,2540,83,100,96,crushing endgame master pawnEndgame veryLong,https://lichess.org/1GTYrLgF#87, 184 | # when not using the tb, the generated puzzle at the time was one move too long, because `e5e4` is not the only winning move 185 | self.get_puzzle("8/8/5p2/3kp3/p5P1/P2K1P2/8/8 w - - 0 44", Cp(0), "d3e3", Cp(400), "d5c4 e3e4 c4b3 e4f5 b3a3") 186 | 187 | def get_puzzle(self, fen: str, prev_score: Score, move: str, current_score: Score, moves: str) -> None: 188 | board = Board(fen) 189 | game = Game.from_board(board) 190 | node = game.add_main_variation(Move.from_uci(move)) 191 | current_eval = PovScore(current_score, not board.turn) 192 | result = self.gen.analyze_position(node, prev_score, current_eval, tier=10) # type: ignore 193 | self.assert_is_puzzle_with_moves(result, [Move.from_uci(x) for x in moves.split()]) 194 | 195 | 196 | def not_puzzle(self, fen: str, prev_score: Score, move: str, current_score: Score) -> None: 197 | board = Board(fen) 198 | game = Game.from_board(board) 199 | node = game.add_main_variation(Move.from_uci(move)) 200 | current_eval = PovScore(current_score, not board.turn) 201 | result = self.gen.analyze_position( node, prev_score, current_eval, tier=10) # type: ignore 202 | self.assertIsInstance(result, Score) 203 | 204 | 205 | def assert_is_puzzle_with_moves(self, puzzle: Union[Puzzle, Score], moves: List[Move]) -> None: 206 | self.assertIsInstance(puzzle, Puzzle) 207 | if isinstance(puzzle, Puzzle): 208 | self.assertEqual(puzzle.moves, moves) 209 | 210 | @classmethod 211 | def tearDownClass(cls): 212 | unused_evals = cls.engine.list_unused_evals() 213 | print(f"unused evals: {unused_evals}") 214 | cls.engine.close() 215 | 216 | class TestTbChecker(VCRTestCase): 217 | 218 | def test_not_applicable_too_many_pieces(self) -> None: 219 | checker = TbChecker(logger) 220 | node = chess.pgn.Game() 221 | tb_pair = checker.get_only_winning_move(node, WHITE, looking_for_mate=False) 222 | self.assertIsNone(tb_pair) 223 | 224 | def test_not_applicable_mate_puzzle(self) -> None: 225 | checker = TbChecker(logger) 226 | fen = "4k3/8/8/8/8/8/3PPPPP/4K3 w - - 0 1" 227 | node = chess.pgn.Game.from_board(Board(fen=fen)) 228 | tb_pair = checker.get_only_winning_move(node, WHITE, looking_for_mate=True) 229 | self.assertIsNone(tb_pair) 230 | 231 | def test_multiple_winning_moves(self) -> None: 232 | checker = TbChecker(logger) 233 | fen = "4k3/8/8/8/8/8/3PPPPP/4K3 w - - 0 1" 234 | node = chess.pgn.Game.from_board(Board(fen=fen)) 235 | tb_pair = checker.get_only_winning_move(node, WHITE, looking_for_mate=False) 236 | self.assertIsInstance(tb_pair, TbPair) 237 | # for mypy 238 | assert isinstance(tb_pair, TbPair) 239 | self.assertFalse(tb_pair.only_winning_move) 240 | 241 | def test_correct_best_move(self) -> None: 242 | """The position allow for only one good move, but it is not chosen by the engine for some reason""" 243 | checker = TbChecker(logger) 244 | fen = "5K2/8/7p/6P1/1p5P/k7/8/8 w - - 0 49" 245 | node = chess.pgn.Game.from_board(Board(fen=fen)) 246 | tb_pair = checker.get_only_winning_move(node, WHITE, looking_for_mate=False) 247 | expected = TbPair( 248 | node=node, 249 | winner=WHITE, 250 | best=EngineMove(Move.from_uci("g5h6"), Cp(999999998)), 251 | second=EngineMove(move=Move.from_uci('h4h5'), score=Cp(0)), 252 | only_winning_move=True 253 | ) 254 | self.assertEqual(tb_pair, expected) 255 | 256 | 257 | def test_correct_best_move_promotion(self) -> None: 258 | """The position allow for only one good promotion (Queen), but it is not chosen by the engine for some reason""" 259 | checker = TbChecker(logger) 260 | fen = "5K2/7P/8/8/7P/k7/1p6/8 w - - 0 51" 261 | node = chess.pgn.Game.from_board(Board(fen=fen)) 262 | tb_pair = checker.get_only_winning_move(node, WHITE, looking_for_mate=False) 263 | expected = TbPair( 264 | node=node, 265 | winner=WHITE, 266 | best=EngineMove(Move.from_uci("h7h8q"), Cp(999999998)), 267 | second=EngineMove(move=Move.from_uci('h4h5'), score=Cp(0)), 268 | only_winning_move=True 269 | ) 270 | self.assertEqual(tb_pair, expected) 271 | 272 | 273 | if __name__ == '__main__': 274 | unittest.main() 275 | -------------------------------------------------------------------------------- /generator/test_pgn_3fold_uDMCM.pgn: -------------------------------------------------------------------------------- 1 | [Event "Rated Blitz game"] 2 | [Site "https://lichess.org/ZlCTzfMG"] 3 | [Date "2021.06.28"] 4 | [White "genassien"] 5 | [Black "Freiheitskaempfer"] 6 | [Result "1-0"] 7 | [UTCDate "2021.06.28"] 8 | [UTCTime "18:21:43"] 9 | [WhiteElo "2370"] 10 | [BlackElo "2441"] 11 | [WhiteRatingDiff "+7"] 12 | [BlackRatingDiff "-7"] 13 | [WhiteTitle "FM"] 14 | [BlackTitle "FM"] 15 | [Variant "Standard"] 16 | [TimeControl "300+0"] 17 | [ECO "C24"] 18 | [Opening "Bishop's Opening: Berlin Defense"] 19 | [Termination "Time forfeit"] 20 | [Annotator "lichess.org"] 21 | 22 | 1. e4 { [%eval 0.24] [%clk 0:05:00] } 1... e5 { [%eval 0.2] [%clk 0:05:00] } 2. Bc4 { [%eval 0.0] [%clk 0:04:58] } 2... Nf6 { [%eval 0.0] [%clk 0:04:59] } { C24 Bishop's Opening: Berlin Defense } 3. d3 { [%eval 0.0] [%clk 0:04:53] } 3... c6 { [%eval 0.0] [%clk 0:04:58] } 4. Nf3 { [%eval 0.0] [%clk 0:04:52] } 4... d5 { [%eval 0.02] [%clk 0:04:58] } 5. Bb3 { [%eval 0.0] [%clk 0:04:51] } 5... Bb4+ { [%eval 0.05] [%clk 0:04:57] } 6. c3 { [%eval 0.05] [%clk 0:04:45] } 6... Bd6 { [%eval 0.09] [%clk 0:04:56] } 7. Nbd2 { [%eval -0.35] [%clk 0:04:33] } 7... Nbd7 { [%eval -0.24] [%clk 0:04:54] } 8. O-O { [%eval -0.14] [%clk 0:04:26] } 8... O-O { [%eval -0.04] [%clk 0:04:50] } 9. Re1 { [%eval -0.33] [%clk 0:04:25] } 9... dxe4 { [%eval -0.19] [%clk 0:04:42] } 10. dxe4 { [%eval -0.01] [%clk 0:04:16] } 10... Qe7 { [%eval 0.0] [%clk 0:04:35] } 11. Nc4 { [%eval -0.08] [%clk 0:04:14] } 11... Bc7 { [%eval 0.0] [%clk 0:04:34] } 12. Ne3? { (0.00 → -1.25) Mistake. a4 was best. } { [%eval -1.25] [%clk 0:04:13] } (12. a4 Rd8 13. Qe2 Nb6 14. Na5 Nbd7) 12... Nc5 { [%eval -1.25] [%clk 0:04:28] } 13. Bc2 { [%eval -1.12] [%clk 0:04:08] } 13... Ncxe4 { [%eval -0.98] [%clk 0:04:23] } 14. Nc4?! { (-0.98 → -1.74) Inaccuracy. Nf1 was best. } { [%eval -1.74] [%clk 0:03:49] } (14. Nf1 Nc5 15. b4 Ncd7 16. Ng3 Re8 17. a4 Nb6 18. h3 Nbd5 19. Bd2 h6 20. b5 Qc5) 14... Qc5?! { (-1.74 → -0.99) Inaccuracy. Bf5 was best. } { [%eval -0.99] [%clk 0:03:42] } (14... Bf5 15. Ng5) 15. Bxe4?! { (-0.99 → -1.80) Inaccuracy. Qe2 was best. } { [%eval -1.8] [%clk 0:03:13] } (15. Qe2 Bf5) 15... Qxc4 { [%eval -1.69] [%clk 0:03:40] } 16. Bc2?! { (-1.69 → -2.37) Inaccuracy. Bd3 was best. } { [%eval -2.37] [%clk 0:03:06] } (16. Bd3) 16... Bg4?! { (-2.37 → -1.36) Inaccuracy. Rd8 was best. } { [%eval -1.36] [%clk 0:03:15] } (16... Rd8 17. Nd2 Qh4 18. Qe2 Nd5 19. Nf3 Qh5 20. a4 a5 21. h3 f6 22. Bd2 Qf7 23. c4) 17. Bg5 { [%eval -1.16] [%clk 0:03:01] } 17... Rad8 { [%eval -0.83] [%clk 0:03:08] } 18. Qb1 { [%eval -1.1] [%clk 0:02:54] } 18... Bxf3 { [%eval -1.38] [%clk 0:02:53] } 19. Bxf6?? { (-1.38 → Mate in 6) Checkmate is now unavoidable. gxf3 was best. } { [%eval #-6] [%clk 0:02:53] } (19. gxf3) 19... gxf6?? { (Mate in 6 → -3.06) Lost forced checkmate sequence. Qg4 was best. } { [%eval -3.06] [%clk 0:02:50] } (19... Qg4 20. Bxh7+ Kh8 21. Qg6 fxg6 22. g3 Qh3 23. Bxg7+ Kxg7 24. a3 Qg2#) 20. Bxh7+? { (-3.06 → -5.75) Mistake. gxf3 was best. } { [%eval -5.75] [%clk 0:02:50] } (20. gxf3 Kh8 21. Re4 Rg8+ 22. Kh1 Qd5 23. Bb3 Qd2 24. Rh4 Rg7 25. Rg4 Rxg4 26. fxg4 Qxf2) 20... Kg7 { [%eval -6.06] [%clk 0:02:47] } 21. gxf3 { [%eval -6.09] [%clk 0:02:42] } 21... Qh4 { [%eval -5.99] [%clk 0:02:34] } 22. Be4?! { (-5.99 → -10.07) Inaccuracy. Qf5 was best. } { [%eval -10.07] [%clk 0:02:38] } (22. Qf5 Qxh7 23. Qxh7+ Kxh7 24. Rad1 Kg6 25. Kf1 Rh8 26. Rxd8 Rxd8 27. h3 f5 28. Ke2 Rh8) 22... Rh8 { [%eval -7.71] [%clk 0:02:29] } 23. Kf1 { [%eval -8.01] [%clk 0:01:43] } 23... Rd2 { [%eval -6.46] [%clk 0:02:25] } 24. Re2 { [%eval -6.29] [%clk 0:01:42] } 24... Rhd8 { [%eval -6.04] [%clk 0:02:13] } 25. Rxd2 { [%eval -6.4] [%clk 0:01:32] } 25... Rxd2 { [%eval -6.33] [%clk 0:02:11] } 26. Qe1 { [%eval -6.46] [%clk 0:01:31] } 26... Rxb2 { [%eval -6.73] [%clk 0:02:07] } 27. Rb1 { [%eval -7.46] [%clk 0:01:27] } 27... Qh3+?? { (-7.46 → -2.67) Blunder. Rxa2 was best. } { [%eval -2.67] [%clk 0:01:56] } (27... Rxa2) 28. Kg1 { [%eval -2.84] [%clk 0:01:25] } 28... Rxb1?? { (-2.84 → -0.88) Blunder. Rxa2 was best. } { [%eval -0.88] [%clk 0:01:42] } (28... Rxa2 29. Qf1 Qxf1+ 30. Kxf1 f5 31. Bxf5 Bb6 32. Kg2 Kf6 33. Bc8 Rxf2+ 34. Kg3 Rc2 35. Bxb7) 29. Qxb1 { [%eval -0.77] [%clk 0:01:24] } 29... Bb6 { [%eval -0.75] [%clk 0:01:37] } 30. Qf1 { [%eval -0.91] [%clk 0:01:10] } 30... Qh6?! { (-0.91 → -0.30) Inaccuracy. Qe6 was best. } { [%eval -0.3] [%clk 0:01:31] } (30... Qe6 31. Qg2+ Kf8 32. Qg4 Ke7 33. c4 Kd8 34. Qf5 Kc7 35. h4 Bc5 36. f4 Qxc4 37. fxe5) 31. Qg2+ { [%eval -0.3] [%clk 0:00:56] } 31... Kf8 { [%eval -0.3] [%clk 0:01:29] } 32. Qg4 { [%eval -0.31] [%clk 0:00:53] } 32... Qc1+ { [%eval -0.12] [%clk 0:00:57] } 33. Kg2 { [%eval -0.17] [%clk 0:00:52] } 33... Qd2 { [%eval -0.01] [%clk 0:00:39] } 34. Qc8+ { [%eval 0.0] [%clk 0:00:48] } 34... Ke7 { [%eval 0.0] [%clk 0:00:38] } 35. Qxb7+ { [%eval 0.0] [%clk 0:00:47] } 35... Kf8 { [%eval 0.0] [%clk 0:00:35] } 36. Qc8+ { [%eval 0.0] [%clk 0:00:45] } 36... Ke7 { [%eval 0.0] [%clk 0:00:33] } 37. Bxc6 { [%eval 0.0] [%clk 0:00:37] } 37... Qxf2+ { [%eval 0.0] [%clk 0:00:32] } 38. Kh3 { [%eval 0.0] [%clk 0:00:35] } 38... Qf1+ { [%eval 0.0] [%clk 0:00:31] } 39. Kg4?? { (0.00 → Mate in 3) Checkmate is now unavoidable. Kh4 was best. } { [%eval #-3] [%clk 0:00:31] } (39. Kh4 Qc4+ 40. Kh5 Qxa2 41. Qd7+ Kf8 42. Qc8+) 39... Qg2+ { [%eval #-2] [%clk 0:00:30] } 40. Kf5 { [%eval #-1] [%clk 0:00:26] } 40... Qg5+ { [%eval #-2] [%clk 0:00:28] } 41. Ke4 { [%eval #-2] [%clk 0:00:25] } 41... Qe3+ { [%eval #-1] [%clk 0:00:27] } 42. Kd5 { [%eval #-1] [%clk 0:00:23] } 42... Qc5+ { [%eval #-2] [%clk 0:00:25] } 43. Ke4 { [%eval #-2] [%clk 0:00:23] } 43... Qe3+ { [%eval #-1] [%clk 0:00:22] } 44. Kd5 { [%eval #-1] [%clk 0:00:22] } 44... Qc5+ { [%eval #-2] [%clk 0:00:21] } 45. Ke4 { [%eval #-2] [%clk 0:00:21] } 45... f5+?? { (Mate in 2 → 0.00) Lost forced checkmate sequence. Qc4+ was best. } { [%eval 0.0] [%clk 0:00:17] } (45... Qc4+ 46. Kf5 Qf4#) 46. Kxf5 { [%eval 0.0] [%clk 0:00:20] } 46... e4+ { [%eval 0.0] [%clk 0:00:16] } 47. Kxe4 { [%eval 0.0] [%clk 0:00:17] } 47... Qe3+ { [%eval 0.0] [%clk 0:00:16] } 48. Kf5 { [%eval 0.0] [%clk 0:00:15] } 48... Qd3+ { [%eval 0.02] [%clk 0:00:09] } 49. Be4 { [%eval 0.0] [%clk 0:00:13] } 49... Qd6 { [%eval 0.03] [%clk 0:00:05] } 50. Qb7+ { [%eval 0.11] [%clk 0:00:09] } 50... Bc7 { [%eval 0.17] [%clk 0:00:04] } 51. Qb4 { [%eval 0.21] [%clk 0:00:07] } 51... Qxb4 { [%eval 0.16] [%clk 0:00:02] } 52. cxb4 { [%eval 0.21] [%clk 0:00:07] } 52... Bxh2 { [%eval 0.13] [%clk 0:00:01] } 53. Kg4 { [%eval 0.21] [%clk 0:00:07] } 53... Bd6 { [%eval 0.09] [%clk 0:00:00] } 54. f4 { [%eval -0.04] [%clk 0:00:07] } 54... Bxb4 { [%eval -0.04] [%clk 0:00:00] } 55. Kf5 { [%eval -0.03] [%clk 0:00:07] } 55... Bd6 { [%eval 0.0] [%clk 0:00:00] } 56. Kg4 { [%eval 0.0] [%clk 0:00:06] } 56... f6 { [%eval 0.0] [%clk 0:00:00] } 57. f5 { [%eval 0.0] [%clk 0:00:04] } 57... Bc5 { [%eval 0.0] [%clk 0:00:00] } 58. a3 { [%eval -0.12] [%clk 0:00:04] } 58... Bd6 { [%eval 0.0] [%clk 0:00:00] } 59. a4 { [%eval 0.0] [%clk 0:00:04] } { White wins on time. } 1-0 23 | 24 | 25 | -------------------------------------------------------------------------------- /generator/util.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | import math 3 | import chess 4 | import chess.engine 5 | from model import EngineMove, NextMovePair 6 | from chess import Color, Board 7 | from chess.pgn import GameNode 8 | from chess.engine import SimpleEngine, Score 9 | from typing import Optional 10 | 11 | nps = [] 12 | 13 | def material_count(board: Board, side: Color) -> int: 14 | values = { chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9 } 15 | return sum(len(board.pieces(piece_type, side)) * value for piece_type, value in values.items()) 16 | 17 | def material_diff(board: Board, side: Color) -> int: 18 | return material_count(board, side) - material_count(board, not side) 19 | 20 | def is_up_in_material(board: Board, side: Color) -> bool: 21 | return material_diff(board, side) > 0 22 | 23 | def maximum_castling_rights(board: chess.Board) -> chess.Bitboard: 24 | return ( 25 | (board.pieces_mask(chess.ROOK, chess.WHITE) & (chess.BB_A1 | chess.BB_H1) if board.king(chess.WHITE) == chess.E1 else chess.BB_EMPTY) | 26 | (board.pieces_mask(chess.ROOK, chess.BLACK) & (chess.BB_A8 | chess.BB_H8) if board.king(chess.BLACK) == chess.E8 else chess.BB_EMPTY) 27 | ) 28 | 29 | 30 | def get_next_move_pair(engine: SimpleEngine, node: GameNode, winner: Color, limit: chess.engine.Limit) -> NextMovePair: 31 | info = engine.analyse(node.board(), multipv = 2, limit = limit) 32 | global nps 33 | nps.append(info[0]["nps"] / 1000) 34 | nps = nps[-10000:] 35 | # print(info) 36 | best = EngineMove(info[0]["pv"][0], info[0]["score"].pov(winner)) 37 | second = EngineMove(info[1]["pv"][0], info[1]["score"].pov(winner)) if len(info) > 1 else None 38 | return NextMovePair(node, winner, best, second) 39 | 40 | def avg_knps(): 41 | global nps 42 | return round(sum(nps) / len(nps)) if nps else 0 43 | 44 | def win_chances(score: Score) -> float: 45 | """ 46 | winning chances from -1 to 1 https://graphsketch.com/?eqn1_color=1&eqn1_eqn=100+*+%282+%2F+%281+%2B+exp%28-0.004+*+x%29%29+-+1%29&eqn2_color=2&eqn2_eqn=&eqn3_color=3&eqn3_eqn=&eqn4_color=4&eqn4_eqn=&eqn5_color=5&eqn5_eqn=&eqn6_color=6&eqn6_eqn=&x_min=-1000&x_max=1000&y_min=-100&y_max=100&x_tick=100&y_tick=10&x_label_freq=2&y_label_freq=2&do_grid=0&do_grid=1&bold_labeled_lines=0&bold_labeled_lines=1&line_width=4&image_w=850&image_h=525 47 | """ 48 | mate = score.mate() 49 | if mate is not None: 50 | return 1 if mate > 0 else -1 51 | 52 | cp = score.score() 53 | MULTIPLIER = -0.00368208 # https://github.com/lichess-org/lila/pull/11148 54 | return 2 / (1 + math.exp(MULTIPLIER * cp)) - 1 if cp is not None else 0 55 | 56 | def time_control_tier(line: str) -> Optional[int]: 57 | if not line.startswith("[TimeControl "): 58 | return None 59 | try: 60 | seconds, increment = line[1:][:-2].split()[1].replace("\"", "").split("+") 61 | total = int(seconds) + int(increment) * 40 62 | if total >= 480: 63 | return 3 64 | if total >= 180: 65 | return 2 66 | if total > 60: 67 | return 1 68 | return 0 69 | except: 70 | return 0 71 | 72 | def count_mates(board:chess.Board) -> int: 73 | mates = 0 74 | for move in board.legal_moves: 75 | board.push(move) 76 | if board.is_checkmate(): 77 | mates += 1 78 | board.pop() 79 | return mates 80 | 81 | def rating_tier(line: str) -> Optional[int]: 82 | if not line.startswith("[WhiteElo ") and not line.startswith("[BlackElo "): 83 | return None 84 | try: 85 | rating = int(line[11:15]) 86 | if rating > 1750: 87 | return 3 88 | if rating > 1600: 89 | return 2 90 | if rating > 1500: 91 | return 1 92 | return 0 93 | except: 94 | return 0 95 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "executionEnvironments": [ 3 | { 4 | "root": "tagger" 5 | }, 6 | { 7 | "root": "generator" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tagger/README.md: -------------------------------------------------------------------------------- 1 | 2 | ``` 3 | const toMake = id => {p=db.puzzle2.findOne({_id:id}); return `make("${id}", "${p.fen}", "${p.moves.join(' ')}")`} 4 | const toTest = id => `self.assertTrue(cook.test(${toMake(id)}))` 5 | ``` 6 | -------------------------------------------------------------------------------- /tagger/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from chess.pgn import Game, ChildNode 3 | from chess import Color 4 | from typing import List, Literal, Optional 5 | 6 | TagKind = Literal[ 7 | "advancedPawn", 8 | "advantage", 9 | "anastasiaMate", 10 | "arabianMate", 11 | "attackingF2F7", 12 | "attraction", 13 | "backRankMate", 14 | "bishopEndgame", 15 | "bodenMate", 16 | "capturingDefender", 17 | "castling", 18 | "clearance", 19 | "coercion", 20 | "crushing", 21 | "defensiveMove", 22 | "discoveredAttack", 23 | "deflection", 24 | "doubleBishopMate", 25 | "doubleCheck", 26 | "dovetailMate", 27 | "equality", 28 | "enPassant", 29 | "exposedKing", 30 | "fork", 31 | "hangingPiece", 32 | "hookMate", 33 | "interference", 34 | "intermezzo", 35 | "kingsideAttack", 36 | "knightEndgame", 37 | "long", 38 | "mate", 39 | "mateIn5", 40 | "mateIn4", 41 | "mateIn3", 42 | "mateIn2", 43 | "mateIn1", 44 | "oneMove", 45 | "overloading", 46 | "pawnEndgame", 47 | "pin", 48 | "promotion", 49 | "queenEndgame", 50 | "queensideAttack", 51 | "quietMove", 52 | "rookEndgame", 53 | "queenRookEndgame", 54 | "sacrifice", 55 | "short", 56 | "simplification", 57 | "skewer", 58 | "smotheredMate", 59 | "trappedPiece", 60 | "underPromotion", 61 | "veryLong", 62 | "xRayAttack", 63 | "zugzwang" 64 | ] 65 | 66 | @dataclass 67 | class Puzzle: 68 | id: str 69 | game: Game 70 | pov : Color = field(init=False) 71 | mainline: List[ChildNode] = field(init=False) 72 | cp: int 73 | 74 | def __post_init__(self): 75 | self.pov = not self.game.turn() 76 | self.mainline = list(self.game.mainline()) 77 | -------------------------------------------------------------------------------- /tagger/requirements.txt: -------------------------------------------------------------------------------- 1 | chess==1.3.0 2 | pymongo==3.11.0 3 | -------------------------------------------------------------------------------- /tagger/tagger.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | import logging 3 | import argparse 4 | from multiprocessing import Process, Queue, Pool, Manager 5 | from datetime import datetime 6 | from chess import Move, Board 7 | from chess.pgn import Game, GameNode 8 | from chess.engine import SimpleEngine, Mate, Cp 9 | from typing import List, Tuple, Dict, Any 10 | from model import Puzzle, TagKind 11 | import cook 12 | import chess.engine 13 | from zugzwang import zugzwang 14 | 15 | logger = logging.getLogger(__name__) 16 | logging.basicConfig(format='%(asctime)s %(levelname)-4s %(message)s', datefmt='%m/%d %H:%M') 17 | logger.setLevel(logging.INFO) 18 | 19 | def read(doc) -> Puzzle: 20 | board = Board(doc["fen"]) 21 | node: GameNode = Game.from_board(board) 22 | for uci in (doc["line"].split(' ') if "line" in doc else doc["moves"]): 23 | move = Move.from_uci(uci) 24 | node = node.add_main_variation(move) 25 | return Puzzle(doc["_id"], node.game(), int(doc["cp"])) 26 | 27 | if __name__ == "__main__": 28 | parser = argparse.ArgumentParser(prog='tagger.py', description='automatically tags lichess puzzles') 29 | parser.add_argument("--zug", "-z", help="only zugzwang", action="store_true") 30 | parser.add_argument("--bad_mate", help="find bad mates", action="store_true") 31 | parser.add_argument("--dry", "-d", help="dry run", action="store_true") 32 | parser.add_argument("--all", "-a", help="don't skip existing", action="store_true") 33 | parser.add_argument("--threads", "-t", help="count of cpu threads for engine searches", default="4") 34 | parser.add_argument("--engine", "-e", help="analysis engine", default="stockfish") 35 | args = parser.parse_args() 36 | 37 | if args.zug: 38 | threads = int(args.threads) 39 | def cruncher(thread_id: int): 40 | db = pymongo.MongoClient()['puzzler'] 41 | round_coll = db['puzzle2_round'] 42 | play_coll = db['puzzle2_puzzle'] 43 | engine = SimpleEngine.popen_uci(args.engine) 44 | engine.configure({'Threads': 2}) 45 | for doc in round_coll.aggregate([ 46 | {"$match":{"_id":{"$regex":"^lichess:"},"t":{"$nin":['+zugzwang','-zugzwang']}}}, 47 | {'$lookup':{'from':'puzzle2_puzzle','as':'puzzle','localField':'p','foreignField':'_id'}}, 48 | {'$unwind':'$puzzle'},{'$replaceRoot':{'newRoot':'$puzzle'}} 49 | ]): 50 | try: 51 | if ord(doc["_id"][4]) % threads != thread_id: 52 | continue 53 | puzzle = read(doc) 54 | round_id = f'lichess:{puzzle.id}' 55 | zug = zugzwang(engine, puzzle) 56 | if zug: 57 | cook.log(puzzle) 58 | round_coll.update_one( 59 | { "_id": round_id }, 60 | {"$addToSet": {"t": "+zugzwang" if zug else "-zugzwang"}} 61 | ) 62 | play_coll.update_one({"_id":puzzle.id},{"$set":{"dirty":True}}) 63 | except Exception as e: 64 | print(doc) 65 | logger.error(e) 66 | engine.close() 67 | exit(1) 68 | engine.close() 69 | with Pool(processes=threads) as pool: 70 | for i in range(int(args.threads)): 71 | Process(target=cruncher, args=(i,)).start() 72 | exit(0) 73 | 74 | if args.bad_mate: 75 | threads = int(args.threads) 76 | def cruncher(thread_id: int): 77 | db = pymongo.MongoClient()['puzzler'] 78 | bad_coll = db['puzzle2_bad_maybe'] 79 | play_coll = db['puzzle2_puzzle'] 80 | engine = SimpleEngine.popen_uci('stockfish') 81 | engine.configure({'Threads': 4}) 82 | for doc in bad_coll.find({"bad": {"$exists":False}}): 83 | try: 84 | if ord(doc["_id"][4]) % threads != thread_id: 85 | continue 86 | doc = play_coll.find_one({'_id': doc['_id']}) 87 | if not doc: 88 | continue 89 | puzzle = read(doc) 90 | board = puzzle.mainline[len(puzzle.mainline) - 2].board() 91 | info = engine.analyse(board, multipv = 5, limit = chess.engine.Limit(nodes = 30_000_000)) 92 | bad = False 93 | for score in [pv["score"].pov(puzzle.pov) for pv in info]: 94 | if score < Mate(1) and score > Cp(250): 95 | bad = True 96 | # logger.info(puzzle.id) 97 | bad_coll.update_one({"_id":puzzle.id},{"$set":{"bad":bad}}) 98 | except Exception as e: 99 | logger.error(e) 100 | with Pool(processes=threads) as pool: 101 | for i in range(int(args.threads)): 102 | Process(target=cruncher, args=(i,)).start() 103 | exit(0) 104 | 105 | threads = int(args.threads) 106 | 107 | def cruncher(thread_id: int): 108 | db = pymongo.MongoClient()['puzzler'] 109 | play_coll = db['puzzle2_puzzle'] 110 | round_coll = db['puzzle2_round'] 111 | total = 0 112 | computed = 0 113 | updated = 0 114 | for doc in play_coll.find({'themes':[]}): 115 | total += 1 116 | if not thread_id and total % 1000 == 0: 117 | logger.info(f'{total} / {computed} / {updated}') 118 | if ord(doc["_id"][4]) % threads != thread_id: 119 | continue 120 | computed += 1 121 | id = doc["_id"] 122 | if not args.all and round_coll.count_documents({"_id": f"lichess:{id}", "t.1": {"$exists":True}}): 123 | continue 124 | tags = cook.cook(read(doc)) 125 | round_id = f"lichess:{id}" 126 | if not args.dry: 127 | existing = round_coll.find_one({"_id": round_id},{"t":True}) 128 | zugs = [t for t in existing["t"] if t in ['+zugzwang', '-zugzwang']] if existing else [] 129 | new_tags = [f"+{t}" for t in tags] + zugs 130 | if not existing or set(new_tags) != set(existing["t"]): 131 | updated += 1 132 | round_coll.update_one({ 133 | "_id": round_id 134 | }, { 135 | "$set": { 136 | "p": id, 137 | "d": datetime.now(), 138 | "e": 100, 139 | "t": new_tags 140 | } 141 | }, upsert = True); 142 | play_coll.update_many({"_id":id},{"$set":{"dirty":True}}) 143 | print(f'{thread_id}/{args.threads} done') 144 | 145 | with Pool(processes=threads) as pool: 146 | for i in range(int(args.threads)): 147 | Process(target=cruncher, args=(i,)).start() 148 | -------------------------------------------------------------------------------- /tagger/util.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Tuple 2 | import chess 3 | from chess import square_rank, Color, Board, Square, Piece, square_distance 4 | from chess import KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN 5 | from chess.pgn import ChildNode 6 | from typing import Type, TypeVar 7 | 8 | A = TypeVar('A') 9 | def pp(a: A, msg = None) -> A: 10 | print(f'{msg + ": " if msg else ""}{a}') 11 | return a 12 | 13 | def moved_piece_type(node: ChildNode) -> chess.PieceType: 14 | pt = node.board().piece_type_at(node.move.to_square) 15 | assert(pt) 16 | return pt 17 | 18 | def is_advanced_pawn_move(node: ChildNode) -> bool: 19 | if node.move.promotion: 20 | return True 21 | if moved_piece_type(node) != chess.PAWN: 22 | return False 23 | to_rank = square_rank(node.move.to_square) 24 | return to_rank < 3 if node.turn() else to_rank > 4 25 | 26 | def is_very_advanced_pawn_move(node: ChildNode) -> bool: 27 | if not is_advanced_pawn_move(node): 28 | return False 29 | to_rank = square_rank(node.move.to_square) 30 | return to_rank < 2 if node.turn() else to_rank > 5 31 | 32 | def is_king_move(node: ChildNode) -> bool: 33 | return moved_piece_type(node) == chess.KING 34 | 35 | def is_castling(node: ChildNode) -> bool: 36 | return is_king_move(node) and square_distance(node.move.from_square, node.move.to_square) > 1 37 | 38 | def is_capture(node: ChildNode) -> bool: 39 | return node.parent.board().is_capture(node.move) 40 | 41 | def next_node(node: ChildNode) -> Optional[ChildNode]: 42 | return node.variations[0] if node.variations else None 43 | 44 | def next_next_node(node: ChildNode) -> Optional[ChildNode]: 45 | nn = next_node(node) 46 | return next_node(nn) if nn else None 47 | 48 | values = { PAWN: 1, KNIGHT: 3, BISHOP: 3, ROOK: 5, QUEEN: 9 } 49 | king_values = { PAWN: 1, KNIGHT: 3, BISHOP: 3, ROOK: 5, QUEEN: 9, KING: 99 } 50 | ray_piece_types = [QUEEN, ROOK, BISHOP] 51 | 52 | def piece_value(piece_type: chess.PieceType) -> int: 53 | return values[piece_type] 54 | 55 | def material_count(board: Board, side: Color) -> int: 56 | return sum(len(board.pieces(piece_type, side)) * value for piece_type, value in values.items()) 57 | 58 | def material_diff(board: Board, side: Color) -> int: 59 | return material_count(board, side) - material_count(board, not side) 60 | 61 | def attacked_opponent_pieces(board: Board, from_square: Square, pov: Color) -> List[Piece]: 62 | return [piece for (piece, _) in attacked_opponent_squares(board, from_square, pov)] 63 | 64 | def attacked_opponent_squares(board: Board, from_square: Square, pov: Color) -> List[Tuple[Piece, Square]]: 65 | pieces = [] 66 | for attacked_square in board.attacks(from_square): 67 | attacked_piece = board.piece_at(attacked_square) 68 | if attacked_piece and attacked_piece.color != pov: 69 | pieces.append((attacked_piece, attacked_square)) 70 | return pieces 71 | 72 | def is_defended(board: Board, piece: Piece, square: Square) -> bool: 73 | if board.attackers(piece.color, square): 74 | return True 75 | # ray defense https://lichess.org/editor/6k1/3q1pbp/2b1p1p1/1BPp4/rp1PnP2/4PRNP/4Q1P1/4B1K1_w_-_-_0_1 76 | for attacker in board.attackers(not piece.color, square): 77 | attacker_piece = board.piece_at(attacker) 78 | assert(attacker_piece) 79 | if attacker_piece.piece_type in ray_piece_types: 80 | bc = board.copy(stack = False) 81 | bc.remove_piece_at(attacker) 82 | if bc.attackers(piece.color, square): 83 | return True 84 | 85 | return False 86 | 87 | def is_hanging(board: Board, piece: Piece, square: Square) -> bool: 88 | return not is_defended(board, piece, square) 89 | 90 | def can_be_taken_by_lower_piece(board: Board, piece: Piece, square: Square) -> bool: 91 | for attacker_square in board.attackers(not piece.color, square): 92 | attacker = board.piece_at(attacker_square) 93 | assert(attacker) 94 | if attacker.piece_type != chess.KING and values[attacker.piece_type] < values[piece.piece_type]: 95 | return True 96 | return False 97 | 98 | def is_in_bad_spot(board: Board, square: Square) -> bool: 99 | # hanging or takeable by lower piece 100 | piece = board.piece_at(square) 101 | assert(piece) 102 | return (bool(board.attackers(not piece.color, square)) and 103 | (is_hanging(board, piece, square) or can_be_taken_by_lower_piece(board, piece, square))) 104 | 105 | def is_trapped(board: Board, square: Square) -> bool: 106 | if board.is_check() or board.is_pinned(board.turn, square): 107 | return False 108 | piece = board.piece_at(square) 109 | assert(piece) 110 | if piece.piece_type in [PAWN, KING]: 111 | return False 112 | if not is_in_bad_spot(board, square): 113 | return False 114 | for escape in board.legal_moves: 115 | if escape.from_square == square: 116 | capturing = board.piece_at(escape.to_square) 117 | if capturing and values[capturing.piece_type] >= values[piece.piece_type]: 118 | return False 119 | board.push(escape) 120 | if not is_in_bad_spot(board, escape.to_square): 121 | return False 122 | board.pop() 123 | return True 124 | 125 | def attacker_pieces(board: Board, color: Color, square: Square) -> List[Piece]: 126 | return [p for p in [board.piece_at(s) for s in board.attackers(color, square)] if p] 127 | 128 | # def takers(board: Board, square: Square) -> List[Tuple[Piece, Square]]: 129 | # # pieces that can legally take on a square 130 | # t = [] 131 | # for attack in board.legal_moves: 132 | # if attack.to_square == square: 133 | # t.append((board.piece_at(attack.from_square), attack.from_square)) 134 | # return t 135 | -------------------------------------------------------------------------------- /tagger/zugzwang.py: -------------------------------------------------------------------------------- 1 | import chess 2 | import chess.engine 3 | import math 4 | from chess import Board, Move, Color 5 | from chess.engine import SimpleEngine, Score 6 | from model import Puzzle 7 | 8 | engine_limit = chess.engine.Limit(depth = 30, time = 10, nodes = 12_000_000) 9 | 10 | def zugzwang(engine: SimpleEngine, puzzle: Puzzle) -> bool: 11 | for node in puzzle.mainline[1::2]: 12 | board = node.board() 13 | if board.is_check(): 14 | continue 15 | if len(list(board.legal_moves)) > 15: 16 | continue 17 | 18 | score = score_of(engine, board, not puzzle.pov) 19 | 20 | rev_board = node.board() 21 | rev_board.push(Move.null()) 22 | rev_score = score_of(engine, rev_board, not puzzle.pov) 23 | 24 | if win_chances(score) < win_chances(rev_score) - 0.3: 25 | return True 26 | 27 | return False 28 | 29 | def score_of(engine: SimpleEngine, board: Board, pov: Color): 30 | info = engine.analyse(board, limit = engine_limit) 31 | if "nps" in info: 32 | print(f'knps: {int(info["nps"] / 1000)} kn: {int(info["nodes"] / 1000)} depth: {info["depth"]} time: {info["time"]}') 33 | return info["score"].pov(pov) 34 | 35 | def win_chances(score: Score) -> float: 36 | """ 37 | winning chances from -1 to 1 https://graphsketch.com/?eqn1_color=1&eqn1_eqn=100+*+%282+%2F+%281+%2B+exp%28-0.004+*+x%29%29+-+1%29&eqn2_color=2&eqn2_eqn=&eqn3_color=3&eqn3_eqn=&eqn4_color=4&eqn4_eqn=&eqn5_color=5&eqn5_eqn=&eqn6_color=6&eqn6_eqn=&x_min=-1000&x_max=1000&y_min=-100&y_max=100&x_tick=100&y_tick=10&x_label_freq=2&y_label_freq=2&do_grid=0&do_grid=1&bold_labeled_lines=0&bold_labeled_lines=1&line_width=4&image_w=850&image_h=525 38 | """ 39 | mate = score.mate() 40 | if mate is not None: 41 | return 1 if mate > 0 else -1 42 | 43 | cp = score.score() 44 | return 2 / (1 + math.exp(-0.004 * cp)) - 1 if cp is not None else 0 45 | -------------------------------------------------------------------------------- /validator/.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /validator/README.md: -------------------------------------------------------------------------------- 1 | # UI to manually review new puzzle candidates 2 | 3 | ## Dev 4 | 5 | ``` 6 | cd front 7 | yarn install 8 | yarn dev 9 | cd ../back 10 | export OAUTH_APP_SECRET='lichessOauthAppSecret' 11 | export COOKIE_SECRET='sessionCookieSecret' 12 | yarn dev 13 | ``` 14 | 15 | ## Prod 16 | 17 | ``` 18 | cd back 19 | yarn build 20 | export OAUTH_APP_SECRET='lichessOauthAppSecret' 21 | export COOKIE_SECRET='sessionCookieSecret' 22 | yarn start 23 | ``` 24 | -------------------------------------------------------------------------------- /validator/back/config/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "http": { 3 | "port": 8000, 4 | "url": "http://localhost:8000" 5 | }, 6 | "generatorToken": "changeme" 7 | } 8 | -------------------------------------------------------------------------------- /validator/back/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lichess-puzzle-validator-back", 3 | "version": "0.2", 4 | "main": "index.js", 5 | "author": "Thibault Duplessis ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@types/body-parser": "^1.19.0", 9 | "@types/convict": "^5.2.1", 10 | "body-parser": "^1.19.0", 11 | "convict": "^6.0.0", 12 | "express": "4.17.1", 13 | "mongodb": "^6.6" 14 | }, 15 | "devDependencies": { 16 | "@types/express": "4.17.8", 17 | "@types/node": "^20", 18 | "nodemon": "^2.0.5", 19 | "ts-node": "^9.0.0", 20 | "typescript": "~5.1.6" 21 | }, 22 | "scripts": { 23 | "dev": "nodemon src/index.ts", 24 | "build": "tsc --project ./", 25 | "start": "node build/src/index.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /validator/back/public/images/board/3d/woodi.1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/board/3d/woodi.1024.png -------------------------------------------------------------------------------- /validator/back/public/images/board/blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bB.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bK.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bN.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bP.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bQ.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/bR.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wB.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wK.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wN.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wP.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wQ.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/merida/wR.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Bishop-Flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Bishop-Flipped.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Bishop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Bishop.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-King.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-King.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Knight-Flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Knight-Flipped.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Knight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Knight.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Pawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Pawn.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Queen.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/Black-Rook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/Black-Rook.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Bishop-Flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Bishop-Flipped.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Bishop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Bishop.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-King.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-King.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Knight-Flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Knight-Flipped.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Knight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Knight.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Pawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Pawn.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Queen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Queen.png -------------------------------------------------------------------------------- /validator/back/public/images/pieces/staunton/basic/White-Rook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ornicar/lichess-puzzler/585cc33128d74a773f0b6df3d8d146f7a884d6a8/validator/back/public/images/pieces/staunton/basic/White-Rook.png -------------------------------------------------------------------------------- /validator/back/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #161512 linear-gradient(to bottom, #2e2a24, #161512 116px) no-repeat; 3 | color: #bababa; 4 | font: 16px 'Noto Sans', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, Sans-Serif; 5 | 6 | --c-bad: #b21e35; 7 | --c-good: #4f772d; 8 | } 9 | 10 | main { 11 | max-width: 1000px; 12 | margin: auto; 13 | } 14 | 15 | a, 16 | a:visited { 17 | color: #bababa; 18 | } 19 | 20 | h1 a { 21 | text-decoration: none; 22 | } 23 | 24 | .top { 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | margin: 1em 0 3em 0; 29 | } 30 | 31 | .top__right * { 32 | margin-left: 1em; 33 | } 34 | 35 | .puzzle { 36 | display: flex; 37 | } 38 | 39 | .puzzle__ui { 40 | margin-left: 1.5em; 41 | padding: 1em 2em; 42 | background: hsla(0, 0%, 90%, 0.1); 43 | overflow: hidden; 44 | 45 | display: flex; 46 | flex-flow: column; 47 | justify-content: space-between; 48 | } 49 | 50 | .puzzle__info san { 51 | margin: 0 .3ch; 52 | opacity: .3; 53 | font-weight: bold; 54 | display: inline-block; 55 | } 56 | 57 | .puzzle__info san.done { 58 | color: hsl(88, 62%, 37%); 59 | opacity: 1; 60 | } 61 | 62 | .puzzle__info__title em { 63 | margin-left: 1em; 64 | opacity: .3; 65 | } 66 | 67 | .puzzle__info .tags { 68 | opacity: .7; 69 | } 70 | 71 | button { 72 | background: hsla(0, 0%, 90%, 0.1); 73 | color: #bababa; 74 | border-radius: 5px; 75 | cursor: pointer; 76 | } 77 | 78 | .replay { 79 | margin: 1.5em 0 0 0; 80 | } 81 | .replay button { 82 | font-size: 1em; 83 | padding: .3em .7em; 84 | margin-right: 1em; 85 | } 86 | .replay button:disabled { 87 | opacity: .5; 88 | } 89 | .replay button.variation { 90 | border-color: var(--c-bad); 91 | } 92 | 93 | .puzzle__review { 94 | display: flex; 95 | flex-flow: row nowrap; 96 | } 97 | .puzzle__review button { 98 | background: none; 99 | border: none; 100 | flex: 1 1 50%; 101 | padding: 1em; 102 | outline: none!important; 103 | } 104 | .puzzle__review button:hover { 105 | background: hsla(0, 0%, 90%, 0.1); 106 | } 107 | .puzzle__review button strong { 108 | font-size: 6em; 109 | display: block; 110 | line-height: .9em; 111 | } 112 | .puzzle__review .reject { 113 | color: var(--c-bad); 114 | } 115 | .puzzle__review .approve { 116 | color: var(--c-good); 117 | } 118 | .puzzle__review button:active, 119 | .puzzle__review button.active { 120 | color: #fff; 121 | } 122 | .puzzle__review .reject:active, 123 | .puzzle__review .reject.active { 124 | background: var(--c-bad); 125 | } 126 | .puzzle__review .approve:active, 127 | .puzzle__review .approve.active { 128 | background: var(--c-good); 129 | } 130 | .puzzle__review button em { 131 | color: #888; 132 | font-size: .8em; 133 | } 134 | .puzzle__skip { 135 | text-align: center; 136 | } 137 | .puzzle__skip button { 138 | padding: .3em 1em; 139 | } 140 | 141 | .puzzle__help { 142 | font-size: .8em; 143 | opacity: .8; 144 | } 145 | 146 | /* chessground */ 147 | 148 | .cg-wrap { 149 | width: 640px; 150 | height: 640px; 151 | position: relative; 152 | display: block; 153 | } 154 | 155 | @media (max-width: 1000px) { 156 | .cg-wrap { 157 | width: 512px; 158 | height: 512px; 159 | } 160 | } 161 | 162 | @media (max-width: 850px) { 163 | .cg-wrap { 164 | width: 384px; 165 | height: 384px; 166 | } 167 | } 168 | 169 | @media (max-width: 700px) { 170 | .cg-wrap { 171 | width: 256px; 172 | height: 256px; 173 | } 174 | } 175 | 176 | cg-helper { 177 | position: absolute; 178 | width: 12.5%; 179 | padding-bottom: 12.5%; 180 | display: table; 181 | /* hack: round to full pixel size in chrome */ 182 | bottom: 0; 183 | } 184 | 185 | cg-container { 186 | position: absolute; 187 | width: 800%; 188 | height: 800%; 189 | display: block; 190 | bottom: 0; 191 | } 192 | 193 | cg-board { 194 | position: absolute; 195 | top: 0; 196 | left: 0; 197 | width: 100%; 198 | height: 100%; 199 | -webkit-user-select: none; 200 | -moz-user-select: none; 201 | -ms-user-select: none; 202 | user-select: none; 203 | line-height: 0; 204 | background-size: cover; 205 | cursor: pointer; 206 | } 207 | 208 | cg-board square { 209 | position: absolute; 210 | top: 0; 211 | left: 0; 212 | width: 12.5%; 213 | height: 12.5%; 214 | pointer-events: none; 215 | } 216 | 217 | cg-board square.move-dest { 218 | background: radial-gradient(rgba(20, 85, 30, 0.5) 22%, #208530 0, rgba(0, 0, 0, 0.3) 0, rgba(0, 0, 0, 0) 0); 219 | pointer-events: auto; 220 | } 221 | 222 | cg-board square.premove-dest { 223 | background: radial-gradient(rgba(20, 30, 85, 0.5) 22%, #203085 0, rgba(0, 0, 0, 0.3) 0, rgba(0, 0, 0, 0) 0); 224 | } 225 | 226 | cg-board square.oc.move-dest { 227 | background: radial-gradient(transparent 0%, transparent 80%, rgba(20, 85, 0, 0.3) 80%); 228 | } 229 | 230 | cg-board square.oc.premove-dest { 231 | background: radial-gradient(transparent 0%, transparent 80%, rgba(20, 30, 85, 0.2) 80%); 232 | } 233 | 234 | cg-board square.move-dest:hover { 235 | background: rgba(20, 85, 30, 0.3); 236 | } 237 | 238 | cg-board square.premove-dest:hover { 239 | background: rgba(20, 30, 85, 0.2); 240 | } 241 | 242 | cg-board square.last-move { 243 | will-change: transform; 244 | background-color: rgba(155, 199, 0, 0.41); 245 | } 246 | 247 | cg-board square.selected { 248 | background-color: rgba(20, 85, 30, 0.5); 249 | } 250 | 251 | cg-board square.check { 252 | background: radial-gradient(ellipse at center, rgba(255, 0, 0, 1) 0%, rgba(231, 0, 0, 1) 25%, rgba(169, 0, 0, 0) 89%, rgba(158, 0, 0, 0) 100%); 253 | } 254 | 255 | cg-board square.current-premove { 256 | background-color: rgba(20, 30, 85, 0.5); 257 | } 258 | 259 | .cg-wrap piece { 260 | position: absolute; 261 | top: 0; 262 | left: 0; 263 | width: 12.5%; 264 | height: 12.5%; 265 | background-size: cover; 266 | z-index: 2; 267 | will-change: transform; 268 | pointer-events: none; 269 | } 270 | 271 | cg-board piece.dragging { 272 | cursor: move; 273 | z-index: 9; 274 | } 275 | 276 | cg-board piece.anim { 277 | z-index: 8; 278 | } 279 | 280 | cg-board piece.fading { 281 | z-index: 1; 282 | opacity: 0.5; 283 | } 284 | 285 | .cg-wrap square.move-dest:hover { 286 | background-color: rgba(20, 85, 30, 0.3); 287 | } 288 | 289 | .cg-wrap piece.ghost { 290 | opacity: 0.3; 291 | } 292 | 293 | .cg-wrap svg { 294 | overflow: hidden; 295 | position: relative; 296 | top: 0px; 297 | left: 0px; 298 | width: 100%; 299 | height: 100%; 300 | pointer-events: none; 301 | z-index: 2; 302 | opacity: 0.6; 303 | } 304 | 305 | .cg-wrap svg image { 306 | opacity: 0.5; 307 | } 308 | 309 | .cg-wrap coords { 310 | position: absolute; 311 | display: flex; 312 | pointer-events: none; 313 | opacity: 0.8; 314 | font-size: 9px; 315 | } 316 | 317 | .cg-wrap coords.ranks { 318 | right: -15px; 319 | top: 0; 320 | flex-flow: column-reverse; 321 | height: 100%; 322 | width: 12px; 323 | } 324 | 325 | .cg-wrap coords.ranks.black { 326 | flex-flow: column; 327 | } 328 | 329 | .cg-wrap coords.files { 330 | bottom: -16px; 331 | left: 0; 332 | flex-flow: row; 333 | width: 100%; 334 | height: 16px; 335 | text-transform: uppercase; 336 | text-align: center; 337 | } 338 | 339 | .cg-wrap coords.files.black { 340 | flex-flow: row-reverse; 341 | } 342 | 343 | .cg-wrap coords coord { 344 | flex: 1 1 auto; 345 | } 346 | 347 | .cg-wrap coords.ranks coord { 348 | transform: translateY(39%); 349 | } 350 | 351 | .blue .cg-wrap { 352 | background-image: url('images/board/blue.svg'); 353 | } 354 | 355 | .merida .cg-wrap piece.pawn.white { 356 | background-image: url('images/pieces/merida/wP.svg'); 357 | } 358 | 359 | .merida .cg-wrap piece.bishop.white { 360 | background-image: url('images/pieces/merida/wB.svg'); 361 | } 362 | 363 | .merida .cg-wrap piece.knight.white { 364 | background-image: url('images/pieces/merida/wN.svg'); 365 | } 366 | 367 | .merida .cg-wrap piece.rook.white { 368 | background-image: url('images/pieces/merida/wR.svg'); 369 | } 370 | 371 | .merida .cg-wrap piece.queen.white { 372 | background-image: url('images/pieces/merida/wQ.svg'); 373 | } 374 | 375 | .merida .cg-wrap piece.king.white { 376 | background-image: url('images/pieces/merida/wK.svg'); 377 | } 378 | 379 | .merida .cg-wrap piece.pawn.black { 380 | background-image: url('images/pieces/merida/bP.svg'); 381 | } 382 | 383 | .merida .cg-wrap piece.bishop.black { 384 | background-image: url('images/pieces/merida/bB.svg'); 385 | } 386 | 387 | .merida .cg-wrap piece.knight.black { 388 | background-image: url('images/pieces/merida/bN.svg'); 389 | } 390 | 391 | .merida .cg-wrap piece.rook.black { 392 | background-image: url('images/pieces/merida/bR.svg'); 393 | } 394 | 395 | .merida .cg-wrap piece.queen.black { 396 | background-image: url('images/pieces/merida/bQ.svg'); 397 | } 398 | 399 | .merida .cg-wrap piece.king.black { 400 | background-image: url('images/pieces/merida/bK.svg'); 401 | } 402 | -------------------------------------------------------------------------------- /validator/back/src/config.ts: -------------------------------------------------------------------------------- 1 | import convict from 'convict'; 2 | 3 | const schema = convict({ 4 | env: { 5 | format: ['prod', 'dev', 'local'], 6 | default: 'dev', 7 | arg: 'env', 8 | env: 'NODE_ENV', 9 | }, 10 | http: { 11 | port: { 12 | format: 'port', 13 | default: 8000, 14 | }, 15 | url: { 16 | format: String, 17 | default: 'http://localhost:8000', 18 | }, 19 | }, 20 | generatorToken: { 21 | format: String, 22 | default: 'changeme', 23 | }, 24 | mongodb: { 25 | url: { 26 | format: String, 27 | default: 'mongodb://127.0.0.1:27017', 28 | }, 29 | name: { 30 | format: String, 31 | default: 'puzzler', 32 | }, 33 | }, 34 | }); 35 | 36 | schema.loadFile(`config/${schema.get('env')}.json`); 37 | schema.validate({ allowed: 'strict' }); 38 | 39 | interface Config { 40 | env: string; 41 | http: { 42 | port: number; 43 | url: string; 44 | }; 45 | generatorToken: string; 46 | mongodb: { 47 | url: string; 48 | name: string; 49 | }; 50 | } 51 | 52 | export const config: Config = { 53 | env: schema.get('env'), 54 | http: schema.get('http'), 55 | generatorToken: schema.get('generatorToken'), 56 | mongodb: schema.get('mongodb'), 57 | }; 58 | -------------------------------------------------------------------------------- /validator/back/src/env.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, Db } from 'mongodb'; 2 | import { config } from './config'; 3 | import Mongo from './mongo'; 4 | 5 | export default class Env { 6 | constructor(readonly mongo: Mongo) { } 7 | 8 | static make = async (): Promise => new Env(await connectDb(config.mongodb).then(db => new Mongo(db))); 9 | } 10 | 11 | async function connectDb(conf: any): Promise { 12 | const client = new MongoClient(conf.url, { 13 | // useUnifiedTopology: true, 14 | // useNewUrlParser: true, 15 | }); 16 | console.log('Connecting to', conf.url); 17 | await client.connect(); 18 | console.log('Connected'); 19 | return client.db(conf.name); 20 | } 21 | -------------------------------------------------------------------------------- /validator/back/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | import { config } from './config'; 4 | import Env from './env'; 5 | import router from './router'; 6 | 7 | const app = express(); 8 | 9 | Env.make().then((env: Env) => { 10 | app.use(express.static('public')); 11 | 12 | app.use(bodyParser.json()); 13 | 14 | router(app, env); 15 | 16 | app.listen(config.http.port, () => { 17 | console.log(`⚡️[${config.env}]: Server is running at ${config.http.url}`); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /validator/back/src/mongo.ts: -------------------------------------------------------------------------------- 1 | import { Collection, Db } from 'mongodb'; 2 | import { Puzzle } from './puzzle'; 3 | 4 | interface Stats { 5 | nbCandidates: number; 6 | nbReviewed: number; 7 | } 8 | 9 | export default class Mongo { 10 | puzzle: PuzzleMongo; 11 | seen: SeenMongo; 12 | 13 | constructor(readonly db: Db) { 14 | this.puzzle = new PuzzleMongo(this.db.collection('puzzle2')); 15 | this.seen = new SeenMongo(this.db.collection('seen'), this.db.collection('puzzle2')); 16 | } 17 | } 18 | 19 | export class PuzzleMongo { 20 | constructor(readonly coll: Collection) { 21 | this.coll.createIndex({ gameId: 1 }, { unique: true }); 22 | } 23 | 24 | get = (id: string): Promise => this.coll.findOne({ _id: id } as any) as Promise; 25 | 26 | stats = (): Promise => 27 | Promise.all([false, true].map(this.selectReviewed).map(sel => this.coll.countDocuments(sel))).then( 28 | ([nbCandidates, nbReviewed]) => ({ 29 | nbCandidates, 30 | nbReviewed, 31 | }) 32 | ); 33 | 34 | insert = (puzzle: Puzzle): Promise => this.coll.insertOne(puzzle as any); 35 | 36 | private selectReviewed = (v: boolean) => ({ review: { $exists: v } }); 37 | } 38 | 39 | export class SeenMongo { 40 | constructor(readonly seenColl: Collection, readonly puzzleColl: Collection) { } 41 | 42 | exists = (id: string): Promise => 43 | this.seenColl.countDocuments({ _id: (id as any) }).then(n => n > 0); 44 | 45 | positionExists = (fen: string, move: string): Promise => 46 | this.puzzleColl.countDocuments({ fen: fen, 'moves.0': move }).then(n => n > 0); 47 | 48 | set = (id: string) => this.seenColl.insertOne({ _id: id } as any).catch(() => { }); 49 | } 50 | -------------------------------------------------------------------------------- /validator/back/src/puzzle.ts: -------------------------------------------------------------------------------- 1 | export type Uci = string; 2 | export type San = string; 3 | type UserId = string; 4 | 5 | export interface Puzzle { 6 | _id: string; 7 | createdAt: Date; 8 | gameId: string; 9 | fen: string; 10 | ply: number; 11 | moves: Uci[]; 12 | cp?: number; 13 | generator: number; 14 | ip?: string; 15 | tags?: string[]; 16 | } 17 | 18 | const idChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 19 | const idLength = 5; 20 | 21 | export function randomId() { 22 | let result = ''; 23 | for (let i = idLength; i > 0; --i) result += idChars[Math.floor(Math.random() * idChars.length)]; 24 | return result; 25 | } 26 | -------------------------------------------------------------------------------- /validator/back/src/router.ts: -------------------------------------------------------------------------------- 1 | import Express from 'express'; 2 | import Env from './env'; 3 | import { Puzzle, randomId } from './puzzle'; 4 | import { Response as ExResponse } from 'express'; 5 | import { config } from './config'; 6 | 7 | export default function (app: Express.Express, env: Env) { 8 | let duplicates = 0; 9 | app.post('/puzzle', async (req, res) => { 10 | if ((req.query.token as string) != config.generatorToken) return res.status(400).send('Wrong token'); 11 | const puzzle: Puzzle = { 12 | _id: randomId(), 13 | gameId: req.body.game_id, 14 | fen: req.body.fen, 15 | ply: req.body.ply, 16 | moves: req.body.moves, 17 | cp: req.body.cp, 18 | generator: req.body.generator_version, 19 | createdAt: new Date(), 20 | ip: req.ip, 21 | }; 22 | try { 23 | await env.mongo.puzzle.insert(puzzle); 24 | console.log(puzzle.ip); 25 | return res.send(`Created ${config.http.url}/puzzle/${puzzle._id}`); 26 | } catch (e: any) { 27 | const msg = e.code == 11000 ? `Game ${puzzle.gameId} already in the puzzle DB!` : e.message; 28 | if (e.code == 11000) { 29 | duplicates++; 30 | console.info(`${duplicates} duplicates detected.`); 31 | } else console.warn(`Mongo insert error: ${msg}`); 32 | return res.status(200).send(msg); 33 | } 34 | }); 35 | 36 | app.get('/seen', async (req, res) => { 37 | if ((req.query.token as string) != config.generatorToken) return res.status(400).send('Wrong token'); 38 | const id = req.query.id as string; 39 | let exists = false; 40 | if (id.length == 8) exists = await env.mongo.seen.exists(id); 41 | else { 42 | const [fen, move] = id.split(':'); 43 | exists = await env.mongo.seen.positionExists(fen, move); 44 | } 45 | process.stdout.write('.'); 46 | return exists ? res.status(200).send() : res.status(404).send(); 47 | }); 48 | app.post('/seen', async (req, res) => { 49 | if ((req.query.token as string) != config.generatorToken) return res.status(400).send('Wrong token'); 50 | env.mongo.seen.set(req.query.id as string); 51 | return res.status(201).send(); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /validator/back/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "rootDir": "./", 6 | "outDir": "./build", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "noUnusedLocals": false, 12 | "noImplicitAny": false, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noUnusedParameters": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /validator/front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lichess-puzzle-validator-front", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@rollup/plugin-commonjs": "^15.1.0", 8 | "@rollup/plugin-node-resolve": "^9.0.0", 9 | "@rollup/plugin-typescript": "^6.0.0", 10 | "rollup": "^2.32.0", 11 | "typescript": "^4.0.3" 12 | }, 13 | "dependencies": { 14 | "@types/mousetrap": "^1.6.4", 15 | "chessground": "^7.9.3", 16 | "chessops": "^0.7.3", 17 | "mousetrap": "^1.6.5", 18 | "snabbdom": "^0.7.4", 19 | "tslib": "^2.0.3" 20 | }, 21 | "scripts": { 22 | "dev": "rollup --config", 23 | "prod": "rollup --config --config-prod" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /validator/front/rollup.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require('@rollup/plugin-node-resolve').default; 2 | const commonjs = require('@rollup/plugin-commonjs'); 3 | const typescript = require('@rollup/plugin-typescript'); 4 | // const terser = require('rollup-plugin-terser').terser; 5 | 6 | export default (args) => { 7 | const prod = args['config-prod']; 8 | const name = 'PuzzleValidator'; 9 | return { 10 | input: 'src/main.ts', 11 | output: [ 12 | prod ? { 13 | file: '../back/public/dist/puzzle-validator.min.js', 14 | format: 'iife', 15 | name: name, 16 | plugins: [ 17 | // terser({ 18 | // safari10: false, 19 | // output: { 20 | // comments: false, 21 | // }, 22 | // }), 23 | ], 24 | } : { 25 | file: '../back/public/dist/puzzle-validator.dev.js', 26 | format: 'iife', 27 | name: name, 28 | } 29 | ], 30 | plugins: [ 31 | resolve(), 32 | typescript(), 33 | commonjs({ 34 | extensions: ['.js', '.ts'], 35 | }), 36 | ], 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /validator/front/src/ctrl.ts: -------------------------------------------------------------------------------- 1 | import { ServerData } from './types'; 2 | import { Uci, San } from '../../back/src/puzzle'; 3 | import { Api as Chessground } from 'chessground/api'; 4 | import { Key } from 'chessground/types'; 5 | import { Chess, parseUci } from 'chessops'; 6 | import { makeFen, parseFen } from 'chessops/fen'; 7 | import { makeSanVariation } from 'chessops/san'; 8 | import { chessgroundDests } from 'chessops/compat'; 9 | 10 | export default class Ctrl { 11 | 12 | data: ServerData; 13 | chessground: Chessground; 14 | chess: Chess; 15 | solution: San[]; 16 | moves: Uci[] = []; 17 | 18 | constructor(data: ServerData, readonly redraw: () => void) { 19 | this.init(data, true); 20 | } 21 | 22 | private init = (data: ServerData, first: boolean = false) => { 23 | this.data = data; 24 | this.moves = []; 25 | this.chess = this.initialChess(); 26 | this.solution = makeSanVariation(this.chess, this.data.puzzle.moves.slice(1).map(uci => parseUci(uci)!)).replace(/\d+\.+ /g, '').split(' '); 27 | if (!first) this.redraw(); 28 | history.replaceState({}, '', `/puzzle/${data.puzzle._id}`); 29 | } 30 | 31 | review = async (approved: boolean) => { 32 | this.data.puzzle.review = { 33 | approved, 34 | by: this.data.username, 35 | at: new Date() 36 | }; 37 | this.redraw(); 38 | const data = await fetch(`/review/${this.data.puzzle._id}?approved=${approved ? 1 : 0}`, { 39 | method: 'post' 40 | }).then(res => res.json()); 41 | this.init(data); 42 | } 43 | 44 | skip = () => fetch('/skip').then(res => res.json()).then(this.init); 45 | 46 | setChessground(cg: Chessground) { 47 | this.chessground = cg; 48 | } 49 | 50 | onMove = (uci: Uci, autoReply: boolean = true) => { 51 | this.moves.push(uci); 52 | this.chess.play(parseUci(uci)!); 53 | this.chessground.set(this.cgConfig(uci)); 54 | if (this.chess.isCheckmate() && this.moves.length < this.data.puzzle.moves.length) 55 | this.data.puzzle.moves = this.data.puzzle.moves.slice(0, 1).concat(this.moves); 56 | const reply = autoReply && this.findReply(); 57 | if (reply) this.onMove(reply, false); 58 | else this.redraw(); 59 | } 60 | 61 | orientation = () => this.data.puzzle.ply % 2 == 1 ? 'white' : 'black'; 62 | 63 | isComplete = () => 64 | this.moves.join(' ') == this.data.puzzle.moves.slice(1).join(' '); 65 | 66 | isInVariation = () => !this.isComplete() && !this.canForward(); 67 | 68 | back = () => { 69 | if (!this.moves.length) return; 70 | this.moves = this.moves.slice(0, -1); 71 | this.chess = this.initialChess(); 72 | this.moves.forEach(move => this.chess.play(parseUci(move)!)); 73 | const lastMove = this.moves[this.moves.length - 1] || this.data.puzzle.moves[0]; 74 | this.chessground.set(this.cgConfig(lastMove)); 75 | this.redraw(); 76 | } 77 | 78 | canForward = () => 79 | this.moves.length < this.data.puzzle.moves.slice(1).length && 80 | this.moves.join(' ') == this.data.puzzle.moves.slice(1, this.moves.length + 1).join(' '); 81 | 82 | forward = () => { 83 | if (!this.canForward()) return; 84 | const move = this.data.puzzle.moves[this.moves.length + 1]; 85 | if (move) this.onMove(move, false); 86 | this.redraw(); 87 | } 88 | 89 | private findReply = (): Uci | undefined => { 90 | if (this.moves.length % 2 == 0 || this.moves.length > this.data.puzzle.moves.length) return; 91 | if (this.moves.join(' ') != this.data.puzzle.moves.slice(1, this.moves.length + 1).join(' ')) return; 92 | return this.data.puzzle.moves[this.moves.length + 1]; 93 | } 94 | 95 | currentFen = () => makeFen(this.chess.toSetup()); 96 | 97 | nbMovesIn = () => { 98 | let nb = 0; 99 | for (let move of this.moves) { 100 | if (move == this.data.puzzle.moves[nb + 1]) nb++; 101 | else break; 102 | } 103 | return nb; 104 | } 105 | 106 | private cgConfig = (lastMove: Uci) => ({ 107 | fen: this.currentFen(), 108 | turnColor: this.chess.turn, 109 | movable: { 110 | color: this.chess.turn, 111 | dests: chessgroundDests(this.chess) 112 | }, 113 | check: this.chess.isCheck(), 114 | lastMove: this.cgLastMove(lastMove) 115 | }); 116 | 117 | cgLastMove = (move: Uci) => ([move.substr(0, 2) as Key, move.substr(2, 2) as Key]); 118 | 119 | initialChess = () => { 120 | const c = Chess.fromSetup(parseFen(this.data.puzzle.fen).unwrap()).unwrap(); 121 | c.play(parseUci(this.data.puzzle.moves[0])!); 122 | return c; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /validator/front/src/main.ts: -------------------------------------------------------------------------------- 1 | import { init } from 'snabbdom'; 2 | import { VNode } from 'snabbdom/vnode' 3 | import klass from 'snabbdom/modules/class'; 4 | import attributes from 'snabbdom/modules/attributes'; 5 | import * as Mousetrap from 'mousetrap'; 6 | import Ctrl from './ctrl'; 7 | 8 | const patch = init([klass, attributes]); 9 | 10 | import view from './view'; 11 | import { ServerData } from './types'; 12 | 13 | export function start(data: ServerData) { 14 | 15 | const element = document.querySelector('main') as HTMLElement; 16 | 17 | let vnode: VNode; 18 | 19 | function redraw() { 20 | vnode = patch(vnode, view(ctrl)); 21 | } 22 | 23 | const ctrl = new Ctrl(data, redraw); 24 | 25 | const blueprint = view(ctrl); 26 | element.innerHTML = ''; 27 | vnode = patch(element, blueprint); 28 | 29 | redraw(); 30 | 31 | const noRepeat = (f: () => void) => (e: KeyboardEvent) => { if (!e.repeat) f() }; 32 | 33 | Mousetrap.bind('left', ctrl.back); 34 | Mousetrap.bind('right', ctrl.forward); 35 | Mousetrap.bind('backspace', noRepeat(() => ctrl.review(false))); 36 | Mousetrap.bind('enter', noRepeat(() => ctrl.review(true))); 37 | Mousetrap.bind('a', () => (document.querySelector('a.analyse') as HTMLAnchorElement).click()); 38 | }; 39 | -------------------------------------------------------------------------------- /validator/front/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Puzzle } from '../../back/src/puzzle'; 2 | 3 | export interface ServerData { 4 | username: string; 5 | puzzle: Puzzle; 6 | } 7 | 8 | export interface Stats { 9 | nbCandidates: number; 10 | nbReviewed: number; 11 | } 12 | -------------------------------------------------------------------------------- /validator/front/src/view.ts: -------------------------------------------------------------------------------- 1 | import { Chessground } from 'chessground'; 2 | import { h } from 'snabbdom' 3 | import { VNode } from 'snabbdom/vnode'; 4 | import Ctrl from './ctrl'; 5 | import { chessgroundDests } from 'chessops/compat'; 6 | import { Key } from 'chessground/types'; 7 | 8 | export default function(ctrl: Ctrl): VNode { 9 | const puzzle = ctrl.data.puzzle, 10 | nbMovesIn = ctrl.nbMovesIn(), 11 | gameUrl = `https://lichess.org/${puzzle.gameId}${ctrl.orientation() == 'white' ? '' : '/black'}#${puzzle.ply}`; 12 | return h(`main.puzzle-${puzzle._id}`, [ 13 | h('section.top', [ 14 | h('h1', h('a', { attrs: { href: '/' } }, 'Lichess Puzzle Validator')), 15 | h('div.top__right', [ 16 | h('strong', ctrl.data.username), 17 | h('a', { attrs: { href: '/logout' } }, 'Log out') 18 | ]) 19 | ]), 20 | h('section.puzzle', [ 21 | h('div.puzzle__board.chessground.merida.blue', [ 22 | h('div.cg-wrap', { 23 | hook: { 24 | insert(vnode) { 25 | ctrl.setChessground(Chessground(vnode.elm as HTMLElement, cgConfig(ctrl))); 26 | } 27 | } 28 | }) 29 | ]), 30 | h('div.puzzle__ui', [ 31 | h('div.puzzle__info', [ 32 | h('p.puzzle__info__title', [ 33 | h('a', { 34 | attrs: { href: `/puzzle/${puzzle._id}` } 35 | }, `Candidate #${puzzle._id}`), 36 | h('em', { 37 | attrs: { title: 'Generator version' } 38 | }, `v${puzzle.generator}`), 39 | h('p.tags', [(puzzle.tags || []).join(', ')]), 40 | ]), 41 | h('p', [ 42 | 'From game ', 43 | h('a.analyse', { 44 | attrs: { 45 | href: gameUrl, 46 | target: '_blank' 47 | } 48 | }, `#${puzzle.gameId}`) 49 | ]), 50 | h('p', [ 51 | 'Solution: ', 52 | ...ctrl.solution.map((san, i) => 53 | h('san', { 54 | class: { done: i < nbMovesIn } 55 | }, san) 56 | ) 57 | ]) 58 | ]), , 59 | h('div.puzzle__review', [ 60 | h('button.reject', { 61 | hook: onClick(() => ctrl.review(false)), 62 | class: { active: puzzle.review?.approved === false } 63 | }, [ 64 | h('em', 'Reject'), 65 | h('strong', '✗'), 66 | h('em', '[backspace]') 67 | ]), 68 | h('button.approve', { 69 | hook: onClick(() => ctrl.review(true)), 70 | class: { active: puzzle.review?.approved === true } 71 | }, [ 72 | h('em', 'Approve'), 73 | h('strong', '✓'), 74 | h('em', '[enter]') 75 | ]) 76 | ]), 77 | h('div.puzzle__skip', [ 78 | h('button', { hook: onClick(ctrl.skip) }, 'Skip') 79 | ]), 80 | h('div.puzzle__help', [ 81 | h('p', 'Does the puzzle feel a bit off, computer-like, or frustrating? Just reject it.'), 82 | h('p', 'Too difficult and you\'re not sure if interesting? Skip it.'), 83 | h('p', 'Use arrow keys to replay, backspace/enter to review, a to analyse.') 84 | ]) 85 | ]) 86 | ]), 87 | h('p.replay', [ 88 | h('button', { 89 | attrs: { 90 | disabled: !ctrl.moves.length 91 | }, 92 | class: { 93 | variation: ctrl.isInVariation() 94 | }, 95 | hook: onClick(ctrl.back) 96 | }, '< Rewind'), 97 | h('button', { 98 | attrs: { 99 | disabled: !ctrl.canForward() 100 | }, 101 | hook: onClick(ctrl.forward) 102 | }, 'Forward >') 103 | ]) 104 | ]); 105 | } 106 | 107 | const cgConfig = (ctrl: Ctrl) => { 108 | const p = ctrl.data.puzzle, 109 | chess = ctrl.initialChess(); 110 | return { 111 | fen: ctrl.currentFen(), 112 | orientation: chess.turn, 113 | turnColor: chess.turn, 114 | check: chess.isCheck(), 115 | movable: { 116 | free: false, 117 | color: chess.turn, 118 | dests: chessgroundDests(chess) 119 | }, 120 | lastMove: ctrl.cgLastMove(p.moves[0]), 121 | events: { 122 | move(orig: Key, dest: Key) { 123 | ctrl.onMove(`${orig}${dest}`); 124 | } 125 | }, 126 | premovable: { 127 | enabled: false 128 | }, 129 | drawable: { 130 | enabled: true 131 | }, 132 | disableContextMenu: true 133 | } 134 | } 135 | 136 | const onClick = (f: (event: MouseEvent) => any) => ({ 137 | insert(vnode: VNode) { 138 | (vnode.elm as HTMLElement).addEventListener('click', f) 139 | } 140 | }); 141 | -------------------------------------------------------------------------------- /validator/front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**.ts"], 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "strictNullChecks": true, 6 | "noUnusedLocals": true, 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "noImplicitThis": true, 10 | "noUnusedParameters": true, 11 | "moduleResolution": "node", 12 | "target": "ES2016", 13 | "module": "commonjs", 14 | "lib": ["DOM", "ES2016", "DOM.iterable"], 15 | "types": [] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /validator/front/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@badrap/result@^0.2.6": 6 | version "0.2.6" 7 | resolved "https://registry.yarnpkg.com/@badrap/result/-/result-0.2.6.tgz#868d1bc98145f346c465f3e3be45aea42d87aa66" 8 | integrity sha512-YmdWUHsWqsduDKt2sjzR+DFg+NriN+J6GAtDp5GpTC5wjqFnArY4V/00xdYNy9kJ0YlrBpN6OQZeM2dN0Kr1jw== 9 | 10 | "@rollup/plugin-commonjs@^15.1.0": 11 | version "15.1.0" 12 | resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz#1e7d076c4f1b2abf7e65248570e555defc37c238" 13 | integrity sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ== 14 | dependencies: 15 | "@rollup/pluginutils" "^3.1.0" 16 | commondir "^1.0.1" 17 | estree-walker "^2.0.1" 18 | glob "^7.1.6" 19 | is-reference "^1.2.1" 20 | magic-string "^0.25.7" 21 | resolve "^1.17.0" 22 | 23 | "@rollup/plugin-node-resolve@^9.0.0": 24 | version "9.0.0" 25 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" 26 | integrity sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg== 27 | dependencies: 28 | "@rollup/pluginutils" "^3.1.0" 29 | "@types/resolve" "1.17.1" 30 | builtin-modules "^3.1.0" 31 | deepmerge "^4.2.2" 32 | is-module "^1.0.0" 33 | resolve "^1.17.0" 34 | 35 | "@rollup/plugin-typescript@^6.0.0": 36 | version "6.0.0" 37 | resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-6.0.0.tgz#08635d9d04dc3a099ef0150c289ba5735200bc63" 38 | integrity sha512-Y5U2L4eaF3wUSgCZRMdvNmuzWkKMyN3OwvhAdbzAi5sUqedaBk/XbzO4T7RlViDJ78MOPhwAIv2FtId/jhMtbg== 39 | dependencies: 40 | "@rollup/pluginutils" "^3.1.0" 41 | resolve "^1.17.0" 42 | 43 | "@rollup/pluginutils@^3.1.0": 44 | version "3.1.0" 45 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" 46 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== 47 | dependencies: 48 | "@types/estree" "0.0.39" 49 | estree-walker "^1.0.1" 50 | picomatch "^2.2.2" 51 | 52 | "@types/estree@*": 53 | version "0.0.45" 54 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" 55 | integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== 56 | 57 | "@types/estree@0.0.39": 58 | version "0.0.39" 59 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 60 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 61 | 62 | "@types/mousetrap@^1.6.4": 63 | version "1.6.4" 64 | resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.4.tgz#32503197fca4168b10bf251c1d677a9b5b1c2415" 65 | integrity sha512-+Y900DGhe+f+4lRwHm9krsKfsiXcbdOhzTsLbytU4MiG8wE9xOw7CFKtgYKfqEAcUdWEGZRyuTxoyFl2Gx6Rdg== 66 | 67 | "@types/node@*": 68 | version "14.11.10" 69 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef" 70 | integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA== 71 | 72 | "@types/resolve@1.17.1": 73 | version "1.17.1" 74 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" 75 | integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== 76 | dependencies: 77 | "@types/node" "*" 78 | 79 | balanced-match@^1.0.0: 80 | version "1.0.0" 81 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 82 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 83 | 84 | brace-expansion@^1.1.7: 85 | version "1.1.11" 86 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 87 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 88 | dependencies: 89 | balanced-match "^1.0.0" 90 | concat-map "0.0.1" 91 | 92 | builtin-modules@^3.1.0: 93 | version "3.1.0" 94 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" 95 | integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== 96 | 97 | chessground@^7.9.3: 98 | version "7.9.3" 99 | resolved "https://registry.yarnpkg.com/chessground/-/chessground-7.9.3.tgz#dec20a45afe439fe9ed961836a7e5a9157087141" 100 | integrity sha512-6vU1q3u/69aHt/Y/UwNk9h9BxPhOuJl+xTQH/taicVZcVY7VcUEUpt+yvVGgglooPgOzM/uj2Rzv5Pcaj/R2/A== 101 | 102 | chessops@^0.7.3: 103 | version "0.7.3" 104 | resolved "https://registry.yarnpkg.com/chessops/-/chessops-0.7.3.tgz#ecd38943fb0135625cd710c5ac68f1c624a147da" 105 | integrity sha512-lMh+gibYSvpMYf4Tty3l3DO6XL+MpDynyCZN7UcGdVIsbfjGzGUAA6EJuNR4QTGrt4D8o5oqiNvdrtUtJ/lH+Q== 106 | dependencies: 107 | "@badrap/result" "^0.2.6" 108 | 109 | commondir@^1.0.1: 110 | version "1.0.1" 111 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 112 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= 113 | 114 | concat-map@0.0.1: 115 | version "0.0.1" 116 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 117 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 118 | 119 | deepmerge@^4.2.2: 120 | version "4.2.2" 121 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" 122 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== 123 | 124 | estree-walker@^1.0.1: 125 | version "1.0.1" 126 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 127 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 128 | 129 | estree-walker@^2.0.1: 130 | version "2.0.1" 131 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" 132 | integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== 133 | 134 | fs.realpath@^1.0.0: 135 | version "1.0.0" 136 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 137 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 138 | 139 | fsevents@~2.1.2: 140 | version "2.1.3" 141 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" 142 | integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== 143 | 144 | glob@^7.1.6: 145 | version "7.1.6" 146 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 147 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 148 | dependencies: 149 | fs.realpath "^1.0.0" 150 | inflight "^1.0.4" 151 | inherits "2" 152 | minimatch "^3.0.4" 153 | once "^1.3.0" 154 | path-is-absolute "^1.0.0" 155 | 156 | inflight@^1.0.4: 157 | version "1.0.6" 158 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 159 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 160 | dependencies: 161 | once "^1.3.0" 162 | wrappy "1" 163 | 164 | inherits@2: 165 | version "2.0.4" 166 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 167 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 168 | 169 | is-module@^1.0.0: 170 | version "1.0.0" 171 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 172 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 173 | 174 | is-reference@^1.2.1: 175 | version "1.2.1" 176 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" 177 | integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== 178 | dependencies: 179 | "@types/estree" "*" 180 | 181 | magic-string@^0.25.7: 182 | version "0.25.7" 183 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 184 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 185 | dependencies: 186 | sourcemap-codec "^1.4.4" 187 | 188 | minimatch@^3.0.4: 189 | version "3.0.4" 190 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 191 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 192 | dependencies: 193 | brace-expansion "^1.1.7" 194 | 195 | mousetrap@^1.6.5: 196 | version "1.6.5" 197 | resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" 198 | integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== 199 | 200 | once@^1.3.0: 201 | version "1.4.0" 202 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 203 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 204 | dependencies: 205 | wrappy "1" 206 | 207 | path-is-absolute@^1.0.0: 208 | version "1.0.1" 209 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 210 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 211 | 212 | path-parse@^1.0.6: 213 | version "1.0.6" 214 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 215 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 216 | 217 | picomatch@^2.2.2: 218 | version "2.2.2" 219 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 220 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 221 | 222 | resolve@^1.17.0: 223 | version "1.17.0" 224 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" 225 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== 226 | dependencies: 227 | path-parse "^1.0.6" 228 | 229 | rollup@^2.32.0: 230 | version "2.32.0" 231 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.32.0.tgz#ac58c8e85782bea8aa2d440fc05aba345013582a" 232 | integrity sha512-0FIG1jY88uhCP2yP4CfvtKEqPDRmsUwfY1kEOOM+DH/KOGATgaIFd/is1+fQOxsvh62ELzcFfKonwKWnHhrqmw== 233 | optionalDependencies: 234 | fsevents "~2.1.2" 235 | 236 | snabbdom@^0.7.4: 237 | version "0.7.4" 238 | resolved "https://registry.yarnpkg.com/snabbdom/-/snabbdom-0.7.4.tgz#817f07e8d3fb870960c3763b8da56f1ba982d31a" 239 | integrity sha512-nnN+7uZ2NTIiu7EPMNwSDhmrYXqwlfCP/j72RdzvDPujXyvQxOW7Jl9yuLayzxMHDNWQR7FM6Pcn4wnDpKRe6Q== 240 | 241 | sourcemap-codec@^1.4.4: 242 | version "1.4.8" 243 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 244 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 245 | 246 | tslib@^2.0.3: 247 | version "2.0.3" 248 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" 249 | integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== 250 | 251 | typescript@^4.0.3: 252 | version "4.0.3" 253 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" 254 | integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== 255 | 256 | wrappy@1: 257 | version "1.0.2" 258 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 259 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 260 | --------------------------------------------------------------------------------