Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # ancient_poke_side_v2.py
- """
- Ancient Pokémon — side-scroller prototype v2
- Features:
- - Runtime placeholder sprite generation (PNG files) under ./sprites/
- - Sprite loading + animation system with graceful fallback
- - Pokemon AI states: idle, wander, follow, guard, attack, flee
- - Simple in-world combat: attack cooldown, damage, faint
- - Start menu, save/load, parallax, camera, multiple ownership types
- - Uses pygame only (no external libs required)
- """
- import pygame
- import json
- import os
- import random
- from pathlib import Path
- import math
- import time
- # ---- Basic setup ----
- pygame.init()
- WIDTH, HEIGHT = 1200, 640
- SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
- pygame.display.set_caption("Ancient Pokémon — Prototype v2")
- CLOCK = pygame.time.Clock()
- FONT = pygame.font.SysFont("arial", 16)
- BIG_FONT = pygame.font.SysFont("serif", 44)
- SAVE_PATH = Path("savegame_v2.json")
- SPRITE_ROOT = Path("sprites")
- FPS = 60
- # ---- Game constants ----
- GRAVITY = 1400
- GROUND_Y = HEIGHT - 90
- PLAYER_SPEED = 320
- POKEMON_BASE_SPEED = 160
- SCROLL_EDGE = WIDTH * 0.4
- # ---- Utility helpers ----
- def ensure_dir(p: Path):
- if not p.exists():
- p.mkdir(parents=True, exist_ok=True)
- def clamp(v, a, b):
- return max(a, min(b, v))
- # ---- Placeholder sprite generation ----
- def generate_placeholder_sprite(path: Path, text: str, size=(64, 64), bgcolor=(150, 150, 150), fg=(20, 20, 20)):
- """
- Create a simple PNG using pygame surfaces and save it.
- """
- ensure_dir(path.parent)
- surf = pygame.Surface(size, pygame.SRCALPHA)
- surf.fill(bgcolor)
- # border
- pygame.draw.rect(surf, (0,0,0), surf.get_rect(), 2)
- # text
- small = pygame.font.SysFont("arial", max(10, size[1] // 6))
- lines = text.split("\n")
- # center multiline
- total_h = sum(small.size(line)[1] for line in lines)
- y = (size[1] - total_h) // 2
- for line in lines:
- txt = small.render(line, True, fg)
- surf.blit(txt, ((size[0] - txt.get_width())//2, y))
- y += txt.get_height()
- try:
- pygame.image.save(surf, str(path))
- return True
- except Exception as e:
- print("Could not save placeholder sprite:", e)
- return False
- def ensure_default_sprites():
- """
- Create a small set of placeholder sprites for:
- - player: idle/run frames
- - pokemon species: idle/run/attack frames
- """
- species_list = ["scarabeet", "sphinxling", "desertlark", "mummat", "ra-hound", "mini-scarab", "sandpup"]
- # Player frames
- player_dir = SPRITE_ROOT / "player"
- ensure_dir(player_dir)
- # create idle frames and run frames
- for action, frames in [("idle", 3), ("run", 4), ("attack", 3)]:
- ad = player_dir / action
- ensure_dir(ad)
- for i in range(frames):
- p = ad / f"{i}.png"
- if not p.exists():
- generate_placeholder_sprite(p, f"player\n{action}\n{i}", size=(48,72), bgcolor=(180,160,120))
- # Create species frames
- for spec in species_list:
- sdir = SPRITE_ROOT / spec
- for action, frames in [("idle", 3), ("run", 4), ("attack", 3), ("faint", 2)]:
- ad = sdir / action
- ensure_dir(ad)
- for i in range(frames):
- p = ad / f"{i}.png"
- if not p.exists():
- bg = (200,180,120) if "scarab" in spec or "sand" in spec else (180,120,160)
- generate_placeholder_sprite(p, f"{spec}\n{action}\n{i}", size=(56,56), bgcolor=bg)
- # ---- Sprite / Animation loading system ----
- class AnimatedSprite:
- """
- Loads action folders under sprites/<name>/<action>/<frame>.png
- action_frames: dict action -> [Surface,..]
- """
- def __init__(self, name, size_hint=(48,48)):
- self.name = name
- self.base = SPRITE_ROOT / name
- self.actions = {}
- self.size_hint = size_hint
- self.loaded = False
- self.load_all_actions()
- def load_all_actions(self):
- # scan base folder for subfolders (actions)
- if not self.base.exists():
- self.loaded = False
- return
- for action_dir in self.base.iterdir():
- if action_dir.is_dir():
- frames = []
- # load sequentially numbered PNGs: 0.png,1.png,...
- i = 0
- while True:
- p = action_dir / f"{i}.png"
- if p.exists():
- try:
- img = pygame.image.load(str(p)).convert_alpha()
- frames.append(img)
- except Exception:
- # ignore bad frames
- pass
- i += 1
- else:
- break
- if frames:
- self.actions[action_dir.name] = frames
- self.loaded = len(self.actions) > 0
- def get_action_frames(self, action):
- # fallback: idle or rectangle placeholder
- if action in self.actions:
- return self.actions[action]
- if "idle" in self.actions:
- return self.actions["idle"]
- return None
- # ---- Entity base classes ----
- class Entity:
- def __init__(self, x, y, w, h):
- self.x = float(x)
- self.y = float(y)
- self.vx = 0.0
- self.vy = 0.0
- self.w = w
- self.h = h
- self.on_ground = False
- @property
- def rect(self):
- return pygame.Rect(int(self.x), int(self.y), self.w, self.h)
- def apply_gravity(self, dt):
- if not self.on_ground:
- self.vy += GRAVITY * dt
- self.y += self.vy * dt
- def ground_collision(self):
- if self.y + self.h >= GROUND_Y:
- self.y = GROUND_Y - self.h
- self.vy = 0
- self.on_ground = True
- else:
- self.on_ground = False
- # ---- Character (Player/NPC) ----
- class Character(Entity):
- def __init__(self, name, x, y, is_player=False):
- super().__init__(x, y, 36, 64)
- self.name = name
- self.is_player = is_player
- self.facing = 1
- self.speed = PLAYER_SPEED
- self.items = {"healing_potion": 2}
- self.pokemon_party = [] # list of Pokemon instances
- self.sprite = AnimatedSprite("player", size_hint=(48,72))
- # Animation state
- self.action = "idle"
- self.frame_idx = 0
- self.frame_timer = 0.0
- self.frame_duration = 0.12
- def player_input(self, dt, keys):
- self.vx = 0
- moving = False
- if keys[pygame.K_LEFT] or keys[pygame.K_a]:
- self.vx = -self.speed
- self.facing = -1
- moving = True
- if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
- self.vx = self.speed
- self.facing = 1
- moving = True
- if (keys[pygame.K_SPACE] or keys[pygame.K_w] or keys[pygame.K_UP]) and self.on_ground:
- self.vy = -520
- self.on_ground = False
- if moving:
- self.action = "run"
- else:
- self.action = "idle"
- # Item: H heal first pokemon
- if keys[pygame.K_h] and self.items.get("healing_potion",0) > 0:
- if self.pokemon_party:
- target = self.pokemon_party[0]
- target.heal(30)
- self.items["healing_potion"] -= 1
- self.x += self.vx * dt
- def update(self, dt, keys):
- if self.is_player:
- self.player_input(dt, keys)
- # update physics
- self.apply_gravity(dt)
- self.ground_collision()
- # animation frame
- self.frame_timer += dt
- if self.frame_timer >= self.frame_duration:
- self.frame_timer -= self.frame_duration
- self.frame_idx = (self.frame_idx + 1) % max(1, len(self.sprite.get_action_frames(self.action) or [0]))
- def draw(self, surface, camera):
- dest = camera.apply(self.rect)
- frames = self.sprite.get_action_frames(self.action)
- if frames:
- frame = frames[self.frame_idx % len(frames)]
- # flip if facing left
- if self.facing < 0:
- frame = pygame.transform.flip(frame, True, False)
- surface.blit(frame, (dest.x, dest.y))
- else:
- # fallback rectangle
- pygame.draw.rect(surface, (50,120,200) if self.is_player else (160,120,80), dest)
- surface.blit(FONT.render(self.name, True, (0,0,0)), (dest.x, dest.y - 18))
- # ---- Pokemon class with AI and combat ----
- class Pokemon(Entity):
- def __init__(self, species, x, y, owner=None):
- super().__init__(x, y, 48, 48)
- self.species = species.lower()
- self.owner = owner # None | Character | Pokemon
- self.personality = random.choice(["brave", "timid", "curious"])
- self.state = "idle" # idle, wander, follow, guard, attack, flee
- self.health = 100.0
- self.max_health = 100.0
- self.speed = POKEMON_BASE_SPEED * (1.1 if self.personality=="brave" else 0.9 if self.personality=="timid" else 1.0)
- self.wander_timer = random.uniform(0.4, 2.5)
- self.target = None
- # combat
- self.attack_range = 90
- self.attack_damage = 18 if self.personality=="brave" else 12
- self.attack_cooldown = 1.2
- self.attack_timer = 0.0
- self.is_fainted = False
- # sprites + animation
- self.sprite = AnimatedSprite(self.species, size_hint=(56,56))
- self.action = "idle"
- self.frame_idx = 0
- self.frame_timer = 0.0
- self.frame_duration = 0.14
- def heal(self, amount):
- if self.is_fainted:
- return
- self.health = clamp(self.health + amount, 0, self.max_health)
- def take_damage(self, amount, attacker=None):
- if self.is_fainted:
- return
- self.health -= amount
- # reactive: if owner exists and is player, might call to assist
- if self.health <= 0:
- self.health = 0
- self.is_fainted = True
- self.state = "faint"
- self.action = "faint"
- self.vx = 0
- self.attack_timer = 0
- else:
- # small flinch / possible flee
- if self.personality == "timid" and random.random() < 0.6:
- self.state = "flee"
- elif self.owner is not None:
- # guard or counterattack
- if random.random() < 0.6:
- self.state = "attack"
- self.target = attacker
- def _resolve_human_owner(self):
- owner = self.owner
- visited = set()
- while owner and not isinstance(owner, Character):
- if id(owner) in visited:
- break
- visited.add(id(owner))
- owner = getattr(owner, "owner", None)
- return owner if isinstance(owner, Character) else None
- def _distance_to(self, ent):
- return abs((self.x + self.w/2) - (ent.x + ent.w/2))
- def update_animation(self, dt):
- self.frame_timer += dt
- if self.frame_timer >= self.frame_duration:
- self.frame_timer -= self.frame_duration
- frames = self.sprite.get_action_frames(self.action) or []
- if frames:
- self.frame_idx = (self.frame_idx + 1) % len(frames)
- else:
- self.frame_idx = 0
- def update(self, dt, world):
- if self.is_fainted:
- # fainting: slowly sink a little
- self.vx = 0
- self.vy += GRAVITY * dt * 0.2
- self.y += self.vy * dt
- self.ground_collision()
- self.update_animation(dt)
- return
- # Reduce attack timer
- if self.attack_timer > 0:
- self.attack_timer = max(0.0, self.attack_timer - dt)
- # State logic
- if self.owner is None:
- self._wild_ai(dt, world)
- else:
- self._owned_ai(dt, world)
- # movement
- self.x += self.vx * dt
- self.apply_gravity(dt)
- self.ground_collision()
- # animation choice
- if self.state in ("attack","assist"):
- self.action = "attack"
- elif abs(self.vx) > 10:
- self.action = "run"
- elif self.is_fainted:
- self.action = "faint"
- else:
- self.action = "idle"
- self.update_animation(dt)
- def _wild_ai(self, dt, world):
- # wild behavior: wander usually; might attack player-side pokemon if brave
- nearest = world.find_nearest_interesting(self)
- if nearest and self._distance_to(nearest) < 200:
- if self.personality == "timid":
- self.state = "flee"
- self._flee_from(nearest, dt)
- return
- else:
- # brave or curious: maybe attack
- if random.random() < 0.6:
- self.state = "attack"
- self.target = nearest
- self._approach_target(dt)
- self.try_attack(world)
- return
- # wander idle
- self.state = "wander"
- self.wander_timer -= dt
- if self.wander_timer <= 0:
- self.wander_timer = random.uniform(1.0, 3.0)
- self.vx = random.choice([-1,0,1]) * self.speed * 0.5
- self.vx *= 0.88
- def _owned_ai(self, dt, world):
- human_owner = self._resolve_human_owner()
- # guard behavior: if close to owner position and enemy nearby -> attack
- if self.state == "guard" or (self.owner and isinstance(self.owner, Pokemon) and getattr(self.owner, "state", None) == "guard"):
- # stay near owner
- follow_ent = self.owner if self.owner else human_owner
- if follow_ent:
- dx = (follow_ent.x + follow_ent.w/2) - (self.x + self.w/2)
- if abs(dx) > 70:
- self.vx = self.speed * (1 if dx > 0 else -1)
- else:
- self.vx = 0
- # look for enemies
- enemy = world.find_nearest_enemy_for(self)
- if enemy and self._distance_to(enemy) < 160:
- self.state = "attack"
- self.target = enemy
- self._approach_target(dt)
- self.try_attack(world)
- return
- return
- # assist/attack commanded by owner
- if self.state in ("assist","attack") and self.target:
- if self.target.is_fainted:
- self.state = "follow"
- self.target = None
- return
- if self._distance_to(self.target) > 18 and self._distance_to(self.target) < 800:
- self._approach_target(dt)
- else:
- self.vx = 0
- self.try_attack(world)
- return
- # follow owner
- if isinstance(self.owner, Character) or isinstance(self.owner, Pokemon):
- follow_ent = self.owner
- dx = (follow_ent.x + follow_ent.w/2 - 40) - self.x
- if abs(dx) > 60:
- self.vx = self.speed if dx > 0 else -self.speed
- else:
- self.vx = 0
- # look for nearby enemies if guard-ish or brave
- nearby = world.find_nearest_enemy_for(self)
- if nearby and self._distance_to(nearby) < 160:
- if self.personality != "timid":
- self.state = "attack"
- self.target = nearby
- return
- # default fallback
- self.state = "idle"
- self.vx *= 0.8
- def _approach_target(self, dt):
- if not self.target:
- return
- dx = (self.target.x + self.target.w/2) - (self.x + self.w/2)
- if abs(dx) > 6:
- self.vx = self.speed if dx > 0 else -self.speed
- else:
- self.vx = 0
- def _flee_from(self, ent, dt):
- dx = (self.x + self.w/2) - (ent.x + ent.w/2)
- self.vx = self.speed if dx > 0 else -self.speed
- self.x += self.vx * dt
- def try_attack(self, world):
- # if in range and cooldown expired, perform attack
- if not self.target or self.attack_timer > 0:
- return
- if self._distance_to(self.target) <= self.attack_range:
- # perform attack
- self.attack_timer = self.attack_cooldown
- # damage
- # simple damage: attack_damage +/- 25%
- dmg = self.attack_damage * random.uniform(0.75, 1.25)
- self.target.take_damage(dmg, attacker=self)
- # slight recoil or animation handled via action
- self.action = "attack"
- def draw(self, surface, camera):
- if self.is_fainted:
- color = (90,90,90)
- elif self.owner is None:
- color = (200,70,70)
- elif isinstance(self.owner, Character) and self._resolve_human_owner() and self._resolve_human_owner().is_player:
- color = (80,200,120)
- else:
- color = (200,180,80)
- dest = camera.apply(self.rect)
- frames = self.sprite.get_action_frames(self.action)
- if frames:
- frame = frames[self.frame_idx % len(frames)]
- # flip based on velocity
- if self.vx < -10:
- frame = pygame.transform.flip(frame, True, False)
- surface.blit(frame, (dest.x, dest.y))
- else:
- pygame.draw.ellipse(surface, color, dest)
- # health bar
- hb_w = self.w
- hb_h = 6
- hp_ratio = max(0.0, self.health / self.max_health)
- hp_rect = pygame.Rect(dest.x, dest.y - 10, int(hb_w * hp_ratio), hb_h)
- bg_rect = pygame.Rect(dest.x, dest.y - 10, hb_w, hb_h)
- pygame.draw.rect(surface, (80,80,80), bg_rect)
- pygame.draw.rect(surface, (80,200,120), hp_rect)
- # name + state
- txt = FONT.render(f"{self.species} [{self.state}]", True, (0,0,0))
- surface.blit(txt, (dest.x, dest.y - 24))
- # ---- Camera & Parallax ----
- class Camera:
- def __init__(self):
- self.x = 0.0
- def apply(self, rect: pygame.Rect):
- return rect.move(-int(self.x), 0)
- def update(self, player: Character):
- target_x = player.x - SCROLL_EDGE
- if target_x > self.x:
- self.x = target_x
- # clamp min
- self.x = max(0, self.x)
- class ParallaxLayer:
- def __init__(self, color, height, y_offset, speed):
- self.color = color
- self.height = height
- self.y_offset = y_offset
- self.speed = speed
- def draw(self, surf, camera_x):
- w = surf.get_width()
- tile_w = 480
- offset = int((camera_x * self.speed) % tile_w)
- for i in range(-1, w//tile_w + 2):
- r = pygame.Rect(i*tile_w - offset, self.y_offset, tile_w - 40, self.height)
- pygame.draw.rect(surf, self.color, r)
- # ---- World class ----
- class World:
- def __init__(self):
- self.camera = Camera()
- self.bg_layers = [
- ParallaxLayer((210,190,140), 200, 40, 0.12),
- ParallaxLayer((160,140,100), 120, 140, 0.28),
- ]
- self.foreground = []
- self.characters = []
- self.pokemons = []
- self.populate_demo()
- def populate_demo(self):
- # player
- player = Character("Player", 220, GROUND_Y - 64, is_player=True)
- self.player = player
- self.characters.append(player)
- # npc
- npc = Character("Merchant", 760, GROUND_Y - 64, is_player=False)
- self.characters.append(npc)
- # foreground objects
- for i in range(10):
- rx = i * 300 + 120
- self.foreground.append(pygame.Rect(rx, GROUND_Y - random.randint(10,40) - 60, 90, random.randint(40,120)))
- # pokemons
- p1 = Pokemon("scarabeet", 260, GROUND_Y - 48, owner=player)
- p2 = Pokemon("sphinxling", 300, GROUND_Y - 48, owner=player)
- w1 = Pokemon("desertlark", 920, GROUND_Y - 48, owner=None)
- w2 = Pokemon("mummat", 1100, GROUND_Y - 48, owner=None)
- p3 = Pokemon("ra-hound", 420, GROUND_Y - 48, owner=None)
- sub = Pokemon("mini-scarab", 440, GROUND_Y - 48, owner=p3)
- # set some personalities for variety
- p1.personality = "brave"
- p2.personality = "curious"
- w2.personality = "timid"
- p3.personality = "brave"
- self.pokemons.extend([p1,p2,w1,w2,p3,sub])
- player.pokemon_party.extend([p1,p2])
- def update(self, dt, keys_pressed):
- for ch in self.characters:
- ch.update(dt, keys_pressed)
- for pk in self.pokemons:
- pk.update(dt, self)
- # simple removal of fainted pokemons from active list after a delay? keep for now
- self.camera.update(self.player)
- def draw(self, surf):
- surf.fill((180,220,255))
- for layer in self.bg_layers:
- layer.draw(surf, self.camera.x)
- pygame.draw.rect(surf, (220,200,160), pygame.Rect(0, GROUND_Y, WIDTH, HEIGHT - GROUND_Y))
- # foreground objects
- for r in self.foreground:
- drawr = self.camera.apply(r)
- pygame.draw.rect(surf, (120,100,70), drawr)
- # characters
- for ch in self.characters:
- ch.draw(surf, self.camera)
- # pokemons
- for pk in self.pokemons:
- pk.draw(surf, self.camera)
- # UI
- self.draw_ui(surf)
- def draw_ui(self, surf):
- x = 8; y = 8
- p = self.player
- items_txt = f"Items: {p.items}"
- surf.blit(FONT.render(items_txt, True, (0,0,0)), (x,y))
- y += 22
- party_txt = "Party: " + ", ".join([f"{pk.species}({int(pk.health)})" for pk in p.pokemon_party])
- surf.blit(FONT.render(party_txt, True, (0,0,0)), (x,y))
- # instructions
- instr = "Arrows/A-D move, Space jump, H heal, 1 attack, 2 follow, C spawn wild, F5 save, F9 load"
- surf.blit(FONT.render(instr, True, (0,0,0)), (8, HEIGHT - 24))
- # world queries
- def find_nearest_interesting(self, pk):
- candidates = []
- for ch in self.characters:
- candidates.append(ch)
- for p in self.pokemons:
- if p is not pk:
- candidates.append(p)
- if not candidates:
- return None
- nearest = min(candidates, key=lambda c: abs((pk.x+pk.w/2)-(c.x+c.w/2)))
- return nearest
- def find_nearest_enemy_for(self, pk):
- my_owner = pk._resolve_human_owner()
- enemies = []
- for p in self.pokemons:
- if p is pk or p.is_fainted:
- continue
- other_owner = p._resolve_human_owner()
- # enemy if owners differ (player vs non-player) or wild vs player's
- if my_owner is None and other_owner is not None:
- enemies.append(p)
- elif my_owner is not None and other_owner is not my_owner:
- enemies.append(p)
- if not enemies:
- return None
- return min(enemies, key=lambda e: abs((pk.x+pk.w/2)-(e.x+e.w/2)))
- # serialization
- def serialize(self):
- data = {
- "player": {"x": self.player.x, "y": self.player.y, "items": self.player.items},
- "pokemons": []
- }
- for pk in self.pokemons:
- data["pokemons"].append({
- "species": pk.species,
- "x": pk.x,
- "y": pk.y,
- "health": pk.health,
- "personality": pk.personality,
- "owner": self._owner_to_ref(pk.owner),
- "is_fainted": pk.is_fainted
- })
- return data
- def _owner_to_ref(self, owner):
- if owner is None:
- return None
- if isinstance(owner, Character):
- return {"type":"player"} if owner.is_player else {"type":"npc","name":owner.name,"x":owner.x,"y":owner.y}
- if isinstance(owner, Pokemon):
- try:
- return {"type":"pokemon","index":self.pokemons.index(owner)}
- except ValueError:
- return None
- return None
- def load_from(self, data):
- if "player" in data:
- pd = data["player"]
- self.player.x = pd.get("x", self.player.x)
- self.player.y = pd.get("y", self.player.y)
- self.player.items = pd.get("items", self.player.items)
- if "pokemons" in data:
- self.pokemons.clear()
- # create instances
- for pkd in data["pokemons"]:
- pk = Pokemon(pkd["species"], pkd.get("x",300), pkd.get("y", GROUND_Y-48), owner=None)
- pk.health = pkd.get("health", 100)
- pk.personality = pkd.get("personality", "curious")
- pk.is_fainted = pkd.get("is_fainted", False)
- self.pokemons.append(pk)
- # resolve owners
- for i, pkd in enumerate(data["pokemons"]):
- ref = pkd.get("owner")
- if not ref:
- self.pokemons[i].owner = None
- elif ref["type"] == "player":
- self.pokemons[i].owner = self.player
- self.player.pokemon_party.append(self.pokemons[i])
- elif ref["type"] == "pokemon":
- idx = ref.get("index")
- if idx is not None and 0 <= idx < len(self.pokemons):
- self.pokemons[i].owner = self.pokemons[idx]
- elif ref["type"] == "npc":
- existing = next((c for c in self.characters if c.name == ref["name"]), None)
- if not existing:
- newnpc = Character(ref["name"], x=ref.get("x",700), y=ref.get("y",GROUND_Y-64), is_player=False)
- self.characters.append(newnpc)
- existing = newnpc
- self.pokemons[i].owner = existing
- # ---- Start menu / game loop ----
- def start_screen():
- clock = pygame.time.Clock()
- options = ["New Game", "Load Game", "Quit"]
- selected = 0
- while True:
- for ev in pygame.event.get():
- if ev.type == pygame.QUIT:
- return None
- if ev.type == pygame.KEYDOWN:
- if ev.key == pygame.K_UP:
- selected = (selected - 1) % len(options)
- if ev.key == pygame.K_DOWN:
- selected = (selected + 1) % len(options)
- if ev.key == pygame.K_RETURN:
- return options[selected]
- SCREEN.fill((10,10,40))
- title = BIG_FONT.render("Ancient Pokémon — Prototype v2", True, (255,230,160))
- SCREEN.blit(title, (WIDTH//2 - title.get_width()//2, 80))
- for i,opt in enumerate(options):
- col = (255,255,200) if i == selected else (200,200,200)
- s = FONT.render(opt, True, col)
- SCREEN.blit(s, (WIDTH//2 - s.get_width()//2, 260 + i*40))
- pygame.display.flip()
- clock.tick(30)
- def game_loop(world):
- running = True
- paused = False
- last_save_time = 0
- while running:
- dt = CLOCK.tick(FPS) / 1000.0
- keys = pygame.key.get_pressed()
- for ev in pygame.event.get():
- if ev.type == pygame.QUIT:
- running = False
- if ev.type == pygame.KEYDOWN:
- if ev.key == pygame.K_F5:
- save_json(SAVE_PATH, world.serialize())
- last_save_time = time.time()
- print("Saved.")
- if ev.key == pygame.K_F9:
- data = load_json(SAVE_PATH)
- if data:
- world.load_from(data)
- print("Loaded.")
- if ev.key == pygame.K_ESCAPE:
- running = False
- if ev.key == pygame.K_1:
- if world.player.pokemon_party:
- pk = world.player.pokemon_party[0]
- pk.state = "attack"
- pk.target = world.find_nearest_enemy_for(pk)
- if ev.key == pygame.K_2:
- if world.player.pokemon_party:
- pk = world.player.pokemon_party[0]
- pk.state = "follow"
- pk.target = None
- if ev.key == pygame.K_c:
- spawn_x = world.camera.x + WIDTH * 0.85
- neww = Pokemon(random.choice(["sandpup","mini-scarab","desertlark"]), spawn_x, GROUND_Y-48, owner=None)
- world.pokemons.append(neww)
- world.update(dt, keys)
- world.draw(SCREEN)
- # overlay
- fps = int(CLOCK.get_fps())
- SCREEN.blit(FONT.render(f"FPS: {fps}", True, (0,0,0)), (WIDTH - 100, 8))
- if last_save_time and time.time() - last_save_time < 2.5:
- SCREEN.blit(FONT.render("Saved!", True, (0,0,0)), (WIDTH - 180, 28))
- pygame.display.flip()
- # on exit save
- try:
- save_json(SAVE_PATH, world.serialize())
- except Exception as e:
- print("Error saving on exit:", e)
- # ---- Simple JSON load/save helpers ----
- def load_json(path: Path):
- if path.exists():
- try:
- with open(path, "r") as f:
- return json.load(f)
- except Exception as e:
- print("Load failed:", e)
- return None
- def save_json(path: Path, data):
- try:
- with open(path, "w") as f:
- json.dump(data, f, indent=2)
- return True
- except Exception as e:
- print("Save failed:", e)
- return False
- # ---- Main entry ----
- def main():
- # generate placeholder assets if missing
- ensure_default_sprites()
- # load sprites for all species used in demo (ensures AnimatedSprite picks them up)
- world = World()
- # ensure each Character/Pokemon AnimatedSprite tries loading (they load during construction)
- while True:
- choice = start_screen()
- if choice is None or choice == "Quit":
- break
- if choice == "New Game":
- world = World()
- game_loop(world)
- elif choice == "Load Game":
- data = load_json(SAVE_PATH)
- if data:
- world = World()
- world.load_from(data)
- game_loop(world)
- else:
- # fallback to new
- world = World()
- game_loop(world)
- pygame.quit()
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment