# INTERFACES — контракты между модулями Обновлено: 2026-08-05 | Статус: контракт core реализован (этап 1) > Правило: изменение контракта — сначала запись сюда, потом код. Код, нарушающий > контракт без обновления файла, — дефект. Удалил сущность из контракта — докажи > grep'ом, что её никто не читает. ## Единицы и система координат (объявлено один раз, I.6) - Координаты доски: целые, 0-based, `(x, y)`, x — слева направо, y — сверху вниз. - Размеры досок: 9, 13, 19. Коми: 6.5 (19×19), 5.5 (9×9 и 13×13) — решение владельца 2026-08-05. - Подсчёт: китайские правила, площадь (Tromp-Taylor). - Формат обмена партиями: SGF FF[4]. ## Контракт движка правил (packages/core) Зафиксировано в этапе 1 до написания кода. Все структуры неизменяемы (`readonly`), операции возвращают новые объекты. Движок — чистый TS: без импортов DOM/worker API, без `Date.now()`/`Math.random()`; время и случайность — только через инжектируемые `Clock`/`Rng` (сам core в этапе 1 их не использует, типы объявлены для этапов 3–4). ### Базовые типы ```ts export type BoardSize = 9 | 13 | 19; export type Color = 'black' | 'white'; export type CellState = 'empty' | 'black' | 'white'; export interface Point { readonly x: number; // 0-based, слева направо readonly y: number; // 0-based, сверху вниз } export type Move = | { readonly kind: 'play'; readonly color: Color; readonly point: Point } | { readonly kind: 'pass'; readonly color: Color } | { readonly kind: 'resign'; readonly color: Color }; ``` ### Позиция и правила ```ts export interface BoardState { readonly size: BoardSize; /** Строго типизированная структура: обычный Array с readonly-типом поверх, * длина size*size, индекс клетки = y*size + x (три состояния клетки). */ readonly grid: ReadonlyArray; readonly toPlay: Color; /** Сколько камней снял каждый цвет за партию. */ readonly captures: { readonly black: number; readonly white: number }; /** Точка простого ко (запрет немедленного обратного захвата) или null. */ readonly koPoint: Point | null; /** Хэши «позиция + toPlay» всех предыдущих позиций (позиционное суперко). */ readonly positionHashes: ReadonlyArray; /** true после двух пасов подряд или resign. */ readonly over: boolean; } /** Машиночитаемая причина отказа — для ветвления UI (подсказки, звуки). */ export type MoveError = 'out-of-bounds' | 'occupied' | 'suicide' | 'ko' | 'superko' | 'game-over' | 'wrong-turn'; export type MoveResult = | { readonly ok: true; readonly state: BoardState; readonly captured: ReadonlyArray } | { readonly ok: false; readonly state: BoardState; readonly reason: MoveError; readonly error: string; // человекочитаемое сообщение (ru) }; export function createBoard(size: BoardSize): BoardState; /** Неизменяемая постановка хода: возвращает новый BoardState. * При ok=false state — исходное состояние без изменений. * Правила: захват групп без дамэ; самоубийство запрещено, кроме хода, * который захватывает чужую группу; простое ко (koPoint); позиционное * суперко — запрет повтора любой предыдущей позиции (хэш позиция+toPlay); * pass/resign всегда легальны (если партия не окончена); два паса подряд * или resign → over: true. */ export function applyMove(state: BoardState, move: Move): MoveResult; /** Группа в точке: камни и число дамэ; null — клетка пуста или вне доски. */ export function groupAt( state: BoardState, point: Point, ): { readonly stones: ReadonlyArray; readonly liberties: number } | null; /** Ключ точки для множеств ("x,y") — формат для dead в scorePosition. */ export function pointKey(point: Point): string; ``` ### Подсчёт (китайские правила, площадь, Tromp-Taylor) ```ts export interface ScoreOptions { readonly komi: number; /** Мёртвые камни (ключи pointKey, "x,y"): снимаются с доски перед подсчётом. */ readonly dead: ReadonlySet; } export interface ScoreResult { readonly black: number; // площадь чёрных: камни + территория readonly white: number; // площадь белых: камни + территория (без коми) readonly komi: number; /** black − white − komi. */ readonly margin: number; /** margin > 0 → 'black', margin < 0 → 'white', margin === 0 → 'draw'. */ readonly winner: Color | 'draw'; /** Нейтральные пункты (дамэ, сэки): никому не засчитываются. */ readonly neutral: ReadonlyArray; } export function scorePosition(state: BoardState, options: ScoreOptions): ScoreResult; /** Эвристика «мёртвая группа»: группа без двух глаз в чужой территории. * Грубая; ручная корректировка — за UI. Возвращает список групп. */ export function suggestDeadGroups(state: BoardState): ReadonlyArray>; /** Коми по умолчанию: 19×19 → 6.5, 9×9 и 13×13 → 5.5. */ export function defaultKomi(size: BoardSize): number; ``` ### История партии (неизменяемое дерево) ```ts export interface Label { readonly point: Point; readonly text: string; } export interface NodeMarkup { readonly labels: ReadonlyArray