|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Basic Tetris HTML Game</title> |
|
<meta charset="UTF-8"> |
|
<style> |
|
html, body { |
|
height: 100%; |
|
margin: 0; |
|
} |
|
|
|
body { |
|
background: black; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
canvas { |
|
border: 1px solid white; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<canvas width="320" height="640" id="game"></canvas> |
|
<script> |
|
// https://tetris.fandom.com/wiki/Tetris_Guideline |
|
|
|
// get a random integer between the range of [min,max] |
|
// @see https://stackoverflow.com/a/1527820/2124254 |
|
function getRandomInt(min, max) { |
|
min = Math.ceil(min); |
|
max = Math.floor(max); |
|
|
|
return Math.floor(Math.random() * (max - min + 1)) + min; |
|
} |
|
|
|
// generate a new tetromino sequence |
|
// @see https://tetris.fandom.com/wiki/Random_Generator |
|
function generateSequence() { |
|
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']; |
|
|
|
while (sequence.length) { |
|
const rand = getRandomInt(0, sequence.length - 1); |
|
const name = sequence.splice(rand, 1)[0]; |
|
tetrominoSequence.push(name); |
|
} |
|
} |
|
|
|
// get the next tetromino in the sequence |
|
function getNextTetromino() { |
|
if (tetrominoSequence.length === 0) { |
|
generateSequence(); |
|
} |
|
|
|
const name = tetrominoSequence.pop(); |
|
const matrix = tetrominos[name]; |
|
|
|
// I and O start centered, all others start in left-middle |
|
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2); |
|
|
|
// I starts on row 21 (-1), all others start on row 22 (-2) |
|
const row = name === 'I' ? -1 : -2; |
|
|
|
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 |
|
}; |
|
} |
|
|
|
// rotate an NxN matrix 90deg |
|
// @see https://codereview.stackexchange.com/a/186834 |
|
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] && ( |
|
// outside the game bounds |
|
cellCol + col < 0 || |
|
cellCol + col >= playfield[0].length || |
|
cellRow + row >= playfield.length || |
|
// collides with another piece |
|
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; |
|
} |
|
} |
|
} |
|
|
|
// check for line clears starting from the bottom and working our way up |
|
for (let row = playfield.length - 1; row >= 0; ) { |
|
if (playfield[row].every(cell => !!cell)) { |
|
|
|
// drop every row above this one |
|
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); |
|
} |
|
|
|
const canvas = document.getElementById('game'); |
|
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 |
|
// tetris playfield is 10x20, with a few rows offscreen |
|
const playfield = []; |
|
|
|
// populate the empty state |
|
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/SRS |
|
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': 'cyan', |
|
'O': 'yellow', |
|
'T': 'purple', |
|
'S': 'green', |
|
'Z': 'red', |
|
'J': 'blue', |
|
'L': 'orange' |
|
}; |
|
|
|
let count = 0; |
|
let tetromino = getNextTetromino(); |
|
let rAF = null; // keep track of the animation frame so we can cancel it |
|
let gameOver = false; |
|
|
|
// game loop |
|
function loop() { |
|
rAF = requestAnimationFrame(loop); |
|
context.clearRect(0,0,canvas.width,canvas.height); |
|
|
|
// draw the playfield |
|
for (let row = 0; row < 20; row++) { |
|
for (let col = 0; col < 10; col++) { |
|
if (playfield[row][col]) { |
|
const name = playfield[row][col]; |
|
context.fillStyle = colors[name]; |
|
|
|
// drawing 1 px smaller than the grid creates a grid effect |
|
context.fillRect(col * grid, row * grid, grid-1, grid-1); |
|
} |
|
} |
|
} |
|
|
|
// draw the active tetromino |
|
if (tetromino) { |
|
|
|
// tetromino falls every 35 frames |
|
if (++count > 35) { |
|
tetromino.row++; |
|
count = 0; |
|
|
|
// place piece if it runs into anything |
|
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) { |
|
tetromino.row--; |
|
placeTetromino(); |
|
} |
|
} |
|
|
|
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]) { |
|
|
|
// drawing 1 px smaller than the grid creates a grid effect |
|
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// listen to keyboard events to move the active tetromino |
|
document.addEventListener('keydown', function(e) { |
|
if (gameOver) return; |
|
|
|
// left and right arrow keys (move) |
|
if (e.which === 37 || e.which === 39) { |
|
const col = e.which === 37 |
|
? tetromino.col - 1 |
|
: tetromino.col + 1; |
|
|
|
if (isValidMove(tetromino.matrix, tetromino.row, col)) { |
|
tetromino.col = col; |
|
} |
|
} |
|
|
|
// up arrow key (rotate) |
|
if (e.which === 38) { |
|
const matrix = rotate(tetromino.matrix); |
|
if (isValidMove(matrix, tetromino.row, tetromino.col)) { |
|
tetromino.matrix = matrix; |
|
} |
|
} |
|
|
|
// down arrow key (drop) |
|
if(e.which === 40) { |
|
const row = tetromino.row + 1; |
|
|
|
if (!isValidMove(tetromino.matrix, row, tetromino.col)) { |
|
tetromino.row = row - 1; |
|
|
|
placeTetromino(); |
|
return; |
|
} |
|
|
|
tetromino.row = row; |
|
} |
|
}); |
|
|
|
// start the game |
|
rAF = requestAnimationFrame(loop); |
|
</script> |
|
</body> |
|
</html> |
I have an html code like the one below. I added the coustume html code section to the block on my wordpress site and pasted the github game code below into it, but it doesn't work on the game wordpress page. :(/ What do you think is the problem? How can I run this without breaking my wordpress page? The code below really works, but when I copy the code to Advance Custom HTML or custom html, the game doesn't work. How do you think I can solve this problem quickly and without any problems?