From 758533450bfe484f06328e98ee6fd60c41dc5bee Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 5 May 2018 23:43:26 +0200 Subject: [PATCH] Graphics overhaul as well as many other things --- animations/click.js | 5 +- animations/victory.js | 28 +- index.html | 1 + minesweeper.js | 579 ++++++++++++++++++++++++------------------ 4 files changed, 353 insertions(+), 260 deletions(-) diff --git a/animations/click.js b/animations/click.js index cac53ec..84bc2ae 100644 --- a/animations/click.js +++ b/animations/click.js @@ -25,10 +25,13 @@ if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(naviga fps = 29; } +let play = true; + // Draw function draw() { // Loop - requestAnimationFrame(draw); + if(play) + requestAnimationFrame(draw); // Set NOW and DELTA now = Date.now(); diff --git a/animations/victory.js b/animations/victory.js index 24c58d6..9c99f1b 100644 --- a/animations/victory.js +++ b/animations/victory.js @@ -10,13 +10,10 @@ overlay2Canvas.height = H; //Random Circles creator function create() { - //Place the circles at the center - this.x = W/2; this.y = H/2; - //Random radius between 2 and 6 this.radius = 2 + Math.random()*3; @@ -30,26 +27,27 @@ function create() { this.b = Math.round(Math.random())*255; } -for (var i = 0; i < 500; i++) { - circles.push(new create()); +function initBalls() { + for (let i = 0; i < 500; i++) { + circles.push(new create()); + } } function drawVictory() { - //Fill overlay2Canvas with black color - overlay2Ctx.globalCompositeOperation = "source-over"; - overlay2Ctx.fillStyle = "rgba(0,0,0,0.15)"; - overlay2Ctx.fillRect(0, 0, W, H); + overlayCtx.globalCompositeOperation = "source-over"; + overlayCtx.fillStyle = "rgba(0,0,0,0.15)"; + overlayCtx.fillRect(0, 0, W, H); //Fill the overlay2Canvas with circles - for(var j = 0; j < circles.length; j++){ - var c = circles[j]; + for(let j = 0; j < circles.length; j++){ + const c = circles[j]; //Create the circles - overlay2Ctx.beginPath(); - overlay2Ctx.arc(c.x, c.y, c.radius, 0, Math.PI*2, false); - overlay2Ctx.fillStyle = "rgba("+c.r+", "+c.g+", "+c.b+", 0.5)"; - overlay2Ctx.fill(); + overlayCtx.beginPath(); + overlayCtx.arc(c.x, c.y, c.radius, 0, Math.PI*2, false); + overlayCtx.fillStyle = "rgba("+c.r+", "+c.g+", "+c.b+", 0.5)"; + overlayCtx.fill(); c.x += c.vx; c.y += c.vy; diff --git a/index.html b/index.html index 9f4cee8..2c88565 100644 --- a/index.html +++ b/index.html @@ -23,5 +23,6 @@ + \ No newline at end of file diff --git a/minesweeper.js b/minesweeper.js index 44249d4..dc5387c 100644 --- a/minesweeper.js +++ b/minesweeper.js @@ -1,7 +1,7 @@ const canvas = document.getElementById('minesweeper-game'); const ctx = canvas.getContext('2d'); -const fieldSize = {x: 21, y: 13}; +const fieldSize = {x: 15, y: 10}; let tileSize; const bombCount = 30; const field = []; @@ -10,78 +10,120 @@ let victory = false; const scaleFactor = .5; let isFirstClick = true; +/** + * Defines all possible colors for the tile numbers + * @type {{ 1: string, 2: string, 3: string, 4: string, 6: string }} + */ +const colors = { + 1: "blue", + 2: "green", + 3: "red", + 4: "purple", + 5: "yellow", + 6: "pink" +}; + ctx.scale(canvas.width / fieldSize.x * scaleFactor, canvas.height / fieldSize.y * scaleFactor); -/** - * 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}); - } - } +function animateBackground(x, y, width, height, curOpacity, finalOpacity, startTime, duration, color) { + const time = (new Date()).getTime() - startTime; - scaleCanvas(); + if (curOpacity >= finalOpacity) + return; + + const newOpacity = easeInOutCubic(time, 0, finalOpacity, duration); + + console.log(newOpacity, finalOpacity, color); + + if (newOpacity <= finalOpacity) + curOpacity = newOpacity; + else + curOpacity = finalOpacity; + + color.a = curOpacity; + + overlay2Ctx.clearRect(x, y, width, height); + overlay2Ctx.fillStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + color.a + ")"; + overlay2Ctx.fillRect(x, y, width, height); + + requestAnimFrame(function () { + animateBackground(x, y, width, height, curOpacity, finalOpacity, startTime, duration, color); + }); } -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); +function animateTile(x, y, curWidth, curHeight, finalWidth, finalHeight, curRadius, finalRadius, startTime, duration, color) { + const time = (new Date()).getTime() - startTime; - if (ranX === startX || ranX === startX - 1 || ranX === startX + 1 || ranY === startY || ranY === startY - 1 || ranY === startY + 1 || field[ranX][ranY].tileValue === true) { - i--; - continue; - } + if (curWidth === finalWidth && curHeight === finalHeight && curRadius === finalRadius) + return; - field[ranX][ranY].tileValue = true; + const newWidth = easeInOutCubic(time, 0, finalWidth, duration); + const newHeight = easeInOutCubic(time, 0, finalHeight, duration); + const newRadius = easeInOutCubic(time, 0, finalRadius, duration); + + if (newWidth < finalWidth) + curWidth = newWidth; + else + curWidth = finalWidth; + + if (newHeight < finalHeight) + curHeight = newHeight; + else + curHeight = finalHeight; + + if (newRadius < finalRadius) + curRadius = newRadius; + else + curRadius = finalRadius; + + drawRoundedRect(ctx, x + 1, y + 1, finalWidth - 2, finalHeight - 2, finalRadius, "#2e8cdd"); + + 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); + }); +} + +function animateText(text, x, y, curFontSize, finalFontSize, startTime, duration, color, font, context) { + const time = (new Date()).getTime() - startTime; + + if(context === undefined) + context = ctx; + + if (curFontSize === finalFontSize) + return; + + const newFontSize = easeInOutCubic(time, 0, finalFontSize, duration); + + if (newFontSize < finalFontSize) { + curFontSize = newFontSize; + } else { + curFontSize = finalFontSize; } - for(let x = 0; x < fieldSize.x; x++) { + if (font === undefined) + font = "Roboto"; + + context.fillStyle = color; + context.font = "bold " + curFontSize + "px " + font; + context.textAlign = "center"; + context.fillText(text, x, y + tileSize.y * .5 + curFontSize * .33); + + requestAnimFrame(function () { + animateText(text, x, y, curFontSize, finalFontSize, startTime, duration, color, font, context); + }); +} + +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].tileValue !== true) { - field[x][y].tileValue = countBombs(x, y); - } + if (field[x][y].clicked && !field[x][y].flagged) + count++; } } -} - -function drawGrid() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - for(let x = 0; x < fieldSize.x; x++) { - for (let y = 0; y < fieldSize.y; y++) { - ctx.strokeRect(x * tileSize.x, y * tileSize.y, tileSize.x, tileSize.y); - if(field[x][y].clicked) - drawText(x, y); - } - } -} - -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; + return count; } function countBombs(x, y) { @@ -94,10 +136,236 @@ function countFlaggedBombs(x, y) { return tiles.countFlagged(true); } +function drawGrid() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + for (let x = 0; x < fieldSize.x; x++) { + for (let y = 0; y < fieldSize.y; y++) { + drawTile(x, y); + } + } +} + +function drawRoundedRect(context, x, y, w, h, r, color) { + context.fillStyle = color; + context.beginPath(); + context.moveTo(x + r, y); + + context.arcTo(x + w, y, x + w, y + h, r); + context.arcTo(x + w, y + h, x, y + h, r); + context.arcTo(x, y + h, x, y, r); + context.arcTo(x, y, x + w, y, r); + + context.closePath(); + context.fill(); +} + +function drawTile(x, y) { + const fontSize = tileSize.y * scaleFactor; + ctx.textAlign = "center"; + if (!field[x][y].flagged && field[x][y].clicked) { + animateTile(x * tileSize.x + .1 * tileSize.x, y * tileSize.y + .1 * tileSize.y, + 0, 0, + tileSize.x - (.2 * tileSize.x), tileSize.y - (.2 * tileSize.y), + 0, tileSize.x * .1, + new Date().getTime(), 150, "#ddd"); + setTimeout(() => { + + }, 151); + if (field[x][y].tileValue !== 0) { + animateText(field[x][y].tileValue, (x + .5) * tileSize.x, y * tileSize.y, 0, fontSize, new Date().getTime(), 150, colors[field[x][y].tileValue]); + setTimeout(() => { + animateText(field[x][y].tileValue, (x + .5) * tileSize.x, y * tileSize.y, fontSize, fontSize, new Date().getTime(), 1, colors[field[x][y].tileValue]); + }, 151); + } + } else if (field[x][y].flagged) { + animateTile(x * tileSize.x + .1 * tileSize.x, y * tileSize.y + .1 * tileSize.y, + 0, 0, + tileSize.x - (.2 * tileSize.x), tileSize.y - (.2 * tileSize.y), + 0, tileSize.x * .1, + new Date().getTime(), 150, "#ff0000"); + + animateText("", (x + .5) * tileSize.x, y * tileSize.y, 0, fontSize, new Date().getTime(), 150, "white", "FontAwesome"); + } else { + drawRoundedRect(ctx, x * tileSize.x + (.1 * tileSize.x), y * tileSize.y + (.1 * tileSize.y), tileSize.x - (.2 * tileSize.x), tileSize.y - (.2 * tileSize.y), tileSize.x * .1, "#2e8cdd"); + } +} + +function easeInOutCubic(t, b, c, d) { + t /= d; + t--; + return c * (Math.pow(t, 3) + 1) + b; +} + +function gameOverEvent() { + console.log("Game Over"); + animateBackground(0, 0, canvas.width, canvas.height, 0, .75, new Date().getTime(), 200, {r: 0, g: 0, b: 0, a: 0}); + animateText("Game Over", canvas.width / 2, canvas.height / 2, 0, tileSize.y * 1.33, new Date().getTime(), 200, "orange", "Roboto", overlay2Ctx); +} + +function getPositon(e) { + const x = e.x - canvas.offsetLeft; + const y = e.y - canvas.offsetTop; + const fieldX = Math.floor(x / tileSize.x); + const fieldY = Math.floor(y / tileSize.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; +} + +/** + * 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(); + initBalls(); +} + +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); + } + } + } +} + +function scaleCanvas() { + tileSize = {x: window.innerWidth / fieldSize.x * .9, y: window.innerWidth / fieldSize.x * .9}; + + W = fieldSize.x * tileSize.x; + H = fieldSize.y * tileSize.y; + + canvas.width = W; + canvas.height = H; + overlayCanvas.width = W; + overlayCanvas.height = H; + overlay2Canvas.width = W; + overlay2Canvas.height = H; + + drawGrid(); + if (gameOver) { + gameOverEvent(); + } +} + +function tileClickEvent(x, y) { + if (gameOver) + 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; + ctx.clearRect(x * tileSize.x + (1 / scaleFactor), y * tileSize.y + (1 / scaleFactor), tileSize.x - (2 / scaleFactor), tileSize.y - (2 / scaleFactor)); + 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, y); + if (field[x][y].tileValue === true) { + gameOverEvent(); + } + if (field[x][y].tileValue === 0) { + setTimeout(() => { + uncoverSurroundings(x, y); + }, 100); + } +} + +function victoryCheck() { + if (!victory && countClickedTiles() === fieldSize.x * fieldSize.y - bombCount) { + victory = true; + victoryEvent(); + } +} + +function victoryEvent() { + console.log("Win!"); + animate(); + play = false; + const fontSize = tileSize.y * 1.33; + animateBackground(0, 0, canvas.width, canvas.height, 0, .01, new Date().getTime(), 200, {r: 0, g: 0, b: 0, a: 0}); + animateText("Victory!", canvas.width / 2, canvas.height / 2 - fontSize / 2, 0, fontSize, new Date().getTime(), 200, "green", "Roboto", overlay2Ctx); +} + Object.prototype.count = function (val) { let counter = 0; - for(let el in this) { - if(this.hasOwnProperty(el)) { + for (let el in this) { + if (this.hasOwnProperty(el)) { if (val === this[el].tileValue.tileValue) { counter++; } @@ -108,9 +376,9 @@ Object.prototype.count = function (val) { Object.prototype.countFlagged = function (val) { let counter = 0; - for(let el in this) { - if(this.hasOwnProperty(el)) { - if(this[el].tileValue.flagged === val) { + for (let el in this) { + if (this.hasOwnProperty(el)) { + if (this[el].tileValue.flagged === val) { counter++; } } @@ -118,40 +386,10 @@ Object.prototype.countFlagged = function (val) { return counter; }; -function tileClickEvent(x, y) { - if(gameOver) - 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; - ctx.clearRect(x * tileSize.x + (1 / scaleFactor), y * tileSize.y + (1 / scaleFactor), tileSize.x - (2 / scaleFactor), tileSize.y - (2 / scaleFactor)); - drawText(x, y); -} - - overlay2Canvas.addEventListener("click", (e) => { const pos = getPositon(e); - if(isFirstClick) { + if (isFirstClick) { initBombs(pos.x, pos.y); isFirstClick = false; } @@ -173,161 +411,14 @@ overlay2Canvas.addEventListener("dblclick", (e) => { overlay2Canvas.addEventListener("contextmenu", (e) => { e.preventDefault(); - console.log(e); const pos = getPositon(e); tileFlag(pos.x, pos.y); }); -function getPositon(e) { - const x = e.x - canvas.offsetLeft; - const y = e.y - canvas.offsetTop; - const fieldX = Math.floor(x / tileSize.x); - const fieldY = Math.floor(y / tileSize.y); - - return {x: fieldX, y: fieldY}; -} - -function scaleCanvas() { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - overlayCanvas.width = window.innerWidth; - overlayCanvas.height = window.innerHeight; - - W = window.innerWidth; - H = window.innerHeight; - - // tileSize = {x: canvas.width / fieldSize.x, y: canvas.height / fieldSize.y}; - tileSize = {x: 100, y: 100}; - drawGrid(); - if(gameOver) { - gameOverEvent(); - } -} - -function uncoverTile(x, y) { - if(field[x][y].clicked || field[x][y].flagged) { - return; - } - field[x][y].clicked = true; - drawText(x, y); - if(field[x][y].tileValue === true) { - gameOverEvent(); - } - if(field[x][y].tileValue === 0) { - uncoverSurroundings(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); - } - } -} - -const colors = { - 1: "blue", - 2: "green", - 3: "red", - 4: "purple", - 5: "yellow", - 6: "pink" -}; - -function drawText(x, y) { - ctx.font = "bold 50px Roboto"; - ctx.textAlign = "center"; - if(!field[x][y].flagged && field[x][y].clicked) { - ctx.fillStyle = "#ddd"; - ctx.fillRect(x * tileSize.x + 1, y * tileSize.y + 1, tileSize.x - 2, tileSize.y - 2); - if (field[x][y].tileValue !== 0) { - ctx.fillStyle = colors[field[x][y].tileValue]; - ctx.fillText(field[x][y].tileValue, (x + .5) * tileSize.x, (y + .5) * tileSize.y + 15); - } - } else if(field[x][y].flagged) { - ctx.fillStyle = "red"; - ctx.fillRect(x * tileSize.x + 5 / scaleFactor, y * tileSize.y + 5 / scaleFactor, tileSize.x - 10 / scaleFactor, tileSize.y - 10 / scaleFactor); - - ctx.font = "bold 50px FontAwesome"; - ctx.fillStyle = "white"; - ctx.fillText("", (x + .5) * tileSize.x, (y + .5) * tileSize.y + 15); - } -} - window.addEventListener("resize", () => { scaleCanvas(); }); -function gameOverEvent() { - console.log("Game Over"); - ctx.fillStyle = "orange"; - ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2); - animateBackground({r: 0, g: 0, b: 0, a: 0}, 0, 0, canvas.width, canvas.height, .75, new Date().getTime(), 2); - animateText("Game Over", canvas.width / 2, canvas.height / 2, 0, 100, new Date().getTime(), 200); -} - -function animateText(text, x, y, curFontSize, finalFontSize, startTime, speed) { - const time = (new Date()).getTime() - startTime; - - const newFontSize = speed * time / 1000; - - if(newFontSize < finalFontSize) { - curFontSize = newFontSize; - } else { - curFontSize = finalFontSize; - } - - // drawGrid(); - ctx.fillStyle = "orange"; - ctx.font = "bold " + curFontSize + "px Roboto"; - ctx.fillText(text, x, y); - - requestAnimFrame(function () { - animateText(text, x, y, curFontSize, finalFontSize, startTime, speed); - }) -} - -function animateBackground(color, x, y, width, height, maxOpacity, startTime, speed) { - const time = (new Date()).getTime() - startTime; - - const newOpacity = speed * time / 1000; - - if(newOpacity <= maxOpacity) color.a = newOpacity; - - drawGrid(); - ctx.fillStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + color.a + ")"; - ctx.fillRect(x, y, width, height); - - requestAnimFrame(function () { - animateBackground(color, x, y, width, height, maxOpacity, startTime, speed); - }); -} - -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 victoryCheck() { - if(!victory && countClickedTiles() === fieldSize.x * fieldSize.y - bombCount) { - victory = true; - victoryEvent(); - } -} - -function victoryEvent() { - console.log("Win!"); - animate(); -} - initGame(); \ No newline at end of file