הקובץ tetris.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">
    <link rel="stylesheet" href="styles.css">
    <title>Reference final project</title>
</head>
<body>
    <h1>טטריס</h1>
    <canvas id="baseCanvas" width="800px" height="800px"></canvas>
    <script src="shape_manager.js"></script>
    <script src="drawingManager.js"></script>
    <script src="tetris_script.js"></script>
</body>
</html>

הקובץ tetris_script.js:

function GenerateNewShape() {
    newShape = {
        shapeType: SHAPE_TYPE[Math.floor(Math.random() * SHAPE_TYPE.length)],
        left: BoardSize.cols / 2 - 1,
        top: -1,
        shapeOrient: 0,
    }
    newShape.squareArr = GetShape(newShape.shapeType, newShape.top, newShape.left, newShape.shapeOrient)

    return newShape
}

function DrawFallingShape() {
        curShape.squareArr.forEach(shapeSquare => {
            fillSquare(shapeSquare)
        });
}

function Init_occupied_squares() {
    retval = []
    for(i = 0; i < BoardSize.rows; i++) {
        boardRow = Array(BoardSize.cols).fill(false)
        retval.push(boardRow)
    }
    return retval
}

function DrawFrame() {
    clearBoard()
    drawBoard()
    drawGrid()
    DrawFallingShape()
    drawOccupiedSquares()
}

function IsSquareOccupied(square) {
    if (square.row >= BoardSize.rows || square.col < 0 || square.col >= BoardSize.cols) {
        return true;
    }
    if(square.row < 0)
        return false
    return occupiedSquares[square.row][square.col]
}

function IsShapeOccupied(shape) {
    return shape.some(square => IsSquareOccupied(square))
}

function AddFallingShapeToOccupiedSquares() {
    curShape.squareArr.forEach(shapeSquare => {
        if(shapeSquare.row < 0) {
            clearInterval(intervalID)
            return
        } else
            occupiedSquares[shapeSquare.row][shapeSquare.col] = true})
}

function RemoveFullLines() {
    fullLines = []
    for(i = 0; i < occupiedSquares.length; i++) {
        if(occupiedSquares[i].every(square => square))
            fullLines.push(i)
    }
    counter = 0
    while(fullLines.length > 0) {
        if(counter >= 5)
            break
        console.log(fullLines.length)
        for(i = fullLines[0]; i>0; i--) {
            occupiedSquares[i] = occupiedSquares[i - 1]
        }
        occupiedSquares[0] = Array(BoardSize.cols).fill(false)
        fullLines.shift()
        counter++
    }
}

function drawOccupiedSquares() {
    for (i = 0; i < occupiedSquares.length; i++) {
        for(j = 0; j < occupiedSquares[i].length; j++) {
            if(occupiedSquares[i][j])
                fillSquare({row:i, col:j})
        }
    }
}

function MainLoop() {
    DrawFrame()

    movedDownShape = GetShape(curShape.shapeType, curShape.top + 1,
        curShape.left, curShape.shapeOrient)
    if (IsShapeOccupied(movedDownShape)) {
        AddFallingShapeToOccupiedSquares()
        RemoveFullLines()
        curShape = GenerateNewShape()
    } else {
        curShape.squareArr = movedDownShape
        curShape.top++
    }
}

document.addEventListener('keydown', (e) => {
    switch (e.key) {
        case 'ArrowLeft':
            e.preventDefault()
            movedLeftShape = GetShape(curShape.shapeType, curShape.top,
                curShape.left - 1, curShape.shapeOrient)
            if (!IsShapeOccupied(movedLeftShape)) {
                curShape.squareArr = movedLeftShape
                curShape.left--
            }
            break
        case 'ArrowRight':
            e.preventDefault()
            movedRightShape = GetShape(curShape.shapeType, curShape.top,
                curShape.left + 1, curShape.shapeOrient)
            if (!IsShapeOccupied(movedRightShape)) {
                curShape.squareArr = movedRightShape
                curShape.left++
            }
            break
        case 'ArrowUp':
            e.preventDefault()
            rotatedShape = GetShape(curShape.shapeType, curShape.top,
                curShape.left, (curShape.shapeOrient + 1) % 4)
            if (!IsShapeOccupied(rotatedShape)) {
                curShape.squareArr = rotatedShape
                curShape.shapeOrient = (curShape.shapeOrient + 1) % 4
            }
            break
    }
})

