app/puzzle_session.h

solving a tactics puzzle on the physical board

               (docs/lichess-docs/PLAN.md §6).
A sibling of GameSession: same worker-thread + listener shape, driving the same IBoardBackend, but the opponent is not an engine — it is the puzzle’s scripted solution. The board sets the position up, lights the move that created the tactic, waits for the solver, judges each try against the solution, and plays the refutation back.

Two things are load-bearing and easy to get wrong:

  1. THE SESSION OWNS THE BOARD’S IDENTITY. It keeps a PieceTracker across puzzles, so the next puzzle’s setup is planned as a delta from where the pieces physically are (PLAN §4). A puzzle position is ~20-25 pick-and-places; resetting to the start between puzzles would make the feature unusably slow. This is also why solve() does not reset the board, and why the caller must tell the session the truth via prepare() or setBoardIdentity() if the pieces are ever moved behind its back.

  2. THERE IS NO HINT PATH, BY CONSTRUCTION. Solving a Lichess puzzle on the board moves the player’s real Lichess puzzle rating, and Lichess forbids engine assistance. So this session takes no IHintSource — not one that is switched off, one that does not exist (PLAN §8). puzzle_session_test static_asserts that no such constructor can be added.

Pure: no HTTP, no Qt, no hardware. Runs against SimBackend in CTest.

namespace kirin

Enums

enum class PuzzleState

Values:

enumerator Idle
enumerator Setup
enumerator Mismatch
enumerator Showing
enumerator AwaitingMove
enumerator Replying
enumerator Solved
enumerator Failed
enumerator Error
struct PuzzleResult
#include <puzzle_session.h>

Public Members

std::string id
bool win = false
bool rated = true
class PuzzleSession
#include <puzzle_session.h>

Public Functions

PuzzleSession(IEngine &engine, IBoardBackend &backend)
~PuzzleSession()
PuzzleSession(const PuzzleSession&) = delete
PuzzleSession &operator=(const PuzzleSession&) = delete
void addListener(PuzzleSessionListener *listener)
void start()
void solve(const Puzzle &puzzle, const PuzzleSessionConfig &config = {})
void submitHumanMove(const std::string &uci)
void stop()
void shutdown()
inline PuzzleState state() const
bool prepare(ResetMode mode = ResetMode::Auto)
void setBoardIdentity(const BoardIdentity &identity)
BoardIdentity boardIdentity() const
int pickNext(const std::vector<Puzzle> &batch, const PuzzleSessionConfig &config = {}) const

Private Types

enum class CmdType

Values:

enumerator Solve
enumerator Stop
enumerator Shutdown

Private Functions

void run()
void solvePuzzle(const Puzzle&, const PuzzleSessionConfig&)
bool setUpPosition(const Puzzle&, const PuzzleSessionConfig&)
bool awaitCorrectSetup()
bool isSolverMoveCorrect(const Puzzle&, size_t ply, const HumanMoveResult&)
void commitMove(int encodedMove)
void enqueue(Command cmd)
void setState(PuzzleState s)
void emitSetupBegan(int ops)
void emitSetupEnded(int ops, int elapsedMs)
void emitMismatch(const BoardMismatchInfo&)
void emitReady(const Puzzle&, const std::string &lastMove)
void emitCorrect(const std::string &uci)
void emitWrong(const std::string &uci, const std::string &expected)
void emitReply(const std::string &uci)
void emitFinished(const PuzzleResult&)
void emitInfo(const std::string&)
void emitError(const std::string&)

Private Members

IEngine &engine_
IBoardBackend &backend_
std::vector<PuzzleSessionListener*> listeners_
std::thread worker_
std::mutex cmdMutex_
std::condition_variable cmdCv_
std::deque<Command> cmdQueue_
std::atomic<bool> stopRequested_ = {false}
std::atomic<bool> shutdownRequested_ = {false}
std::atomic<PuzzleState> state_ = {PuzzleState::Idle}
mutable std::mutex identityMutex_
PieceTracker tracker_

Private Static Attributes

static constexpr int kMismatchPollMs = 500
struct Command

Public Members

CmdType type
Puzzle puzzle
PuzzleSessionConfig config
struct PuzzleSessionConfig
#include <puzzle_session.h>

Public Members

bool rated = true
FenIdentityOptions identity
class PuzzleSessionListener
#include <puzzle_session.h>

Subclassed by kirin::PuzzleLedAssist

Public Functions

virtual ~PuzzleSessionListener() = default
inline virtual void onStateChanged(PuzzleState)
inline virtual void onSetupBegan(int ops)
inline virtual void onSetupEnded(int ops, int elapsedMs)
inline virtual void onBoardMismatch(const BoardMismatchInfo&)
inline virtual void onPuzzleReady(const Puzzle&, const std::string &lastMove)
inline virtual void onCorrectMove(const std::string &uci)
inline virtual void onWrongMove(const std::string &uci, const std::string &expected)
inline virtual void onReply(const std::string &uci)
inline virtual void onPuzzleFinished(const PuzzleResult&)
inline virtual void onInfo(const std::string&)
inline virtual void onError(const std::string&)