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

问题描述

我正在学习 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,1],]
];

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

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

const O = [
    [
        [0,]
];

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

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

const Z = [
    [
        [1,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,SQ);

    }
};



// a VACANT (empty) square has this color.
// const VACANT = "rgba(19,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,"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,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,this.activeTetromino)) {
            this.update();
            this.x++;
            this.draw();
        }
    }

    // Piece.prototype.moveLeft
    this.moveLeft = function() {
            if (!this.collide(-1,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,nextPat)) {
            if (this.x > COL / 2) {
                kick = -1;
            } else {
                kick = 1;
            }
        }
        if (!this.collide(kick,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,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);
                        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,SQ);
                }
                //Increase score
                score += 100;
                console.log(score)
            }
            // if (this.x > 0) {
            //     // x = nextPiece();
            // }
        }
        update();
    }
}

function update() {
    ctx.clearRect(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,color) {
    pre.fillStyle = color;
    pre.fillRect(x * SQ,SQ);
    pre.strokeStyle = "black";;
    pre.linewidth = 1;
    pre.beginPath();
    pre.strokeRect(x * SQ + 1,SQ - 2);
    pre.strokeRect(x * SQ + 2,SQ - 4);
    // pre.strokeRect(x * SQ + 2.5,SQ - 5);




    pre.linewidth = 1;
    pre.beginPath();
    pre.strokeRect(x * SQ + 6.5,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.color);

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

    }
}

解决方法

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

这就是社区所做的。

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

check how the game looks after the edit

因此,首先我创建了一个名为 shadow 的新原型函数,然后使用 for 循环检查 tetromino 碰撞的位置返回 for 循环实例(Tetromino 碰撞的位置)减 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,"piece","shadow")
                    }
                }
            }
        }
    }

在这个函数中,我将从 shadow 函数返回的值存储在一个名为 sha 的变量中,然后绘制 tetromino 块,将 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();



    }
}

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