Numerous WebUI animations, improvements, AI fixes, opporitunity cost-based decision logic, etc.
This commit is contained in:
176
server/config.py
Normal file
176
server/config.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Centralized configuration for Golf game server.
|
||||
|
||||
Configuration is loaded from (in order of precedence):
|
||||
1. Environment variables
|
||||
2. .env file (if exists)
|
||||
3. Default values
|
||||
|
||||
Usage:
|
||||
from config import config
|
||||
print(config.PORT)
|
||||
print(config.card_values)
|
||||
"""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Load .env file if it exists
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
env_path = Path(__file__).parent.parent / ".env"
|
||||
if env_path.exists():
|
||||
load_dotenv(env_path)
|
||||
except ImportError:
|
||||
pass # python-dotenv not installed, use env vars only
|
||||
|
||||
|
||||
def get_env(key: str, default: str = "") -> str:
|
||||
"""Get environment variable with default."""
|
||||
return os.environ.get(key, default)
|
||||
|
||||
|
||||
def get_env_bool(key: str, default: bool = False) -> bool:
|
||||
"""Get boolean environment variable."""
|
||||
val = os.environ.get(key, "").lower()
|
||||
if val in ("true", "1", "yes", "on"):
|
||||
return True
|
||||
if val in ("false", "0", "no", "off"):
|
||||
return False
|
||||
return default
|
||||
|
||||
|
||||
def get_env_int(key: str, default: int = 0) -> int:
|
||||
"""Get integer environment variable."""
|
||||
try:
|
||||
return int(os.environ.get(key, str(default)))
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
@dataclass
|
||||
class CardValues:
|
||||
"""Card point values - the single source of truth."""
|
||||
ACE: int = 1
|
||||
TWO: int = -2
|
||||
THREE: int = 3
|
||||
FOUR: int = 4
|
||||
FIVE: int = 5
|
||||
SIX: int = 6
|
||||
SEVEN: int = 7
|
||||
EIGHT: int = 8
|
||||
NINE: int = 9
|
||||
TEN: int = 10
|
||||
JACK: int = 10
|
||||
QUEEN: int = 10
|
||||
KING: int = 0
|
||||
JOKER: int = -2
|
||||
|
||||
# House rule modifiers
|
||||
SUPER_KINGS: int = -2 # King value when super_kings enabled
|
||||
TEN_PENNY: int = 1 # 10 value when ten_penny enabled
|
||||
LUCKY_SWING_JOKER: int = -5 # Joker value when lucky_swing enabled
|
||||
|
||||
def to_dict(self) -> dict[str, int]:
|
||||
"""Get card values as dictionary for game use."""
|
||||
return {
|
||||
'A': self.ACE,
|
||||
'2': self.TWO,
|
||||
'3': self.THREE,
|
||||
'4': self.FOUR,
|
||||
'5': self.FIVE,
|
||||
'6': self.SIX,
|
||||
'7': self.SEVEN,
|
||||
'8': self.EIGHT,
|
||||
'9': self.NINE,
|
||||
'10': self.TEN,
|
||||
'J': self.JACK,
|
||||
'Q': self.QUEEN,
|
||||
'K': self.KING,
|
||||
'★': self.JOKER,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class GameDefaults:
|
||||
"""Default game settings."""
|
||||
rounds: int = 9
|
||||
initial_flips: int = 2
|
||||
use_jokers: bool = False
|
||||
flip_on_discard: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServerConfig:
|
||||
"""Server configuration."""
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8000
|
||||
DEBUG: bool = False
|
||||
LOG_LEVEL: str = "INFO"
|
||||
|
||||
# Database
|
||||
DATABASE_URL: str = "sqlite:///games.db"
|
||||
|
||||
# Room settings
|
||||
MAX_PLAYERS_PER_ROOM: int = 6
|
||||
ROOM_TIMEOUT_MINUTES: int = 60
|
||||
ROOM_CODE_LENGTH: int = 4
|
||||
|
||||
# Security (for future auth system)
|
||||
SECRET_KEY: str = ""
|
||||
INVITE_ONLY: bool = False
|
||||
ADMIN_EMAILS: list[str] = field(default_factory=list)
|
||||
|
||||
# Card values
|
||||
card_values: CardValues = field(default_factory=CardValues)
|
||||
|
||||
# Game defaults
|
||||
game_defaults: GameDefaults = field(default_factory=GameDefaults)
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "ServerConfig":
|
||||
"""Load configuration from environment variables."""
|
||||
admin_emails_str = get_env("ADMIN_EMAILS", "")
|
||||
admin_emails = [e.strip() for e in admin_emails_str.split(",") if e.strip()]
|
||||
|
||||
return cls(
|
||||
HOST=get_env("HOST", "0.0.0.0"),
|
||||
PORT=get_env_int("PORT", 8000),
|
||||
DEBUG=get_env_bool("DEBUG", False),
|
||||
LOG_LEVEL=get_env("LOG_LEVEL", "INFO"),
|
||||
DATABASE_URL=get_env("DATABASE_URL", "sqlite:///games.db"),
|
||||
MAX_PLAYERS_PER_ROOM=get_env_int("MAX_PLAYERS_PER_ROOM", 6),
|
||||
ROOM_TIMEOUT_MINUTES=get_env_int("ROOM_TIMEOUT_MINUTES", 60),
|
||||
ROOM_CODE_LENGTH=get_env_int("ROOM_CODE_LENGTH", 4),
|
||||
SECRET_KEY=get_env("SECRET_KEY", ""),
|
||||
INVITE_ONLY=get_env_bool("INVITE_ONLY", False),
|
||||
ADMIN_EMAILS=admin_emails,
|
||||
card_values=CardValues(
|
||||
ACE=get_env_int("CARD_ACE", 1),
|
||||
TWO=get_env_int("CARD_TWO", -2),
|
||||
KING=get_env_int("CARD_KING", 0),
|
||||
JOKER=get_env_int("CARD_JOKER", -2),
|
||||
SUPER_KINGS=get_env_int("CARD_SUPER_KINGS", -2),
|
||||
TEN_PENNY=get_env_int("CARD_TEN_PENNY", 1),
|
||||
LUCKY_SWING_JOKER=get_env_int("CARD_LUCKY_SWING_JOKER", -5),
|
||||
),
|
||||
game_defaults=GameDefaults(
|
||||
rounds=get_env_int("DEFAULT_ROUNDS", 9),
|
||||
initial_flips=get_env_int("DEFAULT_INITIAL_FLIPS", 2),
|
||||
use_jokers=get_env_bool("DEFAULT_USE_JOKERS", False),
|
||||
flip_on_discard=get_env_bool("DEFAULT_FLIP_ON_DISCARD", False),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# Global config instance - loaded once at module import
|
||||
config = ServerConfig.from_env()
|
||||
|
||||
|
||||
def reload_config() -> ServerConfig:
|
||||
"""Reload configuration from environment (useful for testing)."""
|
||||
global config
|
||||
config = ServerConfig.from_env()
|
||||
return config
|
||||
Reference in New Issue
Block a user