go-learn/apps/web/src/lib/goals.test.ts
sab.code.lab 1c85091186 chore: восстановление репозитория из снапшота v0.3.1
Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 09:34:02 +02:00

120 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Тесты lib/goals.ts: все 4 предиката целей + формальный глаз v1
* (позитив/негатив: область 4 пунктов — не глаз; область у края — глаз).
* Позиции строятся buildPosition, ходы — applyMove из @go-learn/core.
*/
import { applyMove, buildPosition } from '@go-learn/core';
import type { BoardState, Point } from '@go-learn/core';
import { describe, expect, it } from 'vitest';
import { goalReached } from './goals.js';
function p(x: number, y: number): Point {
return { x, y };
}
/** Ход чёрных/белых, падает с ошибкой теста при нелегальности. */
function play(state: BoardState, color: 'black' | 'white', point: Point): BoardState {
const result = applyMove(state, { kind: 'play', color, point });
if (!result.ok) throw new Error(`тест: ход нелегален: ${result.error}`);
return result.state;
}
describe('goals: capture-target', () => {
// Белый камень (4,4) в атари: чёрные (3,4),(5,4),(4,3), дамэ — (4,5).
const start = buildPosition(9, [p(3, 4), p(5, 4), p(4, 3)], [p(4, 4)], 'black');
const target = [p(4, 4)];
it('цель на доске → не выполнено; захват → выполнено', () => {
expect(goalReached(start, { type: 'capture-target' }, target)).toBe(false);
const after = play(start, 'black', p(4, 5));
expect(goalReached(after, { type: 'capture-target' }, target)).toBe(true);
});
it('пустой target → false', () => {
expect(goalReached(start, { type: 'capture-target' }, [])).toBe(false);
});
});
describe('goals: liberties', () => {
// Чёрный (2,2) прижат белыми (1,2),(3,2),(2,1): одно дамэ (2,3).
const start = buildPosition(9, [p(2, 2)], [p(1, 2), p(3, 2), p(2, 1)], 'black');
const target = [p(2, 2)];
it('группа с 1 дамэ < count=2 → false', () => {
expect(goalReached(start, { type: 'liberties', count: 2 }, target)).toBe(false);
});
it('после выхода (2,3) у группы 3 дамэ ≥ 2 → true', () => {
const after = play(start, 'black', p(2, 3));
expect(goalReached(after, { type: 'liberties', count: 2 }, target)).toBe(true);
expect(goalReached(after, { type: 'liberties', count: 4 }, target)).toBe(false);
});
});
describe('goals: connect', () => {
// Два чёрных камня (4,2) и (4,4), соединяются ходом (4,3).
const start = buildPosition(9, [p(4, 2), p(4, 4)], [], 'black');
const target = [p(4, 2), p(4, 4)];
it('разные группы → false; после соединения → true', () => {
expect(goalReached(start, { type: 'connect' }, target)).toBe(false);
const after = play(start, 'black', p(4, 3));
expect(goalReached(after, { type: 'connect' }, target)).toBe(true);
});
it('камни разных цветов не могут быть одной группой → false', () => {
const mixed = buildPosition(9, [p(4, 2)], [p(4, 4)], 'black');
expect(goalReached(mixed, { type: 'connect' }, target)).toBe(false);
});
});
describe('goals: two-eyes', () => {
// Кольцо чёрных вокруг линии из 3 пустых пунктов (3,4),(4,4),(5,4):
// один формальный глаз (область из 3 пунктов). Ход (4,4) делит область
// на два глаза по 1 пункту.
const ring = [p(2, 4), p(6, 4), p(3, 3), p(4, 3), p(5, 3), p(3, 5), p(4, 5), p(5, 5)];
const start = buildPosition(9, ring, [], 'black');
const target = [p(4, 3)];
it('один глаз из 3 пунктов → false', () => {
expect(goalReached(start, { type: 'two-eyes' }, target)).toBe(false);
});
it('после разделения области — два глаза → true', () => {
const after = play(start, 'black', p(4, 4));
expect(goalReached(after, { type: 'two-eyes' }, target)).toBe(true);
});
});
describe('goals: формальный глаз v1 (границы определения)', () => {
it('область из 4 пустых пунктов — не глаз', () => {
// Кольцо вокруг линии из 4 пунктов (3,4)..(6,4): формальных глаз нет.
const ring4 = [
p(2, 4),
p(7, 4),
p(3, 3),
p(4, 3),
p(5, 3),
p(6, 3),
p(3, 5),
p(4, 5),
p(5, 5),
p(6, 5),
];
const state = buildPosition(9, ring4, [], 'black');
expect(goalReached(state, { type: 'two-eyes' }, [p(4, 3)])).toBe(false);
});
it('глаз у края доски засчитывается (край = «свой» сосед)', () => {
// Группа чёрных у левого края с двумя глазами: (0,0) и (0,2).
const black = [p(1, 0), p(1, 1), p(0, 1), p(1, 2), p(1, 3), p(0, 3)];
const state = buildPosition(9, black, [], 'white');
expect(goalReached(state, { type: 'two-eyes' }, [p(1, 1)])).toBe(true);
});
it('область, смежная с двумя цветами, — не глаз', () => {
// Пустой пункт (0,0) между чёрным (1,0) и белым (0,1) — не глаз.
const state = buildPosition(9, [p(1, 0)], [p(0, 1)], 'black');
expect(goalReached(state, { type: 'two-eyes' }, [p(1, 0)])).toBe(false);
});
});