├── 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 | --------------------------------------------------------------------------------