465 lines
15 KiB
Markdown
465 lines
15 KiB
Markdown
# AI Agent Guidelines for MineSeeker
|
||
|
||
This document provides guidelines and context for AI coding agents working on the MineSeeker project.
|
||
|
||
## Project Overview
|
||
|
||
**MineSeeker** is a real-time multiplayer 1v1 minesweeper game built with Symfony (PHP) and React. Players compete to claim mines on a shared 16×16 grid, with the first to reach 26 mines winning.
|
||
|
||
### Tech Stack
|
||
|
||
- **Backend:** Symfony 7.2 (PHP 8.3)
|
||
- **Frontend:** React 18, Vite 6
|
||
- **Database:** PostgreSQL 17 with materialized views
|
||
- **Storage:** MinIO (S3-compatible)
|
||
- **Real-time:** Mercure (Server-Sent Events)
|
||
- **Styling:** SCSS, MUI (Material-UI), Emotion
|
||
- **Fonts:** @fontsource packages (web), Carlito-Bold.ttf (server-side images)
|
||
|
||
### Key Features
|
||
|
||
**Core Gameplay:**
|
||
- **Multiplayer-focused:** 1v1 competitive gameplay where players race to claim more mines than their opponent
|
||
- **Win condition:** First player to claim 26 out of 51 mines wins
|
||
- **Real-time updates:** WebSocket-like gameplay using Mercure (Server-Sent Events)
|
||
- **Game restoration:** Players can resume unfinished games from where they left off
|
||
- **Bonus points system:** Rewards skilled play (blind hits, chain combos, edge mines, endgame mines, safe cell reveals)
|
||
|
||
**User Features:**
|
||
- **Authentication:** Password + optional TOTP + optional WebAuthn passkeys
|
||
- **Anonymous play:** Guest players can play without creating an account
|
||
- **Profile statistics:** Detailed stats including wins, losses, draws, win rate, average score, total mines hit, and bonus points
|
||
- **Battle history:** View and replay past games move-by-move
|
||
|
||
**Sharing & Social:**
|
||
- **Battle reports:** Shareable public pages for each completed game (`/battle/{uuid}`)
|
||
- **OG image generation:** Automatic creation of 1200×630 PNG images for social media sharing (using PHP GD)
|
||
- **Open Graph tags:** Battle share pages include rich preview cards with player names, avatars, scores, and bonus points
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
### Backend (Symfony)
|
||
|
||
```
|
||
src/
|
||
├── Controller/ # HTTP endpoints (game, profile, battle sharing)
|
||
├── Entity/ # Doctrine ORM entities
|
||
├── Repository/ # Database queries (uses QueryBuilder, not raw SQL)
|
||
├── Service/ # Business logic (BattleCardGenerator, WebAuthn, Email)
|
||
├── Dto/ # Data Transfer Objects (immutable, readonly)
|
||
├── Util/ # Game logic (TopicManager for Mercure)
|
||
└── Migrations/ # Database schema changes
|
||
```
|
||
|
||
**Important patterns:**
|
||
- Use Doctrine ORM QueryBuilder (not raw SQL) in repositories
|
||
- DTOs are `final readonly` classes with constructor property promotion
|
||
- Services use dependency injection via `config/services.yaml`
|
||
- Materialized views for performance (auto-refreshed via triggers)
|
||
|
||
### Frontend (React)
|
||
|
||
```
|
||
assets/
|
||
├── js/
|
||
│ ├── mine-seeker/ # Main game bundle (self-contained)
|
||
│ │ ├── MineSeeker.jsx # Root component, wraps GameProvider + QueryClientProvider
|
||
│ │ ├── components/ # Game-specific components
|
||
│ │ │ ├── GameBoard.jsx # Main game board grid
|
||
│ │ │ ├── GameTimer.jsx # Game timer display
|
||
│ │ │ ├── BonusBox.jsx # Bonus points indicator
|
||
│ │ │ ├── BonusStatsDialog.jsx # Bonus statistics modal
|
||
│ │ │ ├── CaptchaOverlay.jsx # Captcha challenge overlay
|
||
│ │ │ ├── ChallengeCountdown.jsx # Challenge timer
|
||
│ │ │ ├── OnlinePlayersDialog.jsx # Online players list
|
||
│ │ │ ├── WaitingOverlayContent.jsx # Waiting for opponent
|
||
│ │ │ ├── grid/ # Grid-related components (cells, mines)
|
||
│ │ │ ├── profile/ # In-game profile components (PlayerColumn)
|
||
│ │ │ ├── timer/ # Timer-related components
|
||
│ │ │ └── user/ # User-related components
|
||
│ │ ├── contexts/ # React Context API
|
||
│ │ │ ├── GameContext.jsx # Game state context
|
||
│ │ │ └── GameProvider.jsx # Context provider with state logic
|
||
│ │ ├── hooks/ # Custom React hooks
|
||
│ │ │ ├── useGameDataProvider.js # React Query data provider
|
||
│ │ │ ├── useGameRefs.jsx # Refs for DOM elements
|
||
│ │ │ ├── useGameState.jsx # Game state management
|
||
│ │ │ ├── useServerCommunication.jsx # Mercure SSE connection
|
||
│ │ │ └── useStepTimer.jsx # Step-by-step timer
|
||
│ │ └── utils/ # Game-specific utilities
|
||
│ │ └── constants.jsx # Game constants, colors, defaults
|
||
│ ├── components/ # Shared UI components
|
||
│ ├── utils/ # Shared utilities
|
||
│ ├── profile.jsx # Profile page entry
|
||
│ ├── passkey.jsx # Passkey management entry
|
||
│ └── contact.jsx # Contact form entry
|
||
├── css/
|
||
│ └── homepage/ # SCSS partials (imported by style.homepage.scss)
|
||
└── fonts/
|
||
└── Carlito-Bold.ttf # TTF font for PHP GD image generation
|
||
```
|
||
|
||
**Important patterns:**
|
||
- Vite aliases: `@mine-components`, `@mine-contexts`, `@mine-hooks`, `@mine-utils`, `@global-components`, `@global-utils`
|
||
- React Query only available inside `mine-seeker` bundle
|
||
- Avoid circular dependencies (e.g., don't import from `@global-components` inside `components/` directory)
|
||
- PropTypes required on all components
|
||
|
||
---
|
||
|
||
## Common Tasks
|
||
|
||
### Adding a New Feature
|
||
|
||
1. **Backend:**
|
||
- Create migration for schema changes
|
||
- Add/update entities and repositories
|
||
- Create DTOs for data transfer
|
||
- Add controller endpoints
|
||
- Update service configuration if needed
|
||
|
||
2. **Frontend:**
|
||
- Create components in appropriate bundle
|
||
- Add PropTypes to all components
|
||
- Use existing hooks and utilities
|
||
- Follow styled-components pattern for MUI customization
|
||
|
||
3. **Documentation:**
|
||
- Update relevant docs in `docs/` folder
|
||
- Add examples if introducing new patterns
|
||
|
||
### Database Changes
|
||
|
||
- Always create migrations: `bin/console make:migration`
|
||
- Use Doctrine QueryBuilder in repositories (not raw SQL)
|
||
- For PostgreSQL-specific features (materialized views, triggers), use raw SQL in migrations only
|
||
- Materialized views should auto-refresh via triggers
|
||
|
||
### Styling
|
||
|
||
- **Web fonts:** Use `@fontsource` packages (WOFF/WOFF2)
|
||
- **Server-side images:** Use TTF fonts in `assets/fonts/` (PHP GD requires TTF)
|
||
- **CSS:** Create SCSS partials in `assets/css/homepage/`, import in main file
|
||
- **Components:** Use Emotion styled-components or CSS classes
|
||
|
||
### File Headers
|
||
|
||
All PHP and JS/JSX files should have this header:
|
||
|
||
```php
|
||
<?php declare(strict_types=1);
|
||
/*
|
||
* This file is part of the SplendidBear Websites' projects.
|
||
*
|
||
* Copyright (c) 2026 @ www.splendidbear.org
|
||
*
|
||
* For the full copyright and license information, please view the LICENSE
|
||
* file that was distributed with this source code.
|
||
*/
|
||
```
|
||
|
||
```javascript
|
||
/**
|
||
* This file is part of the SplendidBear Websites' projects.
|
||
*
|
||
* Copyright (c) 2026 @ www.splendidbear.org
|
||
*
|
||
* For the full copyright and license information, please view the LICENSE
|
||
* file that was distributed with this source code.
|
||
*/
|
||
```
|
||
|
||
---
|
||
|
||
## Coding Standards
|
||
|
||
### PHP
|
||
|
||
- **PSR Standards:** Follow PSR-1, PSR-12 coding standards
|
||
- **Type declarations:** Use strict types (`declare(strict_types=1)`)
|
||
- **Property promotion:** Use constructor property promotion for DTOs and services
|
||
- **Readonly:** Use `readonly` for immutable properties
|
||
- **Final:** Mark DTOs as `final`
|
||
- **Doctrine:** Use QueryBuilder with `expr()` methods, not string concatenation
|
||
- **Null safety:** Use null coalescing `??` and null-safe operator `?->`
|
||
- **Formatting:** 4-space indentation, opening braces on same line for methods/classes
|
||
|
||
**Example DTO:**
|
||
|
||
```php
|
||
final readonly class ProfileGameDto implements JsonSerializable
|
||
{
|
||
public function __construct(
|
||
public ?int $id,
|
||
public string $redName,
|
||
public string $blueName,
|
||
public bool $bothRegistered,
|
||
) {}
|
||
}
|
||
```
|
||
|
||
### JavaScript/React
|
||
|
||
- **Components:** Functional components with hooks
|
||
- **PropTypes:** Required on all components
|
||
- **Imports:** Use aliases (`@global-components`, `@mine-hooks`, etc.)
|
||
- **State:** Use `useState`, `useEffect`, `useCallback`, `useMemo` appropriately
|
||
- **Avoid:** Circular dependencies, especially with barrel exports
|
||
|
||
**Example component:**
|
||
|
||
```javascript
|
||
import React, { useState } from 'react';
|
||
import { string, number } from 'prop-types';
|
||
|
||
export const MyComponent = ({ title, count }) => {
|
||
const [value, setValue] = useState(0);
|
||
|
||
return <div>{title}: {count + value}</div>;
|
||
};
|
||
|
||
MyComponent.propTypes = {
|
||
title: string.isRequired,
|
||
count: number.isRequired,
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Important Files & Locations
|
||
|
||
### Configuration
|
||
|
||
- `config/services.yaml` - Service definitions and parameters
|
||
- `vite.config.js` - Vite build config, aliases
|
||
- `.env` - Environment variables (not in git)
|
||
- `composer.json` - PHP dependencies
|
||
- `package.json` - Node dependencies
|
||
|
||
### Key Services
|
||
|
||
- `BattleCardGenerator` - Generates 1200×630 PNG OG images using PHP GD
|
||
- `TopicManager` - Mercure topic management and game logic
|
||
- `WebAuthnService` - Passkey authentication
|
||
- `Email services` - Various email senders in `src/Service/Email/`
|
||
|
||
### Important Entities
|
||
|
||
- `User` - Registered users
|
||
- `PlayedGame` - Game records with moves, grid, scores
|
||
- `RecentBattle` - Read-only entity from materialized view
|
||
- `UserStats` - Read-only entity from materialized view
|
||
|
||
### Documentation
|
||
|
||
- `docs/game-mechanics/BONUS_POINTS_SYSTEM.md` - Bonus points reference
|
||
- `docs/FONTS.md` - Font usage and management
|
||
- `AGENTS.md` - This file
|
||
- `CHANGELOG.md` - Project changelog
|
||
|
||
---
|
||
|
||
## Common Pitfalls
|
||
|
||
### ❌ Don't Do
|
||
|
||
- **Don't use raw SQL in repositories** (use Doctrine QueryBuilder)
|
||
- **Don't create circular imports** (e.g., importing `@global-components` from within `components/`)
|
||
- **Don't use system fonts directly** (bundle TTF in `assets/fonts/`)
|
||
- **Don't skip PropTypes** on React components
|
||
- **Don't use `var`** in JavaScript (use `const` or `let`)
|
||
- **Don't define variables after using them** (hoisting issues with `const`)
|
||
|
||
### ✅ Do
|
||
|
||
- **Use Doctrine QueryBuilder** with `expr()` methods
|
||
- **Import components directly** to avoid circular dependencies
|
||
- **Bundle fonts in project** for portability
|
||
- **Add PropTypes** to all components
|
||
- **Use `const` for immutable values**, `let` for mutable
|
||
- **Define variables before using them**
|
||
|
||
---
|
||
|
||
## Keeping Components in Sync
|
||
|
||
### Battle Report Display
|
||
|
||
The battle report/statistics appear in **two places** that must be kept synchronized:
|
||
|
||
#### 1. BattleDialog Component (React)
|
||
**File:** `assets/js/components/BattleDialog.jsx`
|
||
- React dialog component shown on profile page
|
||
- Uses Material-UI and styled-components
|
||
- Displays game stats, bonus points, winner information
|
||
|
||
#### 2. Battle Share Page (Twig)
|
||
**File:** `templates/Game/battle_share.html.twig`
|
||
- Public battle share page (accessible via `/battle/{uuid}`)
|
||
- Server-rendered HTML with SCSS styling
|
||
- Shows same information as BattleDialog
|
||
|
||
### Synchronization Rules
|
||
|
||
**When updating BattleDialog.jsx, also update battle_share.html.twig:**
|
||
|
||
✅ Adding new stats or fields
|
||
✅ Changing display logic (winner calculation, formatting)
|
||
✅ Modifying bonus points display
|
||
✅ Updating labels or text
|
||
|
||
**Keep in sync:**
|
||
- Game outcome logic (win/loss/draw/abandoned)
|
||
- Bonus points formatting
|
||
- Player name display
|
||
- Score display
|
||
- Stats and metadata shown
|
||
|
||
**Example:** If you add a new stat to BattleDialog showing "fastest mine claim time", you must also add it to battle_share.html.twig so both displays show the same information.
|
||
|
||
---
|
||
|
||
## Testing & Building
|
||
|
||
### Backend
|
||
|
||
```bash
|
||
# Run migrations
|
||
bin/console doctrine:migrations:migrate
|
||
|
||
# Clear cache
|
||
bin/console cache:clear
|
||
|
||
# Refresh materialized views
|
||
bin/console dbal:run-sql "REFRESH MATERIALIZED VIEW CONCURRENTLY recent_battles"
|
||
```
|
||
|
||
### Frontend
|
||
|
||
```bash
|
||
# Development build with watch
|
||
npm run dev
|
||
|
||
# Production build
|
||
npm run build
|
||
|
||
# After changing fonts
|
||
rm -rf var/og-cache/*
|
||
```
|
||
|
||
---
|
||
|
||
## Git Workflow
|
||
|
||
### Commit Messages
|
||
|
||
Follow conventional commits:
|
||
|
||
- `feat: add bonus points to battle cards`
|
||
- `fix: resolve circular dependency in BattleDialog`
|
||
- `refactor: use Doctrine QueryBuilder in RecentBattleRepository`
|
||
- `docs: add AGENTS.md for AI coding agents`
|
||
- `chore: update dependencies`
|
||
|
||
### Creating Pull Requests
|
||
|
||
When creating PRs, include:
|
||
|
||
1. **Summary** - What was changed and why
|
||
2. **Changes** - List of modified files/components
|
||
3. **Testing** - How to verify the changes
|
||
4. **Screenshots** - For UI changes
|
||
|
||
---
|
||
|
||
## Performance Considerations
|
||
|
||
### Database
|
||
|
||
- Use materialized views for expensive queries (profile stats, recent battles)
|
||
- Auto-refresh materialized views via triggers on source table changes
|
||
- Index frequently queried columns
|
||
- Use `COALESCE()` for nullable aggregates
|
||
|
||
### Frontend
|
||
|
||
- Lazy load large components
|
||
- Use React Query for data fetching and caching
|
||
- Avoid unnecessary re-renders (use `useMemo`, `useCallback`)
|
||
- Bundle code per entry point (profile, passkey, contact, game)
|
||
|
||
### Images
|
||
|
||
- Battle card images are cached in `var/og-cache/`
|
||
- Images regenerated when games change (via deterministic UUID)
|
||
- Use appropriate image sizes (1200×630 for OG images)
|
||
|
||
---
|
||
|
||
## Security
|
||
|
||
### Authentication
|
||
|
||
- Password + TOTP (optional) + WebAuthn passkeys (optional)
|
||
- Backup codes for TOTP recovery
|
||
- Session-based authentication with `IS_AUTHENTICATED_REMEMBERED`
|
||
|
||
### Data Access
|
||
|
||
- Controllers check `denyAccessUnlessGranted()`
|
||
- Materialized views filter by `user_id`
|
||
- Guest players use separate `Gamer` entity (no `User` account)
|
||
|
||
### Input Validation
|
||
|
||
- Symfony forms for user input
|
||
- File upload validation (size, MIME type)
|
||
- WebAuthn challenge validation
|
||
|
||
---
|
||
|
||
## Useful Commands
|
||
|
||
```bash
|
||
# Symfony
|
||
bin/console debug:container ServiceName # Inspect service configuration
|
||
bin/console debug:router # List all routes
|
||
bin/console make:migration # Create new migration
|
||
bin/console doctrine:migrations:list # List migrations
|
||
|
||
# Database
|
||
bin/console dbal:run-sql "SELECT * FROM ..." # Run SQL query
|
||
|
||
# Assets
|
||
npm run build # Build production assets
|
||
npm run dev # Build with watch mode
|
||
|
||
# Git
|
||
git log --oneline --graph # View commit history
|
||
git diff origin/main...HEAD # See changes since main
|
||
```
|
||
|
||
---
|
||
|
||
## Need Help?
|
||
|
||
- **Bonus Points:** See `docs/game-mechanics/BONUS_POINTS_SYSTEM.md`
|
||
- **Fonts:** See `docs/FONTS.md`
|
||
- **Symfony Docs:** https://symfony.com/doc/current/
|
||
- **React Docs:** https://react.dev/
|
||
- **Doctrine ORM:** https://www.doctrine-project.org/projects/doctrine-orm/en/current/
|
||
|
||
---
|
||
|
||
## Version History
|
||
|
||
- **2026-04-21:** Initial AGENTS.md created
|
||
- Document common patterns, pitfalls, and project structure
|
||
- Include coding standards and examples
|
||
|
||
---
|
||
|
||
**Happy coding! 🚀**
|