类似俄罗斯方块游戏的硬件

时间:2015-08-10 20:12:13

标签: javascript html5 game-physics tetris

我正在构建类似俄罗斯方块的游戏,当你获得一条完整的线条时,我不会删除一条线,而是移除所有连接的部分。清除碎片后,这让我难以理解。

请参阅this example,了解我尝试做的快速而肮脏的版本。

function Board (width, height) {
    this.width = width;
    this.height = height;
    this.board = [];
    this.pieces = [];
    for (var y = 0; y < this.height; y++) {
        for (var x = 0; x < this.width; x++) {
            if (!this.board[y]) {
                this.board[y] = [];
            }
            this.board[y][x] = null;
        }
    }

    this.canPlace = function(piece, at) {
        for (var y = 0; y < piece.getHeight(); y++) {
            for (var x = 0; x < piece.getWidth(); x++) {
                if ((y+at.y >= this.height) || this.board[y+at.y][x+at.x]) {
                    return false;
                }
            }
        }
        return true;
    }

    this.hasFullLine = function(line) {
        for (var x = 0; x < this.width; x++) {
            if (!this.board[line][x]) {
                return false;
            }
        }
        return true;
    }

    this.place = function(piece) {
        var position = piece.getPosition();
        var shape = piece.getShape();
        for (var y = 0; y < piece.getHeight(); y++) {
            for (var x = 0; x < piece.getWidth(); x++) {
                if (shape[y][x]) {
                    this.board[y+position.y][x+position.x] = piece;
                }
            }
        }
        if (this.pieces.indexOf(piece) === -1) {
            this.pieces.push(piece);
        }
        piece.render();
    }

    this.hardDropPieces = function() {
        var pieces = this.pieces.slice();
        pieces = pieces.sort(function(a,b) {
            var aBottom = a.getPosition().y+a.getHeight();
            var bBottom = b.getPosition().y+b.getHeight();
            return bBottom-aBottom;
        });
        for (var i = 0; i < pieces.length; i++) {
            this.hardDrop(pieces[i]);
        }
    }

    this.hardDrop = function(piece) {
        var position = piece.getPosition();
        this.clearArea(piece);
        while(this.canPlace(piece, {x: piece.getPosition().x, y: piece.getPosition().y+1})) {
            piece.setPosition(piece.getPosition().x, piece.getPosition().y+1);
        }
        this.place(piece);
    }

    this.clearArea = function(piece) {
        var position = piece.getPosition();
        var shape = piece.getShape();
        for (var y = 0; y < piece.getHeight(); y++) {
            for (var x = 0; x < piece.getWidth(); x++) {
                if (shape[y][x]) {
                    this.board[y+position.y][x+position.x] = null;
                }
            }
        }
    }

    this.remove = function(piece) {
        this.clearArea(piece);
        this.pieces.splice(this.pieces.indexOf(piece),1);
    }

    this.clearPiecesOnLine = function(line) {
        var piecesToClear = [];
        for (var x = 0; x < this.width; x++) {
            var piece = this.board[line][x];
            if (piecesToClear.indexOf(piece) === -1) {
                piecesToClear.push(piece);
            }
        }
        for (var i = 0; i < piecesToClear.length; i++) {
            this.remove(piecesToClear[i]);
        }
        return piecesToClear;
    }

    this.toString = function() {
        var str = "";
        for (var y = 0; y < this.height; y++) {
            for (var x = 0; x < this.width; x++) {
                str += this.board[y][x] ? "1" : "0";
            }
            str += "\n";
        }
        return str;
    }
}

