from __future__ import annotations from enum import Enum from typing import Annotated from pydantic import BaseModel, Field class Shape(str, Enum): CIRCLE = "circle" RECTANGLE = "rectangle" TRIANGLE = "triangle" STAR = "star" class MovementPattern(str, Enum): STATIC = "static" KEYBOARD_HORIZONTAL = "keyboard_horizontal" KEYBOARD_ALL = "keyboard_all" RAIN_DOWN = "rain_down" BOUNCE = "bounce" CHASE_PLAYER = "chase_player" ZIGZAG = "zigzag" CIRCULAR = "circular" class CollisionAction(str, Enum): GAME_OVER = "game_over" SCORE_PLUS = "score_plus" SCORE_MINUS = "score_minus" DESTROY_TARGET = "destroy_target" BOUNCE_BACK = "bounce_back" class InputKey(str, Enum): ARROW_LEFT = "arrow_left" ARROW_RIGHT = "arrow_right" ARROW_UP = "arrow_up" ARROW_DOWN = "arrow_down" SPACE = "space" class InputAction(str, Enum): MOVE_LEFT = "move_left" MOVE_RIGHT = "move_right" MOVE_UP = "move_up" MOVE_DOWN = "move_down" class ScorePosition(str, Enum): TOP_LEFT = "top_left" TOP_RIGHT = "top_right" TOP_CENTER = "top_center" class SpriteComponent(BaseModel): id: str = Field(description="Unique sprite type identifier (e.g. 'player', 'enemy', 'coin')") shape: Shape color: str = Field(default="#ffffff", description="CSS hex color, e.g. '#00ffcc'") x: float = Field(default=300, ge=-300, le=1100, description="Initial X position; can be negative for off-screen spawning") y: float = Field(default=400, ge=-700, le=1350, description="Initial Y position; can be negative for off-screen spawning") width: float = Field(default=30, ge=5, le=150) height: float = Field(default=30, ge=5, le=150) movement_pattern: MovementPattern = MovementPattern.STATIC speed: float = Field(default=3.0, ge=0.5, le=15.0) is_player: bool = Field(default=False, description="True for the player-controlled sprite") count: int = Field(default=1, ge=1, le=20, description="Number of instances to spawn (for enemies/collectibles)") class CollisionRule(BaseModel): between: Annotated[list[str], Field(min_length=2, max_length=2, description="Exactly two sprite IDs")] action: CollisionAction value: int = Field(default=1, ge=1, description="Points added/removed for score actions") class InputBinding(BaseModel): key: InputKey action: InputAction target: str = Field(description="ID of the sprite to control") class ScoreDisplay(BaseModel): position: ScorePosition = ScorePosition.TOP_RIGHT font_size: int = Field(default=22, ge=12, le=40) color: str = Field(default="#00ffcc") label: str = Field(default="SCORE") class CanvasGame(BaseModel): title: str = Field(description="Short evocative game title (2-4 words)") width: int = Field(default=600, ge=300, le=800) height: int = Field(default=500, ge=300, le=650) background_color: str = Field(default="#0a0a0a") sprites: list[SpriteComponent] = Field(default_factory=list) collision_rules: list[CollisionRule] = Field(default_factory=list) input_bindings: list[InputBinding] = Field(default_factory=list) score_display: ScoreDisplay | None = Field(default=None) lives: int = Field(default=3, ge=1, le=10)