/** * Тесты 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); }); });