Lecture 9: Puralax
1 Introducing the game
2 Stage 1:   Simple tiles
2.1 Model
2.2 View
3 Stage 2:   Walls
4 Stage 3:   Nested tiles
4.1 Controller
5 Stage 4:   Painter tiles
6 Stage 6:   Further extensions
7 Recap
7.5

Lecture 9: Puralax

1 Introducing the game

Puralax is a tile-based puzzle game, playable online or on most mobile phones. The graphics are fairly simple, the rules are reasonably straightforward, but the overall game is quite subtle. As the game progresses, new kinds of tiles are introduced. It’s taken the developer several years to introduce the latest kinds of tiles, and I suspect the internal design of the game was revised to accommodate those changes. We’ll repeat this process—much more quickly!—to see how various design considerations and patterns aid the implementation. The key challenge of this case-study is to see what design choices appear to suffice at first, but become limiting as the game becomes more complex. Along the way, we’ll revisit most of the patterns we’ve encountered so far, and see how they might be helpful.

2 Stage 1: Simple tiles

image

In this board we see the three simplest parts of the game. Tiles can have colors, and can have zero or more dots on them. The player can spend a dot to move a tile to an adjacent empty cell:

image

or to paint an adjacent tile of a different color:

image

The goal of the game is to make all the tiles the same as the border color of the level. The game ends when all tiles are the target color, or there are no more valid moves.

Painting is a flood-fill operation: all adjacent tiles of the same color will get painted:

image becomes image

2.1 Model

Do Now!

Design a model to represent this game so far. Consider how to represent tiles, dots, colors, and goals. What operations can a player take to modify the game as it’s being played?

Do Now!

Design a mechanism to more easily construct a level of this game. Separate your design into two layers: a factory that can construct the various tile types, and a builder that parses an input string and uses the factory to construct the model. For example, the first board above was written (the comments are for clarity, not part of the input)

3 3 B       // height 3, width 3, goal color Blue
1 1 M B 1   // cell (1,1) is Mobile, colored Blue, with 1 dot
1 2 I G     // cell (1,2) is Immobile, colored Green

Hints: use interfaces frequently. A key question will be how to represent “empty” cells: when a dot is spent to move a tile, how will your model update? Also, consider whether movable tiles (with more than zero dots on them) and immobile tiles (with zero dots on them) should be represented in the same way. What are the advantages and disadvantages of doing so? (There is no single “correct” answer here; you can justify several designs here.)

2.2 View

Do Now!

Design a textual view for the game, that gives back a mostly-for-debugging-purposes string explaining the state of the game.

Do Now!

Design a visual view for the game. Recall that we want to ensure that the view cannot modify the model. How might you structure the rendering of tiles, given that more tile types are going to be added in the future?

Hint: we really don’t want to use instanceof here, but we also really don’t want to add methods directly to our model that are graphics-specific (i.e., your model classes should not need to import java.awt.*). What patterns allow us to add this kind of functionality?

3 Stage 2: Walls

image

Dark gray tiles are walls that cannot be moved or painted.

Do Now!

Revise your model to account for walls. What representations do you need to change? What observations or model operations change as well?

Hint: consider what happens after you use the final dot on this blue tile to paint the green tile.

Do Now!

Revise your view to handle walls.

4 Stage 3: Nested tiles

image

A nested tile has multiple colors. Painting a cell like this paints only the outermost layer. For instance, here we use the blue tile first to paint the center tile:

image

But if that new color matches the next color in, then the layers merge. If we now use the yellow tile to paint this previous result, we would see:

image

Do Now!

Starting from the original board in this section, solve this level. Draw each of the intermediate boards.

Do Now!

Revise your model to account for nested cells. What representations do you need to change? What observations or model operations change as well?

Hint: suppose we “solved” the level by using the blue tile, then the yellow, the green, and finally the purple tile. Did we solve the level?

Do Now!

Revise your view to handle nested tiles.

4.1 Controller

Do Now!

It’s finally time to build a controller for the game, to make it playable. Do so. Consider how to ensure that your view knows nothing about the controller, and how to ensure that the controller knows nothing of the internal GUI details of your view. What high-level events occur in your game?

Do Now!

Add the ability to undo and redo moves.

Do Now!

Add the ability to customize the colors of the game, so that instead of the primary colors seen so far, the player can choose a custom theme.

5 Stage 4: Painter tiles

image

A painter tile is indicated by a bar at the bottom of the cell. When a tile is moved into that cell, it is automatically painted the color of that paint bar, and (as usual) any adjacent tiles that were the same color as the moved tile will be painted as well. In the example above, if we move the top tile to the middle, it will be painted blue, and the other green tile will get painted as well:

image

Do Now!

Solve the following board:

image

Do Now!

Solve the following board:

image

What do you think should happen when this painter tile gets used?

Do Now!

Revise your model to account for painter tiles. What representations do you need to change? What observations or model operations change as well?

Hint: what becomes trickier with moving tiles?

Do Now!

Revise your view to handle painter tiles.

6 Stage 6: Further extensions

Try writing a solver that automatically “plays” a level, and produces a sequence of moves that would solve it. It should only require your model, and be independent of your view or controller.

Try writing a level generator. This should only require your model, and be independent of your view or controller.

Try writing a level designer. This entails a new view and a new controller, but should not affect your model.

Try coming up with new tile types. This should affect your model and view, but not your controller.

7 Recap

Building Puralax has required nearly all of the patterns we’ve encountered so far, and motivated new uses for several of them. Reviewing how each of these patterns applies to each part of the MVC design is a good way to solidify your grasp of how we can use these patterns:

image