Post-Mortem: Scout Droid Bravo 325 Jr

Posted by
May 5th, 2013 11:20 am

“Mama Don’t Let Your Programmers Grow Up to be Puzzle Level Designers”

imacharacter
This is the post-mortem for Scout Droid Bravo 325 Jr. I’ll go through the highs (“Hey this works!”), the lows (“HOW MANY more levels do I need to write dialog for in the next HOUR?”) and the ugly (“You know what I should do? I should write a program to finish making the game for me!”) of my first completed Ludum Dare.

In the spirit of Minimalism, this post will be anything but. However, for the abridged version:

What Went Right

  • Rather than trying to bury myself in the game making for weeks running up to the contest, I allowed myself to relax, and had more energy going into the weekend.
  • I found a game I wanted to make, got a working version early on, and had almost a complete game (mechanic wise) on the first day. This meant that the entire second day could have been used for polish and content.

What went Wrong

  • While I did like the dialog, and received mostly good comments on it, I did heavily skew my second day making all the dialog, which meant I didn’t have time for other polish.
  • Sorely underestimated the time that it takes to make good puzzle levels
  • Probably could have removed some levels so that I didn’t have to spend less time coming up with dialog for all of them

Game Description

In the end, the game is a puzzle game that involves the player moving the title character around a level. The player needs to activate “devices” from the map. Each activation causes the player to change their location on the map, with the end goal being to remove all the devices from the level. This requires the user to plan out the order in which they hit the devices.

level2

The image above is level two. If you move your character to the right, you will hit the “up-jet” (the thing that is actually drawn as a trampoline that I never retextured) which causes you to fly up to the platform on the right. There, you will be able to hit the up-jet on the far right. However, you won’t be able to get back to the original platform to hit the far left up-jet. The correct solution is to hit the far left “up jet”, fall to the ground, then go to the right and hit the middle up-jet. Later levels get more difficult, and with different types of devices (trap doors, teleporters, etc.)

Development Backstory

LD#24 was to be my first LD. I had everything ready: the Python build script for deployment, a self-made engine built on pygame (an engine which housed some, erm, “interesting” design decisions). For a good two weeks I forced myself behind a desk readying for the competition. “I’m a good programmer who can build reusable software to reduce boilerplate tedium without sacrificing flexability!”, I said. “I have an advantage with my years of experience of coding and project management!”, I said. “I know when to stop the feature creep!”, I said. Of course, I also made sure to get acquainted with the bare minimum to make my games look… okay….

In short: I was prepared. No: I was OVER-prepared.

Then the law turned inside out, the world upside down, and the long-time runner-up/silver-winner/bridesmaid “Evolution” shocked the LD world by finally becoming the topic.

“Perfect”, I said”. “Evolution means complexity, writing software with complex rules is my speciality!”, I said.

In a moment of gusto I… promptly broke down. I had an idea for a game, but just couldn’t get myself to actually start writing the code. Later, looking back, I realized that I had burnt myself out before the contest (duh, you’re supposed to burn yourself out DURING the contest!).

LD26 Development Plan

fastfoodplan

Going into this LD, I made a few changes to my gameplan.

Just RELAX beforehand

I changed over to Javascript/HTML5 (which I had been meaning to do for awhile), but did minor experimenting over the course of a few weeks. The warmup “weekend” was really a warmup “a few hours spread over the preceding week” for me. I didn’t even start thinking about the finalist themes during the runup to the announced themes. I only started thinking of game ideas when it was announced. But unlike last time, I was now fully focused and full of energy.

Realistic Goals

My goal was to complete a game, no matter how silly it ended up being. Also, right at the very beginning, I told myself that I would NOT be adding any music or sounds. Right away, that was a burden off of my back. To be sure, the game is quite boring without those sounds, and they surely would have made it better. But that is one more thing off my mind, and my goal was the finish (not to win first prize).

“Minimalism”

“Minimalism” was announced at 10pm local time. The goal was to come up with the game idea, then pop a melatonin, head to bed and start fresh in the morning. Well, that was the plan.

