DEV Community

Cover image for Create a Maze Game in Mini Micro
JoeStrout
JoeStrout

Posted on

Create a Maze Game in Mini Micro

In the classic arcade days, there were a lot of maze games. That's because mazes are fun! They give the player interesting choices at every point, and force you to look and plan globally while also paying attention to local hazards and opportunities.

Maze game screen shots
Clockwise from top left: Pac-Man (1980), Wizard of Wor (1980), Nibbler (1982), Mouse Trap (1981).

You don't see this as much in modern games. A 3D shooter or adventure game might be in a maze, but with a first-person view, you miss out on that global planning because you can only see what's directly in front of you.

All of which is to say, people should make more maze games. And Mini Micro is a great environment for making them. Let's make one right now!

Start with the maze demo

Mini Micro has a handy demo of displaying and editing a maze at /sys/demo/maze. Start by running that. You can run it online here, but better to download Mini Micro if you don't have it already, and then just type run "/sys/demo/maze" and press Enter. The demo looks like this:

Screen shot of /sys/demo/maze in Mini Micro

You can click and drag with the mouse to edit the maze. It's important to understand that you're adding and deleting wall corners, not walls or rooms. These are the intersections of two (potential or actual) walls between rooms.

An important thing to understand is how what you see relates to the actual TileDisplay creating it. The best way to get that understanding is to press G in the demo, which toggles a visible grid (by shrinking the cells and spacing them out slightly).

Screen shot of image with grid

These are Wang 2-corner tiles, which are explained in this post, so I won't go into detail here. But they're very neat and easy once you get the hang of them.

Start your program!

Now let's leverage the code in this demo to make our own maze game. If you were running the demo on the web, now you really should download it so that you can save your work.

We're going to use the build a little, test a little approach here, building up the program bit by bit. This is a great habit to have in all your coding; it's much easier and more successful than trying to write out an entire program at once.

So if you're still in the demo, exit out of it by pressing Esc, then type clear and reset to clear the screen and program. Then edit, and type or paste in this code:

clear

if env.importPaths.indexOf("/sys/demo") == null then
    env.importPaths.push "/sys/demo"
end if
import "maze"

maze.generate
Enter fullscreen mode Exit fullscreen mode

This clears the screen, and then checks whether our import paths already include "/sys/demo". That's not normally a directory that import searches, but it's where maze.ms lives, so we need it to check there. Then we import "maze", which loads the code from maze.ms into a map called maze in our program. Finally, we call maze.generate to make and display a random maze, just like the demo.

Save this program (I called mine "mouseMaze", for reasons that will become apparent), and then run it. You should see a maze appear.

Customize the maze

The maze generated so far is a "pure" maze, i.e., one with exactly one path between any two points. That's not actually much fun to play in; we want the player to have choices. You could manually edit your maze layout, save it to disk in some way, and load it into your game; this would be most similar to what the classic games above did. But for now, let's just knock out a random selection of walls.

tiles = maze.tiles
cellSize = tiles.cellSize
extent = tiles.extent
maxCol = (extent[0] / 2) - 1
maxRow = (extent[1] / 2) - 1
tiles.setCellTint range(extent[0]-1), range(extent[1]-1), "#44FF44"

// Delete some extra walls so it's not a "pure" maze
for i in range(20)
    room = maze.rooms[rnd*maze.Room.cols][rnd*maze.Room.rows]
    if rnd < 0.5 then wall = "north" else wall = "east"
    room[wall] = false
end for
maze.setCornersFromRooms
maze.updateTiles
Enter fullscreen mode Exit fullscreen mode

edit your program and add the above code, below what you wrote before. This code begins by copying maze.tiles into a tiles variable, just to make it easier to refer to. Then we also copy cellSize and extent out of that. We also compute the maxRow and maxCol of rooms (rooms are the little spaces between the walls — there are only half as many rooms as walls in any direction). Then we use tiles.setCellTint to color the maze green.

Then, the for loop repeatedly selects a random room, and sets either the north or east wall to false, removing it. The code in maze.ms defines rooms this way, each room only responsible for the walls to its north and east side. How would you be expected to know this? Why, by reading the maze.ms code, of course! (Or by reading this blog post.)

Once the rooms have been edited, we call maze.setCornersFromRooms and maze.updateTiles to update the actual display on screen.

