From e3c7ee229f36bd12b4baf90fe91162240486cb71 Mon Sep 17 00:00:00 2001 From: SotaTamura Date: Sat, 8 Nov 2025 20:06:29 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=AB=E3=83=BC=E3=83=89=E3=81=AE=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/package.json | 4 +- apps/backend/src/memory.ts | 2 +- .../app/routes/magic-square/room.$roomId.tsx | 7 +- .../app/routes/memory-optimization/home.tsx | 8 +- .../memory-optimization/room.$roomId.tsx | 845 +++++++++--------- 5 files changed, 459 insertions(+), 407 deletions(-) diff --git a/apps/backend/package.json b/apps/backend/package.json index 0ec2b4b..acc5a1e 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -21,6 +21,8 @@ "wrangler": "^4.4.0" }, "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./magic": "./src/magic.ts", + "./memory": "./src/memory.ts" } } diff --git a/apps/backend/src/memory.ts b/apps/backend/src/memory.ts index 5a50359..1a2c990 100644 --- a/apps/backend/src/memory.ts +++ b/apps/backend/src/memory.ts @@ -190,7 +190,7 @@ export class Memory extends RoomMatch { const size = this.state.rules.boardSize; this.state.board = Array(size) .fill(null) - .map(() => Array(size).fill(null)); + .map(() => Array(size).fill({ status: "free" })); this.state.round = 0; this.state.turn = 0; this.state.winners = null; diff --git a/apps/frontend/app/routes/magic-square/room.$roomId.tsx b/apps/frontend/app/routes/magic-square/room.$roomId.tsx index fcd520e..883ae74 100644 --- a/apps/frontend/app/routes/magic-square/room.$roomId.tsx +++ b/apps/frontend/app/routes/magic-square/room.$roomId.tsx @@ -1,7 +1,12 @@ /** biome-ignore-all lint/a11y/noStaticElementInteractions: TODO */ /** biome-ignore-all lint/suspicious/noArrayIndexKey: TODO */ /** biome-ignore-all lint/a11y/useKeyWithClickEvents: TODO */ -import type { GameState, MessageType, Operation, Rule } from "@apps/backend"; +import type { + GameState, + MessageType, + Operation, + Rule, +} from "@apps/backend/magic"; import { useEffect, useRef, useState } from "react"; import { type ClientLoaderFunctionArgs, diff --git a/apps/frontend/app/routes/memory-optimization/home.tsx b/apps/frontend/app/routes/memory-optimization/home.tsx index 1aefac9..77590a2 100644 --- a/apps/frontend/app/routes/memory-optimization/home.tsx +++ b/apps/frontend/app/routes/memory-optimization/home.tsx @@ -78,11 +78,11 @@ export default function Lobby() { const roomName = newRoomName || `room-${Math.floor(Math.random() * 100000)}`; const res = await client.rooms.create.$post({ - json: { name: roomName, gameTitle: "magic-square" }, + json: { name: roomName, gameTitle: "memory-optimization" }, }); if (res.ok) { const newRoom = await res.json(); - navigate(`/magic-square/room/${newRoom.id}`); + navigate(`/memory-optimization/room/${newRoom.id}`); } }; @@ -109,7 +109,7 @@ export default function Lobby() { param: { roomId }, }); if (res.ok) { - navigate(`/magic-square/room/${roomId}`); + navigate(`/memory-optimization/room/${roomId}`); } } catch (e) { console.error(e); @@ -125,7 +125,7 @@ export default function Lobby() { }); if (res.ok) { const data = await res.json(); - navigate(`/magic-square/room/${data.id}`); + navigate(`/memory-optimization/room/${data.id}`); } else { setJoinError("Failed to join room"); } diff --git a/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx b/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx index 0d710bc..c2d38a1 100644 --- a/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx +++ b/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx @@ -1,7 +1,14 @@ /** biome-ignore-all lint/a11y/noStaticElementInteractions: TODO */ /** biome-ignore-all lint/suspicious/noArrayIndexKey: TODO */ /** biome-ignore-all lint/a11y/useKeyWithClickEvents: TODO */ -import type { GameState, MessageType, Operation, Rule } from "@apps/backend"; +import type { + CellState, + FunctionCard, + GameState, + MemoryCard, + MessageType, + Rule, +} from "@apps/backend/memory"; import { useEffect, useRef, useState } from "react"; import { type ClientLoaderFunctionArgs, @@ -25,7 +32,7 @@ export async function clientLoader({ params }: ClientLoaderFunctionArgs) { ]); if (!userRes.ok || !roomRes.ok || !roomSecretRes.ok) { - return redirect("/magic-square"); + return redirect("/memory-optimization"); } const user = await userRes.json(); @@ -35,7 +42,7 @@ export async function clientLoader({ params }: ClientLoaderFunctionArgs) { const createdAt = user.createdAt ? new Date(user.createdAt) : null; if (!roomData.users.includes(user.id)) { - return redirect("/magic-square"); + return redirect("/memory-optimization"); } return { @@ -49,10 +56,10 @@ export async function clientLoader({ params }: ClientLoaderFunctionArgs) { function GameBoard({ board, - onCellClick, + // onCellClick, }: { - board: (number | null)[][]; - onCellClick: (x: number, y: number) => void; + board: CellState[][]; + // onCellClick: (x: number, y: number) => void; }) { return (
onCellClick(x, y)} + // onClick={() => onCellClick(x, y)} > - {cell} + {cell.status === "reserved" + ? "R" + : cell.status === "used" + ? "U" + : ""}
)), )} @@ -74,37 +85,72 @@ function GameBoard({ ); } -function FinalGameBoard({ - board, - winnerary, +// function FinalGameBoard({ +// board, +// winnerary, +// }: { +// board: (number | null)[][]; +// winnerary: (true | false)[][]; +// }) { +// return ( +//
+// {board.map((row, y) => +// row.map((cell, x) => +// winnerary[y][x] === true ? ( +//
+// {cell} +//
+// ) : ( +//
+// {cell} +//
+// ) +// ) +// )} +//
+// ); +// } + +function Shape({ + card, + cellSz, }: { - board: (number | null)[][]; - winnerary: (true | false)[][]; + card: MemoryCard | FunctionCard; + cellSz: number; }) { return ( -
- {board.map((row, y) => - row.map((cell, x) => - winnerary[y][x] === true ? ( -
- {cell} -
- ) : ( +
+
+ {card.shape.map((row, y) => + row.map((cell, x) => (
- {cell} + {x === 0 && y === 0 ? ( +
+ ) : null}
- ), - ), - )} + )), + )} +
); } @@ -114,7 +160,7 @@ function Hand({ onCardClick, selectedNumIndex, }: { - cards: number[]; + cards: MemoryCard[]; onCardClick: (i: number) => void; selectedNumIndex: number | null; }) { @@ -124,10 +170,13 @@ function Hand({ {cards.map((card, i) => (
onCardClick(i)} > - {card} + {card.cost} +
))}
@@ -135,50 +184,53 @@ function Hand({ ); } -function Operations({ - onOperationClick, - selectedOperation, -}: { - onOperationClick: (name: Operation) => void; - selectedOperation: Operation; -}) { +// function Operations({ +// onOperationClick, +// selectedOperation, +// }: { +// onOperationClick: (name: Operation) => void; +// selectedOperation: Operation; +// }) { +// return ( +//
+// Operation +// {/*
+//
onOperationClick("add")} +// > +// + +//
+//
onOperationClick("sub")} +// > +// - +//
+//
*/} +//
+// ); +// } + +function Missions({ title, cards }: { title: string; cards: FunctionCard[] }) { return ( -
-
-
onOperationClick("add")} - > - + -
-
onOperationClick("sub")} - > - - -
+
+
{title}
+
+ {cards.map((card, i) => ( +
+ {card.cost} + +
+ ))}
); } -function Mission({ - title, - description, -}: { - title: string; - description: string; -}) { - return ( - -
-

{title}

-

{description}

-
-
- ); -} - // --- Main Page Component --- function TurnDisplay({ @@ -244,13 +296,13 @@ export default function RoomPage() { const currentPlayerId = gameState?.players[gameState.turn] ?? null; const [selectedNumIndex, setSelectedNumIndex] = useState(null); - const [selectedOperation, setSelectedOperation] = useState("add"); + // const [selectedOperation, setSelectedOperation] = useState("add"); - const [winnerDisplay, setWinnerDisplay] = useState(0); + // const [winnerDisplay, setWinnerDisplay] = useState(0); const [remainingTime, setRemainingTime] = useState(0); - const [spectatedPlayerId, setSpectatedPlayerId] = useState( - null, - ); + // const [spectatedPlayerId, setSpectatedPlayerId] = useState( + // null + // ); // WebSocket connection effect useEffect(() => { @@ -316,29 +368,29 @@ export default function RoomPage() { ws.current.send(message); } } - const handleCellClick = (x: number, y: number) => { - if (!gameState || !user || !user.id || selectedNumIndex === null) return; - sendWsMessage({ - type: "makeMove", - payload: { - x, - y, - operation: selectedOperation, - num: gameState.hands[user.id][selectedNumIndex], - numIndex: selectedNumIndex, - }, - }); - setSelectedNumIndex(null); - setSelectedOperation("add"); - }; - - const handleWinnersPlusClick = () => { - setWinnerDisplay(winnerDisplay + 1); - }; - - const handleWinnersMinusClick = () => { - setWinnerDisplay(winnerDisplay - 1); - }; + // const handleCellClick = (x: number, y: number) => { + // if (!gameState || !user || !user.id || selectedNumIndex === null) return; + // sendWsMessage({ + // type: "makeMove", + // payload: { + // x, + // y, + // operation: selectedOperation, + // num: gameState.hands[user.id][selectedNumIndex], + // numIndex: selectedNumIndex, + // }, + // }); + // setSelectedNumIndex(null); + // setSelectedOperation("add"); + // }; + + // const handleWinnersPlusClick = () => { + // setWinnerDisplay(winnerDisplay + 1); + // }; + + // const handleWinnersMinusClick = () => { + // setWinnerDisplay(winnerDisplay - 1); + // }; const handleReadyClick = () => { if (myStatus === "ready") { @@ -348,23 +400,23 @@ export default function RoomPage() { } }; - const handleRuleChange = (rule: Rule) => { - sendWsMessage({ - type: "changeRule", - payload: rule, - }); - }; + // const handleRuleChange = (rule: Rule) => { + // sendWsMessage({ + // type: "changeRule", + // payload: rule, + // }); + // }; - const handleBackToLobby = () => { - sendWsMessage({ type: "backToLobby" }); - }; + // const handleBackToLobby = () => { + // sendWsMessage({ type: "backToLobby" }); + // }; const handleLeaveRoom = async () => { sendWsMessage({ type: "removePlayer" }); if (roomId) { await client.rooms[":roomId"].leave.$post({ param: { roomId } }); } - navigate("/magic-square"); + navigate("/memory-optimization"); }; // --- Render Logic --- @@ -392,130 +444,123 @@ export default function RoomPage() { } if (myStatus === "spectating") { - if (!currentPlayerId) { - throw new Error("Current player ID is missing"); - } - - const playingPlayers = gameState.players.filter( - (p) => gameState.playerStatus[p] === "playing", - ); - - const spectatedPlayer = spectatedPlayerId - ? { - id: spectatedPlayerId, - name: gameState.names[spectatedPlayerId], - hand: gameState.hands[spectatedPlayerId], - mission: gameState.missions[spectatedPlayerId], - } - : null; - - return ( -
-

