feat(server): moderator announces every move and attempt to both players
All move-event announcements in translator.ts and all attempted-move announcements in commit.ts now use audience 'both' so the moderator panel is a complete shared transcript for both players. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ export function handleCommit(game: Game, color: Color, msg: CommitInput): Commit
|
|||||||
|
|
||||||
const piece = game.chess.get(msg.from) as { color: Color; type: Piece['type'] } | false;
|
const piece = game.chess.get(msg.from) as { color: Color; type: Piece['type'] } | false;
|
||||||
if (!piece || piece.color !== color) {
|
if (!piece || piece.color !== color) {
|
||||||
return announceWith(game, 'no_such_piece', color);
|
return announceWith(game, 'no_such_piece');
|
||||||
}
|
}
|
||||||
|
|
||||||
const pseudo = geometricMoves(
|
const pseudo = geometricMoves(
|
||||||
@@ -46,12 +46,12 @@ export function handleCommit(game: Game, color: Color, msg: CommitInput): Commit
|
|||||||
ownSquares(game, color),
|
ownSquares(game, color),
|
||||||
);
|
);
|
||||||
if (pseudo.length === 0) {
|
if (pseudo.length === 0) {
|
||||||
return announceWith(game, 'no_legal_moves', color);
|
return announceWith(game, 'no_legal_moves');
|
||||||
}
|
}
|
||||||
|
|
||||||
const legal = chessJsLegalFrom(game, msg.from);
|
const legal = chessJsLegalFrom(game, msg.from);
|
||||||
if (legal.length === 0) {
|
if (legal.length === 0) {
|
||||||
return announceWith(game, 'wont_help', color);
|
return announceWith(game, 'wont_help');
|
||||||
}
|
}
|
||||||
|
|
||||||
game.armed = { color, from: msg.from };
|
game.armed = { color, from: msg.from };
|
||||||
@@ -77,7 +77,7 @@ function tryMove(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!move) {
|
if (!move) {
|
||||||
return announceWith(game, 'illegal_move', color);
|
return announceWith(game, 'illegal_move');
|
||||||
}
|
}
|
||||||
|
|
||||||
game.armed = null;
|
game.armed = null;
|
||||||
@@ -110,10 +110,10 @@ function tryMove(
|
|||||||
function announceWith(
|
function announceWith(
|
||||||
game: Game,
|
game: Game,
|
||||||
text: 'no_such_piece' | 'no_legal_moves' | 'wont_help' | 'illegal_move',
|
text: 'no_such_piece' | 'no_legal_moves' | 'wont_help' | 'illegal_move',
|
||||||
color: Color,
|
|
||||||
): CommitResult {
|
): CommitResult {
|
||||||
const ply = game.chess.history().length;
|
const ply = game.chess.history().length;
|
||||||
const a = announce(text, color, ply);
|
// Feature 1: attempted moves are announced to both players.
|
||||||
|
const a = announce(text, 'both', ply);
|
||||||
game.announcements.push(a);
|
game.announcements.push(a);
|
||||||
return { kind: 'announce', announcements: [a] };
|
return { kind: 'announce', announcements: [a] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,21 +33,22 @@ export function translateMove(game: Game, move: Move): Announcement[] {
|
|||||||
const isQueensideCastle = move.isQueensideCastle();
|
const isQueensideCastle = move.isQueensideCastle();
|
||||||
const isProm = !!move.promotion;
|
const isProm = !!move.promotion;
|
||||||
|
|
||||||
// To opponent: the move event itself.
|
// To both players: the move event itself (Feature 1 — the moderator
|
||||||
|
// announces every move aloud; both players hear it).
|
||||||
if (isKingsideCastle) {
|
if (isKingsideCastle) {
|
||||||
out.push(announce(`${moverWord}_castled_kingside` as ModeratorText, opp, ply));
|
out.push(announce(`${moverWord}_castled_kingside` as ModeratorText, 'both', ply));
|
||||||
} else if (isQueensideCastle) {
|
} else if (isQueensideCastle) {
|
||||||
out.push(announce(`${moverWord}_castled_queenside` as ModeratorText, opp, ply));
|
out.push(announce(`${moverWord}_castled_queenside` as ModeratorText, 'both', ply));
|
||||||
} else if (isCap && isEp) {
|
} else if (isCap && isEp) {
|
||||||
out.push(announce(`${moverWord}_moved_captured_ep` as ModeratorText, opp, ply));
|
out.push(announce(`${moverWord}_moved_captured_ep` as ModeratorText, 'both', ply));
|
||||||
} else if (isCap) {
|
} else if (isCap) {
|
||||||
out.push(announce(`${moverWord}_moved_captured` as ModeratorText, opp, ply));
|
out.push(announce(`${moverWord}_moved_captured` as ModeratorText, 'both', ply));
|
||||||
} else {
|
} else {
|
||||||
out.push(announce(`${moverWord}_moved` as ModeratorText, opp, ply));
|
out.push(announce(`${moverWord}_moved` as ModeratorText, 'both', ply));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProm) {
|
if (isProm) {
|
||||||
out.push(announce(`${moverWord}_promoted` as ModeratorText, opp, ply, { promotedTo: move.promotion }));
|
out.push(announce(`${moverWord}_promoted` as ModeratorText, 'both', ply, { promotedTo: move.promotion }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// To both: state changes.
|
// To both: state changes.
|
||||||
|
|||||||
@@ -74,6 +74,20 @@ describe('hierarchy decision table', () => {
|
|||||||
expect(game.armed).toBeNull();
|
expect(game.armed).toBeNull();
|
||||||
expect(game.chess.history()).toContain('e4');
|
expect(game.chess.history()).toContain('e4');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('attempted-move announcements are audience: both', () => {
|
||||||
|
const r = handleCommit(game, 'w', { from: 'e4' }); // empty square -> no_such_piece
|
||||||
|
expect(r.kind).toBe('announce');
|
||||||
|
if (r.kind === 'announce') expect(r.announcements[0]!.audience).toBe('both');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applied-move announcements are audience: both', () => {
|
||||||
|
const r = handleCommit(game, 'w', { from: 'e2', to: 'e4' });
|
||||||
|
expect(r.kind).toBe('applied');
|
||||||
|
if (r.kind === 'applied') {
|
||||||
|
for (const a of r.announcements) expect(a.audience).toBe('both');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('touch-move enforcement', () => {
|
describe('touch-move enforcement', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user