2

I got this game demo from a site, and noticed this piece of code:

if(nx == food.x && ny == food.y) {
    var tail = {x: nx, y: ny};
    score++;
    create_food();
} else {
    var tail = snake_array.pop(); 
    tail.x = nx; tail.y = ny;
}

snake_array.unshift(tail); //how is tail used here?

I didn't find any other declarations of a 'tail' variable, so how is this happening? I'm pretty new to this area of programming (web dev/scripting languages), so I'm not sure if this is allowed.

The full code:

$(document).ready(function(){
    var canvas = $("#canvas")[0];
    var ctx = canvas.getContext("2d");
    var w = $("#canvas").width();
    var h = $("#canvas").height();

    var cw = 10;
    var d;
    var food;
    var score;

    var snake_array; 

    function init() {
        d = "right"; 
        create_snake();
        create_food(); 
        score = 0;

        if(typeof game_loop != "undefined") clearInterval(game_loop);
        game_loop = setInterval(paint, 60);
    }
    init();

    function create_snake() {
        var length = 5; 
        snake_array = []; 
        for(var i = length-1; i>=0; i--) {
            snake_array.push({x: i, y:0});
        }
    }

    function create_food() {
        food = {
            x: Math.round(Math.random()*(w-cw)/cw), 
            y: Math.round(Math.random()*(h-cw)/cw), 
        };
    }                

    function paint() {
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, w, h);
        ctx.strokeStyle = "black";
        ctx.strokeRect(0, 0, w, h);

        var nx = snake_array[0].x;
        var ny = snake_array[0].y;

if(d == "right") nx++;
        else if(d == "left") nx--;
        else if(d == "up") ny--;
        else if(d == "down") ny++;

        if(nx == -1 || nx == w/cw || ny == -1 || ny == h/cw || check_collision(nx, ny, snake_array)) {
            init();
            return;
        }

        /* @@@@@@@@@ This is where it is happening @@@@@@@@@ */
        if(nx == food.x && ny == food.y) {
            var tail = {x: nx, y: ny};
            score++;
            create_food();
        } else {
            var tail = snake_array.pop(); 
            tail.x = nx; tail.y = ny;
        }

        snake_array.unshift(tail);

        for(var i = 0; i < snake_array.length; i++) {
            var c = snake_array[i];
            paint_cell(c.x, c.y);
        }

        paint_cell(food.x, food.y);
        var score_text = "Score: " + score;
        ctx.fillText(score_text, 5, h-5);
    }

    function paint_cell(x, y) {
        ctx.fillStyle = "blue";
        ctx.fillRect(x*cw, y*cw, cw, cw);
        ctx.strokeStyle = "white";
        ctx.strokeRect(x*cw, y*cw, cw, cw);
    }

    function check_collision(x, y, array) {
        for(var i = 0; i < array.length; i++) {
            if(array[i].x == x && array[i].y == y)
             return true;
        }
        return false;
    }

    $(document).keydown(function(e) {
        var key = e.which;
        if(key == "37" && d != "right") d = "left";
        else if(key == "38" && d != "down") d = "up";
        else if(key == "39" && d != "left") d = "right";
        else if(key == "40" && d != "up") d = "down";
    })
})

Source of code: http://thecodeplayer.com/walkthrough/html5-game-tutorial-make-a-snake-game-using-html5-canvas-jquery

Any help would be appreciated :)

daniel kullmann
  • 13,653
  • 8
  • 51
  • 67
Vince
  • 14,470
  • 7
  • 39
  • 84
  • `tail` is not local to the `if`. – Jonathan M Jun 04 '14 at 18:19
  • `tail` is declared in both the `if` and the `else` right above it, so either way it is getting declared prior to the `.unshift()` – CrayonViolent Jun 04 '14 at 18:23
  • FYI, best way to write this is: `var tail; if (...) { tail = {...}; } else { tail = {...}; }`. Bring the variable declaration outside of the `if` construct. Thing like JSLint will actually complain about the way you're defining tail, citing "possible use of undeclared variable 'tail'" or "duplicate variable declaration 'tail'" – zamnuts Jun 04 '14 at 18:53
  • @zamnuts That's actually what I was expecting (use of undeclared variable). What threw me off was that it worked perfectly. It makes sense to me now though :) and thanks for the heads up! – Vince Jun 04 '14 at 19:14

2 Answers2

0

var tail is in the function paint() as is snake_array.unshift(tail); and therefore it has access to the var.

Foreign Object
  • 1,630
  • 11
  • 22
0

tail was declared in an if statement, so it is not scope-limited to said statement. If it was in a function, you'd lose the scope.

As said in the comments, JavaScript only has function-level scoping.

Here's a simple example you can run in your console:

if (1>0) {
    var derp = "herp";
}
console.log(derp);
//outputs "herp"
Sterling Archer
  • 22,070
  • 18
  • 81
  • 118
  • 3
    -1, loop does not change scope. – zamnuts Jun 04 '14 at 18:22
  • The other parts of this answer that are correct are also worded very vaguely - e.g. tail is not "declared in" an if statement – user2864740 Jun 04 '14 at 18:22
  • So if a variable is declared within the block of an if-statement, it is not limited to the scope of that block? Does this also apply to switch statements? And is there anywhere I could get some official documentation about this? – Vince Jun 04 '14 at 18:23
  • 3
    @VinceEmigh JavaScript *only* has function-level scoping of local variables and the `var` declaration statement (I like to think of it as an "annotation") is *hoisted* to the top of the enclosing function. That is, there is only one `tail` variable in the given code. – user2864740 Jun 04 '14 at 18:23
  • Oops! You're absolutely right. – Sterling Archer Jun 04 '14 at 18:23
  • @user2864740 what do you mean? The variable was declared inside of an if statement, I don't get how that doesn't make sense – Sterling Archer Jun 04 '14 at 18:25
  • some languages have var scope within conditions and loops (e.g. c++). javascript is not one of those languages. The only relevance for "scope" is whether or not the line of code was actually executed (e.g. putting it within a condition that evaluates false would mean it was never actually declared). Though as @user2864740 mentioned, the declaration is hoisted. So really it's more a matter of whether or not the variable is affected (eg. assignment of value) – CrayonViolent Jun 04 '14 at 18:25
  • @user2864740 yes I know that. In the OP's code, it showed a variable declared in the brackets of a condition, so that's what I was referencing. – Sterling Archer Jun 04 '14 at 18:27
  • 1
    @RUJordan "Declared *in*" implies ownership and/or scope - in this case, especially considering what the OP is asking, it is a vague reply. – user2864740 Jun 04 '14 at 18:27
  • @user2864740 Thank you for clarifying this for me. It make sense now: All variables declared within a function will visible in that function, even if the the variables were declared within a statement block inside of the function. Is there any documentation on this? I'm having a hard time finding specifications on Javascript – Vince Jun 04 '14 at 18:30
  • 2
    @VinceEmigh You're welcome! See [10.5 Declaration Binding Instantiation](http://es5.github.io/#x10.5) for the relevant specification rules (it is a bit wordy, though); the common-term for this effect is "variable hoisting". In addition to the duplicate, ref. http://stackoverflow.com/questions/21096904/variable-hoisting-in-javascript , http://stackoverflow.com/questions/9085839/surprised-that-global-variable-has-undefined-value-in-javascript?lq=1 (which both show usage of a local variable before the `var` declaration statement within a function) – user2864740 Jun 04 '14 at 18:35