Refactored file structure and removed unnecessary code
This commit is contained in:
@@ -85,6 +85,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="https://hammerjs.github.io/dist/hammer.js"></script>
|
<script src="https://hammerjs.github.io/dist/hammer.js"></script>
|
||||||
<script src="js/language.js"></script>
|
<script src="js/language.js"></script>
|
||||||
|
<script src="js/game-info.js"></script>
|
||||||
|
<script src="js/game.js"></script>
|
||||||
|
<script src="js/arena.js"></script>
|
||||||
|
<script src="js/player.js"></script>
|
||||||
<script src="js/tetris.js"></script>
|
<script src="js/tetris.js"></script>
|
||||||
<script src="js/menu.js"></script>
|
<script src="js/menu.js"></script>
|
||||||
<script src="js/touch-control.js"></script>
|
<script src="js/touch-control.js"></script>
|
||||||
|
35
js/arena.js
Normal file
35
js/arena.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
70
js/game-info.js
Normal file
70
js/game-info.js
Normal file
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
178
js/game.js
Normal file
178
js/game.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
86
js/player.js
Normal file
86
js/player.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
373
js/tetris.js
373
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 = [
|
const colors = [
|
||||||
null,
|
null,
|
||||||
'#FF0D72',
|
'#FF0D72',
|
||||||
|
53
style.css
53
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;
|
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
|
Radio Buttons
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user