Randomly generating Tickles Penguin’s Puzzles

The challenge

We want to be able to create a lot of levels for the game. Indeed, for some game modes, such as “puzzle rush”, or our future multiplayer modes, level diversity is really important.

However, while creating a level is really fun, creating 100 or 300 levels in a row proved to be quite cumbersome. We decided to try to create an random level generator.

1.Tickles Penguin’s rules

The first step was to create an object representing the game and its rules.

Example of level

Here are the game rules :

  • A penguin is located in a 8×16 board. The goal is to reach the finish line, at the edge of the board.
  • The player decides to move up, left, right or down so that the penguin slides until it reaches a block that would make it stop. Only then, the player can decide to move to another direction to try to reach the end.

Different types of blocks are available in the game :

Finish LineYou win the game if you reach this block.
PicksIf you touch the picks, you lose.
TreadmillThe treadmill will force your penguin to follow the direction indicated by the arrows.
SnowThe snow makes the penguin stop on the block.
WallThe wall makes the penguin stop in front of it.
TreeThe tree makes the penguin stop in front of it.
Door (yes, it is a door)The door makes the penguin teleport to another predefined door.
BridgeThe bridge makes your penguin go over the block under it.
For example, if you put a bridge above a wall, bridge allows go across the wall.
Blocks in Tickles Penguin

The first challenge was to code this using python. We ended up with an object called Board, which is a class in which you can set the size of the board, the penguin’s starting position, and position all the blocks.

In the Board class, a method called simulate_movement allows you to indicate a starting position, a direction, and get at the end the CharacterState representing the position of the penguin, whether it is still alive and whether you won the game.

    def simulate_movement(
        self,
        position: PositionInput,
        direction: DirectionInput,
        *,
        stop_on_stop: bool = True,
        stop_on_win: bool = True,
        stop_on_death: bool = True,
    ) -> CharacterState:
        """Simulate movement of the character in a direction.

        Args:
            direction: The direction of the movement.
            position: The position of the character.
            stop_on_stop: Whether to stop when the character stops.
            stop_on_win: Whether to stop when the character wins.
            stop_on_death: Whether to stop when the character dies.

        Returns:
            The state of the character after the movement simulation.
        """
        character = Character(position)
        character.direction = direction

        self.move_character_until_end(
            character,
            stop_on_stop=stop_on_stop,
            stop_on_win=stop_on_win,
            stop_on_death=stop_on_death,
        )
        return character.get_state()

2. Generating a random path

Once we are able to code game mechanics, we can now start creating our random levels.

To do so, we used the following algorithm:

  1. Initialize random starting and ending positions
  2. From the starting position, initialize a random step by choosing:
    • A random orientation (horizontal or vertical)
    • A random number of steps (which can be negative). For exemple, horizontal movement with -5 steps means you are moving 5 steps to the left. The random number of steps must be chosen so that the penguin does not leave the board.
  3. For the following iterations (depending on how many moves you want to player to do to reach the end), you simply:
    • Change the orientation (if previous orientation was horizontal, the next orientation if vertical and vice versa)
    • Choose a random number of steps with the following constraints
      • The penguin does not leave the board,
      • The penguin does reach a previous location,
      • The penguin does not cross other steps too much
  4. To reach the end, generate steps to go to the end.

NB: With hindsight, it would have been far better to create random levels starting from the end and without predefined starting positions. Indeed, the method we followed made us write extra lines of code to avoid creating easy levels in which the starting position is in the same axis as the ending position, or a move make you go to the same axis as the ending position.

Here is the result of the random creation of a path:

Result of creating a random path

3. Generating random “constraint” paths

To lure the player and not just create one path, we can also create other paths the penguin must be able to follow using the same method:

Example of creating other random paths

4. Adding blocks

Next, we generate blocks making the level possible (-6 represents a vertical bridge, -5 a horizontal bridge, -3 a wall, -4 is snow, 1 the starting position, -2 the ending position) :

Adding blocks

The blocks must be created so that :

  • The winning path is feasible
  • The constraint path are feasible, only if it does not make the winning path impossible

We can add new constraint paths to make the level more interesting:

Adding new blocks with other constraint paths

5. Coding a game solver

Then, we need to create an game solver to make sure :

  1. The level is feasible
  2. The level is only feasible through the winning path we created, and not some other easier paths.

To this end, the solution is to create a directional graph with all the possible moves the penguin can do. Starting from the penguin initial position, we simulate each and every move (up, down, left, right). The shortest path is then simply the path from the starting position to the finish line with the minimum number of steps.

6. Adding blocks to stop shorter paths

Now that we have a game solver, we can add blocks to stop shorter paths to be possible.

For example, in the following illustration, we added a vertical bridge to prevent the blue path from being be feasible.

Blocking paths

We keep iterating, 10 times, to get the following result:

Final level

Once the level is created, we can choose to accept/or reject it depending on:

  1. Whether the shortest path has enough steps (let’s say we want between no less than 8 moves to finish a level),
  2. Whether the level does not have too much bridges or walls, for example.

And here is the final level in the game:

Generated level in the game, could you solve it?

Don’t hesitate to post comments. 🙂

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top