Tutorial, Part 3: Getting Started


< Previous Tutorial (Part 2)

> Next Tutorial (Part 4)


This section is about the pre-requisite code needed for this game before we can fire it up and make the game playable or look like something besides a blank screen.

For this tutorial, we will be working exclusively with the scripts.js page.

Variables

These are values that change as the game progresses. They help control the player position, timers, snacks collected, score, current map data, the list goes on. Take a look at the list; each variable includes notes on what they do and how they contribute to the game.
We use let to declare them. We 'could' put a let in front of every single entry, but it is just as effective to place commas between them, so long as the last one ends with a semi-colon. Line breaks do not effect the declaration of variables.

Notes:

  • It is always important to declare your variables. Improperly declared variables are what leads to the most serious of memory leaks in javascript and can bloat/crash your browser if the browser has not been patched to compensate for this. Apparently there are some browsers that can handle this, but it's better to practice good coding. This is also why we use "use strict"; it will catch an undeclared variable and give you an error, instead of a browser crash.

Add the following under //VARIABLES:

//
//VARIABLES
let keyPress=[false,false,false,false,false],keyHold=[0,0,0,0,0]; //controls for keys, notably if one if being pressed/held down
//
let pBody,     //Player's body. This will be an array to store the X and Y positions of each body segment.
    pCollect,  //How many snacks the player had nomnom'ed during this round.
    pDeaths,   //Player deaths. We will add a score bonus if the player completes a level without dying once.
    pDir,      //Player's direction. This will match the keys layout (0=left, 1=up, 2=right, 3=down.
               //  4 is used as a placeholder to say there is no direction and the player will not automatically move)
    pLifeScore,//How many points a player must earn to gain another life.
    pLifeBase, //Base for score required for another life.
    pLifeInc,  //The bonus life score increment.
    pLives,    //Player's remaining lives. If this is at zero when the player crashes, it's game over.
    pSegMax,   //How many body segments the player should have.
    pRound,    //Player's current round.
    pScore,    //Player's score
    pSnack,    //How many snacks the player must collect to clear a round.
    pTimer;    //Player timer. This is used to track the # of remaining frames
let endRound,  //When the player exits the screen, this ends the round.
    gfx,       //The value that is used for building the graphics output.
    gTimer,    //Game timer. Useful for different things.
    mapX,      //Current map.
    snack,     //Snack. This holds the the X, Y and color values.
    tempDir;   //Temporary direction. This value is moved to pDir when the player moves a square.

Constants

This serves as a miniature database for odds and ends, plus values we do not want to see changed in-game.

Add the following under //CONSTANTS:

//
//CONSTANTS
const addLength=[0,8,7,7,6,6,6,5,5,5,5],   //A setup for how many body segments are added on when eating a snack.
                                           //Connected to the pCollect variable.
      dirX=[-1,0,1,0],dirY=[0,-1,0,1],     //Controls the changes to the player's X and Y when moving.
      keys=[37,38,39,40,32],               //Keycode values while pressing keys. For WASD, use: keys=[37,38,39,40,32];
      GAME=document.getElementById("game"),//Shortcut for accessing the game canvas.
      GAMEX=GAME.getContext("2d"),         //Allows us to handle the game canvas more easily when making graphics.
      scrWidth=128, scrHeight=128,         //Screen width and height.
      snackColors=[[32,80,168],[168,32,168],[40,176,208],[208,200,32],[192,56,24]]; //Colour setup for snacks (in RGB).

Notes

  • The snackColors Array will double as bonus points for snacks.

Adjusting Screen Size

A canvas box's size can be controlled in two ways: The 'canvas' size and the 'css' size. These do two different things. The 'canvas' is the base size of the project you are working on. This can stay the same and all the variables and scripts will point to this. To change how big it appears on the screen, we will make the css-end of things dynamic and scale to the size of the browser window through the use of scripts.

We will need to some pieces to get the screen size to work.

  • Add an event listener to tell the browser to 'listen' to the size of the browser window.
  • A function that will run when the screen size is affected/changed. It will also run when the game loads for the first time.
  • Set the size of the canvas' base width and height.

Add the following under //LISTENERS:

//
//LISTENERS
addEventListener("resize",function(){SETSIZE();});  //Auto adjusts the game canvas when the browser window is resized.

Then add the following under //FUNCTIONS:

//
//FUNCTIONS
function SETSIZE(){ //Set the size of the screen.
    let i,i2;                                           //Set 'css' size (of the canvas).
    i=(innerHeight-4)/scrWidth,i2=innerWidth/scrHeight; //This will use get the dimensions of the screen as a multiplier of the game screen base size.
    if(i2<i){i=i2}                                      //Whichever is the smaller scaleup value will be used.
    if(i<1){i=1;}                                       //This sets a minimum size.
    GAME.style.width=Math.floor(scrWidth*i)+"px";       //Set the canvas 'css' size.
    GAME.style.height=Math.floor(scrHeight*i)+"px";
    GAME.style.background="white";                      //Temporary. Changes the background of the canvas to white.
}

