Skip to content

Commit

Permalink
Merge pull request #143 from webdevcody/add-in-multiplayer-support
Browse files Browse the repository at this point in the history
Add in multiplayer support
  • Loading branch information
webdevcody authored Nov 4, 2024
2 parents 9886c47 + fa2027d commit 370b9b6
Show file tree
Hide file tree
Showing 57 changed files with 1,726 additions and 869 deletions.
11 changes: 8 additions & 3 deletions app/games/[gameId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { Page } from "@/components/Page";
import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";

export default function GamePage({ params }: { params: { gameId: string } }) {
export default function GamePage({
params,
}: {
params: { gameId: Id<"games"> };
}) {
const game = useQuery(api.games.getGame, {
gameId: params.gameId as Id<"games">,
gameId: params.gameId,
});

const results = useQuery(api.results.getResults, {
gameId: params.gameId as Id<"games">,
gameId: params.gameId,
});

return (
Expand Down
51 changes: 15 additions & 36 deletions app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,30 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { api } from "@/convex/_generated/api";

// Define the types for the data
interface Ranking {
_id: string;
modelId: string;
level?: string;
wins: number;
losses: number;
}

interface Stats {
interface LeaderBoardStats {
wins: number;
losses: number;
total: number;
ratio: number;
}

const LeaderBoard = () => {
const globalRanking = useQuery(api.leaderboard.getGlobalRankings) as
| Ranking[]
| undefined;
const levelRanking = useQuery(api.leaderboard.getLevelRankings, {}) as
| Ranking[]
| undefined;

// Transform the levelRanking data into a pivot table structure
const pivotLevelData = (levelRanking: Ranking[] | undefined) => {
const levels: Record<string, Record<string, Stats>> = {};

levelRanking?.forEach((item) => {
if (!levels[item.level!]) {
levels[item.level!] = {};
}

levels[item.level!][item.modelId] = {
wins: item.wins,
losses: item.losses,
total: item.wins + item.losses,
ratio: item.wins / (item.wins + item.losses),
};
});
const globalRanking = useQuery(api.leaderboard.getGlobalRankings);
const levelRanking = useQuery(api.leaderboard.getLevelRankings, {});
const pivotedLevelData: Record<string, Record<string, LeaderBoardStats>> = {};

return levels;
};
levelRanking?.forEach((item) => {
if (!pivotedLevelData[item.level]) {
pivotedLevelData[item.level] = {};
}

const pivotedLevelData = pivotLevelData(levelRanking);
pivotedLevelData[item.level][item.modelId] = {
wins: item.wins,
losses: item.losses,
total: item.wins + item.losses,
ratio: item.wins / (item.wins + item.losses),
};
});

// Get all unique model IDs to dynamically create columns
const allModels = Array.from(
Expand Down
38 changes: 38 additions & 0 deletions app/multiplayer/[multiplayerGameId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { useQuery } from "convex/react";
import { Page, PageTitle } from "@/components/Page";
import { Visualizer } from "@/components/Visualizer";
import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";

export default function MultiplayerPage({
params,
}: {
params: { multiplayerGameId: Id<"multiplayerGames"> };
}) {
const multiplayerGame = useQuery(api.multiplayerGames.getMultiplayerGame, {
multiplayerGameId: params.multiplayerGameId,
});

if (multiplayerGame === undefined) {
return <div>Loading...</div>;
}

if (multiplayerGame === null) {
return <div>Game not found.</div>;
}

return (
<Page>
<PageTitle>Multiplayer</PageTitle>
<div className="flex justify-center">
<Visualizer
controls={false}
map={multiplayerGame.boardState}
simulatorOptions={{ multiplayer: true }}
/>
</div>
</Page>
);
}
1 change: 0 additions & 1 deletion app/play/[level]/[attempt]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import * as React from "react";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
import { useQuery } from "convex/react";
import Link from "next/link";
Expand Down
2 changes: 1 addition & 1 deletion app/play/[level]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { DEFAULT_REPLAY_SPEED } from "@/constants/visualizer";
import { api } from "@/convex/_generated/api";
import { ZombieSurvival } from "@/simulators/zombie-survival";
import { ZombieSurvival } from "@/simulator";

type PlacementMode = "player" | "block" | "landmine";

Expand Down
12 changes: 3 additions & 9 deletions app/play/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { cn } from "@/lib/utils";

export default function PlayPage() {
const isAdmin = useQuery(api.users.isAdmin);
const maps = useQuery(api.maps.getMaps, {});
const maps = useQuery(api.maps.getMaps);
const userMapResults = useQuery(api.playerresults.getUserMapStatus);
const mapCountResults = useQuery(api.playerresults.getMapsWins);
const adminDeleteMapMutation = useMutation(api.maps.deleteMap);
Expand All @@ -51,17 +51,11 @@ export default function PlayPage() {
const res = new Map<string, boolean>();
const ctr = new Map<string, number>();

for (const result of userMapResults as {
mapId: string;
hasWon: boolean;
}[]) {
for (const result of userMapResults) {
res.set(result.mapId, result.hasWon);
}

for (const result of mapCountResults as {
mapId: string;
count: number;
}[]) {
for (const result of mapCountResults) {
ctr.set(result.mapId, result.count);
}

Expand Down
3 changes: 1 addition & 2 deletions app/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ import { Id } from "@/convex/_generated/dataModel";
import { SIGN_IN_ERROR_MESSAGE } from "@/convex/users";
import { useAITesting } from "@/hooks/useAITesting";
import { errorMessage } from "@/lib/utils";
import { ZombieSurvival } from "@/simulators/zombie-survival";
import { ZombieSurvival } from "@/simulator";

const STORAGE_MAP_KEY = "playground-map";
const STORAGE_MODEL_KEY = "playground-model";

export default function PlaygroundPage() {
const isAdmin = useQuery(api.users.isAdmin);
const submitMap = useMutation(api.maps.submitMap);
const testMap = useAction(api.maps.testMap);
const searchParams = useSearchParams();
const mapId = searchParams.get("map") as Id<"maps"> | null;
const adminMapMaybe = useQuery(
Expand Down
3 changes: 2 additions & 1 deletion app/prompts/[promptId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { Page } from "@/components/Page";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";

export default function EditPromptPage({
params,
}: {
params: { promptId: string };
params: { promptId: Id<"prompts"> };
}) {
const prompt = useQuery(api.prompts.getPromptById, {
promptId: params.promptId,
Expand Down
2 changes: 1 addition & 1 deletion app/result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Result({ result }: { result: ResultWithGame }) {
<Card className="flex flex-col gap-8 rounded-xl border p-4 sm:flex-row">
{result.status === "completed" && (
<Visualizer
cellSize="32"
cellSize={32}
autoReplay
autoStart
controls={false}
Expand Down
8 changes: 3 additions & 5 deletions app/test/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import * as React from "react";
import { useState } from "react";
import { useMutation, useQuery } from "convex/react";
import { useRouter } from "next/navigation";
import { ModelSelector } from "@/components/ModelSelector";
Expand All @@ -10,7 +10,7 @@ import { api } from "@/convex/_generated/api";

export default function TestPage() {
const testModel = useMutation(api.games.testModel);
const [model, setModel] = React.useState("");
const [model, setModel] = useState("");
const router = useRouter();
const activeModels = useQuery(api.models.getActiveModels);

Expand Down Expand Up @@ -39,9 +39,7 @@ export default function TestPage() {

await Promise.all(
activeModels.map((model) => {
return testModel({
modelId: model.slug,
});
return testModel({ modelId: model.slug });
}),
);

Expand Down
8 changes: 4 additions & 4 deletions components/CopyMapButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import * as React from "react";
import { useEffect, useRef, useState } from "react";
import { ClipboardCopyIcon, SmileIcon } from "lucide-react";

export function CopyMapButton({ map }: { map: string[][] }) {
const timeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const [copied, setCopied] = React.useState(false);
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
const [copied, setCopied] = useState(false);

async function handleClick() {
await navigator.clipboard.writeText(JSON.stringify(map));
Expand All @@ -17,7 +17,7 @@ export function CopyMapButton({ map }: { map: string[][] }) {
}, 2000);
}

React.useEffect(() => {
useEffect(() => {
return () => {
if (timeout.current !== null) {
clearTimeout(timeout.current);
Expand Down
2 changes: 1 addition & 1 deletion components/MapStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ZombieSurvival } from "@/simulators/zombie-survival";
import { ZombieSurvival } from "@/simulator";

export function MapStatus({ map }: { map: string[][] }) {
const isWin = ZombieSurvival.isWin(map);
Expand Down
4 changes: 2 additions & 2 deletions components/ModelSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from "react";
import { useEffect } from "react";
import { useQuery } from "convex/react";
import {
Select,
Expand All @@ -18,7 +18,7 @@ export function ModelSelector({
}) {
const models = useQuery(api.models.getActiveModels);

React.useEffect(() => {
useEffect(() => {
if (models !== undefined && models.length !== 0 && value === "") {
onChange(models[0].slug);
}
Expand Down
1 change: 0 additions & 1 deletion components/PlayMapButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import * as React from "react";
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "./ui/button";
Expand Down
43 changes: 43 additions & 0 deletions components/Renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import { DEFAULT_REPLAY_SPEED } from "@/constants/visualizer";
import { Renderer } from "@/renderer";
import { ZombieSurvival } from "@/simulator";

export function useRenderer(
map: string[][] | null | undefined,
canvas: React.MutableRefObject<HTMLCanvasElement | null>,
cellSize: number = 64,
replaySpeed: number = DEFAULT_REPLAY_SPEED,
) {
const [renderer, setRenderer] = useState<Renderer | null>(null);

useEffect(() => {
if (map === null || map === undefined) {
return;
}

const boardWidth = ZombieSurvival.boardWidth(map);
const boardHeight = ZombieSurvival.boardHeight(map);

async function handleInitializeRenderer() {
if (canvas.current === null) {
return;
}

const renderer = new Renderer(
boardWidth,
boardHeight,
canvas.current,
cellSize,
replaySpeed,
);

await renderer.initialize();
setRenderer(renderer);
}

void handleInitializeRenderer();
}, [map, cellSize, replaySpeed]);

return renderer;
}
Loading

0 comments on commit 370b9b6

Please sign in to comment.