简体   繁体   中英

Why the links of my snake getting place on top of each other?

Repro steps:

  1. Visit http://playclassicsnake.com/play
  2. See 3 green squares and 1 red circle
  3. Resize screen small enough to make the grid that the snake lives on be smaller
  4. The red circle will be resized and replaced appropriately, and so will 1 green square, seemingly
  5. console.log(SG.snake.links.length); to see that there are indeed 3 links on the snake. If you look at the grid coordinates of each of the three individually with SG.snake.links[k].pos.x , SG.snake.links[k].pos.y for k=1,2,3 you will see that they live on the same coordinate.

Source of the problem:

The source has to be my implementation of the function that handles the resizing of the board and the elements on the board. It is

this.rescale = function ( newWidth )
{
    // newWidth: new width of the div containing 

    this.board.setSize(newWidth, newWidth); // set the size of the board to be that of the containing div
    var blockWidth = this.board.getSize().width / this.numBlocks; // width in pixels of a block on the board  

    this.food.elem.remove(); // remove old food element   
    this.food.elem = this.board.circle(this.food.pos.x * blockWidth + blockWidth / 2, this.food.pos.y * blockWidth + blockWidth / 2, blockWidth / 2).attr('fill', '#cf6a4c');  // create food element to replace old one, in same grid location (see http://raphaeljs.com/reference.html#Paper.circle)

    for (var i in this.snake.links) 
    {
        var thisLink = this.snake.links[i];
        thisLink.elem.remove(); // remove old link element
        thisLink.elem = this.board.rect(thisLink.pos.x * blockWidth, thisLink.pos.y * blockWidth, blockWidth, blockWidth).attr('fill', '#19FF19'); // creata new link to replace old one http://raphaeljs.com/reference.html#Paper.circle
    }
}

Code dump:

If you want to see the full logic of the object representing the game, it is below.

// 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.coords = []; // map whose key-value pairs represent whether a coordinate is open or taken
    this.food = null; // food element on board

    this.getCoords = function ( )
    {
        // returns a nested list gridList of all grid coordinates on the canvas, 
        // acting like a map so that gridList[i,j]=true if the coordinate i,j is
        // occupied, and gridList[i,j]=false if the coordinate is not occupied
        var gridList = [];
        for (var i = 0; i < this.numBlocks; ++i)
        {
            var innerList = [];
            for (var j = 0; j < this.numBlocks; ++j) innerList.push(true);
            gridList.push(innerList);
        }
        return gridList;
    }

    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: { x: xpos, y: ypos } };
    }


    this.rescale = function ( newWidth )
    {
        // newWidth: new width of the div containing 

        this.board.setSize(newWidth, newWidth); // set the size of the board to be that of the containing div
        var blockWidth = this.board.getSize().width / this.numBlocks; // width in pixels of a block on the board  

        this.food.elem.remove(); // remove old food element   
        this.food.elem = this.board.circle(this.food.pos.x * blockWidth + blockWidth / 2, this.food.pos.y * blockWidth + blockWidth / 2, blockWidth / 2).attr('fill', '#cf6a4c');  // create food element to replace old one, in same grid location (see http://raphaeljs.com/reference.html#Paper.circle)

        for (var i in this.snake.links) 
        {
            var thisLink = this.snake.links[i];
            thisLink.elem.remove(); // remove old link element
            thisLink.elem = this.board.rect(thisLink.pos.x * blockWidth, thisLink.pos.y * blockWidth, blockWidth, blockWidth).attr('fill', '#19FF19'); // creata new link to replace old one http://raphaeljs.com/reference.html#Paper.circle
        }
    }

    this.Snake = function ( game )
    {
        // game: the Game function/object containing this function

        this.links; // list of 

        this.createNew = function ( )
        {

            this.links = [];  
            var blockWidth = game.board.getSize().width / game.numBlocks; // width in pixels of a block on the board
            var centerCoordXY = Math.round(game.numBlocks / 2); // x-y grid coordinate of center 
            for (var i = 0; i < 3; ++i) // start with 3 blocks in the center-ish
            {
                var newX = centerCoordXY + i;
                this.links.push(new game.elementOnGrid(
                                    game.board.rect(newX * blockWidth, centerCoordXY * blockWidth, blockWidth, blockWidth).attr('fill', '#19FF19'), // http://raphaeljs.com/reference.html#Paper.circle
                                    centerCoordXY,
                                    centerCoordXY
                                ) // add element of type elementOnGrid to the links
                );
                game.coords[newX][centerCoordXY] = false; // indicate that coordinates of element just added to snake is no longer open
            }

        }
    }

    this.placeFood = function ( )
    {
        do {
            var randXCoord = randInt(0, this.coords.length), randYCoord = randInt(0, this.coords.length);
        }
        while (this.coords[randXCoord][randYCoord] === false); // get random unused x-y coordinate

        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( 
                    this.board.circle(randXCoord * blockWidth + blockWidth / 2, randYCoord * blockWidth + blockWidth / 2, blockWidth / 2).attr('fill', '#cf6a4c'),  // place circle in random location on the board (see http://raphaeljs.com/reference.html#Paper.circle)
                    randXCoord,
                    randYCoord
            ); // set food to be new element of type elementOnGrid

        }
        else // food element has been initialized (game is in play)
        {
            // move the food element

            // ... 
        }

        this.coords[randXCoord][randYCoord] = false; // indicate that coordinates of the good element is not open

    }


    this.startNew = function ( ) {
        this.coords = this.getCoords();
        this.snake = new this.Snake(this);
        this.snake.createNew();
        this.placeFood();       
    }
}

