Skip to content

Commit

Permalink
Merge branch 'main' of github.com:webdevcody/survive-the-night-sim
Browse files Browse the repository at this point in the history
  • Loading branch information
webdevcody committed Oct 22, 2024
2 parents 895df21 + 1e99338 commit b6100ec
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 48 deletions.
21 changes: 15 additions & 6 deletions app/admin/review/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useMutation, useQuery } from "convex/react";
import { Map } from "@/components/Map";
import { PlayMapButton } from "@/components/PlayMapButton";
import { Button } from "@/components/ui/button";
import {
Card,
Expand All @@ -12,16 +13,17 @@ import {
} from "@/components/ui/card";
import { api } from "@/convex/_generated/api";

const Page = () => {
export default function AdminReviewPage() {
const isAdmin = useQuery(api.users.isAdmin);
const maps = useQuery(api.maps.getMaps, { isReviewed: false });
const adminApprovalMutation = useMutation(api.maps.approveMap);
const adminRejectMapMutation = useMutation(api.maps.rejectMap);

if (isAdmin == true) {
return (
<div className="container mx-auto min-h-screen gap-8 py-12 pb-24">
<h1 className="mb-6 text-center text-3xl font-bold">Review Maps</h1>
<div className="flex flex-col items-center justify-around gap-4">
<div className="grid grid-cols-[max-content_max-content] justify-center gap-4">
{maps?.map((map) => (
<Card key={map._id}>
<CardHeader>
Expand All @@ -32,7 +34,7 @@ const Page = () => {
<Map map={map.grid} />
</CardContent>

<CardFooter className="flex justify-center">
<CardFooter className="flex justify-center gap-2">
<Button
variant="outline"
onClick={async () => {
Expand All @@ -41,6 +43,15 @@ const Page = () => {
>
Approve
</Button>
<Button
variant="destructive"
onClick={async () => {
await adminRejectMapMutation({ mapId: map._id });
}}
>
Reject
</Button>
<PlayMapButton mapId={map._id} />
</CardFooter>
</Card>
))}
Expand All @@ -50,6 +61,4 @@ const Page = () => {
}

return <div>Not an admin</div>;
};

export default Page;
}
11 changes: 9 additions & 2 deletions app/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function Header() {
return (
<header className="flex items-center justify-between border-b px-6 py-4 shadow-sm">
<Link href="/" className="flex items-center">
<Image src="/logo.png" alt="Logo" width={32} height={32} />
<Image src="/logo.png" alt="Logo" width={32} height={32} priority />
<span className="ml-2 text-xl font-bold">SurviveTheNight</span>
</Link>

Expand Down Expand Up @@ -99,11 +99,18 @@ export default function Header() {
</div>
<Button
className="w-9 shrink-0"
asChild
variant="outline"
size="icon"
type="button"
>
<GitHubLogoIcon className="h-4 w-4" />
<Link
href="https://github.com/webdevcody/survive-the-night-sim"
rel="noopener noreferrer"
target="_blank"
>
<GitHubLogoIcon className="h-4 w-4" />
</Link>
</Button>
{!isAuthenticated ? (
<SignInWithGitHub />
Expand Down
31 changes: 27 additions & 4 deletions app/play/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use client";

import { useEffect, useState } from "react";
import { Authenticated, Unauthenticated, useQuery } from "convex/react";
import {
Authenticated,
Unauthenticated,
useMutation,
useQuery,
} from "convex/react";
import { TrashIcon } from "lucide-react";
import Link from "next/link";
import { Map as GameMap } from "@/components/Map";
import { Button } from "@/components/ui/button";
Expand All @@ -18,9 +24,11 @@ import { api } from "@/convex/_generated/api";
import { cn } from "@/lib/utils";

export default function PlayPage() {
const isAdmin = useQuery(api.users.isAdmin);
const maps = useQuery(api.maps.getMaps, {});
const userMapResults = useQuery(api.playerresults.getUserMapStatus);
const mapCountResults = useQuery(api.playerresults.getMapsWins);
const adminDeleteMapMutation = useMutation(api.maps.deleteMap);

const [resMap, setResMap] = useState(new Map());
const [countMap, setCountMap] = useState(new Map());
Expand Down Expand Up @@ -128,9 +136,24 @@ export default function PlayPage() {
)}
>
<CardHeader>
<CardTitle className="text-center text-xl font-semibold">
Night #{map.level}
</CardTitle>
<div
className={`flex ${isAdmin ? "justify-between" : "justify-center"}`}
>
<CardTitle className="text-center text-xl font-semibold">
Night #{map.level}
</CardTitle>
{isAdmin && (
<Button
onClick={async () => {
await adminDeleteMapMutation({ mapId: map._id });
}}
size="icon"
variant="destructive"
>
<TrashIcon size={16} />
</Button>
)}
</div>
</CardHeader>
<CardContent className="flex flex-grow items-center justify-center">
<GameMap map={map.grid} size={52} />
Expand Down
65 changes: 46 additions & 19 deletions app/playground/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,33 @@

import * as React from "react";
import { useAction, useMutation, useQuery } from "convex/react";
import { CircleAlertIcon, EraserIcon, SendIcon, UploadIcon, ChevronLeft } from "lucide-react";
import { CopyMapButton } from "@/components/CopyMapButton";
import { MapBuilder } from "@/components/MapBuilder";
import { ChevronLeft, UploadIcon } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { Map } from "@/components/Map";
import { MapStatus } from "@/components/MapStatus";
import { ModelSelector } from "@/components/ModelSelector";
import { Visualizer } from "@/components/Visualizer";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useToast } from "@/components/ui/use-toast";
import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";
import { errorMessage } from "@/lib/utils";
import { ZombieSurvival } from "@/simulators/zombie-survival";
import { Card } from "@/components/ui/card";
import { Map } from "@/components/Map";

const STORAGE_MAP_KEY = "playground-map";

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(
api.maps.adminGetMapById,
!isAdmin || mapId === null ? "skip" : { mapId },
);
const adminMap = adminMapMaybe ?? null;
const { toast } = useToast();
const [map, setMap] = React.useState<string[][]>([
[" ", " ", " ", " ", " "],
Expand Down Expand Up @@ -99,7 +106,9 @@ export default function PlaygroundPage() {
function handleChangeMap(value: string[][]) {
setMap(value);
setError(null);
window.localStorage.setItem(STORAGE_MAP_KEY, JSON.stringify(value));
if (adminMap === null) {
window.localStorage.setItem(STORAGE_MAP_KEY, JSON.stringify(value));
}
}

function handleChangeModel(value: string) {
Expand All @@ -112,10 +121,10 @@ export default function PlaygroundPage() {
setReasoning(null);
setUserPlaying(false);
setVisualizingUserSolution(false);

// Remove players and blocks from the map
const cleanedMap = map.map(row =>
row.map(cell => (cell === "P" || cell === "B") ? " " : cell)
const cleanedMap = map.map((row) =>
row.map((cell) => (cell === "P" || cell === "B" ? " " : cell)),
);
setMap(cleanedMap);
window.localStorage.setItem(STORAGE_MAP_KEY, JSON.stringify(cleanedMap));
Expand Down Expand Up @@ -169,6 +178,12 @@ export default function PlaygroundPage() {
}
}, []);

React.useEffect(() => {
if (adminMap !== null) {
setMap(adminMap.grid);
}
}, [adminMap]);

const visualizing = solution !== null || visualizingUserSolution;

return (
Expand All @@ -180,16 +195,20 @@ export default function PlaygroundPage() {
<div className="flex-1">
<Card className="p-4">
{!visualizing && !userPlaying && (
<p className="mb-2 text-sm text-gray-600">
Click on the board to place or remove units. Use the buttons below to switch between unit types.
<p className="mb-6 text-sm text-gray-600">
Click on the board to place or remove units. Use the buttons
below to switch between unit types.
</p>
)}
{!visualizing && userPlaying && (
<p className="mb-2 text-sm text-gray-600">
Place a player (P) and blocks (B) on the board to create your escape route. Click to toggle between empty, player, and block.
<p className="mb-6 text-sm text-gray-600">
Place a player (P) and blocks (B) on the board to create your
escape route. Click to toggle between empty, player, and block.
</p>
)}
<div className={`flex justify-center ${visualizing ? "pt-[28px]" : ""}`}>
<div
className={`flex justify-center ${visualizing ? "pt-[28px]" : ""}`}
>
{visualizing && (
<Visualizer
autoReplay
Expand All @@ -213,8 +232,12 @@ export default function PlaygroundPage() {
<div
key={`${x}-${y}`}
className={`${
cell === " " || cell === "Z" || cell === "R" || cell === "P" || cell === "B"
? "z-10 cursor-pointer hover:border-2 hover:border-dashed hover:border-slate-300 "
cell === " " ||
cell === "Z" ||
cell === "R" ||
cell === "P" ||
cell === "B"
? "z-10 cursor-pointer hover:border-2 hover:border-dashed hover:border-slate-300"
: ""
} border border-transparent`}
onClick={() => {
Expand All @@ -223,8 +246,12 @@ export default function PlaygroundPage() {
: [...map];
if (userPlaying) {
// Count existing players and blocks
const playerCount = newMap.flat().filter(c => c === "P").length;
const blockCount = newMap.flat().filter(c => c === "B").length;
const playerCount = newMap
.flat()
.filter((c) => c === "P").length;
const blockCount = newMap
.flat()
.filter((c) => c === "B").length;

// Toggle logic for play mode
if (cell === " ") {
Expand Down Expand Up @@ -252,7 +279,7 @@ export default function PlaygroundPage() {
: handleChangeMap(newMap);
}}
/>
))
)),
)}
</div>
</div>
Expand Down
6 changes: 2 additions & 4 deletions app/prompts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from "@/components/ui/table";
import { api } from "@/convex/_generated/api";

const Page = () => {
export default function PromptsPage() {
const prompts = useQuery(api.prompts.getAllPrompts);
const enablePrompt = useMutation(api.prompts.enablePrompt);
const deletePrompt = useMutation(api.prompts.deletePrompt);
Expand Down Expand Up @@ -91,6 +91,4 @@ const Page = () => {
</div>
</div>
);
};

export default Page;
}
16 changes: 16 additions & 0 deletions components/PlayMapButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import * as React from "react";
import { ExternalLinkIcon } from "lucide-react";
import Link from "next/link";
import { Button } from "./ui/button";

export function PlayMapButton({ mapId }: { mapId: string }) {
return (
<Button asChild variant="outline" size="icon">
<Link href={`/playground?map=${mapId}`}>
<ExternalLinkIcon size={16} />
</Link>
</Button>
);
}
56 changes: 55 additions & 1 deletion convex/maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
query,
} from "./_generated/server";
import { Prompt } from "./prompts";
import { adminMutationBuilder, authenticatedMutation } from "./users";
import {
adminMutationBuilder,
adminQueryBuilder,
authenticatedMutation,
} from "./users";

const LEVELS = [
{
Expand Down Expand Up @@ -244,6 +248,47 @@ export const approveMap = adminMutationBuilder({
},
});

export const rejectMap = adminMutationBuilder({
args: {
mapId: v.id("maps"),
},
handler: async (ctx, args) => {
await ctx.db.delete(args.mapId);
},
});

export const deleteMap = adminMutationBuilder({
args: {
mapId: v.id("maps"),
},
handler: async (ctx, args) => {
const map = await ctx.db.get(args.mapId);

if (map === null) {
return;
}

await ctx.db.delete(args.mapId);

if (map.level === undefined) {
return;
}

const higherLevelMaps = await ctx.db
.query("maps")
.withIndex("by_level", (q) => q.gt("level", map.level))
.collect();

await Promise.all(
higherLevelMaps.map(async (higherLevelMap) => {
return await ctx.db.patch(higherLevelMap._id, {
level: higherLevelMap.level! - 1,
});
}),
);
},
});

export const getMapByLevel = query({
args: { level: v.number() },
handler: async (ctx, args) => {
Expand All @@ -254,6 +299,15 @@ export const getMapByLevel = query({
},
});

export const adminGetMapById = adminQueryBuilder({
args: {
mapId: v.id("maps"),
},
handler: async (ctx, args) => {
return await ctx.db.get(args.mapId);
},
});

export const playMapAction = internalAction({
args: {
gameId: v.id("games"),
Expand Down
Binary file removed public/entities/rocks.png
Binary file not shown.
Loading

0 comments on commit b6100ec

Please sign in to comment.