topical media & game development
mobile-query-game-demos-1-mechalchemist.js / js
//global constant:
var numberOfColumn = 6;
var numberOfRow = 5;
var maxRow = 8; // the limit is the maxRow 8th line
var playgroundHeight = 600;
var playgroundWidth = 600;
var spriteHeight = 60;
var spriteWidth = 60;
var eraseTimeout = 800;
var pointPerBall = 10;
//global variable to store the destination ot the "player"
var sidemove = 0;
var wanapick = false;
var wanadrop = false;
var wanaexpand = false;
var wanaerase = false;
var eraseTimedout = false;
var destination = 3; //those value store the position of the probe for
var pickDestination = 0; // the current action
var convertedBallx = 0;
var convertedBally = 0;
var moveDestinationX = 3;
//the describtion of the probe state
var PROBE_IDLE = 1; // The idle state
var PROBE_PICKING = 3; // The probe is picking a ball
var PROBE_PICKED = 4; // The probe is returning to it's place
var PROBE_DROPING = 5; // some balls are begin droped
var PROBE_DROPED = 6; // the aftermath of a ball droping
var BOARD_COLAPSE = 8; // the board is collapsing after a reduction
var BOARD_EXPAND = 9; // this is when a line is added on top
var BOARD_ERASING = 10; // the board is animating to show the erasing
var BEAM_ERASING = 11; // the animation of erasing of the beam reaction
var GAME_OVER = 12; // the game is over ;(
var gameState = PROBE_IDLE;
var storedColor = 0;
var storedNumber = 0;
var score = 0;
var comboCount = 0;
var comboMax = 0;
//some hellper functions :
this function simply set the eraseTimedout to true
function timedEarse(){
eraseTimedout = true;
}
This funciton returns the number of the row containing the down most, non-null
element of this column. The table is suposed to be square and in row x column
ord
function columnFirstElement(column){
var downMostLine = column.length-1;
while(column[downMostLine] == null){
downMostLine--;
if(downMostLine==-1){
break;
}
}
return downMostLine;
}
This function returns a 2-dimentional array of boolean and a number.
function reduceArray(beamNumberOfBalls, beamPositionX, beamPositionY){
// the value to return:
var markedNumber = 0;
var resultArray = new Array(numberOfColumn);
for(var i=0; i<numberOfColumn; i++){
// the value of the cells of the middle
// depends of the value of the ballArray
resultArray[i] = new Array(maxRow);
for(var j=0; j< maxRow; j++){
if(ballsArray[i][j] == null){
// if there is no cell here it cannot be deleted
resultArray[i][j] = false;
} else {
resultArray[i][j] = null;
}
}
}
// this is a recurrent way to browse the array:
// x and y are the coordinate of the cells whose
// neighbours have to be checked.
function markNeighborCells(x,y){
// the column at the left of x
if(x > 0){
// is it the first time we check this cell
if(resultArray[x-1][y] == null){
// is this cell of the right color
if(ballsArray[x-1][y] == storedColor){
resultArray[x-1][y] = true;
markedNumber++;
// we check the neighbors
markNeighborCells(x-1,y);
} else {
resultArray[x-1][y] = false;
}
}
}
// the column of x
if(y > 0){
// is it the first time we check this cell
if(resultArray[x][y-1] == null){
// is this cell of the right color
if(ballsArray[x][y-1] == storedColor){
resultArray[x][y-1] = true;
markedNumber++;
// we check the neighbors
markNeighborCells(x,y-1);
} else {
resultArray[x][y-1] = false;
}
}
}
if(y+1 < maxRow){
// is it the first time we check this cell
if(resultArray[x][y+1] == null){
// is this cell of the right color
if(ballsArray[x][y+1] == storedColor){
resultArray[x][y+1] = true;
markedNumber++;
// we check the neighbors
markNeighborCells(x,y+1);
} else {
resultArray[x][y+1] = false;
}
}
}
// the column at the right of x
if(x+1 < numberOfColumn){
// is it the first time we check this cell
if(resultArray[x+1][y] == null){
// is this cell of the right color
if(ballsArray[x+1][y] == storedColor){
resultArray[x+1][y] = true;
markedNumber++;
// we check the neighbors
markNeighborCells(x+1,y);
} else {
resultArray[x+1][y] = false;
}
}
}
}
//here we startWith the element of the beam and check for neighbors:
for(var i = 0; i < beamNumberOfBalls; i++){
markNeighborCells(beamPositionX,beamPositionY+i);
}
return {array: resultArray, number: markedNumber};
}
function chainReaction(){
var result = new Array(spriteList.length) // one entry per color
for(var i=0; i < spriteList.length; i++){
result[i] = new Array();
}
// this is a recurrent way to browse the array:
// x and y are the coordinate of the cells whose
// neighbours have to be checked.
function markNeighborCellsInGroup(x,y,color,group){
var markedNumber = 0;
// the column at the left of x
if(x > 0){
// is it the first time we check this cell
if(result[color][group].array[x-1][y] == null){
// is this cell of the right color
if(ballsArray[x-1][y] == color){
result[color][group].array[x-1][y] = true;
markedNumber++;
// we check the neighbors
markedNumber += markNeighborCellsInGroup(x-1,y,color,group);
} else {
result[color][group].array[x-1][y] = false;
}
}
}
// the column of x
if(y > 0){
// is it the first time we check this cell
if(result[color][group].array[x][y-1] == null){
// is this cell of the right color
if(ballsArray[x][y-1] == color){
result[color][group].array[x][y-1] = true;
markedNumber++;
// we check the neighbors
markedNumber += markNeighborCellsInGroup(x,y-1,color,group);
} else {
result[color][group].array[x][y-1] = false;
}
}
}
if(y+1 < maxRow){
// is it the first time we check this cell
if(result[color][group].array[x][y+1] == null){
// is this cell of the right color
if(ballsArray[x][y+1] == color){
result[color][group].array[x][y+1] = true;
markedNumber++;
// we check the neighbors
markedNumber += markNeighborCellsInGroup(x,y+1,color,group);
} else {
result[color][group].array[x][y+1] = false;
}
}
}
// the column at the right of x
if(x+1 < numberOfColumn){
// is it the first time we check this cell
if(result[color][group].array[x+1][y] == null){
// is this cell of the right color
if(ballsArray[x+1][y] == color){
result[color][group].array[x+1][y] = true;
markedNumber++;
// we check the neighbors
markedNumber += markNeighborCellsInGroup(x+1,y,color,group);
} else {
result[color][group].array[x+1][y] = false;
}
}
}
return markedNumber;
}
for(var i=0; i < numberOfColumn; i++){
//for each column we check the conected groups from the first marked element:
if(chainReactionMarker[i] > -1){
for(var j=chainReactionMarker[i]; j < maxRow; j++){
if(ballsArray[i][j] != null){
var currentGroup = 0;
while((result[ballsArray[i][j]][currentGroup] != undefined) && (result[ballsArray[i][j]][currentGroup].array[i][j] == false)){
currentGroup++;
}
if((result[ballsArray[i][j]][currentGroup] == undefined)){ // then this case has not been linked to any group of this color
// we add a group!
// a group is made of:
// - a number that that reprensent the number of element in this group
// - an array where the case belonging to the group are marked by true
result[ballsArray[i][j]][currentGroup] = {number: 1, array: new Array(numberOfColumn)};
for(var k=0; k < numberOfColumn; k++){
result[ballsArray[i][j]][currentGroup].array[k] = new Array(maxRow);
for(var l=0; l < maxRow; l++){
if(ballsArray[k][l] == null){
result[ballsArray[i][j]][currentGroup].array[k][l] = false
} else {
result[ballsArray[i][j]][currentGroup].array[k][l] = null;
}
}
}
// the current element is part of this group
result[ballsArray[i][j]][currentGroup].array[i][j] = true;
// we now check the neighbors:
result[ballsArray[i][j]][currentGroup].number += markNeighborCellsInGroup(i,j,ballsArray[i][j],currentGroup);
}
// ELSE : this case already belongs to a group -> no need to check
}
}
}
}
return result;
}
// This function split the array for colliding, uses the global variables and return true if
// some splitting occured and flase otherwise.
function splitForCollide(){
var splited = false;
//for each column we search for the uper most void:
for(var i=0; i<numberOfColumn; i++){
var firstVoid = -1;
for(var j=0; j<maxRow; j++){
if(ballsArray[i][j] == null){
//we just found it!
firstVoid = j;
break;
}
}
//Is the void we found at an interesting position ?
if((firstVoid >= 0) && (firstVoid < (maxRow-1))){
// we find the collapsing part under the first void:
var nextNoVoid = firstVoid+1;
for(var j=nextNoVoid; j < maxRow; j++){
if(ballsArray[i][j] != null){
nextNoVoid = j;
break;
}
}
//do we have something to collapse ?
if((nextNoVoid > firstVoid) && (ballsArray[i][nextNoVoid] != null)){
splited = true;
//we remove the element from the array and put them to the "mobileElement" array
var collapsingParts = new Array(maxRow-nextNoVoid);
.gQ.Animation({ imageURL: "./mobile-query-game-demos-1-background.png"});
var gameOverAnimation = new .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-cache.png"});
var animationProbe = new .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-container.png"});
var animationContainerSide =new .gQ.ANIMATION_VERTICAL});
var eraseAnimation = new .gQ.ANIMATION_VERTICAL});
var beamAnimation = new .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-spark.png",
numberOfFrame: 5,
delta: 19,
rate: 60,
type: .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-containement-spark.png",
numberOfFrame: 5,
delta: 60,
rate: 60,
type: .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-coal.png"});
conversionList[0] = 1;
spriteList[1] = new .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-silver.png"});
conversionList[2] = 2;
spriteList[3] = new .gQ.Animation({ imageURL: "./mobile-query-game-demos-1-diamon.png"});
conversionList[4] = 1;
animatedSpriteList = new Array(5); //the list of animation for the balls
animatedSpriteList[0] = new .gQ.ANIMATION_VERTICAL});
animatedSpriteList[1] = new .gQ.ANIMATION_VERTICAL});
animatedSpriteList[2] = new .gQ.ANIMATION_VERTICAL});
animatedSpriteList[3] = new .gQ.ANIMATION_VERTICAL});
animatedSpriteList[4] = new .gQ.ANIMATION_VERTICAL});
//generate the initial array of "balls"
function generateRandomColumn(){
var tempArray = new Array(maxRow);
// the numberOfRow gives you the initial amount of bals per column
for(var i=0; i<numberOfRow; i++){
tempArray[i] = Math.round(Math.random()*(spriteList.length-1));
}
// the rest of the array is empty
for(var i=numberOfRow; i<maxRow; i++){
tempArray[i] = null;
}
return tempArray;
}
//beware this array is in column x row order !!!!
ballsArray = new Array(numberOfColumn);
ballsToErase = new Array(numberOfColumn);
for(var i=0; i<numberOfColumn; i++){
ballsArray[i] = generateRandomColumn();
ballsToErase[i] = new Array(maxRow);
for(var j=0; j < maxRow; j++){
ballsToErase[i][j] = false;
}
}
// this array contain the index of the first element for each column that
// has moved durring the collapse (for the chainreaction) and -1 if the
// column has not colided
chainReactionMarker = new Array(numberOfColumn);
for(var i=0; i<numberOfColumn; i++){
chainReactionMarker[i] = -1;
}
// this array contains the mobile elemenet during colapsing
// each element is of the folowing format:
// {desination: index, array: list_of_color}
mobileElement = new Array(numberOfColumn);
//initialize the game screen:
$("#playground").playground({height: playgroundHeight, width: playgroundWidth})
.addSprite("background",{posx: 0, posy: 0, height: playgroundHeight, width: playgroundWidth, animation: animationBG})
.addGroup("arm",{posx: 0, posy: 20, height: 580, width: 100, overflow: "visible"})
.addSprite("probe",{posx: 20, posy: 460, height: 580, width: 60, animation: animationProbe})
.addGroup("containerGroup",{posx:0,posy:500, height: 80, width: 100, overflow: "visible"})
.addSprite("container",{posx: 0, posy: 0, height: 80, width: 100, animation: animationContainer});
.playground()
.addSprite("backgroundMask",{posx: 400, posy: 482, height: 118, width: 200, animation: animationBGmask});
$("#containerGroup").addSprite("container-left",{posx: -600, posy: 0, height: 90, width: 600, animation: animationContainerSide});
$("#containerGroup").addSprite("container-right",{posx: 100, posy: 0, height: 90, width: 600, animation: animationContainerSide});
$("#container").addSprite("sparkEmpty",{ posx: 20, posy: 40, height: 19, width: 59, animation: sparkAnimation});
for(var i in ballsArray){
for(var j=0; j < numberOfRow; j++){
$("#board").addSprite(""+i+"-"+j,{posx: 20+(spriteWidth*i), posy: 20+(spriteHeight*j), height: spriteHeight, width: spriteWidth, animation: spriteList[ballsArray[i][j]]});
}
}
//Here we add the sound we will use durring the game:
TODO: add some sound*
// this sets the id of the loading bar:
.playground().append("<div style='position: absolute; top : 400px; right: 20px; width: 150px;font-weight: bold; font-size: 18pt; color: white;'><span id='score'>"+score+"</span> pts <br /> mult: <span id='combo'>"+comboCount+"</span>x</div>")
storedscore = 0;
// this is the function that control the expanding of the board
.playground().registerCallback(function(){
//cache for the arm div
if(this.armdiv == undefined){
this.armdiv = $("#arm");
}
//the probe can move always but when it's picking a ball
if(gameState!=PROBE_PICKING){
var incremant = 15;
//Do we had some move in the buffer ?
if(((moveDestinationX + sidemove)>=0)&&((moveDestinationX + sidemove) < numberOfColumn)){
moveDestinationX += sidemove;
}
sidemove = 0;
//what direction the prob is moving to?
var currentPos = this.armdiv.x();
if(currentPos < ((moveDestinationX*spriteWidth))){ // we need to go to the right
if(currentPos+incremant < (moveDestinationX*spriteWidth)){
this.armdiv.x(incremant, true);
} else {
this.armdiv.x(moveDestinationX*spriteWidth);
}
} else if (currentPos > (moveDestinationX*spriteWidth)) { //we need to go to the left
if(currentPos-incremant > (moveDestinationX*spriteWidth)){
this.armdiv.x(-incremant, true);
} else {
this.armdiv.x(moveDestinationX*spriteWidth);
}
}
}
}, 30);
//this is the function to control the probe action except the left-right movement
.playground().addGroup("dropBeam",{posx: 20+spriteWidth*destination, posy: 480, height: (spriteHeight*storedNumber > 400)?(spriteHeight*storedNumber):400, width: 60});
// store the name of the sprite depending on the color:
var dropedSprite = spriteList[storedColor];
//and add each ball
for(var k=0; k<storedNumber; k++){
$("#dropBeam").addSprite("dropBeam-"+k,{posx: 0, posy: (spriteHeight*k), height: spriteHeight, width: spriteWidth, animation: dropedSprite});
}
$("#dropBeam").addSprite("glowBeam",{posx: 0, posy: 0, height: 400, width: 60, animation:beamAnimation});
$("#contained").remove(); // we remove the ball from the container
$("#sparkFull").remove();
$("#container").addSprite("sparkEmpty",{ posx: 20, posy: 40, height: 19, width: 59, animation: sparkAnimation});
pickDestination = columnFirstElement(ballsArray[destination])+1;
gameState = PROBE_DROPING;
wanadrop = false;
} else { // nothing to do...
gameState = PROBE_IDLE;
}
}
break;
// This state is when the prob is going UP to take a ball
case PROBE_PICKING:
var incremant = 45;
var currentPos = $("#probe").y();
if(currentPos-incremant > pickDestination*60){
$("#probe").y(currentPos-incremant);
} else {
//we reached the ball!
$("#probe").y(pickDestination*60);
$("#"+destination+"-"+pickDestination).remove();
$("#probe").addSprite("pickedBall",{posx: 0, posy: 0, width: spriteWidth, height: spriteHeight, animation: spriteList[storedColor]})
ballsArray[destination][pickDestination] = null;
storedNumber++;
//we turn all balls of the same color to their "excited state"
if(storedNumber==1){
for(var i=0; i < numberOfColumn; i++){
for(var j=0; j < maxRow-1; j++){
if(ballsArray[i][j]==storedColor){
$("#"+i+"-"+j).setAnimation(animatedSpriteList[storedColor]);
}
}
}
}
gameState = PROBE_PICKED;
}
wanapick=false;
break;
// this state is when the prob is going down from taking a ball
case PROBE_PICKED:
var incremant = 45;
var currentPos = $("#probe").y();
if(currentPos + incremant < 460){
$("#probe").y(currentPos+incremant);
} else {
$("#probe").y(460);
// we add a ball to the container:
$("#pickedBall").remove();
if(storedNumber == 1){
$("#sparkEmpty").remove();
$("#container").addSprite("contained",{posx: 20, posy: 20, width: spriteWidth, height: spriteHeight, animation: spriteList[storedColor]});
$("#container").addSprite("sparkFull",{ posx: 20, posy: 20, height: 60, width: 59, animation: containementSparkAnimation});
}
gameState = PROBE_IDLE;
}
break;
//this state is when the prob is going up to drop all the balls it contain
case PROBE_DROPING:
var incremant = 45;
var currentPos = $("#dropBeam").y();
if(currentPos - incremant > pickDestination*spriteHeight+20){
$("#dropBeam").y(currentPos-incremant);
} else {
$("#dropBeam").y(pickDestination*spriteHeight+20);
$("#glowBeam").remove();
gameState = PROBE_DROPED;
}
wanadrop=false;
break;
//this state is when the prob is going down from droping a ball
case PROBE_DROPED:
// does the beam conects a least the right number of
// balls to triger a fusion ?
var result = reduceArray(storedNumber, destination, pickDestination);
if((result.number + storedNumber) > conversionList[storedColor]){
// add the right amount of points to the score
score += (result.number+storedNumber)*pointPerBall;
comboCount++;
// we mark the balls for removing
for(var i=0; i<result.array.length; i++){
for(var j=0; j<result.array[i].length; j++){
if(result.array[i][j]){
ballsToErase[i][j] = true;
}
}
}
//we add the converted one:
if((storedColor+1) < spriteList.length){
ballsArray[destination][pickDestination] = storedColor+1;
$("#board").addSprite(""+destination+"-"+pickDestination, {posx: 20+(spriteWidth*destination), posy: 20+(spriteHeight*pickDestination), height: spriteHeight, width: spriteWidth, animation: spriteList[storedColor+1]});
$("#dropBeam-0").remove();
}
wanaerase = true;
gameState = BEAM_ERASING;
} else {
// if not do we get outside of the limit ?
// then it's game over
if(pickDestination + storedNumber > maxRow-1){
gameState = GAME_OVER;
.playground().addSprite("gameover",{posx: 0, posy: 0, animation: gameOverAnimation, width: 600, height: 600});
$("#gameover").append('<div style="position: absolute; top: 340px; width: 600px; color: white;"><center><a style="cursor: pointer;" id="restartbutton">Click here to restart the game!</a></center></div>');
$("#restartbutton").click(restartGame);
} else { // nop! we'r good we can add a lline
// now we can hidde the board and start working on it
$("#board").contents( ).remove();
$("#board").y(0);
// shift the balls arrays line down and add a new line:
for(var i=0; i<numberOfColumn; i++){
for(var j=maxRow-2; j > 0; j--){
ballsArray[i][j] = ballsArray[i][j-1];
if(ballsArray[i][j] != null){
if((storedNumber > 0) && (storedColor == ballsArray[i][j])){ //is the ball in "excited state"
$("#board").addSprite(""+i+"-"+j,{posx: 20+(spriteWidth*i), posy: 20+(spriteHeight*j), height: spriteHeight, width: spriteWidth, animation: animatedSpriteList[ballsArray[i][j]]});
} else {
$("#board").addSprite(""+i+"-"+j,{posx: 20+(spriteWidth*i), posy: 20+(spriteHeight*j), height: spriteHeight, width: spriteWidth, animation: spriteList[ballsArray[i][j]]});
}
}
}
ballsArray[i][0] = newLine[i];
//if(ballsArray[i][j] != null){
if((storedNumber > 0) && (storedColor == ballsArray[i][0])){ //is the ball in "excited state"
$("#board").addSprite(""+i+"-0",{posx: 20+(spriteWidth*i), posy: 20, height: spriteHeight, width: spriteWidth, animation: animatedSpriteList[ballsArray[i][0]]});
} else {
$("#board").addSprite(""+i+"-0",{posx: 20+(spriteWidth*i), posy: 20, height: spriteHeight, width: spriteWidth, animation: spriteList[ballsArray[i][0]]});
}
//}
}
gameState = PROBE_IDLE;
}
}
break;
case BOARD_ERASING:
// Does the earsing begins ?
if(wanaerase){
for(var i=0; i < numberOfColumn; i++){
for(var j=0; j < maxRow; j++){
if(ballsToErase[i][j]){
$("#"+i+"-"+j).setAnimation(eraseAnimation);
}
}
}
wanaerase = false;
setTimeout("timedEarse()", eraseTimeout);
}
//cheack for the erasing timeout
if(eraseTimedout){
for(var i=0; i < numberOfColumn; i++){
for(var j=0; j < maxRow; j++){
if(ballsToErase[i][j]){
// we erase this ball
$("#"+i+"-"+j).remove();
ballsArray[i][j] = null;
ballsToErase[i][j] = false;
}
}
}
markForChainReaction();
if(chainReactionMarker[convertedBallx]>convertedBally){
chainReactionMarker[convertedBallx] = convertedBally;
}
splitForCollide()
eraseTimedout = false;
gameState = BOARD_COLAPSE;
}
break;
}
return false;
}, 30);
//this is the function to control the pickup movement
//initialize the start button
$("#startbutton").click(function(){
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.