const stepInMS  = 300
const BoardSize = {rows: 20, cols: 10}

console.log('Welcome to Tetris by Lokipod')
curShape = GenerateNewShape()
occupiedSquares = Init_occupied_squares()
intervalID = setInterval(MainLoop, stepInMS);

הקובץ shape_manager.js:

 

const SHAPE_TYPE = ["L", "L_REV", "DOG", "DOG_REV",
    "LINE", "SQUARE", "PLUS"]

function GetBaseShape(shapeType) {
    let baseShape = {}
    baseShape["L"] = [{col: -1, row: -1}, {col: 0, row: -1}, {col: 0, row: 0}, {col: 0, row: 1}]
    baseShape["L_REV"] = [{col: 0, row: -1}, {col: 1, row: -1}, {col: 0, row: 0}, {col: 0, row: 1}]
    baseShape["DOG"] = [{col: 0, row: 0}, {col: 1, row: 0}, {col: -1, row: 1}, {col: 0, row: 1}]
    baseShape["DOG_REV"] = [{col: -1, row: 0}, {col: 0, row: 0}, {col: 0, row: 1}, {col: 1, row: 1}]
    baseShape["LINE"] = [{col: 0, row: -2}, {col: 0, row: -1}, {col: 0, row: 0}, {col: 0, row: 1}]
    baseShape["SQUARE"] = [{col: 0, row: 0}, {col: 1, row: 0}, {col: 0, row: 1}, {col: 1, row: 1}]
    baseShape["PLUS"] = [{col: 0, row: -1}, {col: -1, row: 0}, {col: 0, row: 0}, {col: 1, row: 0}]

    return baseShape[shapeType]
}

function RotateShape(baseShape, orientation) {
    rotatedShape = baseShape
    for(i = 0; i < orientation; i++) {
        rotatedShape = rotatedShape.map(sq => {return {col: -sq.row, row: sq.col}})
    }
    return rotatedShape
}

function GetShape(shapeType, top, left, shape_orient) {
    baseShape = GetBaseShape(shapeType)
    rotatedShape = RotateShape(baseShape, shape_orient)
    shapeWithOffset = rotatedShape.map(square => { return { row: square.row + top, col: square.col + left } })
    return shapeWithOffset
}

 

הקובץ drawingManager.js:

const squareSize = 20
var canvas = document.getElementById("baseCanvas");
var ctx = canvas.getContext("2d");

function drawBoard() {
    ctx.beginPath();
    ctx.rect(0, 0, BoardSize.cols * squareSize, BoardSize.rows * squareSize);
    ctx.stroke();
}

function clearBoard() {
    ctx.fillStyle = '#FFFFFF'
    ctx.fillRect(0, 0, BoardSize.cols * squareSize, BoardSize.rows * squareSize)
}

function drawGrid() {
    startPoint = {}
    endPoint = {}
    startPoint.col = 0
    endPoint.col = BoardSize.cols

    ctx.fillStyle = '#000000'

    //draw horizontal lines
    for(i = 0; i < BoardSize.rows; i++) {
        startPoint.row = i
        endPoint.row = i
        drawLine(startPoint, endPoint)
    }

    startPoint.row = 0
    endPoint.row = BoardSize.rows
    //draw vertical lines
    for(i = 0; i < BoardSize.cols; i++) {
        startPoint.col = i
        endPoint.col = i
        drawLine(startPoint, endPoint)
    }
}

function drawLine(startPoint, endPoint) {
    if(         startPoint.row < 0 || startPoint.row > BoardSize.rows
            ||  startPoint.col < 0 || startPoint.col > BoardSize.cols
            ||  endPoint.row < 0   || endPoint.row > BoardSize.rows
            ||  endPoint.col < 0   || endPoint.col > BoardSize.cols) {
                console.log("(" + startPoint.row + ", " + startPoint.col + ") : ("
                + endPoint.row + ", " + endPoint.col + ")")
                throw "Line is out of bounds:"
            }
    ctx.beginPath();
    ctx.moveTo(startPoint.col * squareSize, startPoint.row * squareSize)
    ctx.lineTo(endPoint.col * squareSize, endPoint.row * squareSize)
    ctx.stroke();
}

function fillSquare(position) {
    ctx.fillStyle = '#000000'
    ctx.beginPath();
    ctx.fillRect(position.col * squareSize, position.row * squareSize, squareSize, squareSize);
}