lichess.org
Donate

Picture By Sasun Bughdaryan

Agine Analyzer

Software DevelopmentPuzzleAnalysis boardChess engine
An open source tool that does deep analysis for a Lichess player

Intro

Hey fellow Lichess readers, I'm back with another cool tool I have been building on the side, which I want to talk about. It's called Agine Analyzer. I think this tool is quite useful to many chess players because I think the ability to have deeper stats reports based on data rather than guessing might give a real glimpse into a chess player's weakness/strengths and more, so let's get into it!

Analyzing my games

It all started about a month ago, when I was constantly checking the Lichess analysis after every game I played. What I was doing was basically finding where I made mistakes and blunders and basically having these positions stored in my private Lichess study so I could review these positions the other morning and basically follow this route to find where I was going wrong. The thing I was exploring here was hidden patterns within my own gameplay which the opponents can exploit; this was purely inspired by how software teams do penetration tests on their own software: they pay ethical hackers to hack their systems and find vulnerabilities in code which get fixed before it is even released to the public. The idea for me was similar; I was manually trying to find these patterns, which are backed by actual data and not by guesswork, because I wanted some kind of report that I manually create via the Lichess study, lets say N positions where I messed up and after I review 64 positions I would find some trend, maybe all positions are just endgames and I need to work on them, or I'm messing up in open positions more than closed one. The bottleneck here, of course, is that in theory this sounds great, but after reviewing my 3rd game, I was tired of manually creating the study, finding the mistake position and copying the fen into the chapter. I was tired, I think, because I generally make more mistakes and blunders than I imagined (I suck at chess) so I pulled the programmer move by trying to automating this entire process, because I can't sit there and manually make this studies, because I wanted to not look at 3, 5, 10, but 200 games and get rich data which helps me make correct decisions!

Analyzer Algo

I did like to go a little deep on the technical details of how I designed this agine analyzer algo, because without some technical speak noob's blogs are not fun ;P

The idea to scan up to 200 games in theory is, of course, nice, but the brutal part of it is that I wanted this to happen in the browser inside the Chessagine web app on a specific page, agine-analyzer. I basically wanted an easy user experience for myself because I did imagine doing this every month or two so since I be reviewing 25 to 100 games per month, I didn't want to write some Python script because I'm a lazy chess player and need things simple to get the data reports in front of me without me doing same running that script manually again and again, I was imagining clicking a button and boom the report is done.

Of course, these are some high expectations; scanning 200 games after clicking a button on the web app, because the moment I tried writing an algorithm for 10 games with the browser Stockfish engine, it did take about 5 mins to create such a report. The brute force algo I came up with was this, which I will describe in simple pseudocode

func analyzer()
     for each game I played
        parse the game into a list of fens
              for each fen, turn engine on and calculate win Rate drop
                            if win rate drop > 20% move is blunder else win rate drop > 10% its a mistake
              end loop
     end loop   
                  

As you can see, the runtime complexity is around 0(n^2) because we are scanning a set of games for a max size of 10, and inside those games we are turning on the engine for each fen (board representation before/after) to calculate the win rate drop to determine blunder/mistakes. Let's say, on average, a chess game is 40 moves; each move gets before/after, and we omit the first and last fens that are (40 x 2 ) - 2 = 78 fens per game. For 10 games, that's 780 fens. Now imagine running a real Stockfish search on 780 positions in the browser, one at a time, on a worker thread that's also supposed to keep the UI responsive. That's basically what melted my laptop fan on attempt one. Scale that brute force idea up to 200 games, the actual number I wanted to support, and you're somewhere north of 15,000 engine calls. At even half a second per shallow eval, that's over two hours of pure engine grinding before you see a single chart. Not exactly "click a button and boom."

So the brute force version got scrapped, and I had to actually think about where the cost was coming from instead of just throwing more depth/threads at it.

Greedy Agine Analyzer Algo

The realization was that I was treating every single position as equally expensive to evaluate, when in reality most positions don't need MY engine at all. A huge chunk of any amateur's games are opening theory, and a good chunk of the rest have already been evaluated by somebody else, either Lichess itself or the broader engine community via cloud databases like Chessdb. Running Stockfish locally should be the last resort, not the default. That's the greedy insight: at every position, take the cheapest source of truth available and only fall through to the expensive option when you have to.
So instead of one loop that always calls the engine, the analyzer became a three-tier cascade, cheapest-first:

