import { useEffect, useMemo, useState } from "react";
import { domain } from "@repo/shared";
import type { FieldSizePreset, StartGameRequestPayload } from "@repo/shared";
import { config } from "@client/config";
import { OVERLAY_BUTTON_STYLE } from "@client/scenes/shared/styles/overlayStyles";
import { LobbyRuleModal } from "./components/LobbyRuleModal";
import {
LOBBY_BACK_BUTTON_STYLE,
LOBBY_BACKGROUND_STYLE,
LOBBY_CONTAINER_STYLE,
LOBBY_CONTROLS_BLOCK_STYLE,
LOBBY_LABEL_STYLE,
LOBBY_LEFT_INNER_STYLE,
LOBBY_LEFT_PANEL_STYLE,
LOBBY_PLAYER_LIST_HEADER_STYLE,
LOBBY_PLAYER_LIST_ITEM_STYLE,
LOBBY_PLAYER_LIST_PANEL_STYLE,
LOBBY_SELECT_STYLE,
LOBBY_START_BUTTON_STYLE,
LOBBY_TITLE_STYLE,
LOBBY_WAITING_STYLE,
} from "./styles/LobbyScene.styles";
type Props = {
room: domain.room.Room | null;
myId: string | null;
onStart: (payload: StartGameRequestPayload) => void;
onBackToTitle: () => void;
};
export const LobbyScene = ({ room, myId, onStart, onBackToTitle }: Props) => {
if (!room)
return <div style={{ color: "white", padding: 40 }}>読み込み中...</div>;
const isMeOwner = room.ownerId === myId;
const teamUnit = 4;
const minimumStartPlayerCount = useMemo(() => {
return Math.max(
teamUnit,
Math.ceil(room.players.length / teamUnit) * teamUnit,
);
}, [room.players.length]);
const maxStartPlayerCount = useMemo(() => {
return Math.max(
minimumStartPlayerCount,
Math.floor(room.maxPlayers / teamUnit) * teamUnit,
);
}, [minimumStartPlayerCount, room.maxPlayers]);
const startPlayerCountOptions = useMemo(() => {
const options: number[] = [];
for (
let count = minimumStartPlayerCount;
count <= maxStartPlayerCount;
count += teamUnit
) {
options.push(count);
}
return options;
}, [minimumStartPlayerCount, maxStartPlayerCount]);
const [selectedStartPlayerCount, setSelectedStartPlayerCount] = useState(
minimumStartPlayerCount,
);
const fieldPresetOptions = useMemo(() => {
return Object.keys(
config.GAME_CONFIG.FIELD_PRESETS,
) as FieldSizePreset[];
}, []);
const [selectedFieldSizePreset, setSelectedFieldSizePreset] =
useState<FieldSizePreset>(config.GAME_CONFIG.DEFAULT_FIELD_PRESET);
const [isRuleModalOpen, setIsRuleModalOpen] = useState(false);
useEffect(() => {
setSelectedStartPlayerCount((prev) => {
if (prev < minimumStartPlayerCount || prev > maxStartPlayerCount) {
return minimumStartPlayerCount;
}
if (prev % teamUnit !== 0) {
return minimumStartPlayerCount;
}
return prev;
});
}, [minimumStartPlayerCount, maxStartPlayerCount]);
const handleStart = () => {
onStart({
targetPlayerCount: selectedStartPlayerCount,
fieldSizePreset: selectedFieldSizePreset,
});
};
const toFieldPresetLabel = (preset: FieldSizePreset): string => {
const range = config.GAME_CONFIG.FIELD_PRESETS[preset].recommendedPlayers;
const baseLabel =
preset === "SMALL"
? "小"
: preset === "MEDIUM"
? "中"
: preset === "LARGE"
? "大"
: "極大";
return `${baseLabel} (${range.min}-${range.max}人目安)`;
};
return (
<>
<style>{`
* { box-sizing: border-box; }
.lobby-main-layout {
display: flex;
flex-direction: row;
width: 100%;
flex-grow: 1;
gap: 20px;
min-height: 0;
}
.lobby-player-list {
list-style: none;
padding: 0 10px 0 0;
margin: 0;
font-size: 1.1rem;
overflow-y: auto;
flex-grow: 1;
min-height: 0;
touch-action: pan-y;
}
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-thumb { background-color: #555; border-radius: 4px; }
`}</style>
{/* 背景アニメーション */}
<div style={LOBBY_BACKGROUND_STYLE} />
<div className="lobby-container" style={LOBBY_CONTAINER_STYLE}>
<button
onClick={onBackToTitle}
style={{ ...OVERLAY_BUTTON_STYLE, ...LOBBY_BACK_BUTTON_STYLE }}
>
タイトルへ戻る
</button>
<h2 style={LOBBY_TITLE_STYLE}>
ルーム: {room.roomId} (待機中)
</h2>
<div className="lobby-main-layout">
{/* 左半分: スタートボタン or 待機メッセージ */}
<div style={LOBBY_LEFT_PANEL_STYLE}>
<div style={LOBBY_LEFT_INNER_STYLE}>
{isMeOwner ? (
<div style={LOBBY_CONTROLS_BLOCK_STYLE}>
<label htmlFor="start-player-count" style={LOBBY_LABEL_STYLE}>
ゲーム人数
</label>
<select
id="start-player-count"
value={selectedStartPlayerCount}
onChange={(event) => {
setSelectedStartPlayerCount(Number(event.target.value));
}}
style={LOBBY_SELECT_STYLE}
>
{startPlayerCountOptions.map((count) => (
<option key={count} value={count}>
{count}人
</option>
))}
</select>
<label htmlFor="field-size-preset" style={LOBBY_LABEL_STYLE}>
フィールドサイズ
</label>
<select
id="field-size-preset"
value={selectedFieldSizePreset}
onChange={(event) => {
setSelectedFieldSizePreset(event.target.value as FieldSizePreset);
}}
style={LOBBY_SELECT_STYLE}
>
{fieldPresetOptions.map((preset) => (
<option key={preset} value={preset}>
{toFieldPresetLabel(preset)}
</option>
))}
</select>
<button onClick={handleStart} style={LOBBY_START_BUTTON_STYLE}>
ゲームスタート
</button>
<button
onClick={() => { setIsRuleModalOpen(true); }}
style={{ ...OVERLAY_BUTTON_STYLE, width: "100%" }}
>
ルールを見る
</button>
</div>
) : (
<div style={LOBBY_CONTROLS_BLOCK_STYLE}>
<div style={LOBBY_WAITING_STYLE}>
ホストの開始を待っています...
</div>
<button
onClick={() => { setIsRuleModalOpen(true); }}
style={{ ...OVERLAY_BUTTON_STYLE, width: "100%" }}
>
ルールを見る
</button>
</div>
)}
</div>
</div>
{/* 右半分: 参加プレイヤーリスト */}
<div style={LOBBY_PLAYER_LIST_PANEL_STYLE}>
<h3 style={LOBBY_PLAYER_LIST_HEADER_STYLE}>
参加プレイヤー ({room.players.length}/{room.maxPlayers})
</h3>
<ul className="lobby-player-list">
{room.players.map((p: domain.room.RoomMember) => (
<li key={p.id} style={LOBBY_PLAYER_LIST_ITEM_STYLE}>
<span>{p.id === myId ? "🟢" : "⚪"}</span>
<span style={{ fontWeight: p.id === myId ? "bold" : "normal" }}>
{p.name}
</span>
{p.isOwner && <span style={{ fontSize: "0.9em" }}>👑</span>}
{p.isReady && (
<span style={{ marginLeft: "auto", fontSize: "0.9em" }}>
✅
</span>
)}
</li>
))}
</ul>
</div>
</div>
</div>
{isRuleModalOpen && (
<LobbyRuleModal
onClose={() => { setIsRuleModalOpen(false); }}
/>
)}
</>
);
};