Run the program again, and you should see a green maze with extra missing walls (sometimes even producing a dot, where a corner isn't attached to any walls at all).

Screen shot of program result at this point

Create a MazeSprite class

Now to make this maze into a game, we need some sprites to move around in it. And we want those things to move only in valid ways, i.e. not through walls. We'll use the same Room data structure that the maze demo already defined for creating the maze, to determine where sprites can move.

So, edit your program again, and append this code:

MazeSprite = new Sprite
MazeSprite.col = 0
MazeSprite.row = 0
MazeSprite.goToRoom = function(roomCol, roomRow)
    self.col = roomCol; self.row = roomRow
    self.x = cellSize * (roomCol * 2 + 1)
    self.y = cellSize * (roomRow * 2 + 1)
end function

MazeSprite.canMove = function(dx, dy)
    col = self.col; row = self.row
    if dx and dy then return false
    if not 0 <= round(col + dx) <= maxCol then return false
    if not 0 <= round(row + dy) <= maxRow then return false
    if dx < 0 then return not maze.rooms[self.col-1][self.row].east
    if dx > 0 then return not maze.rooms[self.col][self.row].east
    if dy < 0 then return not maze.rooms[self.col][self.row-1].north
    if dy > 0 then return not maze.rooms[self.col][self.row].north
end function

MazeSprite.move = function(dx, dy)
    if not self.canMove(dx, dy) then return
    col = self.col; row = self.row
    for t in range(0, 1, 0.1)
        self.goToRoom col + dx*t, row + dy*t
        yield
    end for
    self.goToRoom round(self.col), round(self.row)
end function
Enter fullscreen mode Exit fullscreen mode

This creates a Sprite subclass called MazeSprite. A MazeSprite knows what room column and row it's in, and in the goToRoom method, it also converts those to screen coordinates. Then there's the canMove method, which determines whether the sprite can move in a given direction. This includes bounds checks, as well as checking the appropriate wall.

Finally, we have a move method that, if the sprite is able to move in the given direction at all, does so smoothly over 10 frames. (In a full game you might need a fancier approach that does not block the rest of the game while moving -- but for today's game, there is only one sprite moving at a time, so this works fine.)

You can run your program again at this point, just to ensure you haven't made any typos. But if everything is good, it won't look any different from before. We've defined the MazeSprite class, but we're not actually doing anything with it yet.

Add the sprites

It's time to add our characters to the maze! I chose to make my game about a mouse questing for cheese, but feel free to explore the pictures available in /sys/pics and make it a bunny chasing carrots, or whatever you like. (The findFile command is a great way to explore!)

So edit your program again, and add this code:

player = new MazeSprite
player.image = file.loadImage("/sys/pics/animals/mouse.png")
player.scale = 1/3
player.goToRoom 0,0
display(4).sprites.push player

cheese = new MazeSprite
cheese.image = file.loadImage("/sys/pics/food/Cheese.png")
cheese.scale = 1/2
cheese.goToRoom round(rnd*maxCol), round(rnd*maxRow)
display(4).sprites.push cheese
Enter fullscreen mode Exit fullscreen mode

This is pretty straightforward: we create a player sprite in room 0,0, and a cheese sprite in some random room. Both sprites are scaled down in order to fit into the maze. Run your program now, and you should see these new additions to the game. But the program still exits immediately to the blinking cursor.

The main loop

We're almost done! Let's just load a sound to play when the mouse gets the cheese, and then add the main loop:

gotSound = file.loadSound("/sys/sounds/munch.wav")

while not key.pressed("escape")
    yield
    dx = sign(round(key.axis("Horizontal", false)))
    dy = sign(round(key.axis("Vertical", false)))
    if not dx and not dy then continue
    player.move dx, dy
    if player.row == cheese.row and player.col == cheese.col then
        cheese.goToRoom round(rnd*maxCol), round(rnd*maxRow)
        gotSound.play
    end if
end while
key.clear
Enter fullscreen mode Exit fullscreen mode

Our main loop here is pretty simple. We start by getting the horizontal (dx) and vertical (dy) axis inputs. Using key.axis allows the player to use arrow keys, WASD, or even a game pad. We round that and take the sign so that we get clean input values of -1, 0, or 1 for each axis. And if we don't have any input, we just continue at the top of the loop (where we included a yield, as all good main loops do).

When we do have an input, then we call player.move, and then check whether the player is now in the same location as the cheese. When that's the case, we play the munch sound and teleport the cheese to a different room.

Screen shot of the final game

Try it! It's such a simple game, and yet it is immediately engaging. You will see how you could build on this in all sorts of ways: make the goal move around, add enemies, add some sort of shooting, provide doors that can open or close or pivot, add sub-goals like visiting every part of the maze, etc.

Comments? Questions?

What was your favorite maze game? What sort of maze game would you like to see in Mini Micro? And what's stopping you from creating that game right now? Share your thoughts in the comments below!

Top comments (0)