chg: dev: create AGENTS.md file for future maintenance #9
This commit is contained in:
464
AGENTS.md
Normal file
464
AGENTS.md
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
# 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! 🚀**
|
||||||
@@ -291,6 +291,7 @@ git push origin v2026.01
|
|||||||
|
|
||||||
For detailed information about game mechanics, bonus systems, fonts, and other technical details, see the [docs](./docs/) directory:
|
For detailed information about game mechanics, bonus systems, fonts, and other technical details, see the [docs](./docs/) directory:
|
||||||
|
|
||||||
|
- **[AI Agent Guidelines](./AGENTS.md)** — Comprehensive guide for AI coding agents working on this project
|
||||||
- **[Bonus Points System](./docs/game-mechanics/BONUS_POINTS_SYSTEM.md)** — Complete reference for all bonus point types, calculation rules, and implementation details
|
- **[Bonus Points System](./docs/game-mechanics/BONUS_POINTS_SYSTEM.md)** — Complete reference for all bonus point types, calculation rules, and implementation details
|
||||||
- **[Fonts](./docs/FONTS.md)** — TrueType fonts used for server-side image generation
|
- **[Fonts](./docs/FONTS.md)** — TrueType fonts used for server-side image generation
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user