Using the Python library, PyGame, I designed a drawing game where players could draw a world of their design for others to interact with in real life, much like a dungeon master in the board game Dungeons and Dragons. Here players in other people’s worlds can take action and the outcome is decided from a dice roll. Below is an explanation of different features and the troubleshooting process behind some obstacles.
Drawing Resolution
Pygame provides a line drawing feature, given a start pixel, end pixel, line thickness, and a drawing surface. I designed the program to match Waveshare’s 800×480 pixel screen. As seen in the “Before” image the drawings are pixelated due to the nature of the draw function. To improve the resolution I added a second drawing surface with a higher pixel density, in this case 5x the original. The mouse was tracked on the original drawing surface and then translated to the higher-resolution surface. Once the lines were drawn the higher-resolution surface was scaled to match the original surface’s dimensions giving a smoother line effect, shown in the “After” image.
Gameplay
Each game consisted of 3 drawing screens and an inventory page. The 3 drawing screens house the world that the owner created. Featured on this page are a pen, an eraser tool, and a 6-sided dice roll for probability outcomes. An option for more dice could be added but to keep with the game’s original design a 6-sided dice is the main option.
An inventory page is included with each game where the owner of the world can track other players’ possessions, health, money, or whatever they would like for their game. This slide defaults with 3 columns and 3 rows but more can be drawn in.
Object-Oriented Games
Taking advantage of object-oriented programming, each game is a separate instance of the class Game. The object has a name association and pixel data for the separate pages and inventory. This simplifies the code while maintaining game integrity. The games are saved to a file and able to be reuploaded to the game upon start.
Hardware Implementation
The game was run on a Raspberry Pi 4B 1 GB RAM connected to a Waveshare 7-inch touch display. The only change required for the program to run was changing the mouse click event type to a finger press, both built into the Pygame library.
To stress-test the hardware with this program, the pages of a world game were filled with drawn lines and then the eraser was used with speed. During this testing, I found the program greatly slowing down, enough to become inoperable. Optimization of the program needed to be done to work on the Raspberry Pi
Optimization
To test the code’s efficiency I used:
- Python gpustat: graphics card utilization
- Python psutil: CPU, memory, and disk utilization
Here I could test for bottlenecks in the code and evaluate where the code was taking the most memory and time to execute. As expected the main choke point was with the eraser function
Original Method:
- Associate each pixel drawn by the pen with a coordinate by iterating through the canvas and seeing which pixel is black vs. white.
- Draw an eraser using
pygame.draw()
and follow the same function as step one but with the eraser. - Compare the two using a hash search.
- Remove intersections.
- Redraw the new lines onto the screen.
This method gave great accuracy since it was a pixel-by-pixel comparison but was slow. It used: 17 MB of memory.
Improved Method:
- Reference the pixels into a 2D array the size of the screen, where a black pixel had a unique value compared to a white one.
- Do the same with an eraser line drawn using the
pygame.draw( )
. - Compare the two by interating through the short eraser list.
- Since the pixel is referenced any intersections pixel can be changed without having to redraw the lines.
This method improved the memory usage immensely, only using 1.8 MB at its peak. A 10 times increase in speed.
An issue that should be fixed in the future is the CPU usage as Python code limits its programs to 1 core. Two proposed fixes for this are to enable parallel computations in this program or to translate the code to C++, which would result in about a 15x speed increase. The latter is the most likely course of action after further investigations into the program’s bottlenecks.