function Piece (shape, fill, stroke, paper, cellWidth) {
    this.shape = shape;
    this.fill = fill;
    this.stroke = stroke;
    this.cellWidth = cellWidth;
    this.svgGroup = paper.g().append();
    this.position = {x:0, y:0};
    this.width = this.shape[0].length;
    this.height = this.shape.length;
    this.removed = false;
    for (var y = 0; y < this.height; y++) {
        for (var x = 0; x < this.width; x++) {
            if (this.shape[y][x]) {
                var rect = paper.rect(x*cellWidth, y*cellWidth, cellWidth, cellWidth);
                rect.attr({
                    fill: this.fill,
                    stroke: this.stroke
                });
                rect.appendTo(this.svgGroup);
            }
        }
    }
    this.setPosition = function(x, y) {
        this.position.x = x;
        this.position.y = y;
    }
    this.getPosition = function() {
        return this.position;
    }
    this.render = function() {
        var matrix = new Snap.Matrix();
        matrix.translate(this.position.x*cellWidth, this.position.y*cellWidth);
        this.svgGroup.attr({
            transform: matrix
        });
    }
    this.getWidth = function() {
        return this.width;
    }
    this.getHeight = function() {
        return this.height;
    }
    this.getShape = function() {
        return this.shape;
    }
    this.delete = function() {
        this.svgGroup.remove();
    }
    this.isRemoved = function() {
        return this.removed;
    }
}

var shapes = [
    [
        [0,1,0],
        [1,1,1]
    ],
    [
        [1,1,1,1]
    ],
    [
        [1,1,1],
        [0,1,0],
        [1,1,1]
    ],
    [
        [1,1],
        [1,1]
    ],
    [
        [1,1,1],
        [0,1,1],
        [0,1,1],
        [1,1,1]
    ],
    [
        [1,1,1,1],
        [1,1,1,1],
        [1,1,1,1],
        [1,1,1,1]
    ],
    [
        [1,0,1],
        [1,1,1]
    ]
];

var width = 10;
var height = 20;
var cellWidth = 20;
var paper = Snap("#svg");
var board = new Board(width, height);
var tick = 500;

paper.attr({
    width: cellWidth*width,
    height: cellWidth*height
});

for (var x = 0; x < width; x++) {
    for (var y = 0; y < height; y++) {
        var rect = paper.rect(x*cellWidth, y*cellWidth, cellWidth, cellWidth);
        rect.attr({
            fill: "#ccc",
            stroke: "#ddd"
        });
    }
}

var piece = new Piece(shapes[0], "red", "white", paper, cellWidth);
piece.setPosition(0, 18);
board.place(piece);

piece = new Piece(shapes[1], "orange", "white", paper, cellWidth);
piece.setPosition(3, 19);
board.place(piece);

piece = new Piece(shapes[2], "yellow", "white", paper, cellWidth);
piece.setPosition(2, 8);
board.place(piece);

piece = new Piece(shapes[3], "green", "white", paper, cellWidth);
piece.setPosition(0, 17);
board.place(piece);

piece = new Piece(shapes[4], "blue", "white", paper, cellWidth);
piece.setPosition(2, 15);
board.place(piece);


piece = new Piece(shapes[5], "indigo", "white", paper, cellWidth);
piece.setPosition(1, 11);
board.place(piece);

piece = new Piece(shapes[6], "violet", "white", paper, cellWidth);
piece.setPosition(7, 17);
piece.render();

function update() {
    if (piece.isRemoved()) {
        return;
    }
    var position = piece.getPosition();
    if (board.canPlace(piece, {x:position.x,y:position.y+1})) {
        piece.setPosition(position.x,position.y+1);
        board.place(piece);
        for (var y = 0; y < piece.getHeight(); y++) {
            if (board.hasFullLine(piece.getPosition().y+y)) {
                var removed = board.clearPiecesOnLine(piece.getPosition().y+y);
                setTimeout(function() {
                    for (var i = 0; i < removed.length; i++) {
                        removed[i].delete();
                    }
                    board.hardDropPieces();
                },tick);
            }
        }
    }
}
setTimeout(update, tick);

这几乎是董事会逻辑的要点。放置的碎片通过引用保存在一个数组中,清理后我将那些未被最低点移除的碎片分类,然后将它们中的每一个尽可能地丢弃。

