#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
The cat module allows to create a Cat or a group of Cats (i.e. a Clowder)
"""
from ..utils import noises
from ..utils import display
from ..utils import facts
import random
import difflib
import math
[docs]
class Cat:
"""
Represents a virtual cat with attributes like name, age, color, mood, hunger, energy, and health.
Parameters
----------
name : str
The name of the cat.
age : int, optional
The age of the cat in years. Default is None.
color : str, optional
Coat color of the cat. Acceptable values are:
'tabby', 'black', 'orange', 'tortoiseshell', and 'tuxedo'.
Fuzzy matching is used to interpret close inputs. Default is None.
mood : int, optional
Mood level on a scale from -10 (grumpy) to 10 (ecstatic). Default is 0.
hunger_level : int, optional
Hunger level of the cat. Higher values indicate greater hunger. Default is 0.
energy : int, optional
Energy level of the cat. Default is 0.
health : int, optional
Health level of the cat. Default is 0.
Attributes
----------
name : str
The name of the cat.
age : int or None
The age of the cat.
color : str or None
The interpreted or validated color of the cat.
mood : int
The cat's mood.
hunger_level : int
The cat's hunger level.
energy : int
The cat's energy level.
health : int
The cat's health level.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
"""
def __init__(self, name, age=None, color=None, mood=0, hunger_level=0,
energy=0, health=0):
self.name = name
self.age = age
possible_colors = ['tabby', 'black', 'orange', 'tortoiseshell', 'tuxedo']
if color:
color_normalized = color.lower().strip()
match = difflib.get_close_matches(color_normalized, possible_colors, n=1, cutoff=0.6)
if match:
self.color = match[0]
print(f"Color '{color}' interpreted as '{self.color}'.")
else:
print(f"Invalid color '{color}'. Valid options are: {', '.join(possible_colors)}.")
self.color = None
self.mood = mood
self.hunger_level = hunger_level
self.energy = energy
self.health = health
[docs]
def give_fact(self):
"""
Gives a random fact about cats
Returns
-------
str
A fact randomly chosen from a pre-defined fact pool
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.give_fact()
"""
return facts.random_facts()
[docs]
def make_noise(self, noise='meow', play=False):
"""
Have the cat make a noise
Parameters
----------
noise : string, optional
The sound the cat makes. Valid options include "meow", "purr", "chirrup", and "hiss". The default is 'meow'.
play : bool, optional
Whether to play the sound (True) or print out the sound (False). The default is False.
Raises
------
ValueError
Raises an error if the sound is not valid
Returns
-------
str
The sound
See also
--------
pyCatSim.utils.noises.meow: Simulates a cat meow
pyCatSim.utils.noises.purr: Simulates a cat purr
pyCatSim.utils.noises.chatter: Simulates a cat chatter
pyCatSim.utils.noises.hiss: Simulates a cat hiss
pyCatSim.utils.noises.chirrup: Simulates a cat chirrup
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.make_noise()
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.make_noise(noise='hiss')
"""
noise_func ={
'meow':noises.meow,
'purr':noises.purr,
'chatter':noises.chatter,
'hiss':noises.hiss,
'chirrup':noises.chirrup}
if noise in noise_func.keys():
return noise_func[noise](play=play)
else:
raise ValueError(f"Invalid noise '{noise}'. Valid options: {', '.join(noise_func.keys())}")
[docs]
def play(self, mood_boost=1, hunger_boost=1, energy_boost=-1):
"""
Simulates playtime with the cat.
Parameters
----------
mood_boost : int, optional
How much mood improves from play. Must be an integer. Default is 1.
hunger_boost : int, optional
How much hunger increases from play. Must be a positive integer. Default is 1.
energy_boost : int, optional
How much energy decreases from play. Must be a negative integer. Default is -1.
Raises
------
TypeError
If any of the arguments are not integers.
ValueError
If hunger_boost is not positive or energy_boost is not negative.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.play()
"""
for arg_name, arg_value in {
"mood_boost": mood_boost,
"hunger_boost": hunger_boost,
"energy_boost": energy_boost
}.items():
if not isinstance(arg_value, int):
raise TypeError(f"{arg_name} must be an integer.")
if hunger_boost <= 0:
raise ValueError("Cats always get hungry when playing! hunger_boost must be positive.")
if energy_boost >= 0:
raise ValueError("Cats always get tired when playing! energy_boost must be negative.")
self.mood += mood_boost
self.hunger_level += hunger_boost
self.energy += energy_boost
[docs]
def bathe(self):
"""
Bathes the cat, decreasing mood and improving health.
Cats typically dislike baths, which lowers their mood by 1,
but it improves their cleanliness and boosts health by 1.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
mochi = cats.Cat(name='Mochi', mood=3, health=5)
mochi.bathe()
print(mochi.mood) # Output: 2
print(mochi.health) # Output: 6
"""
self.mood -= 1
self.health += 1
[docs]
def show(self):
"""
Shows a picture of the cat. If the color is not set, shows a random cat.
Returns
-------
None.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
mochi = cats.Cat(name='Mochi', color = 'black')
mochi.show()
"""
possible_colors = ['tabby', 'black', 'orange', 'tortoiseshell', 'tuxedo']
try:
display.show(self.color)
except:
color = random.choice(possible_colors)
display.show(color)
[docs]
def groom(self):
"""
Grooms a cat, increasing its health and mood levels by one unit.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name = 'Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.groom()
"""
self.mood += 1
self.health += 1
#print(f"{self.name} has been groomed. Health: {self.health}, Mood: {self.mood}")
[docs]
def eat(self):
"""
Feeds the cat by reducing its hunger level and improving its mood.
When called:
- Decreases `hunger_level` by 1 (to a minimum of 0).
- Increases `mood` by 1.
Returns
-------
dict
A dictionary containing the updated `hunger_level` and `mood`.
Examples
--------
.. jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', hunger_level=2, mood=0)
nutmeg.eat()
# Output: {'hunger_level': 1, 'mood': 1}
"""
# Prevent hunger_level from going negative
if self.hunger_level > 0:
self.hunger_level -= 1
else:
self.hunger_level = 0
self.mood += 1
# Optionally, return the new state
return {"hunger_level": self.hunger_level, "mood": self.mood}
[docs]
def sleep(self, duration=0):
"""
Simulates the cat getting some sleep.
Sleep() causes the cat to sleep for an optionally-specified duration (hrs; default=0).
For every 3 hours the cat sleeps, its energy level increases increases by 1 (rounded down
to the nearest integer). For example, having the cat sleeping for a duration of 5 hours raises
its energy level by 1.
Parameters
----------
duration : int or float, optional
Number of hours the cat sleeps. Must be an integer or float. The default is 0.
Raises
------
TypeError
If duration is neither an integer nor float.
ValueError
If duration is not positive or is greater than 16.
Examples
--------
..jupyter-execute::
import pyCatSim as cats
nutmeg = cats.Cat(name='Nutmeg', age = 3, color = 'tortoiseshell')
nutmeg.sleep(duration=5)
"""
# Enforce duration type is int or float
if type(duration) != int:
if type(duration) != float:
raise TypeError("duration must be an integer or float")
# Enforce min (0 hrs) and max duration (16 hrs)
if duration < 0:
raise ValueError("Cats cannot sleep for negative hours. User-specified duration must be positive")
if duration > 16:
raise ValueError("Cats should not sleep for more than 16 hours. User-specified duration must be less than 16")
# Cat gains 1 energy level for every 3 hours of sleep (rounded-down; floor())
energy_boost = math.floor(duration/3)
self.energy += energy_boost