From 6491056b8f9c2d66856c537a1dd35754754e375e Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 15 Dec 2017 19:58:42 +0100 Subject: [PATCH] First working, initial version of Tetris.js --- index.html | 20 +++++ style.css | 40 +++++++++ tetris.js | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 index.html create mode 100644 style.css create mode 100644 tetris.js diff --git a/index.html b/index.html new file mode 100644 index 0000000..b5433cc --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + Tetris + + + +
+ +
+ Controls: +
+ Arrow Keys -> Move left/right or down +
+ Q/W -> Rotate the tile +
+ + + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..b0d4e9c --- /dev/null +++ b/style.css @@ -0,0 +1,40 @@ +body { + margin: 0; + padding: 0; + overflow: hidden; + background: #202028; +} + +#tetris { + position: absolute; + left: 50%; + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + -ms-transform: translateX(-50%); + -o-transform: translateX(-50%); + transform: translateX(-50%); + margin: 20px 0; + border: solid .2em #fff; +} + +#score, #controls { + position: absolute; + font-family: Roboto, Helvetica, Arial, sans-serif; + font-weight: 900; + font-size: 40px; + top: 50%; + left: 25%; + transform: translate(-50%, -50%); + color: #fff; +} + +#score:before { + content: 'Score:'; +} + +#controls { + left: 75%; + font-size: 20px; + font-weight: 300; + transform: translate(0, -50%); +} \ No newline at end of file diff --git a/tetris.js b/tetris.js new file mode 100644 index 0000000..8f214cb --- /dev/null +++ b/tetris.js @@ -0,0 +1,258 @@ +const canvas = document.getElementById('tetris'); +const context = canvas.getContext('2d'); + +const fieldSize = {x: 12, y: 20}; + +function arenaSweep() { + let rowCount = 1; + outer: for(let y = arena.length - 1; y > 0; --y) { + for(let x = 0; x < arena[y].length; ++x) { + if(arena[y][x] === 0) { + continue outer; + } + } + + const row = arena.splice(y, 1)[0].fill(0); + arena.unshift(row); + ++y; + + player.score += rowCount * 10; + rowCount *= 2; + } + dropInterval -= player.score / 10; + dropInterval = dropInterval > 50 ? dropInterval : 50; +} + +function collide(arena, player) { + const [m, o] = [player.matrix, player.pos]; + for(let y = 0; y < m.length; ++y) { + for(let x = 0; x < m[y].length; ++x) { + if(m[y][x] !== 0 && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) { + return true; + } + } + } + return false; +} + +function createMatrix(w, h) { + const matrix = []; + while(h--) { + matrix.push(new Array(w).fill(0)); + } + return matrix; +} + +function createPiece(type) { + switch(type) { + case 'T': + return [ + [0, 0, 0], + [1, 1, 1], + [0, 1, 0] + ]; + case 'O': + return [ + [2, 2], + [2, 2] + ]; + case 'J': + return [ + [0, 3, 0], + [0, 3, 0], + [3, 3, 0] + ]; + case 'L': + return [ + [0, 4, 0], + [0, 4, 0], + [0, 4, 4] + ]; + case 'I': + return [ + [0, 5, 0, 0], + [0, 5, 0, 0], + [0, 5, 0, 0], + [0, 5, 0, 0] + ]; + case 'S': + return [ + [0, 6, 6], + [6, 6, 0], + [0, 0, 0] + ]; + case 'Z': + return [ + [7, 7, 0], + [0, 7, 7], + [0, 0, 0] + ]; + } +} + +function draw() { + context.fillStyle = '#000'; + context.fillRect(0, 0, canvas.width, canvas.height); + + drawMatrix(arena, {x: 0, y: 0}); + drawMatrix(player.matrix, player.pos); +} + +function drawMatrix(matrix, offset) { + matrix.forEach((row, y) => { + row.forEach((value, x) => { + if (value !== 0) { + context.fillStyle = colors[value]; + context.fillRect(x + offset.x, y + offset.y, 1, 1); + } + }); + }); +} + +function merge(arena, player) { + player.matrix.forEach((row, y) => { + row.forEach((value, x) => { + if(value !== 0) { + arena[y + player.pos.y][x + player.pos.x] = value; + } + }); + }); +} + +function playerDrop() { + player.pos.y++; + if(collide(arena, player)) { + player.pos.y--; + merge(arena, player); + playerReset(); + arenaSweep(); + updateScore(); + } + dropCounter = 0; +} + +function playerMove(dir) { + player.pos.x += dir; + if(collide(arena, player)) { + player.pos.x -= dir; + } +} + +function playerReset() { + const pieces = 'IJLOSTZ'; + player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]); + player.pos.y = 0; + player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0); + if(collide(arena, player)) { + arena.forEach(row => row.fill(0)); + player.score = 0; + dropInterval = 1000; + updateScore(); + } +} + +function playerRotate(dir) { + rotate(player.matrix, dir); + + const pos = player.pos.x; + let offset = 1; + while(collide(arena, player )) { + player.pos.x += offset; + offset = -(offset + (offset > 0 ? 1 : -1)); + if(offset > player.matrix[0].length) { + rotate(player.matrix, -dir); + player.pos.x = pos; + return; + } + } +} + +function rotate(matrix, dir) { + for(let y = 0; y < matrix.length; ++y) { + for(let x = 0; x < y; ++x) { + [ + matrix[x][y], + matrix[y][x] + ] = [ + matrix[y][x], + matrix[x][y] + ]; + } + } + + if(dir > 0) { + matrix.forEach(row => row.reverse()); + } else { + matrix.reverse(); + } +} + +let dropCounter = 0; +let dropInterval = 1000; + +let lastTime = 0; +function update(time = 0) { + const deltaTime = time - lastTime; + lastTime = time; + + dropCounter += deltaTime; + if(dropCounter > dropInterval) { + playerDrop(); + } + + draw(); + requestAnimationFrame(update); +} + +function updateScore() { + document.getElementById('score').innerText = player.score.toString(); +} + +const colors = [ + null, + '#FF0D72', + '#0DC2FF', + '#0DFF72', + '#F538FF', + '#FF8E0D', + '#FFE138', + '#3877FF', +]; + +const arena = createMatrix(fieldSize.x, fieldSize.y); + +const player = { + pos: {x: 0, y: 0}, + matrix: null, + score: 0 +}; + +// Keyboard controls +document.addEventListener('keydown', event => { + if(event.keyCode === 37) { + playerMove(-1); + } else if(event.keyCode === 39) { + playerMove(1); + } else if(event.keyCode === 40) { + playerDrop(); + } else if(event.keyCode === 81) { + playerRotate(-1); + } else if(event.keyCode === 87) { + playerRotate(1); + } +}); + +window.onresize = function (event) { + scaleWindow(); +}; + +function scaleWindow() { + canvas.height = window.innerHeight - 40; + canvas.width = canvas.height / (5 / 3); + context.scale(canvas.width / fieldSize.x, canvas.height / fieldSize.y); +} + +scaleWindow(); +playerReset(); +update(); +updateScore(); \ No newline at end of file