如何找到俄罗斯方块 (Tetromino) 在网格上的位置并将其显示为预览?

How to find where the tetris block (Tetromino) land on the gird and Show it as preview?

我正在学习HTML canvas并选择制作俄罗斯方块游戏作为第一个项目我通过观看一些教程完成了游戏。但是想通过在页面上的 Tetromino 登陆位置添加实时预览来让它变得更好,任何人都可以帮助我如何做到这一点? 我所说的预览是指如何向玩家显示棋子落在 gameArea 上的位置。

Something like this

Html 文件:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tetris Game</title>
    <link rel="stylesheet" href="./Game Assets/Style/style.css">
    <link rel="shortcut icon" href="./Game Assets/img/tetris.png" type="image/x-icon">
</head>

<body body>
    <div id="wrapper">
        <div id="GameArea">
            <canvas id="gameCanvas" width="300" height="600">
            </canvas>
            <div id="SLN">
                <div id="SCORE">
                    <h3>Score:</h3>
                    <div id="sc">0</div>
                </div>
                <div id="Level">
                </div>
                <div id="NEXT">
                    <h3>Next:</h3>
                    <canvas id="preCanvas" width="120" height="120"></canvas>
                </div>
            </div>

        </div>
    </div>

    <script src="./Game Assets/Script/index.js"></script>
</body>

</html>


这是Java脚本文件

const canvas = document.getElementById("gameCanvas"); //Get The Canvas Element
const ctx = canvas.getContext("2d");

const preview = document.getElementById("preCanvas");
const pre = preview.getContext("2d");

//Tetrominoes Code
const I = [
    [
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
    ],
    [
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ],

    [
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
    ]
];

const J = [
    [
        [1, 0, 0],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 1],
        [0, 1, 0],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [0, 0, 1]
    ],
    [
        [0, 1, 0],
        [0, 1, 0],
        [1, 1, 0]
    ]
];

const L = [
    [
        [0, 0, 1],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 0],
        [0, 1, 1]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [1, 0, 0]
    ],
    [
        [1, 1, 0],
        [0, 1, 0],
        [0, 1, 0]
    ]
];

const O = [
    [
        [0, 0, 0, 0],
        [0, 1, 1, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0],
    ]
];

const S = [
    [
        [0, 1, 1],
        [1, 1, 0],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 1],
        [0, 0, 1]
    ],
    [
        [0, 0, 0],
        [0, 1, 1],
        [1, 1, 0]
    ],
    [
        [1, 0, 0],
        [1, 1, 0],
        [0, 1, 0]
    ]
];

const T = [
    [
        [0, 1, 0],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 0],
        [0, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 1, 0],
        [1, 1, 0],
        [0, 1, 0]
    ]
];

const Z = [
    [
        [1, 1, 0],
        [0, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 0, 1],
        [0, 1, 1],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 0],
        [0, 1, 1]
    ],
    [
        [0, 1, 0],
        [1, 1, 0],
        [1, 0, 0]
    ]
];
// we first need too define ROW and  columns constants
const ROW = 20;
const COL = 10;

// Const for square size
const SQ = 30;

function drawSquare(x, y, color, stroke, T) {
    if (stroke === "piece") {
        ctx.fillStyle = color;
        ctx.fillRect(x * SQ, y * SQ, SQ, SQ);
        ctx.strokeStyle = "black";;
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.strokeRect(x * SQ + 1, y * SQ + 1, SQ - 2, SQ - 2);
        ctx.strokeRect(x * SQ + 2, y * SQ + 2, SQ - 4, SQ - 4);
        // ctx.strokeRect(x * SQ + 2.5, y * SQ + 2.5, SQ - 5, SQ - 5);




        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.strokeRect(x * SQ + 6.5, y * SQ + 6.5, SQ - 13, SQ - 13);



        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x * SQ + 2, y * SQ + 2)
        ctx.lineTo(x * SQ + 6.5, y * SQ + 6.5);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(x * SQ + SQ - 2, y * SQ + 2)
        ctx.lineTo(x * SQ + SQ - 6.5, y * SQ + 6.5);
        ctx.stroke();
        // Downn

        ctx.beginPath();
        ctx.moveTo(x * SQ + 2, y * SQ - 2 + SQ)
        ctx.lineTo(x * SQ + 6.5, y * SQ - 6.5 + SQ);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(x * SQ + SQ - 2, y * SQ - 2 + SQ)
        ctx.lineTo(x * SQ - 6.5 + SQ, y * SQ - 6.5 + SQ);
        ctx.stroke();



    } else {

        ctx.fillStyle = color;
        ctx.fillRect(x * SQ, y * SQ, SQ, SQ);

    }
};



