Skip to main content
ALPHA    This is new software undergoing tests! Thank you for your patience.

Candidate: Sam Esteves samesteve Assessed by: Mary Mentor 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
![mu_mode.png](/media/assessment/1a0889cc/2022-03-17/20-31-35/mu_mode.png "mu_mode.png")
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
![cat4.png](/media/assessment/1a0889cc/2022-03-17/20-31-35/cat4.png "cat4.png")
cat3.png 70.3 KB samesteve
Markdown code
![cat3.png](/media/assessment/1a0889cc/2022-03-17/20-31-35/cat3.png "cat3.png")
cat2.png 61.6 KB samesteve
Markdown code
![cat2.png](/media/assessment/1a0889cc/2022-03-17/20-31-35/cat2.png "cat2.png")
cat1.png 60.5 KB samesteve
Markdown code
![cat1.png](/media/assessment/1a0889cc/2022-03-17/20-31-35/cat1.png "cat1.png")
splat.wav 36.0 KB samesteve
Markdown code
splat.png 14.1 KB samesteve
Markdown code
![splat.png](/media/assessment/1a0889cc/2022-03-17/20-34-40/splat.png "splat.png")
background6.png 652.9 KB samesteve
Markdown code
![background6.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background6.png "background6.png")
background5.png 526.2 KB samesteve
Markdown code
![background5.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background5.png "background5.png")
background4.png 663.0 KB samesteve
Markdown code
![background4.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background4.png "background4.png")
background2.png 346.5 KB samesteve
Markdown code
![background2.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background2.png "background2.png")
background1.png 487.5 KB samesteve
Markdown code
![background1.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background1.png "background1.png")
background3.png 1.1 MB samesteve
Markdown code
![background3.png](/media/assessment/1a0889cc/2022-03-17/20-50-16/background3.png "background3.png")

Status: Closed (results delivered). 93/100 ~ Pass with DISTINCTION (17/03/2022).


samesteve 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.


samesteve 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.


samesteve 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:

mu_mode.png


samesteve 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:

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.


samesteve 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!


samesteve 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:

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 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:

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.


samesteve 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 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.


samesteve 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 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.


samesteve 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.


samesteve Sam Esteves ~ 17 Mar 2022 8:50 p.m.

Hi Mary,

Here's the latest version of the code.

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 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.


samesteve 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.


Back to top