Private
Public Access
1
0
Files
MineSeeker/AGENTS.md

15 KiB
Raw Blame History

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 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 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:

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:

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

# 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 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

# 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?


Version History

  • 2026-04-21: Initial AGENTS.md created
  • Document common patterns, pitfalls, and project structure
  • Include coding standards and examples

Happy coding! 🚀