diff --git a/holly-knight/.gitignore b/holly-knight/.gitignore new file mode 100755 index 0000000..f9ba7f8 --- /dev/null +++ b/holly-knight/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.DS_Store +server/public +vite.config.ts.* +*.tar.gz \ No newline at end of file diff --git a/holly-knight/AuroraDash-demo-gameplay.mp4 b/holly-knight/AuroraDash-demo-gameplay.mp4 new file mode 100755 index 0000000..32e6615 Binary files /dev/null and b/holly-knight/AuroraDash-demo-gameplay.mp4 differ diff --git a/holly-knight/README.md b/holly-knight/README.md new file mode 100755 index 0000000..cc34276 --- /dev/null +++ b/holly-knight/README.md @@ -0,0 +1,114 @@ +# â„ī¸ AuroraDash + +![Project Status](https://img.shields.io/badge/status-active-success.svg) +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Tech](https://img.shields.io/badge/built%20with-React%20%7C%20TypeScript%20%7C%20Canvas-blueviolet) + +> **"It works on my machine... and it flows in my mind."** + +## 📖 The Story: A Developer's Escape +**AuroraDash** is a high-fidelity, atmospheric endless runner inspired by the meditative vibes of *Alto's Adventure*. + +We know the struggle: The sprint is ending, the `console` is full of red text, and burnout is creeping in. AuroraDash was built for that exact moment. It is a procedural winter sanctuary where you trade high-pressure deadlines for shimmering auroras and soothing soundscapes. + +Steer your rubber-duck-powered sleigh through a world of `syntax errors` and `404` boulders. It isn't just a game; it's a rhythmic, chill escape designed to help developers find their "flow state." + +--- + +## 🎮 Theme Interpretation: "Games o|x" +**Theme:** Architecting a game-themed experience. + +We interpreted the theme by architecting a digital cure for developer fatigue. +* **Visual Architecture:** We engineered a custom rendering engine that prioritizes hypnotic, fluid motion (parallax snow, aurora gradients) to act as a visual palate cleanser. +* **The Metaphor:** The gameplay mirrors the developer experience. The obstacles are the bugs we fight daily, but the movement is the grace we strive for. We didn't just build a runner; we architected a **Zen Mode** for your browser. + +--- + +### đŸŽĨ Gameplay Demo + +[Watch the AuroraDash Gameplay Video](./AuroraDash-demo-gameplay.mp4) + +[Youtube](https://youtu.be/C3NT7L_8EvA) + +--- +## đŸ•šī¸ Gameplay Mechanics & Power-ups + +Navigate the snowy terrain and use your developer toolkit to survive: + +### ☕ The Boost: `Coffee` +> *Signifies: The Flow State* +Collecting a `Coffee` cup triggers a temporary speed boost. It simulates that caffeine-induced burst of productivity where code writes itself, allowing you to glide effortlessly past obstacles. + +### đŸ›Ąī¸ The Protection: `Firewall` +> *Signifies: Security & Stability* +Picking up the shield item activates a `Firewall`. This protective layer wraps your character in a secure shell, allowing you to crash through exactly one `404 Boulder` or `Syntax Error` without breaking the build (ending the run). + +### 👾 The Obstacles +* **`404` Boulders:** Large, immovable rocks representing missing resources. +* **Syntax Spikes:** Sharp coding errors that must be jumped over. + +--- + +## 🚀 Tech Stack + +We utilized a modern, type-safe stack to ensure performance and developer experience: + +* **Frontend:** `React 18`, `TypeScript`, `Vite` +* **Game Engine:** `HTML5 Canvas API` (Custom implementation for fluid physics) +* **Styling:** `Tailwind CSS` +* **Animations:** `Framer Motion` (UI Transitions) +* **State Management:** `TanStack React Query` +* **Icons:** `Lucide React` + +--- + +## âš™ī¸ Installation & Setup + +Follow these steps to get the game running locally. + +**Prerequisites:** Node.js (`v18+`) + +1. **Clone the Repository** + ```bash + git clone https://github.com/codev-aryan/codejam-v6.git + ``` + +2. **Navigate to the Project Directory** + The game lives inside the `holly-knight` folder. + ```bash + cd codejam-v6/holly-knight + ``` + +3. **Install Dependencies** + ```bash + npm install + ``` + +4. **Start the Development Server** + ```bash + npm run dev + ``` + +5. **Launch** + Open your browser and navigate to `http://localhost:5173`. + +--- + +## đŸ‘Ĩ The Team + +* **Aryan Mehta (Team Leader)** + * *Role:* Project Architect & Lead Developer + * *Contrib:* Core game loop, `Canvas` physics engine, and procedural generation logic. + +* **Ishaan Sahi** + * *Role:* UI/UX & Game Logic + * *Contrib:* Interface design, power-up mechanics (`Coffee`/`Firewall` logic), and developer-themed assets. + +--- + +## 📜 License + +This project is Open Source under the **MIT License**. + +* **Audio:** Custom procedural audio via `Web Audio API`. +* **Inspiration:** *Alto's Adventure*. \ No newline at end of file diff --git a/holly-knight/client/index.html b/holly-knight/client/index.html new file mode 100755 index 0000000..f657986 --- /dev/null +++ b/holly-knight/client/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + AuroraDash + + +
+ + + \ No newline at end of file diff --git a/holly-knight/client/public/favicon.png b/holly-knight/client/public/favicon.png new file mode 100755 index 0000000..625caff Binary files /dev/null and b/holly-knight/client/public/favicon.png differ diff --git a/holly-knight/client/requirements.md b/holly-knight/client/requirements.md new file mode 100755 index 0000000..e91e7d0 --- /dev/null +++ b/holly-knight/client/requirements.md @@ -0,0 +1,8 @@ +## Packages +framer-motion | Smooth UI transitions for game states and overlays + +## Notes +Game logic uses HTML5 Canvas API in a React component +Game runs at 60fps using requestAnimationFrame +Audio assets are procedurally generated or standard HTML5 Audio +High scores are persisted via /api/scores diff --git a/holly-knight/client/src/App.tsx b/holly-knight/client/src/App.tsx new file mode 100755 index 0000000..6ac1f4b --- /dev/null +++ b/holly-knight/client/src/App.tsx @@ -0,0 +1,29 @@ +import { Switch, Route } from "wouter"; +import { queryClient } from "./lib/queryClient"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import Home from "@/pages/Home"; +import NotFound from "@/pages/not-found"; + +function Router() { + return ( + + + + + ); +} + +function App() { + return ( + + + + + + + ); +} + +export default App; diff --git a/holly-knight/client/src/components/GameCanvas.tsx b/holly-knight/client/src/components/GameCanvas.tsx new file mode 100755 index 0000000..1658431 --- /dev/null +++ b/holly-knight/client/src/components/GameCanvas.tsx @@ -0,0 +1,239 @@ +import { useEffect, useRef, useState } from 'react'; +import { GameEngine } from '@/game/engine'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Button } from '@/components/ui/button'; +import { Volume2, VolumeX, Play, RotateCcw } from 'lucide-react'; + +export default function GameCanvas() { + const canvasRef = useRef(null); + const engineRef = useRef(null); + const requestRef = useRef(); + + const [gameState, setGameState] = useState<'start' | 'playing' | 'gameover'>('start'); + const [score, setScore] = useState(0); + const [isMuted, setIsMuted] = useState(false); + const audioContextRef = useRef(null); + const bgMusicRef = useRef(null); + + // Audio System + const playSound = (type: 'jump' | 'hit') => { + if (isMuted) return; + if (!audioContextRef.current) audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)(); + const ctx = audioContextRef.current; + const osc = ctx.createOscillator(); + const gain = ctx.createGain(); + + if (type === 'jump') { + osc.type = 'triangle'; + osc.frequency.setValueAtTime(150, ctx.currentTime); + osc.frequency.exponentialRampToValueAtTime(400, ctx.currentTime + 0.1); + gain.gain.setValueAtTime(0.1, ctx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2); + } else { + // Zen-like chime for crash/reset + osc.type = 'sine'; + osc.frequency.setValueAtTime(523.25, ctx.currentTime); // C5 + osc.frequency.exponentialRampToValueAtTime(261.63, ctx.currentTime + 0.5); // Slide down to C4 + gain.gain.setValueAtTime(0.1, ctx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.8); + } + + osc.connect(gain); + gain.connect(ctx.destination); + osc.start(); + osc.stop(ctx.currentTime + 0.3); + }; + + const startBackgroundMusic = () => { + if (!audioContextRef.current) audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)(); + const ctx = audioContextRef.current; + + // Richer synth pad for background + const masterGain = ctx.createGain(); + masterGain.gain.setValueAtTime(0.04, ctx.currentTime); + masterGain.connect(ctx.destination); + + const playNote = (freq: number, startTime: number, duration: number = 6) => { + const osc = ctx.createOscillator(); + const g = ctx.createGain(); + osc.type = 'triangle'; + osc.frequency.setValueAtTime(freq, startTime); + + // Soft envelope + g.gain.setValueAtTime(0, startTime); + g.gain.linearRampToValueAtTime(0.3, startTime + duration * 0.4); + g.gain.linearRampToValueAtTime(0, startTime + duration); + + osc.connect(g); + g.connect(masterGain); + osc.start(startTime); + osc.stop(startTime + duration); + }; + + const scheduleMusic = () => { + const scales = [ + [261.63, 329.63, 392.00, 523.25], // C Major + [349.23, 440.00, 523.25, 698.46], // F Major + [392.00, 493.88, 587.33, 783.99] // G Major + ]; + + const playLoop = () => { + if (!isMuted && gameState !== 'gameover') { + const scale = scales[Math.floor(Date.now() / 10000) % scales.length]; + const note = scale[Math.floor(Math.random() * scale.length)]; + playNote(note, ctx.currentTime); + // Add a low fifth for depth + playNote(note * 0.66, ctx.currentTime, 8); + } + setTimeout(playLoop, 4000); + }; + playLoop(); + }; + scheduleMusic(); + }; + + // Initialize Engine + useEffect(() => { + if (!canvasRef.current) return; + + // Start music early + if (!audioContextRef.current) startBackgroundMusic(); + + const engine = new GameEngine(canvasRef.current); + engineRef.current = engine; + + engine.onScoreUpdate = (s) => setScore(s); + engine.onGameOver = (finalScore) => { + setScore(finalScore); + setGameState('gameover'); + playSound('hit'); + }; + + const animate = () => { + engine.update(); + engine.draw(); + requestRef.current = requestAnimationFrame(animate); + }; + + requestRef.current = requestAnimationFrame(animate); + + const handleResize = () => engine.resize(); + window.addEventListener('resize', handleResize); + + return () => { + if (requestRef.current) cancelAnimationFrame(requestRef.current); + window.removeEventListener('resize', handleResize); + }; + }, []); + + // Input Handling + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.code === 'Space') { + e.preventDefault(); + if (gameState === 'playing') { + const jumped = engineRef.current?.jump(); + if (jumped) { + playSound('jump'); + } + } + } + }; + + const handleMouseDown = (e: MouseEvent) => { + if (gameState === 'playing') { + const jumped = engineRef.current?.jump(); + if (jumped) { + playSound('jump'); + } + } + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('mousedown', handleMouseDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('mousedown', handleMouseDown); + }; + }, [gameState]); + + const startGame = () => { + if (!audioContextRef.current) startBackgroundMusic(); + engineRef.current?.start(); + setGameState('playing'); + setScore(0); + }; + + return ( +
+ + + {/* HUD - Score */} +
+
+ {score} m +
+
+ + + {gameState === 'start' && ( + +
+

+ AuroraDash +

+ +
+

+ Welcome to the chill zone. No deadlines here.{"\n"} + Tap or Spacebar to debug. +

+ +
+
+
+ )} + + {gameState === 'gameover' && ( + +
+
+

Uncaught ReferenceError

+
{score}
+

Skill not found. Drink more coffee.

+
+ +
+ +
+
+
+ )} +
+
+ ); +} diff --git a/holly-knight/client/src/components/ui/accordion.tsx b/holly-knight/client/src/components/ui/accordion.tsx new file mode 100755 index 0000000..e6a723d --- /dev/null +++ b/holly-knight/client/src/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/holly-knight/client/src/components/ui/alert-dialog.tsx b/holly-knight/client/src/components/ui/alert-dialog.tsx new file mode 100755 index 0000000..8722561 --- /dev/null +++ b/holly-knight/client/src/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/holly-knight/client/src/components/ui/alert.tsx b/holly-knight/client/src/components/ui/alert.tsx new file mode 100755 index 0000000..41fa7e0 --- /dev/null +++ b/holly-knight/client/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/holly-knight/client/src/components/ui/aspect-ratio.tsx b/holly-knight/client/src/components/ui/aspect-ratio.tsx new file mode 100755 index 0000000..c4abbf3 --- /dev/null +++ b/holly-knight/client/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/holly-knight/client/src/components/ui/avatar.tsx b/holly-knight/client/src/components/ui/avatar.tsx new file mode 100755 index 0000000..fc7f964 --- /dev/null +++ b/holly-knight/client/src/components/ui/avatar.tsx @@ -0,0 +1,51 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/holly-knight/client/src/components/ui/badge.tsx b/holly-knight/client/src/components/ui/badge.tsx new file mode 100755 index 0000000..b59d7ad --- /dev/null +++ b/holly-knight/client/src/components/ui/badge.tsx @@ -0,0 +1,38 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + // Whitespace-nowrap: Badges should never wrap. + "whitespace-nowrap inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" + + " hover-elevate " , + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow-xs", + secondary: "border-transparent bg-secondary text-secondary-foreground", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow-xs", + + outline: " border [border-color:var(--badge-outline)] shadow-xs", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants } diff --git a/holly-knight/client/src/components/ui/breadcrumb.tsx b/holly-knight/client/src/components/ui/breadcrumb.tsx new file mode 100755 index 0000000..60e6c96 --- /dev/null +++ b/holly-knight/client/src/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>