15 KiB
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 readonlyclasses 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-seekerbundle - Avoid circular dependencies (e.g., don't import from
@global-componentsinsidecomponents/directory) - PropTypes required on all components
Common Tasks
Adding a New Feature
-
Backend:
- Create migration for schema changes
- Add/update entities and repositories
- Create DTOs for data transfer
- Add controller endpoints
- Update service configuration if needed
-
Frontend:
- Create components in appropriate bundle
- Add PropTypes to all components
- Use existing hooks and utilities
- Follow styled-components pattern for MUI customization
-
Documentation:
- Update relevant docs in
docs/folder - Add examples if introducing new patterns
- Update relevant docs in
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
@fontsourcepackages (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 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.
*/
/**
* 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
readonlyfor 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:
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,useMemoappropriately - Avoid: Circular dependencies, especially with barrel exports
Example component:
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 parametersvite.config.js- Vite build config, aliases.env- Environment variables (not in git)composer.json- PHP dependenciespackage.json- Node dependencies
Key Services
BattleCardGenerator- Generates 1200×630 PNG OG images using PHP GDTopicManager- Mercure topic management and game logicWebAuthnService- Passkey authenticationEmail services- Various email senders insrc/Service/Email/
Important Entities
User- Registered usersPlayedGame- Game records with moves, grid, scoresRecentBattle- Read-only entity from materialized viewUserStats- Read-only entity from materialized view
Documentation
docs/game-mechanics/BONUS_POINTS_SYSTEM.md- Bonus points referencedocs/FONTS.md- Font usage and managementAGENTS.md- This fileCHANGELOG.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-componentsfrom withincomponents/) - Don't use system fonts directly (bundle TTF in
assets/fonts/) - Don't skip PropTypes on React components
- Don't use
varin JavaScript (useconstorlet) - 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
constfor immutable values,letfor 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
# 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
# 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 cardsfix: resolve circular dependency in BattleDialogrefactor: use Doctrine QueryBuilder in RecentBattleRepositorydocs: add AGENTS.md for AI coding agentschore: update dependencies
Creating Pull Requests
When creating PRs, include:
- Summary - What was changed and why
- Changes - List of modified files/components
- Testing - How to verify the changes
- 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
Gamerentity (noUseraccount)
Input Validation
- Symfony forms for user input
- File upload validation (size, MIME type)
- WebAuthn challenge validation
Useful Commands
# 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! 🚀