diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f759ae8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,98 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Business Minesweeper is a real-time multiplayer minesweeper game with expanding boards, user accounts, match history, spectating, and collectibles. Built with React frontend and Bun backend using WebSockets for real-time communication. + +## Development Commands + +### Setup +```bash +# Initial setup (requires Bun installed) +echo "SECRET=SOME_RANDOM_STRING" > .env +bun install +bun run drizzle:migrate +``` + +### Development +```bash +bun dev # Start both frontend and backend in development mode +bun run dev:client # Start only frontend (Vite dev server) +bun run dev:backend # Start only backend with hot reload +``` + +### Build & Quality +```bash +bun run build # Build for production (TypeScript compilation + Vite build) +bun run lint # Run ESLint +bun run preview # Preview production build +``` + +### Database +```bash +bun run drizzle:schema # Generate database migrations +bun run drizzle:migrate # Run database migrations +bun run nukedb # Delete and recreate database (removes sqlite.db) +``` + +## Architecture + +### Frontend (`src/`) +- **Framework**: React 18 + TypeScript with Vite build system +- **Routing**: Wouter for client-side routing +- **State Management**: + - Jotai for component state (atoms in `src/atoms.ts`) + - TanStack Query for server state and caching +- **Styling**: Tailwind CSS v4 with custom CSS variables +- **Animation**: Motion (Framer Motion) for UI animations +- **Game Rendering**: Pixi.js with PixiViewport for the minesweeper board + +### Backend (`backend/`) +- **Runtime**: Bun with TypeScript +- **Architecture**: WebSocket-based real-time API with controller pattern +- **Database**: SQLite with Drizzle ORM +- **Structure**: + - `router.ts` - Main request handler and routing + - `controller/` - Business logic controllers (game, user, scoreboard) + - `repositories/` - Data access layer + - `database/` - DB connection and configuration + - `entities/` - Type definitions + - `events.ts` - Real-time event system + +### Communication +- **WebSocket Client**: `src/wsClient.ts` handles connection, reconnection, and type-safe API calls +- **Real-time Updates**: Server publishes events to all connected clients for live game updates +- **Type Safety**: Shared types between frontend/backend via `Routes` interface + +### Key Components + +#### Game Logic +- `src/views/endless/Endless.tsx` - Main game view with Pixi.js board +- `src/components/LazyBoard.tsx` - Game board rendering component +- `backend/controller/gameController.ts` - Server-side game logic + +#### UI Architecture +- `src/Shell.tsx` - Main layout with responsive drawer navigation +- `src/main.tsx` - App entry point with routing setup +- `src/components/` - Reusable UI components using Radix UI primitives + +#### Data Flow +- WebSocket mutations for game actions (reveal, flag, etc.) +- TanStack Query for caching user data, game state, leaderboards +- Jotai atoms for local UI state (current game ID, settings) + +## Key Patterns + +### WebSocket API Usage +```typescript +const mutation = useWSMutation("game.reveal"); +const { data } = useWSQuery("game.getGameState", gameId, !!gameId); +``` + +### Database Queries +Uses Drizzle ORM with repository pattern for data access. Each controller has corresponding repository in `backend/repositories/`. + +### Real-time Events +Server publishes events via `backend/events.ts`, frontend handles via WebSocket message listeners in `wsClient.ts`. \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index ab2a726..2269434 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index cba12a9..158048c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "react-confetti-boom": "^1.0.0", "react-dom": "^18.3.1", "tailwind-merge": "^2.5.3", - "use-sound": "^4.0.3", "wouter": "^3.3.5", "zod": "^3.23.8" }, diff --git a/src/components/Board.tsx b/src/components/Board.tsx index add2d08..8eb0778 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -29,7 +29,7 @@ import Coords from "./Coords"; import { cn } from "../lib/utils"; import { Button } from "./Button"; import { Maximize2, Minimize2, RotateCcw } from "lucide-react"; -import useSound from "use-sound"; +import { useAudio } from "../hooks/useAudio"; import explosion from "../sound/explosion.mp3"; import "@pixi/canvas-display"; import "@pixi/canvas-extract"; @@ -80,7 +80,7 @@ const Board: React.FC = (props) => { const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const showLastPos = game.user !== user || isServerGame(game); - const [playSound] = useSound(explosion, { + const [playSound] = useAudio(explosion, { volume: 0.5, }); diff --git a/src/hooks/useAudio.ts b/src/hooks/useAudio.ts new file mode 100644 index 0000000..f9b2b6c --- /dev/null +++ b/src/hooks/useAudio.ts @@ -0,0 +1,37 @@ +import { useCallback, useRef } from 'react'; + +interface AudioOptions { + volume?: number; + loop?: boolean; +} + +export const useAudio = (src: string, options: AudioOptions = {}) => { + const audioRef = useRef(null); + + const play = useCallback(() => { + if (!audioRef.current) { + audioRef.current = new Audio(src); + audioRef.current.volume = options.volume ?? 1; + audioRef.current.loop = options.loop ?? false; + } + + // Reset to beginning and play + audioRef.current.currentTime = 0; + audioRef.current.play().catch((error) => { + console.warn('Audio play failed:', error); + }); + }, [src, options.volume, options.loop]); + + const pause = useCallback(() => { + audioRef.current?.pause(); + }, []); + + const stop = useCallback(() => { + if (audioRef.current) { + audioRef.current.pause(); + audioRef.current.currentTime = 0; + } + }, []); + + return [play, { pause, stop }] as const; +}; \ No newline at end of file