""" Sprite Generation - Shared utilities and resource/building sprites Character sprites are generated by separate pipeline classes """ import numpy as np from PIL import Image, ImageDraw from dataclasses import dataclass from typing import List @dataclass class Sprite: frames: List[np.ndarray] current_frame: int = 0 frame_speed: int = 5 tick_counter: int = 0 def next_frame(self) -> np.ndarray: self.tick_counter += 1 if self.tick_counter >= self.frame_speed: self.tick_counter = 0 self.current_frame = (self.current_frame + 1) % len(self.frames) return self.frames[self.current_frame] def get_current_frame(self) -> np.ndarray: return self.frames[self.current_frame] def reset(self): self.current_frame = 0 self.tick_counter = 0 class SpriteGenerator: """Shared sprite generation utilities for resources and buildings""" # ======================================================================== # RESOURCE SPRITES # ======================================================================== @staticmethod def generate_mushroom_sprite() -> Sprite: img = Image.new('RGBA', (32, 32), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Stem draw.rectangle([13, 16, 19, 28], fill=(240, 230, 210)) # Cap draw.ellipse([8, 10, 24, 22], fill=(200, 50, 50)) # White spots draw.ellipse([11, 13, 15, 17], fill=(255, 255, 255)) draw.ellipse([17, 12, 20, 15], fill=(255, 255, 255)) return Sprite(frames=[np.array(img)]) @staticmethod def generate_berry_sprite() -> Sprite: img = Image.new('RGBA', (32, 32), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Bush draw.ellipse([6, 12, 26, 28], fill=(34, 139, 34)) # Berries draw.ellipse([10, 15, 14, 19], fill=(200, 0, 0)) draw.ellipse([18, 14, 22, 18], fill=(200, 0, 0)) draw.ellipse([14, 20, 18, 24], fill=(200, 0, 0)) return Sprite(frames=[np.array(img)]) @staticmethod def generate_wood_sprite() -> Sprite: img = Image.new('RGBA', (32, 32), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Log pile draw.rectangle([8, 18, 24, 24], fill=(101, 67, 33)) draw.rectangle([10, 12, 22, 18], fill=(139, 90, 43)) draw.rectangle([12, 6, 20, 12], fill=(101, 67, 33)) # Wood rings draw.ellipse([14, 8, 18, 10], fill=(80, 50, 20)) return Sprite(frames=[np.array(img)]) # ======================================================================== # BUILDING SPRITES (IMPROVED MUSHROOM HOUSES!) # ======================================================================== @staticmethod def generate_house_sprite(level: int = 1) -> Sprite: """Beautiful mushroom house!""" size = 64 + (level - 1) * 8 img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Stem (house base) stem_color = (240, 230, 210) if level == 1 else (250, 240, 220) draw.rectangle([size//4, size//2, size*3//4, size-4], fill=stem_color) # Add texture to stem for i in range(3): y = size//2 + 10 + i*8 draw.line([(size//4+2, y), (size*3//4-2, y)], fill=(220, 210, 190), width=1) # Mushroom cap (red with white spots) cap_color = (200, 50, 50) if level == 1 else (220, 70, 70) draw.ellipse([4, size//4, size-4, size//2+10], fill=cap_color) # White spots on cap spot_positions = [ (size//4, size//3), (size//2 - 4, size//4 + 4), (size*3//4 - 8, size//3 + 2) ] if level > 1: spot_positions.extend([ (size//3 - 4, size//2 - 2), (size*2//3, size//2 - 4) ]) for x, y in spot_positions: spot_size = 6 if level == 1 else 8 draw.ellipse([x, y, x+spot_size, y+spot_size], fill=(255, 255, 255)) # Door door_width = 12 if level == 1 else 14 door_x = size//2 - door_width//2 draw.rectangle([door_x, size-18, door_x+door_width, size-4], fill=(101, 67, 33)) # Door knob draw.ellipse([door_x+door_width-4, size-12, door_x+door_width-2, size-10], fill=(255, 215, 0)) # Windows (cute round windows) if level > 1: window_size = 8 # Left window draw.ellipse([size//4-4, size//2+8, size//4+4, size//2+16], fill=(135, 206, 250)) draw.line([(size//4, size//2+8), (size//4, size//2+16)], fill=(100, 150, 200), width=1) # Right window draw.ellipse([size*3//4-4, size//2+8, size*3//4+4, size//2+16], fill=(135, 206, 250)) draw.line([(size*3//4, size//2+8), (size*3//4, size//2+16)], fill=(100, 150, 200), width=1) if level > 2: # Chimney draw.rectangle([size*3//4-4, size//4-4, size*3//4+4, size//4+8], fill=(120, 80, 50)) # Smoke puffs draw.ellipse([size*3//4-2, size//4-8, size*3//4+2, size//4-4], fill=(200, 200, 200)) return Sprite(frames=[np.array(img)]) @staticmethod def generate_storage_sprite(level: int = 1) -> Sprite: size = 64 img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Barn/warehouse style draw.rectangle([8, 20, size-8, size-4], fill=(139, 90, 43)) draw.polygon([(8, 20), (size//2, 8), (size-8, 20)], fill=(120, 80, 50)) # Capacity bars for i in range(level): y = size - 12 - i*8 draw.rectangle([12, y, size-12, y+6], fill=(200, 180, 140)) # Door draw.rectangle([size//2-8, size-16, size//2+8, size-4], fill=(80, 50, 30)) return Sprite(frames=[np.array(img)]) @staticmethod def generate_workshop_sprite(level: int = 1) -> Sprite: size = 64 img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Workshop building draw.rectangle([8, 24, size-8, size-4], fill=(120, 100, 80)) draw.polygon([(8, 24), (size//2, 12), (size-8, 24)], fill=(100, 80, 60)) # Chimney with smoke draw.rectangle([size-16, 8, size-12, 24], fill=(80, 60, 40)) draw.ellipse([size-18, 4, size-10, 10], fill=(180, 180, 180)) # Tool rack visual draw.line([(12, 32), (size-12, 32)], fill=(60, 40, 20), width=2) return Sprite(frames=[np.array(img)]) @staticmethod def generate_townhall_sprite(level: int = 1) -> Sprite: size = 80 img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Grand mushroom building draw.rectangle([10, 30, size-10, size-4], fill=(180, 100, 60)) draw.ellipse([5, 10, size-5, 35], fill=(220, 80, 80)) # Multiple white spots spots = [(15, 18), (30, 15), (45, 17), (60, 19)] for x, y in spots: draw.ellipse([x, y, x+8, y+8], fill=(255, 255, 255)) # Flag on top draw.rectangle([size//2-2, 4, size//2+2, 20], fill=(101, 67, 33)) draw.polygon([(size//2+2, 8), (size//2+2, 16), (size//2+14, 12)], fill=(255, 0, 0)) # Large entrance draw.rectangle([size//2-10, size-24, size//2+10, size-4], fill=(101, 67, 33)) # Star emblem draw.text((size//2-6, size//2+4), "★", fill=(255, 215, 0)) return Sprite(frames=[np.array(img)]) @staticmethod def generate_grave_sprite() -> Sprite: img = Image.new('RGBA', (32, 32), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Tombstone draw.rectangle([10, 12, 22, 28], fill=(100, 100, 100)) draw.ellipse([10, 8, 22, 16], fill=(100, 100, 100)) # Cross engraving draw.line([(16, 14), (16, 20)], fill=(80, 80, 80), width=2) draw.line([(13, 17), (19, 17)], fill=(80, 80, 80), width=2) # Flowers draw.ellipse([8, 26, 11, 29], fill=(255, 100, 100)) draw.ellipse([21, 26, 24, 29], fill=(255, 200, 100)) return Sprite(frames=[np.array(img)])