termpet-pythonists 0.1.0__tar.gz → 0.1.1__tar.gz

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.
Files changed (30) hide show
  1. termpet_pythonists-0.1.1/PKG-INFO +292 -0
  2. termpet_pythonists-0.1.1/README.md +280 -0
  3. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/pyproject.toml +3 -2
  4. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/__init__.py +2 -0
  5. termpet_pythonists-0.1.1/src/termpet_pythonists.egg-info/PKG-INFO +292 -0
  6. termpet_pythonists-0.1.0/PKG-INFO +0 -14
  7. termpet_pythonists-0.1.0/README.md +0 -2
  8. termpet_pythonists-0.1.0/src/termpet_pythonists.egg-info/PKG-INFO +0 -14
  9. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/LICENSE +0 -0
  10. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/setup.cfg +0 -0
  11. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/constants.py +0 -0
  12. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/feed.py +0 -0
  13. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/pet.py +0 -0
  14. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/play.py +0 -0
  15. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/render.py +0 -0
  16. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/sleep.py +0 -0
  17. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/status.py +0 -0
  18. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/storage.py +0 -0
  19. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet/tick.py +0 -0
  20. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet_pythonists.egg-info/SOURCES.txt +0 -0
  21. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet_pythonists.egg-info/dependency_links.txt +0 -0
  22. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/src/termpet_pythonists.egg-info/top_level.txt +0 -0
  23. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_feed.py +0 -0
  24. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_pet.py +0 -0
  25. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_play.py +0 -0
  26. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_render.py +0 -0
  27. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_sleep.py +0 -0
  28. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_status.py +0 -0
  29. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_storage.py +0 -0
  30. {termpet_pythonists-0.1.0 → termpet_pythonists-0.1.1}/tests/test_tick.py +0 -0
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: termpet-pythonists
3
+ Version: 0.1.1
4
+ Summary: A playful terminal-pet Python package.
5
+ Author: Ray Yang, Ryan Lin, Jolie Leong, Layan Alyas, Hoang Ngo
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/software-students-fall2026/python-package-exercise-pythonists
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: license-file
12
+
13
+ # termpet
14
+
15
+ [![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml)
16
+
17
+ A small virtual pet that lives in your terminal. Adopt a pet, feed it, play with it, let time pass, put it to sleep, and save it to a file so it's still there next time. Its mood changes with how well you look after it, and you can draw it as ASCII art.
18
+
19
+ **PyPI:** [termpet-pythonists](https://pypi.org/project/termpet-pythonists/)
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install termpet-pythonists
25
+ ```
26
+
27
+ The package installs as `termpet-pythonists` but you import it as `termpet`:
28
+
29
+ ```python
30
+ import termpet
31
+ ```
32
+
33
+ Requires Python 3.12 or newer.
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from termpet import Pet, feed, play, tick, sleep, mood, render, status, save, load
39
+
40
+ pet = Pet(name="Mochi", species="cat")
41
+ feed(pet, "steak")
42
+ tick(pet, 4)
43
+ sleep(pet, 2)
44
+ print(render(pet))
45
+ save(pet, "mochi.json")
46
+ ```
47
+
48
+ Each action function updates the pet you pass in and returns the same pet, so you can call them on their own or chain the result.
49
+
50
+ ## Pet stats
51
+
52
+ Every pet has three stats, each from 0 to 100. All start at 50.
53
+
54
+ | Stat | 0 means | 100 means |
55
+ |---|---|---|
56
+ | `hunger` | starving | full |
57
+ | `energy` | exhausted | fully rested |
58
+ | `happiness` | miserable | ecstatic |
59
+
60
+ Stats are kept between 0 and 100 automatically; actions never push them outside that range.
61
+
62
+ ## Functions
63
+
64
+ ### `Pet(name, species, hunger=50, energy=50, happiness=50)`
65
+
66
+ Creates a pet. `name` and `species` are required and can be any non-blank text. The three stats are optional, which is useful for restoring a pet with known values.
67
+
68
+ ```python
69
+ pet = Pet(name="Mochi", species="cat")
70
+ tired = Pet(name="Biscuit", species="dog", energy=10)
71
+ ```
72
+
73
+ Raises:
74
+ - `TypeError` if `name` or `species` isn't a string, or a stat isn't a number.
75
+ - `ValueError` if `name` or `species` is blank, or a stat is outside 0–100.
76
+
77
+ ### `mood(pet)`
78
+
79
+ Returns the pet's mood as a string, based on the average of its three stats.
80
+
81
+ | Average stat | Mood |
82
+ |---|---|
83
+ | 75 or higher | `"ecstatic"` |
84
+ | 50 to 74 | `"content"` |
85
+ | 25 to 49 | `"grumpy"` |
86
+ | below 25 | `"feral"` |
87
+
88
+ ```python
89
+ >>> mood(Pet(name="Mochi", species="cat"))
90
+ 'content'
91
+ ```
92
+
93
+ Raises `TypeError` if `pet` isn't a `Pet`.
94
+
95
+ ### `feed(pet, food)`
96
+
97
+ Feeds the pet and returns it. Food names ignore case and surrounding spaces.
98
+
99
+ | Food | Hunger | Energy | Happiness |
100
+ |---|---|---|---|
101
+ | `apple` | +10 | +5 | +2 |
102
+ | `kibble` | +20 | 0 | +1 |
103
+ | `steak` | +35 | +5 | +10 |
104
+ | `treat` | +5 | +15 | +15 |
105
+ | `veggies` | +15 | 0 | 0 |
106
+
107
+ ```python
108
+ feed(pet, "treat")
109
+ feed(pet, " Steak ") # same as "steak"
110
+ ```
111
+
112
+ Raises:
113
+ - `TypeError` if `pet` isn't a `Pet` or `food` isn't a string.
114
+ - `ValueError` if `food` isn't one of the foods above.
115
+
116
+ ### `play(pet, game, minutes=10)`
117
+
118
+ Plays a game with the pet for `minutes` minutes and returns it. Each game changes the pet's stats by a set amount per minute, so longer play has a bigger effect. Game names ignore case and surrounding spaces.
119
+
120
+ | Game | Hunger / min | Energy / min | Happiness / min |
121
+ |---|---|---|---|
122
+ | `fetch` | −1 | −1 | +2 |
123
+ | `hide & seek` | −0.5 | −0.5 | +1.5 |
124
+ | `tug of war` | −1.5 | −1.5 | +2.5 |
125
+ | `chase` | −2 | −2 | +3 |
126
+
127
+ `minutes` can be a whole or decimal number, greater than 0 and at most MAX_PLAY_MINUTES (60), defaulting at 10 minutes.
128
+
129
+ ```python
130
+ pet = Pet(name="Mochi", species="cat") # all stats start at 50
131
+ play(pet, "fetch") # 10 minutes default
132
+ print(pet.hunger, pet.energy, pet.happiness) # 40 40 70
133
+
134
+ play(pet, " Hide & Seek ", 5) # same as "hide & seek"
135
+ ```
136
+
137
+ Raises:
138
+ - `TypeError` if `pet` isn't a `Pet`, `game` isn't a string, or `minutes` isn't a number.
139
+ - `ValueError` if `game` isn't one of the games above, or `minutes` is 0 or less, or more than MAX_PLAY_MINUTES.
140
+
141
+ ### `tick(pet, hours)`
142
+
143
+ Lets time pass and returns the pet. Each hour lowers its stats:
144
+
145
+ | Stat | Change per hour |
146
+ |---|---|
147
+ | `hunger` | −4 |
148
+ | `energy` | −2 |
149
+ | `happiness` | −3 |
150
+
151
+ `hours` can be a whole or decimal number, greater than 0 and at most 24.
152
+
153
+ ```python
154
+ tick(pet, 6) # six hours pass
155
+ tick(pet, 0.5) # half an hour passes
156
+ ```
157
+
158
+ Raises:
159
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
160
+ - `ValueError` if `hours` is 0 or less, or more than 24.
161
+
162
+ ### `sleep(pet, hours)`
163
+
164
+ Puts the pet to sleep and returns it. Each hour adds 10 energy and lowers hunger by 2. `hours` must be greater than 0 and at most 24.
165
+
166
+ ```python
167
+ sleep(pet, 8)
168
+ ```
169
+
170
+ Raises:
171
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
172
+ - `ValueError` if `hours` is 0 or less, or more than 24.
173
+
174
+ ### `render(pet, width=10)`
175
+
176
+ Returns the pet as ASCII art, with a face that matches its mood and a bar for each stat. `width` sets how many characters long each bar is.
177
+
178
+ ```python
179
+ >>> print(render(Pet(name="Mochi", species="cat", hunger=80, energy=70, happiness=90)))
180
+ /\_/\
181
+ ( ^.^ ) Mochi the cat — ecstatic
182
+ > ^ <
183
+ Hunger [########--]
184
+ Energy [#######---]
185
+ Happiness [#########-]
186
+ ```
187
+
188
+ Faces by mood: `^.^` ecstatic, `o.o` content, `-.-` grumpy, `>.<` feral.
189
+
190
+ Raises:
191
+ - `TypeError` if `pet` isn't a `Pet` or `width` isn't a whole number.
192
+ - `ValueError` if `width` is less than 1.
193
+
194
+ ### `status(pet, verbose=False)`
195
+
196
+ Returns a text summary of the pet's stats.
197
+
198
+ ```python
199
+ >>> pet = Pet(name="Mochi", species="cat", hunger=30, energy=70, happiness=100)
200
+ >>> print(status(pet))
201
+ Mochi the cat | Hunger 30 | Energy 70 | Happiness 100
202
+
203
+ >>> print(status(pet, verbose=True))
204
+ Mochi the cat
205
+ Hunger ███░░░░░░░ 30
206
+ Energy ███████░░░ 70
207
+ Happiness ██████████ 100
208
+ ```
209
+
210
+ Raises `TypeError` if `pet` isn't a `Pet` or `verbose` isn't `True`/`False`.
211
+
212
+ ### `save(pet, path)` and `load(path)`
213
+
214
+ Save a pet to a JSON file and load it back.
215
+
216
+ ```python
217
+ save(pet, "mochi.json")
218
+ pet = load("mochi.json")
219
+ ```
220
+
221
+ `save` overwrites an existing file. `path` can be a string or a `pathlib.Path`.
222
+
223
+ Raises:
224
+ - `TypeError` if `pet` isn't a `Pet` or `path` isn't a string or `Path`.
225
+ - `ValueError` from `save` if `path` is empty.
226
+ - `FileNotFoundError` from `load` if the file doesn't exist.
227
+ - `ValueError` from `load` if the file isn't valid JSON or doesn't describe a valid pet.
228
+
229
+ ## Example program
230
+
231
+ [`examples/play_demo.py`](examples/play_demo.py) is an interactive demo and uses every function in the package.
232
+
233
+ With a fresh clone, after running `pipenv install --dev`:
234
+ ```bash
235
+ pipenv run python examples/play_demo.py
236
+ ```
237
+
238
+
239
+ If you installed the package from PyPI download the file and run:
240
+
241
+ ```bash
242
+ python examples/play_demo.py
243
+ ```
244
+
245
+ ## Contributing
246
+
247
+ ### Setup
248
+
249
+ You need Python 3.12+ and [pipenv](https://pipenv.pypa.io/).
250
+
251
+ ```bash
252
+ git clone https://github.com/software-students-fall2026/python-package-exercise-pythonists.git
253
+ cd python-package-exercise-pythonists
254
+ pipenv install --dev
255
+ ```
256
+
257
+ If `pipenv` isn't installed, run `python -m pip install --user pipenv` (or `brew install pipenv` on macOS). On macOS and Linux, use `python3` wherever these instructions say `python` if `python` isn't found.
258
+
259
+ ### Run the tests
260
+
261
+ ```bash
262
+ pipenv run python -m pytest
263
+ ```
264
+
265
+ ### Build the package
266
+
267
+ ```bash
268
+ pipenv run python -m build
269
+ ```
270
+
271
+ This creates a source distribution and a wheel in `dist/`.
272
+
273
+ ### Workflow
274
+
275
+ 1. Create a feature branch from `main`.
276
+ 2. Make your changes and run the tests.
277
+ 3. Push the branch and open a pull request into `main` on this repository, not the upstream course repository.
278
+ 4. A teammate reviews the code and runs the tests before merging.
279
+
280
+ GitHub Actions runs the tests and builds the package on Python 3.12 and 3.13 for every pull request and push to `main`.
281
+
282
+ ## Configuration
283
+
284
+ termpet needs no environment variables, secret files, or database.
285
+
286
+ ## Team
287
+
288
+ - [Hoang Ngo](https://github.com/ogngnaoh)
289
+ - [Jolie Leong](https://github.com/jl15113)
290
+ - [Layan Alyas](https://github.com/layan-al)
291
+ - [Ray Yang](https://github.com/rayyang262)
292
+ - [Ryan Lin](https://github.com/ryanwlin)
@@ -0,0 +1,280 @@
1
+ # termpet
2
+
3
+ [![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml)
4
+
5
+ A small virtual pet that lives in your terminal. Adopt a pet, feed it, play with it, let time pass, put it to sleep, and save it to a file so it's still there next time. Its mood changes with how well you look after it, and you can draw it as ASCII art.
6
+
7
+ **PyPI:** [termpet-pythonists](https://pypi.org/project/termpet-pythonists/)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install termpet-pythonists
13
+ ```
14
+
15
+ The package installs as `termpet-pythonists` but you import it as `termpet`:
16
+
17
+ ```python
18
+ import termpet
19
+ ```
20
+
21
+ Requires Python 3.12 or newer.
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ from termpet import Pet, feed, play, tick, sleep, mood, render, status, save, load
27
+
28
+ pet = Pet(name="Mochi", species="cat")
29
+ feed(pet, "steak")
30
+ tick(pet, 4)
31
+ sleep(pet, 2)
32
+ print(render(pet))
33
+ save(pet, "mochi.json")
34
+ ```
35
+
36
+ Each action function updates the pet you pass in and returns the same pet, so you can call them on their own or chain the result.
37
+
38
+ ## Pet stats
39
+
40
+ Every pet has three stats, each from 0 to 100. All start at 50.
41
+
42
+ | Stat | 0 means | 100 means |
43
+ |---|---|---|
44
+ | `hunger` | starving | full |
45
+ | `energy` | exhausted | fully rested |
46
+ | `happiness` | miserable | ecstatic |
47
+
48
+ Stats are kept between 0 and 100 automatically; actions never push them outside that range.
49
+
50
+ ## Functions
51
+
52
+ ### `Pet(name, species, hunger=50, energy=50, happiness=50)`
53
+
54
+ Creates a pet. `name` and `species` are required and can be any non-blank text. The three stats are optional, which is useful for restoring a pet with known values.
55
+
56
+ ```python
57
+ pet = Pet(name="Mochi", species="cat")
58
+ tired = Pet(name="Biscuit", species="dog", energy=10)
59
+ ```
60
+
61
+ Raises:
62
+ - `TypeError` if `name` or `species` isn't a string, or a stat isn't a number.
63
+ - `ValueError` if `name` or `species` is blank, or a stat is outside 0–100.
64
+
65
+ ### `mood(pet)`
66
+
67
+ Returns the pet's mood as a string, based on the average of its three stats.
68
+
69
+ | Average stat | Mood |
70
+ |---|---|
71
+ | 75 or higher | `"ecstatic"` |
72
+ | 50 to 74 | `"content"` |
73
+ | 25 to 49 | `"grumpy"` |
74
+ | below 25 | `"feral"` |
75
+
76
+ ```python
77
+ >>> mood(Pet(name="Mochi", species="cat"))
78
+ 'content'
79
+ ```
80
+
81
+ Raises `TypeError` if `pet` isn't a `Pet`.
82
+
83
+ ### `feed(pet, food)`
84
+
85
+ Feeds the pet and returns it. Food names ignore case and surrounding spaces.
86
+
87
+ | Food | Hunger | Energy | Happiness |
88
+ |---|---|---|---|
89
+ | `apple` | +10 | +5 | +2 |
90
+ | `kibble` | +20 | 0 | +1 |
91
+ | `steak` | +35 | +5 | +10 |
92
+ | `treat` | +5 | +15 | +15 |
93
+ | `veggies` | +15 | 0 | 0 |
94
+
95
+ ```python
96
+ feed(pet, "treat")
97
+ feed(pet, " Steak ") # same as "steak"
98
+ ```
99
+
100
+ Raises:
101
+ - `TypeError` if `pet` isn't a `Pet` or `food` isn't a string.
102
+ - `ValueError` if `food` isn't one of the foods above.
103
+
104
+ ### `play(pet, game, minutes=10)`
105
+
106
+ Plays a game with the pet for `minutes` minutes and returns it. Each game changes the pet's stats by a set amount per minute, so longer play has a bigger effect. Game names ignore case and surrounding spaces.
107
+
108
+ | Game | Hunger / min | Energy / min | Happiness / min |
109
+ |---|---|---|---|
110
+ | `fetch` | −1 | −1 | +2 |
111
+ | `hide & seek` | −0.5 | −0.5 | +1.5 |
112
+ | `tug of war` | −1.5 | −1.5 | +2.5 |
113
+ | `chase` | −2 | −2 | +3 |
114
+
115
+ `minutes` can be a whole or decimal number, greater than 0 and at most MAX_PLAY_MINUTES (60), defaulting at 10 minutes.
116
+
117
+ ```python
118
+ pet = Pet(name="Mochi", species="cat") # all stats start at 50
119
+ play(pet, "fetch") # 10 minutes default
120
+ print(pet.hunger, pet.energy, pet.happiness) # 40 40 70
121
+
122
+ play(pet, " Hide & Seek ", 5) # same as "hide & seek"
123
+ ```
124
+
125
+ Raises:
126
+ - `TypeError` if `pet` isn't a `Pet`, `game` isn't a string, or `minutes` isn't a number.
127
+ - `ValueError` if `game` isn't one of the games above, or `minutes` is 0 or less, or more than MAX_PLAY_MINUTES.
128
+
129
+ ### `tick(pet, hours)`
130
+
131
+ Lets time pass and returns the pet. Each hour lowers its stats:
132
+
133
+ | Stat | Change per hour |
134
+ |---|---|
135
+ | `hunger` | −4 |
136
+ | `energy` | −2 |
137
+ | `happiness` | −3 |
138
+
139
+ `hours` can be a whole or decimal number, greater than 0 and at most 24.
140
+
141
+ ```python
142
+ tick(pet, 6) # six hours pass
143
+ tick(pet, 0.5) # half an hour passes
144
+ ```
145
+
146
+ Raises:
147
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
148
+ - `ValueError` if `hours` is 0 or less, or more than 24.
149
+
150
+ ### `sleep(pet, hours)`
151
+
152
+ Puts the pet to sleep and returns it. Each hour adds 10 energy and lowers hunger by 2. `hours` must be greater than 0 and at most 24.
153
+
154
+ ```python
155
+ sleep(pet, 8)
156
+ ```
157
+
158
+ Raises:
159
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
160
+ - `ValueError` if `hours` is 0 or less, or more than 24.
161
+
162
+ ### `render(pet, width=10)`
163
+
164
+ Returns the pet as ASCII art, with a face that matches its mood and a bar for each stat. `width` sets how many characters long each bar is.
165
+
166
+ ```python
167
+ >>> print(render(Pet(name="Mochi", species="cat", hunger=80, energy=70, happiness=90)))
168
+ /\_/\
169
+ ( ^.^ ) Mochi the cat — ecstatic
170
+ > ^ <
171
+ Hunger [########--]
172
+ Energy [#######---]
173
+ Happiness [#########-]
174
+ ```
175
+
176
+ Faces by mood: `^.^` ecstatic, `o.o` content, `-.-` grumpy, `>.<` feral.
177
+
178
+ Raises:
179
+ - `TypeError` if `pet` isn't a `Pet` or `width` isn't a whole number.
180
+ - `ValueError` if `width` is less than 1.
181
+
182
+ ### `status(pet, verbose=False)`
183
+
184
+ Returns a text summary of the pet's stats.
185
+
186
+ ```python
187
+ >>> pet = Pet(name="Mochi", species="cat", hunger=30, energy=70, happiness=100)
188
+ >>> print(status(pet))
189
+ Mochi the cat | Hunger 30 | Energy 70 | Happiness 100
190
+
191
+ >>> print(status(pet, verbose=True))
192
+ Mochi the cat
193
+ Hunger ███░░░░░░░ 30
194
+ Energy ███████░░░ 70
195
+ Happiness ██████████ 100
196
+ ```
197
+
198
+ Raises `TypeError` if `pet` isn't a `Pet` or `verbose` isn't `True`/`False`.
199
+
200
+ ### `save(pet, path)` and `load(path)`
201
+
202
+ Save a pet to a JSON file and load it back.
203
+
204
+ ```python
205
+ save(pet, "mochi.json")
206
+ pet = load("mochi.json")
207
+ ```
208
+
209
+ `save` overwrites an existing file. `path` can be a string or a `pathlib.Path`.
210
+
211
+ Raises:
212
+ - `TypeError` if `pet` isn't a `Pet` or `path` isn't a string or `Path`.
213
+ - `ValueError` from `save` if `path` is empty.
214
+ - `FileNotFoundError` from `load` if the file doesn't exist.
215
+ - `ValueError` from `load` if the file isn't valid JSON or doesn't describe a valid pet.
216
+
217
+ ## Example program
218
+
219
+ [`examples/play_demo.py`](examples/play_demo.py) is an interactive demo and uses every function in the package.
220
+
221
+ With a fresh clone, after running `pipenv install --dev`:
222
+ ```bash
223
+ pipenv run python examples/play_demo.py
224
+ ```
225
+
226
+
227
+ If you installed the package from PyPI download the file and run:
228
+
229
+ ```bash
230
+ python examples/play_demo.py
231
+ ```
232
+
233
+ ## Contributing
234
+
235
+ ### Setup
236
+
237
+ You need Python 3.12+ and [pipenv](https://pipenv.pypa.io/).
238
+
239
+ ```bash
240
+ git clone https://github.com/software-students-fall2026/python-package-exercise-pythonists.git
241
+ cd python-package-exercise-pythonists
242
+ pipenv install --dev
243
+ ```
244
+
245
+ If `pipenv` isn't installed, run `python -m pip install --user pipenv` (or `brew install pipenv` on macOS). On macOS and Linux, use `python3` wherever these instructions say `python` if `python` isn't found.
246
+
247
+ ### Run the tests
248
+
249
+ ```bash
250
+ pipenv run python -m pytest
251
+ ```
252
+
253
+ ### Build the package
254
+
255
+ ```bash
256
+ pipenv run python -m build
257
+ ```
258
+
259
+ This creates a source distribution and a wheel in `dist/`.
260
+
261
+ ### Workflow
262
+
263
+ 1. Create a feature branch from `main`.
264
+ 2. Make your changes and run the tests.
265
+ 3. Push the branch and open a pull request into `main` on this repository, not the upstream course repository.
266
+ 4. A teammate reviews the code and runs the tests before merging.
267
+
268
+ GitHub Actions runs the tests and builds the package on Python 3.12 and 3.13 for every pull request and push to `main`.
269
+
270
+ ## Configuration
271
+
272
+ termpet needs no environment variables, secret files, or database.
273
+
274
+ ## Team
275
+
276
+ - [Hoang Ngo](https://github.com/ogngnaoh)
277
+ - [Jolie Leong](https://github.com/jl15113)
278
+ - [Layan Alyas](https://github.com/layan-al)
279
+ - [Ray Yang](https://github.com/rayyang262)
280
+ - [Ryan Lin](https://github.com/ryanwlin)
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "termpet-pythonists"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "A playful terminal-pet Python package."
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.12"
11
11
  authors = [
12
12
  { name = "Ray Yang" },
13
13
  { name = "Ryan Lin" },
14
14
  { name = "Jolie Leong" },
15
15
  { name = "Layan Alyas" },
16
+ {name = "Hoang Ngo" }
16
17
  ]
17
18
  license = "GPL-3.0-or-later"
18
19
  license-files = ["LICENSE"]
@@ -7,6 +7,7 @@ from .storage import save, load
7
7
  from .status import status
8
8
  from .play import play
9
9
  from .tick import tick
10
+ from .render import render
10
11
 
11
12
  __all__ = [
12
13
  "Pet",
@@ -18,4 +19,5 @@ __all__ = [
18
19
  "status",
19
20
  "play",
20
21
  "tick",
22
+ "render",
21
23
  ]
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: termpet-pythonists
3
+ Version: 0.1.1
4
+ Summary: A playful terminal-pet Python package.
5
+ Author: Ray Yang, Ryan Lin, Jolie Leong, Layan Alyas, Hoang Ngo
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/software-students-fall2026/python-package-exercise-pythonists
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: license-file
12
+
13
+ # termpet
14
+
15
+ [![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml)
16
+
17
+ A small virtual pet that lives in your terminal. Adopt a pet, feed it, play with it, let time pass, put it to sleep, and save it to a file so it's still there next time. Its mood changes with how well you look after it, and you can draw it as ASCII art.
18
+
19
+ **PyPI:** [termpet-pythonists](https://pypi.org/project/termpet-pythonists/)
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install termpet-pythonists
25
+ ```
26
+
27
+ The package installs as `termpet-pythonists` but you import it as `termpet`:
28
+
29
+ ```python
30
+ import termpet
31
+ ```
32
+
33
+ Requires Python 3.12 or newer.
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from termpet import Pet, feed, play, tick, sleep, mood, render, status, save, load
39
+
40
+ pet = Pet(name="Mochi", species="cat")
41
+ feed(pet, "steak")
42
+ tick(pet, 4)
43
+ sleep(pet, 2)
44
+ print(render(pet))
45
+ save(pet, "mochi.json")
46
+ ```
47
+
48
+ Each action function updates the pet you pass in and returns the same pet, so you can call them on their own or chain the result.
49
+
50
+ ## Pet stats
51
+
52
+ Every pet has three stats, each from 0 to 100. All start at 50.
53
+
54
+ | Stat | 0 means | 100 means |
55
+ |---|---|---|
56
+ | `hunger` | starving | full |
57
+ | `energy` | exhausted | fully rested |
58
+ | `happiness` | miserable | ecstatic |
59
+
60
+ Stats are kept between 0 and 100 automatically; actions never push them outside that range.
61
+
62
+ ## Functions
63
+
64
+ ### `Pet(name, species, hunger=50, energy=50, happiness=50)`
65
+
66
+ Creates a pet. `name` and `species` are required and can be any non-blank text. The three stats are optional, which is useful for restoring a pet with known values.
67
+
68
+ ```python
69
+ pet = Pet(name="Mochi", species="cat")
70
+ tired = Pet(name="Biscuit", species="dog", energy=10)
71
+ ```
72
+
73
+ Raises:
74
+ - `TypeError` if `name` or `species` isn't a string, or a stat isn't a number.
75
+ - `ValueError` if `name` or `species` is blank, or a stat is outside 0–100.
76
+
77
+ ### `mood(pet)`
78
+
79
+ Returns the pet's mood as a string, based on the average of its three stats.
80
+
81
+ | Average stat | Mood |
82
+ |---|---|
83
+ | 75 or higher | `"ecstatic"` |
84
+ | 50 to 74 | `"content"` |
85
+ | 25 to 49 | `"grumpy"` |
86
+ | below 25 | `"feral"` |
87
+
88
+ ```python
89
+ >>> mood(Pet(name="Mochi", species="cat"))
90
+ 'content'
91
+ ```
92
+
93
+ Raises `TypeError` if `pet` isn't a `Pet`.
94
+
95
+ ### `feed(pet, food)`
96
+
97
+ Feeds the pet and returns it. Food names ignore case and surrounding spaces.
98
+
99
+ | Food | Hunger | Energy | Happiness |
100
+ |---|---|---|---|
101
+ | `apple` | +10 | +5 | +2 |
102
+ | `kibble` | +20 | 0 | +1 |
103
+ | `steak` | +35 | +5 | +10 |
104
+ | `treat` | +5 | +15 | +15 |
105
+ | `veggies` | +15 | 0 | 0 |
106
+
107
+ ```python
108
+ feed(pet, "treat")
109
+ feed(pet, " Steak ") # same as "steak"
110
+ ```
111
+
112
+ Raises:
113
+ - `TypeError` if `pet` isn't a `Pet` or `food` isn't a string.
114
+ - `ValueError` if `food` isn't one of the foods above.
115
+
116
+ ### `play(pet, game, minutes=10)`
117
+
118
+ Plays a game with the pet for `minutes` minutes and returns it. Each game changes the pet's stats by a set amount per minute, so longer play has a bigger effect. Game names ignore case and surrounding spaces.
119
+
120
+ | Game | Hunger / min | Energy / min | Happiness / min |
121
+ |---|---|---|---|
122
+ | `fetch` | −1 | −1 | +2 |
123
+ | `hide & seek` | −0.5 | −0.5 | +1.5 |
124
+ | `tug of war` | −1.5 | −1.5 | +2.5 |
125
+ | `chase` | −2 | −2 | +3 |
126
+
127
+ `minutes` can be a whole or decimal number, greater than 0 and at most MAX_PLAY_MINUTES (60), defaulting at 10 minutes.
128
+
129
+ ```python
130
+ pet = Pet(name="Mochi", species="cat") # all stats start at 50
131
+ play(pet, "fetch") # 10 minutes default
132
+ print(pet.hunger, pet.energy, pet.happiness) # 40 40 70
133
+
134
+ play(pet, " Hide & Seek ", 5) # same as "hide & seek"
135
+ ```
136
+
137
+ Raises:
138
+ - `TypeError` if `pet` isn't a `Pet`, `game` isn't a string, or `minutes` isn't a number.
139
+ - `ValueError` if `game` isn't one of the games above, or `minutes` is 0 or less, or more than MAX_PLAY_MINUTES.
140
+
141
+ ### `tick(pet, hours)`
142
+
143
+ Lets time pass and returns the pet. Each hour lowers its stats:
144
+
145
+ | Stat | Change per hour |
146
+ |---|---|
147
+ | `hunger` | −4 |
148
+ | `energy` | −2 |
149
+ | `happiness` | −3 |
150
+
151
+ `hours` can be a whole or decimal number, greater than 0 and at most 24.
152
+
153
+ ```python
154
+ tick(pet, 6) # six hours pass
155
+ tick(pet, 0.5) # half an hour passes
156
+ ```
157
+
158
+ Raises:
159
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
160
+ - `ValueError` if `hours` is 0 or less, or more than 24.
161
+
162
+ ### `sleep(pet, hours)`
163
+
164
+ Puts the pet to sleep and returns it. Each hour adds 10 energy and lowers hunger by 2. `hours` must be greater than 0 and at most 24.
165
+
166
+ ```python
167
+ sleep(pet, 8)
168
+ ```
169
+
170
+ Raises:
171
+ - `TypeError` if `pet` isn't a `Pet` or `hours` isn't a number.
172
+ - `ValueError` if `hours` is 0 or less, or more than 24.
173
+
174
+ ### `render(pet, width=10)`
175
+
176
+ Returns the pet as ASCII art, with a face that matches its mood and a bar for each stat. `width` sets how many characters long each bar is.
177
+
178
+ ```python
179
+ >>> print(render(Pet(name="Mochi", species="cat", hunger=80, energy=70, happiness=90)))
180
+ /\_/\
181
+ ( ^.^ ) Mochi the cat — ecstatic
182
+ > ^ <
183
+ Hunger [########--]
184
+ Energy [#######---]
185
+ Happiness [#########-]
186
+ ```
187
+
188
+ Faces by mood: `^.^` ecstatic, `o.o` content, `-.-` grumpy, `>.<` feral.
189
+
190
+ Raises:
191
+ - `TypeError` if `pet` isn't a `Pet` or `width` isn't a whole number.
192
+ - `ValueError` if `width` is less than 1.
193
+
194
+ ### `status(pet, verbose=False)`
195
+
196
+ Returns a text summary of the pet's stats.
197
+
198
+ ```python
199
+ >>> pet = Pet(name="Mochi", species="cat", hunger=30, energy=70, happiness=100)
200
+ >>> print(status(pet))
201
+ Mochi the cat | Hunger 30 | Energy 70 | Happiness 100
202
+
203
+ >>> print(status(pet, verbose=True))
204
+ Mochi the cat
205
+ Hunger ███░░░░░░░ 30
206
+ Energy ███████░░░ 70
207
+ Happiness ██████████ 100
208
+ ```
209
+
210
+ Raises `TypeError` if `pet` isn't a `Pet` or `verbose` isn't `True`/`False`.
211
+
212
+ ### `save(pet, path)` and `load(path)`
213
+
214
+ Save a pet to a JSON file and load it back.
215
+
216
+ ```python
217
+ save(pet, "mochi.json")
218
+ pet = load("mochi.json")
219
+ ```
220
+
221
+ `save` overwrites an existing file. `path` can be a string or a `pathlib.Path`.
222
+
223
+ Raises:
224
+ - `TypeError` if `pet` isn't a `Pet` or `path` isn't a string or `Path`.
225
+ - `ValueError` from `save` if `path` is empty.
226
+ - `FileNotFoundError` from `load` if the file doesn't exist.
227
+ - `ValueError` from `load` if the file isn't valid JSON or doesn't describe a valid pet.
228
+
229
+ ## Example program
230
+
231
+ [`examples/play_demo.py`](examples/play_demo.py) is an interactive demo and uses every function in the package.
232
+
233
+ With a fresh clone, after running `pipenv install --dev`:
234
+ ```bash
235
+ pipenv run python examples/play_demo.py
236
+ ```
237
+
238
+
239
+ If you installed the package from PyPI download the file and run:
240
+
241
+ ```bash
242
+ python examples/play_demo.py
243
+ ```
244
+
245
+ ## Contributing
246
+
247
+ ### Setup
248
+
249
+ You need Python 3.12+ and [pipenv](https://pipenv.pypa.io/).
250
+
251
+ ```bash
252
+ git clone https://github.com/software-students-fall2026/python-package-exercise-pythonists.git
253
+ cd python-package-exercise-pythonists
254
+ pipenv install --dev
255
+ ```
256
+
257
+ If `pipenv` isn't installed, run `python -m pip install --user pipenv` (or `brew install pipenv` on macOS). On macOS and Linux, use `python3` wherever these instructions say `python` if `python` isn't found.
258
+
259
+ ### Run the tests
260
+
261
+ ```bash
262
+ pipenv run python -m pytest
263
+ ```
264
+
265
+ ### Build the package
266
+
267
+ ```bash
268
+ pipenv run python -m build
269
+ ```
270
+
271
+ This creates a source distribution and a wheel in `dist/`.
272
+
273
+ ### Workflow
274
+
275
+ 1. Create a feature branch from `main`.
276
+ 2. Make your changes and run the tests.
277
+ 3. Push the branch and open a pull request into `main` on this repository, not the upstream course repository.
278
+ 4. A teammate reviews the code and runs the tests before merging.
279
+
280
+ GitHub Actions runs the tests and builds the package on Python 3.12 and 3.13 for every pull request and push to `main`.
281
+
282
+ ## Configuration
283
+
284
+ termpet needs no environment variables, secret files, or database.
285
+
286
+ ## Team
287
+
288
+ - [Hoang Ngo](https://github.com/ogngnaoh)
289
+ - [Jolie Leong](https://github.com/jl15113)
290
+ - [Layan Alyas](https://github.com/layan-al)
291
+ - [Ray Yang](https://github.com/rayyang262)
292
+ - [Ryan Lin](https://github.com/ryanwlin)
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: termpet-pythonists
3
- Version: 0.1.0
4
- Summary: A playful terminal-pet Python package.
5
- Author: Ray Yang, Ryan Lin, Jolie Leong, Layan Alyas
6
- License-Expression: GPL-3.0-or-later
7
- Project-URL: Homepage, https://github.com/software-students-fall2026/python-package-exercise-pythonists
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Dynamic: license-file
12
-
13
- # Python Package Exercise
14
- ![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)
@@ -1,2 +0,0 @@
1
- # Python Package Exercise
2
- ![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: termpet-pythonists
3
- Version: 0.1.0
4
- Summary: A playful terminal-pet Python package.
5
- Author: Ray Yang, Ryan Lin, Jolie Leong, Layan Alyas
6
- License-Expression: GPL-3.0-or-later
7
- Project-URL: Homepage, https://github.com/software-students-fall2026/python-package-exercise-pythonists
8
- Requires-Python: >=3.10
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
- Dynamic: license-file
12
-
13
- # Python Package Exercise
14
- ![CI](https://github.com/software-students-fall2026/python-package-exercise-pythonists/actions/workflows/ci.yml/badge.svg)