Skip to content
Back to skills

Chess Engine Mechanic Builder

ASecurity

Wraps python-chess inside a SkillChessBoard that adds turn tracking, per-skill cooldown ticks, a Teleport mechanic and a Freeze Square mechanic with expiry timers, plus rotate_view_180 coordinate mapping, a pygame-free ASCII renderer and an optional pygame renderer, all driven by a runnable demo.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 27, 2026
developmentpythonawsapi

Works with

  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 27, 2026

npx -y skills add RMAzrim/opencode-artes --skill chess-engine-mechanic-builder --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Chess Engine Mechanic Builder?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Chess Engine Mechanic Builder
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rmazrim-chess-engine-mechanic-builder/badge)](https://www.skillsdirectory.com/skills/rmazrim-chess-engine-mechanic-builder)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Custom Chess Engine Mechanic Builder
description: Wraps python-chess inside a SkillChessBoard that adds turn tracking, per-skill cooldown ticks, a Teleport mechanic and a Freeze Square mechanic with expiry timers, plus rotate_view_180 coordinate mapping, a pygame-free ASCII renderer and an optional pygame renderer, all driven by a runnable demo.
metadata:
  source: skills/chess-engine-mechanic-builder/chess-engine-mechanic-builder.md
---

# Custom Chess Engine Mechanic Builder

## 1. System Architecture & Prerequisites

This skill builds a variant chess engine on top of `python-chess` while
keeping the logic layer completely pygame-free. The architecture splits into
four parts:

- **`SkillChessBoard`** - a wrapped `chess.Board`. It mirrors the standard
  rule engine (`push_uci`, `legal_moves`, `is_game_over`) and adds
  `turn_player` bookkeeping, a JSON-serialisable `cooldowns` dictionary, and
  a `log` of `MoveEntry` records so gameplay can be replayed/reported.
- **Skill mechanics** - `apply_teleport(sq_from, sq_to)` relocates a friendly
  piece instantly (legal moves route through `board.push_uci`; impossible
  movements use a variant fallback with manual turn advance) and
  `apply_freeze(sq)` marks a square frozen. A frozen square's occupant is
  skipped by `legal_moves()` until its expiry ticks run out.
- **Cooldown scheduler** - `cooldown_ticks` defines honest cooldowns per
  skill; `can_cast(skill)` gates casting, `tick_cooldowns()` decays both skill
  cooldowns and freeze expiries as the turn clock advances.
- **View layer** - `rotate_view_180(board_ui)` maps file/rank indices for a
  180-degree board rotation, `render_ascii()` is a pure-text renderer that
  works without any GUI library, and `render()` draws the same state on a
  pygame surface guarded by an `ImportError` fallback.

Prerequisites:

| Dependency        | Required?            | Install                         |
| ----------------- | -------------------- | ------------------------------- |
| Python 3.9+       | yes                  | -                               |
| `python-chess`    | yes (logic layer)    | `python -m pip install python-chess` |
| `pygame` 8.0+     | only for `render()`  | `python -m pip install pygame`  |

The core module imports cleanly with only `python-chess` installed; scene
rendering degrades to no-ops when pygame is absent.

## 2. Input/Output Data Contracts

### 2.1 Input JSON Schema - `SkillChessBoard` options

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "title": "SkillChessBoardConfig",
  "additionalProperties": false,
  "properties": {
    "start_fen": {
      "type": "string",
      "default": "",
      "description": "Initial FEN; empty string starts the standard game"
    },
    "cooldown_ticks": {
      "type": "object",
      "properties": {
        "teleport": { "type": "integer", "minimum": 0, "default": 6 },
        "freeze": { "type": "integer", "minimum": 0, "default": 9 }
      }
    },
    "freeze_duration": {
      "type": "integer",
      "minimum": 0,
      "default": 3
    }
  }
}
```

### 2.2 Output contracts

- `cooldowns: Dict[str, int]` - remaining ticks per skill after the last cast
  (0 means ready).
- `frozen: Set[int]` - chess square indices currently frozen.
- `frozen_expiry: Dict[int, int]` - remaining freeze ticks per frozen square.
- `log: List[MoveEntry]` - ordered `MoveEntry(kind, detail, turn, cooldowns)`.
- `render_ascii(board, view_rotated=False, frozen=None) -> str` - 8 x 8 text
  board, uppercase = white, lowercase = black, `.` = empty, frozen cells
  shown as `[p]`.
- `render(screen, board, ...)` - optional pygame draw, returns `None` when
  pygame is missing.

### 2.3 Output artifacts

| Artifact                 | Path                                                    | Format |
| ------------------------ | ------------------------------------------------------- | ------ |
| Reference implementation | `skills/chess-engine-mechanic-builder/chess_mechanics.py` | Python |
| Demo transcript          | printed by `main()` (moves, teleport, freeze, cooldowns) | stdout |

## 3. Production Reference Implementation

```python
"""chess_mechanics.py - custom chess engine with skill cooldowns.

Wraps python-chess inside a SkillChessBoard that adds turn tracking,
per-skill cooldown ticks, a Teleport mechanic and a Freeze Square mechanic
with expiry timers.  Rendering ships as a pygame-free ASCII view plus an
optional pygame surface renderer imported lazily, so the logic layer never
requires pygame.

Hard dependency (logic):  python-chess   (pip install python-chess)
Optional dependency:      pygame 8.0+    (used only by render())
Run directly:  python chess_mechanics.py
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple

try:
    import chess
    from chess import Board, Piece
except ImportError as exc:  # pragma: no cover - user-facing guard
    raise ImportError(
        "python-chess is required:  python -m pip install python-chess"
    ) from exc

COOLDOWN_TICKS: Dict[str, int] = {"teleport": 6, "freeze": 9}
FREEZE_DURATION_TICKS = 3
SKILL_NAMES = tuple(COOLDOWN_TICKS)

PIECE_GLYPH = {
    chess.PAWN: "p",
    chess.KNIGHT: "n",
    chess.BISHOP: "b",
    chess.ROOK: "r",
    chess.QUEEN: "q",
    chess.KING: "k",
}

try:
    import pygame
    PYGAME_OK = True
except ImportError:  # pragma: no cover - optional dependency
    pygame = None  # type: ignore[assignment]
    PYGAME_OK = False


@dataclass
class MoveEntry:
    """A recorded action for replay / reporting."""

    kind: str
    detail: str
    turn: str
    cooldowns: Dict[str, int] = field(default_factory=dict)


class SkillChessBoard:
    """A chess.Board wrapper with fantasy mechanics.

    Standard chess moves are pushed through ``board.push_uci`` (the rules
    engine stays authoritative).  Skill moves are applied as board-level
    mutations and logged as ``kind in {teleport, freeze}`` entries so the
    flavour never desyncs the main rule book.
    """

    def __init__(
        self,
        start_fen: str = "",
        cooldown_ticks: Optional[Dict[str, int]] = None,
        freeze_duration: int = FREEZE_DURATION_TICKS,
    ) -> None:
        self.board: Board = chess.Board(start_fen) if start_fen else chess.Board()
        self.turn_player: str = "white" if self.board.turn else "black"
        self.cooldown_ticks: Dict[str, int] = dict(COOLDOWN_TICKS)
        if cooldown_ticks:
            self.cooldown_ticks.update(cooldown_ticks)
        self.cooldowns: Dict[str, int] = {name: 0 for name in SKILL_NAMES}
        self.freeze_duration = freeze_duration
        self.frozen: Set[int] = set()
        self.frozen_expiry: Dict[int, int] = {}
        self.log: List[MoveEntry] = []

    # -- turn bookkeeping ----------------------------------------------------

    def increment_turn(self) -> None:
        """Manually advance the player turn (used by variant teleports)."""
        self.turn_player = "black" if self.turn_player == "white" else "white"

    def _sync_turn(self) -> None:
        """Re-derive ``turn_player`` from the authoritative chess board."""
        self.turn_player = "white" if self.board.turn else "black"

    # -- standard moves ------------------------------------------------------

    def push_uci(self, uci: str) -> None:
        """Push a standard move through the rule engine."""
        self.board.push_uci(uci)
        self._sync_turn()
        self.log.append(MoveEntry("move", uci, self.turn_player,
                                  dict(self.cooldowns)))

    def legal_moves(self) -> Iterable[Any]:
        """All legal moves except those whose FROM square is frozen."""
        for move in self.board.legal_moves:
            if move.from_square in self.frozen:
                continue
            yield move

    def can_move_from(self, square: int) -> bool:
        """True when the occupant of ``square`` may legally be moved."""
        return square not in self.frozen

    def piece_at(self, square: int) -> Optional[Piece]:
        return self.board.piece_at(square)

    # -- skill API -----------------------------------------------------------

    def can_cast(self, skill: str) -> bool:
        """A skill is castable while its cooldown is spent and a game lives."""
        if skill not in self.cooldowns:
            return False
        if self.board.is_game_over():
            return False
        return self.cooldowns[skill] <= 0

    def tick_cooldowns(self, ticks: int = 1) -> None:
        """Advance every skill cooldown and freeze expiry by ``ticks``."""
        for _ in range(max(0, ticks)):
            for name in SKILL_NAMES:
                self.cooldowns[name] = max(0, self.cooldowns[name] - 1)
            for sq in list(self.frozen_expiry):
                self.frozen_expiry[sq] -= 1
                if self.frozen_expiry[sq] <= 0:
                    self.frozen.discard(sq)
                    del self.frozen_expiry[sq]

    def apply_teleport(self, sq_from: int, sq_to: int) -> bool:
        """Instantly relocate a friendly piece from ``sq_from`` to ``sq_to``.

        First the move is attempted as a legal chess move via
        ``board.push_uci`` (the turn flips naturally).  When the board
        rejects it as an impossible jump, a variant teleport mutates the
        board directly with ``remove_piece_at`` / ``set_piece_at`` and the
        turn advances manually.  Returns False when the cast cannot happen.
        """
        if not self.can_cast("teleport"):
            return False
        if not (0 <= sq_from < 64 and 0 <= sq_to < 64) or sq_from == sq_to:
            return False
        piece = self.board.piece_at(sq_from)
        if piece is None or piece.color != self.board.turn:
            return False
        target = self.board.piece_at(sq_to)
        if target is not None and target.color == piece.color:
            return False
        actor = self.turn_player
        moved = False
        try:
            uci = chess.square_name(sq_from) + chess.square_name(sq_to)
            candidate = chess.Move.from_uci(uci)
            if candidate in self.board.legal_moves:
                self.board.push_uci(uci)
                moved = True
        except ValueError:
            moved = False
        if moved:
            self._sync_turn()
        else:
            self.board.remove_piece_at(sq_from)
            if target is not None:
                self.board.remove_piece_at(sq_to)
            self.board.set_piece_at(sq_to, piece)
            self.increment_turn()
        self.cooldowns["teleport"] = self.cooldown_ticks["teleport"]
        self.log.append(MoveEntry(
            "teleport",
            "%s->%s" % (chess.square_name(sq_from), chess.square_name(sq_to)),
            actor,
            dict(self.cooldowns),
        ))
        return True

    def apply_freeze(self, square: int) -> bool:
        """Freeze ``square`` so its occupant cannot move for the duration.

        Freezing is a free action: it only costs the skill cooldown, not a
        turn.  The occupant's move options are skipped until the expiry ticks
        elapse (handled by ``tick_cooldowns``).
        """
        if not self.can_cast("freeze"):
            return False
        if not (0 <= square < 64):
            return False
        self.frozen.add(square)
        self.frozen_expiry[square] = self.freeze_duration
        self.cooldowns["freeze"] = self.cooldown_ticks["freeze"]
        self.log.append(MoveEntry("freeze", chess.square_name(square),
                                  self.turn_player, dict(self.cooldowns)))
        return True


def rotate_coords_180(file_: int, rank: int, size: int = 8) -> Tuple[int, int]:
    """Map (file, rank) to its 180-degree rotated position on an 8-rank board."""
    return (size - 1 - file_, size - 1 - rank)


def rotate_view_180(board_ui: Any) -> List[List[Any]]:
    """Rotate a square board view 180 degrees.

    ``board_ui`` is any 8 x 8 nested sequence (list of rows of piece glyphs,
    ASCII grid, pygame colour keys ...).  Rank order is reversed and every
    row is reversed, mapping [file, rank] onto [7 - file, 7 - rank].
    """
    rows: List[Any] = list(board_ui)
    if len(rows) != 8 or any(len(row) != 8 for row in rows):
        raise ValueError("board_ui must be an 8x8 nested sequence")
    return [list(reversed(row)) for row in reversed(rows)]


def _board_rows(board: Any) -> List[List[str]]:
    """Rows of one-char piece symbols with rank 8 on top."""
    if hasattr(board, "board"):
        board = board.board
    rows: List[List[str]] = []
    for rank in range(7, -1, -1):
        row: List[str] = []
        for file_ in range(8):
            piece = board.piece_at(chess.square(file_, rank))
            if piece is None:
                row.append(".")
            elif piece.color == chess.WHITE:
                row.append(PIECE_GLYPH[piece.piece_type].upper())
            else:
                row.append(PIECE_GLYPH[piece.piece_type])
        rows.append(row)
    return rows


def render_ascii(board: Any, view_rotated: bool = False,
                 frozen: Optional[Set[int]] = None) -> str:
    """Text board renderer (pygame-free).

    Uppercase = white, lowercase = black, '.' = empty, frozen cells are
    wrapped as ``[p]``.  ``view_rotated=True`` renders the board from the
    opponent's perspective (180-degree rotation).
    """
    rows = _board_rows(board)
    if view_rotated:
        rows = rotate_view_180(rows)
    lines: List[str] = []
    for i, row in enumerate(rows):
        rank_label = (8 - i) if not view_rotated else (i + 1)
        cells: List[str] = []
        for col, glyph in enumerate(row):
            if frozen:
                file_ = col if not view_rotated else 7 - col
                rank = (7 - i) if not view_rotated else i
                if chess.square(file_, rank) in frozen:
                    glyph = "[%s]" % glyph
            cells.append(glyph)
        lines.append("%d %s" % (rank_label, " ".join(cells)))
    files = "abcdefgh" if not view_rotated else "hgfedcba"
    lines.append("  %s" % " ".join(files))
    return "\n".join(lines)


def render(screen: Any, board: Any, view_rotated: bool = False,
           cell_px: int = 56, square_highlight: Optional[int] = None,
           frozen: Optional[Set[int]] = None) -> None:
    """Optional pygame renderer.  Silently no-ops when pygame is missing.

    Drawing positions follow ``rotate_view_180`` coordinate mapping whenever
    ``view_rotated`` is set, while square lookups keep using real board
    coordinates so pieces stay correct under rotation.
    """
    if pygame is None:
        return
    if hasattr(board, "board"):
        skill_ok = True
        board = board.board
    else:
        skill_ok = False
    palette = {
        "light": (240, 217, 181),
        "dark": (181, 136, 99),
        "frozen": (220, 70, 80),
        "sel": (90, 200, 90),
        "piece": (20, 20, 20),
    }
    board_size = cell_px * 8
    screen.fill((30, 30, 34))
    for rank in range(8):
        for file_ in range(8):
            rx, ry = file_, rank
            if view_rotated:
                rx, ry = rotate_coords_180(file_, rank)
            colour = palette["light"] if ((file_ + rank) % 2 == 0) else palette["dark"]
            sq = chess.square(file_, rank)
            if skill_ok and frozen and sq in frozen:
                colour = palette["frozen"]
            if sq == square_highlight:
                colour = palette["sel"]
            rect = pygame.Rect(rx * cell_px, ry * cell_px, cell_px, cell_px)
            pygame.draw.rect(screen, colour, rect)
            piece = board.piece_at(sq)
            if piece:
                font = pygame.font.Font(None, int(cell_px * 0.8))
                glyph = PIECE_GLYPH[piece.piece_type]
                if piece.color == chess.WHITE:
                    glyph = glyph.upper()
                label = font.render(glyph, True, palette["piece"])
                screen.blit(label, label.get_rect(center=rect.center))
    pygame.display.flip()


def main() -> None:
    """Demo: a short opening line, then Teleport and Freeze casts showing
    the cooldown and freeze-expiry lifecycle, plus both board views."""
    skill = SkillChessBoard()
    print("== Initial position ==")
    print(render_ascii(skill))

    line = ["e2e4", "e7e5", "g1f3", "b8c6"]
    for uci in line:
        skill.push_uci(uci)
    print("\n== After %s ==" % ", ".join(line))
    print(render_ascii(skill))

    e4 = chess.parse_square("e4")
    e7 = chess.parse_square("e7")
    teleported = skill.apply_teleport(e4, e7)
    print("\n== Teleport e4 -> e7 cast %s ==" % ("OK" if teleported else "FAILED"))
    print(render_ascii(skill, frozen=skill.frozen))

    froze = skill.apply_freeze(e7)
    print("\n== Freeze e7 cast %s ==" % ("OK" if froze else "FAILED"))
    print(render_ascii(skill, frozen=skill.frozen))
    if skill.frozen:
        print("Frozen squares: %s"
              % ", ".join(chess.square_name(s) for s in sorted(skill.frozen)))

    print("\n== Cooldowns right after cast ==")
    print(json.dumps(skill.cooldowns, indent=2, sort_keys=True))
    print("can_cast('teleport')=%s can_cast('freeze')=%s"
          % (skill.can_cast("teleport"), skill.can_cast("freeze")))

    frozen_sq = e7
    before = skill.can_move_from(frozen_sq)
    skill.tick_cooldowns(ticks=6)
    after = skill.can_move_from(frozen_sq)
    print("\ncan move from e7 while frozen: %s  ->  after 6 ticks: %s"
          % (before, after))
    print("cooldowns after ticking: teleport=%d freeze=%d"
          % (skill.cooldowns["teleport"], skill.cooldowns["freeze"]))

    print("\n== 180-degree rotated view of the same moment ==")
    print(render_ascii(skill, view_rotated=True, frozen=skill.frozen))

    print("\n== Move log ==")
    for entry in skill.log:
        print("%-10s %-14s %s" % (entry.kind, entry.detail, entry.turn))

    if PYGAME_OK:
        pygame.init()
        screen = pygame.display.set_mode((8 * 56, 8 * 56))
        pygame.display.set_caption("Skill Chess render() demo")
        render(screen, skill, view_rotated=False, frozen=skill.frozen)
        print("\npygame window shown; close it to exit the demo.")


if __name__ == "__main__":
    main()
```

## 4. Execution Protocol & Step-by-Step Workflow

1. **Install logic dependency.** Run `python -m pip install python-chess`.
   pygame is optional and only needed for `render()`.
2. **Bring up a skill board.** Create `SkillChessBoard()`, optionally passing
   `start_fen`, custom `cooldown_ticks` and `freeze_duration`.
3. **Play standard moves.** Call `board.push_uci("e2e4")`. The wrapper keeps
   `turn_player` in sync with the authoritative `chess.Board` and appends a
   `MoveEntry` to `log`.
4. **Gate skill use.** Before casting, assert `can_cast(skill)` is True and
   `cooldowns[skill] == 0`; otherwise the cast is rejected and returns False.
5. **Cast Teleport.** `apply_teleport(sq_from, sq_to)` relocates a friendly
   piece. Legal jumps route through `push_uci`; impossible jumps use the
   variant path and manually advance the turn. Verify success via the return
   value and the new `cooldowns["teleport"]`.
6. **Cast Freeze.** `apply_freeze(square)` locks that square; its occupant is
   now skipped by `legal_moves()` and `can_move_from()` returns False.
7. **Advance the clock.** Call `tick_cooldowns(ticks=N)` once per turn. This
   decays skill cooldowns and freeze expiry; squares whose expiry reaches
   zero are removed from `frozen` and unblocked again.
8. **Render.** Use `render_ascii(board, view_rotated=..., frozen=...)` for
   console/debug output. For a GUI, call `render(screen, board, ...)` after
   `pygame.display.set_mode(...)`.
9. **Rotate the view.** Wrap any 8 x 8 board view with
   `rotate_view_180(board_ui)` or pass `view_rotated=True` to the renderers.
10. **Sanity check (agent).** Run `main()` and confirm the transcript shows
    two legal pushes per side, the teleport relocating the e4 pawn, the e7
    square freezing, cooldowns descending from `{"teleport":6,"freeze":9}`,
    and e7 becoming movable again after the ticks.

## 5. Edge Cases & Error Handling

- **Missing `python-chess`.** Module import raises a descriptive
  `ImportError` with the install command; the logic layer cannot run without
  the rule engine.
- **Missing pygame.** `render()` and the GUI demo branch become no-ops;
  `render_ascii` keeps delivering full fidelity, so the demo still runs.
- **Teleport on cooldown or game over.** `can_cast` returns False and
  `apply_teleport` returns False without touching the board.
- **Invalid teleport targets.** Out-of-range squares, self-squares, absent
  source pieces, and destinations holding a friendly piece are all rejected
  before any mutation. `chess.square_name`/`Move.from_uci` failures are
  swallowed and routed to the variant fallback.
- **Frozen occupants.** A frozen piece remains on the board but every move
  whose `from_square` is frozen is omitted from `legal_moves()`, and
  `can_move_from` reports False, splitting the checks cleanly for any AI.
- **Expiry bookkeeping.** `tick_cooldowns` snaps cooldowns at 0 (never
  negative) and removes expired freezes from both `frozen` and
  `frozen_expiry` atomically, so the two structures never disagree.
- **Variant-move legality gap.** Direct board mutation bypasses push-side
  validity (e.g., a teleported king may sit in attack). Callers that need
  strict safety can re-run `board.is_valid()` after a variant cast and undo
  the cast through `log` when it fails.
- **Ragged board views.** `rotate_view_180` and `_board_rows` validate the
  8 x 8 shape and raise `ValueError` instead of producing garbled mirrors.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.