added the games route and two test game sub-routes for colyseus testing.
This commit is contained in:
80
src/app/games/page.tsx
Normal file
80
src/app/games/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
100
src/app/games/tix-tax/page.tsx
Normal file
100
src/app/games/tix-tax/page.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
192
src/app/games/visions-of-reality/page.tsx
Normal file
192
src/app/games/visions-of-reality/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
12
src/env.d.ts
vendored
Normal 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 {}
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user