Finally, add the following under //EXECUTE THE GAME:

//
//EXECUTE THE GAME
GAME.width=scrWidth;   //Set 'canvas' size.
GAME.height=scrHeight;
SETSIZE();             //Set 'css' size.

If you run this, a white box will appear on your screen. You can play around with the browser window size and watch it change.

Key Pressing/Holding

To move the worm around the map (and pause the game), we will need to setup two parts to make ths work. First, is to add two addEventListeners, like we did for checking the size of the browser window. One will keep an eye for keys that are held down and the other is for when a key has been released.

Browsers normally have a default setup for what certain keys on a keyboard do (for instance, arrow keys will scroll the page up and down, so we will disable this feature in the second addEventListener to prevent that from happening).

  • The second addEventListener controls both the keySetup and keyHold value, defaulting them to false and 0 when a key is let go.
  • As a reminder, the following numbers refer to the following keys (in their position in the array):
    • 0 = Left
    • 1 = Up
    • 2 = Right
    • 3 = Down
    • 4 = Spacebar

First, the listeners. Place these under the resize listener:

addEventListener( //This records when certain keys are pressed down.
    'keydown',function(e){
        if(e.keyCode==32||e.keyCode==37||e.keyCode==38||e.keyCode==39||e.keyCode==40){e.preventDefault()};//This prevents the spacebar and arrow keys from doing their usual thing.
        keyPress[e.keyCode]=true
    }, true
);
addEventListener( //This records when certain keys are released.
    'keyup',function(e){
        keyPress[e.keyCode]=false;
        let i0=keys.length; for(let i=0; i<i0; i++){if(e.keyCode==keys[i]){keyHold[i]=0;}} //If a key is let go, this will free the 'it's held down' value.
    }, true
);

Next, add the two functions that are called on when a key is pressed.

The functions KEYBOARD() and KEYBOARDHOLD() are setup to do something when your arrow keys are pressed.

  • KEYBOARD() is used as part of the check to verify if you are pressing that key. We will call on a function to do our key pressing check.
    if(keyPress[37] && keyHold[0]==0)  --> if(KEYBOARD(0))
  • KEYBOARDHOLD() This will confirm that a key is being held down. This will be used by the spacebar for pausing the game (much further down the tutorial).

Place these underneath the SETSIZE() function:

//
// Keyboard Handling
function KEYBOARD(a){return keyPress[keys[a]]&&keyHold[a]==0;} //Returns true when a key is pressed AND the key was not already held down.
function KEYBOARDHOLD(a){keyHold[a]=1;} //The key is now 'held down'.

We will fire up the main game functions as well. We are including 2 more functions for the time being: STARTGAME(); and ACTION();

  • STARTGAME(); is the preliminary function. It will be used to set up a whole bunch of variables, get the right map data, setup where the first snack is, etc. For now, We'll just have it connect to ACTION().
  • ACTION(); controls the game mechanics and what appears on the screen. It will handle keyboard controls (which we setup functions for, prior), what actions are performed on the board and how everything looks.

Put this under the SETSIZE() function:

//
// -- Start the game.
function STARTGAME(){
    requestAnimationFrame(ACTION); //Fire up the game.
}
//
// -- Main game cycle
function ACTION(){
    //Keyboard controls
    if(KEYBOARD(0)){}      //Left
    else if(KEYBOARD(1)){} //Up
    else if(KEYBOARD(2)){} //Right
    else if(KEYBOARD(3)){} //Down
    if(KEYBOARD(4)){}    //Pause Game?
    //
    requestAnimationFrame(ACTION);
}

Notes:

  • This part is optional. If you want to verify that the arrow keys and spacebar do something, you can make a few modifications. Start with the index.html page and add:
    <body>
    <canvas id="game"></canvas>
    <div id="filler"></div>
    </body>
    Then go to scripts and make the following changes to the ACTION() function:
    //
    // -- Main game cycle
    function ACTION(){
        //Keyboard controls
        if(KEYBOARD(0)){document.getElementById("filler").innerHTML="Left";}      //Left
        else if(KEYBOARD(1)){document.getElementById("filler").innerHTML="Up";} //Up
        else if(KEYBOARD(2)){document.getElementById("filler").innerHTML="Right";} //Right
        else if(KEYBOARD(3)){document.getElementById("filler").innerHTML="Down";} //Down
        if(KEYBOARD(4)){document.getElementById("filler").innerHTML="Spacebar";}    //Pause Game?
        //
        requestAnimationFrame(ACTION);
    }
    When you open up the game and press a key, something will appear under the box. You can scroll down and watch the message change as you press arrow keys. Once you have verified this, you can remove the changes above and continue on.

  • I am well aware of the event.key option for onkeydown, instead of event.keyCode, but with my setup, it does not work as well for tracking when a key is held down, something that is needed for pausing the game. 'Technically' event.keycode is considered deprecated, but I have yet to hear of any browsers that have withdrawn support for it (There is plenty of javascript that is considered deprecated, yet still works). If you wish to change the code to suit the way you prefer things, that is up to you, of course.