$(function () { // equivalent to $(document).ready(function() { 

    // div that holds the game area
    var snakeBoardHolder = $('#snake-board-holder');
    // make it have the same height as width
    snakeBoardHolder.height(snakeBoardHolder.width());

    // draw canvas for the snake to live on
    // http://raphaeljs.com/reference.html#Raphael
    if (!Raphael.svg) throw new Error("Your browser does not support SVG elements! Game won't work.");
    snakeBoard = Raphael("snake-board-holder", snakeBoardHolder.width(), snakeBoardHolder.height());

    // start new snake game
    SG = new Game(snakeBoard, 16);
    SG.startNew();

    // make the game area (div) have height always equal to width,
    // and make the Raphel object (canvas) inside it and all its elements
    // to be resized proportionally
    $(window).resize(function () {
        var w = snakeBoardHolder.width();
        snakeBoardHolder.height(w);
        SG.rescale(w);
    });

});

Any help in determing the piece of logic that is causing the bug would be greatly appreciated!

Inside this.Snake, I believe createNew() should be:

this.createNew = function ( )
{

    this.links = [];  
    var blockWidth = game.board.getSize().width / game.numBlocks; // width in pixels of a block on the board
    var centerCoordXY = Math.round(game.numBlocks / 2); // x-y grid coordinate of center 
    for (var i = 0; i < 3; ++i) // start with 3 blocks in the center-ish
    {
        var newX = centerCoordXY + i;
        this.links.push(new game.elementOnGrid(
                            game.board.rect(newX * blockWidth, centerCoordXY * blockWidth, blockWidth, blockWidth).attr('fill', '#19FF19'), // http://raphaeljs.com/reference.html#Paper.circle
                            newX,
                            centerCoordXY
                        ) // add element of type elementOnGrid to the links
        );
        game.coords[newX][centerCoordXY] = false; // indicate that coordinates of element just added to snake is no longer open
    }

}

Inside this.links.push I've replaced an instance of centerCoordXY with newX.

You've got a lot of duplicated data (positions stored in 3 different places and in two different formats?) which is likely to cause issues like this if you fail to keep them all in sync. It may be better to use Canvas rather than SVG. If you're set on SVG, I'd recommend more helper functions. For example, instead of

new game.elementOnGrid(
    game.board.rect(newX * blockWidth, centerCoordXY * blockWidth, blockWidth, blockWidth).attr('fill', '#19FF19'), // http://raphaeljs.com/reference.html#Paper.circle
    newX,
    centerCoordXY
)

consider a function that would allow you to do something like

newGridElement(newX, centerCoordXy, "#19FF19");

Anyway, good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM