“zNumbers” game level generator commented source code released

Read all posts about "" game
Are you enjoying znumberz game, the remake of zNumbers? It’s free with no ads both for iOS and Android and you should definitively download and install it, or at least play online on triqui.com. One of the most interesting features in my opinion is the random level generator, which allows you to generate new levels and add them to the 10 levels of the original zNumbers game. Have a look at it:
Check previous posts of the series to know how to play, and let’s focus on level generation. Just hit “Restart” button to generate a new level, and look at the console to see the solution. The core is on generateRandomLevel(maxAttempts) method which has an argument called maxAttempts. The higher maxAttempts, the harder the level. Basically it works this way: * Start with an empty level * Pick a start position in a randomly choosen tile * There is a loop which is executed n times, where n is the number of tiles in the game * From start position, try maxAttempts times to generate a valid move – which means is landing on an empty tile – going in a random direction for a random (1 to 4) number of steps * If you find a valid move before maxAttempts attempts, set start position to the destination of valid move. * No matter if you found or not the valid move, execute the loop again. It’s easy to see The higher maxAttempts, the harder the level, and the more tiles already placed, the harder to find a valid move in less than maxAttempts attempts. The interesting thing is you can also write down the moves to solve the level. Look at the source code:
<pre class="wp-block-syntaxhighlighter-code">var game;

var gameOptions = {
	gameWidth: 700,
	gameHeight: 800,
    tileSize: 100,
	fieldSize: {
        rows: 6,
        cols: 6
    },
    colors: [0x999999, 0xffcb97, 0xffaeae, 0xa8ffa8, 0x9fcfff],
    directions: [
        new Phaser.Point(0, 1),
        new Phaser.Point(0, -1),
        new Phaser.Point(1, 0),
        new Phaser.Point(-1, 0),
        new Phaser.Point(1, 1),
        new Phaser.Point(-1, -1),
        new Phaser.Point(1, -1),
        new Phaser.Point(-1, 1)
    ]
}

window.onload = function() {

	game = new Phaser.Game(gameOptions.gameWidth, gameOptions.gameHeight);
    game.state.add("TheGame", TheGame);
    game.state.start("TheGame");
}



var TheGame = function(){};

