Create class "Game"

This class can now have multiple instances (currently not at the same time)
This commit is contained in:
Marcel
2018-05-11 12:27:39 +02:00
parent 8373409e13
commit 5b0265c1ab
3 changed files with 535 additions and 512 deletions

523
game.js Normal file
View File

@@ -0,0 +1,523 @@
class Game {
// tileSize;
// renderingConfig;
// timer;
constructor() {
this.canvas = document.getElementById('minesweeper-game');
this.ctx = this.canvas.getContext('2d');
this.statsContainer = document.getElementById('game-stats');
this.timeEl = document.getElementById('time');
this.bombsEl = document.getElementById('bombs');
this.fieldSize = {x: 16, y: 12};
this.bombCount = 25;
this.field = [];
this.gameOver = false;
this.scaleFactor = .5;
this.isFirstClick = true;
this.windowX = 0;
this.windowY = 0;
this.zoomFactor = 1;
this.startTime = 0;
this.startClientX = 0;
this.startClientY = 0;
this.startWindowX = 0;
this.startWindowY = 0;
this.hasClicked = false;
this.isDragging = false;
this.ctx.scale(this.canvas.width / this.fieldSize.x * this.scaleFactor, this.canvas.height / this.fieldSize.y * this.scaleFactor);
this.initGame();
}
applyScaling() {
this.renderingConfig = this.calcScaling();
this.drawGrid(false);
}
calcScaling() {
const width = Math.ceil(this.fieldSize.x * this.zoomFactor) + 1;
const height = Math.ceil(this.fieldSize.y * this.zoomFactor) + 1;
const offsetX = Math.floor(this.windowX * this.fieldSize.x);
const offsetY = Math.floor(this.windowY * this.fieldSize.y);
const tiltX = this.windowX * this.fieldSize.x - offsetX;
const tiltY = this.windowY * this.fieldSize.y - offsetY;
const sizeX = this.tileSize.x / this.zoomFactor;
const sizeY = this.tileSize.y / this.zoomFactor;
return {
width, height, offsetX, offsetY, tiltX, tiltY, sizeX, sizeY
};
}
countBombs(x, y) {
const tiles = this.getSurroundingTiles(x, y);
return tiles.count(true);
}
countFlaggedBombs(x, y) {
const tiles = this.getSurroundingTiles(x, y);
return tiles.countFlagged(true);
}
countClickedTiles() {
let count = 0;
for (let x = 0; x < this.fieldSize.x; x++) {
for (let y = 0; y < this.fieldSize.y; y++) {
if (this.field[x][y].clicked && !this.field[x][y].flagged)
count++;
}
}
return count;
}
countTotalFlags() {
let count = 0;
for (let x = 0; x < this.fieldSize.x; x++) {
for (let y = 0; y < this.fieldSize.y; y++) {
if (this.field[x][y].flagged)
count++;
}
}
return count;
}
drawGrid(animations = true) {
this.ctx.clearRect(0, 0, this.width, this.height);
for (let x = 0; x < this.renderingConfig.width; x++) {
for (let y = 0; y < this.renderingConfig.height; y++) {
this.drawTile(x, y, animations);
}
}
}
drawTile(x, y, animations = true) {
const virtualX = this.renderingConfig.offsetX + x;
const virtualY = this.renderingConfig.offsetY + y;
if (virtualX >= this.fieldSize.x || virtualY >= this.fieldSize.y)
return;
const content = this.field[virtualX][virtualY];
const width = .8 * this.renderingConfig.sizeX;
const height = .8 * this.renderingConfig.sizeY;
const drawX = (x + .1 - this.renderingConfig.tiltX) * this.renderingConfig.sizeX;
const drawY = (y + .1 - this.renderingConfig.tiltY) * this.renderingConfig.sizeY;
const radius = this.renderingConfig.sizeX * .1;
let color = this.getColor(virtualX, virtualY);
const fontSize = this.renderingConfig.sizeY * .5;
let fontFamily = "Roboto";
let textColor = "white";
let text = "";
this.ctx.textAlign = "center";
let duration = 0;
if (animations)
duration = 150;
if (!content.flagged && content.clicked) {
color = "#ddd";
if (content.tileValue === true) {
text = "";
fontFamily = "FontAwesome";
textColor = "#aa2211";
color = "#333";
} else if (content.tileValue !== 0) {
text = content.tileValue;
textColor = colors[content.tileValue];
}
} else if (content.flagged) {
color = "#ff0000";
fontFamily = "FontAwesome";
text = "";
}
animateTile(this.ctx, drawX, drawY, 0, 0, width, height, 0, radius, new Date().getTime(), duration, color);
if (text !== "") {
animateText(this.ctx, this.renderingConfig, text, x, y, 0, fontSize, new Date().getTime(), duration, textColor, fontFamily);
}
}
gameOverEvent() {
play = false;
animateBackground(0, 0, this.width, this.height, 0, .75, new Date().getTime(), 200, {
r: 0,
g: 0,
b: 0,
a: 0
});
animateText(overlay2Ctx, this.renderingConfig, "Game Over", this.fieldSize.x / 2 - .5, this.fieldSize.y / 2 - .5, 0, this.tileSize.y * 1.33, new Date().getTime(), 200, "orange", "Roboto");
}
getColor(x, y) {
x++;
y++;
const pos = x * y;
const limit = this.fieldSize.x * this.fieldSize.y;
let percentage = pos / limit * 360;
return `hsl(${percentage},100%,50%)`;
}
getPosition(e) {
const x = (e.x - (window.innerWidth - this.width) / 2) / (this.width * this.zoomFactor) + this.windowX;
const y = (e.y - (window.innerHeight - this.height) * .75) / (this.height * this.zoomFactor) + this.windowY;
const fieldX = Math.floor(x * this.fieldSize.x);
const fieldY = Math.floor(y * this.fieldSize.y);
return {x: fieldX, y: fieldY};
}
getSurroundingTiles(x, y) {
const tiles = {};
if (x > 0) {
tiles["left"] = {tileValue: this.field[x - 1][y], x: x - 1, y: y};
if (y > 0) {
tiles["left-top"] = {tileValue: this.field[x - 1][y - 1], x: x - 1, y: y - 1};
}
if (y < this.fieldSize.y - 1) {
tiles["left-bottom"] = {tileValue: this.field[x - 1][y + 1], x: x - 1, y: y + 1};
}
}
if (x < this.fieldSize.x - 1) {
tiles["right"] = {tileValue: this.field[x + 1][y], x: x + 1, y: y};
if (y > 0)
tiles["right-top"] = {tileValue: this.field[x + 1][y - 1], x: x + 1, y: y - 1};
if (y < this.fieldSize.y - 1)
tiles["right-bottom"] = {tileValue: this.field[x + 1][y + 1], x: x + 1, y: y + 1};
}
if (y > 0)
tiles["top"] = {tileValue: this.field[x][y - 1], x: x, y: y - 1};
if (y < this.fieldSize.y - 1)
tiles["bottom"] = {tileValue: this.field[x][y + 1], x: x, y: y + 1};
return tiles;
}
initBombs(startX, startY) {
for (let i = 0; i < this.bombCount; i++) {
const ranX = Math.floor(Math.random() * this.fieldSize.x);
const ranY = Math.floor(Math.random() * this.fieldSize.y);
if (ranX === startX || ranX === startX - 1 || ranX === startX + 1 || ranY === startY || ranY === startY - 1 || ranY === startY + 1 || this.field[ranX][ranY].tileValue === true) {
i--;
continue;
}
this.field[ranX][ranY].tileValue = true;
}
for (let x = 0; x < this.fieldSize.x; x++) {
for (let y = 0; y < this.fieldSize.y; y++) {
if (this.field[x][y].tileValue !== true) {
this.field[x][y].tileValue = this.countBombs(x, y);
}
}
}
}
initEventListeners() {
overlay2Canvas.addEventListener("click", (e) => {
if (this.isDragging)
return;
const pos = this.getPosition(e);
if (this.isFirstClick) {
this.initBombs(pos.x, pos.y);
this.initTime();
this.isFirstClick = false;
}
this.tileClickEvent(pos.x, pos.y);
this.victoryEvent();
clicked(e);
});
overlay2Canvas.addEventListener("dblclick", (e) => {
if (this.isDragging)
return;
const pos = this.getPosition(e);
this.tileDoubleClick(pos.x, pos.y);
this.victoryEvent();
});
overlay2Canvas.addEventListener("contextmenu", (e) => {
if (this.isDragging)
return;
e.preventDefault();
const pos = this.getPosition(e);
this.tileFlag(pos.x, pos.y);
this.updateBombs();
this.victoryEvent();
});
window.addEventListener("keyup", (e) => {
e.preventDefault();
const changeRate = .05;
let newZoomFactor = this.zoomFactor;
let newWindowX = this.windowX;
let newWindowY = this.windowY;
if (e.code === "BracketRight") {
newZoomFactor -= changeRate;
} else if (e.code === "Slash") {
newZoomFactor += changeRate;
} else if (e.code === "ArrowLeft") {
newWindowX -= changeRate;
} else if (e.code === "ArrowRight") {
newWindowX += changeRate;
} else if (e.code === "ArrowUp") {
newWindowY -= changeRate;
} else if (e.code === "ArrowDown") {
newWindowY += changeRate;
} else {
return;
}
newZoomFactor = Math.min(newZoomFactor, 1);
newZoomFactor = Math.max(newZoomFactor, .1);
newWindowX = Math.min(newWindowX, 1 - newZoomFactor);
newWindowY = Math.min(newWindowY, 1 - newZoomFactor);
newWindowX = Math.max(newWindowX, 0);
newWindowY = Math.max(newWindowY, 0);
if (newZoomFactor !== this.zoomFactor || newWindowX !== this.windowX || newWindowY !== this.windowY) {
this.zoomFactor = newZoomFactor;
this.windowX = newWindowX;
this.windowY = newWindowY;
this.applyScaling();
}
});
document.addEventListener("mousedown", (e) => {
if (e.button === 0) {
this.hasClicked = true;
this.startClientX = e.clientX;
this.startClientY = e.clientY;
this.startWindowX = this.windowX;
this.startWindowY = this.windowY;
}
});
document.addEventListener("mouseup", () => {
this.hasClicked = false;
if (this.isDragging) {
setTimeout(() => {
this.isDragging = false;
}, 10);
}
});
document.addEventListener("mousemove", (e) => {
if (this.hasClicked) {
this.isDragging = true;
const deltaX = e.clientX - this.startClientX;
const deltaY = e.clientY - this.startClientY;
this.windowX = this.startWindowX - deltaX / this.width;
this.windowY = this.startWindowY - deltaY / this.height;
this.windowX = Math.min(this.windowX, 1 - this.zoomFactor);
this.windowY = Math.min(this.windowY, 1 - this.zoomFactor);
this.windowX = Math.max(this.windowX, 0);
this.windowY = Math.max(this.windowY, 0);
this.applyScaling();
}
});
window.addEventListener("resize", () => {
this.scaleCanvas();
});
}
/**
* Initializes game by creating the game field and setting bombs
*/
initGame() {
for (let x = 0; x < this.fieldSize.x; x++) {
this.field.push([]);
for (let y = 0; y < this.fieldSize.y; y++) {
this.field[x].push({tileValue: 0, clicked: false, flagged: false});
}
}
this.scaleCanvas();
this.updateBombs();
this.initEventListeners();
}
initTime() {
this.startTime = new Date().getTime();
this.timer = setInterval(() => {
const duration = (new Date().getTime() - this.startTime) / 1000;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
this.timeEl.innerText = (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}, 1000);
}
restartGame() {
}
scaleCanvas() {
let size = window.innerWidth / this.fieldSize.x * .9;
if (size * this.fieldSize.y > window.innerHeight) {
size = window.innerHeight / this.fieldSize.y * .9;
}
this.tileSize = {x: size, y: size};
this.width = this.fieldSize.x * size;
this.height = this.fieldSize.y * size;
this.canvas.width = this.width;
this.canvas.height = this.height;
overlayCanvas.width = this.width;
overlayCanvas.height = this.height;
overlay2Canvas.width = this.width;
overlay2Canvas.height = this.height;
this.statsContainer.style.width = this.width + "px";
initBalls();
this.applyScaling();
if (this.gameOver) {
this.gameOverEvent();
} else if (this.victoryCheck()) {
this.victoryEvent();
}
}
testFlagPositions() {
for (let x = 0; x < this.fieldSize.x; x++) {
for (let y = 0; y < this.fieldSize.y; y++) {
if (this.field[x][y].flagged && this.field[x][y].tileValue !== true)
return false;
}
}
return true;
}
tileClickEvent(x, y) {
if (this.gameOver || this.victoryCheck())
return;
this.uncoverTile(x, y);
if (!this.field[x][y].flagged && this.field[x][y].tileValue === true) {
this.gameOver = true;
this.gameOverEvent();
}
}
tileDoubleClick(x, y) {
if (this.gameOver)
return;
if (this.field[x][y].clicked && !this.field[x][y].flagged && this.countFlaggedBombs(x, y) === this.field[x][y].tileValue) {
this.uncoverSurroundings(x, y);
}
}
tileFlag(x, y) {
if (this.gameOver)
return;
if (this.field[x][y].clicked && !this.field[x][y].flagged)
return;
this.field[x][y].flagged = !this.field[x][y].flagged;
this.field[x][y].clicked = this.field[x][y].flagged;
x -= this.renderingConfig.offsetX;
y -= this.renderingConfig.offsetY;
const drawX = (x - this.renderingConfig.tiltX) * this.renderingConfig.sizeX;
const drawY = (y - this.renderingConfig.tiltY) * this.renderingConfig.sizeY;
this.ctx.clearRect(drawX, drawY, this.renderingConfig.sizeX, this.renderingConfig.sizeY);
this.drawTile(x, y);
}
uncoverSurroundings(x, y) {
const surrounding = this.getSurroundingTiles(x, y);
for (let tile in surrounding) {
if (surrounding.hasOwnProperty(tile)) {
this.uncoverTile(surrounding[tile].x, surrounding[tile].y);
}
}
}
uncoverTile(x, y) {
if (this.field[x][y].clicked || this.field[x][y].flagged) {
return;
}
this.field[x][y].clicked = true;
this.drawTile(x - this.renderingConfig.offsetX, y - this.renderingConfig.offsetY);
if (this.field[x][y].tileValue === true) {
this.gameOverEvent();
}
if (this.field[x][y].tileValue === 0) {
setTimeout(() => {
this.uncoverSurroundings(x, y);
}, 100);
}
}
updateBombs() {
const remainingBombs = this.bombCount - this.countTotalFlags();
this.bombsEl.innerText = (remainingBombs < 100 ? "0" : 0) + (remainingBombs < 10 ? "0" : "") + remainingBombs;
}
victoryCheck() {
return !play && (this.countClickedTiles() === this.fieldSize.x * this.fieldSize.y - this.bombCount || (this.countTotalFlags() === this.bombCount && this.testFlagPositions()));
}
victoryEvent() {
if (this.victoryCheck()) {
animateVictory();
play = false;
const fontSize = this.tileSize.y * 1.33;
animateBackground(0, 0, this.canvas.width, this.canvas.height, 0, .01, new Date().getTime(), 300, {
r: 0,
g: 0,
b: 0,
a: 0
});
animateText(overlay2Ctx, this.renderingConfig, "Victory!", this.fieldSize.x / 2 - .5, this.fieldSize.y / 2 - .5, 0, fontSize, new Date().getTime(), 300, "green", "Roboto");
}
}
}

View File

@@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Minesweeper.js</title> <title>Minesweeper.js</title>
<link rel="shortcut icon" type="image/x-icon" href="icons/icon.ico"> <!--<link rel="shortcut icon" type="image/x-icon" href="icons/icon.ico">-->
<link rel="icon" type="image/png" href="icons/icon.png" sizes="32x32"> <!--<link rel="icon" type="image/png" href="icons/icon.png" sizes="32x32">-->
<style> <style>
html { html {
overflow: hidden; overflow: hidden;
@@ -75,6 +75,7 @@
<script type="text/javascript" src="animations/click.js"></script> <script type="text/javascript" src="animations/click.js"></script>
<script type="text/javascript" src="animations/victory.js"></script> <script type="text/javascript" src="animations/victory.js"></script>
<script type="text/javascript" src="game.js"></script>
<script type="text/javascript" src="minesweeper.js"></script> <script type="text/javascript" src="minesweeper.js"></script>
</body> </body>

View File

@@ -1,26 +1,3 @@
const canvas = document.getElementById('minesweeper-game');
const ctx = canvas.getContext('2d');
const statsContainer = document.getElementById('game-stats');
const timeEl = document.getElementById('time');
const bombsEl = document.getElementById('bombs');
const fieldSize = {x: 16, y: 12};
let tileSize;
const bombCount = 25;
const field = [];
let gameOver = false;
const scaleFactor = .5;
let isFirstClick = true;
let windowX = 0;
let windowY = 0;
let zoomFactor = 1;
let renderingConfig;
let startTime = 0;
let timer;
/** /**
* Defines all possible colors for the tile numbers * Defines all possible colors for the tile numbers
* @type {{ 1: string, 2: string, 3: string, 4: string, 6: string }} * @type {{ 1: string, 2: string, 3: string, 4: string, 6: string }}
@@ -34,8 +11,6 @@ const colors = {
6: "pink" 6: "pink"
}; };
ctx.scale(canvas.width / fieldSize.x * scaleFactor, canvas.height / fieldSize.y * scaleFactor);
function animateBackground(x, y, width, height, curOpacity, finalOpacity, startTime, duration, color) { function animateBackground(x, y, width, height, curOpacity, finalOpacity, startTime, duration, color) {
const time = (new Date()).getTime() - startTime; const time = (new Date()).getTime() - startTime;
@@ -60,7 +35,7 @@ function animateBackground(x, y, width, height, curOpacity, finalOpacity, startT
}); });
} }
function animateTile(x, y, curWidth, curHeight, finalWidth, finalHeight, curRadius, finalRadius, startTime, duration, color) { function animateTile(ctx, x, y, curWidth, curHeight, finalWidth, finalHeight, curRadius, finalRadius, startTime, duration, color) {
const time = (new Date()).getTime() - startTime; const time = (new Date()).getTime() - startTime;
if (curWidth === finalWidth && curHeight === finalHeight && curRadius === finalRadius) if (curWidth === finalWidth && curHeight === finalHeight && curRadius === finalRadius)
@@ -88,16 +63,13 @@ function animateTile(x, y, curWidth, curHeight, finalWidth, finalHeight, curRadi
drawRoundedRect(ctx, x + (finalWidth - curWidth) / 2, y + (finalHeight - curHeight) / 2, curWidth, curHeight, curRadius, color); drawRoundedRect(ctx, x + (finalWidth - curWidth) / 2, y + (finalHeight - curHeight) / 2, curWidth, curHeight, curRadius, color);
requestAnimFrame(() => { requestAnimFrame(() => {
animateTile(x, y, curWidth, curHeight, finalWidth, finalHeight, curRadius, finalRadius, startTime, duration, color); animateTile(ctx, x, y, curWidth, curHeight, finalWidth, finalHeight, curRadius, finalRadius, startTime, duration, color);
}); });
} }
function animateText(text, x, y, curFontSize, finalFontSize, startTime, duration, color, font, context) { function animateText(ctx, renderingConfig, text, x, y, curFontSize, finalFontSize, startTime, duration, color, font) {
const time = (new Date()).getTime() - startTime; const time = (new Date()).getTime() - startTime;
if (context === undefined)
context = ctx;
if (curFontSize === finalFontSize) if (curFontSize === finalFontSize)
return; return;
@@ -115,82 +87,16 @@ function animateText(text, x, y, curFontSize, finalFontSize, startTime, duration
if (font === undefined) if (font === undefined)
font = "Roboto"; font = "Roboto";
context.fillStyle = color; ctx.fillStyle = color;
context.font = "bold " + curFontSize + "px " + font; ctx.font = "bold " + curFontSize + "px " + font;
context.textAlign = "center"; ctx.textAlign = "center";
context.fillText(text, textDrawX, textDrawY); ctx.fillText(text, textDrawX, textDrawY);
requestAnimFrame(function () { requestAnimFrame(function () {
animateText(text, x, y, curFontSize, finalFontSize, startTime, duration, color, font, context); animateText(ctx, renderingConfig, text, x, y, curFontSize, finalFontSize, startTime, duration, color, font);
}); });
} }
function applyScaling() {
renderingConfig = calcScaling();
drawGrid(false);
}
function calcScaling(field = fieldSize, tile = tileSize, zoom = zoomFactor) {
const width = Math.ceil(field.x * zoom) + 1;
const height = Math.ceil(field.y * zoom) + 1;
const offsetX = Math.floor(windowX * field.x);
const offsetY = Math.floor(windowY * field.y);
const tiltX = windowX * field.x - offsetX;
const tiltY = windowY * field.y - offsetY;
const sizeX = tile.x / zoom;
const sizeY = tile.y / zoom;
return {
width, height, offsetX, offsetY, tiltX, tiltY, sizeX, sizeY
};
}
function countBombs(x, y) {
const tiles = getSurroundingTiles(x, y);
return tiles.count(true);
}
function countClickedTiles() {
let count = 0;
for (let x = 0; x < fieldSize.x; x++) {
for (let y = 0; y < fieldSize.y; y++) {
if (field[x][y].clicked && !field[x][y].flagged)
count++;
}
}
return count;
}
function countFlaggedBombs(x, y) {
const tiles = getSurroundingTiles(x, y);
return tiles.countFlagged(true);
}
function countTotalFlags() {
let count = 0;
for(let x = 0; x < fieldSize.x; x++) {
for(let y = 0; y < fieldSize.y; y++) {
if(field[x][y].flagged)
count++;
}
}
return count;
}
function drawGrid(animations = true) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let x = 0; x < renderingConfig.width; x++) {
for (let y = 0; y < renderingConfig.height; y++) {
drawTile(x, y, animations);
}
}
}
function drawRoundedRect(context, x, y, w, h, r, color) { function drawRoundedRect(context, x, y, w, h, r, color) {
context.fillStyle = color; context.fillStyle = color;
context.beginPath(); context.beginPath();
@@ -205,283 +111,12 @@ function drawRoundedRect(context, x, y, w, h, r, color) {
context.fill(); context.fill();
} }
function drawTile(x, y, animations = true) {
const virtualX = renderingConfig.offsetX + x;
const virtualY = renderingConfig.offsetY + y;
if(virtualX >= fieldSize.x || virtualY >= fieldSize.y)
return;
const content = field[virtualX][virtualY];
const width = .8 * renderingConfig.sizeX;
const height = .8 * renderingConfig.sizeY;
const drawX = (x + .1 - renderingConfig.tiltX) * renderingConfig.sizeX;
const drawY = (y + .1 - renderingConfig.tiltY) * renderingConfig.sizeY;
const radius = renderingConfig.sizeX * .1;
let color = getColor(virtualX, virtualY);
const fontSize = renderingConfig.sizeY * .5;
let fontFamily = "Roboto";
let textColor = "white";
let text = "";
ctx.textAlign = "center";
let duration = 0;
if (animations)
duration = 150;
if (!content.flagged && content.clicked) {
color = "#ddd";
if(content.tileValue === true) {
text = "";
fontFamily = "FontAwesome";
textColor = "#aa2211";
color = "#333";
} else if (content.tileValue !== 0) {
text = content.tileValue;
textColor = colors[content.tileValue];
}
} else if (content.flagged) {
color = "#ff0000";
fontFamily = "FontAwesome";
text = "";
}
animateTile(drawX, drawY, 0, 0, width, height, 0, radius, new Date().getTime(), duration, color);
if(text !== "") {
animateText(text, x, y, 0, fontSize, new Date().getTime(), duration, textColor, fontFamily, ctx);
}
}
function easeInOutCubic(t, b, c, d) { function easeInOutCubic(t, b, c, d) {
t /= d; t /= d;
t--; t--;
return c * (Math.pow(t, 3) + 1) + b; return c * (Math.pow(t, 3) + 1) + b;
} }
function gameOverEvent() {
play = false;
animateBackground(0, 0, canvas.width, canvas.height, 0, .75, new Date().getTime(), 200, {r: 0, g: 0, b: 0, a: 0});
animateText("Game Over", fieldSize.x / 2 - .5, fieldSize.y / 2 - .5, 0, tileSize.y * 1.33, new Date().getTime(), 200, "orange", "Roboto", overlay2Ctx);
}
function getColor(x, y) {
x++;
y++;
const pos = x * y;
const limit = fieldSize.x * fieldSize.y;
let percentage = pos / limit * 360;
return `hsl(${percentage},100%,50%)`;
}
function getPosition(e) {
const x = (e.x - (window.innerWidth - W) / 2) / W * zoomFactor + windowX;
const y = (e.y - (window.innerHeight - H) / 2) / H * zoomFactor + windowY;
const fieldX = Math.floor(x * fieldSize.x);
const fieldY = Math.floor(y * fieldSize.y);
return {x: fieldX, y: fieldY};
}
function getSurroundingTiles(x, y) {
const tiles = {};
if (x > 0) {
tiles["left"] = {tileValue: field[x - 1][y], x: x - 1, y: y};
if (y > 0) {
tiles["left-top"] = {tileValue: field[x - 1][y - 1], x: x - 1, y: y - 1};
}
if (y < fieldSize.y - 1) {
tiles["left-bottom"] = {tileValue: field[x - 1][y + 1], x: x - 1, y: y + 1};
}
}
if (x < fieldSize.x - 1) {
tiles["right"] = {tileValue: field[x + 1][y], x: x + 1, y: y};
if (y > 0)
tiles["right-top"] = {tileValue: field[x + 1][y - 1], x: x + 1, y: y - 1};
if (y < fieldSize.y - 1)
tiles["right-bottom"] = {tileValue: field[x + 1][y + 1], x: x + 1, y: y + 1};
}
if (y > 0)
tiles["top"] = {tileValue: field[x][y - 1], x: x, y: y - 1};
if (y < fieldSize.y - 1)
tiles["bottom"] = {tileValue: field[x][y + 1], x: x, y: y + 1};
return tiles;
}
function initBombs(startX, startY) {
for (let i = 0; i < bombCount; i++) {
const ranX = Math.floor(Math.random() * fieldSize.x);
const ranY = Math.floor(Math.random() * fieldSize.y);
if (ranX === startX || ranX === startX - 1 || ranX === startX + 1 || ranY === startY || ranY === startY - 1 || ranY === startY + 1 || field[ranX][ranY].tileValue === true) {
i--;
continue;
}
field[ranX][ranY].tileValue = true;
}
for (let x = 0; x < fieldSize.x; x++) {
for (let y = 0; y < fieldSize.y; y++) {
if (field[x][y].tileValue !== true) {
field[x][y].tileValue = countBombs(x, y);
}
}
}
}
/**
* Initializes game by creating the game field and setting bombs
*/
function initGame() {
for (let x = 0; x < fieldSize.x; x++) {
field.push([]);
for (let y = 0; y < fieldSize.y; y++) {
field[x].push({tileValue: 0, clicked: false, flagged: false});
}
}
scaleCanvas();
updateBombs();
}
function initTime() {
startTime = new Date().getTime();
timer = setInterval(() => {
const duration = (new Date().getTime() - startTime) / 1000;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
timeEl.innerText = (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}, 1000);
}
function scaleCanvas() {
let size = window.innerWidth / fieldSize.x * .9;
if(size * fieldSize.y > window.innerHeight) {
size = window.innerHeight / fieldSize.y * .9;
}
tileSize = {x: size, y: size};
W = fieldSize.x * size;
H = fieldSize.y * size;
canvas.width = W;
canvas.height = H;
overlayCanvas.width = W;
overlayCanvas.height = H;
overlay2Canvas.width = W;
overlay2Canvas.height = H;
statsContainer.style.width = W + "px";
initBalls();
applyScaling();
if (gameOver) {
gameOverEvent();
} else if (victoryCheck()) {
victoryEvent();
}
}
function testFlagPositions() {
for(let x = 0; x < fieldSize.x; x++) {
for(let y = 0; y < fieldSize.y; y++) {
if(field[x][y].flagged && field[x][y].tileValue !== true)
return false;
}
}
return true;
}
function tileClickEvent(x, y) {
if (gameOver || victoryCheck())
return;
uncoverTile(x, y);
if (!field[x][y].flagged && field[x][y].tileValue === true) {
gameOver = true;
gameOverEvent();
}
}
function tileDoubleClick(x, y) {
if (gameOver)
return;
if (field[x][y].clicked && !field[x][y].flagged && countFlaggedBombs(x, y) === field[x][y].tileValue) {
uncoverSurroundings(x, y);
}
}
function tileFlag(x, y) {
if (gameOver)
return;
if (field[x][y].clicked && !field[x][y].flagged)
return;
field[x][y].flagged = !field[x][y].flagged;
field[x][y].clicked = field[x][y].flagged;
x -= renderingConfig.offsetX;
y -= renderingConfig.offsetY;
const drawX = (x - renderingConfig.tiltX) * renderingConfig.sizeX;
const drawY = (y - renderingConfig.tiltY) * renderingConfig.sizeY;
ctx.clearRect(drawX, drawY, renderingConfig.sizeX, renderingConfig.sizeY);
drawTile(x, y);
}
function uncoverSurroundings(x, y) {
const surrounding = getSurroundingTiles(x, y);
for (let tile in surrounding) {
if (surrounding.hasOwnProperty(tile)) {
uncoverTile(surrounding[tile].x, surrounding[tile].y);
}
}
}
function uncoverTile(x, y) {
if (field[x][y].clicked || field[x][y].flagged) {
return;
}
field[x][y].clicked = true;
drawTile(x - renderingConfig.offsetX, y - renderingConfig.offsetY);
if (field[x][y].tileValue === true) {
gameOverEvent();
}
if (field[x][y].tileValue === 0) {
setTimeout(() => {
uncoverSurroundings(x, y);
}, 100);
}
}
function updateBombs() {
const remainingBombs = bombCount - countTotalFlags();
bombsEl.innerText = (remainingBombs < 100 ? "0" : 0) + (remainingBombs < 10 ? "0" : "") + remainingBombs;
}
function victoryCheck() {
return !play && (countClickedTiles() === fieldSize.x * fieldSize.y - bombCount || (countTotalFlags() === bombCount && testFlagPositions()));
}
function victoryEvent() {
if(victoryCheck()) {
animateVictory();
play = false;
const fontSize = tileSize.y * 1.33;
animateBackground(0, 0, canvas.width, canvas.height, 0, .01, new Date().getTime(), 300, {r: 0, g: 0, b: 0, a: 0});
animateText("Victory!", fieldSize.x / 2 - .5, fieldSize.y / 2 - .5, 0, fontSize, new Date().getTime(), 300, "green", "Roboto", overlay2Ctx);
}
}
Object.prototype.count = function (val) { Object.prototype.count = function (val) {
let counter = 0; let counter = 0;
for (let el in this) { for (let el in this) {
@@ -506,140 +141,4 @@ Object.prototype.countFlagged = function (val) {
return counter; return counter;
}; };
overlay2Canvas.addEventListener("click", (e) => { const game = new Game();
if(isDragging)
return;
const pos = getPosition(e);
if (isFirstClick) {
initBombs(pos.x, pos.y);
initTime();
isFirstClick = false;
}
tileClickEvent(pos.x, pos.y);
victoryEvent();
clicked(e);
});
overlay2Canvas.addEventListener("dblclick", (e) => {
if(isDragging)
return;
const pos = getPosition(e);
tileDoubleClick(pos.x, pos.y);
victoryEvent();
});
overlay2Canvas.addEventListener("contextmenu", (e) => {
if(isDragging)
return;
e.preventDefault();
const pos = getPosition(e);
tileFlag(pos.x, pos.y);
updateBombs();
victoryEvent();
});
window.addEventListener("keyup", (e) => {
e.preventDefault();
const changeRate = .05;
let newZoomFactor = zoomFactor;
let newWindowX = windowX;
let newWindowY = windowY;
if (e.code === "BracketRight") {
newZoomFactor -= changeRate;
} else if (e.code === "Slash") {
newZoomFactor += changeRate;
} else if (e.code === "ArrowLeft") {
newWindowX -= changeRate;
} else if (e.code === "ArrowRight") {
newWindowX += changeRate;
} else if (e.code === "ArrowUp") {
newWindowY -= changeRate;
} else if (e.code === "ArrowDown") {
newWindowY += changeRate;
} else {
return;
}
newZoomFactor = Math.min(newZoomFactor, 1);
newZoomFactor = Math.max(newZoomFactor, .1);
newWindowX = Math.min(newWindowX, 1 - newZoomFactor);
newWindowY = Math.min(newWindowY, 1 - newZoomFactor);
newWindowX = Math.max(newWindowX, 0);
newWindowY = Math.max(newWindowY, 0);
if(newZoomFactor !== zoomFactor || newWindowX !== windowX || newWindowY !== windowY) {
zoomFactor = newZoomFactor;
windowX = newWindowX;
windowY = newWindowY;
applyScaling();
}
});
let startClientX = 0;
let startClientY = 0;
let startWindowX = 0;
let startWindowY = 0;
let hasClicked = false;
let isDragging = false;
document.addEventListener("mousedown", (e) => {
if(e.button === 0) {
hasClicked = true;
startClientX = e.clientX;
startClientY = e.clientY;
startWindowX = windowX;
startWindowY = windowY;
}
});
document.addEventListener("mouseup", () => {
hasClicked = false;
if(isDragging) {
setTimeout(() => {
isDragging = false;
}, 10);
}
});
document.addEventListener("mousemove", (e) => {
if(hasClicked) {
isDragging = true;
const deltaX = e.clientX - startClientX;
const deltaY = e.clientY - startClientY;
windowX = startWindowX - deltaX / W;
windowY = startWindowY - deltaY / H;
windowX = Math.min(windowX, 1 - zoomFactor);
windowY = Math.min(windowY, 1 - zoomFactor);
windowX = Math.max(windowX, 0);
windowY = Math.max(windowY, 0);
applyScaling();
}
});
window.addEventListener("resize", () => {
scaleCanvas();
});
initGame();