치지직 채널 ID로 입장하게 변경 및 일부 판 커스텀 설정 추가.
This commit is contained in:
@@ -83,6 +83,7 @@ function boardForExport(board: BoardConfig): BoardConfig {
|
||||
type: cell.type,
|
||||
eventId: cell.eventId ?? null,
|
||||
imageSrc: null,
|
||||
contentVisible: cell.contentVisible !== false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ export type BoardCellConfig = {
|
||||
eventId?: string | null;
|
||||
/** 칸 전용 이미지 (data URL 또는 상대 경로) */
|
||||
imageSrc?: string | null;
|
||||
/** false면 게임에서 이름·이미지를 숨김 (기본 true) */
|
||||
contentVisible?: boolean;
|
||||
};
|
||||
|
||||
export type BoardConfig = {
|
||||
@@ -117,9 +119,18 @@ export function createRandomBoardConfig(
|
||||
const geo = buildPerimeterGeometry(w, h);
|
||||
const specials = defaultSpecials(w, h);
|
||||
const cells: BoardCellConfig[] = geo.map((_, index) => {
|
||||
if (index === 0) return { type: '시작', eventId: null, imageSrc: null };
|
||||
if (specials[index]) return { ...specials[index], imageSrc: null };
|
||||
return { type: randomPresetType(), eventId: null, imageSrc: null };
|
||||
if (index === 0) {
|
||||
return { type: '시작', eventId: null, imageSrc: null, contentVisible: true };
|
||||
}
|
||||
if (specials[index]) {
|
||||
return { ...specials[index], imageSrc: null, contentVisible: true };
|
||||
}
|
||||
return {
|
||||
type: randomPresetType(),
|
||||
eventId: null,
|
||||
imageSrc: null,
|
||||
contentVisible: true,
|
||||
};
|
||||
});
|
||||
return { version: BOARD_VERSION, width: w, height: h, cells };
|
||||
}
|
||||
@@ -136,7 +147,8 @@ function sanitizeCell(src: BoardCellConfig | undefined, fallback: BoardCellConfi
|
||||
: String(src.eventId);
|
||||
const imageSrc =
|
||||
typeof src.imageSrc === 'string' && src.imageSrc.length > 0 ? src.imageSrc : null;
|
||||
return { type: src.type.trim(), eventId, imageSrc };
|
||||
const contentVisible = src.contentVisible !== false;
|
||||
return { type: src.type.trim(), eventId, imageSrc, contentVisible };
|
||||
}
|
||||
|
||||
export function normalizeBoardConfig(input: BoardConfig | null | undefined): BoardConfig {
|
||||
@@ -154,7 +166,12 @@ export function normalizeBoardConfig(input: BoardConfig | null | undefined): Boa
|
||||
cells.push(sanitizeCell(input.cells[i], base.cells[i]));
|
||||
}
|
||||
|
||||
cells[0] = { type: '시작', eventId: null, imageSrc: cells[0]?.imageSrc ?? null };
|
||||
cells[0] = {
|
||||
type: '시작',
|
||||
eventId: null,
|
||||
imageSrc: cells[0]?.imageSrc ?? null,
|
||||
contentVisible: cells[0]?.contentVisible !== false,
|
||||
};
|
||||
return { version: BOARD_VERSION, width, height, cells };
|
||||
}
|
||||
|
||||
@@ -227,6 +244,7 @@ export function resizeBoardConfig(
|
||||
type: '시작',
|
||||
eventId: null,
|
||||
imageSrc: prev.cells[0]?.imageSrc ?? null,
|
||||
contentVisible: prev.cells[0]?.contentVisible !== false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -242,6 +260,7 @@ export function resizeBoardConfig(
|
||||
type: src?.type && src.type !== '시작' ? src.type : randomPresetType(),
|
||||
eventId: src?.eventId ?? null,
|
||||
imageSrc: src?.imageSrc ?? null,
|
||||
contentVisible: src?.contentVisible !== false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -258,6 +277,10 @@ export function updateBoardCell(
|
||||
if (index < 0 || index >= current.cells.length) return current;
|
||||
const cells = current.cells.map((cell, i) => {
|
||||
if (i !== index) return cell;
|
||||
const contentVisible =
|
||||
patch.contentVisible === undefined
|
||||
? cell.contentVisible !== false
|
||||
: Boolean(patch.contentVisible);
|
||||
if (i === 0) {
|
||||
return {
|
||||
type: '시작',
|
||||
@@ -268,6 +291,7 @@ export function updateBoardCell(
|
||||
: patch.imageSrc === ''
|
||||
? null
|
||||
: patch.imageSrc,
|
||||
contentVisible,
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -284,6 +308,7 @@ export function updateBoardCell(
|
||||
: patch.imageSrc === ''
|
||||
? null
|
||||
: patch.imageSrc,
|
||||
contentVisible,
|
||||
};
|
||||
});
|
||||
const next = {
|
||||
@@ -296,6 +321,25 @@ export function updateBoardCell(
|
||||
return next;
|
||||
}
|
||||
|
||||
/** 모든 칸의 내용 표시 여부를 한 번에 설정 */
|
||||
export function setAllCellsContentVisible(
|
||||
visible: boolean,
|
||||
current = getOrCreateBoardConfig(),
|
||||
): BoardConfig {
|
||||
const cells = current.cells.map((cell) => ({
|
||||
...cell,
|
||||
contentVisible: visible,
|
||||
}));
|
||||
const next = {
|
||||
version: BOARD_VERSION,
|
||||
width: current.width,
|
||||
height: current.height,
|
||||
cells,
|
||||
};
|
||||
writeStoredBoard(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
export function buildPlaneCells(config: BoardConfig = getOrCreateBoardConfig()): PlaneCell[] {
|
||||
const normalized = normalizeBoardConfig(config);
|
||||
const geo = buildPerimeterGeometry(normalized.width, normalized.height);
|
||||
@@ -306,10 +350,19 @@ export function buildPlaneCells(config: BoardConfig = getOrCreateBoardConfig()):
|
||||
type: normalized.cells[index]?.type ?? (index === 0 ? '시작' : '한잔'),
|
||||
eventId: index === 0 ? null : (normalized.cells[index]?.eventId ?? null),
|
||||
imageSrc: normalized.cells[index]?.imageSrc ?? null,
|
||||
contentVisible: normalized.cells[index]?.contentVisible !== false,
|
||||
}));
|
||||
}
|
||||
|
||||
export function cellColorFor(eventId: string | null | undefined, index: number) {
|
||||
export function cellColorFor(
|
||||
eventId: string | null | undefined,
|
||||
index: number,
|
||||
options?: { contentVisible?: boolean },
|
||||
) {
|
||||
// 내용 숨김이면 종류가 색으로도 드러나지 않게 통일
|
||||
if (options?.contentVisible === false && index !== 0) {
|
||||
return '#be123c';
|
||||
}
|
||||
if (index === 0) return '#059669';
|
||||
switch (eventId) {
|
||||
case 'island':
|
||||
|
||||
@@ -10,6 +10,8 @@ export type PlaneCell = {
|
||||
eventId?: string | null;
|
||||
/** 칸 이미지 (없으면 텍스트만) */
|
||||
imageSrc?: string | null;
|
||||
/** false면 게임 판에서 이름·이미지를 숨김 */
|
||||
contentVisible?: boolean;
|
||||
};
|
||||
|
||||
export type TurnContext = {
|
||||
|
||||
@@ -10,8 +10,10 @@ import { getAssets } from './asset-storage';
|
||||
import {
|
||||
buildPlaneCells,
|
||||
cellColorFor,
|
||||
DEFAULT_PLANE_SIZE,
|
||||
diceAreaSpan,
|
||||
getOrCreateBoardConfig,
|
||||
perimeterLength,
|
||||
} from './board-config';
|
||||
|
||||
const STEP_MS = 280;
|
||||
@@ -34,15 +36,13 @@ export default function DicePlane({
|
||||
chatFeed?: ChatFeedItem[];
|
||||
showChatFeed?: boolean;
|
||||
}) {
|
||||
const initial = getOrCreateBoardConfig();
|
||||
const [planeSize, setPlaneSize] = useState({
|
||||
width: initial.width,
|
||||
height: initial.height,
|
||||
const [boardReady, setBoardReady] = useState(false);
|
||||
const [planeSize, setPlaneSize] = useState<{ width: number; height: number }>({
|
||||
width: DEFAULT_PLANE_SIZE.width,
|
||||
height: DEFAULT_PLANE_SIZE.height,
|
||||
});
|
||||
const [dicePlaneArray, setDicePlaneArray] = useState<PlaneCell[]>(() =>
|
||||
buildPlaneCells(initial),
|
||||
);
|
||||
const chessmanRef = useRef(new Chessman(initial.cells.length));
|
||||
const [dicePlaneArray, setDicePlaneArray] = useState<PlaneCell[]>([]);
|
||||
const chessmanRef = useRef(new Chessman(perimeterLength(DEFAULT_PLANE_SIZE.width, DEFAULT_PLANE_SIZE.height)));
|
||||
const boardRef = useRef(dicePlaneArray);
|
||||
const [chessmanIndex, setChessmanIndex] = useState(0);
|
||||
const [chessmanSrc, setChessmanSrc] = useState('/chessman/default.png');
|
||||
@@ -59,6 +59,7 @@ export default function DicePlane({
|
||||
const [islandTurnsLeft, setIslandTurnsLeft] = useState(0);
|
||||
const moveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// SSR/초기 렌더에서 랜덤 판을 쓰지 않고, 브라우저에 저장된 설정을 로드
|
||||
useEffect(() => {
|
||||
const board = getOrCreateBoardConfig();
|
||||
const cells = buildPlaneCells(board);
|
||||
@@ -72,6 +73,7 @@ export default function DicePlane({
|
||||
setStatusText('시계방향');
|
||||
setIslandTurnsLeft(0);
|
||||
setDicePlaneArray(cells);
|
||||
setBoardReady(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -175,6 +177,25 @@ export default function DicePlane({
|
||||
const chessmanCell = dicePlaneArray[chessmanIndex] ?? dicePlaneArray[0];
|
||||
const diceSpan = diceAreaSpan(planeSize.width, planeSize.height);
|
||||
|
||||
if (!boardReady || dicePlaneArray.length === 0) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'rgba(255,255,255,0.7)',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
판 불러오는 중…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||
<div
|
||||
@@ -192,6 +213,7 @@ export default function DicePlane({
|
||||
{dicePlaneArray.map((plane, index) => {
|
||||
const isStart = index === 0;
|
||||
const isLanded = !moving && index === chessmanIndex && landedLabel != null;
|
||||
const showContent = plane.contentVisible !== false;
|
||||
return (
|
||||
<div
|
||||
key={plane.id}
|
||||
@@ -201,8 +223,11 @@ export default function DicePlane({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: cellColorFor(plane.eventId, index),
|
||||
backgroundImage: plane.imageSrc ? `url(${plane.imageSrc})` : undefined,
|
||||
backgroundColor: cellColorFor(plane.eventId, index, {
|
||||
contentVisible: showContent,
|
||||
}),
|
||||
backgroundImage:
|
||||
showContent && plane.imageSrc ? `url(${plane.imageSrc})` : undefined,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
color: 'white',
|
||||
@@ -219,10 +244,13 @@ 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,
|
||||
textShadow:
|
||||
showContent && plane.imageSrc
|
||||
? '0 1px 3px rgba(0,0,0,0.85)'
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
{isStart ? '시작' : plane.type}
|
||||
{showContent ? (isStart ? '시작' : plane.type) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user