├── LICENSE ├── README.md ├── css └── styles.css ├── images ├── flag.png ├── git.jpg └── mine.png ├── index.html └── js └── minesweeper.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Joey van Ommen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HTML5-Minesweeper 2 | ----------------- 3 | 4 | Minesweeper game using jQuery and HTML5 canvas. 5 | 6 | Online demo can be found here at [joeyvo.me/HTML5-minesweeper](http://joeyvo.me/HTML5-minesweeper/) 7 | 8 | Now featured on [Chrome Experiments](http://www.chromeexperiments.com/detail/html5-minesweeper) 9 | 10 | Follow news and developments on twitter: [@Joeynoh](https://twitter.com/Joeynoh) 11 | 12 | ### Current version: 3.0.1 13 | 14 | ##### New 15 | - Speed solving, right click on a revealed square 16 | - Faster mine index algorithm 17 | - Listener put on mouseup rather than mousedown 18 | - Retina support (contributed by meltingice) 19 | - Image preloading 20 | - Replaced that Github ribbon 21 | - Game no longer automatically resets after a win or loss 22 | 23 | ##### Previous versions 24 | - Brand new and more complete UI 25 | - Better interaction 26 | - Custom animations on the playingfield 27 | - New images 28 | - Scoreboard and use of local storage for scores 29 | - Refactoring and optimization 30 | - Some bug fixes 31 | - New UI 32 | - New difficulty features 33 | - Click on empty square, reveal larger space up until other numbered squares 34 | - Timer 35 | - Better grid, using rounded squares 36 | - Fixed numerous issues like unflagging and clicking after the game has been won or lost 37 | - Added mine and flag counters 38 | - Created a reset function so the page no longer needs to refresh to start over 39 | 40 | TODO 41 | ---- 42 | - Score algorithm 43 | 44 | 45 | Inspired by: 46 | https://github.com/wbrowne/HTML5-Minesweeper 47 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* CSS Resets */ 4 | 5 | html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,address,button,cite,code,del,dfn,em,img,ins,q,small,strong,sub,sup,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{border:0;margin:0;padding:0}article,aside,figure,figure img,figcaption,hgroup,footer,header,nav,section,video,object{display:block}a img{border:0}figure{position:relative}figure img{width:100%} 6 | 7 | /* ==================================================================================================================== */ 8 | /* ! The 1140px Grid V2 by Andy Taylor \ http://cssgrid.net \ http://www.twitter.com/andytlr \ http://www.andytlr.com */ 9 | /* ==================================================================================================================== */ 10 | 11 | .container{padding-left:20px;padding-right:20px}.row{width:100%;max-width:940px;min-width:600px;margin:0 auto;overflow:hidden}.onecol,.twocol,.threecol,.fourcol,.fivecol,.sixcol,.sevencol,.eightcol,.ninecol,.tencol,.elevencol{margin-right:3.8%;float:left;min-height:1px}.row .onecol{width:4.85%}.row .twocol{width:13.45%}.row .threecol{width:22.05%}.row .fourcol{width:30.75%}.row .fivecol{width:39.45%}.row .sixcol{width:48%}.row .sevencol{width:56.75%}.row .eightcol{width:65.4%}.row .ninecol{width:74.05%}.row .tencol{width:82.7%}.row .elevencol{width:91.35%}.row .twelvecol{width:100%;float:left}.last{margin-right:0}img,object,embed{max-width:100%}img{height:auto} 12 | 13 | body { 14 | font-family: 'Open Sans', sans-serif; 15 | font-weight: 300; 16 | font-size: 13px; 17 | } 18 | 19 | header { 20 | padding: 30px 0px; 21 | } 22 | 23 | footer { 24 | border-top: 2px dashed #eee; 25 | margin-top: 30px !important; 26 | padding-top: 30px; 27 | } 28 | 29 | h1 { 30 | font-weight: 300; 31 | } 32 | 33 | canvas { 34 | /*padding: 5px 4px 4px 5px; 35 | border: 3px solid #eee; */ 36 | } 37 | 38 | button { 39 | background: #f16529; 40 | color: #fff; 41 | border: none; 42 | font-size: 20px; 43 | font-weight: 300; 44 | padding: 5px 15px; 45 | } 46 | 47 | button:hover { 48 | background: #f27c4a; 49 | cursor: pointer; 50 | } 51 | 52 | .gamescreen, .startscreen { 53 | text-align: center; 54 | height: 330px; 55 | } 56 | 57 | .startscreen h1, .gamescreen h1 { 58 | font-size: 26px; 59 | margin-top: 30px; 60 | } 61 | 62 | .startscreen p, .gamescreen p { 63 | line-height: 30px; 64 | } 65 | 66 | #scoreboard { 67 | width: 100%; 68 | margin-top: 15px; 69 | } 70 | 71 | .git { 72 | line-height: 60px; 73 | color: #bbb; 74 | font-weight: 500; 75 | text-decoration: none; 76 | } 77 | 78 | .desc { 79 | color: #bbb; 80 | } 81 | 82 | .git img { 83 | float: left; 84 | margin-right: 10px; 85 | margin-top: 5px; 86 | } 87 | -------------------------------------------------------------------------------- /images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joeynoh/HTML5-Minesweeper/33fa835c67f17b58cd34f7e300c6068bb1028914/images/flag.png -------------------------------------------------------------------------------- /images/git.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joeynoh/HTML5-Minesweeper/33fa835c67f17b58cd34f7e300c6068bb1028914/images/git.jpg -------------------------------------------------------------------------------- /images/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Joeynoh/HTML5-Minesweeper/33fa835c67f17b58cd34f7e300c6068bb1028914/images/mine.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Minesweeper Canvas 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Fork me on GitHub 17 | 18 |
19 |
20 |
21 |

HTML5 Minesweeper

22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 |

Unfortunately, your browser does not seem to support the canvas element.

31 |
32 | 33 | 36 |
37 |
38 |
39 |

Start Game

40 |

Please choose a difficulty to get started.

41 | 42 | 43 | 44 | 45 | 46 |

Games You Won

47 | 48 | 49 | 50 | 51 | 52 | 53 |
NameMinesSeconds
54 | 55 |
56 | 57 |
58 |

Game on :)