当没有任何部分相互关联时,这是有效的,但我无法弄清楚它们是如何实现的,如this example中那样。

显然,蓝色部分是最低点,但由于绿色部分位于其中,因此无法向下移动。我考虑合并它们并丢弃它们,但这会导致其他问题。就像this case中会发生什么一样?

我很确定我只是很厚,并且有一种相对简单的方法来解决这个问题......?任何帮助将不胜感激!

所有部分都是自动生成的,并且有太多的部分,并且可以随时添加更多部分,以便不做出一般解决方案。

1 个答案:

答案 0 :(得分:2)

我发现有两个缺少逻辑的部分。第一部分是你进行滴剂的地方。您需要为每个块一次一步地执行此操作,然后继续执行此操作,直到您不再丢弃为止。喜欢这个

this.hardDropPieces = function() {
    var pieces = this.pieces.slice();
    pieces = pieces.sort(function(a,b) {
        var aBottom = a.getPosition().y+a.getHeight();
        var bBottom = b.getPosition().y+b.getHeight();
        return bBottom-aBottom;
    });
    var canStillDrop = true;
    while (canStillDrop) { // Keep going until we can't drop no more
        canStillDrop = false;
        for (var i = 0; i < pieces.length; i++) {
            canStillDrop = this.hardDrop(pieces[i]) ? true : canStillDrop;
        }
    }
}

this.hardDrop = function(piece) {
    var didDrop = false;
    var position = piece.getPosition();
    this.clearArea(piece);
    if(this.canPlace(piece, {x: position.x, y: position.y+1})) {
        piece.setPosition(position.x, position.y+1);
        didDrop = true; // Oh, I see we have dropped
    }
    this.place(piece);
    return didDrop; // Did we drop a spot? Then we should keep going
}

第二部分是您可以使用一点递归来检查是否有任何阻止您丢弃的图块实际连接到地板。你已经认识到这一点:

this.canPlace = function(piece, at) {
    // Will it fall below the floor? Then it's a no-go
    if (piece.getHeight()+at.y > this.height) {
        return false;
    }
    // Loop through shape
    for (var y = 0; y < piece.getHeight(); y++) {
        for (var x = 0; x < piece.getWidth(); x++) {
            // Ignore non-shape positions
            if (!piece.shape[y][x]) continue;
            // Get piece at current shape position
            var pieceAtPos = this.board[y+at.y][x+at.x];
            // Is the piece (or any that it's resting on) connected to the floor?
            if (pieceAtPos && pieceAtPos!==piece && this.isPieceGrounded(pieceAtPos, [piece]) ){
                return false;
            }
        }
    }
    return true;
}

但是也向isPieceGrounded问好。

this.isPieceGrounded = function(piece, testedPieces) {
    // Check all positions BELOW the piece
    var at = { x: piece.getPosition().x, y: piece.getPosition().y+1 };
    // Is it connected to the floor? 
    if (piece.getHeight()+at.y+1 >= this.height) {
        return true;
    }
    // *Sigh* Loop through THIS whole piece
    for (var y = 0; y < piece.getHeight(); y++) {
        for (var x = 0; x < piece.getWidth(); x++) {
            if (!piece.shape[y][x]) continue;
            var pieceAtPos = this.board[y+at.y][x+at.x];
            if (pieceAtPos && pieceAtPos!==piece && testedPieces.indexOf(pieceAtPos) < 0) {
                // Keep a list of all tested pieces so we don't end up in an infinite loop by testing them back and forth
                testedPieces.push(pieceAtPos);
                // Let's test that one and all its connected ones as well
                if (this.isPieceGrounded(pieceAtPos, testedPieces)) {
                    return true;
                };
            }
        }
    }
    return false;
}

http://jsfiddle.net/971yvc8r/2/

我确定有很多不同的解决方案,但我认为这样的话可能效率最高。