Ahrens Labs

Coding Lab

TrifangX

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.

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.

Play vs TrifangX Pick sides, clocks, and board style in the browser lobby. The engine replies from a hosted Python service.
Live game page When you start from the lobby, play moves to TrifangX Live — a dedicated page for your in-progress engine game that survives reload without dropping the session.
Game history & replays Signed-in players keep recent games, step through moves on the board, and open shareable replay links from game history (chess-replay/…).
Daily challenges Three deterministic goals per calendar day (e.g. win with a tactic, reach an endgame). Completing them feeds achievements.
Achievements & career points Long-term goals track captures, openings, streaks, and more. Points show on the career leaderboard.
Season track Monthly UTC steps unlock gradient boards, pieces, leaderboard row colors, and flair frames/titles.
Chess shop Spend achievement points on boards, piece sets, highlight colors, arrows, themes, and checkmate effects.
Cloud profile Stats, unlocks, equipped cosmetics, and settings sync to your Ahrens Labs account across devices.

How you play it here

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.

  • Open Chess Engine from the site menu to start a game against the computer.
  • While the engine searches, the UI may show a short wait — reload-safe duplicate requests return the same move without restarting the search.
  • Customize which header links you see from the account dashboard.
  • Optional email: daily challenge roundups from the dashboard if you want all three goals in your inbox.

How the site talks to the engine

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.).

/start game_id + snapshot /move search /move_result SAN reply
  • /start — allocates a session, captures engine globals into a pickle-friendly dict, and can bind the game to your account username.
  • /move — applies your move, runs _compute_engine_move_reply, returns 202 + job_id (or a synchronous 200 when configured).
  • /move_result/<job_id> — client polls here so long searches do not block the web worker on a single HTTP connection.
  • /heartbeat and /stop — keep sessions alive or tear them down when you leave.
  • /modifiers — read or tweak 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.

Inside the engine: board and rules

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.

# TrifangX.py — starting position board = [ ['R','N','B','Q','K','B','N','R'], ['P','P','P','P','P','P','P','P'], # ... four empty ranks ... ['p','p','p','p','p','p','p','p'], ['r','n','b','q','k','b','n','r'], ]

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.

Static evaluation (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.

Game phase

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.

Piece-specific terms

  • 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.

Modifiers and captures

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.

Server design for real traffic

Hosting taught a few hard lessons that shaped the current architecture:

  • Per-game snapshotsGAMES[game_id]['snapshot'] stores a full copy of engine globals so many browser tabs can play without clobbering each other.
  • Async moves — default 202 + background thread avoids uWSGI workers blocking for tens of seconds during deep lines; clients poll /move_result.
  • Dedicated subprocess per game — when enabled, each game_id can own a worker process so concurrent games do not serialize on one Python GIL; fork pool and inline paths are fallbacks.
  • Account limits — games can require username on /move so only the owner drives that session; caps limit concurrent games per account.
  • Performance switches — environment flags like 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.