# game.py - Multi-Pet CLI with JSON Save/Load
from test import test_pet_runner
import os
import sys
import json
from pet import Pet
SAVE_FILE = "pets.json"
def choose(prompt, options):
"""
options: dict like {"c": "Create", "q": "Quit"}
returns the chosen key
"""
while True:
print(prompt)
for key in options:
label = options[key]
print("[{}] {}".format(key, label))
ans = input("> ").strip().lower()
if ans in options:
return ans
print("Invalid choice.\n")
# ---------- Persistence ----------
def save_all_pets(pets, filename=SAVE_FILE):
data = {}
for name in pets:
data[name] = pets[name].to_dict()
with open(filename, "w") as f:
json.dump(data, f, indent=2)
return filename
def load_all_pets(filename=SAVE_FILE):
if not os.path.exists(filename):
print("(No save file '{}' found; starting fresh.)".format(filename))
return {}
try:
with open(filename, "r") as f:
raw = json.load(f)
pets = {}
for name in raw:
pets[name] = Pet.from_dict(raw[name])
return pets
except ValueError:
print("(Could not read '{}' - invalid JSON.)".format(filename))
return {}
# ---------- Pet gameplay loop ----------
def play_with_pet(pet):
print("\nNow playing with {}. Keep them fed, rested, and happy!\n".format(pet.name))
while pet.alive:
print(pet.status())
choice = choose(
"Choose an action:",
{"f": "Feed", "p": "Play", "s": "Sleep", "a": "Pet/Affection", "b": "Back to main menu"},
)
if choice == "b":
return
if choice == "f":
print(pet.feed())
elif choice == "p":
print(pet.play())
elif choice == "s":
print(pet.sleep())
elif choice == "a":
print(pet.pet())
pet.tick()
print("\nOh no... {} has passed away. [heartbroken]\n".format(pet.name))
# ---------- Main program ----------
def list_pets(pets):
if not pets:
print("(No pets yet.)")
return
print("\nYour Pets:")
for name in pets:
print(" - {}".format(name))
print("")
def create_pet(pets):
name = input("Name your new pet: ").strip()
if not name:
print("Pet not created (empty name).")
return None
if name in pets:
print("A pet with that name already exists.")
return None
pet = Pet(name)
pets[name] = pet
print("Created pet '{}'.".format(name))
return pet
def select_pet(pets):
if not pets:
print("(No pets to select.)")
return None
list_pets(pets)
name = input("Enter pet name: ").strip()
return pets.get(name)
def main():
print("=== Virtual Pet - Multi-Pet with Save/Load ===")
pets = load_all_pets(SAVE_FILE)
active = None
while True:
print("\nMain Menu")
if active:
print("(Active pet: {})".format(active.name))
choice = choose(
"Choose an option:",
{
"c": "Create Pet",
"s": "Select Active Pet",
"l": "List Pets",
"p": "Play with Active Pet",
"v": "Save All Pets",
"o": "Load Pets",
"q": "Quit",
},
)
if choice == "q":
try:
save_all_pets(pets, SAVE_FILE)
print("Saved to {}. Goodbye!".format(SAVE_FILE))
except Exception:
print("Goodbye!")
break
if choice == "c":
pet = create_pet(pets)
if pet is not None:
active = pet
elif choice == "s":
pet = select_pet(pets)
if pet is not None:
active = pet
else:
print("No such pet.")
elif choice == "l":
list_pets(pets)
elif choice == "p":
if active is None:
print("No active pet. Select or create one first.")
else:
play_with_pet(active)
elif choice == "v":
fname = save_all_pets(pets, SAVE_FILE)
print("Saved {} pet(s) to {}.".format(len(pets), fname))
elif choice == "o":
pets = load_all_pets(SAVE_FILE)
if active is not None and active.name in pets:
active = pets[active.name]
else:
active = None
print("Loaded {} pet(s).".format(len(pets)))
if __name__ == "__main__":
# Call main to play
main()
# Call test_pet_runner ti test
#test_pet_runner()
# pet.py — Virtual Pet
def clamp(x, lo=0, hi=100):
# COMPLETE: return newx between lo and high
x = newx # Temp code for now
return newx
class Pet:
def __init__(self, name):
self.name = name
self.hunger = 30 # 0=full, 100=starving
self.energy = 70 # 0=exhausted, 100=rested
self.happiness = 70 # 0=sad, 100=thrilled
self.age_days = 0
self.alive = True
def feed(self):
self.hunger -= 20
self.energy -= 2
self._normalize()
return "{} enjoyed the meal.".format(self.name)
def play(self):
#. COMPLETE: What do do here?
return "what to return?"
def sleep(self):
# COMPLETE: What to do here?
return "what to return?"
def pet(self):
#. COMPLETE: What do do here?
return "what to return?"
def tick(self):
# complete
print("Time passes, replace this with an update to the pet")
def status(self):
# COMPLETE: Create a string showing all the attributes of the pet
status_str = "something..."
return status_str
def mood(self):
if not self.alive:
return "[dead]"
if self.hunger >= 80:
return "hungry :("
if self.energy <= 25:
return "tired zzz"
if self.happiness >= 85:
return "thrilled :D"
# COMPLETE: Add some more...
return "okay :)"
def _normalize(self):
self.hunger = clamp(self.hunger)
self.energy = clamp(self.energy)
self.happiness = clamp(self.happiness)
# COMPLETE: Clamp all values
def _life_checks(self):
if self.hunger >= 100:
self.alive = False
if self.energy <= 0:
self.alive = False
def to_dict(self):
return {
"name": self.name,
"hunger": int(self.hunger),
# COMPLETE: Save all parameters to dict
}
@classmethod
def from_dict(cls, data):
name = data.get("name", "Pet")
p = cls(name)
p.hunger = int(data.get("hunger", 30))
p.energy = int(data.get("energy", 70))
p.happiness = int(data.get("happiness", 70))
p.age_days = int(data.get("age_days", 0))
p.alive = bool(data.get("alive", True))
return p
# test_pet_runner.py
# ASCII-only test runner for pet.py on onlineGDB
#
# HOW TO RUN:
# 1) Put pet.py, main.py, and this file in the project root.
# 2) Run this file (set as the entry file) to test Pet features.
# 3) It creates pets_test.json in the project root.
import os
import json
import traceback
try:
from pet import Pet
except Exception as e:
print("FATAL: Could not import Pet from pet.py")
print("Error:", e)
raise
TEST_SAVE_FILE = "pets_test.json"
def print_header(title):
print("\n" + "=" * 72)
print(title)
print("=" * 72)
def assert_true(cond, msg):
if not cond:
raise AssertionError(msg)
def ascii_only(s):
try:
if not isinstance(s, str):
s = str(s)
s.encode("ascii")
return True
except Exception:
return False
# -------------------- tests --------------------
def test_defaults():
p = Pet("Testy")
assert_true(isinstance(p.name, str), "name must be str")
for attr in ("hunger", "energy", "happiness"):
val = getattr(p, attr, None)
assert_true(isinstance(val, int), attr + " must be int")
assert_true(0 <= val <= 100, attr + " must start in 0..100")
assert_true(isinstance(p.age_days, int), "age_days must be int")
assert_true(isinstance(p.alive, bool), "alive must be bool")
return "Defaults OK"
def test_feed():
p = Pet("F")
before = p.hunger
p.feed()
assert_true(p.hunger < before, "feed must reduce hunger")
return "feed reduces hunger"
def test_play():
p = Pet("P")
before = p.happiness
p.play()
assert_true(p.happiness > before, "play must increase happiness")
return "play increases happiness"
def test_sleep():
p = Pet("S")
before = p.energy
p.sleep()
assert_true(p.energy > before, "sleep must increase energy")
return "sleep increases energy"
def test_affection():
p = Pet("A")
before = p.happiness
p.pet()
assert_true(p.happiness >= before, "pet must not reduce happiness")
return "pet increases/keeps happiness"
def test_tick():
p = Pet("T")
h0, e0, a0 = p.hunger, p.energy, p.age_days
p.tick()
assert_true(p.age_days >= a0 + 1, "tick must increase age_days")
assert_true(p.hunger >= h0 or p.hunger == 100, "tick should raise hunger")
assert_true(p.energy <= e0 or p.energy == 0, "tick should lower energy")
return "tick drifts stats and ages"
def test_clamp():
p = Pet("Clamp")
p.hunger = 1000
p.energy = -50
p.happiness = 9999
p.tick()
for attr in ("hunger", "energy", "happiness"):
v = getattr(p, attr)
assert_true(0 <= v <= 100, attr + " must clamp 0..100")
return "clamp works"
def test_life_checks():
p = Pet("HC")
p.hunger = 100
if hasattr(p, "_life_checks"):
p._life_checks()
else:
p.tick()
assert_true(p.alive is False, "alive must be False when hunger >= 100")
return "life checks (hunger) OK"
def test_status_ascii():
p = Pet("Stat")
s = p.status()
assert_true(isinstance(s, str), "status must return str")
assert_true(ascii_only(s), "status must be ASCII only")
return "status ASCII OK"
def test_mood_ascii():
p = Pet("Mood")
s = p.mood()
assert_true(isinstance(s, str), "mood must return str")
assert_true(ascii_only(s), "mood must be ASCII only")
return "mood ASCII OK"
def test_dict_roundtrip():
p1 = Pet("Round")
p1.hunger, p1.energy, p1.happiness, p1.age_days = 55, 44, 66, 7
d = p1.to_dict()
p2 = Pet.from_dict(d)
assert_true(p2.name == p1.name, "name restored")
assert_true(p2.hunger == p1.hunger, "hunger restored")
assert_true(p2.energy == p1.energy, "energy restored")
assert_true(p2.happiness == p1.happiness, "happiness restored")
assert_true(p2.age_days == p1.age_days, "age_days restored")
assert_true(isinstance(p2.alive, bool), "alive is bool")
return "to_dict/from_dict OK"
def test_file_roundtrip():
p1, p2 = Pet("Fluffy"), Pet("Rex")
data = {"Fluffy": p1.to_dict(), "Rex": p2.to_dict()}
tmp = TEST_SAVE_FILE + ".tmp"
with open(tmp, "w") as f:
json.dump(data, f, indent=2)
try:
os.replace(tmp, TEST_SAVE_FILE)
except Exception:
if os.path.exists(TEST_SAVE_FILE):
os.remove(TEST_SAVE_FILE)
os.rename(tmp, TEST_SAVE_FILE)
with open(TEST_SAVE_FILE, "r") as f:
raw = json.load(f)
assert_true("Fluffy" in raw and "Rex" in raw, "keys must be present")
p1b = Pet.from_dict(raw["Fluffy"])
p2b = Pet.from_dict(raw["Rex"])
assert_true(isinstance(p1b, Pet) and isinstance(p2b, Pet), "from_dict returns Pet")
return "JSON file roundtrip OK (pets_test.json)"
TESTS = [
("Defaults", test_defaults),
("feed()", test_feed),
("play()", test_play),
("sleep()", test_sleep),
("pet()", test_affection),
("tick()", test_tick),
("Clamp", test_clamp),
("Life checks", test_life_checks),
("status ASCII", test_status_ascii),
("mood ASCII", test_mood_ascii),
("dict roundtrip", test_dict_roundtrip),
("file roundtrip", test_file_roundtrip),
]
def test_pet_runner():
print_header("Virtual Pet - test runner")
total, passed, failures = len(TESTS), 0, []
for name, fn in TESTS:
try:
msg = fn()
print("[PASS] {} - {}".format(name, msg))
passed += 1
except AssertionError as ae:
print("[FAIL] {} - {}".format(name, ae))
failures.append(name)
except Exception as e:
print("[ERROR] {} - {}".format(name, e))
traceback.print_exc()
failures.append(name)
print_header("Summary")
print("Passed: {}/{}".format(passed, total))
if failures:
print("Failed tests:")
for n in failures:
print(" - {}".format(n))
else:
print("All tests passed.")
print("\nTest JSON saved at: {}".format(TEST_SAVE_FILE))
{
"Fluffy": {
"name": "Fluffy",
"hunger": 30
},
"Rex": {
"name": "Rex",
"hunger": 30
}
}
Add you comments here:
- Lose conditions you chose
- How each action affects stats
= Stretch features implemented
- How to run, save, and load