feat(shared): pure phantom-model helpers (seed positions, deserialize)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,3 +2,4 @@ export * from './types.js';
|
|||||||
export * from './moderator.js';
|
export * from './moderator.js';
|
||||||
export * from './protocol.js';
|
export * from './protocol.js';
|
||||||
export * from './geometric.js';
|
export * from './geometric.js';
|
||||||
|
export * from './phantoms.js';
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { FILES, isSquare, type Color, type Piece, type PieceType, type Square } from './types.js';
|
||||||
|
|
||||||
|
const BACK_RANK: PieceType[] = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'];
|
||||||
|
const COLORS: Color[] = ['w', 'b'];
|
||||||
|
const TYPES: PieceType[] = ['p', 'n', 'b', 'r', 'q', 'k'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The standard starting position for one colour, as a square→piece map.
|
||||||
|
* Used to seed the phantom opponent-model layer with the opponent's army.
|
||||||
|
*/
|
||||||
|
export function opponentStartPosition(opponentColor: Color): Partial<Record<Square, Piece>> {
|
||||||
|
const backRank = opponentColor === 'w' ? '1' : '8';
|
||||||
|
const pawnRank = opponentColor === 'w' ? '2' : '7';
|
||||||
|
const out: Partial<Record<Square, Piece>> = {};
|
||||||
|
FILES.forEach((file, i) => {
|
||||||
|
out[`${file}${backRank}` as Square] = { color: opponentColor, type: BACK_RANK[i]! };
|
||||||
|
out[`${file}${pawnRank}` as Square] = { color: opponentColor, type: 'p' };
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a persisted phantom map (from localStorage). Tolerant: returns {} on
|
||||||
|
* any structural failure and silently drops individual invalid entries.
|
||||||
|
*/
|
||||||
|
export function deserializePhantoms(raw: string | null): Partial<Record<Square, Piece>> {
|
||||||
|
if (!raw) return {};
|
||||||
|
let parsed: unknown;
|
||||||
|
try { parsed = JSON.parse(raw); } catch { return {}; }
|
||||||
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};
|
||||||
|
const out: Partial<Record<Square, Piece>> = {};
|
||||||
|
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
||||||
|
if (!isSquare(k)) continue;
|
||||||
|
if (typeof v !== 'object' || v === null) continue;
|
||||||
|
const { color, type } = v as { color?: unknown; type?: unknown };
|
||||||
|
if (!COLORS.includes(color as Color)) continue;
|
||||||
|
if (!TYPES.includes(type as PieceType)) continue;
|
||||||
|
out[k] = { color: color as Color, type: type as PieceType };
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { opponentStartPosition, deserializePhantoms } from '../src/phantoms.js';
|
||||||
|
|
||||||
|
describe('opponentStartPosition', () => {
|
||||||
|
it('seeds 16 black pieces on ranks 7-8', () => {
|
||||||
|
const p = opponentStartPosition('b');
|
||||||
|
expect(Object.keys(p).length).toBe(16);
|
||||||
|
expect(p.e8).toEqual({ color: 'b', type: 'k' });
|
||||||
|
expect(p.d8).toEqual({ color: 'b', type: 'q' });
|
||||||
|
expect(p.a8).toEqual({ color: 'b', type: 'r' });
|
||||||
|
expect(p.h7).toEqual({ color: 'b', type: 'p' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('seeds 16 white pieces on ranks 1-2', () => {
|
||||||
|
const p = opponentStartPosition('w');
|
||||||
|
expect(Object.keys(p).length).toBe(16);
|
||||||
|
expect(p.e1).toEqual({ color: 'w', type: 'k' });
|
||||||
|
expect(p.a2).toEqual({ color: 'w', type: 'p' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deserializePhantoms', () => {
|
||||||
|
it('returns {} for null or invalid JSON', () => {
|
||||||
|
expect(deserializePhantoms(null)).toEqual({});
|
||||||
|
expect(deserializePhantoms('not json')).toEqual({});
|
||||||
|
expect(deserializePhantoms('[]')).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps valid entries and drops invalid ones', () => {
|
||||||
|
const raw = JSON.stringify({
|
||||||
|
e5: { color: 'b', type: 'n' },
|
||||||
|
zz: { color: 'b', type: 'p' }, // invalid square
|
||||||
|
a1: { color: 'x', type: 'p' }, // invalid colour
|
||||||
|
b2: { color: 'b', type: 'z' }, // invalid type
|
||||||
|
});
|
||||||
|
expect(deserializePhantoms(raw)).toEqual({ e5: { color: 'b', type: 'n' } });
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user