// a VACANT (empty) square has this color.
// const VACANT = "rgba(19, 18, 18, 0.719)";
const VACANT = "transparent";
// now we define the board array.
let board = [];


// let's create the rows.
for (let r = 0; r < ROW; r++) {
    board[r] = [];
    // let's create the columns
    for (let c = 0; c < COL; c++) {
        board[r][c] = VACANT;
        // when we first draw the board all the square are empty, so every square has the value "VACANT".
    }
}


function drawBoard() {
    for (r = 0; r < ROW; r++) {
        for (c = 0; c < COL; c++) {
            if (board[r][c] != VACANT) {
                drawSquare(c, r, board[r][c], "piece")
            } else {
                drawSquare(c, r, board[r][c], "board");
            }

        }
    }
}
drawBoard()
    //Piece and there color
let Score = 0;
const PIECES = [
        [Z, "blue"],
        [S, "rgb(238, 132, 46)"],
        [T, "rgb(248, 232, 232)"],
        [O, "yellow"],
        [L, "rgb(245, 94, 144)"],
        [I, "purple"],
        [J, "rgb(121, 236, 240)"]
    ]
    // Generator random Piece
function NewPiece(canvas) {
    let r = randomN = Math.floor(Math.random() * PIECES.length)

    return new Piece(PIECES[r][0], PIECES[r][1])

}


function nextPiece() {
    r = Math.floor(Math.random() * PIECES.length)
    previewPiece = new PreviewPiece(PIECES[r][0], PIECES[r][1]);
    xd = r
    previewPiece.clear();
    previewPiece.draw();
    return xd

}

x = nextPiece()
let p = new Piece(PIECES[x][0], PIECES[x][1]);