59 |

Click on a square to start the game!

60 | 61 | 62 |
63 |

0 seconds have passed

64 | 65 | 66 |
67 |
68 |
69 | 87 |
88 | 89 | 90 | -------------------------------------------------------------------------------- /js/minesweeper.js: -------------------------------------------------------------------------------- 1 | /*global $: true, console: true */ 2 | 3 | $(function(){ 4 | "use strict"; 5 | 6 | /* =========================================== */ 7 | // --- Global & Default Variables --- 8 | /* =========================================== */ 9 | 10 | var globals = { 11 | firstClick: true, 12 | gameover: false, 13 | canvas: null, 14 | context: null, 15 | totalMines: 0, 16 | totalFlags: 0, 17 | elapsedTime: 0, 18 | clock: '', 19 | restart: '', 20 | mineMap: '', 21 | flagMap: '', 22 | revealedMap: '', 23 | currentAnimation: '', 24 | previous: new Array(2), 25 | squaresX: '', 26 | squaresY: '' 27 | }, 28 | 29 | defaults = { 30 | difficulty: 0, 31 | celSize: 20, 32 | width: 400, 33 | height: 400, 34 | background: 'white', 35 | font: '14px Arial', 36 | celColor: '#dadada', 37 | celStroke: 'white', 38 | celRadius: 5, 39 | mineImg: 'images/mine.png', 40 | flagImg: 'images/flag.png' 41 | }, 42 | 43 | containers = { 44 | // In core.init(), we add containers. 45 | }; 46 | 47 | /* =========================================== */ 48 | // --- Core Functions --- 49 | /* =========================================== */ 50 | 51 | var core = { 52 | 53 | /* ------------------------------------------- */ 54 | // -- Initiate function 55 | // -- Get the canvas and context, as well as 56 | // -- attach some listeners. 57 | // -- @return void 58 | /* ------------------------------------------- */ 59 | 60 | init: function(){ 61 | globals.canvas = $('#board'); 62 | globals.context = globals.canvas[0].getContext("2d"); 63 | globals.context.background = defaults.background; 64 | 65 | var ratio = this.hiDPIRatio(); 66 | if (ratio !== 1) { 67 | var originalWidth = globals.canvas[0].width; 68 | var originalHeight = globals.canvas[0].height; 69 | 70 | globals.canvas[0].width = originalWidth * ratio; 71 | globals.canvas[0].height = originalHeight * ratio; 72 | globals.canvas.css({ 73 | width: originalWidth + "px", 74 | height: originalHeight + "px" 75 | }); 76 | 77 | globals.context.scale(ratio, ratio); 78 | } 79 | 80 | globals.context.font = defaults.font; 81 | 82 | defaults.width = globals.canvas.width(); 83 | globals.squaresX = Math.floor(defaults.width / defaults.celSize); 84 | globals.squaresY = Math.floor(defaults.height / defaults.celSize); 85 | 86 | globals.mineMap = new Array(globals.squaresX); 87 | globals.flagMap = new Array(globals.squaresX); 88 | globals.revealedMap = new Array(globals.squaresX); 89 | 90 | containers.flags = $('#flags'); 91 | containers.mines = $('#mines'); 92 | containers.status = $('#status'); 93 | containers.time = $('#time'); 94 | containers.msg = $('#msg'); 95 | containers.scoreboard = $('#scoreboard'); 96 | 97 | containers.easy = $('#easybtn'); 98 | containers.medium = $('#mediumbtn'); 99 | containers.insane = $('#insanebtn'); 100 | containers.switchscreens = $('#switchscreens'); 101 | containers.reset = $('#reset'); 102 | 103 | var difarr = { 9: containers.easy, 6: containers.medium, 3: containers.insane}; 104 | 105 | $.each(difarr, function(index, value){ 106 | value.on({ 107 | click: function(){ 108 | defaults.difficulty = index; 109 | util.switchScreens(); 110 | } 111 | }); 112 | }); 113 | 114 | containers.switchscreens.on({ 115 | click: function(){ 116 | util.switchScreens(); 117 | } 118 | }); 119 | 120 | containers.reset.on({ 121 | click: function(){ 122 | core.reset(); 123 | } 124 | }); 125 | 126 | $('.gamescreen').hide(); 127 | 128 | // Attach some listeners, at this point only mousedown 129 | globals.canvas.on({ 130 | mouseup: function(e){ 131 | action.click(e); 132 | }, 133 | mousemove: function(e){ 134 | action.hover(e); 135 | } 136 | }); 137 | 138 | // Some quick preloading of the mine and flag images 139 | var images = new Array(); 140 | images[0] = new Image(); 141 | images[0].src = defaults.mineImg; 142 | images[1] = new Image(); 143 | images[1].src = defaults.flagImg; 144 | 145 | // Initialize the board 146 | core.setup(); 147 | 148 | //animation.arrow(); 149 | 150 | }, 151 | 152 | hiDPIRatio: function() { 153 | var devicePixelRatio, backingStoreRatio; 154 | 155 | devicePixelRatio = window.devicePixelRatio || 1; 156 | backingStoreRatio = globals.context.webkitBackingStorePixelRatio || 157 | globals.context.mozBackingStorePixelRatio || 158 | globals.context.msBackingStorePixelRatio || 159 | globals.context.oBackingStorePixelRatio || 160 | globals.context.backingStorePixelRatio || 1; 161 | 162 | return devicePixelRatio / backingStoreRatio; 163 | }, 164 | 165 | /* ------------------------------------------- */ 166 | // -- Reset function 167 | // -- Resets the game, clears the timers, etc. 168 | // -- @return void 169 | /* ------------------------------------------- */ 170 | 171 | reset: function(){ 172 | 173 | // Clear the timer 174 | window.clearInterval(globals.clock); 175 | window.clearInterval(globals.restart); 176 | 177 | // Wipe the canvas clean 178 | globals.context.clearRect(0,0,defaults.width,defaults.height); 179 | 180 | // Reset all global vars to their default value 181 | globals.gameover = false; 182 | globals.firstClick = true; 183 | globals.totalMines = 0; 184 | globals.totalFlags = 0; 185 | globals.elapsedTime = 0; 186 | globals.mineMap = new Array(globals.squaresX); 187 | globals.flagMap = new Array(globals.squaresX); 188 | globals.revealedMap = new Array(globals.squaresX); 189 | 190 | // Clear certain containers 191 | containers.flags.html(''); 192 | containers.mines.html(''); 193 | containers.status.html('Game on :)'); 194 | containers.time.html('0'); 195 | containers.msg.html('Click on a square to start the game!'); 196 | 197 | // Initialize the board 198 | core.setup(); 199 | 200 | window.clearInterval(globals.currentAnimation); 201 | animation.walker(); 202 | }, 203 | 204 | /* ------------------------------------------- */ 205 | // -- Setup function 206 | // -- Sets up certain variables and draws the 207 | // -- board. Used during both init() and reset() 208 | // -- @return void 209 | /* ------------------------------------------- */ 210 | 211 | setup: function(){ 212 | // Clear flagMap array 213 | for(var k = 0; k < globals.squaresX; k++){ 214 | globals.flagMap[k] = Array(globals.squaresY); 215 | globals.revealedMap[k] = Array(globals.squaresY); 216 | } 217 | 218 | scores.display(); 219 | 220 | // Make sure proper styles are set 221 | globals.context.strokeStyle = defaults.celStroke; 222 | globals.context.fillStyle = defaults.celColor; 223 | 224 | animation.standardBoard(); 225 | }, 226 | 227 | /* ------------------------------------------- */ 228 | // -- Timer function 229 | // -- Starts the clock 230 | // -- @return void 231 | /* ------------------------------------------- */ 232 | 233 | timer: function(){ 234 | // Setup global var timer 235 | globals.clock = setInterval(function(){ 236 | globals.elapsedTime++; 237 | // Append time to #time 238 | containers.time.html(globals.elapsedTime); 239 | }, 1000); 240 | } 241 | }; 242 | 243 | /* =========================================== */ 244 | // --- Action Functions --- 245 | /* =========================================== */ 246 | 247 | var action = { 248 | 249 | /* ------------------------------------------- */ 250 | // -- Click function 251 | // -- listens to right and left mouse clicks 252 | // -- and determines the proper cel and what 253 | // -- action to take. 254 | // -- @return void 255 | /* ------------------------------------------- */ 256 | 257 | click: function(e){ 258 | 259 | if(globals.gameover){ 260 | return false; 261 | } 262 | 263 | // Calculate x & y relevant to the cel size, also (l) check if current x,y combo has already been revealed 264 | var x = Math.floor((e.pageX - globals.canvas[0].offsetLeft - 1) / defaults.celSize), 265 | y = Math.floor((e.pageY - globals.canvas[0].offsetTop - 1) / defaults.celSize), 266 | l = (globals.revealedMap[x][y]) ? 1 : -1; 267 | 268 | // If left-click, not a flag and the game is still going on 269 | if(e.which === 1 && globals.flagMap[x][y] !== 1 && defaults.difficulty !== 0){ 270 | 271 | // Is this the first click of the game? 272 | if(globals.firstClick === true){ 273 | 274 | window.clearInterval(globals.currentAnimation); 275 | animation.standardBoard(); 276 | 277 | // Set difficulty, based on default and difficulty selector, and start the timer 278 | //defaults.difficulty = containers.difficulty.val(); 279 | core.timer(); 280 | 281 | // Keep generating possible minemaps till one is generate where the square first clicked is not a mine 282 | do{ 283 | action.generateMines(globals.mineMap); 284 | }while(globals.mineMap[x][y] === -1); 285 | 286 | // Set number of mines 287 | containers.mines.html('You have to find ' + globals.totalMines + ' mines to win.'); 288 | globals.firstClick = false; 289 | } 290 | 291 | // Activate index function. See below for more details 292 | action.index(x, y); 293 | 294 | // If middle-click and a revealed square 295 | }else if(e.which === 3 && util.is('revealed', x, y)){ 296 | 297 | // Calculate number of surrounding mines 298 | var num = 0, 299 | surrounded = new Array(), 300 | xArr = [x, x + 1, x - 1], 301 | yArr = [y, y + 1, y - 1]; 302 | 303 | for(var a = 0; a < 3; a++){ 304 | for(var b = 0; b < 3; b++){ 305 | 306 | if(util.is('flag', xArr[a], yArr[b])){ 307 | num++; 308 | }else{ 309 | surrounded.push([xArr[a], yArr[b]]); 310 | } 311 | } 312 | } 313 | 314 | // Compare with number of actual mines 315 | if(num === globals.mineMap[x][y]){ 316 | $.each(surrounded, function(){ 317 | // Remove non-flagged squares, using action.index 318 | action.index(this[0], this[1]); 319 | }); 320 | } 321 | 322 | // If right-click, game is not over, square has not been revealed and this is not the first click 323 | }else if(e.which === 3 && l < 0 && globals.firstClick !== true){ 324 | 325 | // Flag the square 326 | var flag = new Image(); 327 | flag.src = defaults.flagImg; 328 | flag.onload = function(){ 329 | action.flag(flag,x,y); 330 | }; 331 | } 332 | }, 333 | 334 | /* ------------------------------------------- */ 335 | // -- Hover function 336 | // -- Speaks for itself 337 | // -- @return void 338 | /* ------------------------------------------- */ 339 | 340 | hover: function(e){ 341 | 342 | if(!globals.gameover){ 343 | // Calculate x & y relevant to the cel size, also (l) check if current x,y combo has already been revealed 344 | var x = Math.floor((e.pageX - globals.canvas[0].offsetLeft - 1) / defaults.celSize), 345 | y = Math.floor((e.pageY - globals.canvas[0].offsetTop - 1) / defaults.celSize), 346 | l = (globals.revealedMap[x][y]) ? 1 : -1, 347 | f = (globals.flagMap[x][y]) ? 1 : -1; 348 | 349 | var pX = globals.previous[0], 350 | pY = globals.previous[1]; 351 | 352 | if(typeof pX !== 'undefined' && globals.revealedMap[pX][pY] !== 1 && globals.flagMap[pX][pY] !== 1){ 353 | globals.context.fillStyle = defaults.celColor; 354 | util.roundRect(globals.previous[0], globals.previous[1]); 355 | } 356 | 357 | if(l < 0 && f < 0 && !globals.firstClick){ 358 | 359 | globals.context.fillStyle = '#aaa'; 360 | util.roundRect(x, y); 361 | globals.previous[0] = x; 362 | globals.previous[1] = y; 363 | } 364 | } 365 | }, 366 | 367 | /* ------------------------------------------- */ 368 | // -- Index function 369 | // -- Used to determine whether square is a mine 370 | // -- or not 371 | // -- @return void 372 | /* ------------------------------------------- */ 373 | 374 | index: function(x, y){ 375 | 376 | // If square is not revealed, is within boundaries and exists 377 | if(x >= 0 && y >= 0 && x <= globals.squaresX && y <= globals.squaresY && globals.mineMap[x] !== undefined){ 378 | 379 | var l = (globals.revealedMap[x][y]) ? 1 : -1; 380 | 381 | if(!util.is('revealed', x, y)){ 382 | 383 | // Add revealed square to the revealed array 384 | globals.revealedMap[x][y] = 1; 385 | 386 | if(globals.mineMap[x][y] !== -1){ 387 | // 'remove square', by drawing a white one over it 388 | var alpha = 0.1, 389 | squareFade = setInterval(function(){ 390 | globals.context.strokeStyle = 'white'; 391 | globals.context.fillStyle = 'rgba(255,255,255,' + alpha + ')'; 392 | util.roundRect(x, y); 393 | 394 | if(globals.mineMap[x][y] !== -1){ 395 | 396 | // Default colors for the index numbers in an array. [0] not having a color. 397 | var colorMap = ['none', 'blue', 'green', 'red', 'black', 'orange', 'cyan']; 398 | globals.context.fillStyle = colorMap[globals.mineMap[x][y]]; 399 | globals.context.fillText(globals.mineMap[x][y], (x * defaults.celSize) + 5, (y * defaults.celSize) + 16); 400 | } 401 | 402 | alpha = alpha + .1; 403 | 404 | if(alpha > 1){ 405 | window.clearInterval(squareFade); 406 | } 407 | }, 50); 408 | 409 | // If the square that was clicked has no surrounding mines... 410 | }else{ 411 | 412 | // If unforutantely there is mine, display it and trigger the function leading to the end of the game 413 | var mine = new Image(); 414 | mine.src = defaults.mineImg; 415 | mine.onload = function() { 416 | action.revealMines(mine); 417 | }; 418 | } 419 | 420 | if(globals.mineMap[x][y] === 0){ 421 | 422 | // remove all neighbors till squares are found that do have surrounding mines 423 | for(var i = -1; i <= 1; i++){ 424 | for(var j = -1; j <= 1; j++){ 425 | // If a neighboring square is also not surrounded by mines, remove his neighbors also; and repeat 426 | if(l < 0 && x + i >= 0 && y + j >= 0 && x + i <= globals.squaresX && y + j <= globals.squaresX){ 427 | action.index(x + i, y + j); 428 | } 429 | } 430 | } 431 | 432 | // If the square does not cover a mine, display the index number 433 | } 434 | } 435 | } 436 | }, 437 | 438 | /* ------------------------------------------- */ 439 | // -- Flag function 440 | // -- Used to flag a square 441 | // -- @return void 442 | /* ------------------------------------------- */ 443 | 444 | flag: function(flag, x, y){ 445 | 446 | // If square is not already flagged 447 | if(globals.flagMap[x][y] !== 1){ 448 | 449 | // Draw flag 450 | globals.context.drawImage(flag, x * defaults.celSize, y * defaults.celSize, defaults.celSize, defaults.celSize); 451 | globals.flagMap[x][y] = 1; 452 | globals.totalFlags++; 453 | 454 | }else{ 455 | 456 | // Remove flag image 457 | var img = globals.context.createImageData(defaults.celSize, defaults.celSize); 458 | for(var i = img.data.length; --i >= 0;){ 459 | img.data[i] = 0; 460 | } 461 | 462 | globals.context.putImageData(img, x * defaults.celSize, y * defaults.celSize); 463 | 464 | // Make sure proper styles are set 465 | globals.context.strokeStyle = defaults.celStroke; 466 | globals.context.fillStyle = defaults.celColor; 467 | 468 | util.roundRect(x, y); 469 | 470 | globals.flagMap[x][y] = 0; 471 | globals.totalFlags--; 472 | } 473 | 474 | // Adjust counters accordingly 475 | containers.mines.html('You have to find ' + (globals.totalMines - globals.totalFlags) + ' mines to win.'); 476 | containers.flags.html('You have set ' + globals.totalFlags + ' flags.'); 477 | 478 | // With every flag (or unflag) check if the game has been won 479 | action.won(); 480 | }, 481 | 482 | /* ------------------------------------------- */ 483 | // -- Won function 484 | // -- Used to determine if the game has been won 485 | // -- @return void 486 | /* ------------------------------------------- */ 487 | 488 | won: function(){ 489 | 490 | // Setup counter 491 | var count = 0; 492 | 493 | // Count the number of flagged mines 494 | for(var i = 0; i < globals.squaresX; i++){ 495 | for(var j = 0; j < globals.squaresY; j++){ 496 | if((globals.flagMap[i][j] === 1 ) && (globals.mineMap[i][j] === -1)){ 497 | count++; 498 | } 499 | } 500 | } 501 | 502 | // If the number of flagged mines equals the total number of mines, the game has been won 503 | if(count === globals.totalMines){ 504 | // Set game over status 505 | globals.gameover = true; 506 | containers.status.html('You won! :D'); 507 | 508 | scores.save(); 509 | 510 | // Stops the timer and counts down to a reset of the game 511 | window.clearInterval(globals.clock); 512 | } 513 | }, 514 | 515 | /* ------------------------------------------- */ 516 | // -- Generate Mines function 517 | // -- Places the mines on the field, at random 518 | // -- @return void 519 | /* ------------------------------------------- */ 520 | 521 | generateMines: function(){ 522 | 523 | // For every square 524 | for(var i = 0; i < globals.squaresX; i++){ 525 | globals.mineMap[i] = new Array(globals.squaresX); 526 | 527 | // The lower the dificulty, the more mines 528 | for(var j = 0; j < globals.squaresY; j++){ 529 | globals.mineMap[i][j] = Math.floor((Math.random() * defaults.difficulty) - 1); 530 | 531 | if(globals.mineMap[i][j] > 0){ 532 | globals.mineMap[i][j] = 0; 533 | } 534 | } 535 | } 536 | 537 | // Move on to the next step of the setup 538 | action.calculateMines(); 539 | }, 540 | 541 | /* ------------------------------------------- */ 542 | // -- Calculate Mines function 543 | // -- Used to calculate surrounding mines number 544 | // -- @return void 545 | /* ------------------------------------------- */ 546 | 547 | calculateMines: function() { 548 | 549 | var mineCount = 0; 550 | globals.totalMines = 0; 551 | 552 | // Check every square 553 | for(var i = 0; i < globals.squaresX; i++){ 554 | for(var j = 0; j < globals.squaresY; j++){ 555 | 556 | if(globals.mineMap[i][j] === -1){ 557 | 558 | var xArr = [i, i + 1, i - 1], 559 | yArr = [j, j + 1, j - 1]; 560 | 561 | /* 562 | 563 | The loop iterates over the surrounding squares as shown below: 564 | 565 | ------------------------- 566 | | i - 1 | i | i + 1 | 567 | | j - 1 | j - 1 | j - 1 | 568 | ------------------------- 569 | | i - 1 | i | i + 1 | 570 | | j | j | j | 571 | ------------------------- 572 | | i - 1 | i | i + 1 | 573 | | j + 1 | j - 1 | j + 1 | 574 | ------------------------- 575 | */ 576 | 577 | for(var a = 0; a < 3; a++){ 578 | for(var b = 0; b < 3; b++){ 579 | if(util.is('mine', xArr[a], yArr[b])){ 580 | globals.mineMap[xArr[a]][yArr[b]]++; 581 | } 582 | } 583 | } 584 | 585 | globals.totalMines++; 586 | } 587 | } 588 | } 589 | }, 590 | 591 | /* ------------------------------------------- */ 592 | // -- Reveal Mines function 593 | // -- Reveals all the mines and triggers a game 594 | // -- over status 595 | // -- @return void 596 | /* ------------------------------------------- */ 597 | 598 | revealMines: function(mine){ 599 | 600 | // Draw all the mines 601 | for(var i = 0; i < globals.squaresX; i++){ 602 | for(var j = 0; j < globals.squaresY; j++){ 603 | if(globals.mineMap[i][j] === -1){ 604 | globals.context.drawImage(mine, i * defaults.celSize, j * defaults.celSize, defaults.celSize, defaults.celSize); 605 | } 606 | } 607 | } 608 | 609 | // Set game over status 610 | globals.gameover = true; 611 | containers.status.html('Game over :('); 612 | containers.msg.html('Click the reset button to start a new game'); 613 | 614 | // Stops the timer and counts down to a reset of the game 615 | window.clearInterval(globals.clock); 616 | } 617 | }; 618 | 619 | /* =========================================== */ 620 | // --- Scores Functions --- 621 | /* =========================================== */ 622 | 623 | var scores = { 624 | 625 | display: function(){ 626 | 627 | if(typeof Storage !== 'undefined'){ 628 | 629 | //delete localStorage.scores; 630 | 631 | if(typeof localStorage.scores !== 'undefined'){ 632 | 633 | var lScores = JSON.parse(localStorage.scores); 634 | 635 | containers.scoreboard.html('NameMinesSeconds'); 636 | 637 | $.each(lScores, function(){ 638 | containers.scoreboard.append('' + this[0] + '' + this[2] + '' + this[3] + ''); 639 | }); 640 | 641 | }else{ 642 | 643 | containers.scoreboard.html('You have not won any games yet :('); 644 | } 645 | 646 | }else{ 647 | containers.scoreboard.html('Unfortunately, your browser does not support local storage'); 648 | } 649 | 650 | }, 651 | 652 | save: function(){ 653 | 654 | if(typeof Storage !== 'undefined'){ 655 | 656 | var name = prompt('Your score is being stored. Please enter your name','Name'), 657 | score = [name, 'Insane', globals.totalMines, globals.elapsedTime, 10000]; 658 | 659 | var scores = (typeof localStorage.scores !== 'undefined') ? JSON.parse(localStorage.scores) : new Array(); 660 | 661 | scores.push(score); 662 | localStorage.scores = JSON.stringify(scores); 663 | } 664 | } 665 | 666 | }; 667 | 668 | /* =========================================== */ 669 | // --- Animation Functions --- 670 | /* =========================================== */ 671 | 672 | var animation = { 673 | 674 | standardBoard: function(){ 675 | 676 | globals.context.fillStyle = defaults.celColor; 677 | 678 | for(var i = 0; i <= globals.squaresX; i++){ 679 | for(var j = 0; j <= globals.squaresY; j++){ 680 | util.roundRect(i, j); 681 | } 682 | } 683 | }, 684 | 685 | walker: function(){ 686 | // Make sure proper styles are set 687 | globals.context.strokeStyle = defaults.celStroke; 688 | 689 | var x = 0, y = 0; 690 | globals.currentAnimation = setInterval(function(){ 691 | 692 | animation.standardBoard(); 693 | 694 | globals.context.fillStyle = '#f16529'; 695 | util.roundRect(x, y); 696 | 697 | x++; 698 | 699 | if(x === globals.squaresX){ x = 0; y++; } 700 | 701 | if(y === globals.squaresY){ x = 0; y = 0; } 702 | 703 | }, 30); 704 | }, 705 | 706 | topDown: function(){ 707 | // Make sure proper styles are set 708 | globals.context.strokeStyle = defaults.celStroke; 709 | 710 | var y = 0; 711 | globals.currentAnimation = setInterval(function(){ 712 | 713 | animation.standardBoard(); 714 | 715 | globals.context.fillStyle = '#f16529'; 716 | 717 | for(var x = 0; x <= globals.squaresX; x++){ 718 | util.roundRect(x, y); 719 | } 720 | 721 | if(y === globals.squaresY){ 722 | y = 0; 723 | } 724 | 725 | y++; 726 | 727 | }, 50); 728 | }, 729 | 730 | leftRight: function(){ 731 | 732 | globals.context.strokeStyle = defaults.celStroke; 733 | 734 | var x = 0, dir = 0; 735 | globals.currentAnimation = setInterval(function(){ 736 | 737 | animation.standardBoard(); 738 | 739 | globals.context.fillStyle = '#f16529'; 740 | 741 | util.roundRect(x, y); 742 | 743 | if(dir === 0 && x === globals.squaresX){ 744 | dir = 1; 745 | }else if(dir === 1 && x === -1){ 746 | dir = 0; 747 | } 748 | 749 | if(dir === 0){ 750 | x++; 751 | }else if(dir === 1){ 752 | x--; 753 | } 754 | 755 | }, 50); 756 | }, 757 | 758 | arrow: function(){ 759 | 760 | var longArrow = [ 761 | [5, 9], [5, 10], [5, 11], 762 | [6, 9], [6, 10], [6, 11], 763 | [7, 9], [7, 10], [7, 11], 764 | [8, 9], [8, 10], [8, 11], 765 | [9, 9], [9, 10], [9, 11], 766 | [10, 9], [10, 10], [10, 11], 767 | [11, 9], [11, 10], [11, 11], 768 | 769 | [12, 8], [12, 9], [12, 10], [12, 11], [12, 12], 770 | [13, 9], [13, 10], [13, 11], 771 | [14, 10] 772 | ], 773 | shortArrow = [ 774 | [5, 9], [5, 10], [5, 11], 775 | [6, 9], [6, 10], [6, 11], 776 | [7, 9], [7, 10], [7, 11], 777 | [8, 9], [8, 10], [8, 11], 778 | [9, 9], [9, 10], [9, 11], 779 | [10, 9], [10, 10], [10, 11], 780 | 781 | [11, 8], [11, 9], [11, 10], [11, 11], [11, 12], 782 | [12, 9], [12, 10], [12, 11], 783 | [13, 10] 784 | ], 785 | x = 0, 786 | arrow = shortArrow; 787 | 788 | globals.currentAnimation = setInterval(function(){ 789 | 790 | animation.standardBoard(); 791 | 792 | globals.context.fillStyle = '#f16529'; 793 | 794 | for(var i = 0; i <= arrow.length; i++){ 795 | if(arrow[i] !== undefined){ 796 | util.roundRect(arrow[i][0] * defaults.celSize, arrow[i][1] * defaults.celSize, defaults.celSize - 1, defaults.celSize - 1); 797 | } 798 | } 799 | 800 | if(x % 2 === 0){ 801 | arrow = longArrow; 802 | }else{ 803 | arrow = shortArrow; 804 | } 805 | 806 | x++; 807 | 808 | }, 200); 809 | } 810 | 811 | }; 812 | 813 | /* =========================================== */ 814 | // --- Utility Functions --- 815 | /* =========================================== */ 816 | 817 | var util = { 818 | 819 | /* ------------------------------------------- */ 820 | // -- Rounded Rectangle function 821 | // -- Draws rounded rectangles 822 | /* ------------------------------------------- */ 823 | 824 | roundRect: function(x, y){ 825 | 826 | var width = defaults.celSize - 1, 827 | height = defaults.celSize - 1, 828 | x = x * defaults.celSize, 829 | y = y * defaults.celSize; 830 | 831 | globals.context.beginPath(); 832 | globals.context.moveTo(x + defaults.celRadius, y); 833 | globals.context.lineTo(x + width - defaults.celRadius, y); 834 | globals.context.quadraticCurveTo(x + width, y, x + width, y + defaults.celRadius); 835 | globals.context.lineTo(x + width, y + height - defaults.celRadius); 836 | globals.context.quadraticCurveTo(x + width, y + height, x + width - defaults.celRadius, y + height); 837 | globals.context.lineTo(x + defaults.celRadius, y + height); 838 | globals.context.quadraticCurveTo(x, y + height, x, y + height - defaults.celRadius); 839 | globals.context.lineTo(x, y + defaults.celRadius); 840 | globals.context.quadraticCurveTo(x, y, x + defaults.celRadius, y); 841 | globals.context.closePath(); 842 | globals.context.stroke(); 843 | globals.context.fill(); 844 | }, 845 | 846 | /* ------------------------------------------- */ 847 | // -- Switch Screens function 848 | // -- Switch between start and game screen 849 | /* ------------------------------------------- */ 850 | 851 | switchScreens: function(){ 852 | if($('.startscreen').is(':hidden') === false){ 853 | $('.startscreen').fadeToggle(400, 'swing', function(){ 854 | core.reset(); 855 | $('.gamescreen').fadeToggle(); 856 | }); 857 | }else{ 858 | $('.gamescreen').fadeToggle(400, 'swing', function(){ 859 | core.reset(); 860 | defaults.difficulty = 0; 861 | $('.startscreen').fadeToggle(); 862 | }); 863 | } 864 | }, 865 | 866 | is: function(what, x, y){ 867 | var p = { 868 | 'revealed': globals.revealedMap, 869 | 'mine': globals.mineMap, 870 | 'flag': globals.flagMap 871 | }; 872 | 873 | if(typeof p[what][x] !== 'undefined' && typeof p[what][x][y] !== 'undefined' && p[what][x][y] > -1){ 874 | return true; 875 | }else{ 876 | return false; 877 | } 878 | } 879 | }; 880 | 881 | /* =========================================== */ 882 | // --- Initiate Minesweeper --- 883 | /* =========================================== */ 884 | 885 | core.init(); 886 | }); 887 | --------------------------------------------------------------------------------