-
- 000
-
+
+
+
Minesweeper.js
+ A sweet, tasty, little Minesweeper game written in JavaScript with HTML5 <canvas> Element
+
-
-
-
- 00:00
-
-
-
-
-
-
-
-
+
diff --git a/minesweeper.js b/minesweeper.js
index 1a0a476..c514cc0 100644
--- a/minesweeper.js
+++ b/minesweeper.js
@@ -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
* @type {{ 1: string, 2: string, 3: string, 4: string, 6: string }}
@@ -34,8 +11,6 @@ const colors = {
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) {
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;
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);
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;
- if (context === undefined)
- context = ctx;
-
if (curFontSize === finalFontSize)
return;
@@ -115,82 +87,16 @@ function animateText(text, x, y, curFontSize, finalFontSize, startTime, duration
if (font === undefined)
font = "Roboto";
- context.fillStyle = color;
- context.font = "bold " + curFontSize + "px " + font;
- context.textAlign = "center";
- context.fillText(text, textDrawX, textDrawY);
+ ctx.fillStyle = color;
+ ctx.font = "bold " + curFontSize + "px " + font;
+ ctx.textAlign = "center";
+ ctx.fillText(text, textDrawX, textDrawY);
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) {
context.fillStyle = color;
context.beginPath();
@@ -205,283 +111,12 @@ function drawRoundedRect(context, x, y, w, h, r, color) {
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) {
t /= d;
t--;
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) {
let counter = 0;
for (let el in this) {
@@ -506,140 +141,27 @@ Object.prototype.countFlagged = function (val) {
return counter;
};
-overlay2Canvas.addEventListener("click", (e) => {
- if(isDragging)
- return;
+String.prototype.toDOM=function(){
+ let d=document
+ ,i
+ ,a=d.createElement("div")
+ ,b=d.createDocumentFragment();
+ a.innerHTML=this;
+ while(i=a.firstChild)b.appendChild(i);
+ return b;
+};
- const pos = getPosition(e);
+let game;
- if (isFirstClick) {
- initBombs(pos.x, pos.y);
- initTime();
- isFirstClick = false;
- }
-
- tileClickEvent(pos.x, pos.y);
-
- victoryEvent();
-
- clicked(e);
+const startContainer = document.getElementsByClassName('start-container')[0];
+const startBackground = document.getElementsByClassName('start-background')[0];
+const startButton = document.getElementById('startgame');
+startButton.addEventListener('click', () => {
+ startContainer.classList.add('slideDown');
+ startBackground.classList.add('transparent');
+ game = new Game();
+ game.initGame();
});
-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();
+// const game = new Game();
+// game.initGame();
\ No newline at end of file