First of all, here's the completed game:
Pulp JSON:
Shape_Fill_-_Emit_Version_2.json.zip (5.8 KB)
Playdate PDX:
Shape_Fill_-_Emit_Version_2.pdx.zip (45.8 KB)
Changes
v1.2 (downloads above):
- Only use a single emit function type
- Swap shapes as they are processed to limit lag
v1.1:
- Recursive board update logic is now handled with multiple emit cycles
Shape_Fill_-_Emit_Version.json.zip (6.0 KB)
Shape_Fill_-_Emit_Version.pdx.zip (46.2 KB)
v1.0:
- Initial code upload
Shape Fill.json.zip (6.4 KB)
Shape_Fill.pdx.zip (45.7 KB)
Technical Info
I went into this project with two goals in mind; to get my feet wet with Pulp and to stretch the bounds of Pulp a bit. Shape fill was inspired by the game Flood-It, which consists of a grid of random colors with the goal being to flood the entire board with a single color within a turn limit.
Shapes
As the playdate's screen is monochromatic, color-based gameplay was obviously the first element that needed to be changed. I settled on a set of 6 unique shapes to represent the different "colors". These shapes are items so that they all can have behavioral scripts attached to them. I set up a 10x10 grid of placeholder objects in the middle of the screen surrounded by a border to contain them. Each of the shapes are labeled as shape{number}Board
so that randomization of the game board can be done on room load by the placeholder object:
on random do
num = random 1,6
swap "shape{num}Board"
end
Selection
Shape selection is handled by an invisible player character which only can move left and right on top of the shape selection area below the main game board. This is implemented with walls surrounding the shape selection area to limit the player character's movement:
The tiles in this area are world tiles named with the shape{number}
syntax to differentiate them from the item tiles that make up the game board and to prevent them from being updated with the rest of the game board. The player's appearance is swapped on each movement to match that of the tile below it and the arrow follows the player to provide information about which object the player is selecting. The player movement can be controlled from either the d-pad or the crank and the selection loops around in both directions.
Game Logic
[Edit] - Unfortunately, the recursive algorithm was too much for the Playdate hardware to handle in certain instances and was causing crashes. The below write-up will be retained for posterity, but isn't how the game is currently implemented. The code can still be accessed under the v1.0 changelog above.
One of the biggest restrictions to work around was handling the state of the game and updating the tiles without arrays or standard recursive functions. Normally, I would have stored the state of the game board in a 2d array. As there are no arrays in PulpScript, I utilized the screen as the storage of the game board's state and used functions to convert x,y positions to 1-dimensional positions and back again.
Implementing the flooding algorithm is easiest with recursion. However, with all user-made variables in PulpScript being global, the child functions created through recursion end up modifying the variables that will end up being used by their parents when the child function returns.
This problem took me a while to work my head around, but I eventually came up with a solution that allowed me to mimic recursion without the problems that global variables were causing. The magic lies in the event
object. From the docs:
In events being handled by an instance of a tile, its
x
andy
members will be set to the coordinates of the tile handling the event.
As such, for each cell on the board, I could "recursively" call each of its neighboring cells. This posed another problem, however -- what stops the cycle of endless calls?
This problem was complicated by the lack of arrays and local variables. With arrays we could just store a 1 dimensional view of the grid and then store 1s to mark cells as visited. With local variables, we could set each cell to have a visited flag. As neither of these solutions were supported by PulpScript, I had to get a bit creative. The solution I came up with was to duplicate the frames of all of the shapes and use the current frame number to determine if a cell had been updated or not. Then, I just reset all of the cells to frame 0 after the flooding algorithm had completed to prepare the game for the next selection.
if event.x>xOffset then
leftX = event.x
leftX -= 1
tell leftX,event.y to
call "getTileFrame"
end
if tileFrame!=1 then
targetTile = name leftX,event.y
if targetTile==startTile then
modifiedTiles += 1
tell leftX,event.y to
swap newTile
frame 1
call "updateNeighbors"
end
end
end
end
Final Thoughts
This ended up being a more difficult endeavor than I had anticipated, but both the process and end result were well worth the effort. The restrictions that come about from building a game in Pulp as well as the hardware itself lead to interesting design decisions. The process for architecting the game became an unexpectedly fun part of the whole process. There's also something to be said for completing a project and the satisfaction that brings.