Watching Game

- - {/* Player perspective switcher */} -
- - {playingPlayers.map((pId) => ( - - ))} -
- - {/* Opponents' Missions */} -
- {spectatedPlayer - ? // Single player perspective - playingPlayers - .filter((pId) => pId !== spectatedPlayer.id) - .map((opponentId) => - gameState.missions[opponentId] ? ( - - ) : null, - ) - : null} -
- - {/* Game Board */} -
- - {}} /> -
- - {/* Player's Info (Hand and Mission) */} -
- {spectatedPlayer ? ( - <> - - {}} - selectedNumIndex={null} - /> - - ) : ( -
- { - // Overview perspective - playingPlayers.map((playerId) => - gameState.missions[playerId] ? ( -
-
- -
-
- {}} - selectedNumIndex={null} - /> -
-
- ) : null, - ) - } -
-

You are watching

-

Select a player above to see their perspective.

-
-
- )} -
-
- ); + // if (!currentPlayerId) { + // throw new Error("Current player ID is missing"); + // } + // const playingPlayers = gameState.players.filter( + // (p) => gameState.playerStatus[p] === "playing", + // ); + // const spectatedPlayer = spectatedPlayerId + // ? { + // id: spectatedPlayerId, + // name: gameState.names[spectatedPlayerId], + // hand: gameState.hands[spectatedPlayerId], + // mission: gameState.missions[spectatedPlayerId], + // } + // : null; + // return ( + //
+ //

