├── CNAME
├── README.md
├── bombsweeper.appcache
├── css
    └── main.css
├── img
    ├── twitter.jpg
    └── twitter.png
├── index.html
└── js
    ├── cell.js
    ├── data.js
    ├── el.js
    ├── game.js
    ├── main.js
    └── table.js
/CNAME:
--------------------------------------------------------------------------------
1 | bombsweeper.js.org
2 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bombsweeper
2 | VanillaJS bombsweeper (minesweeper)! Works in mobile and offline as well.
3 | 
4 | https://bombsweeper.js.org
5 | 
--------------------------------------------------------------------------------
/bombsweeper.appcache:
--------------------------------------------------------------------------------
 1 | CACHE MANIFEST
 2 | # 2016-08-22 22:52:00
 3 | 
 4 | CACHE:
 5 | index.html
 6 | css/main.css
 7 | js/el.js
 8 | js/data.js
 9 | js/table.js
10 | js/cell.js
11 | js/game.js
12 | js/main.js
13 | 
14 | NETWORK:
15 | *
16 | 
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
  1 | 
  2 | * {
  3 |   box-sizing: inherit;
  4 |   margin: 0;
  5 |   padding: 0;
  6 | }
  7 | 
  8 | html, body {
  9 |   height: 100%;
 10 | }
 11 | 
 12 | body {
 13 |   background-color: #ddd;
 14 |   font-family: sans-serif;
 15 |   box-sizing: border-box;
 16 |   color: #444;
 17 |   tap-highlight-color: rgba(0, 0, 0, 0);
 18 |   -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
 19 |   -moz-tap-highlight-color: rgba(0, 0, 0, 0);
 20 |   -ms-tap-highlight-color: rgba(0, 0, 0, 0);
 21 |   -o-tap-highlight-color: rgba(0, 0, 0, 0);
 22 |   user-select: none;
 23 |   -webkit-user-select: none;
 24 |   -moz-user-select: none;
 25 |   -ms-user-select: none;
 26 |   -o-user-select: none;
 27 | }
 28 | 
 29 | .game {
 30 |   position: absolute;
 31 |   top: 50%;
 32 |   left: 50%;
 33 |   transform: translate(-50%, -50%);
 34 |   -webkit-transform: translate(-50%, -50%);
 35 |   -moz-transform: translate(-50%, -50%);
 36 |   -ms-transform: translate(-50%, -50%);
 37 |   -o-transform: translate(-50%, -50%);
 38 | }
 39 | 
 40 | .game .container {
 41 |   max-width: calc(100vw - 3rem);
 42 |   max-height: calc(100vh - 6rem);
 43 |   overflow: auto;
 44 |   background-color: #eee;
 45 |   outline: .25rem solid #fff;
 46 |   box-shadow: 0 1.5rem 5rem -1rem rgba(0, 0, 0, .3);
 47 |   -webkit-box-shadow: 0 1.5rem 5rem -1rem rgba(0, 0, 0, .3);
 48 |   -moz-box-shadow: 0 1.5rem 5rem -1rem rgba(0, 0, 0, .3);
 49 |   -ms-box-shadow: 0 1.5rem 5rem -1rem rgba(0, 0, 0, .3);
 50 |   -o-box-shadow: 0 1.5rem 5rem -1rem rgba(0, 0, 0, .3);
 51 | }
 52 | 
 53 | .game .score {
 54 |   font-size: .75rem;
 55 |   height: 3rem;
 56 |   line-height: 3rem;
 57 | }
 58 | 
 59 | .game .newgame {
 60 |   font-size: .75rem;
 61 |   height: 3rem;
 62 |   line-height: 3rem;
 63 |   text-transform: uppercase;
 64 |   font-weight: 600;
 65 |   text-align: center;
 66 | }
 67 | 
 68 | .game .newgame a {
 69 |   padding: 0 .5rem;
 70 |   text-decoration: none;
 71 | }
 72 | 
 73 | .game .newgame a:hover {
 74 |   text-decoration: underline;
 75 | }
 76 | 
 77 | table {
 78 |   border-collapse: collapse;
 79 |   border-spacing: 0;
 80 |   table-layout: fixed;
 81 | }
 82 | 
 83 | td {
 84 |   box-shadow: inset -1px -1px 0 rgba(0, 0, 0, .15), inset 1px 1px 0 #fff;
 85 |   -webkit-box-shadow: inset -1px -1px 0 rgba(0, 0, 0, .15), inset 1px 1px 0 #fff;
 86 |   -moz-box-shadow: inset -1px -1px 0 rgba(0, 0, 0, .15), inset 1px 1px 0 #fff;
 87 |   -ms-box-shadow: inset -1px -1px 0 rgba(0, 0, 0, .15), inset 1px 1px 0 #fff;
 88 |   -o-box-shadow: inset -1px -1px 0 rgba(0, 0, 0, .15), inset 1px 1px 0 #fff;
 89 |   text-align: center;
 90 |   margin: 0;
 91 |   padding: 0;
 92 | }
 93 | 
 94 | td > p {
 95 |   display: block;
 96 |   width: 1rem;
 97 |   height: 1rem;
 98 |   font-size: .75rem;
 99 |   line-height: 1rem;
100 |   margin: 0;
101 |   padding: 0;
102 |   overflow: hidden;
103 | }
104 | 
105 | td:not(.opened):not(.gameover) {
106 |   cursor: pointer;
107 | }
108 | 
109 | td.opened {
110 |   box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
111 |   -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
112 |   -moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
113 |   -ms-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
114 |   -o-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
115 |   background-color: #e5e5e5;
116 | }
117 | 
118 | td.bomb {
119 |   content: '💣';
120 | }
121 | 
122 | .source {
123 |   padding: .5rem;
124 | }
125 | 
--------------------------------------------------------------------------------
/img/twitter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pakastin/bombsweeper/b793c3ee53674032b72c07e5269a4943e8a65826/img/twitter.jpg
--------------------------------------------------------------------------------
/img/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pakastin/bombsweeper/b793c3ee53674032b72c07e5269a4943e8a65826/img/twitter.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     
11 |     
12 |     
13 |     
14 |     
15 |     Bombsweeper!
16 |   
17 |   
18 |     Source
19 |     
20 |     
21 |     
22 |     
23 |     
24 |     
25 |     
34 |   
35 | 
36 | 
--------------------------------------------------------------------------------
/js/cell.js:
--------------------------------------------------------------------------------
 1 | 
 2 | function createCell (cell) {
 3 |   var $cell = el('td');
 4 |   var $p = el('p');
 5 | 
 6 |   $cell.appendChild($p);
 7 | 
 8 |   $cell.oncontextmenu = function (e) {
 9 |     e.cell = cell;
10 |   }
11 | 
12 |   $cell.onclick = function (e) {
13 |     e.cell = cell;
14 |   }
15 | 
16 |   return $cell;
17 | }
18 | 
--------------------------------------------------------------------------------
/js/data.js:
--------------------------------------------------------------------------------
  1 | 
  2 | function generateBombs (ROWS, COLS, BOMBS) {
  3 |   var bombs = new Array(ROWS * COLS);
  4 |   var grid = new Array(ROWS);
  5 | 
  6 |   for (var i = 0; i < bombs.length; i++) {
  7 |     if (i < BOMBS) {
  8 |       bombs[i] = 1;
  9 |     } else {
 10 |       bombs[i] = 0;
 11 |     }
 12 |   }
 13 | 
 14 |   randomize(bombs);
 15 | 
 16 |   return bombs;
 17 | }
 18 | 
 19 | function generateGrid (ROWS, COLS, bombs) {
 20 |   var grid = new Array(ROWS);
 21 |   var cells = [];
 22 |   var i = 0;
 23 | 
 24 |   for (var y = 0; y < grid.length; y++) {
 25 |     var row = grid[y] = new Array(COLS);
 26 | 
 27 |     for (var x = 0; x < row.length; x++) {
 28 |       var isBomb = bombs[y * COLS + x];
 29 | 
 30 |       var cell = row[x] = {
 31 |         i: i,
 32 |         x: x,
 33 |         y: y,
 34 |         isBomb: isBomb,
 35 |         opened: false,
 36 |         flagged: false,
 37 |         neighbours: null,
 38 |         nearbyBombs: 0,
 39 | 
 40 |         flag: function () {
 41 |           if (this.opened || this.exploded) {
 42 |             return;
 43 |           }
 44 |           this.flagged = !this.flagged;
 45 |         },
 46 | 
 47 |         open: function () {
 48 |           if (this.opened || this.exploded) {
 49 |             return;
 50 |           }
 51 |           if (this.isBomb) {
 52 |             this.flagged = false;
 53 |             this.exploded = true;
 54 |             explodeAll();
 55 |           } else {
 56 |             this.opened = true;
 57 | 
 58 |             if (!this.nearbyBombs) {
 59 |               this.neighbours.forEach(function (neighbour) {
 60 |                 if (!neighbour) {
 61 |                   return;
 62 |                 }
 63 |                 if (neighbour.exploded || neighbour.opened || neighbour.flagged) {
 64 |                   return;
 65 |                 }
 66 |                 neighbour.open();
 67 |               });
 68 |             }
 69 |           }
 70 |         }
 71 |       };
 72 | 
 73 |       cells.push(cell);
 74 |       i++;
 75 |     }
 76 |   }
 77 | 
 78 |   cells.forEach(function (cell) {
 79 |     var x = cell.x;
 80 |     var y = cell.y;
 81 |     var i = cell.i;
 82 | 
 83 |     var neighbours = cell.neighbours = [
 84 |       grid[y][x - 1], // left
 85 |       grid[y - 1] && grid[y - 1][x - 1], // topleft
 86 |       grid[y - 1] && grid[y - 1][x], // top
 87 |       grid[y - 1] && grid[y - 1][x + 1], // topright
 88 |       grid[y][x + 1], // right
 89 |       grid[y + 1] && grid[y + 1][x + 1], // bottomright
 90 |       grid[y + 1] && grid[y + 1][x], // bottom
 91 |       grid[y + 1] && grid[y + 1][x - 1] // bottomleft
 92 |     ];
 93 | 
 94 |     cell.nearbyBombs = neighbours.reduce(function (sum, neighbour) {
 95 |       if (!neighbour) {
 96 |         return sum;
 97 |       }
 98 |       return sum + neighbour.isBomb;
 99 |     }, 0);
100 |   });
101 | 
102 |   return grid;
103 | 
104 |   function explodeAll () {
105 |     cells.forEach(function (cell) {
106 |       if (cell.opened || cell.exploded) {
107 |         return;
108 |       }
109 |       if (cell.isBomb) {
110 |         cell.exploded = true;
111 |       }
112 |     });
113 |   }
114 | }
115 | 
116 | function randomize (array) {
117 |   var rnd, temp
118 | 
119 |   for (var i = array.length - 1; i; i--) {
120 |     rnd = Math.random() * i | 0
121 |     temp = array[i]
122 |     array[i] = array[rnd]
123 |     array[rnd] = temp
124 |   }
125 | 
126 |   return array
127 | }
128 | 
--------------------------------------------------------------------------------
/js/el.js:
--------------------------------------------------------------------------------
 1 | function el (tagName) {
 2 |   var el = document.createElement(tagName);
 3 | 
 4 |   for (var i = 1; i < arguments.length; i++) {
 5 |     var arg = arguments[i];
 6 | 
 7 |     if (arg instanceof Node) {
 8 |       el.appendChild(arg);
 9 |     } else {
10 |       for (var key in arg) {
11 |         if (key === 'style') {
12 |           var style = arg.style;
13 | 
14 |           for (var styleKey in style) {
15 |             el.style[styleKey] = style[styleKey];
16 |           }
17 |         } else {
18 |           el.setAttribute(key, arg[key]);
19 |         }
20 |       }
21 |     }
22 |   }
23 | 
24 |   return el;
25 | }
26 | 
--------------------------------------------------------------------------------
/js/game.js:
--------------------------------------------------------------------------------
  1 | 
  2 | function createGame (grid, cells) {
  3 |   var $game = el('div', { class: 'game' });
  4 |   var $container = el('div', { class: 'container' });
  5 |   var $score = el('p', { class: 'score' });
  6 |   var $scoreleft = el('span', { style: { float: 'left' } });
  7 |   var $scoreright = el('span', { style: { float: 'right' } });
  8 |   var $newgame = el('div', { class: 'newgame' });
  9 |   var $levels = el('span');
 10 |   var $table = createTable(grid);
 11 |   var $cells = Array.prototype.slice.call($table.querySelectorAll('td'));
 12 |   var $ps = $table.querySelectorAll('td > p');
 13 | 
 14 |   $newgame.innerHTML = '💣💣💣💣💣💣';
 15 | 
 16 |   $score.appendChild($scoreleft);
 17 |   $score.appendChild($scoreright);
 18 | 
 19 |   $game.appendChild($score);
 20 |   $game.appendChild($container);
 21 |   $game.appendChild($newgame);
 22 | 
 23 |   $container.appendChild($table);
 24 | 
 25 |   $game.oncontextmenu = function (e) {
 26 |     var cell = e.cell;
 27 | 
 28 |     if (!cell) {
 29 |       return;
 30 |     }
 31 |     e.preventDefault();
 32 | 
 33 |     cell.flag();
 34 |     update();
 35 |   }
 36 | 
 37 |   $game.onclick = function (e) {
 38 |     var cell = e.cell;
 39 | 
 40 |     if (!cell) {
 41 |       return;
 42 |     }
 43 | 
 44 |     cell.open();
 45 |     update();
 46 |   }
 47 | 
 48 |   update();
 49 | 
 50 |   return $game;
 51 | 
 52 |   function update () {
 53 |     var win = false;
 54 |     var gameover = false;
 55 |     var points = 0;
 56 |     var bombs = 0;
 57 |     var openable = 0;
 58 | 
 59 |     cells.forEach(function (cell) {
 60 |       var i = cell.i;
 61 |       var $cell = $cells[i];
 62 |       var $p = $ps[i];
 63 | 
 64 |       if (cell.exploded) {
 65 |         gameover = true;
 66 |         $cell.classList.add('opened');
 67 |         if (cell.flagged) {
 68 |           points++;
 69 |           $p.textContent = '🏴';
 70 |         } else {
 71 |           $p.textContent = '💣';
 72 |         }
 73 |       } else if (cell.opened) {
 74 |         points++;
 75 |         $cell.classList.add('opened');
 76 |         if (cell.nearbyBombs) {
 77 |           $p.textContent = cell.nearbyBombs;
 78 |         } else {
 79 |           $p.textContent = '';
 80 |         }
 81 |       } else if (cell.flagged) {
 82 |         $p.textContent = '🏳';
 83 |         if (!cell.isBomb) {
 84 |           bombs--;
 85 |         }
 86 |       } else if (cell.isBomb) {
 87 |         $p.textContent = '';
 88 |         bombs++;
 89 |         openable++;
 90 |       } else {
 91 |         $p.textContent = '';
 92 |         openable++;
 93 |       }
 94 |     });
 95 | 
 96 |     $scoreleft.textContent = 'Bombs: ' + bombs;
 97 |     $scoreright.textContent = 'Score: ' + points;
 98 | 
 99 |     if (openable === bombs) {
100 |       $scoreleft.textContent = 'WIN!';
101 |       $game.oncontextmenu = null;
102 |       $game.onclick = null;
103 |       $cells.forEach(function ($cell) {
104 |         $cell.classList.add('gameover');
105 |       });
106 |     } else if (gameover) {
107 |       $cells.forEach(function ($cell) {
108 |         $cell.classList.add('gameover');
109 |       });
110 |       $game.oncontextmenu = null;
111 |       $game.onclick = null;
112 |       $scoreleft.textContent = 'GAME OVER';
113 |     }
114 |   }}
115 | 
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
 1 | 
 2 | var slice = Array.prototype.slice;
 3 | 
 4 | start();
 5 | 
 6 | window.addEventListener('hashchange', function () {
 7 |   start();
 8 | });
 9 | 