Pre-Game Content

Prior to firing up the game, we need some values.

To sum this up, this will setup a game with all important stats reset, such are the score for a new life, first round, 2 lives, where the player starts and the map of the first round.

Replace the one line already in the STARTGAME() function with this:

function STARTGAME(){
    pBody=[[7,8]], //We start with the head at a specific X & Y.
    pCollect=0,
    pDeaths=0,
    pDir="x",
    pLifeScore=250,
    pLifeBase=250,
    pLifeInc=250,
    pLives=2,
    pRound=0,
    pSegMax=5,
    pScore=0,
    pSnack=5,
    pTimer=0;
    //
    endRound=0,
    gTimer=0,
    mapX=COPY(mapSrc[0]),
    tempDir=4;
    SNACKSPOT();
    //
    requestAnimationFrame(ACTION); //Fire up the game.
}

The variable updates call on two functions that do not exist in our code. Let us create them, now.

The first one is calleed COPY(). It is a simple function that lets up duplicate a value / set of values without impacting the original source. For instance, if I try to do something like mapX = mapSrc[0], it will create a link between them, rather than copying them. If I make changes to mapX, it will also change mapSrc[0]. We can avoid this by using this function to duplicate the values, instead.

Copy and paste the following code underneath the functions that control the keyboard arrays.

//
// -- COPY value
function COPY(a){return JSON.parse(JSON.stringify(a));} //Duplicates the value.