func greedyAnalyzer()
   for each game
       if game has Lichess server analysis
           use it directly, zero local cost
           continue
       
       parse game into fens, skip known opening book plies
       for each remaining fen (in parallel, 8 at a time)
           ask ChessDB cloud eval database
           if ChessDB has an eval
               use it
           else
               queue it for local Stockfish
       end loop

       for each queued fen (reverse move order, shallow depth)
           run local Stockfish
       end loop
   end loop

A few things are doing the heavy lifting here

  • Tier 1 — Lichess server analysis. If a game was already analyzed (the analysis array Lichess gives you when you fetch with evals=true). Zero engine calls, zero ChessDB calls. A huge percentage of rated games already have this sitting there for free.
  • Tier 2 — ChessDB cloud lookups, fired in parallel. For unanalyzed games, every non-book position gets queried against ChessDB's cloud eval database through fetchChessDBFast(), run 8 at a time with a small parallelLimit helper rather than sequentially. Network calls overlap, so this tier is mostly bottlenecked by the slowest single request, not the sum of all of them. Results get cached in IndexedDB too, so re-running an analysis on the same username doesn't re-fetch positions it already knows.
  • Tier 3 — Shallow local Stockfish, only for the leftovers. Only the fens ChessDB couldn't answer actually touch the local engine, and even then at a shallow depth (default depth 12), not a deep search. They also run in reverse move order, same trick I'd used in the single-game reviewer, so the transposition table stays warm as you walk backward through a game, instead of starting cold on every call.
  • Opening book skip. Plies inside the game's known opening never get queried anywhere. They're pinned to a neutral baseline win rate, because nobody needs an engine to tell them book theory is fine.

The complexity didn't magically become better Big-O; in the worst case (a brand new unrated game with no server analysis and ChessDB drawing a blank everywhere), you still fall back toward the brute force shape. But the average case, which is what 95% of real games look like, collapses dramatically: most plies get answered by tier 1 or tier 2, and only a small residual touches the slow path. That's the whole game with browser-side engines — you're not allowed to assume you have unlimited compute, so you design the algorithm around skipping work you don't actually need to do, not around making the work itself faster.
That's also why the puzzle pack step at the end uses the same logic in miniature: when validating that a flagged blunder/mistake position is actually puzzle-worthy, it first tries any best-move hint already attached, then ChessDB, and only drops to a slightly deeper local Stockfish search (depth 14) as the final fallback capped at 200 candidate positions so that step doesn't quietly become its own bottleneck

Agine Analyzer Walkthrough

Now that I explained some technical parts of the agine analyzer, let me write some steps that you can follow to use the analyzer here

  1. Pick a mode. Single Player Report for your own games, or Compare 2 Players if you want to put your stats side by side with a friend or rival.
  2. Enter a Lichess username. You can optionally log in with Lichess on the settings page here
  3. Set how many games to pull, anywhere from 5 up to 200.
  4. (Optional) Open Advanced settings to filter by perf type (bullet, blitz, rapid, classical, etc.), filter rated vs. casual, tune the local engine depth, and adjust the blunder/mistake win-rate thresholds (20%/10% by default) if you want a stricter or looser bar for what counts as a mistake.
  5. Hit the button. You'll watch three progress phases: downloading games from Lichess, analyzing them game-by-game (the label even tells you whether a given game used Lichess analysis or fell to local engines), and finally building your puzzle pack from the worst blunders found.

image.png
After the analysis is complete, you can view the report, and after you connect your Lichess account in setting page, you can export the puzzle packs into Lichess study by clicking on the cloud icon.

Conclusion

What I ended up building is basically the automated version of the manual study-building work I described at the start of the blog: instead of me hand-picking FENs into a Lichess study after every game and squinting for patterns across a handful of them, Agine Analyzer pulls up to 200 games, leans on free server/cloud evals wherever possible, and only spends real engine cycles where it has to. The output is the same kind of report I was chasing manually, opening weaknesses, accuracy trends, recurring blunder patterns, a puzzle pack made entirely of your own mistakes, except it now takes minutes instead of the multiple tired evenings I burned building it by hand. I think this tool has many benefits to the chess community, and that's why I have open-sourced ChessAgine Analyzer and made it so anyone can use it without a ChessAgine account. I imagine chess coaches can generate analyzer reports on their students' Lichess games and find various trends in their gameplay, and help them solve puzzles from their own games. The analyzer can also be used to prep against a Lichess player by scanning their last 10 games and seeing which openings they play, where they make most mistakes, and what types of blunders they make to better prep in advance.

Hopefully you find this tool useful! Let me know what improvements I can make to this tool in the comments or any other feedback you have!

Thanks for reading,

Noob