Back

Slide & Solve

Preface

“Slide & Solve” is a fast-paced sliding puzzle game where players race to recreate predefined patterns as quickly as possible. With simple rules, it's perfect for family game nights or casual gatherings with friends. Since you don't have any friends around, I made a digital prototype in Phaser, so you can compete against a computer. Just click on the tiles of your puzzle to move them.

Digital Prototype

Details

System Design

General Info

First, let's look at how the game would play out in real life. It's made for one or more players, with no real limit on how many can join in. It's great for ages 6 and up and usually takes about 15-30 minutes to play.

Game Components

Here's what would come with the game if it were published. It's also a good idea to have a pen, some paper, and a die handy:

4 Sliding Puzzles: Each player gets a 3x3 sliding puzzle with 8 colored tiles, leaving one space empty for movement. For the prototype, I 3D-printed 4 puzzles and added colored tape to the tiles.

Pattern Cards: A deck of cards featuring various patterns to recreate, including event cards for extra variety. I keep them on my phone for now since printing and cutting them to size takes forever and isn't worth it until the designs are finalized.

Setup

Each player takes a sliding puzzle and places it face down, while the pattern card deck is shuffled and placed face down in the center of the table. Every player starts with 3 lives.

Gameplay

  1. The starting player draws the top pattern card from the deck and places it face up in the center for everyone to see.
  2. All players count down together: “3, 2, 1, Go!” and pick up their sliding puzzles.
  3. Players slide their puzzle tiles simultaneously to match the pattern on the card.
  4. The first player to finish places their puzzle on top of the card, stopping the round.
  5. Check the pattern: if it's correct the player earns 1 point, if it's incorrect they lose 1 life.
  6. Set the puzzles face down for the next round.

Winning the Game

A player is eliminated when they run out of lives, and the game continues with the remaining players. The match can end in two ways: if a player earns 3 points, they win, or if only one player is left standing, that player is declared the winner.

Single Player Mode

Draw a pattern card and set a personal time limit to recreate the design. Try to solve as many cards as you can within the time limit, or challenge yourself against the computer in the prototype above.

Cards

As you may have noticed, the deck contains both white and black cards. The white cards are the standard template cards. You draw one, and your goal is to replicate the pattern as quickly as possible. The black cards, on the other hand, are event cards mentioned earlier. Each one introduces a unique twist to keep the game exciting. Below, you can see the different event cards I've created so far:

The Digital Prototype

Lastly, let me walk you through how the digital prototype works. I used Phaser to create a 3x3 sliding puzzle game where players compete against an AI opponent. The code sets up the game board, generates solvable puzzles, and lets both the player and the AI move tiles to match a given template. I've also included score tracking to include the competitive aspect.

Code Snippets


create() {
    // Initialize the computer's solution path array
    this.computerSolutionPath = [];

    // Generate a puzzle configuration for the template
    const templateConfiguration = this.generateSolvableConfiguration();
    // Generate puzzle configuration for both player and computer
    const puzzleConfiguration = this.generateSolvableConfiguration();

    // Create the template puzzle in the top-center
    this.createTemplatePuzzle(templateConfiguration);

    // Create the player and computer puzzles in the bottom area
    this.createPuzzles(puzzleConfiguration);

    // Create the score text
    this.createScores();

    // Start the computer move timer
    this.time.addEvent({
        delay: Phaser.Math.Between(800, 1400), // time between computer moves
        callback: this.computerMove,
        callbackScope: this,
        loop: true,
    });
}

The create() function in the GameScene class initializes the core elements of the game when the scene starts. It begins by setting up variables, such as the computer's solution path, and generating initial puzzle configurations for the template, player, and computer puzzles. The function then creates the visual representation of the template puzzle at the top of the game board and the player and computer puzzles at the bottom. It also adds score displays for both the player and the computer and starts a timer event to handle automated computer moves at randomized intervals. This interval can be adjusted to make the computer easier or harder to beat.