I had four different ideas in the first hour, the fourth one eventually became what I wrote. The other three were:

  • A game where you controlled a number of cities (or factories, or buildings, or something) and needed to find ways to gain efficiencies to provide the same output out of fewer and fewer buildings. This was my favorite of the ideas, but I couldn’t really think of how the mechanics would work well enough. I wanted to pick an idea early, and not need to develop it too much.
  • A puzzle/platformer where you need to get from one end of the puzzle to the other. You can place a “tool” anywhere you want onto the level (like a springboard, or a platform) to help you navigate the level, but in the spirit of minimalism, you can only place ONE tool per level. This really wasn’t too different from the actual game I made, I really could have gone this way as well.
  • A game in which you control a person who is trying to remove distractions (television, fashion, etc.) from their life and achieve a minimal lifestyle slowly by making choices of when to engage in distracting activities, and when to cut a little slack. I just didn’t feel like I’d have fun making this one.

The inspiration for this idea actually came from a quote I saw in one of the links from google’s search results for “minimalism quotes”.

I started sketching out on a whiteboard the various types of “devices” that could be used for the current game. Then, I sketched out a simple puzzle using the devices. It took me all of ten minutes. “Well, that was simple: just code this up in a few hours, then make a bunch of levels”.

And so, code I did…

Early Development

A few hours in, my game looked like the image below (which, through the magic of version control, you can play here):

tplus4hours

It works, and from there I was able to continue on creating more “devices”. By the end of the Friday night (just before I went to bed at 2am), I had the following game, which included an additional device and a level loader.

So much for “go to bed and work on it in the morning”.

Level Loading

One thing I knew going into making a game with levels is needing the ability to create new levels very easily. If you don’t have some sort of program that you can create your level in and import into your game, ascii art is just as good. I wrote the level loader early on, and never really needed to change it much.

levels

Each character represents a different tile. Underscores (“_”) are ground, the asterisk (“*”) is the players starting location, an “S” is an up-jet (originally, I called it a “springboard”), an “R” is a rickity bridge. A space is air.

Saturday Day

Saturday was spent trying to make graphics. Saturday is also when I noticed that the game is pretty much done, and just needs more levels, and perhaps better graphics. You can view the results of the end of the first day here. Notice that, graphics and mechanics-wise, there is almost NO CHANGE from the final game. Really, the only thing I added after that point was the star field and landscape background images. I also only had dialog for the first couple of levels, and about 10 or so levels. Since the mechanics seemed to be going in pretty easy, I also decided that I would add Lost Vikings-style dialog before and after the level. In these development releases you can skip to the end dialog by pressing “S”, or skip a level by pressing “N”.

Just before going to bed Saturday night (well, Sunday morning) I noticed a trend in the day’s development. Basically, I ended up doing the following:

  • Realize my game needed more levels (or more difficult levels)
  • Do something other than design levels

While I had ten levels, they weren’t what you called “challenging”. Part of it was that I really didn’t know what I was doing. I had never designed puzzle levels before. Do I think of how I WANT the level to be solved, then end up throwing in red herrings to throw the user off the scent? Do I just throw stuff onto the level haphazardly, play the level, then make tweaks? Also, if I just throw together a level and then try it out and can’t complete it, how can I tell if the level I made is impossible to beat, or just really really good?

A Better Way to Design Puzzle Levels

So, Saturday night, unable to fall asleep, I started thinking about if I could come up with a better way to build these levels. Surely I could just create a tree of possible things the user can do, then create a level from that tree…

tree

I experimented with this strategy a bit, but wasn’t able to bring it anywhere.

Writing an AI to play my game

I decided that the second day, I was just going to have to just continue creating the levels manually, and haphazardly. However, to make the task a little less arduous, I decided to write an AI that would solve the level that I created. In addition to telling me if it was actually a solvable puzzle or not, perhaps I could get some sort of metric to its difficulty (for example, how many correct paths are there to the solution? How many incorrect paths?)

You can view the git repository for the AI. Consider the first level:

firstlevel

A very early version of the AI would see the following list of steps as a “winning path”:

  • Move Right -> Move Right (Hitting device, moving the character up to next platform) -> Move Right (Hit device, move up) -> Move Right (Hit final device)

However, even though it’s a simple puzzle, an AI that brute-forces this puzzle will still have a bunch of ways to lose. Here are some losing scenarios:

  • Right -> Left -> Right -> Left -> Right -> …. (Basically, going back and forth)
  • Right -> Right -> (Hitting device, moving up to next platform) -> Left (Falling off of platform, killing all chances of winning).

I soon realized that sticking with simple “move left” and “move right” as my actions would be difficult (I would need to be careful about infinite loops in the AI, etc.) So, I switched to using two different actions: “move left until something happens” or “move right until something happens”. “Something happens” would be one of…

  • Hitting device
  • Hitting an “air block” (in other words, falling off of a platform)
  • Hitting the walls of the game

