Candidate:
Sam Esteves
Assessed by:
Mary Mentor
Python (2023) ~ Grade 1 (Initial)
Splat a Cat
A silly whack-a-mole style PyGameZero game written for my young cousins to play.
Attached files
Filename (click to download) | Size | Uploaded by |
---|
mu_mode.png | 26.2 KB | samesteve |
Markdown code

meow5.wav | 150.9 KB | samesteve |
Markdown code
meow1.wav | 266.8 KB | samesteve |
Markdown code
meow4.wav | 237.3 KB | samesteve |
Markdown code
meow3.wav | 127.4 KB | samesteve |
Markdown code
meow2.wav | 150.6 KB | samesteve |
Markdown code
disco.mp3 | 2.4 MB | samesteve |
Markdown code
cat4.png | 88.2 KB | samesteve |
Markdown code

cat3.png | 70.3 KB | samesteve |
Markdown code

cat2.png | 61.6 KB | samesteve |
Markdown code

cat1.png | 60.5 KB | samesteve |
Markdown code

splat.wav | 36.0 KB | samesteve |
Markdown code
splat.png | 14.1 KB | samesteve |
Markdown code

background6.png | 652.9 KB | samesteve |
Markdown code

background5.png | 526.2 KB | samesteve |
Markdown code

background4.png | 663.0 KB | samesteve |
Markdown code

background2.png | 346.5 KB | samesteve |
Markdown code

background1.png | 487.5 KB | samesteve |
Markdown code

background3.png | 1.1 MB | samesteve |
Markdown code

