Rock Paper Scissors

2024-10-11 : previous : next : index


I spent the past month making a rock paper scissors game for the Playdate console.


https://uguu-org.itch.io/rock-paper-scissors

A short game

I have built a fair bit of infrastructure for Magero, and I thought it would be a shame if they were only used for just one game, so I started making a sequel right away. I figured I just have to draw some maps and would be done in a few months. Then I remembered... the reason why it took me so long to make Magero was because I spent a lot of time drawing. After a few weeks (plus a year), I just got really tired of drawing maps.

I thought: I have got to make a short game with the explicit goal of not taking a whole year to make it. The next thought that came to mind was rock paper scissors, because I figured the only things I needed to draw were rocks, papers, and scissors. So I settled on this Rock Paper Scissors idea in maybe 2 minutes, and spent 0 minutes in thinking up a different name. I probably should have thought of a more distinctive name.

Naming aside, the rest of the project very much went according to plan, which was to have lots of rock+paper+scissors sprites fight things out. And I did it in about a month.

Week 1: Scissors papers rock

I started by drawing scissors, since I had a clear idea of what shape I wanted. It looked best in top-down view, so it has been decided that the game would be top-down. Paper airplanes might have looked nice if I had gone with isometric view, but a simple rectangle to represent the papers in top-down view also looked reasonable. Both scissors and papers seemed compelling when animated, and I made some effort to allow papers to be cut at different angles to match the scissors that killed it.

Rocks are just icosahedrons, generated with a script because it was faster for me to write this script than to draw an object rotated in 512 different angles. I like icosahedrons because "roll a d20" has a nice ring to it.

Having decided that I would minimize the amount of drawing I had to do and actually drawing only 2 out of 3 required objects, I thought I was making great progress.

Week 2: Automatic rock paper scissors

I was able to save a bit of time by reusing most of the tools and scripts I wrote for dealing with SVGs. I couldn't reuse most of the Lua code since this is a very different game, but it's also a much simpler game (no inverse kinematics or bouncy ball physics). There is roughly just one core feature, which is to simulate lots of objects at a decent frame rate. I just need to solve the two problems of knowing where to go and knowing when you hit something.

Knowing where to go is just "follow the next victim" in this game, there are no (intentional) evasive maneuvers. Each killer is matched with one victim with some heuristics to get all victims covered, and then try to arrive at a spot slightly ahead of the victim's path. Setting movement direction based on victim position requires computing arctangent, which might have been expensive since we have to do it hundreds of times, but since there are only so few angles to choose from, I used a table-driven approach.

Detecting collisions is done by maintaining a grid of object indices, each object occupying 4 cells (16x16 pixels). There are already collision facilities available in Playdate SDK's sprite library, but because I was doing fixed-point arithmetic and lots of sprite changes, there is a fair bit of overhead needed to synchronize the sprite state with my world state. I ended up not using the sprite library at all, and made only one drawImage call per object to draw exactly what I wanted.

With the movement and drawing functions in place, I was able to observe how the heuristics were doing with the game playing itself automatically. I also added simulation functionality so that I can complete lots of game runs without waiting for each frame to render. And what I observed was that the last few objects were very tedious to kill, because after the world became sparse from all the early battles, the remaining kills only happen near walls.

What we needed was more walls.

Week 3: Walls and floors

Wall graphics were entirely generated. Each wall tile only occupies 8x8 pixels, but there is a special indexing scheme to make sure they tile properly, and that would be tedious to do by hand. So I wrote a program for that. Wall placement was done using a celluar automata method. I had lots of aspirations for nicer cave-like walls, which I implemented in another program used to test the wall tiles, but there wasn't enough CPU cycles for it, so I ended up just making the walls breakable.

Having added walls, I thought the floors could use some decorations as well, so I wrote a script to generate basic floor tiles. The first revision looked bland, so I updated the generator and did some manual post processing.

Up until this point, most graphics other than the scissors were either partially or completely generated by code. It's not that I hate drawing, but there are many things that I find easier to generate with code than to manually draw them. Generating graphics has caused me to trip over some Inkscape issue that I am sure is fairly unique to my usage.

Adding walls allowed more places for objects to get cornered, so that made most of the games complete more quickly. Since walls are breakable, I also added some indestructible slimes for good measure, so that there are always some obstacles around. The increased setup cost was amortized by initializing in the background, and the increased run time cost due to extra collisions were minimized by avoiding repeated collisions.

Week 4: Fossils, respawns, and backdoors

The floor tiles would have been purely decorative, but it actually solves a power balance problem during the endgame phase. If population can only decrease, the outcome of the game would be effectively decided when there are only two groups of objects remaining. This is especially frustrating for humans when their auto-controlled teammates prematurely killed off the last victim. Being able to respawn objects would undo the damage done by those premature kills, and the trigger areas for the respawns are marked by special floor tiles:

I implemented a game mode selection screen next, which showed instructions on how each mode worked. I thought that was the last thing I needed, but then I got concerned about the accelerometer. Testing accelerometer settings was cumbersome, so I added a backdoor to make it easier to get raw readings. Having added a backdoor for rocks, I added another one for scissors and one more for paper to complete the set.

Last thing I did was some documentation updates. There is one FAQ bit about scissors having an edge over rocks and papers, which was a result from thousands of simulation runs showing scissors having a higher win rate. During the early weeks of development, I was telling my friends: "I am not sure why, but scissors appears to have an edge over rocks and papers", and one of them immediately said "yes, two, in fact." I liked this pun so much, I had to put it in the FAQ.

Release

I announced it in mostly the same places as last time:

Rock Paper Scissors saw 100+ downloads in the first 3 days, which I thought was pretty good. Magero saw 100+ downloads just on the first day, but I suppose for the amount of time I spent on each project, I might have been disappointed if the numbers were the other way around.

Now I have a short game that I can play every time I need to wait for my computer to reboot :)


Previous (2024-08-09): A crank-based platform game
Next (2024-10-21): Picky Treats

Index

uguu...