Thus, the AI would do a combination of “move left until something happens” or “move right until something happens”. If the movement caused the unit to hit a wall, I considered that a “losing” path. Going back to the first level, here are ALL of the losing paths (R for “move right until something happens”, L for “move left until something happens”).

  • R (Hit device) -> L (Fall off platform) -> L (Hit wall)
  • R (Hit device) -> L (Fall off platform) -> R (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> L (Fall off platform) -> L (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> L (Fall off platform) -> R (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> R (Fall off platform) -> L (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> R (Fall off platform) -> R (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> R (Fall off platform) -> L (Hit wall)
  • R (Hit device) -> R (Hit device) -> L (Fall off platform) -> R (Fall off platform) -> R (Hit wall)

Unless I missed some, there are 8 losing paths to go along with the one winning path. Originally, I tried to determine level complexity from the “winning percent” (the percent of paths that are winning paths). For the first level, the winning percent would have been 11% (only 11% of paths would win). However, when I ran this algorithm on some of my other puzzles, I started noticing that this didn’t really give me a good indication of how hard the level was. My second level (which isn’t “hard” by any stretch of the imagination, but certainly not as obvious as the first) actually showed a HIGHER win percent (due to the fact that it didn’t have as many platforms for the AI to jump off of and cause new loss paths).

Improving the Algorithm

I continued tweaking out various algorithms, with one major change that made it better. A “path” is only the order in which you hit the devices. There could be multiple paths to hit the devices in the same order, or you could hit the devices in the right order so that all you need to do is hit the last one, but you still have 50 ways to lose (by ignoring the last device and jumping off of various platforms).

Finally, I was able to run this algorithm on a level and get output like the following:

mark@gobo:~/projects/games/ld26-helper$ python run.py 
'       _            ',
'                    ',
'     _S             ',
'                    ',
'   _S               ',
'                    ',
'*_S_________________',
WINS:
((2, 0), (4, 2), (6, 4))
1 Wins
0 Losses
100% Win Prcnt

The script will run a map (showing the ASCII art), show me the various winning paths (the list of tile coordinates of the devices to hit). The first level is a 100% win percent, even though you COULD theoretically lose the level. Here’s the result of the run on the second level:

mark@gobo:~/projects/games/ld26-helper$ python run.py 
'                    ',
'                    ',
'            _S      ',
'        S_*         ',
'___________S________',
WINS:
((8, 1), (11, 0), (13, 2))
1 Wins
1 Losses
50% Win Prcnt

Here, we see there is one loss path that the user could take.

Analyzing Level Complexity

For these early levels, the AI wasn’t that helpful, but it was ENORMOUSLY helpful for later levels. Here are two levels, 13 and 15. You can use this slightly older version of the game to play. In this version, pressing the “N” key skips to the next level.

Level 13:
Level 13

Level 15:
Level 15

At first glance, they both seem about equal in complexity, right? If anything level13, with more devices, actually seems harder. So I put on my robe and wizard hat….

Level 13:

'                    ',
'     E              ',
'           ___      ',
'__T_RR_TT_          ',
'_RRRR_ _S_          ',
'______SS*_____E_____',
WINS:
((14, 0), (7, 2), (6, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (7, 0), (8, 2), (8, 1), (4, 1), (3, 1))
((7, 0), (7, 2), (8, 1), (8, 2), (14, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (4, 1), (3, 1))
((14, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (8, 1), (8, 2), (7, 0), (4, 1), (3, 1))
((14, 0), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (7, 0), (8, 2), (8, 1), (5, 2), (4, 1), (3, 1))
((14, 0), (7, 2), (8, 1), (8, 2), (6, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (7, 0), (4, 1), (3, 1))
((7, 0), (7, 2), (6, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (14, 0), (8, 2), (8, 1), (4, 1), (3, 1))
((7, 0), (7, 2), (8, 1), (8, 2), (14, 0), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (5, 2), (4, 1), (3, 1))
((7, 0), (7, 2), (8, 1), (8, 2), (6, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (14, 0), (4, 1), (3, 1))
((14, 0), (7, 2), (8, 1), (8, 2), (7, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (4, 1), (3, 1))
((7, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (14, 0), (8, 2), (8, 1), (4, 1), (3, 1))
((7, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (8, 1), (8, 2), (14, 0), (4, 1), (3, 1))
((14, 0), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (8, 1), (8, 2), (7, 0), (5, 2), (4, 1), (3, 1))
((14, 0), (5, 2), (4, 2), (2, 2), (2, 1), (1, 1), (6, 0), (7, 2), (7, 0), (8, 2), (8, 1), (4, 1), (3, 1))
13 Wins
20 Losses
39% Win Prcnt

Level 15:

'                    ',
'                    ',
'       RRTRR_       ',
'    _E_SS__TRT__    ',
'        ST*__       ',
'__________E_________',
WINS:
((9, 1), (10, 0), (7, 2), (8, 3), (9, 3), (11, 2), (8, 1), (7, 3), (8, 2), (10, 3), (11, 3), (12, 2), (13, 2))
1 Wins
4 Losses
20% Win Prcnt

On the one hand, level 13 has many more ways to fail. On the other hand, level 15 only has one way to win, and a lower win percentage. So, which ones harder? I’m still not sure what a good algorithm would be for this.

Let the Computer do the Work

I decided that, ideally, a hard puzzle should have exactly one win path and a high number of loss paths. So, I would come up with SOME puzzle, throw it through the AI, and tweak the results. I realized there was a better way to do this: let the computer generate levels, run the AI, and spit out when it found a “hard” one. It would take too long to just have it really generate one (if I did that, most of the levels would be impossible to beat because of a device in the upper left corner of the map or something), so I created something of a template with question marks in the parts of the level that I wanted a device randomly generated for. For example, here is level 18…

level18 = [
    '                    ',
    '                    ',
    '    _SRSE           ',
    '   _T_TT_T_         ',
    '   SSR_R___TSR_     ',
    '  _S___SR*_R        ',
    '________E___________',
]

Here is something like what the final level looked like when I originally wrote it…

new_level = [
    '                    ',
    '                    ',
    '    _???E           ',
    '   _?_??_?_         ',
    '   ???_?___???_     ',
    '  _?___??*_?        ',
    '________E___________',
]

Notice the question marks for some devices. I added a function to the script to take the level, and randomly generate devices for the question marks, then solve the puzzle until it found a puzzle with only one victory condition. Here is the output of a run…

0
0
0
0
0
0
0
0
0
0
0
0
411
0
0
0
0
0
0
0
0
0
0
0
0
0
1
'                    ',
'                    ',
'    _RTSE           ',
'   _T_SR_R_         ',
'   STR_R___SRT_     ',
'  _S___RS*_T        ',
'________E___________',
WINS:
((11, 1), (8, 0), (7, 4), (6, 4), (6, 3), (5, 4), (4, 3), (4, 2), (7, 1), (8, 1), (9, 3), (7, 2), (5, 2), (3, 2), (3, 1), (7, 3), (11, 2), (12, 2), (13, 2))
1 Wins
7 Losses
12% Win Prcnt

At first, it dumps out one number per line. Each of those numbers represents a level that was generated, and the number of winning paths. In this case, most of the levels were impossible (0 winning paths). One of the levels it generated had 411 winning paths. Finally, it generated a level with 1 winning path, and 7 losing paths. For the curious, the final level of the game has 1 winning path and 17 losing paths.

Oh, that’s right, I’m supposed to be making a game here

So I spent the rest of Sunday tweaking the algorithm so that it could generate good levels. However, I went a bit overboard, and had 22 levels. The remaining 6 hours or so was spent just writing dialog.

In total, I would estimate that half of my waking time was spent writing either dialog, or the AI (which isn’t even part of the game!).

Of course, this last-second race meant a few things:

  • I wasn’t playing my game. Afterwards I would realize that having to restart the level and go through all of the dialog again was annoying
  • The dialog would sometimes make sense when I wrote it, but because the user actually had to play the game, some dialog that makes sense read seconds apart became confusing when spaced out by minutes

Conclusion

As I mentioned earlier, my main problems here was an under-appreciation of the amount of work it takes to DESIGN good puzzle levels. Even the lower-win-percentage auto-generated levels were really only gaining in complexity because there was more devices in them, not because they required more clever, outside-the-box thinking.

I kept the game mechanics simple, and that allowed me the ability to not use effeciently much of the second day and still come up with a decent game. I’m hoping in the next LD I will have the opportunity to try out creating audio/sound. I really did like the dialog, so I want to keep doing stuff like that, although I might need to reduce the scope of it a bit.

Also, this was a good test for my Canvas library and I have ideas of how I want to change it.

Okay, I think I’ve written enough for one day. If you’re still with me, thanks! I hope this proved an interesting read.


Leave a Reply

You must be logged in to post a comment.

[cache: storing page]