Code viewer for World: Rythm of the night
 

// ==== Rythm of the night ===============================================================================================
// (c) Ancient Brain Ltd. All rights reserved.
// This code is only for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// This code may not be copied, re-published or used on any other website.
// To include a run of this code on another website, see the "Embed code" links provided on the Ancient Brain site.
// ==================================================================================================================




// ===================================================================================================================
// === Start of tweaker's box ======================================================================================== 
// ===================================================================================================================

// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?


AB.clockTick       = 20;            // many frames per second, with small moves, looks best  

AB.screenshotStep  = 1500;   
  
AB.maxSteps        = 100000;     
	

const SKYCOLOR 	= "#000000";

//the three images that represents the player (in this version it's three smileys)
var PLAYER_IMG = ['/uploads/sinfulsalad/neutralplayer.png',
                '/uploads/sinfulsalad/okplayer.png',
                '/uploads/sinfulsalad/badplayer.png'];
        
//the layers that constitute the background           
const BACKGROUND_IMG = ['/uploads/sinfulsalad/neoncity.jpg', 
                        '/uploads/sinfulsalad/citycropped.png', 
                        '/uploads/sinfulsalad/neonground.png'];

//the scrolling speed of each layer.
//For more on how the background works, check out the "Scrolling Background"
//world of Enhanced
const BACKGROUND_SPEED = [1, 2, 4];

//the images for the 4 arrows
var OBSTACLES_IMG = ['/uploads/sinfulsalad/left.png',
                    '/uploads/sinfulsalad/up.png',
                    '/uploads/sinfulsalad/right.png',
                    '/uploads/sinfulsalad/down.png',];
 
//the image of the heart                   
var LIFE_IMG = ['/uploads/sinfulsalad/heart.png'];

  
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================


// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.


//generate a random float between [ A ; B [
function randomfloatAtoB ( A, B )			 
{
 return ( A + ( Math.random() * (B-A) ) );
}


//returns a whole number in from the [A,B[ range
function randomintAtoB ( A, B )			 
{
 return  ( Math.floor ( randomfloatAtoB ( A, B ) ) );
}