function Piece(Tetromino, color) {
    this.tetromino = Tetromino;
    this.color = color;
    this.tetrominoN = 0;
    this.activeTetromino = this.tetromino[this.tetrominoN];
    this.x = 3;
    this.y = -2;
    // Piece.prototype.draw
    this.draw = function() {
        for (r = 0; r < this.activeTetromino.length; r++) {
            for (c = 0; c < this.activeTetromino.length; c++) {
                if (this.activeTetromino[r][c]) {
                    drawSquare(this.x + c, this.y + r, this.color, "piece");
                }
            }

        }


    }

    // Piece.prototype.Clear 
    this.update = function() {
            for (r = 0; r < this.activeTetromino.length; r++) {
                for (c = 0; c < this.activeTetromino.length; c++) {
                    if (this.activeTetromino[r][c]) {
                        update();
                    }
                }
            }
        }
        // Piece.prototype.moveDown
    this.moveDown = function() {
            if (!this.collide(0, 1, this.activeTetromino)) {

                this.update();
                this.y++;
                this.draw();
                // ctx.shadowOffsetY -= SQ
            } else {
                //generate new Piece
                this.lockPiece();
                p = new Piece(PIECES[x][0], PIECES[x][1]);
            }
            if (this.y === -1) {
                // ctx.shadowOffsetY -= 1 * SQ
                x = nextPiece();
            }
        }
        // Piece.prototype.moveRight 
    this.moveRight = function() {
        if (!this.collide(1, 0, this.activeTetromino)) {
            this.update();
            this.x++;
            this.draw();
        }
    }

    // Piece.prototype.moveLeft
    this.moveLeft = function() {
            if (!this.collide(-1, 0, this.activeTetromino)) {
                this.update();
                this.x--;
                this.draw();
            }
        }
        // Piece.prototype.rotate 
    this.rotate = function() {
        let nextPat = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length];
        let kick = 0;
        if (this.collide(0, 0, nextPat)) {
            if (this.x > COL / 2) {
                kick = -1;
            } else {
                kick = 1;
            }
        }
        if (!this.collide(kick, 0, nextPat)) {

            this.update();
            this.x += kick;
            this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length;
            this.activeTetromino = this.tetromino[this.tetrominoN];
            this.draw();
        }
    }

    // Piece.prototype.collide
    this.collide = function(x, y, piece) {
        for (r = 0; r < piece.length; r++) {
            for (c = 0; c < piece.length; c++) {
                //Empty square skip
                if (!piece[r][c]) {
                    continue;
                }
                let newX = this.x + c + x;
                let newY = this.y + r + y;
                if (newX < 0 || newX > COL || newY >= ROW) {
                    return true;
                }
                if (newY < 0) {
                    continue;
                }
                if (board[newY][newX] != VACANT) { return true; }
            }
        }
    }
    this.lockPiece = function() {
        for (r = 0; r < this.activeTetromino.length; r++) {
            for (c = 0; c < this.activeTetromino.length; c++) {
                //skip empty block
                if (!this.activeTetromino[r][c]) {

                    continue;
                }
                //piece to lock if reaches the top
                if (this.y + r < 0) {
                    //Game over
                    // alert("Game Over");
                    //Stop Game
                    gameOver = true;
                    if (gameOver) {
                        // document.getElementById("gameCanvas").style.display = "none"
                    }
                    break;
                }
                //we lock piece when it reaches bottom
                board[this.y + r][this.x + c] = this.color
            }
        }
        // remove full rows
        for (r = 0; r < ROW; r++) {
            let isRowFull = true;
            for (c = 0; c < COL; c++) {
                isRowFull = isRowFull && (board[r][c] != VACANT);
            }
            if (isRowFull) {
                for (y = r; y > 1; y--) {
                    for (c = 0; c < COL; c++) {

                        // ctx.clearRect(y * SQ, c * SQ, SQ, SQ);
                        board[y][c] = board[y - 1][c];

                    }
                }
                //the top row board[0][...] has no row above it
                for (c = 0; c < COL; c++) {
                    board[0][c] = VACANT;
                    // ctx.clearRect(r * SQ, c * SQ, SQ, SQ);
                }
                //Increase Score
                Score += 100;
                console.log(Score)
            }
            // if (this.x > 0) {
            //     // x = nextPiece();
            // }
        }
        update();
    }
}

function update() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    drawBoard();
}


let dropStart = Date.now();
let gameOver = false;
let speed = 1000;
let PrevScore = 0;

function drop() {
    let now = Date.now();
    let delta = now - dropStart;
    if (delta > speed) {
        // p.x = 3;
        // p.y = -1
        p.moveDown()

        dropStart = Date.now();
    }
    if (!gameOver) {

        requestAnimationFrame(drop)
    }

    document.getElementById("sc").innerHTML = Score;

    if (Score - PrevScore > 1000) {
        speed -= 10;
        PrevScore = Score;
    }
}
drop()

document.addEventListener("keydown", CONTROL);

function CONTROL(event) {
    if (event.keyCode == 37) {
        if (!gameOver) {
            event.preventDefault();
            p.moveLeft();
            // dropStart = Date.now();
        }
    } else if (event.keyCode == 38) {
        if (!gameOver) {
            event.preventDefault();

            p.rotate();
            // dropStart = Date.now();
        }
    } else if (event.keyCode == 39) {
        if (!gameOver) {
            event.preventDefault();

            p.moveRight()
                // dropStart = Date.now();
        }
    } else if (event.keyCode == 40) {
        if (!gameOver) {
            event.preventDefault();
            Score++;
            p.moveDown();
        }
    }
}
document.getElementById("gameCanvas").addEventListener("click", function(e) {
    e.preventDefault();
    p.rotate();
    dropStart = Date.now();
})
let TouchX, TouchY, MoveX = 0,
    MoveY = 0,
    XDiff, YDiff;