computerMove() {
    if (!this.computerTiles || !this.computerTiles.tilesMap) {
        console.error("Error: computerTiles or tilesMap is undefined");
        return;
    }

    // If we have no solution path, or it's empty, generate one
    if (!this.computerSolutionPath || this.computerSolutionPath.length === 0) {
        const currentState = Array.from(this.computerTiles.tilesMap.values()).map(
        (tile) => (tile ? tile.tileInfo.value : null)
        );
        const goalState = Array.from(this.templateTiles.tilesMap.values()).map(
        (tile) => (tile ? tile.tileInfo.value : null)
        );

        this.computerSolutionPath = solvePuzzle(currentState, goalState);
    }

    // === Introduce a random move chance ===
    // e.g. 30% chance to do a random legal move instead of following the solution
    const RANDOM_MOVE_CHANCE = 0.3; 
    const doRandomMove = Math.random() < RANDOM_MOVE_CHANCE;

    if (doRandomMove) {
        this.randomComputerMove();
        return;
    }

    // If we still have moves in the solution path, do one
    if (this.computerSolutionPath && this.computerSolutionPath.length > 0) {
        const currentState = Array.from(this.computerTiles.tilesMap.values()).map(
        (tile) => (tile ? tile.tileInfo.value : null)
        );
        const nextState = this.computerSolutionPath.shift();

        // Find how the empty tile changed from currentState to nextState
        const emptyIndex = currentState.indexOf(null);
        const nextEmptyIndex = nextState.indexOf(null);

        const dx = (nextEmptyIndex % gridSize) - (emptyIndex % gridSize);
        const dy =
        Math.floor(nextEmptyIndex / gridSize) -
        Math.floor(emptyIndex / gridSize);

        // The tile we need to move is the one that ended up in the old empty position
        const tileX = (emptyIndex % gridSize) + dx;
        const tileY = Math.floor(emptyIndex / gridSize) + dy;
        const tileKey = `${tileX},${tileY}`;
        const tileToMove = this.computerTiles.tilesMap.get(tileKey);

        if (tileToMove) {
        this.moveTile(tileToMove, this.computerTiles, "computer");
        }
    }
}

The computerMove() function controls the computer's automated puzzle moves during gameplay. It starts by checking that the computer's puzzle data is ready and, if needed, generates a solution path using an A* algorithm to align the computer's puzzle with the template. Each time it's called, the function either follows the solution path or, with a set probability, makes a random valid move. When following the path, it figures out the next state, finds the tile to move, and updates the puzzle. For random moves, it looks at all the tiles next to the empty space, picks one at random, and moves it. You can tweak the random move probability to make the computer more or less prone to mistakes.


checkMatch(type) {
    let puzzle;
    if (type === "player") {
        puzzle = this.playerTiles;
    } else if (type === "computer") {
        puzzle = this.computerTiles;
    } else {
        return;
    }

    if (!puzzle) {
        console.error("No puzzle data found for type:", type);
        return;
    }

    const isMatching = this.isMatchingTemplate(puzzle);
    if (isMatching) {
        if (type === "player") {
        playerScore++;
        this.playerScoreText.setText(`Player: ${playerScore}`);
        } else if (type === "computer") {
        computerScore++;
        this.computerScoreText.setText(`Computer: ${computerScore}`);
        // Clear out the computer's old solution path so it can recalc later
        this.computerSolutionPath = [];
        }

        // Re-randomize ONLY the template puzzle so that a new round can start
        const newConfiguration = this.generateSolvableConfiguration();
        this.applyStateToTiles(this.templateTiles, newConfiguration);
    }
}
    
    

The checkMatch(type) function checks if the player or computer puzzle matches the template. It figures out which puzzle to evaluate based on the type parameter (either "player" or "computer"). If there's a match, it updates the score, either adding to the player's or the computer's, and refreshes the score display. For the computer, it also clears the solution path so it can calculate a new one later. Once a match is found, the function generates a new solvable configuration for the template puzzle to kick off the next round. This function gets called every time a tile is moved, whether by the player or the computer.

And that's it! I've been prototyping physical games for a while, but this was the first one where I really felt like I had to take it further. Playing it with friends and family has been a lot of fun so far, especially since you can feel yourself improving with each round. The event cards also add tons of variety and lead to some hilarious interactions.

I created the digital prototype as a challenge to learn a new framework for web games: Phaser. It's incredibly powerful, and I'm definitely going to use it for more advanced projects in the future. But if you'd like to try the physical version of the game and own a 3D printer, feel free to reach out. I'd be happy to share the .stl files with you!