2

I'm writing a game and what I have so far for the object that represents the game's playing board is

// define game object
function Game ( board, numBlocks ) {
    //     board: Raphael object that the snake will live on
    // numBlocks: Number of blocks both horizontally AND vertically -- the grid structure should be squares

    this.board = board;
    this.numBlocks = numBlocks;
    this.snake; // Snake object on the board
    this.openCoords = []; // coordinates on which the snake is not living at the moment
    this.food = null; // food element on board

    this.getAllCoords = function ( )
    {
        // returns list of all grid coordinates on the canvas, 
        // e.g. [{x:0,y:0}, {x:0,y:1}, ..., {x:15, y:15}] on a 16x16 board
        var retList = [];
        for (var i = 0; i < this.numBlocks; ++i)
            for (var j = 0; j < this.numBlocks; ++j)
                retList.push({ x : i, y : j });
        return retList;
    }

    this.Snake = function ( )
    {

        // start with 3 blocks in the center-ish
        var blockWidth = this.board.getSize().width / this.numBlocks;
        var centerCoord = this.openCoords[this.openCoords.length / 2];
        var headElement = new elementOnGrid(
                board.rect(centerCoord.x * blockWidth, centerCoord.y * blockWidth, blockWidth, blockWidth, 5).attr('fill', '#19FF19'),
                centerCoord.x,
                centerCoord.y
            );
    }

    this.elementOnGrid = function ( elem, xpos, ypos )
    {
        //       elem: Rapael element (see: http://raphaeljs.com/reference.html#Element) 
        // xpos, ypos: x and y grid coordinates of the current position of the element 
        return { elem: elem, pos : [xpos, ypos] };
    }

    this.placeFood = function ( )
    {
        var randIndex = randInt(0, this.openCoords.length);
        var randCoord = this.openCoords[randIndex]; // get random x-y grid coordinate from list of open coordinates
        var blockWidth = this.board.getSize().width / this.numBlocks; // width in pixels of a block on the board
        if (this.food == null) // if food element hasn't been initialized
        {
            // initialize the food element
            this.food = new this.elementOnGrid( 
                    board.circle(randCoord.x * blockWidth + blockWidth / 2, randCoord.y * blockWidth + blockWidth / 2, blockWidth / 2).attr('fill', '#cf6a4c'),  // place circle in random location on the board (see http://raphaeljs.com/reference.html#Paper.circle)
                    randCoord.x, 
                    randCoord.y
            ); // set food to be new element of type elementOnGrid
        }
        else // food element has been initialized (game is in play)
        {
            // move the food element

            // ... 
        }

        this.openCoords.splice(1, randIndex); // remove from openCoords the element that    

    }


    this.startNew = function ( ) {
        this.openCoords = this.getAllCoords();
        this.snake = new this.Snake();
        this.placeFood();       
    }
}

and my problem is that I'm getting a

Uncaught TypeError: Cannot read property 'getSize' of undefined

on the line

var blockWidth = this.board.getSize().width / this.numBlocks;

when I run

SG = new Game(snakeBoard, 16);
SG.startNew();

to start the game.

I know this is some type of scope/inheritance/hoisting/etc. issue, but I can't put my finger on exactly what the problem is. What's strange is that I just now created the this.Snake ( ) { ... } function/object, and before that the line var blockWidth = this.board.getSize().width / this.numBlocks; inside the this.placeFood ( ) { ... } function wasn't causing problems, though I can't see anything fundamentally different about it ..

CLICK HERE FOR A LIVE EXAMPLE

user5124106
  • 383
  • 2
  • 3
  • 11

1 Answers1

3

You are correct about it being a scope issue. The difference between what you were doing before with placeFood() and what you're doing now with Snake() is the new keyword.

The new keyword tells the function to manage its own this and so your Game context no longer applies to this within the Snake function.

One solution would be to pass the context into the Snake function like this

this.snake = new this.Snake(this);

and then manage the context in the Snake constructor like this

this.Snake = function(game) {
  var blockWidth = game.board.getSize().width / game.numBlocks;
  ...
  // replacing `this` with `game` for all relevant properties

See here for more information on the new keyword.

Community
  • 1
  • 1
sfletche
  • 36,606
  • 25
  • 86
  • 108
  • I thought of that, but doesn't that make a copy of `game` to pass in? I want to avoid unnecessary copies of large elements. – user5124106 Jul 24 '15 at 05:32
  • Javascript passes objects by reference (so you don't have to worry about passing copies of large objects around). See [here](http://stackoverflow.com/questions/13104494/does-javascript-pass-by-reference) for more on passing by reference vs passing by value. – sfletche Jul 24 '15 at 05:36