The next function is called SNACKSPOT(). This generates a set of values for the snack array: Where it's X & Y position is and what it's colour/value is. (The 3rd value is used to determine both it's colour and bonus points).

Place this between the COPY() and STARTGAME() functions:

//
// -- Get position of snack
function SNACKSPOT(){
    let x,y,z=1,i0;
    while(z==1){ //This runs while an obstacle prevents the snack from being
                 //generated. This includes walls and body segments.
        z=0,
        x=Math.floor(Math.random()*16),              //Pick a random X & Y spot on the map
        y=Math.floor(Math.random()*14);
        if(mapX[y*16+x]==1){                         //Cancel if this lands on a map tile.
            z=1;
        }
        i0=pBody.length;
        for(let i=0; i<i0; i++){
            if(pBody[i][0]==x && pBody[i][1]==y){    //Cancel is this lands on a body segment.
                z=1;
            }
        }
        if(z==0){                                    //Set the snack co-ordinates and colour, here.
            snack=[x,y,Math.floor(Math.random()*5)];
        }
    }
}
//
// -- Start the game.
function STARTGAME(){
    pBody=[[7,8]], //We start with the head at a specific X & Y.
    pCollect=0,
    pDeaths=0,
    pDir="x",

Here is the up-to-date progress for the code:

//FIRST THINGS, FIRST
"use strict";  //Help better identify problems in the code, especially with bad variable declarations.
//
//VARIABLES
let keyPress=[false,false,false,false,false],keyHold=[0,0,0,0,0]; //controls for keys, notably if one if being pressed/held down
//
let pBody,     //Player's body. This will be an array to store the X and Y positions of each body segment.
    pCollect,  //How many snacks the player had nomnom'ed during this round.
    pDeaths,   //Player deaths. We will add a score bonus if the player completes a level without dying once.
    pDir,      //Player's direction. This will match the keys layout (0=left, 1=up, 2=right, 3=down.
               //  4 is used as a placeholder to say there is no direction and the player will not automatically move)
    pLifeScore,//How many points a player must earn to gain another life.
    pLifeBase, //Base for score required for another life.
    pLifeInc,  //The bonus life score increment.
    pLives,    //Player's remaining lives. If this is at zero when the player crashes, it's game over.
    pSegMax,   //How many body segments the player should have.
    pRound,    //Player's current round.
    pScore,    //Player's score
    pSnack,    //How many snacks the player must collect to clear a round.
    pTimer;    //Player timer. This is used to track the # of remaining frames
let endRound,  //When the player exits the screen, this ends the round.
    gfx,       //The value that is used for building the graphics output.
    gTimer,    //Game timer. Useful for different things.
    mapX,      //Current map.
    snack,     //Snack. This holds the the X, Y and color values.
    tempDir;   //Temporary direction. This value is moved to pDir when the player moves a square.
//
//CONSTANTS
const addLength=[0,8,7,7,6,6,6,5,5,5,5],   //A setup for how many body segments are added on when eating a snack.
                                           //Connected to the pCollect variable.
      dirX=[-1,0,1,0],dirY=[0,-1,0,1],     //Controls the changes to the player's X and Y when moving.
      keys=[37,38,39,40,32],               //Keycode values while pressing keys. For WASD, use: keys=[37,38,39,40,32];
      GAME=document.getElementById("game"),//Shortcut for accessing the game canvas.
      GAMEX=GAME.getContext("2d"),         //Allows us to handle the game canvas more easily when making graphics.
      scrWidth=128, scrHeight=128,         //Screen width and height.
      snackColors=[[32,80,168],[168,32,168],[40,176,208],[208,200,32],[192,56,24]]; //Colour setup for snacks (in RGB).
//
//LISTENERS
addEventListener("resize",function(){SETSIZE();});  //Auto adjusts the game canvas when the browser window is resized.
addEventListener( //This records when certain keys are pressed down.
    'keydown',function(e){
        if(e.keyCode==32||e.keyCode==37||e.keyCode==38||e.keyCode==39||e.keyCode==40){e.preventDefault()};//This prevents the spacebar and arrow keys from doing their usual thing.
        keyPress[e.keyCode]=true
    }, true
);
addEventListener( //This records when certain keys are released.
    'keyup',function(e){
        keyPress[e.keyCode]=false;
        let i0=keys.length; for(let i=0; i<i0; i++){if(e.keyCode==keys[i]){keyHold[i]=0;}} //If a key is let go, this will free the 'it's held down' value.
    }, true
);
//
//FUNCTIONS
function SETSIZE(){ //Set the size of the screen.
    let i,i2;                                           //Set 'css' size (of the canvas).
    i=(innerHeight-4)/scrWidth,i2=innerWidth/scrHeight; //This will use get the dimensions of the screen as a multiplier of the game screen base size.
    if(i2<i){i=i2}                                      //Whichever is the smaller scaleup value will be used.
    if(i<1){i=1;}                                       //This sets a minimum size.
    GAME.style.width=Math.floor(scrWidth*i)+"px";       //Set the canvas 'css' size.
    GAME.style.height=Math.floor(scrHeight*i)+"px";
    GAME.style.background="white";                      //Temporary. Changes the background of the canvas to white.
}
//
//Keyboard Handling
function KEYBOARD(a){return keyPress[keys[a]]&&keyHold[a]==0;} //Returns true when a key is pressed AND the key was not already held down.
function KEYBOARDHOLD(a){keyHold[a]=1;} //The key is now 'held down'.
//
// -- COPY value
function COPY(a){return JSON.parse(JSON.stringify(a));} //Duplicates the value.
//
// -- Get position of snack
function SNACKSPOT(){
    let x,y,z=1,i0;
    while(z==1){ //This runs while an obstacle prevents the snack from being
                 //generated. This includes walls and body segments.
        z=0,
        x=Math.floor(Math.random()*16),              //Pick a random X & Y spot on the map
        y=Math.floor(Math.random()*14);
        if(mapX[y*16+x]==1){                         //Cancel if this lands on a map tile.
            z=1;
        }
        i0=pBody.length;
        for(let i=0; i<i0; i++){
            if(pBody[i][0]==x && pBody[i][1]==y){    //Cancel is this lands on a body segment.
                z=1;
            }
        }
        if(z==0){                                    //Set the snack co-ordinates and colour, here.
            snack=[x,y,Math.floor(Math.random()*5)];
        }
    }
}
//
// -- Start the game.
function STARTGAME(){
    pBody=[[7,8]], //We start with the head at a specific X & Y.
    pCollect=0,
    pDeaths=0,
    pDir="x",
    pLifeScore=250,
    pLifeBase=250,
    pLifeInc=250,
    pLives=2,
    pRound=0,
    pSegMax=5,
    pScore=0,
    pSnack=5,
    pTimer=0;
    //
    endRound=0,
    gTimer=0,
    mapX=COPY(mapSrc[0]),
    tempDir=4;
    SNACKSPOT();
    //
    requestAnimationFrame(ACTION); //Fire up the game.
}
//
// -- Main game cycle
function ACTION(){
    //Keyboard controls
    if(KEYBOARD(0)){}      //Left
    else if(KEYBOARD(1)){} //Up
    else if(KEYBOARD(2)){} //Right
    else if(KEYBOARD(3)){} //Down
    if(KEYBOARD(4)){}    //Pause Game?
    //
    requestAnimationFrame(ACTION);
}
//
//EXECUTE THE GAME
GAME.width=scrWidth;   //Set 'canvas' size.
GAME.height=scrHeight;
SETSIZE();             //Set 'css' size.
STARTGAME();           //Get the game underway.

The next tutorial will cover

Leave a comment

Log in with itch.io to leave a comment.