Skip to content

Commit

Permalink
refactor(web): use routing to start games
Browse files Browse the repository at this point in the history
  • Loading branch information
Neosoulink committed Dec 1, 2024
1 parent 429e3ec commit 3535c51
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 185 deletions.
22 changes: 12 additions & 10 deletions apps/web/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { FC } from "react";
import { Routes as NativeRoutes, Route } from "react-router";
import { FC, Fragment } from "react";
import { Routes, Route } from "react-router";

import { MainMenuComponent } from "./shared/components";
import { MainLayout } from "./shared/layouts";
import { HomeRoute } from "./routes/home.route";
import { PlayRoute } from "./routes/play.route";

export const App: FC = () => (
<>
<NativeRoutes>
<Route index element={<HomeRoute />} />
</NativeRoutes>

<MainMenuComponent />
</>
<Fragment>
<Routes>
<Route path="/" element={<MainLayout />}>
<Route index element={<HomeRoute />} />
<Route path="/play" element={<PlayRoute />} />
</Route>
</Routes>
</Fragment>
);
17 changes: 14 additions & 3 deletions apps/web/src/routes/home.route.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { useMainMenuStore } from "../shared/stores";

export const HomeRoute = () => {
const { openMenu } = useMainMenuStore();
const { openMenu, openNewGameSection } = useMainMenuStore();

return (
<main className="flex flex-col justify-center items-center h-screen gap-8">
<div className="flex flex-col justify-center items-center">
<h1 className="text-4xl"> Chess Dimension </h1>
<p className="text-2xl">Basic 3D chess game built with Three.js </p>
<p className="text-2xl">
3D chess game built with{" "}
<code className="font-semibold">Three.js</code>
</p>
</div>

<button
className="transition-colors bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => openMenu()}
onClick={() => {
openMenu();
setTimeout(openNewGameSection, 0);
}}
>
Start a new game
</button>

<small>
Or open the main menu with{" "}
<code className="border rounded p-[1px]">Esc</code>
</small>
</main>
);
};
25 changes: 25 additions & 0 deletions apps/web/src/routes/play.route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FC, useCallback, useEffect, useMemo } from "react";

import { useGame } from "../shared/hooks";
import { getGameModeFromUrl } from "../shared/utils";
import { useLocation } from "react-router";

export const PlayRoute: FC = () => {
const location = useLocation();

const { state: gameState, init: initGame, dispose: disposeGame } = useGame();

useEffect(() => {
if (!gameState.app && !gameState.isPending && !gameState.isReady) {
getGameModeFromUrl();
initGame();
}

return () => {
if (gameState.app && !gameState.isPending && gameState.isReady)
disposeGame();
};
}, [disposeGame, gameState, initGame, location]);

return null;
};
85 changes: 0 additions & 85 deletions apps/web/src/shared/components/game.component.tsx

This file was deleted.

111 changes: 77 additions & 34 deletions apps/web/src/shared/components/main-menu.component.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import { FC } from "react";
import { Link, useBeforeUnload } from "react-router";
import { FC, useCallback, useEffect } from "react";
import { Link, useLocation } from "react-router";

import { useMainMenuStore } from "../stores";
import { GameMode } from "../enum";
import { stopEventPropagation } from "../utils";
import { useMainMenuStore } from "../stores";

export interface MainMenuComponentProps {}

/** @internal */
const GameModeOptions: {
label: string;
title: string;
mode: keyof typeof GameMode;
}[] = [
{ label: "AI", mode: "ai", title: "Play against the computer" },
{
label: "Human",
mode: "human",
title: "Play against another human player"
},
{ label: "Free Mode", mode: "free", title: "Play against yourself" },
{
label: "Simulation",
mode: "simulation",
title: "Watch the AIs playing against each other"
}
];

