Rosalind Bradshaw's Final Project and Final Reflection

by RosalindBradshaw

05 Dec 2022

Adventure Maze:

Reflection:

When I first heard that one of the final projects for this class was to code a video game, I knew right away that would be the option I would choose for a couple of reasons; I love playing video games, and the graphical turtle exercises were much more engaging for me, so I knew that would be easier for me to commit to doing for a larger, more complicated project. I was also inspired by my love of D&D to create an adventure-maze game in which levels of the maze were drawn as the player navigated through the maze it self via click actions.

To start out, before I even began coding anything, I had to draw out the mazes I wanted to use on graph paper. Each map is drawn as a 26x26 square area, with each square corresponding to roughly 15 pixels on the turtle screen, and x and y coordinates are charted along the edges of each map. I knew each of these steps would be critical to allow me to build such complex structures within the code and have all the sections match up to each other. Each section of the maze is built into a smaller function, allowing me to call each one only when I need it to be drawn. This allows the illusion that the maze is being generated or revealed as the player is reaching that area, and creating a “fog of war” effect on the maze areas that have yet to be drawn.

The first level of the maze is themed as the player making their way through a forest. To help convey the concept of the forest, I imported an image of a tree, made that the turtle shape, then had the turtle stamp the image in the appropriate areas. The forest isn’t as thick as I might have otherwise liked, because I didn’t account for the space of the tree image when originally drawing my maze maps. If I was to re-do this project in the future, that might be one avenue that I would rework. This was the most challenging map to draw, as it incorporates mostly curved lines throughout. To achieve this, I used many definite for loops to create each section of curved area:

for i in range (20):
    matt.forward(3)
    matt.right(4)

Each for loop had to be meticulously, and repeatedly, tested to ensure that all of the ends matched up and looked good in the final product. As a byproduct of all of the for loops, this maze is also the slowest one to generate. I don’t know if there was a way to get around that, but I’m pleased with how it turned out.

Levels two and three of the maze are themed to be inside the theoretical castle. These levels both use exclusively straight lines, which are far easier and faster to draw. To convey the idea of the castle, I used two separate background images of cobblestones for the levels. The original images I used presented a few different issues. Both were too “busy” and were highly distracting from the basic maze generation I was creating. They were also the wrong size to convey the idea I was going for. To address both of these issues, I imported the image files into GIMP, shrank them down, then tiled them across an area to create a larger composite image. Then I added a second layer and modified the opacity settings to dull out the overall image. This allows the background image to be just that, a background, and not be quite so distracting to the player experience.

To test all of the mazes being drawn by the turtles, I created separate trinkets for each maze. That allowed me to test and modify each one independently, before importing them into a larger program.

Level 1 Map: https://trinket.io/python/462f27804c

Level 2 Map: https://trinket.io/python/e38d389f4e

Level 3 Map: https://trinket.io/python/311c68b732

Once all the mazes were good and each section was able to be called independently, I imported them all into a mazes.py module. The next step, after coding the basic maze functions, was to allow the player to navigate the mazes. This ended up being the most challenging part of this entire project because of the extremely complicated flow of control I had to generate. At first, I made the choice to put all of the navigation codes into a separate module. I made this choice, initially, because I wanted the code to be well organized, but this ended up creating a huge problem. While I could create the navigation functions and call them from the main module I couldn’t figure out how to pass the flow of control back into the main module to create an iterative process. I experimented with a number of different options to try to fix the issue, but none of them were really successful. Finally, after ruminating on that issue for the better part of 3 weeks I realized that the solution was to get rid of the navigation module entirely, and just keep all of the navigation code within main.py. This prevents flow of control traveling outside of main.py and allows me to create a truly re-playable game, with the ability for the player to exit out of a maze level, or replay at the final screen.

The other big challenge with the movement was how to create boundaries for the player turtle. I did a lot of research early on and found out that turtles can’t recognize a shift in their background screen. Originally, I’d wanted to create an if/else loop such that if the background color were a certain color the turtle knew not to go there, but that was impossible with the turtle’s current functionality. Instead, each section of movement happens within carefully prescribed x,y boundaries. If the player clicks within the allowed area, the turtle moves to that space, a new section of maze is drawn (if necessary) and the game progresses. If the player clicks outside of that allowed area, a random error message is pulled from the error_code dictionary and displayed. Each section of movement works through nested if/else loops, allowing the player to move through an area, interact with doorways, and reveal new sections of the maze. This is why the flow of control was so challenging; the code allows for full backwards movement, meaning that each function calls to other related functions to create a mostly-seamless loop. This is also why having the mazes physically drawn out was critical as well, because that way I could keep track of which coordinates were in and out of bounds. It took me a solid 4 or 5 days, with well over 24 combined hours of coding to get all of the navigation completely done, but given that that was such a huge component of the maze, I’m not surprised it took most of the time to get done. I also made sure to include a few secrets, as any good video game should have. Each level has at least one secret to discover, though some are more challenging than others.

