<!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("&#9760;"); //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>