From 10444580300f1fc94373831baf723d621bacb0f2 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 18 Feb 2018 00:33:49 +0100 Subject: [PATCH] Refactored file structure and removed unnecessary code --- index.html | 4 + js/arena.js | 35 +++++ js/game-info.js | 70 +++++++++ js/game.js | 178 +++++++++++++++++++++++ js/player.js | 86 +++++++++++ js/tetris.js | 373 ------------------------------------------------ style.css | 53 ------- 7 files changed, 373 insertions(+), 426 deletions(-) create mode 100644 js/arena.js create mode 100644 js/game-info.js create mode 100644 js/game.js create mode 100644 js/player.js diff --git a/index.html b/index.html index fbf79f8..fbc4c88 100644 --- a/index.html +++ b/index.html @@ -85,6 +85,10 @@ + + + + diff --git a/js/arena.js b/js/arena.js new file mode 100644 index 0000000..d95615d --- /dev/null +++ b/js/arena.js @@ -0,0 +1,35 @@ +class Arena { + constructor(gameInfo, game) { + this.g = gameInfo; + this.game = game; + this.field = createMatrix(this.g.fieldSize.x, this.g.fieldSize.y); + } + + clearScreen() { + this.g.context.clearRect(0, 0, this.g.canvas.width, this.g.canvas.height); + } + + sweep() { + let rowCount = 1; + outer: for (let y = this.field.length - 1; y > 0; --y) { + for (let x = 0; x < this.field[y].length; ++x) { + if (this.field[y][x] === 0) { + continue outer; + } + } + + const row = this.field.splice(y, 1)[0].fill(0); + this.field.unshift(row); + ++y; + + this.p.score += rowCount * 10; + rowCount *= 2; + } + if (this.p.score - this.g.prevUpdateScore > 50) { + this.g.dropInterval -= 20; + this.g.dropInterval = this.g.dropInterval > 100 ? this.g.dropInterval : 100; + this.g.prevUpdateScore = this.p.score; + } + this.game.drawArena(); + } +} \ No newline at end of file diff --git a/js/game-info.js b/js/game-info.js new file mode 100644 index 0000000..d6de3db --- /dev/null +++ b/js/game-info.js @@ -0,0 +1,70 @@ +class GameInfo { + constructor(game) { + this.fieldSize = {x: 12, y: 20}; + this.arena = new Arena(this, game); + + this.player = new Player(this, game); + + this.canvas = document.getElementById('tetris'); + this.context = this.canvas.getContext('2d'); + + this.canvasBg = document.getElementById('tetris-background'); + this.contextBg = this.canvasBg.getContext('2d'); + + this.canvasHold = document.getElementById('tetris-hold'); + this.contextHold = this.canvasHold.getContext('2d'); + + this.canvasUpcoming = document.getElementById('tetris-upcoming'); + this.contextUpcoming = this.canvasUpcoming.getContext('2d'); + + this.isPaused = true; + + this.dropCounter = 0; + this.dropInterval = 1000; + + this.keys = { + down: { + keys: [40, 83], + action: () => this.player.drop() + }, + left: { + keys: [37, 65], + action: () => this.player.move(-1) + }, + right: { + keys: [39, 68], + action: () => this.player.move(1) + }, + rotateLeft: { + keys: [81], + action: () => this.player.rotate(-1) + }, + rotateRight: { + keys: [69], + action: () => this.player.rotate(1) + }, + holdTile: { + keys: [38, 87], + action: () => this.player.hold() + } + }; + + this.prevUpdateScore = 0; + + this.lastScore = 0; + this.lastTime = 0; + this.lastTimeUpdate = Date.now(); + + this.startTime = 0; + + this.upcomingTiles = []; + + /* + default -> plain squares + retro -> original look + modern -> rounded corners + snake -> all tiles are connected + */ + this.theme = 'default'; + } +} \ No newline at end of file diff --git a/js/game.js b/js/game.js new file mode 100644 index 0000000..0227e35 --- /dev/null +++ b/js/game.js @@ -0,0 +1,178 @@ +class Game { + constructor() { + this.g = new GameInfo(this); + this.p = this.g.player; + + this.registerListeners(); + } + + draw() { + this.g.context.clearRect(0, 0, this.g.canvas.width, this.g.canvas.height); + this.drawMatrix(this.p.matrix, this.p.pos); + } + + drawArena() { + this.g.contextBg.fillStyle = '#000'; + this.g.contextBg.fillRect(0, 0, this.g.canvas.width, this.g.canvas.height); + this.drawMatrix(this.g.arena.field, {x: 0, y: 0}, this.g.contextBg); + } + + drawHolding() { + this.g.contextHold.clearRect(0, 0, this.g.canvasHold.width, this.g.canvasHold.height); + const offset = centerOffset(this.p.holdingTile); + const x = 3 - (this.p.holdingTile[0].length / 2) + offset.x; + const y = 3 - (this.p.holdingTile.length / 2) + offset.y; + this.drawMatrix(this.p.holdingTile, {x: x, y: y}, this.g.contextHold); + } + + drawMatrix(matrix, offset, useContext = this.g.context) { + matrix.forEach((row, y) => { + row.forEach((value, x) => { + if (value !== 0) { + this.drawTile(x, y, offset, colors[value], matrix, useContext); + } + }); + }); + } + + drawTile(x, y, offset, color, matrix, useContext = this.g.context) { + const ctx = useContext; + switch (this.g.theme) { + case "default": + ctx.fillStyle = color; + ctx.fillRect(x + offset.x + tileGap / 2, y + offset.y + tileGap / 2, 1 - tileGap, 1 - tileGap); + break; + case "modern": + ctx.fillStyle = color; + drawRoundRect(ctx, x + offset.x + tileGap / 2, y + offset.y + tileGap / 2, 1 - tileGap, 1 - tileGap, .15); + break; + case "snakes": + ctx.fillStyle = color; + let r1 = .15, // top right + r2 = .15, // bottom right + r3 = .15, // bottom left + r4 = .15; // top left + // Is there a tile to the left? + if (matrix[y][x - 1] > 0) { + r3 = 0; + r4 = 0; + } + // Is there a tile to the right? + if (matrix[y][x + 1] > 0) { + r1 = 0; + r2 = 0; + } + // Is there a tile to the top? + if (matrix[y - 1] !== undefined && matrix[y - 1][x] > 0) { + r1 = 0; + r4 = 0; + } + // Is there a tile to the bottom? + if (matrix[y + 1] !== undefined && matrix[y + 1][x] > 0) { + r2 = 0; + r3 = 0; + } + drawRoundRect(ctx, x + offset.x, y + offset.y, 1, 1, [r1, r2, r3, r4]); + break; + default: + this.g.theme = "default"; + this.drawTile(x, y, offset, color, matrix, ctx); + break; + } + } + + drawUpcoming() { + this.g.contextUpcoming.clearRect(0, 0, this.g.canvasUpcoming.width, this.g.canvasUpcoming.height); + let offsetY = 0; + let offset; + this.g.upcomingTiles.forEach((tile) => { + offset = centerOffset(tile); + const x = 3 - (tile[0].length / 2) + offset.x; + const y = offsetY + 3 - (tile.length / 2) + offset.y; + this.drawMatrix(tile, {x: x, y: y}, this.g.contextUpcoming); + offsetY += 6; + }); + } + + gameOver() { + this.g.arena.field.forEach(row => row.fill(0)); + timePassed = 0; + this.g.lastTimeUpdate = Date.now(); + this.updateTime(); + this.p.score = 0; + this.g.dropInterval = 1000; + this.updateScore(); + } + + merge(arena, player) { + player.matrix.forEach((row, y) => { + row.forEach((value, x) => { + if (value !== 0) { + arena.field[y + player.pos.y][x + player.pos.x] = value; + } + }); + }); + this.drawArena(); + } + + registerListeners() { + // Keyboard controls + document.addEventListener('keydown', event => { + Object.keys(this.g.keys).map((objKey) => { + const keyBind = this.g.keys[objKey]; + if (keyBind.keys.includes(event.keyCode)) { + keyBind.action(); + } + }); + }); + } + + saveHighscore() { + if (getCookie("highscore").value < this.p.score) { + document.cookie = "highscore=" + this.p.score + "; max-age=" + 60 * 60 * 24 * 365 * 1000 + "; path=/;"; + } + } + + start() { + this.drawArena(); + this.p.addTile(); + this.p.addTile(); + this.p.addTile(); + this.p.reset(); + this.g.startTime = Date.now(); + this.update(); + this.updateScore(); + } + + update(time = 0) { + if (!this.g.isPaused) { + const deltaTime = time - this.g.lastTime; + this.g.lastTime = time; + + this.g.dropCounter += deltaTime; + if (this.g.dropCounter > this.g.dropInterval) { + this.p.drop(); + } + + this.updateTime(); + + this.draw(); + requestAnimationFrame(this.update.bind(this)); + } + } + + updateScore() { + if (this.g.lastScore !== this.p.score) { + scoreUpdateAni(); + this.g.lastScore = this.p.score; + this.saveHighscore(); + } + document.getElementById('score').innerText = this.p.score.toString(); + } + + updateTime() { + timePassed += Date.now() - this.g.lastTimeUpdate; + timeElement.innerHTML = formatMillis(timePassed); + this.g.lastTimeUpdate = Date.now(); + } +} \ No newline at end of file diff --git a/js/player.js b/js/player.js new file mode 100644 index 0000000..56447da --- /dev/null +++ b/js/player.js @@ -0,0 +1,86 @@ +class Player { + constructor(gameInfo, game) { + this.g = gameInfo; + this.game = game; + this.a = this.g.arena; + this.pos = {x: 0, y: 0}; + this.matrix = null; + this.score = 0; + + this.isHolding = false; + this.holdingTile = null; + + this.a.p = this; + } + + addTile() { + this.g.upcomingTiles.push(createPiece(pieces[pieces.length * Math.random() | 0])); + } + + drop() { + this.pos.y++; + if (collide(this.a.field, this)) { + this.pos.y--; + this.game.merge(this.a, this); + this.reset(); + this.a.sweep(); + this.game.updateScore(); + } + this.g.dropCounter = 0; + } + + hold() { + if (this.isHolding) + return; + if (this.holdingTile === null) { + this.holdingTile = this.matrix; + this.reset(true); + } else { + this.holdingTile = [this.matrix, this.matrix = this.holdingTile][0]; + this.reset(true, false); + } + this.game.drawHolding(); + } + + move(dir) { + this.pos.x += dir; + if (collide(this.a.field, this)) { + this.pos.x -= dir; + } + this.g.dropCounter *= .75; + } + + reset(resetHold = false, newTile = true) { + this.isHolding = resetHold; + if (newTile) { + this.matrix = this.g.upcomingTiles[0]; + this.g.upcomingTiles.splice(0, 1); + this.addTile(); + } + + this.pos.y = 0; + this.pos.x = (this.a.field[0].length / 2 | 0) - (this.matrix[0].length / 2 | 0); + + this.game.drawUpcoming(); + + if (collide(this.a.field, this)) { + this.game.gameOver(); + } + } + + rotate(dir) { + rotate(this.matrix, dir); + + const pos = this.pos.x; + let offset = 1; + while (collide(this.a.field, this)) { + this.pos.x += offset; + offset = -(offset + (offset > 0 ? 1 : -1)); + if (offset > this.matrix[0].length) { + rotate(this.matrix, -dir); + this.pos.x = pos; + return; + } + } + } +} \ No newline at end of file diff --git a/js/tetris.js b/js/tetris.js index 12107a3..79af12b 100644 --- a/js/tetris.js +++ b/js/tetris.js @@ -1,376 +1,3 @@ -class GameInfo { - constructor(game) { - this.fieldSize = {x: 12, y: 20}; - this.arena = new Arena(this, game); - - this.player = new Player(this, game); - - this.canvas = document.getElementById('tetris'); - this.context = this.canvas.getContext('2d'); - - this.canvasBg = document.getElementById('tetris-background'); - this.contextBg = this.canvasBg.getContext('2d'); - - this.canvasHold = document.getElementById('tetris-hold'); - this.contextHold = this.canvasHold.getContext('2d'); - - this.canvasUpcoming = document.getElementById('tetris-upcoming'); - this.contextUpcoming = this.canvasUpcoming.getContext('2d'); - - this.isPaused = true; - - this.dropCounter = 0; - this.dropInterval = 1000; - - this.keys = { - down: { - keys: [40, 83], - action: () => this.player.drop() - }, - left: { - keys: [37, 65], - action: () => this.player.move(-1) - }, - right: { - keys: [39, 68], - action: () => this.player.move(1) - }, - rotateLeft: { - keys: [81], - action: () => this.player.rotate(-1) - }, - rotateRight: { - keys: [69], - action: () => this.player.rotate(1) - }, - holdTile: { - keys: [38, 87], - action: () => this.player.hold() - } - }; - - this.prevUpdateScore = 0; - - this.lastScore = 0; - this.lastTime = 0; - this.lastTimeUpdate = Date.now(); - - this.startTime = 0; - - this.upcomingTiles = []; - - /* - default -> plain squares - retro -> original look - modern -> rounded corners - snake -> all tiles are connected - */ - this.theme = 'default'; - } -} - -class Game { - constructor() { - this.g = new GameInfo(this); - this.p = this.g.player; - - this.registerListeners(); - } - - draw() { - this.g.context.clearRect(0, 0, this.g.canvas.width, this.g.canvas.height); - this.drawMatrix(this.p.matrix, this.p.pos); - } - - drawArena() { - this.g.contextBg.fillStyle = '#000'; - this.g.contextBg.fillRect(0, 0, this.g.canvas.width, this.g.canvas.height); - this.drawMatrix(this.g.arena.field, {x: 0, y: 0}, this.g.contextBg); - } - - drawHolding() { - this.g.contextHold.clearRect(0, 0, this.g.canvasHold.width, this.g.canvasHold.height); - const offset = centerOffset(this.p.holdingTile); - const x = 3 - (this.p.holdingTile[0].length / 2) + offset.x; - const y = 3 - (this.p.holdingTile.length / 2) + offset.y; - this.drawMatrix(this.p.holdingTile, {x: x, y: y}, this.g.contextHold); - } - - drawMatrix(matrix, offset, useContext = this.g.context) { - matrix.forEach((row, y) => { - row.forEach((value, x) => { - if (value !== 0) { - this.drawTile(x, y, offset, colors[value], matrix, useContext); - } - }); - }); - } - - drawTile(x, y, offset, color, matrix, useContext = this.g.context) { - const ctx = useContext; - switch (this.g.theme) { - case "default": - ctx.fillStyle = color; - ctx.fillRect(x + offset.x + tileGap / 2, y + offset.y + tileGap / 2, 1 - tileGap, 1 - tileGap); - break; - case "modern": - ctx.fillStyle = color; - drawRoundRect(ctx, x + offset.x + tileGap / 2, y + offset.y + tileGap / 2, 1 - tileGap, 1 - tileGap, .15); - break; - case "snakes": - ctx.fillStyle = color; - let r1 = .15, // top right - r2 = .15, // bottom right - r3 = .15, // bottom left - r4 = .15; // top left - // Is there a tile to the left? - if (matrix[y][x - 1] > 0) { - r3 = 0; - r4 = 0; - } - // Is there a tile to the right? - if (matrix[y][x + 1] > 0) { - r1 = 0; - r2 = 0; - } - // Is there a tile to the top? - if (matrix[y - 1] !== undefined && matrix[y - 1][x] > 0) { - r1 = 0; - r4 = 0; - } - // Is there a tile to the bottom? - if (matrix[y + 1] !== undefined && matrix[y + 1][x] > 0) { - r2 = 0; - r3 = 0; - } - drawRoundRect(ctx, x + offset.x, y + offset.y, 1, 1, [r1, r2, r3, r4]); - break; - default: - this.g.theme = "default"; - this.drawTile(x, y, offset, color, matrix, ctx); - break; - } - } - - drawUpcoming() { - this.g.contextUpcoming.clearRect(0, 0, this.g.canvasUpcoming.width, this.g.canvasUpcoming.height); - let offsetY = 0; - let offset; - this.g.upcomingTiles.forEach((tile) => { - offset = centerOffset(tile); - const x = 3 - (tile[0].length / 2) + offset.x; - const y = offsetY + 3 - (tile.length / 2) + offset.y; - this.drawMatrix(tile, {x: x, y: y}, this.g.contextUpcoming); - offsetY += 6; - }); - } - - gameOver() { - this.g.arena.field.forEach(row => row.fill(0)); - timePassed = 0; - this.g.lastTimeUpdate = Date.now(); - this.updateTime(); - this.p.score = 0; - this.g.dropInterval = 1000; - this.updateScore(); - } - - merge(arena, player) { - player.matrix.forEach((row, y) => { - row.forEach((value, x) => { - if (value !== 0) { - arena.field[y + player.pos.y][x + player.pos.x] = value; - } - }); - }); - this.drawArena(); - } - - registerListeners() { - // Keyboard controls - document.addEventListener('keydown', event => { - Object.keys(this.g.keys).map((objKey) => { - const keyBind = this.g.keys[objKey]; - if (keyBind.keys.includes(event.keyCode)) { - keyBind.action(); - } - }); - }); - } - - saveHighscore() { - if (getCookie("highscore").value < this.p.score) { - document.cookie = "highscore=" + this.p.score + "; max-age=" + 60 * 60 * 24 * 365 * 1000 + "; path=/;"; - } - } - - start() { - this.drawArena(); - this.p.addTile(); - this.p.addTile(); - this.p.addTile(); - this.p.reset(); - this.g.startTime = Date.now(); - this.update(); - this.updateScore(); - } - - update(time = 0) { - if (!this.g.isPaused) { - const deltaTime = time - this.g.lastTime; - this.g.lastTime = time; - - this.g.dropCounter += deltaTime; - if (this.g.dropCounter > this.g.dropInterval) { - this.p.drop(); - } - - this.updateTime(); - - this.draw(); - requestAnimationFrame(this.update.bind(this)); - } - } - - updateScore() { - if (this.g.lastScore !== this.p.score) { - scoreUpdateAni(); - this.g.lastScore = this.p.score; - this.saveHighscore(); - } - document.getElementById('score').innerText = this.p.score.toString(); - } - - updateTime() { - timePassed += Date.now() - this.g.lastTimeUpdate; - timeElement.innerHTML = formatMillis(timePassed); - this.g.lastTimeUpdate = Date.now(); - } -} - -class Arena { - constructor(gameInfo, game) { - this.g = gameInfo; - this.game = game; - this.field = createMatrix(this.g.fieldSize.x, this.g.fieldSize.y); - } - - clearScreen() { - this.g.context.clearRect(0, 0, this.g.canvas.width, this.g.canvas.height); - } - - sweep() { - let rowCount = 1; - outer: for (let y = this.field.length - 1; y > 0; --y) { - for (let x = 0; x < this.field[y].length; ++x) { - if (this.field[y][x] === 0) { - continue outer; - } - } - - const row = this.field.splice(y, 1)[0].fill(0); - this.field.unshift(row); - ++y; - - this.p.score += rowCount * 10; - rowCount *= 2; - } - if (this.p.score - this.g.prevUpdateScore > 50) { - this.g.dropInterval -= 20; - this.g.dropInterval = this.g.dropInterval > 100 ? this.g.dropInterval : 100; - this.g.prevUpdateScore = this.p.score; - } - this.game.drawArena(); - } -} - -class Player { - constructor(gameInfo, game) { - this.g = gameInfo; - this.game = game; - this.a = this.g.arena; - this.pos = {x: 0, y: 0}; - this.matrix = null; - this.score = 0; - - this.isHolding = false; - this.holdingTile = null; - - this.a.p = this; - } - - addTile() { - this.g.upcomingTiles.push(createPiece(pieces[pieces.length * Math.random() | 0])); - } - - drop() { - this.pos.y++; - if (collide(this.a.field, this)) { - this.pos.y--; - this.game.merge(this.a, this); - this.reset(); - this.a.sweep(); - this.game.updateScore(); - } - this.g.dropCounter = 0; - } - - hold() { - if (this.isHolding) - return; - if (this.holdingTile === null) { - this.holdingTile = this.matrix; - this.reset(true); - } else { - this.holdingTile = [this.matrix, this.matrix = this.holdingTile][0]; - this.reset(true, false); - } - this.game.drawHolding(); - } - - move(dir) { - this.pos.x += dir; - if (collide(this.a.field, this)) { - this.pos.x -= dir; - } - this.g.dropCounter *= .75; - } - - reset(resetHold = false, newTile = true) { - this.isHolding = resetHold; - if (newTile) { - this.matrix = this.g.upcomingTiles[0]; - this.g.upcomingTiles.splice(0, 1); - this.addTile(); - } - - this.pos.y = 0; - this.pos.x = (this.a.field[0].length / 2 | 0) - (this.matrix[0].length / 2 | 0); - - this.game.drawUpcoming(); - - if (collide(this.a.field, this)) { - this.game.gameOver(); - } - } - - rotate(dir) { - rotate(this.matrix, dir); - - const pos = this.pos.x; - let offset = 1; - while (collide(this.a.field, this)) { - this.pos.x += offset; - offset = -(offset + (offset > 0 ? 1 : -1)); - if (offset > this.matrix[0].length) { - rotate(this.matrix, -dir); - this.pos.x = pos; - return; - } - } - } -} - const colors = [ null, '#FF0D72', diff --git a/style.css b/style.css index 0f49110..33adaea 100644 --- a/style.css +++ b/style.css @@ -323,59 +323,6 @@ body { transition: 0.2s background linear 0.1s, 0.2s top linear, 0.2s transform linear 0.2s, 0.2s -webkit-transform linear 0.2s; } -/*@media (max-width: 1400px) {*/ -/*#tetris, #tetris-background {*/ -/*height: 75%;*/ -/*}*/ - -/*#game-stats {*/ -/*top: 10px;*/ -/*left: 50%;*/ -/*transform: translateX(-50%);*/ -/*}*/ -/**/ -/*#game-stats > div {*/ -/*display: inline;*/ -/*}*/ -/**/ -/*#time {*/ -/*margin-right: 50px;*/ -/*}*/ -/**/ -/*#controls {*/ -/*top: initial;*/ -/*bottom: 10px;*/ -/*left: 50%;*/ -/*transform: translateX(-50%);*/ -/*height: 10%;*/ -/*overflow: auto;*/ -/*}*/ -/**/ -/*#control-text {*/ -/*height: 100%;*/ -/*}*/ -/**/ -/*#language-selector {*/ -/*font-size: 20px;*/ -/*}*/ -/*}*/ - -@media (max-width: 840px) { - /*#game-stats {*/ - /*font-size: 20px;*/ - /*text-align: center;*/ - /*}*/ - /**/ - /*#game-stats > div {*/ - /*margin-right: 0;*/ - /*display: block;*/ - /*}*/ - /**/ - /*#control-text {*/ - /*font-size: 14px;*/ - /*}*/ -} - /* Radio Buttons */