// Stolen from https://codepen.io/loktar00/pen/BaGqXY // However has modifications to work good as a theme "use strict"; function createTetris() { var tetrominos = [ { // box colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], ], }, { // stick colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], ], }, { // z colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0], ], }, { // T colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 0, 0], [0, 1, 1, 1], [0, 0, 1, 0], [0, 0, 0, 0], ], }, { // s colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 0, 0], [0, 1, 1, 0], [1, 1, 0, 0], [0, 0, 0, 0], ], }, { // backwards L colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 0, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], ], }, { // L colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"], data: [ [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0], ], }, ]; var Tetris = function (x, y, width, height) { this.posX = x || 0; this.posY = y || 0; this.width = width || window.innerWidth; this.height = height || window.innerHeight; this.bgCanvas = document.createElement("canvas"); this.fgCanvas = document.createElement("canvas"); this.bgCanvas.width = this.fgCanvas.width = this.width; this.bgCanvas.height = this.fgCanvas.height = this.height; this.bgCtx = this.bgCanvas.getContext("2d"); this.fgCtx = this.fgCanvas.getContext("2d"); this.bgCanvas.style.left = this.posX + "px"; this.bgCanvas.style.top = this.posY + "px"; // Stay in background this.bgCanvas.style.position = "absolute"; this.fgCanvas.style.position = "absolute"; this.fgCanvas.style.left = this.posX + "px"; this.fgCanvas.style.top = this.posY + "px"; document.body.insertBefore(this.fgCanvas, document.body.firstChild); document.body.insertBefore(this.bgCanvas, document.body.firstChild); this.init(); }; Tetris.prototype.init = function () { this.curPiece = { data: null, colors: [0, 0, 0], x: 0, y: 0, }; this.lastMove = Date.now(); this.curSpeed = 50 + Math.random() * 50; this.unitSize = 20; this.linesCleared = 0; this.level = 0; this.loseBlock = 0; // init the board this.board = []; this.boardWidth = Math.floor(this.width / this.unitSize); this.boardHeight = Math.floor(this.height / this.unitSize); var board = this.board, boardWidth = this.boardWidth, boardHeight = this.boardHeight, halfHeight = boardHeight / 2, curPiece = this.curPiece, x = 0, y = 0; // init board for (x = 0; x <= boardWidth; x++) { board[x] = []; for (y = 0; y <= boardHeight; y++) { board[x][y] = { data: 0, colors: ["rgb(0,0,0)", "rgb(0,0,0)", "rgb(0,0,0)"], }; if (Math.random() > 0.15 && y > halfHeight) { board[x][y] = { data: 1, colors: tetrominos[Math.floor(Math.random() * tetrominos.length)].colors, }; } } } // collapse the board a bit for (x = 0; x <= boardWidth; x++) { for (y = boardHeight - 1; y > -1; y--) { if (board[x][y].data === 0 && y > 0) { for (var yy = y; yy > 0; yy--) { if (board[x][yy - 1].data) { board[x][yy].data = 1; board[x][yy].colors = board[x][yy - 1].colors; board[x][yy - 1].data = 0; board[x][yy - 1].colors = [ "rgb(0,0,0)", "rgb(0,0,0)", "rgb(0,0,0)", ]; } } } } } var self = this; window.addEventListener("keydown", function (e) { switch (e.keyCode) { case 37: if (self.checkMovement(curPiece, -1, 0)) { curPiece.x--; } break; case 39: if (self.checkMovement(curPiece, 1, 0)) { curPiece.x++; } break; case 40: if (self.checkMovement(curPiece, 0, 1)) { curPiece.y++; } break; case 32: case 38: curPiece.data = self.rotateTetrimono(curPiece); break; } }); // render the board this.checkLines(); this.renderBoard(); // assign the first tetri this.newTetromino(); this.update(); }; Tetris.prototype.update = function () { var curPiece = this.curPiece; if (!this.checkMovement(curPiece, 0, 1)) { if (curPiece.y < -1) { // you lose this.loseScreen(); return true; } else { this.fillBoard(curPiece); this.newTetromino(); } } else { if (Date.now() > this.lastMove) { this.lastMove = Date.now() + this.curSpeed; if (this.checkMovement(curPiece, 0, 1)) { curPiece.y++; } else { this.fillBoard(curPiece); this.newTetromino(); } } } this.render(); var self = this; requestAnimationFrame(function () { self.update(); }); }; // render only the board. Tetris.prototype.renderBoard = function () { var canvas = this.bgCanvas, ctx = this.bgCtx, unitSize = this.unitSize, board = this.board, boardWidth = this.boardWidth, boardHeight = this.boardHeight; ctx.clearRect(0, 0, canvas.width, canvas.height); for (var x = 0; x <= boardWidth; x++) { for (var y = 0; y <= boardHeight; y++) { if (board[x][y].data !== 0) { var bX = x * unitSize, bY = y * unitSize; //ctx.shadowBlur = 5; //ctx.shadowColor = "rgba(0,255,0,.6)"; ctx.fillStyle = board[x][y].colors[0]; ctx.fillRect(bX, bY, unitSize, unitSize); ctx.fillStyle = board[x][y].colors[1]; ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4); ctx.fillStyle = board[x][y].colors[2]; ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8); } } } }; // Render the current active piece Tetris.prototype.render = function () { var canvas = this.fgCanvas, ctx = this.fgCtx, unitSize = this.unitSize, curPiece = this.curPiece; ctx.clearRect(0, 0, canvas.width, canvas.height); for (var x = 0; x < 4; x++) { for (var y = 0; y < 4; y++) { if (curPiece.data[x][y] === 1) { var xPos = (curPiece.x + x) * unitSize, yPos = (curPiece.y + y) * unitSize; if (yPos > -1) { ctx.shadowBlur = 10; ctx.shadowColor = "rgba(0,255,0,.6)"; ctx.fillStyle = curPiece.colors[0]; ctx.fillRect(xPos, yPos, unitSize, unitSize); ctx.fillStyle = curPiece.colors[1]; ctx.fillRect(xPos + 2, yPos + 2, unitSize - 4, unitSize - 4); ctx.fillStyle = curPiece.colors[2]; ctx.fillRect(xPos + 4, yPos + 4, unitSize - 8, unitSize - 8); } } } } }; // Make sure we can mov where we want. Tetris.prototype.checkMovement = function (curPiece, newX, newY) { var piece = curPiece.data, posX = curPiece.x, posY = curPiece.y, board = this.board, boardWidth = this.boardWidth, boardHeight = this.boardHeight; for (var x = 0; x < 4; x++) { for (var y = 0; y < 4; y++) { if (piece[x][y] === 1) { if (!board[posX + x + newX]) { board[posX + x + newX] = []; } if (!board[posX + x + newX][y + posY + newY]) { board[posX + x + newX][y + posY + newY] = { data: 0, }; } if ( posX + x + newX >= boardWidth || posX + x + newX < 0 || board[posX + x + newX][y + posY + newY].data == 1 ) { return false; } if (posY + y + newY > boardHeight) { return false; } } } } return true; }; // checks for completed lines and clears them Tetris.prototype.checkLines = function () { var board = this.board, boardWidth = this.boardWidth, boardHeight = this.boardHeight, linesCleared = this.linesCleared, level = this.level, y = boardHeight + 1; while (y--) { var x = boardWidth, lines = 0; while (x--) { if (board[x][y].data === 1) { lines++; } } if (lines === boardWidth) { linesCleared++; level = Math.round(linesCleared / 20) * 20; var lineY = y; while (lineY) { for (x = 0; x <= boardWidth; x++) { if (lineY - 1 > 0) { board[x][lineY].data = board[x][lineY - 1].data; board[x][lineY].colors = board[x][lineY - 1].colors; } } lineY--; } y++; } } }; // Lose animation Tetris.prototype.loseScreen = function () { var ctx = this.bgCtx, unitSize = this.unitSize, boardWidth = this.boardWidth, boardHeight = this.boardHeight, y = boardHeight - this.loseBlock; for (var x = 0; x < boardWidth; x++) { var bX = x * unitSize, bY = y * unitSize; ctx.fillStyle = "rgb(0,0,0)"; ctx.fillRect(bX, bY, unitSize, unitSize); ctx.fillStyle = "#54a13c"; ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4); ctx.fillStyle = "rgb(0,0,0)"; ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8); } if (this.loseBlock <= boardHeight + 1) { this.loseBlock++; var self = this; requestAnimationFrame(function () { self.loseScreen(); }); } else { this.init(); } }; // adds the piece as part of the board Tetris.prototype.fillBoard = function (curPiece) { var piece = curPiece.data, posX = curPiece.x, posY = curPiece.y, board = this.board; for (var x = 0; x < 4; x++) { for (var y = 0; y < 4; y++) { if (piece[x][y] === 1) { board[x + posX][y + posY].data = 1; board[x + posX][y + posY].colors = curPiece.colors; } } } this.checkLines(); this.renderBoard(); }; // rotate a piece Tetris.prototype.rotateTetrimono = function (curPiece) { var rotated = []; for (var x = 0; x < 4; x++) { rotated[x] = []; for (var y = 0; y < 4; y++) { rotated[x][y] = curPiece.data[3 - y][x]; } } if ( !this.checkMovement( { data: rotated, x: curPiece.x, y: curPiece.y, }, 0, 0 ) ) { rotated = curPiece.data; } return rotated; }; // assign the player a new peice Tetris.prototype.newTetromino = function () { var pieceNum = Math.floor(Math.random() * tetrominos.length), curPiece = this.curPiece; curPiece.data = tetrominos[pieceNum].data; curPiece.colors = tetrominos[pieceNum].colors; curPiece.x = Math.floor( Math.random() * (this.boardWidth - curPiece.data.length + 1) ); curPiece.y = -4; }; var width = window.innerWidth, boardDiv = 20 * Math.round(window.innerWidth / 20), boards = 8, bWidth = boardDiv / boards, tetrisInstances = []; for (var w = 0; w < boards; w++) { tetrisInstances.push( new Tetris(20 * Math.round((w * bWidth) / 20), 0, bWidth) ); } } function main() { if (!isActive()) { return; } createTetris(); } function isActive() { let style = getComputedStyle(document.body); return style.getPropertyValue("--is-haxxorman") == "true" } document.addEventListener("DOMContentLoaded", main);