Developer   Tutorials   Snake   Score

Score

Score is an important feature, which makes the game more challenging. Users can compete against each other to achieve better score but also, only one user can try to achieve the best score. In this game, the score depends on number of eaten foods and the target is to achieve the highest score without coming to contact with wall or part of the snake.

Two types of foods

Every time the snake eats the food, the score increases. However, to make the game more interesting, we have created two types of food. When snake eats the smaller food the score increases by 10 points and when it eats the larger one, the score increases by 20 points.

small food - increases score by 10 points
large food - increases score by 20 points                              

How to calculate the score?

Score is recalculated each time the snake eats the food. If we generalised it, we can consider only the snake's head as that is the part snake eats the food with.

All the food distributed on the screen was pushed to one array called food. When we have access to all food objects and also to the snake head, it is really easy to check if snake has eaten some food. We need to check if the is not food under / inside head. To do this, Moscrif framework offers us predefined function. However, here we require different behaviour (to check if the object does not lie under the center of snake's head; not under the whole head), so we need to owerwrite it, by our custom function:

Example: Heat.intersectsBounds function - checks if objects overlap

/**
Check intersects by object
@param Gameobject obj
@return Boolean
*/
function intersectsBounds(obj)
{
    // check if obj is instance of GameObject (if have all needed properties)
    assert obj instanceof GameObject;
    // get position of this object
    var tx = this._x;
    var ty = this._y;
    // get position of second object
    var ox = obj._x;
    var oy = obj._y;
    // check if both objects overlap
    return (
        tx + this.width > ox && tx < ox + obj.width &&
        ty + this.height > oy && ty < oy + obj.height
    );
}

Once we are able to check if objects overlap, it is easy to call this function for all food objects. The best place to do it is in onProcess event, where movement is managed as well. If head overlaps with some food, we increase the value of score property (containing current score) by the value of food eaten.

/**
Event called within Scene onProcess
@param GameScene sender
*/
function onProcess(sender)
{
       ...
       
        //Check collisions with food
        for (var f in sender.food)
            if (this.intersectsBounds(f)) {
                //Set new random positions
                var stop = false;
                while (!stop) {
                    stop = true;
                    f.x = (1+rand(sender.width/CELL_WIDTH-2))*CELL_WIDTH -10;
                    f.y = (1+rand(sender.height/CELL_HEIGHT-2))*CELL_HEIGHT -10;
                    for (var i in sender.food)
                        if (f != i && f.intersectsBounds(i))
                            stop = false;
                    for (var i in sender.walls)
                        if (f.intersectsBounds(i))
                            stop = false;
                }
                sender.snake.addPart(); //Add new part to the body.
                if (sender.score.value < 300)
                    sender.snake.timer.sleep -= f.speedValue; //It will call method onProcess  more often and snake will be faster.
                sender.score.value += f.score;
                break;
     } }

Score drawing

Score drawing is managed by class Score, which is extended from Spirte class. Sprite class implements fuctionality, which enables us to devide image to the frames and then show only one of these frames and switch between these frames. This is mostly suitable for animations. 

We use this feature to draw score, because it uses some unusual fonts. All digits are placed in one image file. 

By using parameters frameWidth and frameHeight, we split image on separate frames. We can choose from these by parameter frame. The code below shows how to set properties image, frameWidth and frameHeight. There is an example of using this feature in score object.

function _initVariables()
{
     //set image
     this.image = _numbersImage;
     //set size of one subimage
     this.frameWidth = 25;
     this.frameHeight = 34;
     //init score value
     this._value = 0;
     //init helper variables to define position
     this._right = 0;
     this._top = 0;
     //init variable by which we do effect at adding new value
     this._effect = null;
}

Numerical value of score is saved in variable this._value. At first we must transform this value to array of frames, which we draw on scene. Each digit represents one frame. You can see this transformation in following code:

var str = this.value.toString();
for (var i = 0; i < str.length; i++) {
    this.frame = str[i] - 48;
    super.draw(canvas);
    this.x += this.frameWidth;
}

Firstly, we convert actual value to the string by method toString(). Now we can work with number as with array of digits, but we must convert it by simple shift in ascii table. We sequently proceed trough the array with the cycle and in every step, we assign image index to this.frame variable. Then we draw the frame on its actual position and change x coordinate.

Now that we know how use frames with advanced rendering, we can show you how we can animate this object using onProcess event. This event is called every 25 milliseconds. Each sprite object has onProcess handler, that can be assigned to our own function - as you can see it in example.

this.onProcess = function(sender)
{
    var self = this super;
    if (self._effect) {
        if (self._effect.color & 0xff000000) {
            self._effect.color -= 0x02000000;
            self._effect.top += 1;
        } else
            self._effect = null;
    }
}

We assign our function to this.onProcess variable. By using formula "this super", we can access the superior object, in this case the Score object. Firstly, we need to test if self._effect isn't null. If it isn’t, it means that it is a Paint object that we create when we change property value. Then we need to test if alpha channel value isn’t zero. If it isn’t we decrease it and move the object down. Otherwise, we assign null value to self._effect variable. Then we use this Paint object  for rendering. You can learn more about rendering you  in API within section Graphics.

 Game elements   Score   Snake