HTML5 Diamond Digger Saga prototype made with Phaser – adding dirt and water

Read all posts about "" game

If you followed me last week, I published an HTML5 Diamond Digger Saga prototype made with Phaser which is mostly based on flood fill algorithm.

While in the first post I only showed how to remove and replace diamonds, now it’s time to see how to create dust and water tiles. Obviously, water behavior too is managed by flood fill algorithm. I am going to use Phaser in this example too.

This is what we are going to do now:

You can also play with your mobile device at this link.

You should know how to play: click/tap on a gem to remove it and all contiguous gems of the same color. Removing a gem will also remove the dirt below the gem, if any, to make water flow.

Here it is the source code, with new lines highligthed:

<!doctype html>
<html>
	<head>
		<style>
          	body{
				margin:0;
				padding:0;
			}
          </style>
		<script src="phaser.min.js"></script>
		<script type="text/javascript">
		 
			var game = new Phaser.Game(315,315,Phaser.CANVAS,"",{preload:onPreload, create:onCreate});

			var tileSize = 35;                	// tile size, in pixels
			var fieldSize = 9;				// number of tiles per row/column
			var tileTypes = 3;                 // different kind of tiles allowed
			var tileArray = [];                // array with all game tiles
			var groundArray = [];			// array with rocks and water
			var tileGroup;                     // group for sprites representing the tiles
			var groundGroup;                   // group for sprites representing the ground

			function onPreload() {
				game.load.spritesheet("tiles","assets/tiles.png",35,35);
				game.load.image("rock","assets/rock.png");
				game.load.image("water","assets/water.png");
				game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
				game.scale.setScreenSize(true);
			}
			
			function onCreate(){
				groundGroup = game.add.group();
				tileGroup = game.add.group();
				for(i=0;i<fieldSize;i++){
					tileArray[i]=[];
					groundArray[i] = [];
					for(j=0;j<fieldSize;j++){
						var randomTile = Math.floor(Math.random()*tileTypes)
						var theTile=game.add.sprite(j*tileSize+tileSize/2,i*tileSize+tileSize/2,"tiles");
						theTile.frame = randomTile;
						theTile.anchor.setTo(0.5,0.5);
						tileGroup.add(theTile);
						tileArray[i][j]=theTile;
						if(Math.random()>0.4){
							var theRock = game.add.sprite(j*tileSize+tileSize/2,i*tileSize+tileSize/2,"rock");
							theRock.anchor.setTo(0.5,0.5);
							groundGroup.add(theRock);
							groundArray[i][j]=theRock;	
						}
					}
				}
				var waterCol = Math.floor(Math.random()*fieldSize);
				if(groundArray[0][waterCol]!=null){
					groundArray[0][waterCol].destroy();
					groundArray[0][waterCol]=null;	
				}
				waterFill(0,waterCol)
				game.input.onDown.add(pickTile, this);	
			}

			// a tile has been picked
			function pickTile(){
				// save input coordinates
				startX = game.input.worldX;
				startY = game.input.worldY;
				// retrieve selected row and column 
				selectedRow = Math.floor(startY/tileSize);
				selectedCol = Math.floor(startX/tileSize);
				// delete using flood fill
				floodFill(selectedRow,selectedCol,tileArray[selectedRow][selectedCol].frame);
				// make existing gems fall down
				fallDown();
				// replenish game field from the top 
				fallFromTop();
			}
			
			function waterFill(row,col){
				if(row>=0 && row<fieldSize && col>=0 && col<fieldSize){
					if(groundArray[row][col]==null){
						var theWater = game.add.sprite(col*tileSize+tileSize/2,row*tileSize+tileSize/2,"water");
						theWater.anchor.setTo(0.5,0.5);
						groundGroup.add(theWater);
                              groundArray[row][col]=theWater;
                              waterFill(row+1,col);
	                         waterFill(row-1,col);
	                         waterFill(row,col+1);
	                         waterFill(row,col-1);
					}
				}	
			}
			
			function floodFill(row,col,val){
				if(row>=0 && row<fieldSize && col>=0 && col<fieldSize){
					if(tileArray[row][col]!=null && tileArray[row][col].frame==val){
						tileArray[row][col].destroy();
						tileArray[row][col]=null;
                              if(groundArray[row][col]!=null && groundArray[row][col].key=="rock"){
                                   groundArray[row][col].destroy();
                                   groundArray[row][col]=null;  
                                   if(nextToWater(row,col)){
                                        waterFill(row,col);
                                   }   
                              }
						floodFill(row+1,col,val);
	                         floodFill(row-1,col,val);
	                         floodFill(row,col+1,val);
	                         floodFill(row,col-1,val);	
					}
				}	
			}
               
               function nextToWater(row,col){
                    if(col>0 && groundArray[row][col-1]!=null && groundArray[row][col-1].key=="water"){
                         return true;
                    }
                    if(row>0 && groundArray[row-1][col]!=null && groundArray[row-1][col].key=="water"){
                         return true;
                    }
                    if(row<fieldSize-1 && groundArray[row+1][col]!=null && groundArray[row+1][col].key=="water"){
                         return true;
                    }
                    if(col<fieldSize-1 && groundArray[row][col+1]!=null && groundArray[row][col+1].key=="water"){
                         return true;
                    }
                    return false;
               }
			
			function fallDown(){
				for(var i=fieldSize-1;i>=0;i--){
					for(var j=0;j<fieldSize;j++){
						if(tileArray[i][j]!=null){
		                         var delta = holesBelow(i,j);
		                         if(delta>0){
		                         	var tileTween = game.add.tween(tileArray[i][j]);
								tileTween.to({
									y: (i+delta)*tileSize+tileSize/2
								},800,Phaser.Easing.Cubic.Out,true);
		                              tileArray[i+delta][j]=tileArray[i][j];
	                              	tileArray[i][j]=null;
		                         }
						}	
					}
				}
			}
			
			function fallFromTop(){
				for(i=0;i<fieldSize;i++){
					var holes = holesBelow(-1,i);
					for(j=0;j<holes;j++){
						var randomTile = Math.floor(Math.random()*tileTypes);
						var tileXPos = i*tileSize+tileSize/2;
						var tileYPos = -(holes-j)*tileSize-tileSize/2;
						var theTile = game.add.sprite(tileXPos,tileYPos,"tiles");
						theTile.frame = randomTile;
						theTile.anchor.setTo(0.5,0.5);
						tileGroup.add(theTile);
						tileArray[j][i]=theTile;		
	                    	tileTween = game.add.tween(tileArray[j][i]);
						tileTween.to({
							y: j*tileSize+tileSize/2
						},800,Phaser.Easing.Cubic.Out,true);	
					}
				}
			}
			
			function holesBelow(row,col){
				var holes = 0;
				for(var i=row+1;i<fieldSize;i++){
					if(tileArray[i][col]==null){
						holes++;
					}		
				}
				return holes;				
			}
			           
		</script>
    </head>
    <body>
    </body>
</html>

Let’s have a look at the new lines:

Lines 19-21: declaration of new variables to use: groundArray to store ground information, and two groups to properly manage display list. Gems will always stay on top of the ground.

Lines 25-26: preloading new graphic assets

Lines 32-33: adding the groups. Notice tile group is added after ground group, to make it stay in front of the stage.

Line 42: now gems are placed on the proper group

Lines 44-49:placing random dirt tiles

Lines 52-57: choosing a random tile on the first row, removing the dirt if any, then add water. waterFill function will perform flood fill with water and dirt.

Lines 77-90: flood fill algorithm applied to water

Lines 97-103: removing the dirt if under a gem we are removing. If the dirt we just removed is next to a water tile, we need to perform another flood fill

Lines 112-126: simple function to detect if there’s water next to a given tile

And this is the entire Diamond Digger Saga prototype. I must admit I did not play it that much, so if you want me to add some extra feature, just leave a comment and explain how should it work. Meanwhile you can download the full source code.

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

215 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
// Stairs
// 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