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

Hard-dropping pieces in tetris-like game

我正在构建一款类似俄罗斯方块的游戏,在其中,当您拥有完整的一行时,我不会只删除一行,而是删除所有连接的部分。这让我在清理碎片后陷入困境。

请参阅 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 会发生什么?

我很确定我只是太笨了,有一种相对简单的方法可以解决这个问题......?任何帮助将不胜感激!

所有片段都是自动生成的,太多了,可以随时添加更多,不做一个通用的解决方案。

我发现有两个部分缺少逻辑。第一部分是你进行空投的地方。你需要对每个方块一步一步地做,然后继续做,直到你不能再掉下去了。像这样

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/

我敢肯定有很多不同的解决方案,但我认为这样的方法可能是最有效的。