Status: Closed (results delivered). 93/100 ~ Pass with DISTINCTION (17/03/2022).
Sam Esteves
~ 17 Mar 2022 6:26 p.m.
I have three cousins (5, 7 and 8) who enjoy video games. They're into quick reaction games, silly pictures and funny sound effects. So, I'm going to write them the silliest reaction game I can think of.
I also have other reasons to write this game: my university's Computing Society have a new "teach code" outreach programme. They run courses so non-computing people like me can learn to code. Usually this takes the form of a really dry and (how can I put this delicately?) boring lecture about Python. I'm on a quest to make a fun game so I can learn some first computing concepts by getting my fingers dirty with code.
I've called it Splat a Cat because I'm a dog person. 🐱 🚫 🐕 ❤️
That's it..! Let the mayhem begin.
Sam Esteves
~ 17 Mar 2022 6:30 p.m.
I've just run the "Splat a Cat" idea past my cousins, and they're very excited. It's right up their street. The sillier the better. Mission accepted!
I've also been talking to a buddy in the computing society. He's interested in using my game as an example project for introductory tutorials in Python. This is an extra challenge but one I think I'll enjoy since I'll be forced to explain myself as I learn, so others can follow along with their own version of the game.
I'm going to find some free game resources and upload them here along with the code for the first version of the game. I'll add more refined versions of the game as I become more experienced.
I've been looking around, and I think it'll be easiest to follow the CodeGrades advice and use Mu and PygameZero to make the actual game.
Sam Esteves
~ 17 Mar 2022 8:31 p.m.
(updated: 17 Mar 2022 8:32 p.m.)
I've attached some initial assets to this comment.
Music: Disco Cat by Komiku (public domain).
Sounds: a selection of free-to-use meows downloaded as WAV files from MixKit.
Images: a selection of royalty free cats on a transparent background found via this Google search.
You need to have Mu in PyGame Zero mode, and make sure the music, images and sounds files go in the appropriately named directories opened by the highlighted buttons:
Sam Esteves
~ 17 Mar 2022 8:32 p.m.
Here's the first version of the game.
I read the PygameZero tutorial, which helped me understand the way a basic game works. I asked a load of questions of my CompSci buddy, and then threw together the code copied below. He took a look and suggested improvements, mostly to simplify things a bit (it was quite messy) and encouraged me to write comments so I remember what's going on.
This game is called "Cat Disco". You can see a video of it in action here:
Here's the source code (to be used with the assets uploaded in the previous comment):
"""
Cat disco.
"""
import random
WIDTH = 800
HEIGHT = 600
cats = ["cat1", "cat2", "cat3", "cat4"] # cat sprites.
meows = [sounds.meow1, sounds.meow2, sounds.meow3, sounds.meow4, sounds.meow5] # meows.
current_cats = [] # A list of cats that are currently alive.
music.play("disco") # Play some annoying kitty music.
def draw():
"""
Draws each frame in the game.
"""
screen.fill((255, 255, 255)) # White background.
for cat in current_cats:
cat.draw() # Draw the cat on the screen.
def update():
"""
Update the game state. Randomly add some cats.
"""
if random.randint(1, 100) == 1: # 1 in 100 chance.
cat_sprite = random.choice(cats) # choose a sprite.
meow_sound = random.choice(meows) # choose a meow.
x = random.randint(100, 700) # new x coordinate.
y = random.randint(100, 500) # new y coordinate.
new_cat = Actor(cat_sprite, pos=(x, y)) # make the cat.
current_cats.append(new_cat) # Add to list of cats.
meow_sound.play() # play the meow.
As you'll see, there are copious comments that help the other beginners in our coding tutorial session. When we met for our tutorial we were able to use this code as the focus of a show and tell. We discussed four broad areas as we reviewed the code:
- Python lists used to store game state.
- The
random
module and a conditional to trigger cats of various combinations of image and sound. - Event handlers such as the
draw
andupdate
functions. - Core PGZero concepts such as the
Actor
representing something that can be drawn.
I presented and explained things and my CompSci buddy running the tutorial clarified things. This was a LOT of work and my brain hurts. BUT IT WORKS!!!
The other folks in the tutorial took my code and were encouraged to mess around with it. For instance, a physics student downloaded some pictures of Galileo, Newton, Descartes and so on and was able to modify her code to be a science disco (despite the remaining cat sound effects).
I've yet to show my young cousins.
Sam Esteves
~ 17 Mar 2022 8:34 p.m.
Another week, another feline adventure, but it feels good.
I've attached two further (splat related) assets: a wav file and a png file to go in the "Sounds" and "Images" folders via Mu.
This week's refinement was to add interactions with the cats appearing in the disco. Because I'm shooting for something cartoon-y and goofy, when you click on the cat it splats (the origin of the name of this project).
I showed this to my cousins. To say their reaction was enthusiastic is an understatement. However, feedback collected after they finally calmed down was that the game got a bit boring after a while and needed extra challenges to make it more engaging than just splatting a bunch of cats (fun though that was).
Here's a video of the new version of the game.
(No actual cats were harmed in the making of this video.)
The modified code (including commentary) now looks like this:
"""
Cat splat.
"""
import random
WIDTH = 800
HEIGHT = 600
cats = ["cat1", "cat2", "cat3", "cat4"] # cat sprites.
meows = [sounds.meow1, sounds.meow2, sounds.meow3, sounds.meow4, sounds.meow5] # meows.
current_cats = [] # A list of cats that are currently alive.
dead_cats = [] # A list of all the dead cats.
music.play("disco") # Play some annoying kitty music.
def draw():
"""
Draws each frame in the game.
"""
screen.fill((255, 255, 255))
for dead_cat in dead_cats:
dead_cat.draw()
for cat in current_cats:
cat.draw()
def update():
"""
Update the game state. Randomly add some cats.
"""
if random.randint(1, 100) == 1: # 1 in 100 chance.
cat_sprite = random.choice(cats) # select a sprite.
meow_sound = random.choice(meows) # select a meow.
x = random.randint(100, 700) # new x coordinate.
y = random.randint(100, 500) # new y coordinate.
new_cat = Actor(cat_sprite, pos=(x, y)) # make the cat.
current_cats.append(new_cat) # Add to list of cats.
meow_sound.play() # play the meow.
def on_mouse_down(pos):
"""
Handle a mouse-click event.
"""
new_dead_cats = [] # Temporary list of cats newly killed.
for cat in current_cats: # Check all alive cats.
if cat.collidepoint(pos): # Hit a cat!
cat.image = "splat" # Cat image is now a splat.
dead_cats.append(cat) # Cat is now dead.
new_dead_cats.append(cat) # Killed cats need further work.
sounds.splat.play() # SPLAT!
for dead_cat in new_dead_cats:
# Newly killed cats need removing from the current cat list.
current_cats.remove(dead_cat)
As you'll see, it's mostly the same as the last version, but with a new event handler: on_mouse_down
. I use this to work out what to do when the user clicks their mouse. It took several attempts and some calls to my CompSci buddy to work out how to track all the cats, splats and so on. But in the end, I think it's quite understandable.
Our coding outreach tutorial was lots of fun.
We had a further deep dive into lists and worked our way through how the various lists worked together to preserve game state. The new on_mouse_down
event handler was an opportunity to describe collision detection (where I check if the current position of the mouse pointer is in the same location as a cat Actor). My CompSci buddy even started to discuss "state transitions" from live to dead cats. That was really interesting to think about and probably a useful way to approach things.
The physics student got very excited: they suggested a Schrödinger's cat version of the game. You move your pointer around the screen and have no idea if the (invisible) cat under the pointer is dead or alive until you click! She's going to try to modify my code to make this happen.
I await next week with a sense of expectation!
Sam Esteves
~ 17 Mar 2022 8:36 p.m.
OK. I think I have a finished minimum viable game for my cousins. I'm interested to see what my mentor makes of this mayhem!?!
I needed to make the game more challenging, so I introduced all sorts of updates:
- The cats rotate.
- The cats move, sometimes in random directions.
- The screen is bigger.
- More probability of cats appearing on the screen.
This was done under "play test" conditions with my cousins, who clearly enjoyed the fact that I was able to customise such things especially for them. Here's a video of this latest version of the game, modified to settings that best match the expectations of my cousins.
Here's the Python code (run this from inside Mu):
"""
Splat-a-cat.
"""
import random
WIDTH = 1024
HEIGHT = 768
cats = ["cat1", "cat2", "cat3", "cat4"] # cat sprites.
meows = [sounds.meow1, sounds.meow2, sounds.meow3, sounds.meow4, sounds.meow5] # meows.
current_cats = [] # A list of cats that are currently alive.
dead_cats = [] # A list of all the dead cats.
music.play("disco") # Play some annoying kitty music.
def draw():
"""
Draws each frame in the game.
"""
screen.fill((255, 255, 255))
for dead_cat in dead_cats:
dead_cat.draw()
for cat in current_cats:
cat.draw()
def update():
"""
Update the game state. Randomly add some cats.
"""
if random.randint(1, 80) == 1: # 1 in 100 chance.
cat_sprite = random.choice(cats) # select a sprite.
meow_sound = random.choice(meows) # select a meow.
x = random.randint(100, 924) # new x coordinate.
y = random.randint(100, 200) # new y coordinate.
new_cat = Actor(cat_sprite, pos=(x, y)) # make the cat.
current_cats.append(new_cat) # Add to list of cats.
meow_sound.play() # play the meow.
for cat in current_cats:
cat.y += 10 # Move the cat down the screen.
cat.angle += 10 # Rotate the cat..!
shuffle = random.randint(1, 3) # 1 in 3 chance of shuffle.
if shuffle == 1:
# Shuffle left.
cat.x -= 20
elif shuffle == 2:
# Shuffle right.
cat.x += 20
# No need to handle shuffle == 3, there is no shuffle!
def on_mouse_down(pos):
"""
Handle a mouse-click event.
"""
new_dead_cats = [] # Temporary list of cats newly killed.
for cat in current_cats: # Check all alive cats.
if cat.collidepoint(pos): # Hit a cat!
cat.image = "splat" # Cat image is now a splat.
dead_cats.append(cat) # Cat is now dead.
new_dead_cats.append(cat) # Killed cats need further work.
sounds.splat.play() # SPLAT!
for dead_cat in new_dead_cats:
# Newly killed cats need removing from the current cat list.
current_cats.remove(dead_cat)
Finally, my physicist friend from the tutorial (why is it always the physics students?) created this monster... Schrödinger's Splat:
Mary Mentor
~ 17 Mar 2022 8:40 p.m.
Sam,
I'm Mary, and I'm going to be your mentor.
What a wonderfully fun-yet-simple game you have created. I'm so pleased your cousins enjoy playing it, and I really like how you've managed to share your work with others for educational reasons. Many congratulations.
I have some bugs, feedback and questions:
- The cats appear to start to fall from random points within the screen itself. They suddenly appear and this is a bit disconcerting. I wonder if they could start to fall from above the screen? I'd be interested to see how this changes the game.
- The background is white. This is just asking for a creative modification. I look forward to seeing what you come up with.
- I want to keep score. Can you tell me how many cats I've splatted and how many have escaped. Perhaps I can win the game if I manage some sort of achievement, like 10 consecutive splats in a row?
- You talk about play testing, what is that and how else do you test your code?
- When you spot a bug, how do you go about fixing it?
- Could you tell me more about how you and your Computer Science friend have collaborated on this code?
I look forward to seeing what you do next.
Please remember that I'm here to help you too (although I won't write code for you), so don't hesitate to ask questions.
Sam Esteves
~ 17 Mar 2022 8:40 p.m.
Hi Mary,
Thank you for your feedback. I'll post a new version of the game with your requested changes later, but I'll try to answer your questions now.
Play testing (just playing the game to see what it's like) is both fun but can be monotonous. That's why the study group has been so useful: I've been able to see the reactions of my fellow students and get their feedback. This is equally the case with my young cousins, although getting meaningful feedback from a 5 year can be a challenge at times. ;-)
I know that experienced coders write tests for their code, but this is beyond me at this stage. However I do test my code by making small changes and running things as often to catch when I break something. I also use Mu's built-in "Check" and "Tidy" buttons to point out errors and reformat my code nicely (these are really useful). At the start of the project I would write lots of code and then spend even longer trying to work out why it didn't work. I've found the "small steps, carefully taken" (and then checked) approach works well for me. This process is also how I try to fix bugs: I spot something I don't like or that doesn't work, and then make a small change and try again.
My buddy who runs the study group is a very experienced coder and enthusiast for forcing us to get our hands dirty. Early on he explained the concept of "pair programming" and it is this form of coding, with me in the driving seat and him as co-pilot, we use when he's giving feedback. I don't know if this makes sense, but it feels very much like having a driving lesson with the instructor asking you to do something, but it's up to you to actually turn the wheel or change gear. If your question is more about how much of the code I've written, I can honestly say all of it, but with a few sessions of pair coding where my friend suggested changes without telling me what to do.
Does this help?
Mary Mentor
~ 17 Mar 2022 8:41 p.m.
Hi Sam,
Thanks for your answers and clarifications.
I'm impressed with your friend encouraging you to code together "pair programming" fashion. It's a common technique in professional coding situations, and a great way to share knowledge.
Regarding tests. What you write sounds good for this level of coding. Should you want to investigate further I suggest you start looking into unit testing in Python. The PyTest website might be a good start, and Harry Percival's (free) Goat Book contains some great introductory chapters so you can start learning the terminology and concepts for your next grade.
I look forward to playing the revised version of your game.
Sam Esteves
~ 17 Mar 2022 8:43 p.m.
Hi Mary,
I have a question for you: what on earth is global
for?
I have to use it when keeping score. I set a variable like this:
score = 0
...at the start of the game. But I can't add or subtract from it. I get this error message in the Mu terminal:
Traceback (most recent call last):
File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
exec(code, run_globals)
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/__main__.py", line 3, in <module>
main()
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/runner.py", line 93, in main
run_mod(mod)
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/runner.py", line 113, in run_mod
PGZeroGame(mod).run()
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/game.py", line 217, in run
self.mainloop()
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/game.py", line 247, in mainloop
self.dispatch_event(event)
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/game.py", line 172, in dispatch_event
handler(event)
File "/home/sam/.local/share/mu/mu_venv-38-20220222-152734/lib/python3.8/site-packages/pgzero/game.py", line 164, in new_handler
return handler(**prepped)
File "cat.py", line 83, in on_mouse_down
score += 1 # A splat adds a point to the score.
UnboundLocalError: local variable 'score' referenced before assignment
---------- FINISHED ----------
exit code: 1 status: 0
But when I add:
global score
it works!
I've read the official docs on the Python website but they don't make much sense to me.
Mary Mentor
~ 17 Mar 2022 8:44 p.m.
Sam,
That's a great question.
I guess you have the score = 0
code at the top of your file? I also assume that you're adding or subtracting from the score
variable inside the on_mouse_down
function (or similar)?
If so, you should get to know about scope and what it means for a variable to be local or global (doing an internet search for these terms will help).
Put simply, you have declared a global variable when you do score = 0
at the top of your file. The scope of a variable is like defining in what context it can be used. Usually only variables declared inside a function can be used in the function. These are the local variables. To modify a global variable (as you have created) you have to use the global
keyword to indicate to Python that you're pulling this variable into the scope of the function. I hope this makes sense. A quick search has turned up this beginner friendly explanation.
Sam Esteves
~ 17 Mar 2022 8:49 p.m.
Aha. That makes perfect sense. Thank you Mary!
I also encountered another problem when trying to display the score.
TypeError: can only concatenate str (not "int") to str
But that was easily fixed and I learned about casting and more about types. While I realised there are different types of things in Python (strings, numbers and so on), this was really the first time I was able to see the consequences. In the end I just did:
screen.draw.text("Score: " + str(score), (20, 20), fontsize=64, color=(0, 0, 0))
...and it works really well!
I'll post the revised code soon. Right now I'm trying to work out how to make backgrounds coloured in the right way so players can still see the mouse pointer.
Sam Esteves
~ 17 Mar 2022 8:50 p.m.
Hi Mary,
Here's the latest version of the code.
- I've fixed the bug so cats fall from above the screen.
- I hope you like the new backgrounds! (Attached to this comment, just drop them into the images directory.)
- A score is displayed.
- I've changed the game-play so the higher the score, the faster the falling cats.
What is your highest score?
I have to say, my cousins are really impressed. :-)
If there's anything else, do let me know.
"""
Splat-a-cat.
"""
import random
WIDTH = 1024
HEIGHT = 683
cats = ["cat1", "cat2", "cat3", "cat4"] # cat sprites.
meows = [sounds.meow1, sounds.meow2, sounds.meow3, sounds.meow4, sounds.meow5] # meows.
current_cats = [] # A list of cats that are currently alive.
dead_cats = [] # A list of all the dead cats.
music.play("disco") # Play some annoying kitty music.
background = random.choice( # Add a background from around the world.
[
"background1",
"background2",
"background3",
"background4",
"background5",
"background6",
]
)
score = 0 # Player score.
def draw():
"""
Draws each frame in the game.
"""
screen.blit(background, (0, 0))
for dead_cat in dead_cats:
dead_cat.draw()
for cat in current_cats:
cat.draw()
screen.draw.text("Score: " + str(score), (20, 20), fontsize=64, color=(0, 0, 0))
def update():
"""
Update the game state. Randomly add some cats.
"""
if random.randint(1, 80) == 1: # 1 in 80 chance.
cat_sprite = random.choice(cats) # select a sprite.
meow_sound = random.choice(meows) # select a meow.
x = random.randint(100, 924) # new x coordinate.
y = random.randint(100, 200) # new y coordinate.
new_cat = Actor(cat_sprite, pos=(x, -y)) # make the cat.
current_cats.append(new_cat) # Add to list of cats.
meow_sound.play() # play the meow.
cat_speed = 10 # Amount of cat movement.
global score
# The higher the score, the faster the cat.
if score > 10:
cat_speed = 20
if score > 20:
cat_speed = 30
off_screen_cats = [] # temporary score of vanished cats.
for cat in current_cats:
cat.y += cat_speed # Move the cat down the screen.
cat.angle += 10 # Rotate the cat..!
if cat.y > 800:
# The cat is off the bottom of the screen.
off_screen_cats.append(cat)
# Remove all the cats that have fallen off the screen.
# Deduct a point from the score.
for gone_cat in off_screen_cats:
current_cats.remove(gone_cat)
score -= 1
def on_mouse_down(pos):
"""
Handle a mouse-click event.
"""
new_dead_cats = [] # Temporary list of cats newly killed.
for cat in current_cats: # Check all alive cats.
if cat.collidepoint(pos): # Hit a cat!
cat.image = "splat" # Cat image is now a splat.
dead_cats.append(cat) # Cat is now dead.
new_dead_cats.append(cat) # Killed cats need further work.
sounds.splat.play() # SPLAT!
global score
score += 1 # A splat adds a point to the score.
for dead_cat in new_dead_cats:
# Newly killed cats need removing from the current cat list.
current_cats.remove(dead_cat)
Here's a video of this version running with most of the features highlighted.
I downloaded the backgrounds from the Unsplash website. They're free to use so long as I credit the photographers. So, thanks to Lucas Davies, Luca Bravo, Chris Karidis, Jovyn Chamb, Daniele Colucci and Jeremy Thomas.
Mary Mentor
~ 17 Mar 2022 8:52 p.m.
Hi Sam,
Thank you for your hard work. I think I have enough evidence of your work for grade 1. You'll get your results soon.
Sam Esteves
~ 17 Mar 2022 8:53 p.m.
Mary,
Thanks for your mentorship. If there's anything else you need from me, just ask here. The most important thing is my cousins love their game.