Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Diamond Digger Saga game, Game development, HTML5, Javascript and Phaser.

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.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.