function World() 
{ 
    
    //------------------------ GLOBAL VARIABLES --------------------------------

    //will contain the context of the canvas
    var ctx;
    
    //will keep track of how to draw the background
    var backgroundPos = [];
    var positionOffset;
    
    //here is stored the sequence of arrows of the level
    var obstacles = [];
    
    //the length of the sequence
    var nbObstacles;
    
    //the variable level is always worth 5 more than the level the player
    //is actually playing. For exemple, when he is playing level 1, this variable
    //contains the number 6.
    var level;
    
    //the movement speed of the level
    var speedMult;
    
    //describes which type of arrows will be present in the level.
    //it can take the values 38 to 41. The html keycode of the arrow keys goes
    //from 37 to 40. 'actionsPossible = 38' means that only the arrowkey 37 will
    //be present, 'actionsPossible = 39' means that both 37 and 38 will be present
    //'actionsPossible = 40' means that 37 38 and 39 will be present, etc...
    var actionsPossible;
    
    //used to code what happens when the game ends.
    //It can take the values 'going' and 'end'
    var gameState;
    
    //keep track of what input is needed from the player at any given moment
    var actionRequired;
    
    //keep track of wether the player did what was expected from him
    var success;
    
    //number of lives of the player
    var life;
    
    //serves only to keep track of when to display the sad smiley.
    //unhappy is a whole number that is decremented at each step, down to zero.
    //whenever 'unhappy > 0', the sad face is displayed
    var unhappy;
    
    
    
    //------------------------- INIT FUNCTIONS ---------------------------------
 
    //properly load all the images
    function initIMG()
    {
        
        var img;
        
        //background
        for (var i = 0; i<BACKGROUND_IMG.length ; i++)
        {
            img = new Image();
            img.src = BACKGROUND_IMG[i];
            BACKGROUND_IMG[i] = img;
            
            backgroundPos.push(0);
        }
        
        //arrows
        for (i = 0; i<OBSTACLES_IMG.length ; i++)
        {
            img = new Image();
            img.src = OBSTACLES_IMG[i];
            OBSTACLES_IMG[i] = img;
        }
        
        
        //player's smiley
        for (i = 0; i<PLAYER_IMG.length ; i++)
        {
            img = new Image();
            img.src = PLAYER_IMG[i];
            PLAYER_IMG[i] = img;
        }
        
        //heart  
        img = new Image();
            img.src = LIFE_IMG;
            LIFE_IMG = img;
    }
    
    
    //setup the program so it listens to the player's input on the arrow keys
    function keyDownHandler(e)		
    {
        if (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40)
        {
            if (actionRequired !== undefined && e.keyCode == obstacles[actionRequired])
                success = true;
                
            else unhappy = 10;
        }
    }
    
    
    //-------------------------- ENGINE FUNCTIONS ------------------------------
    
    
    function setupLevel()
    {
        life = 3;
        
        //resets the drawing position of the obstacles
        positionOffset = 0;
        
        level++;
        
        
        var speedDown = 0;
        
        //on level 1, allows 2 types of arrows to be displayed
        if (level >= 6)
        {
            actionsPossible = 39;
        }
        
        //on level 10, slows the game a little bit, and allows 3 types of arrows
        //to be displayed
        if (level >= 15)
        {
            speedDown = -0.6;
            actionsPossible = 40;
        }
        
        //on level 20, slows the game a little bit, and allows the 4 types of arrows
        //to be displayed
        if (level >= 25)
        {
            speedDown = -1.2;
            actionsPossible = 41;
        }
        
        //sets the right speed for the current level
        speedMult = (1+level/8)+speedDown;
        
        //generate the obstacles that will be part of this level
        generateLevel();
    }
    
    
    //generate the obstacles that will be part of this level, and store them into
    //obstacles[].
    //the levels are generated procedurally.
    function generateLevel()
    {
        //calculate the length of the sequence depending on the level
        nbObstacles = 10+Math.floor(level/3);
        
        //resets the array
        obstacles = [];
        
        //link each step of the sequence to either a specific arrow (represented
        //by the keycode of that arrow)(70% of the time) either nothing (represented
        //by the number -1)(30% of the time)
        for (var i = 0 ; i<nbObstacles ; i++)
        {
            if (randomintAtoB(0,10)>2)
                obstacles.push(randomintAtoB(37, actionsPossible));
            else obstacles.push(-1);
            
            //this additional -1 push is here to put a little bit of space between
            //each obstacle
            obstacles.push(-1);
        }
    }
    
    
    function drawBackground()
    {
        
        //skyscrapers
        backgroundPos[0] -= speedMult*BACKGROUND_SPEED[0]*AB.clockTick/30;
            if (backgroundPos[0] < -threeworld.canvas.width)
                backgroundPos[0] = 0;
                
        ctx.drawImage( BACKGROUND_IMG[0], backgroundPos[0], 0, threeworld.canvas.width, 400 );
        ctx.drawImage( BACKGROUND_IMG[0], threeworld.canvas.width+backgroundPos[0], 0, threeworld.canvas.width, 400 );
        
        //low city
        backgroundPos[1] -= speedMult*BACKGROUND_SPEED[1]*AB.clockTick/30;
            if (backgroundPos[1] < -threeworld.canvas.width)
                backgroundPos[1] = 0;
                
        ctx.drawImage( BACKGROUND_IMG[1], backgroundPos[1], threeworld.canvas.height-500, threeworld.canvas.width, 280 );
        ctx.drawImage( BACKGROUND_IMG[1], threeworld.canvas.width+backgroundPos[1], threeworld.canvas.height-500, threeworld.canvas.width, 280 );
        
        //floor
        backgroundPos[2] -= speedMult*BACKGROUND_SPEED[2]*AB.clockTick/30;
            if (backgroundPos[2] < -threeworld.canvas.width)
                backgroundPos[2] = 0;
                
        ctx.drawImage( BACKGROUND_IMG[2], backgroundPos[2], threeworld.canvas.height-250, threeworld.canvas.width, 250 );
        ctx.drawImage( BACKGROUND_IMG[2], threeworld.canvas.width+backgroundPos[2], threeworld.canvas.height-250, threeworld.canvas.width, 250 );
    }
    
    
    function drawPlayer()
    {
        var image;
        
        //if the player did what whas expected of him, display the happy smiley
        if (success)
            image = PLAYER_IMG[1];
            
        //display the sad face whenever the player does an unexpected input
        else if (unhappy > 0)
            image = PLAYER_IMG[2];
        
        //display the neutral smiley
        else
            image = PLAYER_IMG[0];
            
        ctx.drawImage( image, 220, threeworld.canvas.height-210, 100, 100);
        ctx.fillRect(430, threeworld.canvas.height-195, 5, 70);
    }
    
    
    //draw the arrows AND calculate new position
    function drawObstacles()
    {
        for (var i = 0 ; i < obstacles.length ; i++)
        {
            //if that step of the sequence corresponds to an arrow
            if(obstacles[i] >= 0)
            {
                ctx.drawImage(OBSTACLES_IMG[obstacles[i]-37], threeworld.canvas.width-positionOffset+50*i-10, threeworld.canvas.height-180, 70, 50);
                
                //if that step is finished
                if(threeworld.canvas.width-positionOffset+50*i+10 < 430-50)
                {
                    //if that step was the 'current step' at the previous frame
                    if (actionRequired == i)
                    {
                        //since this step just ended, the action expected from the
                        //player is reseted, so is the fact that he did what was needed.
                        //Plus he looses a heart if he missed the arrow
                        if (!success)
                            loseLife();
                        else success = false;
                        actionRequired = undefined;
                    }
                }
            
                //else if this is the current step
                else if(threeworld.canvas.width-positionOffset+50*i < 435)
                    actionRequired = i;
            }
            
            //the value of an obstacle is set to -2 if the player got it wrong
            //if that value is -2, replace the arrow by a red square
            if(obstacles[i] == -2)
            {
                ctx.fillRect(threeworld.canvas.width-positionOffset+50*i, threeworld.canvas.height-180, 50, 50);
            }
        }
    }
    
    
    function drawLife()
    {
        for (var i = 0 ; i < life ; i++)
            ctx.drawImage(LIFE_IMG, 150, threeworld.canvas.height-140-50*i, 50, 50);
    }
    
    
    function loseLife()
    {
        life--;
        obstacles[actionRequired] = -2;
        
        //draws a red splash screen for a frame
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        if (life === 0)
            gameState = 'end';
    }
    
    
    //---------------------------- RUN FUNCTIONS -------------------------------
    
    
    this.newRun = function() 
    {
        //init world and context
        threeworld.init (  SKYCOLOR  ); 
        ctx = threeworld.getContext ( "2d" );
    
        initIMG();
    
        //initialisation of the global variables
        gameState = 'going';
        unhappy = 0;
        positionOffset = 0;
        actionRequired = undefined;
        success = false;
        life = 3;
        
        //since the id of the level played by the player is always the value of the level variable
        //minus 5, and since the level variable is incremented at the start of generateLevel(),
        //initialising this variable with the value of 4 means that the first level played
        //will be level 0
        level = 4;
        
        speed = 0;
        actionsPossible = 38;
        setupLevel();
        
        //initialisation of the font
        ctx.fillStyle = '#FF2222';
        ctx.textAlign = 'center';
        ctx.font = 'bold 70px Arial';
        
        document.onkeydown = keyDownHandler;
        
        
        //initialisation of the save&load feature
        if ( AB.runloggedin )
		{
			// Definitely can save, not sure if can restore:
			$("#user_span2").html ( " <button onclick='AB.saveData();' >Save your work</button> " );
			
			// Check if any data exists, if so make restore button:
			AB.queryDataExists();   	// will call World.queryDataExists when done 
		}	
		else         			
			$("#user_span2").html(  " <p align=\"center\"> <b> To save your work, go to <br>the World page and run this \"logged in\". </b> </p> " );
    };
    
    
    
    this.nextStep = function()
    {
        $("#user_span3").html("<p align=\"center\">hit the correct arrow key when <br> the arrow reaches the red line</p>");
        
        if (gameState == 'going')
        {
        	drawBackground();
            drawObstacles();
            if (positionOffset > threeworld.canvas.width-430+100+nbObstacles*100)
            {
                setupLevel();
            }
        	drawPlayer();
        	drawLife();
        	
        	if (unhappy > 0)
        	    unhappy -= 1;
        	    
        	positionOffset += speedMult*BACKGROUND_SPEED[BACKGROUND_SPEED.length-1]*AB.clockTick/30;
        	
        	ctx.fillText('level '+(level-5), 500, 100);
        }
        
        else if (gameState == 'end')
        {
            drawBackground();
            ctx.fillText('GAME OVER', threeworld.canvas.width/2+100, 180);
        }
        
    };


    //----------------------- SAVE AND LOAD FUNCTIONS --------------------------


    //this function saves the more advanced level you reached.
    //Since the saveDate don't seem to work with single digit numbers, I add 10
    //to the level, and then substract 10 when I load the data from the server.
    //Finally, I substract 1, because the level variable is incremented at the start
    //of the generateLevel() function
    this.saveData = function()
    {
	    // if no restore button, can make one now 
	    $("#user_span1").html ( " <button onclick='AB.restoreData();' >Restore your work</button> " );
	
        // console.log ( "Saving " + BLOCKARRAY.length + " blocks to server" );
        return ( 5 );             
    };
 

    this.restoreData = function ( a ) 
    {
        level = a-11;
        setupLevel();
    };
 
 
    this.queryDataExists = function ( exists )
    {
        if ( exists ) 
    	    $("#user_span1").html ( " <button onclick='AB.restoreData();' >Restore your work</button> " );
    };


}