added the games route and two test game sub-routes for colyseus testing.

This commit is contained in:
HP
2026-01-16 21:12:54 -05:00
parent e3141030ab
commit ff6eb30f96
8 changed files with 580 additions and 5 deletions

80
src/app/games/page.tsx Normal file
View File

@@ -0,0 +1,80 @@
"use client"
import React from "react"
import {
Box,
Card,
Container,
List,
ListItemButton,
ListItemText,
Stack,
Typography,
} from "@mui/material";
import { useRouter } from "next/navigation";
import { paths } from "@/paths";
const navItems = [
{ label: "Visions Of Reality", path: paths.games.vor },
{ label: "Tix-Tax", path: paths.games.tixtax },
];
export default function VisionsOfReality() {
const router = useRouter();
return (
<Box
sx={{
minHeight: "100vh",
position: "relative",
overflow: "hidden",
}}
>
<Stack spacing={2}>
<Box>
<Typography variant="h5" fontWeight={700}>
Some online games available to you.
</Typography>
<Typography variant="body2" sx={{ opacity: 0.85 }}>
Choose a one to continue.
</Typography>
</Box>
<List
disablePadding
sx={{
display: "flex",
flexDirection: "column",
gap: 1,
}}
>
{navItems.map((item) => (
<ListItemButton
key={item.path}
onClick={() => router.push(item.path)}
sx={{
borderRadius: 2,
border: "1px solid rgba(255,255,255,0.16)",
backgroundColor: "rgba(0,0,0,0.18)",
transition: "transform 120ms ease, background-color 120ms ease",
"&:hover": {
backgroundColor: "rgba(255,255,255,0.12)",
transform: "translateX(-4px)",
},
"&:active": {
transform: "translateX(-2px) scale(0.99)",
},
}}
>
<ListItemText
primary={item.label}
primaryTypographyProps={{
fontWeight: 600,
}}
/>
</ListItemButton>
))}
</List>
</Stack>
</Box>
)
}

View File

@@ -0,0 +1,100 @@
'use client'
import React from "react"
import { Client, Room } from "colyseus.js"
import { Box, Button } from "@mui/material";
interface TixTaxLike{
playerOne: { name: string; coins: number },
playerTwo: { name: string; coins: number }
}
export default function Page() {
const [client] = React.useState(() => new Client("http://prospera:2567"));
const [room, setRoom] = React.useState<Room<TixTaxLike>>();
const [state, setState] = React.useState<TixTaxLike | undefined>(undefined);
const handleFindMatch = async () => {
const room = await client.joinOrCreate("tix_tax")
setRoom(room);
room.onStateChange((state) => {
setState(state)
})
room.onLeave(() => {
})
}
return (
<>
<Box>
Welcome to Tix Tax
<Button>
How to play
</Button>
</Box>
{state ? <TixTax state={state}/> : <>
<Button onClick={handleFindMatch} >
Find a match
</Button>
</>}
<div style={{
display: "grid",
gridTemplateColumns: "300px 300px 300px",
gridAutoRows: "300 px",
gap: "1em",
justifyItems: "center",
alignItems: "center",
justifyContent: "center",
alignContent: "center"
}}>
{Array.from({ length: 11 }, (_, index) => (
<div style={{
height: "200px",
width: "200px",
padding: "2em",
backgroundColor: "var(--primary-color)",
color: "white",
textAlign: "center",
"borderRadius": "8px",
fontSize: "2rem",
}}>{index + 1}</div>
))}
</div>
</>
)
}
function TixTax({ state }: { state: TixTaxLike} ){
return (
<>
<Box>
Player one Coins {state.playerOne.coins}
</Box>
<Box>
{Array.from({ length: 3 }, (_, i) => (
<Box sx={{
border: 2,
display: "grid",
placeContent: "center"
}}>
{Array.from({ length: 3 }, (_, j) => (
<Box sx={{
border: 2,
}}>
</Box>
))}
</Box>
))}
</Box>
</>
)
}

View File

