From 5a2d865377dd7babc3c147ad4b96a2d3acb62c47 Mon Sep 17 00:00:00 2001 From: EJClaw Date: Tue, 6 Oct 2026 13:26:02 +0900 Subject: [PATCH] =?UTF-8?q?M9:=20=EC=B2=B4=EC=8A=A4960=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=C2=B7E2E=C2=B7=EB=AC=B8=EC=84=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 방 설정에 규칙(일반/체스960), 옆 패널에 시작 배치 번호와 캐슬링 버튼 - 캐슬링 칸을 노란 테두리 + '캐슬링' 글씨로 표시, 킹 선택 중 내 룩을 누르면 캐슬링 - E2E: chess960 시나리오(시작 번호를 읽어 뒷줄을 비우고 킹→룩으로 캐슬링), Plan.game - docs: chess.md 5.4·8.0·9, 13-decisions, 02-architecture Co-Authored-By: Claude Opus 5.5 --- apps/web/src/games/chess/index.tsx | 70 ++++++++++++++++++-- docs/02-architecture.md | 2 +- docs/13-decisions.md | 2 +- docs/games/chess.md | 20 ++++-- e2e/chess960-plan.ts | 100 +++++++++++++++++++++++++++++ e2e/games.e2e.ts | 54 +++++++++++++++- 6 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 e2e/chess960-plan.ts diff --git a/apps/web/src/games/chess/index.tsx b/apps/web/src/games/chess/index.tsx index 3fa282a..dc4741c 100644 --- a/apps/web/src/games/chess/index.tsx +++ b/apps/web/src/games/chess/index.tsx @@ -277,7 +277,8 @@ function ChessBoard({ view: v, act, pending }: BoardProps) { onPointerDown={(e) => { setKbd(false); const sq = cellAt(e); - if (!sq || !ownMovable(sq) || pending) return; + // Chess960: with the king picked, my own rook is a castling target, not a new selection. + if (!sq || !ownMovable(sq) || pending || (selected && targets.some((m) => m.to === sq))) return; drag.current = { from: sq, wasSelected: selected === sq, sx: e.clientX, sy: e.clientY, moved: false }; setSelected(sq); svgRef.current?.setPointerCapture(e.pointerId); @@ -361,6 +362,18 @@ function ChessBoard({ view: v, act, pending }: BoardProps) { {showDots && targets.map((m) => { const c = cell(m.to); + if (m.castle) { + // Castling target (king's square or, in 960, the own rook): gold frame + label, so it is never mistaken for a capture. + return ( + + + + + 캐슬링 + + + ); + } const capture = !!pieceAt(v, m.to) || (pieceAt(v, m.from)?.toLowerCase() === 'p' && m.from[0] !== m.to[0]); return capture ? ( @@ -386,6 +399,15 @@ function ChessBoard({ view: v, act, pending }: BoardProps) {

{announce}

+ {myTurn && selected && targets.some((m) => m.castle) && (showDots || v.chess960Index !== null) && ( +

+ {v.chess960Index === null + ? '캐슬링하려면 노란 테두리 칸을 누르세요.' + : showDots + ? '캐슬링하려면 노란 테두리 칸(내 룩, 또는 킹이 갈 g·c열 칸)을 누르세요.' + : '캐슬링하려면 내 룩이나 킹이 갈 g·c열 칸을 누르세요.'} +

+ )} {!v.myColor && (
)} + {castles.length > 0 && ( +
+ {castles.map((side) => ( + + ))} +
+ )} {canClaim && (