After the maze and navigation codes, the next biggest hurdle was the start menu. Originally, I coded the start menu to respond to player clicks, but I quickly realized that the start menu would continue to exist as a phantom artifact after the code progressed to one of the playable levels. A player could, if they knew where to click, still interact with the start menu, even though it wasn’t visible. To get around that, originally, I modified the start menu to respond to keyboard input commands. That wasn’t as nice of a player experience as I wanted, but it solved the problem. You can see from my various trinket iterations, that coding the start menu was actually a very early piece of work. I kept it as a keyboard input system right up until the last few days of coding when I decided to go back and see if I could make it work through screen clicks. This time, the code I’d already created, was robust enough that the phantom menu I had been encountering was overridden and the game play progressed as expected. Moving the navigation into main.py also allowed me to create the functionality to go back to the start menu whenever I wanted to, which creates a more complete player experience.

Adventure Maze Draft 1: https://trinket.io/python/213825b302

Adventure Maze Draft 3: https://trinket.io/python/294b0413fb

Adventure Maze Draft 5: https://trinket.io/python/954170a20a

Adventure Maze Draft 8: https://trinket.io/python/8b54fafe40

Another major piece of code that I wanted to work in was the ability for the code to count the clicks the player made as they moved through the levels and display that as a sort of “score”. I was grateful to see that Dr. Hauser had provided a basic click-counting code segment in one of the resources for our class, and I attempted to incorporate that into my code. The issue I ran into was that I could get the code to either count the clicks, or move the player turtle, but not both simultaneously. In the end I gave up on actually incorporating this into my end product. I made this decision because I knew that I was already behind where I really wanted to be at that point in working on my final project and I opted to make a more polished finish product that just lacked that aspect, than have a code that counted clicks but wasn’t as complete in other aspects. If I had more time I’m positive I could have figured out a way to incorporate both click counting and click movement, but it will have to be something I really dedicate more time to later. To display the turtle’s state, I opted to have a display on each screen to show which level the player is playing on.

After getting the start menu, mazes, and navigation established, I began working on the extra pieces to really flesh out the code. The first was the error_code dictionary. For this, I had to do a lot of research and experimentation to actually create my own dictionary. After it was created, I had to figure out how to grab a key, at random, to display. To achieve this, I turned the dictionary into a list and used:

    error = random.choice(list(error_code.values()))
    print(error)

To generate each error message when the if/else loops call it. The dictionary process ended up creating one of the single most annoying and frustrating bugs within my code in the entire project. As I was playtesting, at one point, my code stopped and returned an error message “bad token on line 5 in menus.py”. Trinket flagged my menus.py module as the source of the error was the line from navigation import *. Since the line it was having issues with was an entire other module that had over a thousand lines of code, it was definitely the most unhelpful error message I’d ever seen. It took me several minutes to figure out what it was trying to tell me. Fortunately, I knew the code had been working prior to building the dictionary, so I was able to go back to a working form and work forward from there to figure out what had happened. In the end, it turned out that the error was coming up because I’d missed a single apostrophe in a new line I’d put in my dictionary. While debugging this particular error didn’t really take all that long it was definitely the most annoying one I encountered, just because of the vagueness of the error message, and the actual cause of the error. It really demonstrated how beneficial it is to run your code frequently as you work, if I’d gone on to work on another section after finishing the dictionary, I know debugging this error would have been enormously more challenging to figure out.

The final piece of my code was the images I incorporated. Overall, I imported and incorporated 6 different images in my code: the trees in level one, the cobblestone backgrounds in levels two and three, the player character image, the final win screen, and the scrolling credits. Incorporating the images ended up being a bit more challenging than I originally anticipated because I had to do a lot of modification to get the images to work with my base code. To get the player icon, I started out with a generic “adventurer” image, shrank it down, and imported it as the player turtle image. I actually imported two images, one facing right and one facing left, so that the player character isn’t moving backwards all the time, though it does still appear to be moving backwards occasionally. This could be solved by finding a character image that is facing the “camera” head on, or by implementing more swaps between left/right facing, but that would be something I’d leave to a future refactoring of the code. Another idea I had for an eventual refactor of my code was to build in an ability to select a player character from a screen of options. For the win screen image, I used the same process as the trees, I incorporated an image, assigned it as a turtle shape then had the turtle stamp it on the screen. I’m actually pretty proud of how I created the final credits image and gave it the appearance of scrolling. I started out with creating the image as a google slide, taking a screen shot of it, and importing that as a custom image. Then I set it as the turtle shape and used a for loop to animate it.

