"""Pig entity class."""
from .base_entity import BaseEntity
import pygame
import random
import math
from ..utils.constants import *
from ..utils.asset_loader import AssetLoader
[docs]
class Pig(BaseEntity):
"""Represents a pig entity in the game."""
[docs]
ANIM_FRAME_DURATION = 0.15 # seconds per frame
def __init__(self, x, y):
[docs]
self.size = random.uniform(0.8, 1.2) # Size variation
[docs]
self.asset_loader = AssetLoader()
[docs]
self.direction = 'left'
[docs]
self.sprites = self._load_sprites()
def _load_sprites(self):
"""Load all directional walk frames; fall back to a generated sprite."""
sprite_size = int(32 * self.size)
sprites = {}
for direction in ('left', 'right', 'up', 'down'):
frames = []
for i in range(1, 4):
try:
img = self.asset_loader.load_image(f"pig/pig-{direction}{i}.png")
frames.append(pygame.transform.scale(img, (sprite_size, sprite_size)))
except (FileNotFoundError, Exception):
break
if not frames:
fallback = pygame.transform.scale(self._create_pig_sprite(), (sprite_size, sprite_size))
frames = [fallback]
sprites[direction] = frames
return sprites
def _create_pig_sprite(self):
"""Create a simple pig sprite as fallback."""
# This is kept as fallback if no sprite file is found
size = int(24 * self.size)
surface = pygame.Surface((size, size), pygame.SRCALPHA)
# Draw simple pig body
pygame.draw.ellipse(surface, (255, 192, 203), (2, 4, size-4, size-8))
pygame.draw.ellipse(surface, (255, 182, 193), (2, 4, size-4, size-8), 2)
# Draw snout
pygame.draw.circle(surface, (255, 182, 193), (size//2, size//2), size//4)
# Draw eyes
pygame.draw.circle(surface, (0, 0, 0), (size//2 - 3, size//2 - 2), 2)
pygame.draw.circle(surface, (0, 0, 0), (size//2 + 3, size//2 - 2), 2)
return surface
[docs]
def update(self, dt, isometric_map):
"""Update pig behavior."""
# Movement behavior
self.move_timer -= dt
if self.move_timer <= 0:
# Choose new random target
self._choose_new_target(isometric_map)
self.move_timer = random.uniform(2, 5)
# Move towards target
dx, dy, distance = self._step_towards_target(dt, PIG_MOVE_SPEED)
if distance > 0.1:
# Map world movement to screen-space direction using the 2:1 tile ratio:
# screen_x ∝ (dx - dy), screen_y ∝ (dx + dy) * 0.5
# so left/right dominates when 2*|dx-dy| >= |dx+dy|
sdx = dx - dy
sdy = dx + dy
new_direction = self.direction
if abs(sdx) * 2 >= abs(sdy):
new_direction = 'right' if sdx > 0 else 'left'
else:
new_direction = 'down' if sdy > 0 else 'up'
if new_direction != self.direction:
self.direction = new_direction
self.anim_frame = 0
self.anim_timer = 0.0
# Advance walk animation
self.anim_timer += dt
frames = self.sprites[self.direction]
self.anim_frame = int(self.anim_timer / self.ANIM_FRAME_DURATION) % len(frames)
else:
self.anim_frame = 0
self.anim_timer = 0.0
# Eating behavior
self.eating_timer -= dt
if self.eating_timer <= 0:
self._eat_vegetation(isometric_map)
self.eating_timer = random.uniform(1, 3)
def _choose_new_target(self, isometric_map):
"""Choose a target tile within a local radius, preferring less degraded (healthier) land."""
candidates = []
cx, cy = int(round(self.x)), int(round(self.y))
radius = 5
world_min_x = isometric_map.map_offset_x
world_max_x = isometric_map.map_offset_x + isometric_map.width - 1
world_min_y = isometric_map.map_offset_y
world_max_y = isometric_map.map_offset_y + isometric_map.height - 1
lo_x = max(world_min_x, cx - radius)
hi_x = min(world_max_x, cx + radius)
lo_y = max(world_min_y, cy - radius)
hi_y = min(world_max_y, cy + radius)
if lo_x > hi_x or lo_y > hi_y:
return
for _ in range(20):
new_x = random.randint(lo_x, hi_x)
new_y = random.randint(lo_y, hi_y)
if (isometric_map.is_valid_position(new_x, new_y) and
isometric_map.get_tile_type(new_x, new_y) in WALKABLE_TILES):
degradation = isometric_map.get_degradation(new_x, new_y)
candidates.append((degradation, new_x, new_y))
if candidates:
_, self.target_x, self.target_y = min(candidates)
else:
self.target_x = int(round(self.x))
self.target_y = int(round(self.y))
def _eat_vegetation(self, isometric_map):
"""Eat vegetation and cause degradation."""
tile_x, tile_y = int(round(self.x)), int(round(self.y))
# Eat in radius around pig
for dx in range(-PIG_EATING_RADIUS, PIG_EATING_RADIUS + 1):
for dy in range(-PIG_EATING_RADIUS, PIG_EATING_RADIUS + 1):
eat_x, eat_y = tile_x + dx, tile_y + dy
if isometric_map.is_valid_position(eat_x, eat_y):
# Add degradation based on distance
distance = math.sqrt(dx * dx + dy * dy)
if distance <= PIG_EATING_RADIUS:
degradation_amount = 0.01 * (1 - distance / PIG_EATING_RADIUS)
isometric_map.add_degradation(eat_x, eat_y, degradation_amount)
[docs]
def render(self, screen, isometric_map):
"""Render the pig."""
screen_x, screen_y = self.get_screen_position(isometric_map)
# Render current animation frame
frame = self.sprites[self.direction][self.anim_frame]
sprite_rect = frame.get_rect()
sprite_rect.center = (screen_x, screen_y - 10)
screen.blit(frame, sprite_rect)
# Render health/status indicator
if self.eating_timer > 2:
# Show eating indicator
pygame.draw.circle(screen, (0, 255, 0), (screen_x, screen_y - 25), 3)