10 | function start () {
11 |   var $game = slice.call(document.body.querySelectorAll('.game'));
12 |   $game.forEach(function ($game) {
13 |     document.body.removeChild($game);
14 |   });
15 |   var hash = location.hash.slice(1).split(',');
16 |   var ROWS = parseInt(hash[0]) || 10;
17 |   var COLS = parseInt(hash[1]) || 10;
18 |   var BOMBS = parseInt(hash[2]) || 10;
19 | 
20 |   var bombs = generateBombs(ROWS, COLS, BOMBS);
21 |   var grid = generateGrid(ROWS, COLS, bombs);
22 |   var cells = flatten(grid);
23 |   var $game = createGame(grid, cells);
24 | 
25 |   document.body.appendChild($game);
26 | }
27 | 
28 | function flatten (grid) {
29 |   var cells = [];
30 | 
31 |   for (var y = 0; y < grid.length; y++) {
32 |     var row = grid[y];
33 | 
34 |     for (var x = 0; x < row.length; x++) {
35 |       cells.push(row[x]);
36 |     }
37 |   }
38 | 
39 |   return cells;
40 | }
41 | 
--------------------------------------------------------------------------------
/js/table.js:
--------------------------------------------------------------------------------
 1 | 
 2 | function createTable (grid) {
 3 |   var $table = el('table');
 4 | 
 5 |   for (var y = 0; y < grid.length; y++) {
 6 |     row = grid[y];
 7 |     $row = document.createElement('tr');
 8 | 
 9 |     for (var x = 0; x < row.length; x++) {
10 |       var cell = grid[y][x];
11 |       var $cell = createCell(cell);
12 | 
13 |       $row.appendChild($cell);
14 |     }
15 |     $table.appendChild($row);
16 |   }
17 | 
18 |   return $table;
19 | }
20 | 
--------------------------------------------------------------------------------