document.getElementById("GameArea").addEventListener("touchstart", function(e) {
    TouchX = e.touches[0].clientX
    TouchY = e.touches[0].clientY
        // console.log("Tx: ", TouchX, "Ty: ", TouchY)
    document.getElementById("GameArea").addEventListener('touchmove', function(e) {
        e.preventDefault() // prevent scrolling when inside DIV
        XDiff = TouchX - MoveX;
        YDiff = TouchY - MoveY;
        if (Math.abs(e.touches[0].clientX - MoveX) > 15) {
            if (Math.abs(XDiff) > Math.abs(YDiff)) {

                if (XDiff < 0) {
                    //Right Swipe;
                    p.moveRight();
                } else {
                    // Left Swipe
                    p.moveLeft();
                }
            }
        } else if (Math.abs(e.touches[0].clientY - MoveY) > 0) {
            if (Math.abs(XDiff) < Math.abs(YDiff)) {
                if (XDiff < 0) {
                    //Down Swipe;
                    Score++;
                    p.moveDown();
                }
            }
        }
        MoveX = e.touches[0].clientX;
        MoveY = e.touches[0].clientY;

        // console.log("x: ", e.touches[0].clientX, "y: ", e.touches[0].clientY);


    }, { passive: false })
}, false);


function drawSquarePreview(x, y, color) {
    pre.fillStyle = color;
    pre.fillRect(x * SQ, y * SQ, SQ, SQ);
    pre.strokeStyle = "black";;
    pre.lineWidth = 1;
    pre.beginPath();
    pre.strokeRect(x * SQ + 1, y * SQ + 1, SQ - 2, SQ - 2);
    pre.strokeRect(x * SQ + 2, y * SQ + 2, SQ - 4, SQ - 4);
    // pre.strokeRect(x * SQ + 2.5, y * SQ + 2.5, SQ - 5, SQ - 5);




    pre.lineWidth = 1;
    pre.beginPath();
    pre.strokeRect(x * SQ + 6.5, y * SQ + 6.5, SQ - 13, SQ - 13);



    pre.lineWidth = 1;
    pre.beginPath();
    pre.moveTo(x * SQ + 2, y * SQ + 2)
    pre.lineTo(x * SQ + 6.5, y * SQ + 6.5);
    pre.stroke();
    pre.beginPath();
    pre.moveTo(x * SQ + SQ - 2, y * SQ + 2)
    pre.lineTo(x * SQ + SQ - 6.5, y * SQ + 6.5);
    pre.stroke();
    // Down
    pre.beginPath();
    pre.moveTo(x * SQ + 2, y * SQ - 2 + SQ)
    pre.lineTo(x * SQ + 6.5, y * SQ - 6.5 + SQ);
    pre.stroke();
    pre.beginPath();
    pre.moveTo(x * SQ + SQ - 2, y * SQ - 2 + SQ)
    pre.lineTo(x * SQ - 6.5 + SQ, y * SQ - 6.5 + SQ);
}

function PreviewPiece(Tetromino, color) {
    this.tetromino = Tetromino;
    this.color = color;
    this.tetrominoN = 0;
    this.activeTetromino = this.tetromino[this.tetrominoN];
    this.x = 0;
    this.y = 0;
    this.draw = function() {
        for (r = 0; r < this.activeTetromino.length; r++) {
            for (c = 0; c < this.activeTetromino.length; c++) {
                if (this.activeTetromino[r][c]) {
                    drawSquarePreview(this.x + c, this.y + r, this.color);

                }
            }
        }
    }
    this.clear = function() {
        pre.clearRect(0, 0, preview.width, preview.height);

    }
}