@@ -0,0 +1,192 @@
"use client"
import React from "react"
import { Client, Room } from "colyseus.js"
import { Button } from "@mui/material"
type RealityLike = {
barracks?: Record<string, number>
entities?: Record<string, unknown>
hero?: unknown
}
type GameStateLike = {
realities?: Record<string, RealityLike> | Map<string, RealityLike>
home_reality_index?: number
target_reality_index?: number
}
const ROOM_NAME = "main_game";
function toRealityEntries(realities: GameStateLike["realities"]): Array<[string, RealityLike]> {
if (!realities) return []
if (typeof (realities as Map<string, RealityLike>).entries === "function") {
return Array.from((realities as Map<string, RealityLike>).entries())
}
return Object.entries(realities as Record<string, RealityLike>)
}
export default function VisionsOfReality() {
const [client] = React.useState(() => new Client(process.env.SERVER_URL))
const [room, setRoom] = React.useState<Room<GameStateLike> | null>(null)
const [status, setStatus] = React.useState("disconnected")
const [tick, setTick] = React.useState(0)
const [homeRealityIndex, setHomeRealityIndex] = React.useState<number | null>(null)
const [targetRealityIndex, setTargetRealityIndex] = React.useState<number | null>(null)
const [viewRealityIndex, setViewRealityIndex] = React.useState<number | null>(null)
React.useEffect(() => {
return () => {
room?.leave()
}
}, [room])
const connect = async () => {
setStatus("connecting")
try {
const joined = await client.joinOrCreate<GameStateLike>(ROOM_NAME)
setRoom(joined)
setStatus("connected")
setHomeRealityIndex(joined.state?.home_reality_index ?? null)
setTargetRealityIndex(joined.state?.target_reality_index ?? null)
setViewRealityIndex(joined.state?.home_reality_index ?? null)
joined.onStateChange((state) => {
setHomeRealityIndex(state.home_reality_index ?? null)
setTargetRealityIndex(state.target_reality_index ?? null)
setViewRealityIndex((prev) => (prev === null ? state.home_reality_index ?? null : prev))
setTick((t) => t + 1)
})
joined.onLeave(() => {
setStatus("disconnected")
setRoom(null)
})
} catch (err) {
console.error("Failed to join room", err)
setStatus("error")
}
}
const disconnect = async () => {
await room?.leave()
setRoom(null)
setStatus("disconnected")
}
const realities = toRealityEntries(room?.state?.realities)
const currentReality =
viewRealityIndex !== null
? realities.find(([id]) => Number(id) === viewRealityIndex)?.[1]
: undefined
const sendTarget = (index: number) => {
if (!room) return
setTargetRealityIndex(index)
room.send("set_target_reality_index", { index })
}
return (
<div
style={{
minHeight: "100vh",
display: "grid",
gridTemplateRows: "auto 1fr",
gap: 12,
padding: 12,
}}
>
<div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
<Button variant="contained" disabled={status === "connected"} onClick={connect}>
Join
</Button>
<Button variant="outlined" disabled={!room} onClick={disconnect}>
Leave
</Button>
<div>status: {status}</div>
<div>tick: {tick}</div>
<div>home: {homeRealityIndex ?? "?"}</div>
<div>view: {viewRealityIndex ?? "?"}</div>
<div>target: {targetRealityIndex ?? "?"}</div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 44px)",
gap: 6,
}}
>
{Array.from({ length: 9 })
.map((_, index) => index)
.filter((index) => index !== 4)
.filter((index) => index !== homeRealityIndex)
.map((index) => {
const isTarget = targetRealityIndex === index
const isViewing = viewRealityIndex === index
return (
<button
key={index}
onClick={() => {
sendTarget(index)
setViewRealityIndex(index)
}}
style={{
height: 44,
borderRadius: "50%",
border: "1px solid #333",
background: isTarget ? "#f0b429" : isViewing ? "#9ae6b4" : "#f7f7f7",
cursor: "pointer",
}}
title={`Reality ${index}`}
>
{index}
</button>
)
})}
</div>
</div>
<div
style={{
display: "grid",
gap: 12,
gridTemplateRows: "auto 1fr auto",
minHeight: 0,
}}
>
<div style={{ fontWeight: 600, fontSize: 18 }}>
Reality {viewRealityIndex ?? "?"} {viewRealityIndex === homeRealityIndex ? "(home)" : ""}
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(15, 1fr)",
gridAutoRows: "1fr",
gap: 2,
height: "100%",
minHeight: 0,
background: "#fafafa",
padding: 8,
borderRadius: 8,
border: "1px solid #e5e7eb",
}}
>
{Array.from({ length: 225 }).map((__, cellIndex) => (
<div
key={cellIndex}
style={{
aspectRatio: "1 / 1",
background: "#e5e7eb",
}}
/>
))}
</div>
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
<div>barracks: {currentReality?.barracks ? Object.keys(currentReality.barracks).length : 0}</div>
<div>entities: {currentReality?.entities ? Object.keys(currentReality.entities).length : 0}</div>
<div>hero: {currentReality?.hero ? "yes" : "no"}</div>
</div>
</div>
</div>
)
}

View File

@@ -16,7 +16,7 @@ import { paths } from "@/paths";
const navItems = [
{ label: "Products", path: paths.products.home },
{ label: "Pricing", path: "/pricing" },
{ label: "Games", path: paths.games.home },
{ label: "About", path: "/about" },
{ label: "Contact", path: "/contact" },
];
@@ -48,7 +48,6 @@ export default function Home() {
zIndex: 0,
}}
/>
<Box
sx={{
position: "fixed",
@@ -59,6 +58,11 @@ export default function Home() {
}}
/>
<Typography sx={{
}}>
Tartarus
</Typography>
<Container
maxWidth="lg"
sx={{
@@ -130,9 +134,6 @@ export default function Home() {
</List>
</Stack>
<br />
<Typography color="textPrimary" style={{ fontSize: 12, color: "##808080" }}>
Tartarus
</Typography>
</Card>
</Container>
</Box>

12
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
export interface Env {
DB_STRING?: string;
SERVER_URL?: string;
TIX_TAX_ROOM_NAME?: string;
}
declare global {
namespace NodeJS{
interface ProcessEnv extends Env {}
}
}

View File

@@ -3,6 +3,11 @@ export const paths = {
products: {
home: '/products',
},
games: {
home: "/games",
vor: "/games/visions-of-reality",
tixtax: "/games/tix-tax"
},
auth: {
signIn: '/auth/sign-in',
resetPassword: '/auth/reset-password',