for i in range (200):
    matt.forward(5)
    matt.delay(30)

I chose to use a for loop instead of just setting the turtles speed because, even at the slowest speed, the turtle was still moving too quickly for the credits to be read. Using the for loop allowed me to slow the turtle down enough to have the credits be readable and scroll across the screen from top to bottom.

I’d like to credit the following websites for the artwork I used within my game:

Trees: https://www.deviantart.com/phyromatical/art/Tons-of-Tileset-1-10-Light-jungle-trees-485775828

Cobblestone 1: https://www.reddit.com/r/PixelArt/comments/faapnw/my_first_attempt_at_a_decrepit_cobblestone_road/

Cobblestone 2: https://thumbs.dreamstime.com/b/set-seamless-cobblestone-paving-patterns-to-improve-textured-backgrounds-rubble-drawing-173259074.jpg

PC Icon: https://www.nicepng.com/png/detail/785-7857975_generate-sprites-pixel-art-rpg-character.png

Cake: https://png.pngtree.com/png-clipart/20210714/ourmid/pngtree-celebrate-the-cute-pixel-style-of-the-three-tier-cake-tower-png-image_3584824.jpg

Starting out, this was my original, anticipated workflow:

Nov 12-16: Base Functionality

  • Draw level maps on graph paper
  • Code level maps with functions for each section
  • Expand Turtle Classes
  • Code Start Menu with number entry options
    • Level 1
    • Level 2
    • Level 3
    • Help
    • Exit
  • Allow a user to navigate between new screens using the Start Menu
  • Code consistently available help menu

    Nov 17-23: Interactive Functionality

  • Research screen-clear functionality for between-level-transitions
  • Research click-counting ability for turtles
  • Incorporate click counting into game levels
  • Code scoreboard to display moves taken
  • Code player-navigation for each maze section
  • Boundaries for turtle movements
  • Options to proceed to the next area

    Nov 25-Nov 30: Background Functionality

  • Code list or dictionary of error codes
  • Incorporate error codes into main code sets -Code images into the levels
    • PC icons?
    • Transition images?
  • Win animation

    Dec1-7: Playtesting and Debugging

    Dec 8: Project Complete and Turned In

Overall, I ended up not keeping to this timeline quite as well as I had anticipated. Partially, that was because I didn’t work in the order I originally laid out, not due to any real flaw in my own planning, but simply because I had a lot of real-life stuff come up all at the same time that required a lot of flexibility on my part. And partially that was because I didn’t know how to anticipate the amount of time certain sections would take because I’ve never done a project like this before.

For my final project, this is the way I completed all the requirements laid out for this assignment:

  • At least one external data file. – turtleclass.py, menus.py, drawmazes.py, random
  • Dictionaries – error codes
  • Custom modules – turtle classes, menus, and maze graphics
  • definite (for) loops – level 1 maze graphics, win animation, credits screen
  • Custom functions – navigation, maze graphics, win animation, credits screen, start menu, help menu, exit menu
  • A Python 3 or Pygame (Python 3 + GUI) Trinket. – #!/bin/python3 shebang enabled
  • Have a graphical user interface, responding to key and click events – start menu, level navigation, exit menu
  • Have a constantly available help dialog – help option in start menu, and help button in maze levels
  • Display information about the program’s state such as score or level – maze level display
  • Have at least 3 levels, increasing in difficulty – levels 1-3 of the mazes
  • Extend a custom Turtle Class – PlayerTurtle and DMTurtle
  • Have a ‘win’ screen – Win animation
  • Have an iterative interface – Start menu, Exit buttons in levels, and Exit menu after credits
  • Use one or more custom images – Trees, cobblestones, player image, win screen, and credits

In the future, I see many ways I could improve my code, overall. There is the obvious addition of more levels, or a more comprehensive storyline to the code. But on the actual coding side, a big part would be to re-work the navigation such that the maze doesn’t draw every section over and over. Right now, every time a player navigates to an area, that section gets drawn again. This isn’t normally a big deal, because they player doesn’t see it happening. But it does mean that if they player clicks repeatedly in an area, the turtle will scribble all over the screen. The way to fix that would be to code a duplicate navigation set for each section so that the maze is only drawn the first time. Another big change would be to introduce a broader selection of player icons. Additionally, I would have liked to have the player icon respond so that it always appears to be traveling forwards, which would have required having the image change to face the correct direction. I’d also like, at some point, to figure out how to actually get the click counting to work. I’m sure it’s possible, it will just take time to figure out exactly how to make it happen, as an addendum to that I’d like to code in a score board so that players can see how many moves they take compared to how many other players took on the same level.

Second year grad student going into archival work. Find RosalindBradshaw on Twitter, Github, and on the web.