Watching Game

+ // {/* Player perspective switcher */} + //
+ // + // {playingPlayers.map((pId) => ( + // + // ))} + //
+ // {/* Opponents' Missions */} + //
+ // {spectatedPlayer + // ? // Single player perspective + // playingPlayers + // .filter((pId) => pId !== spectatedPlayer.id) + // .map((opponentId) => + // gameState.missions[opponentId] ? ( + // + // ) : null, + // ) + // : null} + //
+ // {/* Game Board */} + //
+ // + // {}} /> + //
+ // {/* Player's Info (Hand and Mission) */} + //
+ // {spectatedPlayer ? ( + // <> + // + // {}} + // selectedNumIndex={null} + // /> + // + // ) : ( + //
+ // { + // // Overview perspective + // playingPlayers.map((playerId) => + // gameState.missions[playerId] ? ( + //
+ //
+ // + //
+ //
+ // {}} + // selectedNumIndex={null} + // /> + //
+ //
+ // ) : null, + // ) + // } + //
+ //

You are watching

+ //

Select a player above to see their perspective.

+ //
+ //
+ // )} + //
+ //
+ // ); } if (myStatus === "preparing" || myStatus === "ready") { return ( @@ -536,7 +581,7 @@ export default function RoomPage() { className="flex items-center justify-between gap-4 p-2" > - {gameState.names[playerId]} + {gameState.names?.[playerId] ?? playerId} {playerId === roomHost && ( Host @@ -545,16 +590,16 @@ export default function RoomPage() { - {gameState.playerStatus[playerId] === "ready" + {gameState.playerStatus?.[playerId] === "ready" ? "Ready!" - : gameState.playerStatus[playerId] === "error" + : gameState.playerStatus?.[playerId] === "error" ? "Error" : "Preparing..."} @@ -568,12 +613,12 @@ export default function RoomPage() { className="select select-bordered" value={gameState.rules.boardSize} disabled={user.id !== roomHost} - onChange={(e) => - handleRuleChange({ - rule: "boardSize", - state: parseInt(e.target.value, 10), - }) - } + // onChange={(e) => + // handleRuleChange({ + // rule: "boardSize", + // state: parseInt(e.target.value, 10), + // }) + // } > @@ -591,12 +636,12 @@ export default function RoomPage() { className="toggle toggle-success" checked={gameState.rules.negativeDisabled} disabled={user.id !== roomHost} - onChange={(e) => - handleRuleChange({ - rule: "negativeDisabled", - state: e.target.checked, - }) - } + // onChange={(e) => + // handleRuleChange({ + // rule: "negativeDisabled", + // state: e.target.checked, + // }) + // } />
@@ -607,12 +652,12 @@ export default function RoomPage() { className="select select-bordered" value={gameState.rules.timeLimit} disabled={user.id !== roomHost} - onChange={(e) => - handleRuleChange({ - rule: "timeLimit", - state: parseInt(e.target.value), - }) - } + // onChange={(e) => + // handleRuleChange({ + // rule: "timeLimit", + // state: parseInt(e.target.value), + // }) + // } > @@ -639,133 +684,133 @@ export default function RoomPage() { } if (myStatus === "finished") { - if (!gameState.winners || gameState.winners.length === 0) { - throw new Error("Winners data is missing"); - } - if (winnerDisplay === 0) { - return ( -
-
-

GAME SET

-
- {gameState.winners && ( -
- {gameState.winners.map((winnersId) => ( -

- {gameState.names[winnersId]} -

- ))} -

WIN!!

-
- )} -
- - Array(gameState?.rules.boardSize).fill(false), - )} - /> -
-
- -
-
- ); - } - if (winnerDisplay === gameState.winners.length) { - return ( -
-
-

- Result {winnerDisplay}/{gameState.winners.length} -

-
-
- -
-
- -
-
- - -
-
- ); - } - return ( -
-
-

- Result {winnerDisplay}/{gameState.winners.length} -

-
-
- -
-
- -
-
- - -
-
- ); + // if (!gameState.winners || gameState.winners.length === 0) { + // throw new Error("Winners data is missing"); + // } + // if (winnerDisplay === 0) { + // return ( + //
+ //
+ //