/** @internal */
const MainSection: FC = () => {
const { openNewGameSection } = useMainMenuStore();

return (
<nav className="flex flex-col text-2xl">
<Link
to="/"
className="py-4 hover:pl-2 hover:bg-gray-100 border-b border-gray-300 transition-[padding,background-color] duration-300"
>
Home
</Link>

<button
onClick={openNewGameSection}
className="py-4 hover:pl-2 hover:bg-gray-100 border-b border-gray-300 text-left transition-[padding,background-color] duration-300"
Expand All @@ -29,6 +57,7 @@ const MainSection: FC = () => {
);
};

/** @internal */
const NewGameSection: FC = () => {
const { closeNewGameSection } = useMainMenuStore();

Expand All @@ -38,49 +67,63 @@ const NewGameSection: FC = () => {
<h2 className="text-xl mb-2">Choose your game mode:</h2>

<div className="flex flex-wrap gap-4 text-xl">
<button
className="p-5 rounded shadow-md hover:bg-gray-100"
title="Play against the computer"
>
AI
</button>

<button
className="p-5 rounded shadow-md hover:bg-gray-100"
title="Play against another human player"
>
Human
</button>

<button
className="p-5 rounded shadow-md hover:bg-gray-100"
title="Play against yourself"
>
Free Mode
</button>

<button
className="p-5 rounded shadow-md hover:bg-gray-100"
title="Watch the computer play against itself"
>
Simulation
</button>
{GameModeOptions.map((option) => (
<Link
key={option.mode}
to={`/play?mode=${option.mode}`}
title={option.title}
viewTransition
className="p-5 rounded shadow-md hover:bg-gray-100"
>
{option.label}
</Link>
))}
</div>
</div>

<button onClick={closeNewGameSection}>Close</button>
<button className="shadow-md p-2 rounded" onClick={closeNewGameSection}>
Return
</button>
</section>
);
};

export const MainMenuComponent: FC<MainMenuComponentProps> = () => {
const { isMenuOpen, isNewGameSectionOpen, closeMenu } = useMainMenuStore();
const location = useLocation();

const {
isMenuOpen,
isNewGameSectionOpen,
openMenu,
closeMenu,
closeNewGameSection
} = useMainMenuStore();

const handleEscPress = useCallback(
(event: KeyboardEvent) => {
if (event.key === "Escape" && isMenuOpen) closeMenu();
if (event.key === "Escape" && !isMenuOpen) openMenu();
},
[isMenuOpen, closeMenu, openMenu]
);

useEffect(() => {
closeMenu();
closeNewGameSection();
}, [closeMenu, closeNewGameSection, location]);

useEffect(() => {
document.addEventListener("keydown", handleEscPress);
return () => document.removeEventListener("keydown", handleEscPress);
}, [handleEscPress]);

useBeforeUnload(closeMenu);
useEffect(() => {
if (isMenuOpen) closeNewGameSection();
}, [closeNewGameSection, isMenuOpen]);

return (
<div
className={`fixed h-dvh w-dvw flex justify-center items-center z-50 top-0 left-0 p-4 bg-gradient-to-b from-gray-900/40 via-gray-950/80 to-gray-900/40 transition-opacity duration-300 overflow-hidden ${isMenuOpen ? "opacity-100" : "pointer-events-none opacity-0 delay-100"}`}
className={`fixed h-dvh w-dvw flex justify-center items-center z-50 top-0 left-0 p-4 bg-gradient-to-b from-gray-900/40 via-gray-950/80 to-gray-900/40 transition-opacity duration-300 overflow-hidden ${isMenuOpen ? "opacity-100" : "pointer-events-none opacity-0"}`}
onClick={closeMenu}
>
<section
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/shared/enum/game.enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export enum GameMode {
ai = "AI_MODE",
HUMAN = "HUMAN_MODE",
FREE = "FREE_MODE",
SIMULATION = "SIMULATION_MODE"
human = "HUMAN_MODE",
free = "FREE_MODE",
simulation = "SIMULATION_MODE"
}
1 change: 1 addition & 0 deletions apps/web/src/shared/enum/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./game.enum";
Loading

0 comments on commit 3535c51

Please sign in to comment.