Analyze chess evidence#

Exact facts, move effects, authored puzzle correctness, and counterfactual validity are different kinds of evidence. This notebook keeps them separate while using one ordinary python-chess board. No neural model is required.

[1]:
import chess

from lczerolens import (
    Puzzle,
    PuzzleContinuation,
    PuzzleSolution,
    analyze_line,
    analyze_move,
    sibling_counterfactual,
)
from lczerolens.facts import FactPerspective, MaterialAnalyzer

board = chess.Board()
white_material = MaterialAnalyzer(FactPerspective.WHITE).analyze(board)
move = analyze_move(board, "e2e4")
line = analyze_line(board, ["e2e4", "e7e5", "g1f3"])
{
    "white_material": white_material.value,
    "move_effects": [effect.value for effect in move.effects],
    "changed_facts": len(move.changed),
    "line_plies": len(line.steps),
    "history_complete": line.final_position.history_complete,
    "terminal": line.terminal.is_terminal,
}
[1]:
{'white_material': 39,
 'move_effects': [],
 'changed_facts': 18,
 'line_plies': 3,
 'history_complete': True,
 'terminal': False}

A sibling counterfactual compares two legal children of the same parent. Its validity and history guarantee are explicit; they do not say that either move is strategically better.

[2]:
pair = sibling_counterfactual(board, factual="e2e4", alternative="d2d4")
assert pair.succeeded
{
    "validity": pair.validity.value,
    "shared_parent": pair.history.shared_parent,
    "reachability_proven": pair.history.reachability_proven,
    "factual_fen": pair.factual.fen,
    "alternative_fen": pair.alternative.fen,
}
[2]:
{'validity': 'history_consistent',
 'shared_parent': True,
 'reachability_proven': True,
 'factual_fen': 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
 'alternative_fen': 'rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1'}

Puzzle correctness comes from an authored solution tree. A solution leaf may mark the task solved even if the resulting chess position is not terminal.

[3]:
task = Puzzle.from_board(
    board,
    PuzzleSolution((PuzzleContinuation("e2e4"), PuzzleContinuation("d2d4"))),
)
accepted = tuple(move.uci() for move in task.accepted_moves())
solved = task.grade(["e2e4"])
failed = task.grade(["g1f3"])
{"accepted": accepted, "solved": solved.status.value, "failed": failed.status.value}
[3]:
{'accepted': ('e2e4', 'd2d4'), 'solved': 'solved', 'failed': 'failed'}