回答我自己的问题很有趣,但是,我在梦中找到了答案,所以让我把它放在这里,如果有人想知道答案,可以得到帮助因为,

That's what the community do.

所以我们不要再浪费时间了。

check how the game looks after the edit

所以我首先创建了一个名为 shadow 的新原型函数,然后使用 for 循环检查四联骨牌碰撞的位置 return for 循环实例(四联骨牌碰撞的地方)减 1。因为我们要绘制碰撞前的影子 1 步像这样

this.shadow = function() {
        for (i = 1; i < ROW; i++) {
            if (this.collide(0, i, this.activeTetromino)) {

                return i - 1 //i is the point of collision therefore we draw piece one step before collision
            } else { continue; }
        }
    }

然后,我更新了绘图功能 像这样

this.draw = function() {
        let sha = this.shadow();
        for (r = 0; r < this.activeTetromino.length; r++) {
            for (c = 0; c < this.activeTetromino.length; c++) {
                if (this.activeTetromino[r][c]) {
                    drawSquare(this.x + c, this.y + r, this.color, "piece");

                   // this will also work without the if condition 
                    if (this.y + r !== this.y + r + sha) {
                        drawSquare(this.x + c, this.y + sha + r, this.color, "piece", "shadow")
                    }
                }
            }
        }
    }

在这个函数中,我将影子函数的值 returned 存储在一个名为 sha 的变量中,然后绘制四联骨牌,将 y 更改为 this.y + sha + r 并传递一个额外的绘制阴影的参数 & 你猜对了 drawSquare 函数中有一个更新,可以绘制不同的正方形,这样玩家就可以弄清楚哪块是阴影,哪块是实际的块。在这里你可以做任何事情让它与众不同,比如只给它灰色或只画笔划边界或改变 globalApha 值所以我选择像这样改变全局 alpha 值

function drawSquare(x, y, color, stroke, T) {
    ctx.globalAlpha = 1;
    if (stroke === "piece") {
        if (T === "shadow") { ctx.globalAlpha = 0.4; }
        ctx.fillStyle = color;
        ctx.fillRect(x * SQ, y * SQ, SQ, SQ);
        ctx.strokeStyle = "black";

        ctx.lineWidth = 1;
        if (T === "shadow") { ctx.lineWidth = 3; };
        ctx.beginPath();
        ctx.strokeRect(x * SQ + 1, y * SQ + 1, SQ - 2, SQ - 2);
        ctx.strokeRect(x * SQ + 2, y * SQ + 2, SQ - 4, SQ - 4);
        // ctx.strokeRect(x * SQ + 2.5, y * SQ + 2.5, SQ - 5, SQ - 5);




        ctx.lineWidth = 1;
        if (T === "shadow") { ctx.lineWidth = 2; };
        ctx.beginPath();
        ctx.strokeRect(x * SQ + 6.5, y * SQ + 6.5, SQ - 13, SQ - 13);



        ctx.lineWidth = 2;
        // if (T === "shadow") { ctx.lineWidth = 5; };
        ctx.beginPath();
        ctx.moveTo(x * SQ + 2, y * SQ + 2)
        ctx.lineTo(x * SQ + 6.5, y * SQ + 6.5);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(x * SQ + SQ - 2, y * SQ + 2)
        ctx.lineTo(x * SQ + SQ - 6.5, y * SQ + 6.5);
        ctx.stroke();
        // Downn

        ctx.beginPath();
        ctx.moveTo(x * SQ + 2, y * SQ - 2 + SQ)
        ctx.lineTo(x * SQ + 6.5, y * SQ - 6.5 + SQ);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(x * SQ + SQ - 2, y * SQ - 2 + SQ)
        ctx.lineTo(x * SQ - 6.5 + SQ, y * SQ - 6.5 + SQ);
        ctx.stroke();



    }
}

如果您正在寻找这个,希望这对您有所帮助。如果您有更好的优化方法,请随时回答。我不是一个很好的解释者,这是我的问题,也是这个表格的答案,所以请原谅任何语法问题。