<!DOCTYPE html> <html> <head> <title>endless mines</title> <style type="text/css"> @font-face { font-family: Helsinki; src: url('helsinki.eot'); src: url('helsinki.eot?#iefix') format('embedded-opentype'), url('helsinki.woff2') format('woff2'), url('helsinki.woff') format('woff'), url('helsinki.ttf') format('truetype'), url('helsinki.svg#helsinkiregular') format('svg'); font-weight: normal; font-style: normal; } * { box-sizing: border-box; font-family: Helsinki; } html { background-color: #272822; margin: 0; height: 10em; /* fallback */ height: 100vh; width: 10em; /* fallback */ width: 100vw; overflow: hidden; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none; } body { height: 10em; /* fallback */ height: 100vh; margin: 0 auto; width: 10em; /* fallback */ width: 100vmin; } #game { background-color: #1b1c17; float: left; margin: 0 auto; height: 100%; overflow: hidden; } #stats { height: 1em; /* fallback */ height: 10vmin; width: 10em; /* fallback */ width: 100vmin; float: left; background: #768087; background: -moz-linear-gradient(top, #768087 0%, #53595e 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #768087), color-stop(100%, #53595e)); background: -webkit-linear-gradient(top, #768087 0%,#53595e 100%); background: -o-linear-gradient(top, #768087 0%,#53595e 100%); background: -ms-linear-gradient(top, #768087 0%,#53595e 100%); background: linear-gradient(to bottom, #768087 0%,#53595e 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#768087', endColorstr='#53595e',GradientType=0 ); } #stats button { margin: 0.1em; /* fallback */ margin: 1vmin; float: right; font-size: 0.3em; /* fallback */ font-size: 3vmin; border-radius: 0.2em; /* fallback */ border-radius: 2vmin; padding: 0.175em; /* fallback */ padding: 1.75vmin; box-shadow: 0.05em 0.05em 0 #000; /* fallback */ box-shadow: 0.5vmin 0.5vmin 0 #000; border: 0.4vmin solid #454e52; position: relative; top: auto; left: auto; } #stats div { float: left; font-size: 0.54em; /* fallback */ font-size: 5.4vmin; margin: 0.2em; /* fallback */ margin: 2vmin; width: auto; } #gameOver, #setup { position: absolute; width: 10em; /* fallback */ width: 100vmin; height: 10em; /* fallback */ height: 100vh; background-color: rgba(0,0,0,0.3); } #setup h2 { margin-top: 0; } #setup h2, #setup label { margin: 0.4em 0.35em; /* fallback */ margin: 4vmin 3.5vmin; font-size: 0.6em; /* fallback */ font-size: 6vmin; color: #fe7ac6; text-shadow: 0.05em 0.05em 0 #a4f4b4; /* fallback */ text-shadow: 0.5vmin 0.5vmin 0 #a4f4b4; /* x y blur-radius colour */ } #setup div.centre { text-align: center; margin-top: 0.2em; /* fallback */ margin-top: 2vmin; } input[type="radio"] { display: none; } /* after checked input */ input:checked + label { border: 0.1em dotted #a4f4b4; /* fallback */ border: 1vmin dotted #a4f4b4; border-radius: 0.2em; /* fallback */ border-radius: 2vmin; padding: 0.1em 0.2em; /* fallback */ padding: 1vmin 2vmin; } button { font-size: 0.6em; /* fallback */ font-size: 6vmin; border-radius: 0.2em; /* fallback */ border-radius: 2vmin; padding: 0.2em; /* fallback */ padding: 2vmin; margin: 0 auto; box-shadow: 0.1em 0.1em 0 #000; /* fallback */ box-shadow: 1vmin 1vmin 0 #000; border: none; background: #768087; background: -moz-linear-gradient(top, #768087 0%, #53595e 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#768087), color-stop(100%,#53595e)); background: -webkit-linear-gradient(top, #768087 0%,#53595e 100%); background: -o-linear-gradient(top, #768087 0%,#53595e 100%); background: -ms-linear-gradient(top, #768087 0%,#53595e 100%); background: linear-gradient(to bottom, #768087 0%,#53595e 100%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#768087', endColorstr='#53595e',GradientType=0 ); } button.cancel { right: 2em; right: 20vmin; left: auto; } h1, h2 { text-align: center; } h1 { color: #a4f4b4; font-size: 1.2em; /* fallback */ font-size: 12vmin; margin-top: 0.6em; /* fallback */ margin-top: 6vmin; margin-bottom: 0.6em; /* fallback */ margin-bottom: 6vmin; text-shadow: 0.1em 0..1em 0 #fe7ac6; /* fallback */ text-shadow: 1vmin 1vmin 0 #fe7ac6; /* x y blur-radius colour */ } ul { float: left; clear: both; list-style-type: none; padding: 0; margin: 0; overflow: hidden; } li { color: white; font-size: 0.8em; /* fallback */ font-size: 8vmin; padding: 0.1em; /* fallback */ padding: 1vmin; padding-top: 0.05em; /* fallback */ padding-top: 0.5vmin; padding-bottom: 0.15em; /* fallback */ padding-bottom: 1.5vmin; text-align: center; vertical-align: middle; float: left; height: 1em; /* fallback */ height: 10vmin; width: 1em; /* fallback */ width: 10vmin; background-color: #454e52; border-radius: 0.2em; /* fallback */ border-radius: 2vmin; cursor: default; } li.revealed { background-color: #1b1c17; } li:not(.revealed):hover { background-color: #a0a9af; } li.flagged { background-color: #b5fe52; } li.flagged:hover { background-color: #c6fe7a; } li.mine.revealed { background-color: #d23000; color: #000; } li.mines1 { color: #c6fe7a; } li.mines2 { color: #7ac5fe; } li.mines3 { color: #fe7ac6; } li.mines4 { color: #b17afe; } li.mines5 { color: #feb27a; } li.mines6 { color: #7afeb2; } li.mines7 { color: #d74600; } li.mines8 { color: #8c4600; } p { color: #fff; font-size: 0.43em; /* fallback */ font-size: 4.3vmin; padding: 0 1em; /* fallback */ padding: 0 10vmin; } strong { color: #fe7ac6; } </style> <script src="jquery-2.1.3.min.js"></script> <script> var gameBoardWidth = 10; var gameBoardHeight = 9; var score = 0; var firstClick = true; var mineChance = 0.2; var clickholdMs = 200; var timeout; //hold timer var mouseHeld = false; var viewportSizingAvailable = ($('#game').height == $(window).height()); // only run once $(document).ready(function() { function drawGameBoard() { $('#game').html(""); //determine aspect ratio so as to fit the screen gameBoardHeight = Math.floor((1 / getAspectRatio()) * 10) - 1; if (gameBoardHeight <= 9) { gameBoardHeight = 9; } for (var i = 0; i < gameBoardHeight; i++) { $('#game').append(newRow()); } } function newTile() { if (Math.random() < mineChance) { return '<li class="mine"></li>'; } else { return '<li></li>'; } } function newRow() { var row = '<ul>'; for (var i = 0; i < gameBoardWidth; i++) { row += newTile(); }; row += '</ul>'; return row; } function saveGame() { if (typeof(Storage) == "undefined") { return; } //variables localStorage.setItem("score", score); localStorage.setItem("mineChance", mineChance); localStorage.setItem("firstClick", firstClick); //game board localStorage.setItem("gameBoard", $('#game').html()); } function loadGame() { //check if storage available and if saved game exists if (typeof(Storage) == "undefined" || localStorage.getItem("gameBoard") == null) { drawGameBoard(); return; } //variables score = parseInt(localStorage.getItem("score")); mineChance = parseFloat(localStorage.getItem("mineChance")); firstClick = (localStorage.getItem("firstClick") == "true"); //game board $('#game').html(localStorage.getItem("gameBoard")); $('#setup').hide(); } $.fn.check = function() { //unclicked tiles if ($(this).filter('li:not(.revealed):not(.flagged)').length > 0) { return false; } //incorrectly flagged tiles if ($(this).filter('li.flagged:not(.mine)').length > 0) { return false; } //clicked mines if ($(this).filter('li.revealed.mine').length > 0) { return false; } return true; } $.fn.checkRow = function() { return $(this).children().check(); } $.fn.checkColumn = function() { return $(this).column().check(); } window.removeClearedRows = function() { var rowsToRemove = $('#game ul:not(.removing)').filter(function() { return $(this).checkRow(); }); if (rowsToRemove.length > 0) { suddenDeath(); } rowsToRemove.each(function() { score += $(this).children('.mine').length; $(this).addClass("removing"); $(this).slideUp("slow", function() { $(this).remove(); //add new row on bottom $('#game').append(newRow()); refreshMineCounts(); //click blank tiles $('li.revealed:not(.mine):empty').mouseup(); }); }); } window.removeClearedColumns = function() { var columnsToRemove = $('ul:not(.removing):eq(0) li:not(.removing)').filter(function() { return $(this).checkColumn(); }); if (columnsToRemove.length > 0) { suddenDeath(); } columnsToRemove.each(function() { score += $(this).column().filter('.mine').length; $(this).column().addClass("removing"); //animation for top row + deletion of column $(this).animate({width: 0, borderRadius: 0, padding: 0}, "slow", function() { $(this).column().remove(); $('ul').each(function() { $(this).append(newTile()); }); refreshMineCounts(); //click blank tiles $('li.revealed:not(.mine, .removing):empty').mouseup(); }); //then just animation for others $(this).column().animate({width: 0, borderRadius: 0, padding: 0}, "slow"); }); } function refreshMineCounts() { $('ul:not(.removing) li.revealed:not(.mine, .removing)').each(function() { var mineCount = $(this).countMinesText(); $(this).text(mineCount); //remove "mines1" etc $(this).attr( 'class', $(this).attr('class').replace( /mines[0-9]/, "mines" + mineCount ) ); $(this).removeClass("mines mines0"); }); } function updateScore() { $('#score').text("Score: " + score); } function isGameOver() { return $('ul').has('.mine.revealed').length == gameBoardHeight && $('ul').first().children().filter(function() { return $(this).column().filter('.mine.revealed').length > 0; }).length == gameBoardWidth; } function checkGameOver() { if (isGameOver()) { $('#gameOver').show(); } } function updateMinesLeft() { //unflagged mines - revealed mines - flagged not-mines $('#mines').text( "Mines left: " + ($('.mine:not(.flagged)').length - $('.mine.revealed').length - $('li:not(.mine).flagged').length) ); } function getAspectRatio() { return $(window).width() / $(window).height(); } function resizeToWindow() { if (!viewportSizingAvailable) { $('html').css('font-size', Math.min($(window).width(), $(window).height()) / 10); } } function isPortrait() { return getAspectRatio() > 1; } $.fn.updateMineCount = function() { $(this).text( $(this).countMinesText ); } $.fn.rowScore = function() { return $(this).children('.mine').length; } $.fn.rowAbove = function() { return $(this).parent('ul').prev(); } $.fn.rowBelow = function() { return $(this).parent('ul').next(); } $.fn.column = function() { var x = $(this).getX(); var column = $(''); $('ul').each(function() { column = column.add($(this).children().eq(x)); }); return column; } window.oneColumnLeft = function() { return $('ul').first().children().filter(function() { return $(this).column().filter('.mine:not(.revealed, .flagged)').length == 0; }).length >= (gameBoardWidth - 1) } window.oneRowLeft = function() { return $('ul').filter(function() { return $(this).children('.mine:not(.revealed, .flagged)').length == 0; }).length >= (gameBoardHeight - 1); } window.isSuddenDeath = function() { return oneRowLeft() && oneColumnLeft(); } window.suddenDeath = function() { if (isSuddenDeath()) { mineChance += 0.01; //don't want the chance to get to 100% because that's completely predictable if (mineChance > 0.8) { mineChance = 0.8; } } } $.fn.getX = function() { return $(this).index(); } $.fn.getY = function() { return $(this).parent('ul').index(); } $.fn.isMine = function() { return $(this).hasClass("mine"); } $.fn.countMinesAdjacent = function() { return $(this).getAdjacentTiles().filter('.mine').length; } $.fn.getAdjacentTiles = function() { var adjacentTiles = $(''); var x = $(this).getX(); var y = $(this).getY(); if (y > 0) { var tileAbove = $(this).rowAbove().children().eq(x); adjacentTiles = adjacentTiles.add(tileAbove.prev()); adjacentTiles = adjacentTiles.add(tileAbove ); adjacentTiles = adjacentTiles.add(tileAbove.next()); } adjacentTiles = adjacentTiles.add($(this).prev()); adjacentTiles = adjacentTiles.add($(this).next()); if (y < (gameBoardHeight - 1)) { var tileBelow = $(this).rowBelow().children().eq(x); adjacentTiles = adjacentTiles.add(tileBelow.prev()); adjacentTiles = adjacentTiles.add(tileBelow ); adjacentTiles = adjacentTiles.add(tileBelow.next()); } return adjacentTiles; } $.fn.countMinesText = function() { return $(this).countMinesAdjacent().toString().replace("0", ""); } $('input[type="radio"]').on("change", function() { switch ($(this).val()) { case "easy": mineChance = 0.13; break; case "normal": mineChance = 0.2; break; case "hard": mineChance = 0.285; break; } }); $('#gameOver button').on("click", function() { drawGameBoard(); $('#gameOver').hide(); $('button.cancel').hide(); $('button.start').removeAttr('style'); $('#setup').show(); }); $('button.reset').on("click", function() { //prompt user with setup screen $('#setup').show(); $('button.cancel').show(); $('button.start').css('left', '20vmin'); }); $('button.cancel').on("click", function() { //prompt user with setup screen $('#setup').hide(); $('button.cancel').hide(); $('button.start').removeAttr('style'); }); $('button.start').on("click", function() { drawGameBoard(); //reset stats firstClick = true; //reset difficulty $('input:checked').change(); score = 0; updateScore(); updateMinesLeft(); $('#setup').hide(); }); $.fn.leftClick = function(automated) { if (!automated) automated = false; //don't want first click to be a mine if (firstClick) { var x = $(this).getX(); x = (x >= 1) ? x : 1; x = (x <= gameBoardWidth - 2) ? x : gameBoardWidth - 2; var y = $(this).getY(); y = (y >= 1) ? y : 1; y = (y <= gameBoardHeight - 2) ? y : gameBoardHeight - 2; $('ul').eq(y - 1).children().slice(x - 1).filter(':lt(3)').removeClass("mine"); $('ul').eq(y ).children().slice(x - 1).filter(':lt(3)').removeClass("mine"); $('ul').eq(y + 1).children().slice(x - 1).filter(':lt(3)').removeClass("mine"); firstClick = false; } if ($(this).hasClass("flagged")) { return; } if ($(this).isMine()) { $(this).addClass("revealed"); $(this).html("☠"); //skull & crossbones //game over, or lose a life, or whatever //... } else if (!automated && parseInt($(this).text()) === $(this).getAdjacentTiles().filter('.flagged, .revealed.mine').length) { //already clicked; use middle click reveal functionality //number of flags matches number of adjacent mines $(this).getAdjacentTiles().filter(':not(.flagged, .revealed)').mouseup(); } else { $(this).addClass("revealed"); $(this).updateMineCount(); $(this).addClass("mines" + $(this).countMinesAdjacent()); //if no mines adjacent, cascade! if ($(this).countMinesAdjacent() == 0) { $(this).getAdjacentTiles().filter(':not(.revealed)').mouseup(); } } firstClick = false; } $.fn.middleClick = function() { //number of flags matches number of adjacent mines if (parseInt($(this).text()) === $(this).getAdjacentTiles().filter('.flagged, .revealed.mine').length) { $(this).getAdjacentTiles().filter(':not(.flagged, .revealed)').mouseup(); } } $.fn.rightClick = function() { if (!$(this).hasClass("revealed")) { $(this).toggleClass("flagged"); } clearTimeout(timeout); } $(document).on("contextmenu", "body", function(event) { event.preventDefault(); }); $(document).on("mousedown touchstart", "li", function(event) { var x = $(this).getX(); var y = $(this).getY(); timeout = setTimeout(function() { $('ul:eq(' + y + ') li:eq(' + x + ')').rightClick(); mouseHeld = true; }, clickholdMs); }); $(document).on("mouseleave", "li", function(event) { clearTimeout(timeout); }); $(document).on("mouseup touchend", "li", function(event) { event.preventDefault(); clearTimeout(timeout); if (mouseHeld) { mouseHeld = false; } else { switch (event.which) { case 3: $(this).rightClick(); break; case 2: $(this).middleClick(); break; case 1: $(this).leftClick(); break; default: $(this).leftClick(true); //automated break; } } removeClearedRows(); removeClearedColumns(); checkGameOver(); updateScore(); updateMinesLeft(); saveGame(); }); $(window).resize(function() { resizeToWindow(); }); resizeToWindow(); //instantiate the game loadGame(); //loadGame will draw the game board if no saved game is found $('#gameOver').hide(); $('button.cancel').hide(); }); </script> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-60523398-1', 'auto'); ga('send', 'pageview'); </script> </head> <body> <div id="stats"> <div id="score">Score: 0</div> <div id="mines">Mines left: 0</div> <button class="reset">reset</button> </div> <div id="game"></div> <div id="gameOver"> <h1>game over</h1> <p>a game by max bradbury</p> <p>inspirations include minesweeper, tetris and 2048</p> <p>tell your friends</p> <button>reset</button> </div> <div id="setup"> <h1>endless mines</h1> <p> <strong>left click</strong> or <strong>tap</strong> to clear a tile. <br> <strong>right click</strong> or <strong>hold</strong> to flag a mine. <br> rows and columns with <strong>exploded mines</strong> cannot be cleared. </p> <h2>Difficulty</h2> <div class="centre"> <input type="radio" name="difficulty" id="difficultyEasy" value="easy"> <label for="difficultyEasy">Easy</label> <input type="radio" name="difficulty" id="difficultyNormal" value="normal" checked> <label for="difficultyNormal">Normal</label> <input type="radio" name="difficulty" id="difficultyHard" value="hard"> <label for="difficultyHard">Hard</label> </div> <div class="centre"> <button class="start">start</button> <button class="cancel">cancel</button> </div> </div> </body> </html>