커스텀 필요한 항목들 모두 커스텀 가능하도록 수정.
This commit is contained in:
@@ -4,86 +4,32 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import Dice from './dice';
|
||||
import { Chessman } from './chessman';
|
||||
import ChessmanPiece from './chessman-piece';
|
||||
import {
|
||||
applyLandEvent,
|
||||
type PlaneCell,
|
||||
} from './cell-event';
|
||||
import { applyLandEvent, type PlaneCell } from './cell-event';
|
||||
import { getAssets } from './asset-storage';
|
||||
import {
|
||||
buildPlaneCells,
|
||||
cellColorFor,
|
||||
diceAreaSpan,
|
||||
getOrCreateBoardConfig,
|
||||
} from './board-config';
|
||||
|
||||
const PLANE_SIZE = { width: 9, height: 7 } as const;
|
||||
const STEP_MS = 280;
|
||||
|
||||
const CELL_TYPES = ['한잔', '안주', '모두한잔', '두잔', '벌칙1', '벌칙2'] as const;
|
||||
|
||||
/** 특수 칸 배치 (28칸 기준 인덱스) */
|
||||
const SPECIAL_CELLS: Record<number, { type: string; eventId: string }> = {
|
||||
8: { type: '무인도', eventId: 'island' },
|
||||
14: { type: '역방향', eventId: 'reverse' },
|
||||
22: { type: '비행기', eventId: 'airplane' }, // → 시작(0)
|
||||
};
|
||||
|
||||
function randomType() {
|
||||
return CELL_TYPES[Math.floor(Math.random() * CELL_TYPES.length)];
|
||||
}
|
||||
|
||||
function cellLabel(cell: PlaneCell | undefined, index: number) {
|
||||
if (!cell) return '';
|
||||
return index === 0 ? '시작' : cell.type;
|
||||
}
|
||||
|
||||
function cellColor(cell: PlaneCell, index: number) {
|
||||
if (index === 0) return '#059669';
|
||||
switch (cell.eventId) {
|
||||
case 'island':
|
||||
return '#0f766e';
|
||||
case 'reverse':
|
||||
return '#7c3aed';
|
||||
case 'airplane':
|
||||
return '#0284c7';
|
||||
default:
|
||||
return '#e11d48';
|
||||
}
|
||||
}
|
||||
|
||||
/** 시계방향 둘레 칸. 모서리는 한 번만 넣어서 9x7 기준 28칸이 된다. */
|
||||
function buildPerimeter(width: number, height: number, assignType: () => string): PlaneCell[] {
|
||||
const cells: PlaneCell[] = [];
|
||||
let id = 0;
|
||||
|
||||
for (let x = 0; x < width; x++) {
|
||||
cells.push({ id: id++, x, y: 0, type: assignType() });
|
||||
}
|
||||
for (let y = 1; y < height; y++) {
|
||||
cells.push({ id: id++, x: width - 1, y, type: assignType() });
|
||||
}
|
||||
for (let x = width - 2; x >= 0; x--) {
|
||||
cells.push({ id: id++, x, y: height - 1, type: assignType() });
|
||||
}
|
||||
for (let y = height - 2; y >= 1; y--) {
|
||||
cells.push({ id: id++, x: 0, y, type: assignType() });
|
||||
}
|
||||
|
||||
if (cells[0]) {
|
||||
cells[0].type = '시작';
|
||||
cells[0].eventId = null;
|
||||
}
|
||||
|
||||
for (const [indexText, special] of Object.entries(SPECIAL_CELLS)) {
|
||||
const index = Number(indexText);
|
||||
if (cells[index]) {
|
||||
cells[index].type = special.type;
|
||||
cells[index].eventId = special.eventId;
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
export default function DicePlane() {
|
||||
const [dicePlaneArray, setDicePlaneArray] = useState(() =>
|
||||
buildPerimeter(PLANE_SIZE.width, PLANE_SIZE.height, () => 'none'),
|
||||
const initial = getOrCreateBoardConfig();
|
||||
const [planeSize, setPlaneSize] = useState({
|
||||
width: initial.width,
|
||||
height: initial.height,
|
||||
});
|
||||
const [dicePlaneArray, setDicePlaneArray] = useState<PlaneCell[]>(() =>
|
||||
buildPlaneCells(initial),
|
||||
);
|
||||
const chessmanRef = useRef(new Chessman(28));
|
||||
const chessmanRef = useRef(new Chessman(initial.cells.length));
|
||||
const boardRef = useRef(dicePlaneArray);
|
||||
const [chessmanIndex, setChessmanIndex] = useState(0);
|
||||
const [chessmanSrc, setChessmanSrc] = useState('/chessman/default.png');
|
||||
@@ -96,9 +42,11 @@ export default function DicePlane() {
|
||||
const moveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const cells = buildPerimeter(PLANE_SIZE.width, PLANE_SIZE.height, randomType);
|
||||
const board = getOrCreateBoardConfig();
|
||||
const cells = buildPlaneCells(board);
|
||||
chessmanRef.current = new Chessman(cells.length, 0);
|
||||
boardRef.current = cells;
|
||||
setPlaneSize({ width: board.width, height: board.height });
|
||||
setChessmanIndex(0);
|
||||
setChessmanSrc(getAssets().chessman);
|
||||
setHopKey(0);
|
||||
@@ -180,15 +128,15 @@ export default function DicePlane() {
|
||||
);
|
||||
|
||||
const onDiceRolled = useCallback(
|
||||
(value: number) => {
|
||||
const roll = Math.max(0, Math.trunc(value));
|
||||
(rollResult: { sum: number; faces: number[] }) => {
|
||||
const roll = Math.max(0, Math.trunc(rollResult.sum));
|
||||
if (roll === 0) return;
|
||||
|
||||
setLandedLabel(null);
|
||||
const man = chessmanRef.current;
|
||||
|
||||
if (man.isOnIsland) {
|
||||
const outcome = man.resolveIslandRoll(roll);
|
||||
const outcome = man.resolveIslandRoll(roll, rollResult.faces);
|
||||
setIslandTurnsLeft(man.islandTurnsLeft);
|
||||
if (outcome === 'wait') {
|
||||
showBanner(
|
||||
@@ -198,7 +146,6 @@ export default function DicePlane() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
// escape with 6
|
||||
showBanner('무인도 탈출!');
|
||||
}
|
||||
|
||||
@@ -208,14 +155,15 @@ export default function DicePlane() {
|
||||
);
|
||||
|
||||
const chessmanCell = dicePlaneArray[chessmanIndex] ?? dicePlaneArray[0];
|
||||
const diceSpan = diceAreaSpan(planeSize.width, planeSize.height);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(${PLANE_SIZE.width}, minmax(0, 1fr))`,
|
||||
gridTemplateRows: `repeat(${PLANE_SIZE.height}, minmax(0, 1fr))`,
|
||||
gridTemplateColumns: `repeat(${planeSize.width}, minmax(0, 1fr))`,
|
||||
gridTemplateRows: `repeat(${planeSize.height}, minmax(0, 1fr))`,
|
||||
gap: 'clamp(6px, 1vw, 12px)',
|
||||
padding: 'clamp(8px, 1.2vw, 16px)',
|
||||
width: '100%',
|
||||
@@ -235,7 +183,10 @@ export default function DicePlane() {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: cellColor(plane, index),
|
||||
backgroundColor: cellColorFor(plane.eventId, index),
|
||||
backgroundImage: plane.imageSrc ? `url(${plane.imageSrc})` : undefined,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
color: 'white',
|
||||
borderRadius: 8,
|
||||
fontSize: 'clamp(11px, 1.6vw, 16px)',
|
||||
@@ -250,6 +201,7 @@ export default function DicePlane() {
|
||||
transform: isLanded ? 'scale(1.04)' : undefined,
|
||||
transition: 'box-shadow 180ms ease, transform 180ms ease',
|
||||
zIndex: isLanded ? 4 : undefined,
|
||||
textShadow: plane.imageSrc ? '0 1px 3px rgba(0,0,0,0.85)' : undefined,
|
||||
}}
|
||||
>
|
||||
{isStart ? '시작' : plane.type}
|
||||
@@ -268,8 +220,8 @@ export default function DicePlane() {
|
||||
|
||||
<div
|
||||
style={{
|
||||
gridColumn: '2 / 4',
|
||||
gridRow: '2 / 4',
|
||||
gridColumn: `2 / ${2 + diceSpan}`,
|
||||
gridRow: `2 / ${2 + diceSpan}`,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
zIndex: 6,
|
||||
|
||||
Reference in New Issue
Block a user