What you get on Ahrens Labs
TrifangX is more than a single-player opponent. The site wraps the engine in accounts, cloud saves, cosmetics, and progression systems so games feel like part of a larger chess profile.
chess-replay/…).
A full chess engine and browser platform built by Caleb Ahrens — play against the computer, earn career points,
customize your board, and read how the Python search in TrifangX.py actually works.
TrifangX is more than a single-player opponent. The site wraps the engine in accounts, cloud saves, cosmetics, and progression systems so games feel like part of a larger chess profile.
chess-replay/…).
The board, clocks, and UI run entirely in your browser (js/trifangx_chess_app.js). When it is
TrifangX’s turn, the client POSTs your move to the engine server and polls until the reply is ready.
TrifangX.py is a Flask app that exposes a small HTTP API. Each active game gets a
game_id and its own engine snapshot (board, move list, castling flags, repetition counts, etc.).
_compute_engine_move_reply, returns 202 + job_id (or a synchronous 200 when configured).SCORING_MODIFIERS to change how aggressively the evaluator weights material, king safety, pawn structure, and activity.
Move deduplication uses the client’s chess.js history length (ply) so a tab reload mid-think
does not enqueue a second identical search.
The board is an 8×8 list of lists. Empty squares are '0'; white pieces are uppercase
(P N B R Q K) and black are lowercase. This representation is fast to copy and hash for caching.
Move generation and legality live in large helper layers: pawn pushes and captures (including en passant),
sliding pieces, knights, castling (0-0 / 0-0-0), and promotions (queen and knight only
in the search). The server accepts SAN or long algebraic from the client and normalizes through
clean_move, convert_to_long_algebraic, and players_turn /
players_turn_white before searching.
Special outcomes are encoded in search results: checkmate returns large sentinel scores (±10000), self-blunders that walk into mate return −1000, threefold repetition scores −0.25, and draws detected during reply search are penalized similarly.
White replies call best_move_black; black (engine as white) calls best_move_function.
Each function enumerates every legal move for the side to move, scores candidates, and picks the best line.
The core lookahead is a two-ply reply model inside evaluate_white and
evaluate_black: for a candidate engine move, TrifangX simulates the opponent’s best reply
(best_move_player), then its own follow-up (best_move2), evaluates the resulting
position with score(board, turn), then unwinds the board. That is how the engine “sees” tactics
one move deeper without a full alpha-beta tree.
Before brute-force search, best_move_function builds a PGN-style prefix from
game_moves and matches it against a built-in list of full games (Ruy Lopez, Sicilian, French,
Caro-Kann, Alekhine, and others). opening_book_next_move returns the next SAN from the first
matching line; those moves are injected into result_scores with a high priority so the engine
plays known theory when the position still fits a book line.
A small good_moves tuple list nudges scores for familiar developing moves (center pawns, knight
hops, bishop development) so early play stays principled even off-book.
score)
Leaf and mid-search positions are judged by _score_uncached, wrapped in an LRU cache
(_score_cached, up to 200k entries) keyed by board hash, side to move, castling flags, and a
SCORING_VERSION that bumps when modifiers change.
The evaluator detects opening, middlegame, and endgame from material count, queen presence, development (knights/bishops off back rank), and castling. Phase weights blend pawn structure, king safety, and piece activity differently — endgames lean on material and king activity; openings still reward development and central control.
score_pawn — chains, passed pawns, holes, and advancement.score_knight / score_bishop — outposts, mobility, bishop pair, light/dark square coverage.score_rook — open/semi-open files, seventh rank, connectivity.score_queen — centralization and safe squares.score_king — shelter, distance to enemy pieces, endgame activity.score_holes — weak squares near kings in non-endgame positions.
SCORING_MODIFIERS scales buckets like material, king_safety_b/w,
attack_b/w, piece_activity_b/w, and pawn_structure_b/w. The HTTP
/modifiers endpoint updates them at runtime and clears caches so personality tweaks take effect
immediately.
Hanging-piece logic adds bonuses for safe captures and penalties for leaving pieces en prise via
_best_protected_capture_adjustment and _best_unprotected_capture_adjustment.
Hosting taught a few hard lessons that shaped the current architecture:
GAMES[game_id]['snapshot'] stores a full copy of engine globals so many browser tabs can play without clobbering each other.202 + background thread avoids uWSGI workers blocking for tens of seconds during deep lines; clients poll /move_result.game_id can own a worker process so concurrent games do not serialize on one Python GIL; fork pool and inline paths are fallbacks.username on /move so only the owner drives that session; caps limit concurrent games per account.TRIFANGX_SYNC_MOVE_SINGLE, TRIFANGX_DISABLE_DEDICATED_WORKERS, and TRIFANGX_FORCE_MOVE_POOL tune latency vs. isolation for different deploy targets.
Multiprocessing inside every move was disabled by default (ENABLE_MULTIPROCESSING = False) because
spawning pools per request on shared hosting was slower than in-process search; the dedicated-worker model is the
preferred way to use multiple cores when available.
The live site is the best demo. For implementation detail, TrifangX.py in this repository is the
authoritative engine and Flask server; the browser client is js/trifangx_chess_app.js.