From e2114e5150b629ce0f05f74b8845a9203820dc69 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 00:31:04 +0300 Subject: [PATCH 1/8] Code style in playground --- app/playground/page.tsx | 42 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/app/playground/page.tsx b/app/playground/page.tsx index 7455877..d6efadf 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -2,19 +2,17 @@ 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 { 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 { 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"; @@ -112,10 +110,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)); @@ -181,15 +179,19 @@ export default function PlaygroundPage() { {!visualizing && !userPlaying && (

- Click on the board to place or remove units. Use the buttons below to switch between unit types. + Click on the board to place or remove units. Use the buttons + below to switch between unit types.

)} {!visualizing && userPlaying && (

- Place a player (P) and blocks (B) on the board to create your escape route. Click to toggle between empty, player, and block. + Place a player (P) and blocks (B) on the board to create your + escape route. Click to toggle between empty, player, and block.

)} -
+
{visualizing && ( { @@ -223,8 +229,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 === " ") { @@ -252,7 +262,7 @@ export default function PlaygroundPage() { : handleChangeMap(newMap); }} /> - )) + )), )}
From 1e2981d4c77101307fcd82ab9ccf347fa02f37fc Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 00:43:37 +0300 Subject: [PATCH 2/8] Actual link to GitHub repo --- app/header.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/header.tsx b/app/header.tsx index 9e6720f..25c7948 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -99,11 +99,18 @@ export default function Header() { {!isAuthenticated ? ( From 53638688ef2f89a55e37b094b8c21ba923feb19b Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 00:44:06 +0300 Subject: [PATCH 3/8] Increase hint text margin in playground --- app/playground/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/playground/page.tsx b/app/playground/page.tsx index d6efadf..7ec09e8 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -178,13 +178,13 @@ export default function PlaygroundPage() {
{!visualizing && !userPlaying && ( -

+

Click on the board to place or remove units. Use the buttons below to switch between unit types.

)} {!visualizing && userPlaying && ( -

+

Place a player (P) and blocks (B) on the board to create your escape route. Click to toggle between empty, player, and block.

From 3718613e2623de4ebc7e89b70fc787e133c6be53 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 00:59:34 +0300 Subject: [PATCH 4/8] A way for admin to delete map during review --- app/admin/review/page.tsx | 19 +++++++++++++------ app/prompts/page.tsx | 6 ++---- convex/maps.ts | 9 +++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/admin/review/page.tsx b/app/admin/review/page.tsx index 2071686..b73e24a 100644 --- a/app/admin/review/page.tsx +++ b/app/admin/review/page.tsx @@ -12,16 +12,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 (

Review Maps

-
+
{maps?.map((map) => ( @@ -32,7 +33,7 @@ const Page = () => { - + + ))} @@ -50,6 +59,4 @@ const Page = () => { } return
Not an admin
; -}; - -export default Page; +} diff --git a/app/prompts/page.tsx b/app/prompts/page.tsx index 91b4dc3..466feeb 100644 --- a/app/prompts/page.tsx +++ b/app/prompts/page.tsx @@ -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); @@ -91,6 +91,4 @@ const Page = () => {
); -}; - -export default Page; +} diff --git a/convex/maps.ts b/convex/maps.ts index 003dc04..f824bf4 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -244,6 +244,15 @@ 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 getMapByLevel = query({ args: { level: v.number() }, handler: async (ctx, args) => { From 62736b77498e5214b2615fe0d318c7bfe95e5111 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 01:21:09 +0300 Subject: [PATCH 5/8] Display hits on zombies with different SVG --- public/entities/rocks.png | Bin 2412 -> 0 bytes renderer/index.ts | 55 +++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 9 deletions(-) delete mode 100644 public/entities/rocks.png diff --git a/public/entities/rocks.png b/public/entities/rocks.png deleted file mode 100644 index 1f2e53a75ab628ba229e93d39f059c1198e5f9f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2412 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 e{D1v7tiJ { @@ -63,18 +67,46 @@ function getEntityImage(entity: Entity): HTMLImageElement | null { return assets.rock; } case EntityType.Zombie: { - return assets.zombie; + if (entity.getHealth() === 1) { + return assets.zombieHit; + } else { + return assets.zombie; + } } } } function getEntityOffset(entity: Entity): { x: number; y: number } { return { - x: entity.getType() === EntityType.Zombie ? 16 : 0, + x: + entity.getType() === EntityType.Zombie && entity.getHealth() !== 1 + ? 8 + : 0, y: 0, }; } +function getEntityRatio(entity: Entity): { width: number; height: number } { + switch (entity.getType()) { + case EntityType.Box: { + return { width: 0.87, height: 1 }; // 41x47 + } + case EntityType.Player: { + return { width: 1, height: 1 }; // 64x64 + } + case EntityType.Rock: { + return { width: 1, height: 0.76 }; // 67x51 + } + case EntityType.Zombie: { + if (entity.getHealth() === 1) { + return { width: 0.61, height: 1 }; // 40x65 + } else { + return { width: 1, height: 1 }; // 64x64 + } + } + } +} + export class Renderer { private readonly assets: RendererAssets; private readonly cellSize: number; @@ -118,8 +150,6 @@ export class Renderer { for (const entity of entities) { this.drawEntity(entity); } - - this.ctx.globalAlpha = 1.0; } private drawBg() { @@ -158,6 +188,7 @@ export class Renderer { const entityPosition = entity.getPosition(); const entityOffset = getEntityOffset(entity); + const entityScale = getEntityRatio(entity); this.ctx.globalAlpha = entity.getType() === EntityType.Zombie && entity.getHealth() === 1 @@ -166,10 +197,16 @@ export class Renderer { this.ctx.drawImage( entityImage, - entityPosition.x * this.cellSize + entityOffset.x, - entityPosition.y * this.cellSize + entityOffset.y, - this.cellSize, - this.cellSize, + entityPosition.x * this.cellSize + + ((1 - entityScale.width) / 2) * this.cellSize + + entityOffset.x, + entityPosition.y * this.cellSize + + ((1 - entityScale.height) / 2) * this.cellSize + + entityOffset.y, + this.cellSize * entityScale.width, + this.cellSize * entityScale.height, ); + + this.ctx.globalAlpha = 1; } } From 0f022591d14ede783b979a7a0cd9ecb2a79e2c51 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 02:12:26 +0300 Subject: [PATCH 6/8] Adjust zombie offsets --- renderer/index.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/renderer/index.ts b/renderer/index.ts index 1cc422f..505efd8 100644 --- a/renderer/index.ts +++ b/renderer/index.ts @@ -77,13 +77,18 @@ function getEntityImage(entity: Entity): HTMLImageElement | null { } function getEntityOffset(entity: Entity): { x: number; y: number } { - return { - x: - entity.getType() === EntityType.Zombie && entity.getHealth() !== 1 - ? 8 - : 0, - y: 0, - }; + switch (entity.getType()) { + case EntityType.Zombie: { + if (entity.getHealth() === 1) { + return { x: -2, y: 0 }; + } else { + return { x: 14, y: 0 }; + } + } + default: { + return { x: 0, y: 0 }; + } + } } function getEntityRatio(entity: Entity): { width: number; height: number } { From 15cd47e3d755122ce81f749067c0bca2984c5230 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 02:14:46 +0300 Subject: [PATCH 7/8] Ability to preview map in playground --- app/admin/review/page.tsx | 2 ++ app/playground/page.tsx | 19 ++++++++++++++++++- components/PlayMapButton.tsx | 16 ++++++++++++++++ convex/maps.ts | 15 ++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 components/PlayMapButton.tsx diff --git a/app/admin/review/page.tsx b/app/admin/review/page.tsx index b73e24a..ad5b201 100644 --- a/app/admin/review/page.tsx +++ b/app/admin/review/page.tsx @@ -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, @@ -50,6 +51,7 @@ export default function AdminReviewPage() { > Reject + ))} diff --git a/app/playground/page.tsx b/app/playground/page.tsx index 7ec09e8..41ece90 100644 --- a/app/playground/page.tsx +++ b/app/playground/page.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { useAction, useMutation, useQuery } from "convex/react"; 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"; @@ -11,6 +12,7 @@ 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"; @@ -20,6 +22,13 @@ 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([ [" ", " ", " ", " ", " "], @@ -97,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) { @@ -167,6 +178,12 @@ export default function PlaygroundPage() { } }, []); + React.useEffect(() => { + if (adminMap !== null) { + setMap(adminMap.grid); + } + }, [adminMap]); + const visualizing = solution !== null || visualizingUserSolution; return ( diff --git a/components/PlayMapButton.tsx b/components/PlayMapButton.tsx new file mode 100644 index 0000000..21ec6a2 --- /dev/null +++ b/components/PlayMapButton.tsx @@ -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 ( + + ); +} diff --git a/convex/maps.ts b/convex/maps.ts index f824bf4..547d599 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -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 = [ { @@ -263,6 +267,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"), From 20be21205bf6aab9b6f9e38d37dfb8d1ec4abc4b Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Tue, 22 Oct 2024 02:57:52 +0300 Subject: [PATCH 8/8] Ability to delete maps from Play page --- app/header.tsx | 2 +- app/play/page.tsx | 31 +++++++++++++++++++++++++++---- convex/maps.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/app/header.tsx b/app/header.tsx index 25c7948..9238bab 100644 --- a/app/header.tsx +++ b/app/header.tsx @@ -45,7 +45,7 @@ export default function Header() { return (
- Logo + Logo SurviveTheNight diff --git a/app/play/page.tsx b/app/play/page.tsx index 332da1e..bb8f0c5 100644 --- a/app/play/page.tsx +++ b/app/play/page.tsx @@ -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"; @@ -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()); @@ -128,9 +136,24 @@ export default function PlayPage() { )} > - - Night #{map.level} - +
+ + Night #{map.level} + + {isAdmin && ( + + )} +
diff --git a/convex/maps.ts b/convex/maps.ts index 547d599..9841252 100644 --- a/convex/maps.ts +++ b/convex/maps.ts @@ -257,6 +257,38 @@ export const rejectMap = adminMutationBuilder({ }, }); +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) => {