Files
joke-app/apps/web/src/games/janggi/quick.tsx
2026-10-06 13:35:21 +09:00

107 lines
4.4 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.
/** 장기 30초 규칙 요약 (docs/07 §5). */
import type { ReactNode } from 'react';
import type { QuickCard } from '../types';
type Side = 'cho' | 'han';
const INK: Record<Side, string> = { cho: '#1d4ed8', han: '#c0262d' };
function octagon(cx: number, cy: number, r: number): string {
return Array.from({ length: 8 }, (_, i) => {
const a = Math.PI / 8 + (i * Math.PI) / 4;
return `${(cx + r * Math.cos(a)).toFixed(3)},${(cy + r * Math.sin(a)).toFixed(3)}`;
}).join(' ');
}
/** Octagonal piece with its hanja, blue for 초 and red for 한. */
function Piece({ ch, side, x, y, r = 0.4 }: { ch: string; side: Side; x: number; y: number; r?: number }) {
return (
<g>
<polygon points={octagon(x, y, r)} fill="#fbf3dc" stroke={INK[side]} strokeWidth={0.05} />
<text x={x} y={y} fill={INK[side]} fontSize={r * 1.05} fontWeight={700} fontFamily="'Noto Serif KR','Noto Serif CJK KR','Batang',serif" textAnchor="middle" dominantBaseline="central">
{ch}
</text>
</g>
);
}
/** Wooden board of w×h line crossings; `palace` draws the X of a 3×3 palace whose top-left crossing is given. */
function Board({ w, h, size, palace, children }: { w: number; h: number; size: number; palace?: [number, number]; children: ReactNode }) {
const pad = 0.55;
const vw = w - 1 + 2 * pad;
const vh = h - 1 + 2 * pad;
const scale = size / Math.max(vw, vh);
return (
<svg viewBox={`${-pad} ${-pad} ${vw} ${vh}`} width={vw * scale} height={vh * scale} aria-hidden>
<rect x={-pad} y={-pad} width={vw} height={vh} rx={0.2} fill="#e8c48a" />
<g stroke="#7a5228" strokeWidth={0.04}>
{Array.from({ length: w }, (_, x) => (
<line key={`v${x}`} x1={x} y1={0} x2={x} y2={h - 1} />
))}
{Array.from({ length: h }, (_, y) => (
<line key={`h${y}`} x1={0} y1={y} x2={w - 1} y2={y} />
))}
{palace && (
<>
<line x1={palace[0]} y1={palace[1]} x2={palace[0] + 2} y2={palace[1] + 2} />
<line x1={palace[0] + 2} y1={palace[1]} x2={palace[0]} y2={palace[1] + 2} />
</>
)}
</g>
{children}
</svg>
);
}
const Dot = ({ x, y }: { x: number; y: number }) => <circle cx={x} cy={y} r={0.15} fill="#16a34a" />;
const Cross = ({ x, y }: { x: number; y: number }) => (
<g stroke="#dc2626" strokeWidth={0.09}>
<line x1={x - 0.17} y1={y - 0.17} x2={x + 0.17} y2={y + 0.17} />
<line x1={x + 0.17} y1={y - 0.17} x2={x - 0.17} y2={y + 0.17} />
</g>
);
export const quickStart: QuickCard[] = [
{
pic: (
<Board w={3} h={4} size={160} palace={[0, 0]}>
<circle cx={1} cy={1} r={0.6} fill="rgba(220,38,38,0.2)" stroke="#dc2626" strokeWidth={0.07} />
<Piece ch="漢" side="han" x={1} y={1} r={0.46} />
<Piece ch="車" side="cho" x={1} y={3} />
</Board>
),
alt: '파란 차가 궁성 안의 빨간 궁을 곧게 겨누고 있어요(장군)',
title: '상대 궁을 몰아요',
text: '초(파랑)가 먼저 두고 번갈아 한 수씩 둬요. 상대 궁이 어디로 피해도 잡히게 만들면(외통) 이겨요.',
},
{
pic: (
<Board w={5} h={5} size={160}>
{[[0, 1], [4, 1], [0, 3], [4, 3], [1, 4], [3, 4]].map(([x, y]) => (
<Dot key={`${x}-${y}`} x={x!} y={y!} />
))}
<Cross x={1} y={0} />
<Cross x={3} y={0} />
<Piece ch="馬" side="cho" x={2} y={2} />
<Piece ch="卒" side="cho" x={2} y={1} r={0.3} />
</Board>
),
alt: '가운데 마 둘레에 갈 수 있는 곳이 초록 점으로, 바로 위가 막힌 쪽은 빨간 ✕로 보여요',
title: '누르면 갈 곳이 보여요',
text: '기물을 누르면 갈 수 있는 곳에 초록 점이 찍혀요. 기물은 칸이 아니라 선이 만나는 점에 놓여요. 마·상은 바로 앞(멱)이 막히면 못 가요.',
},
{
pic: (
<Board w={5} h={2} size={220}>
<Dot x={3} y={1} />
<Dot x={4} y={1} />
<path d="M 0 0.55 Q 1.5 -0.4 3 0.6" fill="none" stroke="#7c3aed" strokeWidth={0.07} strokeDasharray="0.14 0.08" />
<Piece ch="包" side="cho" x={0} y={1} />
<Piece ch="兵" side="han" x={2} y={1} r={0.3} />
</Board>
),
alt: '포가 기물 하나를 뛰어넘어 그 너머 점으로 가는 모습이에요',
title: '포는 넘어서 가요',
text: '포는 꼭 기물 하나를 뛰어넘어서만 움직여요. 장군을 받으면 다른 수보다 먼저 궁을 피하거나 막아야 해요.',
},
];