From a1184c63db4cb57d39f633ba145fd982bdf3d6e4 Mon Sep 17 00:00:00 2001 From: rgantzos <86856959+rgantzos@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:36:53 -0700 Subject: [PATCH] Very, very important feature --- extras/games/tetris/index.html | 13 ++ extras/games/tetris/styles.css | 38 +++++ extras/games/tetris/tetris.js | 291 +++++++++++++++++++++++++++++++++ extras/popup/popup.js | 11 ++ 4 files changed, 353 insertions(+) create mode 100644 extras/games/tetris/index.html create mode 100644 extras/games/tetris/styles.css create mode 100644 extras/games/tetris/tetris.js diff --git a/extras/games/tetris/index.html b/extras/games/tetris/index.html new file mode 100644 index 00000000..7902d6f9 --- /dev/null +++ b/extras/games/tetris/index.html @@ -0,0 +1,13 @@ + + + + + + Tetris Game + + + + + + + diff --git a/extras/games/tetris/styles.css b/extras/games/tetris/styles.css new file mode 100644 index 00000000..ed00833b --- /dev/null +++ b/extras/games/tetris/styles.css @@ -0,0 +1,38 @@ +body, +html { + height: 100%; + margin: 0; + display: flex; + justify-content: center; + align-items: center; + transition: background-color 0.3s; +} + +canvas { + border: 0px; + display: block; + background-color: white; + padding: 0.5rem; + border-radius: 0.5rem; +} + +body.colorful { + animation: gradientFlash 5s infinite; + background: linear-gradient(to right, #ffc700, #ff0000); +} + +@keyframes gradientFlash { + 0%, + 100% { + background-position: 0%; + } + 25% { + background-position: 100%; + } + 50% { + background-position: 100%; + } + 75% { + background-position: 0%; + } +} \ No newline at end of file diff --git a/extras/games/tetris/tetris.js b/extras/games/tetris/tetris.js new file mode 100644 index 00000000..27757050 --- /dev/null +++ b/extras/games/tetris/tetris.js @@ -0,0 +1,291 @@ +const canvas = document.getElementById('gameCanvas'); +const context = canvas.getContext('2d'); +const grid = 32; +const tetrominoSequence = []; + +// Keep track of what is in every cell of the game using a 2d array +const playfield = []; + +// Create the empty state for the playfield +for (let row = -2; row < 20; row++) { + playfield[row] = []; + + for (let col = 0; col < 10; col++) { + playfield[row][col] = 0; + } +} + +// how to draw each tetromino +// @see https://tetris.fandom.com/wiki/Tetris_Guideline +const tetrominos = { + 'I': [ + [0,0,0,0], + [1,1,1,1], + [0,0,0,0], + [0,0,0,0] + ], + 'J': [ + [1,0,0], + [1,1,1], + [0,0,0], + ], + 'L': [ + [0,0,1], + [1,1,1], + [0,0,0], + ], + 'O': [ + [1,1], + [1,1], + ], + 'S': [ + [0,1,1], + [1,1,0], + [0,0,0], + ], + 'Z': [ + [1,1,0], + [0,1,1], + [0,0,0], + ], + 'T': [ + [0,1,0], + [1,1,1], + [0,0,0], + ] +}; + +// color of each tetromino +const colors = { + 'I': '#36b5ff', + 'O': '#ffe436', + 'T': '#d036ff', + 'S': '#36ff6b', + 'Z': '#ff3636', + 'J': '#3646ff', + 'L': '#ff9a36' +}; + +// Keep track of the position of the current tetromino +let tetromino = getNextTetromino(); +let rAF = null; // keep track of the animation frame so we can cancel it +let gameOver = false; + +// Get the next tetromino in the sequence +function getNextTetromino() { + if (tetrominoSequence.length === 0) { + const tetrominos = ['I', 'J', 'L', 'O', 'S', 'Z', 'T']; + + while (tetrominos.length) { + const rand = getRandomInt(0, tetrominos.length - 1); + const name = tetrominos.splice(rand, 1)[0]; + tetrominoSequence.push(name); + } + } + + const name = tetrominoSequence.pop(); + const matrix = tetrominos[name]; + + const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2); + + const row = name === 'I' ? -1 : -2; + + document.body.style.backgroundColor = colors[name] + + return { + name: name, // name of the piece (L, O, etc.) + matrix: matrix, // the current rotation matrix + row: row, // current row (starts offscreen) + col: col // current col + }; +} + +// Generate a random number between min and max (inclusive) +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// Rotate the matrix 90 degrees clockwise +function rotate(matrix) { + const N = matrix.length - 1; + const result = matrix.map((row, i) => + row.map((val, j) => matrix[N - j][i]) + ); + + return result; +} + +// Check to see if the new matrix/row/col is valid +function isValidMove(matrix, cellRow, cellCol) { + for (let row = 0; row < matrix.length; row++) { + for (let col = 0; col < matrix[row].length; col++) { + if (matrix[row][col] && ( + cellCol + col < 0 || + cellCol + col >= playfield[0].length || + cellRow + row >= playfield.length || + playfield[cellRow + row][cellCol + col]) + ) { + return false; + } + } + } + + return true; +} + +// Place the tetromino on the playfield +function placeTetromino() { + for (let row = 0; row < tetromino.matrix.length; row++) { + for (let col = 0; col < tetromino.matrix[row].length; col++) { + if (tetromino.matrix[row][col]) { + + // game over if piece has any part offscreen + if (tetromino.row + row < 0) { + return showGameOver(); + } + + playfield[tetromino.row + row][tetromino.col + col] = tetromino.name; + } + } + } + + for (let row = playfield.length - 1; row >= 0; ) { + if (playfield[row].every(cell => !!cell)) { + + for (let r = row; r >= 0; r--) { + for (let c = 0; c < playfield[r].length; c++) { + playfield[r][c] = playfield[r-1][c]; + } + } + } + else { + row--; + } + } + + tetromino = getNextTetromino(); +} + +// Show the game over screen +function showGameOver() { + cancelAnimationFrame(rAF); + gameOver = true; + + context.fillStyle = 'black'; + context.globalAlpha = 0.75; + context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60); + + context.globalAlpha = 1; + context.fillStyle = 'white'; + context.font = '36px monospace'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2); +} + +// Draw the tetromino +function drawTetromino() { + context.fillStyle = colors[tetromino.name]; + + for (let row = 0; row < tetromino.matrix.length; row++) { + for (let col = 0; col < tetromino.matrix[row].length; col++) { + if (tetromino.matrix[row][col]) { + + context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid, grid); + } + } + } +} + +// Draw the playfield +function drawPlayfield() { + for (let row = 0; row < playfield.length; row++) { + for (let col = 0; col < playfield[row].length; col++) { + if (playfield[row][col]) { + const name = playfield[row][col]; + context.fillStyle = colors[name]; + + context.fillRect(col * grid, row * grid, grid, grid); + } + } + } +} + +// Listen to keyboard events to move the active tetromino +document.addEventListener('keydown', function(e) { + if (gameOver) return; + + // Left arrow key (move left) + if (e.which === 37 || e.keyCode === 37) { + const col = tetromino.col - 1; + if (isValidMove(tetromino.matrix, tetromino.row, col)) { + tetromino.col = col; + } + } + + // Right arrow key (move right) + if (e.which === 39 || e.keyCode === 39) { + const col = tetromino.col + 1; + if (isValidMove(tetromino.matrix, tetromino.row, col)) { + tetromino.col = col; + } + } + + // Up arrow key (rotate) + if (e.which === 38 || e.keyCode === 38) { + const matrix = rotate(tetromino.matrix); + if (isValidMove(matrix, tetromino.row, tetromino.col)) { + tetromino.matrix = matrix; + } + } + + // Down arrow key (soft drop) + if (e.which === 40 || e.keyCode === 40) { + const row = tetromino.row + 1; + if (!isValidMove(tetromino.matrix, row, tetromino.col)) { + tetromino.row = row - 1; + placeTetromino(); + return; + } + + tetromino.row = row; + } + + if (e.which === 32 || e.keyCode === 32) { + while (isValidMove(tetromino.matrix, tetromino.row + 1, tetromino.col)) { + tetromino.row++; + } + placeTetromino(); + } +}); + +// Game loop +function loop() { + rAF = requestAnimationFrame(loop); + context.clearRect(0,0,canvas.width,canvas.height); + + drawPlayfield(); + drawTetromino(); + + if (++count > 35) { + tetromino.row++; + count = 0; + + if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) { + tetromino.row--; + placeTetromino(); + } + } +} + +// Fit the canvas to the screen +canvas.width = 10 * grid; // 10 columns +canvas.height = 20 * grid; // 20 rows + +// Keep track of time +let count = 0; + +// Start the game loop +rAF = requestAnimationFrame(loop); diff --git a/extras/popup/popup.js b/extras/popup/popup.js index 9df30f8a..c32f55d2 100644 --- a/extras/popup/popup.js +++ b/extras/popup/popup.js @@ -1523,6 +1523,17 @@ function searchAndSort(query) { } let searchbar = document.querySelector(".searchbar"); + +searchbar.addEventListener("keypress", function (e) { + if (e.which === 13) { + if (searchbar.value === "node tetris.js") { + searchbar.value = ""; + searchbar.dispatchEvent(new Event("change", { bubbles: true })); + chrome.tabs.create({ url: "/extras/games/tetris/index.html" }); + } + } +}); + searchbar.addEventListener("input", function () { if (document.querySelector(".welcome")) { if (searchbar.value) {