Development Guide
This guide covers development setup, architecture, coding standards, and contribution guidelines for Empire Builder.
Development Environment Setup
Prerequisites
Required Software: - Python 3.11+ - Git - Code editor (VS Code recommended) - Supabase account
Recommended Tools: - Docker (for containerized development) - Postman (for API testing) - pgAdmin (for database management)
Initial Setup
Fork and Clone
# Fork the repository on GitHub git clone https://github.com/logan-code-del/empire-builder-game.git cd empire-builder-game
Create Development Environment
# Create virtual environment python -m venv empire-dev # Activate environment # Windows: empire-dev\Scripts\activate # macOS/Linux: source empire-dev/bin/activate
Install Dependencies
# Install main dependencies pip install -r requirements.txt # Install development dependencies pip install -r requirements-dev.txt # If available
Set Up Development Database
Create a separate Supabase project for development: - Use
dev-empire-builderas project name - Run the schema fromsupabase_auth_schema_uuid.sql- Configure.envwith development credentialsConfigure Pre-commit Hooks
pip install pre-commit pre-commit install
Project Architecture
Directory Structure
empire/
├── app_supabase.py # Main Flask application
├── models_supabase.py # Database models and game logic
├── auth_supabase.py # Authentication system
├── ai_system.py # AI opponents and automation
├── supabase_config.py # Database configuration
├── templates/ # Jinja2 HTML templates
│ ├── base.html # Base template
│ ├── dashboard.html # Game dashboard
│ ├── login.html # Login page
│ └── register.html # Registration page
├── static/ # Static assets
│ ├── css/ # Stylesheets
│ ├── js/ # JavaScript files
│ └── images/ # Image assets
├── tests/ # Unit tests
│ ├── test_auth.py # Authentication tests
│ ├── test_models.py # Model tests
│ └── test_api.py # API tests
├── docs/ # Documentation
├── debug_*.py # Debug and testing scripts
├── requirements.txt # Python dependencies
├── .env.template # Environment template
└── README.md # Project README
Core Components
app_supabase.py - Flask application factory - Route definitions - WebSocket event handlers - Middleware configuration
models_supabase.py - Database models (Empire, City, Unit, Battle) - Game logic and business rules - Data validation and processing - Supabase integration
auth_supabase.py - User authentication - Session management - Password hashing and verification - Authorization decorators
ai_system.py - AI opponent behavior - Automated game actions - Decision-making algorithms - Performance optimization
supabase_config.py - Database connection management - Configuration loading - Client initialization - Error handling
Coding Standards
Python Style Guide
PEP 8 Compliance: - Line length: 88 characters (Black formatter) - Indentation: 4 spaces - Import organization: stdlib, third-party, local - Function and variable names: snake_case - Class names: PascalCase - Constants: UPPER_CASE
Type Hints:
from typing import Optional, List, Dict, Any
def create_empire(name: str, owner_id: str) -> Optional[str]:
"""Create a new empire and return its ID."""
# Implementation
return empire_id
Docstrings:
def calculate_battle_outcome(attacker: Army, defender: Army) -> BattleResult:
"""
Calculate the outcome of a battle between two armies.
Args:
attacker: The attacking army
defender: The defending army
Returns:
BattleResult containing winner, casualties, and loot
Raises:
ValueError: If armies are invalid or empty
"""
# Implementation
Error Handling:
def get_empire_by_id(empire_id: str) -> Optional[Empire]:
"""Get empire by ID with proper error handling."""
try:
result = client.table('empires').select('*').eq('id', empire_id).execute()
if result.data:
return Empire.from_dict(result.data[0])
return None
except Exception as e:
logger.error(f"Failed to get empire {empire_id}: {e}")
return None
Database Design Patterns
Model Classes:
@dataclass
class Empire:
id: str
name: str
owner_id: str
created_at: str
resources: Dict[str, int]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'Empire':
"""Create Empire from database row."""
return cls(
id=data['id'],
name=data['name'],
owner_id=data['owner_id'],
created_at=data['created_at'],
resources=json.loads(data['resources'])
)
def to_dict(self) -> Dict[str, Any]:
"""Convert Empire to database format."""
return {
'id': self.id,
'name': self.name,
'owner_id': self.owner_id,
'created_at': self.created_at,
'resources': json.dumps(self.resources)
}
Database Operations:
class EmpireDatabase:
def __init__(self, client):
self.client = client
def create_empire(self, empire: Empire) -> bool:
"""Create new empire in database."""
try:
result = self.client.table('empires').insert(
empire.to_dict()
).execute()
return bool(result.data)
except Exception as e:
logger.error(f"Failed to create empire: {e}")
return False
Frontend Development
HTML Templates:
<!-- Use semantic HTML -->
<main class="dashboard">
<section class="empire-overview">
<h2>{{ empire.name }}</h2>
<div class="resources">
<span class="gold">{{ empire.resources.gold }}</span>
<span class="food">{{ empire.resources.food }}</span>
</div>
</section>
</main>
JavaScript Standards:
// Use modern ES6+ syntax
class GameClient {
constructor(apiUrl) {
this.apiUrl = apiUrl;
this.socket = io();
}
async getEmpire(empireId) {
try {
const response = await fetch(`${this.apiUrl}/empire/${empireId}`);
return await response.json();
} catch (error) {
console.error('Failed to get empire:', error);
return null;
}
}
}
CSS Organization:
/* Use BEM methodology */
.dashboard {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
gap: 1rem;
}
.dashboard__sidebar {
background: var(--sidebar-bg);
}
.dashboard__main {
padding: 1rem;
}
Testing Guidelines
Test Structure
Unit Tests:
import unittest
from unittest.mock import patch, MagicMock
from models_supabase import Empire, EmpireDatabase
class TestEmpireDatabase(unittest.TestCase):
def setUp(self):
"""Set up test fixtures."""
self.mock_client = MagicMock()
self.db = EmpireDatabase(self.mock_client)
def test_create_empire_success(self):
"""Test successful empire creation."""
# Arrange
empire = Empire(
id='test-id',
name='Test Empire',
owner_id='user-id',
created_at='2024-01-01T00:00:00Z',
resources={'gold': 1000}
)
self.mock_client.table.return_value.insert.return_value.execute.return_value.data = [empire.to_dict()]
# Act
result = self.db.create_empire(empire)
# Assert
self.assertTrue(result)
self.mock_client.table.assert_called_with('empires')
Integration Tests:
class TestEmpireAPI(unittest.TestCase):
def setUp(self):
"""Set up test client."""
self.app = create_app(testing=True)
self.client = self.app.test_client()
self.ctx = self.app.app_context()
self.ctx.push()
def tearDown(self):
"""Clean up test context."""
self.ctx.pop()
def test_create_empire_endpoint(self):
"""Test empire creation API endpoint."""
response = self.client.post('/api/empire', json={
'name': 'Test Empire'
})
self.assertEqual(response.status_code, 201)
Running Tests:
# Run all tests
python -m unittest discover tests
# Run specific test file
python -m unittest tests.test_models
# Run with coverage
pip install coverage
coverage run -m unittest discover tests
coverage report
Mock and Fixtures
Database Mocking:
@patch('supabase_config.get_supabase_client')
def test_with_mocked_db(self, mock_get_client):
"""Test with mocked database client."""
mock_client = MagicMock()
mock_get_client.return_value = mock_client
# Test implementation
result = some_database_operation()
# Verify mock calls
mock_client.table.assert_called_with('expected_table')
Test Fixtures:
class TestFixtures:
@staticmethod
def create_test_empire() -> Empire:
"""Create test empire fixture."""
return Empire(
id='test-empire-id',
name='Test Empire',
owner_id='test-user-id',
created_at='2024-01-01T00:00:00Z',
resources={'gold': 1000, 'food': 500, 'materials': 200}
)
Debugging and Profiling
Debug Scripts
The project includes several debug scripts:
debug_registration.py - Tests user registration flow - Validates database connections - Checks authentication system
debug_login.py - Tests login functionality - Validates session creation - Checks password verification
test_simple_connection.py - Basic connectivity tests - Environment validation - Supabase client testing
Usage:
# Test registration system
python debug_registration.py
# Test login with specific credentials
python debug_login.py
# Test basic connectivity
python test_simple_connection.py
Logging Configuration
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('empire.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Usage in code
logger.info("Empire created: %s", empire.name)
logger.error("Failed to create empire: %s", str(e))
Performance Profiling
import cProfile
import pstats
def profile_function():
"""Profile a specific function."""
profiler = cProfile.Profile()
profiler.enable()
# Code to profile
result = expensive_operation()
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10) # Top 10 functions
Database Development
Schema Management
Migration Scripts: Create migration scripts for database changes:
-- migrations/001_add_alliance_table.sql
CREATE TABLE alliances (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
leader_id UUID REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
description TEXT
);
Schema Validation:
def validate_schema():
"""Validate database schema matches expectations."""
required_tables = ['users', 'empires', 'cities', 'battles']
for table in required_tables:
result = client.table(table).select('count').limit(1).execute()
assert result.data is not None, f"Table {table} not found"
Local Development
SQLite Fallback: For offline development, implement SQLite fallback:
def get_database_client():
"""Get database client with fallback."""
try:
return get_supabase_client()
except Exception:
logger.warning("Using SQLite fallback")
return get_sqlite_client()
Test Data Generation:
def create_test_data():
"""Generate test data for development."""
# Create test users
test_users = [
{'username': 'player1', 'email': 'player1@test.com'},
{'username': 'player2', 'email': 'player2@test.com'}
]
for user_data in test_users:
create_test_user(user_data)
Deployment Considerations
Environment Configuration
Production Settings:
# .env.production
DEBUG=False
SECRET_KEY=production-secret-key-very-long-and-random
SUPABASE_URL=https://prod-project.supabase.co
SUPABASE_ANON_KEY=prod-anon-key
SUPABASE_SERVICE_KEY=prod-service-key
Docker Configuration:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app_supabase:app"]
Performance Optimization
Database Optimization: - Add indexes to frequently queried columns - Use connection pooling - Implement query result caching
Application Optimization: - Use Redis for session storage - Implement API rate limiting - Add CDN for static assets
Contributing Workflow
Development Process
Create Feature Branch
git checkout -b feature/alliance-system
Implement Changes - Write code following style guidelines - Add comprehensive tests - Update documentation
Test Thoroughly
# Run tests python -m unittest discover tests # Check code style black --check . flake8 . # Test manually python app_supabase.py
Commit Changes
git add . git commit -m "feat: add alliance system with member management"
Push and Create PR
git push origin feature/alliance-system
Pull Request Guidelines
PR Description Template:
## Description
Brief description of changes
## Changes Made
- [ ] Feature implementation
- [ ] Tests added
- [ ] Documentation updated
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Screenshots
(If UI changes)
Review Checklist: - Code follows style guidelines - Tests cover new functionality - Documentation is updated - No breaking changes (or properly documented) - Performance impact considered
This development guide provides the foundation for contributing to Empire Builder while maintaining code quality and project consistency.