TheGame.prototype = {

    preload: function(){
        game.stage.backgroundColor = 0xf5f5f5;
        game.load.image("tiles", "assets/sprites/tile.png");
        game.load.image("restart", "assets/sprites/restart.png");
    },

  	create: function(){
        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
		game.scale.pageAlignHorizontally = true;
		game.scale.pageAlignVertically = true;
        this.generateRandomLevel(60);
        this.createLevel();
  	},

    // function to generate a random playable level
    generateRandomLevel: function(maxAttempts){

        console.log("A POSSIBLE SOLUTION");

        // we will store the generated level here
        this.level = []

        // initializing the array
        for(var i = 0; i < gameOptions.fieldSize.rows; i++){
            this.level[i] = [];
			for(var j = 0; j < gameOptions.fieldSize.cols; j++){
                this.level[i][j] = 0;
            }
        }

        // choosing a random start position
        var startPosition = new Phaser.Point(game.rnd.integerInRange(0, gameOptions.fieldSize.rows - 1), game.rnd.integerInRange(0, gameOptions.fieldSize.cols - 1));

        // here we'll store the solution
        var solution = "";

        // we'll execute this process once for each tile in the game
        for(i = 0; i <= gameOptions.fieldSize.rows * gameOptions.fieldSize.cols; i++){

            // keeping count of how many attempts we are doing to place a tile
            var attempts = 0;

            // we repeat this loop...
            do{

                // choosing a random tile value from 1 to 4
                var randomTileValue = game.rnd.integerInRange(1, 4);

                // choosing a random direction
                var randomDirection = game.rnd.integerInRange(0, gameOptions.directions.length - 1);

                // given the start position and the tile value, we can determine the destination
                var randomDestination = new Phaser.Point(startPosition.x + randomTileValue * gameOptions.directions[randomDirection].x, startPosition.y + randomTileValue * gameOptions.directions[randomDirection].y);

                // we made one more attempt
                attempts ++;

            // until we find a legal destination or we made too many attempts
            } while(!this.isLegalDestination(randomDestination) && attempts < maxAttempts);

            // if we did not make too many attempts...
            if(attempts < maxAttempts){

                // updating solution string
                solution = "(" + startPosition.x + "," + startPosition.y + ") => (" + randomDestination.x + "," + randomDestination.y + ")\n" + solution;

                // inserting the tile in the field
                this.level[startPosition.x][startPosition.y] = randomTileValue;

                // start position now is the position of the last placed tile
                startPosition = new Phaser.Point(randomDestination.x, randomDestination.y);
            }
        }

        // these rows just display the solution in the Chrome console
        console.log(this.level);
        console.log(solution);
    },

    // function to check if a destination is legal
    isLegalDestination: function(p){

        // it's not legal if it's outside the game field
        if(p.x < 0 || p.y < 0 || p.x >= gameOptions.fieldSize.rows || p.y >= gameOptions.fieldSize.cols){
            return false;
        }

        // it's not legal if there's already a tile
        if(this.level[p.x][p.y]!=0){
            return false;
        }

        // ok, it's legal
        return true
    },

	createLevel: function(){
        this.tilesArray = [];
		this.tileGroup = game.add.group();
        this.tileGroup.x = (game.width - gameOptions.tileSize * gameOptions.fieldSize.cols) / 2;
        this.tileGroup.y = this.tileGroup.x;
  		for(var i = 0; i < gameOptions.fieldSize.rows; i++){
            this.tilesArray[i] = [];
			for(var j = 0; j < gameOptions.fieldSize.cols; j++){
				this.addTile(i, j);
			}
		}
        game.input.onDown.add(this.pickTile, this);
        game.add.button(game.width / 2, game.height - this.tileGroup.y, "restart", function(){
            game.state.start("TheGame");
        }, this).anchor.set(0.5, 1);
	},

	addTile: function(row, col){
		var tileXPos = col * gameOptions.tileSize + gameOptions.tileSize / 2;
		var tileYPos = row * gameOptions.tileSize + gameOptions.tileSize / 2;
        var theTile = game.add.sprite(tileXPos, tileYPos, "tiles");
        theTile.anchor.set(0.5);
        theTile.width = gameOptions.tileSize;
        theTile.height = gameOptions.tileSize;
        var tileValue = this.level[row][col];
        theTile.tint = gameOptions.colors[tileValue];
        var tileText = game.add.text(0, 0, tileValue.toString(), {
            font: (gameOptions.tileSize / 2).toString() + "px Arial",
            fontWeight: "bold"
        });
        tileText.anchor.set(0.5);
        tileText.alpha = (tileValue > 0) ? 0.5 : 0
        theTile.addChild(tileText);
        this.tilesArray[row][col] = {
            tileSprite: theTile,
            value: tileValue,
            text: tileText
        };
	    this.tileGroup.add(theTile);
	},

    pickTile: function(e){
        this.resetTileTweens();
        var posX = e.x - this.tileGroup.x;
        var posY = e.y - this.tileGroup.y;
        var pickedRow = Math.floor(posY / gameOptions.tileSize);
        var pickedCol = Math.floor(posX / gameOptions.tileSize);
        if(pickedRow >= 0 && pickedCol >= 0 && pickedRow < gameOptions.fieldSize.rows && pickedCol < gameOptions.fieldSize.cols){
            var pickedTile = this.tilesArray[pickedRow][pickedCol];
            var pickedValue = pickedTile.value;
            if(pickedValue > 0){
                this.saveTile = new Phaser.Point(pickedRow, pickedCol);
                this.possibleLanding = [];
                this.possibleLanding.length = 0;
                this.setTileTweens(pickedTile.tileSprite);
                for(var i = 0; i < gameOptions.directions.length; i++){
                    var newRow = pickedRow + pickedValue * gameOptions.directions[i].x;
                    var newCol = pickedCol + pickedValue * gameOptions.directions[i].y;
                    if(newRow < gameOptions.fieldSize.rows && newRow >= 0 && newCol < gameOptions.fieldSize.cols && newCol >=0 && this.tilesArray[newRow][newCol].value == 0){
                        this.setTileTweens(this.tilesArray[newRow][newCol].tileSprite);
                        this.possibleLanding.push(new Phaser.Point(newRow, newCol));
                    }
                }
            }
            else{
                if(this.pointInArray(new Phaser.Point(pickedRow, pickedCol))){
                    this.tilesArray[pickedRow][pickedCol].value = -1;
                    this.tilesArray[pickedRow][pickedCol].text.alpha = 0.5;
                    this.tilesArray[pickedRow][pickedCol].text.text = this.tilesArray[this.saveTile.x][this.saveTile.y].value.toString();
                    this.tilesArray[this.saveTile.x][this.saveTile.y].value = 0;
                    this.tilesArray[this.saveTile.x][this.saveTile.y].tileSprite.tint = gameOptions.colors[0];
                    this.tilesArray[this.saveTile.x][this.saveTile.y].text.alpha = 0;
                }
                this.possibleLanding = [];
                this.possibleLanding.length = 0;
            }
        }
    },

    setTileTweens: function(tile){
        this.pulseTween = game.add.tween(tile).to({
            width: gameOptions.tileSize * 0.8,
            height: gameOptions.tileSize * 0.8
        }, 200, Phaser.Easing.Cubic.InOut, true, 0, -1, true);
    },

    resetTileTweens: function(){
        var activeTweens = game.tweens.getAll();
        for(var i = 0; i < activeTweens.length; i++){
            activeTweens[i].target.width = gameOptions.tileSize;
            activeTweens[i].target.height = gameOptions.tileSize;
        }
        game.tweens.removeAll();
    },

    pointInArray: function(p){
        for(var i = 0; i < this.possibleLanding.length; i++){
            if(this.possibleLanding[i].x == p.x && this.possibleLanding[i].y == p.y){
                return true;
            }
        }
        return false;
    }
}</pre>
Download the source code and play with it, in my opinion the best levels are generated with maxAttemtps ranging from 40 to 80. And don’t forget to get znumberz for iOS or Android.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers