Team2-Virtual-Pet 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .energy import drain_energy, restore_energy
2
+ from .health import update_health, wellness
3
+ from .history import get_history, record_event
4
+ from .save_load import load_pet, save_pet
5
+ from .death import check_death
6
+ from .diagnosis import diagnose_pet, recommend_action
package/death.py ADDED
@@ -0,0 +1,52 @@
1
+ from datetime import datetime, timedelta, timezone
2
+
3
+ from .pet import check_pet
4
+ from .health import DEATH_HEALTH
5
+
6
+ INACTIVE_DAYS = 7
7
+
8
+
9
+ def check_death(pet, last_opened=None, now=None):
10
+ #Check if the pet has died from low health or inactivity.
11
+
12
+ check_pet(pet, "health")
13
+
14
+ if "alive" not in pet or not isinstance(pet["alive"], bool):
15
+ raise TypeError("alive must be a bool")
16
+
17
+
18
+ if not pet["alive"]:
19
+ return True
20
+
21
+
22
+ if pet["health"] <= DEATH_HEALTH:
23
+ pet["alive"] = False
24
+ return True
25
+
26
+
27
+ if last_opened is not None:
28
+ if not isinstance(last_opened, str):
29
+ raise TypeError("last_opened must be a string")
30
+
31
+ try:
32
+ last_date = datetime.fromisoformat(last_opened)
33
+ except ValueError:
34
+ raise ValueError("last_opened must be a valid date")
35
+
36
+ if now is None:
37
+ now = datetime.now(timezone.utc)
38
+
39
+ if not isinstance(now, datetime):
40
+ raise TypeError("now must be a datetime")
41
+
42
+ if last_date.tzinfo is None or now.tzinfo is None:
43
+ raise ValueError("dates must include a timezone")
44
+
45
+ if last_date > now:
46
+ raise ValueError("last_opened cannot be in the future")
47
+
48
+ if now - last_date >= timedelta(days=INACTIVE_DAYS):
49
+ pet["alive"] = False
50
+ return True
51
+
52
+ return False
package/diagnosis.py ADDED
@@ -0,0 +1,71 @@
1
+
2
+ from .health import HEALTHY_HUNGER, HEALTHY_STAT, DEATH_HEALTH
3
+ from .pet import STATS, check_pet
4
+
5
+
6
+ def _validate_pet(pet):
7
+ """Validate all pet stats and the alive flag."""
8
+ for stat in STATS:
9
+ check_pet(pet, stat)
10
+
11
+ if not isinstance(pet.get("alive"), bool):
12
+ raise TypeError("alive must be a bool")
13
+
14
+
15
+ def diagnose_pet(pet):
16
+ """Return a list of health issues affecting the pet."""
17
+ _validate_pet(pet)
18
+
19
+ if not pet["alive"] or pet["health"] <= DEATH_HEALTH:
20
+ return ["Your pet has passed away."]
21
+
22
+ issues = []
23
+
24
+ if pet["hunger"] > HEALTHY_HUNGER:
25
+ issues.append("Your pet is hungry. Feed it!")
26
+
27
+ if pet["energy"] < HEALTHY_STAT:
28
+ issues.append("Your pet is tired. Let it rest!")
29
+
30
+ if pet["hygiene"] < HEALTHY_STAT:
31
+ issues.append("Your pet is dirty. Give it a bath!")
32
+
33
+ if pet["happiness"] < HEALTHY_STAT:
34
+ issues.append("Your pet is sad. Play with it!")
35
+
36
+ if pet["health"] <= 30:
37
+ issues.append("Your pet is critically unhealthy!")
38
+
39
+ if not issues:
40
+ return ["Your pet is healthy and happy!"]
41
+
42
+ return issues
43
+
44
+
45
+ def recommend_action(pet):
46
+ """Recommend care for the pet's most urgent need."""
47
+ _validate_pet(pet)
48
+
49
+ if not pet["alive"] or pet["health"] <= DEATH_HEALTH:
50
+ return "Your pet has passed away."
51
+
52
+ needs = {
53
+ "feed": max(pet["hunger"] - HEALTHY_HUNGER, 0),
54
+ "rest": max(HEALTHY_STAT - pet["energy"], 0),
55
+ "clean": max(HEALTHY_STAT - pet["hygiene"], 0),
56
+ "play": max(HEALTHY_STAT - pet["happiness"], 0),
57
+ }
58
+
59
+ most_urgent = max(needs, key=needs.get)
60
+
61
+ if needs[most_urgent] == 0:
62
+ return "Your pet is healthy! Keep taking good care of it."
63
+
64
+ recommendations = {
65
+ "feed": "Feed your pet to reduce its hunger.",
66
+ "rest": "Let your pet rest to restore energy.",
67
+ "clean": "Give your pet a bath to improve hygiene.",
68
+ "play": "Play with your pet to improve happiness.",
69
+ }
70
+
71
+ return recommendations[most_urgent]
package/display.py ADDED
@@ -0,0 +1,56 @@
1
+
2
+ """Display the Tamagotchi as an ASCII cat."""
3
+
4
+ HAPPY_LEVEL = 70
5
+ NEUTRAL_LEVEL = 35
6
+ DEATH_HEALTH = 10
7
+
8
+
9
+ def show_pet(pet):
10
+ """Return the Tamagotchi's appearance based on its stats."""
11
+
12
+ if not isinstance(pet, dict):
13
+ raise TypeError("pet must be a dictionary")
14
+
15
+ for stat in ("happiness", "health", "alive"):
16
+ if stat not in pet:
17
+ raise TypeError(f"pet must have a '{stat}' key")
18
+
19
+ happiness = pet["happiness"]
20
+ health = pet["health"]
21
+ alive = pet["alive"]
22
+
23
+ if type(happiness) is not int:
24
+ raise TypeError("happiness must be an int")
25
+ if happiness < 0 or happiness > 100:
26
+ raise ValueError("happiness must be between 0 and 100")
27
+
28
+ if type(health) is not int:
29
+ raise TypeError("health must be an int")
30
+ if health < 0 or health > 100:
31
+ raise ValueError("health must be between 0 and 100")
32
+
33
+ if not isinstance(alive, bool):
34
+ raise TypeError("alive must be a bool")
35
+
36
+ if not alive or health <= DEATH_HEALTH:
37
+ face = "( x.x )"
38
+ mouth = "> _ <"
39
+ elif happiness >= HAPPY_LEVEL:
40
+ face = "( ^.^ )"
41
+ mouth = "> w <"
42
+ elif happiness >= NEUTRAL_LEVEL:
43
+ face = "( o.o )"
44
+ mouth = "> - <"
45
+ else:
46
+ face = "( ;.; )"
47
+ mouth = "> n <"
48
+
49
+ cat = (
50
+ " /\\_/\\\n"
51
+ f" {face}\n"
52
+ f" {mouth}"
53
+ )
54
+
55
+ return cat
56
+
package/energy.py ADDED
@@ -0,0 +1,43 @@
1
+
2
+ from .history import record_event
3
+ from .pet import check_count, check_pet, clamp
4
+
5
+ REST_ENERGY = 20
6
+
7
+
8
+ def drain_energy(pet, amount):
9
+ """Decrease energy by a given amount."""
10
+ check_pet(pet, "energy")
11
+ check_count(amount, "amount")
12
+
13
+ before = pet["energy"]
14
+ pet["energy"] = clamp(before - amount)
15
+
16
+ if pet["energy"] != before:
17
+ record_event(
18
+ pet, "stat",
19
+ stat="energy",
20
+ before=before,
21
+ after=pet["energy"]
22
+ )
23
+
24
+ return pet["energy"]
25
+
26
+
27
+ def restore_energy(pet, amount=REST_ENERGY):
28
+ """Restore energy by a given amount."""
29
+ check_pet(pet, "energy")
30
+ check_count(amount, "amount")
31
+
32
+ before = pet["energy"]
33
+ pet["energy"] = clamp(before + amount)
34
+
35
+ if pet["energy"] != before:
36
+ record_event(
37
+ pet, "stat",
38
+ stat="energy",
39
+ before=before,
40
+ after=pet["energy"]
41
+ )
42
+
43
+ return pet["energy"]
package/happy.py ADDED
@@ -0,0 +1,72 @@
1
+ from .pet import clamp, check_count, check_pet
2
+ from .history import record_event
3
+ from .energy import drain_energy
4
+
5
+
6
+ def play(pet, name, play_type):
7
+ """Uses user input to select which type of play, increases happiness accordingly"""
8
+
9
+ # Invalid arguments
10
+ if play_type is None or not isinstance(play_type, str):
11
+ return "\033[1mThat is not a valid play type.\033[0m"
12
+
13
+ # Valid arguments
14
+ if play_type == "1": # Pet
15
+ before = pet["happiness"]
16
+ increase_happiness(pet, 15)
17
+ drain_energy(pet, 5)
18
+ record_event(
19
+ pet,
20
+ "play",
21
+ detail="pet",
22
+ stat="happiness",
23
+ before=before,
24
+ after=pet["happiness"],
25
+ )
26
+ return f"\033[1m*You pet {name}, they love it!*\033[0m"
27
+
28
+ elif play_type == "2": # Catch
29
+ before = pet["happiness"]
30
+ increase_happiness(pet, 10)
31
+ drain_energy(pet, 10)
32
+ record_event(
33
+ pet,
34
+ "play",
35
+ detail="catch",
36
+ stat="happiness",
37
+ before=before,
38
+ after=pet["happiness"],
39
+ )
40
+ return f"\033[1m*You throw a ball to {name}, they catch it and happily bring it back to you!*\033[0m"
41
+
42
+ elif play_type == "3": # Walk
43
+ before = pet["happiness"]
44
+ increase_happiness(pet, 5)
45
+ drain_energy(pet, 15)
46
+ record_event(
47
+ pet,
48
+ "play",
49
+ detail="walk",
50
+ stat="happiness",
51
+ before=before,
52
+ after=pet["happiness"],
53
+ )
54
+ return f"\033[1m*You take {name} on a walk, they smile the entire time!*\033[0m"
55
+
56
+ else:
57
+ return "\033[1mThat is not a valid play type.\033[0m"
58
+
59
+
60
+ def increase_happiness(pet, happiness_change):
61
+ """Increment pet happiness by specified amount."""
62
+ check_pet(pet, "happiness")
63
+ check_count(happiness_change, "happiness_change")
64
+ pet["happiness"] = clamp(pet["happiness"] + happiness_change)
65
+ return pet["happiness"]
66
+
67
+
68
+ def decrease_happiness(pet):
69
+ """Decrement pet happiness by 10 for every non-play interaction (feed, clean, rest)."""
70
+ check_pet(pet, "happiness")
71
+ pet["happiness"] = clamp(pet["happiness"] - 10)
72
+ return pet["happiness"]
package/health.py ADDED
@@ -0,0 +1,59 @@
1
+ from .history import record_event
2
+ from .pet import MAX_STAT, STATS, check_count, check_pet, clamp
3
+
4
+ HEALTHY_STAT = 60 # baseline health stat level
5
+ HEALTHY_HUNGER = 40 # hunger at or below this is healthy
6
+ SICK_WELLNESS = 50 # wellness below this starts hurting health
7
+ DAMAGE_DIVISOR = 5 # every 5 wellness points below SICK_WELLNESS costs 1 health
8
+ REGEN_PER_TICK = 2
9
+ DEATH_HEALTH = 10 # pet dies at or below this
10
+
11
+
12
+ def _check_full_pet(pet):
13
+ for stat in STATS:
14
+ check_pet(pet, stat)
15
+
16
+
17
+ def _all_healthy(pet):
18
+ return pet["hunger"] <= HEALTHY_HUNGER and all(
19
+ pet[stat] >= HEALTHY_STAT for stat in ("energy", "hygiene", "happiness")
20
+ )
21
+
22
+
23
+ def wellness(pet):
24
+ """Average of hunger, energy, hygiene and happiness, from 0 to 100."""
25
+ _check_full_pet(pet)
26
+ scores = [
27
+ MAX_STAT - pet["hunger"],
28
+ pet["energy"],
29
+ pet["hygiene"],
30
+ pet["happiness"],
31
+ ]
32
+ return sum(scores) / len(scores)
33
+
34
+
35
+ def update_health(pet, ticks=1):
36
+ """Apply `ticks` rounds of health change and return the new health."""
37
+ _check_full_pet(pet)
38
+ check_count(ticks, "ticks")
39
+
40
+ for _ in range(ticks):
41
+ if not pet.get("alive", True):
42
+ break
43
+ if _all_healthy(pet):
44
+ change = REGEN_PER_TICK
45
+ else:
46
+ change = -int(max(SICK_WELLNESS - wellness(pet), 0) // DAMAGE_DIVISOR)
47
+ before = pet["health"]
48
+ pet["health"] = clamp(pet["health"] + change)
49
+ if pet["health"] != before:
50
+ record_event(
51
+ pet,
52
+ "stat",
53
+ stat="health",
54
+ before=before,
55
+ after=pet["health"],
56
+ )
57
+ if pet["health"] <= DEATH_HEALTH:
58
+ pet["alive"] = False
59
+ return pet["health"]
package/history.py ADDED
@@ -0,0 +1,60 @@
1
+ """Record what happened to a pet and read those events back."""
2
+
3
+ from .pet import MAX_STAT, MIN_STAT, STATS, check_count
4
+
5
+ ACTIONS = ("feed", "play", "clean", "stat")
6
+
7
+
8
+ def _check_bound(value, name):
9
+ """Raise unless value is an int stat, or None."""
10
+ if value is None:
11
+ return
12
+ if isinstance(value, bool) or not isinstance(value, int):
13
+ raise TypeError(f"{name} must be an int")
14
+ if value < MIN_STAT or value > MAX_STAT:
15
+ raise ValueError(f"{name} must be between {MIN_STAT} and {MAX_STAT}")
16
+
17
+
18
+ def record_event(pet, action, detail=None, stat=None, before=None, after=None):
19
+ """Append one event and return a copy of it."""
20
+ if not isinstance(pet, dict):
21
+ raise TypeError("pet must be a dict")
22
+ if action not in ACTIONS:
23
+ raise ValueError("action must be feed, play, clean, or stat")
24
+ if detail is not None and not isinstance(detail, str):
25
+ raise TypeError("detail must be a string")
26
+ if stat is not None and stat not in STATS:
27
+ raise ValueError("stat must be energy, hunger, hygiene, happiness, or health")
28
+ if action == "stat" and stat is None:
29
+ raise ValueError("stat is required when action is 'stat'")
30
+ _check_bound(before, "before")
31
+ _check_bound(after, "after")
32
+ if (before is None) != (after is None):
33
+ raise ValueError("before and after must both be set or both be omitted")
34
+
35
+ event = {
36
+ "action": action,
37
+ "detail": detail,
38
+ "stat": stat,
39
+ "before": before,
40
+ "after": after,
41
+ }
42
+ history = pet.setdefault("history", [])
43
+ if not isinstance(history, list):
44
+ raise TypeError("history must be a list")
45
+ history.append(event)
46
+ return dict(event)
47
+
48
+
49
+ def get_history(pet, limit):
50
+ """Return the most recent events, oldest of that slice first."""
51
+ if not isinstance(pet, dict):
52
+ raise TypeError("pet must be a dict")
53
+ check_count(limit, "limit")
54
+
55
+ history = pet.get("history", [])
56
+ if not isinstance(history, list):
57
+ raise TypeError("history must be a list")
58
+ if limit == 0:
59
+ return []
60
+ return [dict(event) for event in history[-limit:]]
package/hunger.py ADDED
@@ -0,0 +1,25 @@
1
+ """Update the pet's hunger based on inputs since it was fed."""
2
+
3
+ from .history import record_event
4
+ from .pet import MIN_STAT, check_count, check_pet, clamp
5
+
6
+ HUNGER_PER_INPUT = 5
7
+
8
+
9
+ def feed(pet):
10
+ """Set hunger to zero and record a feed event."""
11
+ check_pet(pet, "hunger")
12
+ before = pet["hunger"]
13
+ pet["hunger"] = MIN_STAT
14
+ record_event(pet, "feed", stat="hunger", before=before, after=pet["hunger"])
15
+ return pet["hunger"]
16
+
17
+
18
+ def update_hunger(pet, inputs_since_fed):
19
+ """Raise hunger as inputs occur after the pet was fed."""
20
+ check_pet(pet, "hunger")
21
+ check_count(inputs_since_fed, "inputs_since_fed")
22
+
23
+ before = pet["hunger"]
24
+ pet["hunger"] = clamp(inputs_since_fed * HUNGER_PER_INPUT)
25
+ return pet["hunger"]
package/hygiene.py ADDED
@@ -0,0 +1,27 @@
1
+ """Update the pet's hygiene based on commits behind origin."""
2
+
3
+ from .history import record_event
4
+ from .pet import MAX_STAT, check_count, check_pet, clamp
5
+
6
+ HYGIENE_LOSS_PER_COMMIT_BEHIND = 10
7
+
8
+
9
+ def clean(pet):
10
+ """Set hygiene to full and record a clean event."""
11
+ check_pet(pet, "hygiene")
12
+ before = pet["hygiene"]
13
+ pet["hygiene"] = MAX_STAT
14
+ record_event(pet, "clean", stat="hygiene", before=before, after=pet["hygiene"])
15
+ return pet["hygiene"]
16
+
17
+
18
+ def update_hygiene(pet, commits_behind):
19
+ """Lower hygiene as the number of commits behind origin increases."""
20
+ check_pet(pet, "hygiene")
21
+ check_count(commits_behind, "commits_behind")
22
+
23
+ before = pet["hygiene"]
24
+ pet["hygiene"] = clamp(
25
+ MAX_STAT - commits_behind * HYGIENE_LOSS_PER_COMMIT_BEHIND
26
+ )
27
+ return pet["hygiene"]