GAME SET

+ //
+ // {gameState.winners && ( + //
+ // {gameState.winners.map((winnersId) => ( + //

+ // {gameState.names[winnersId]} + //

+ // ))} + //

WIN!!

+ //
+ // )} + //
+ // + // Array(gameState?.rules.boardSize).fill(false), + // )} + // /> + //
+ //
+ // + //
+ //
+ // ); + // } + // if (winnerDisplay === gameState.winners.length) { + // return ( + //
+ //
+ //

+ // Result {winnerDisplay}/{gameState.winners.length} + //

+ //
+ //
+ // + //
+ //
+ // + //
+ //
+ // + // + //
+ //
+ // ); + // } + // return ( + //
+ //
+ //

+ // Result {winnerDisplay}/{gameState.winners.length} + //

+ //
+ //
+ // + //
+ //
+ // + //
+ //
+ // + // + //
+ //
+ // ); } if (myStatus === "playing") { @@ -779,12 +824,10 @@ export default function RoomPage() { {opponentIds && (
{opponentIds.map((opponentId) => ( - ))}
@@ -798,25 +841,27 @@ export default function RoomPage() { myId={user.id} remainingTime={Math.ceil(remainingTime / 1000)} /> - +
{/* Player's Info */}
- {gameState.missions[user.id] && ( - )}
{gameState.hands[user.id] && ( )} -
+ {/*
PASS - + */}
{gameState.status === "paused" && ( @@ -853,7 +898,7 @@ export default function RoomPage() {

An error occurred in the game. Please try again later.

- + Go back