// Match the SCSS variables here
- rows = 9
- cols = 9

- count = rows * cols

%form
	- count.times do |i|
		%input{:type => "checkbox", :id => "c#{i+1}"}
		%input{:type => "checkbox", :id => "f#{i+1}"}

	%input(type="radio" name="mode" id="modeMine" checked)
	%input(type="radio" name="mode" id="modeFlag")

	.actionSelector
		%label(for="modeMine")
			⛏
		%label(for="modeFlag")
			🚩

	.grid
		- count.times do |i|
			%label{:for => "c#{i+1}"}
		.flags
			- count.times do |i|
				%label{:for => "f#{i+1}"}
		%button.error(type="reset" tabindex="-1")
			Ooohhh 🙁
			%br
			Click to try again
		%button.victory(type="reset" tabindex="-1")
			👌👀✔💯💯💯
			%br
			Click to restart
			
	.infos
		.counter
		.timer
			- 3.times do
				.digit
			.separator
			- 2.times do
				.digit
View Compiled
@import 'https://fonts.googleapis.com/css?family=Roboto+Mono:700';

// medium difficulty (16x16x40) takes a long time to compile and is slow to play :/
$rows: 9;
$cols: 9; // don't forget to make the Haml match
$mines: 10;

$size: 24px;
$colors: #0000ff, #008100, #ff1300, #000083, #810500, #2a9494, #000000, #808080;

$count: $rows * $cols;
@if $mines > $count { @error "More mines than blocks"; }
$pos: ();
@for $i from 0 to $mines {
	$mine: 0;
	$continue: true;	
	@while $continue != null {
		$mine: random($count);
		$continue: index($pos, $mine);
	}
	$pos: append($pos, $mine);
}

body {
	min-height: 100vh;
	// strange margin behaviour thing cancelling
	padding: 1px; box-sizing: border-box;
	background: teal url(https://assets.codepen.io/15664/bliss.jpg) center / cover no-repeat;
	counter-reset: mines $mines;
}

form {
	display: flex;
	flex-flow: column nowrap;
	align-items: center;
}

input { visibility: hidden; position: absolute; top: -99px; left: -99px; }
input[id^="f"]:checked { counter-increment: mines -1; }

.infos {
	order: 2;
	display: flex;
	flex-flow: row nowrap;
	justify-content: space-between;
	width: $cols * $size;
}
.timer {
	font-family: "Roboto Sans", monospace;
	font-size: 0; // Prevent white-space
	background: #ccc;
	border: 1px solid #808080;
	height: 2.25rem; line-height: 2.25rem;
	padding: 0 .5rem;
	.separator {
		display: inline-block;
		vertical-align: middle;
		font-size: 1rem;
		&:before { content: ':'; }
	}
	@keyframes digit {
		from { top: 0; }
		to { top: -1000%; }
	}
	@keyframes digitTo6 {
		from { top: 0; }
		to { top: -600%; }
	}
	@keyframes extend { from { width: 0; } 10%, to { width: auto; } }
	.digit {
		display: inline-block;
		position: relative;
		overflow: hidden;
		vertical-align: middle;
		font-size: 1rem;
		&:before { content: '0'; visibility: hidden; } // Size
		&:after {
			content: '0 \A 1 \A 2 \A 3 \A 4 \A 5 \A 6 \A 7 \A 8 \A 9';
			position: absolute;
			top: 0; left: 0;
			animation: digit 1s steps(10) infinite paused;
		}
		&:nth-last-child(1):after { animation-duration: 10s; }
		&:nth-last-child(2):after {
			content: '0 \A 1 \A 2 \A 3 \A 4 \A 5';
			animation-name: digitTo6;
			animation-timing-function: steps(6);
			animation-duration: 60s;
		}
		&:nth-last-child(4):after { animation-duration: 600s; }
		&:nth-last-child(5):after { animation-duration: 6000s; }
		&:nth-last-child(6) {
			width: 0;
			animation: extend 60000s steps(1) infinite paused;
			&:after { animation-duration: 60000s; }
		}
	}
}
.counter {
	display: inline-block;
	border: 1px solid #808080;
	background: #ccc;
	padding: 0 .5rem;
	font-size: 1.25rem;
	font-family: "Roboto Sans", monospace;
	height: 2.25rem; line-height: 2.25rem;
	&:before { content: '🤔'; font-size: 1rem; margin-right: .5em; }
	&:after {
		content: counter(mines);
	}
}

input[id^="c"]:checked ~ .infos .timer .digit {
	&, &:after {
		animation-play-state: running;
	}
}

.actionSelector {
	order: 1;
	text-align: center;
	margin: 10px;
	cursor: default;
	label {
		display: inline-block;
		position: relative;
		width: 1.8em; height: 1.8em;
		text-align: center; line-height: 1.8em;
		cursor: pointer;
		&:before {
			content: '';
			position: absolute;
			left: 0; top: 0;
			width: 100%; height: 100%;
			transform: scale(0);
			border-radius: 50%;
			background: rgba(210, 210, 210, .8);
			box-sizing: border-box;
			border: 1px solid #808080;
			transition: transform .3s, border-radius .3s;
			transition-timing-function: cubic-bezier(.75,1.75,.75,.75);
			z-index: -1;
		}
	}
}
#modeMine:checked ~ .actionSelector label[for="modeMine"],
#modeFlag:checked ~ .actionSelector label[for="modeFlag"] {
	cursor: default;
	&:before {
		transform: scale(1);
		border-radius: 2px;
	}
}

.grid {
	order: 3;
	user-select: none;
	position: relative;
	margin: 10px auto;
	width: $cols * 1em; height: $rows * 1em;
	font-size: $size;
	display: flex;
	flex-flow: row wrap;
	border: solid #808080;
	border-width: 1px 0 0 1px;
	label {
		display: block;
		position: relative;
		width: 1em; height: 1em;
		background: #c0c0c0;
		box-sizing: border-box;
		border: solid #808080;
		border-width: 0 1px 1px 0;
		flex: 0 0 (100% / $cols);
		overflow: hidden;
		cursor: pointer;
		pointer-events: none;
		&:before {
			content: '';
			font-size: .9rem;
			font-family: 'Roboto Mono', monospace;
			font-weight: bold;
			position: absolute;
			left: 50%; top: 50%;
			transform: translate(-50%, -50%);
		}
		&:after {
			content: '';
			position: absolute;
			left: 0; top: 0;
			width: 100%; height: 100%;
			box-sizing: border-box;
			background: #c0c0c0;
			border: 2px outset #ececec;
			font-size: .75rem;
			text-align: center;
			pointer-events: auto;
		}
		&:active:after {
			background: #bdbdbd;
			border: solid #999;
			border-width: 2px 0 0 2px;
		}
	}
	.flags {
		position: absolute;
		top: 0; left: 0;
		width: 100%; height: 100%;
		display: flex;
		flex-flow: row wrap;
		opacity: 0;
		visibility: hidden;
	}
	.error, .victory {
		position: absolute;
		top: 0; left: 0;
		width: 100%; height: 100%;
		background: rgba(10, 0, 0, .75);
		color: #fff;
		font-family: "Century Gothic",CenturyGothic,AppleGothic,sans-serif;
		border: none;
		opacity: 0;
		visibility: hidden;
		transition: opacity .3s, visibility .3s;
	}
	.victory {
		background: rgba(0, 10, 0, .75);
	}
}

#modeFlag:checked ~ .grid .flags {
	visibility: visible;
}

#modeMine:checked ~ .grid:active ~ .infos .counter:before {
	content: '😓' !important;
}
@function makeFlagCountSelector($n) {
	$sel: ();
	@for $i from 1 through $n {
		$sel: append($sel, "input[id^="f"]:checked ~ ", space);
	}
	@return $sel;
}
#{makeFlagCountSelector(1)} .infos .counter:before { content: '😐'; }
#{makeFlagCountSelector(round($mines / 3))} .infos .counter:before { content: '😏'; }
#{makeFlagCountSelector(round($mines / 2))} .infos .counter:before { content: '🙂'; }
#{makeFlagCountSelector(round($mines * 2 / 3))} .infos .counter:before { content: '😊'; }
#{makeFlagCountSelector(round($mines * 3 / 4))} .infos .counter:before { content: '😃'; }
// almost there
#{makeFlagCountSelector($mines - 1)} .infos .counter:before { content: '🤓'; }
// 0 mines left but no victory screen : uh oh
#{makeFlagCountSelector($mines)} .infos .counter:before { content: '😕'; }
// now you're just not even trying
#{makeFlagCountSelector($mines + 1)} .infos .counter:before { content: '😒'; }
@if round(($count + $mines) / 2) > $mines + 1 {
	#{makeFlagCountSelector(round(($count + $mines) / 2))} .infos .counter:before { content: '😠'; }
}
@if $count > $mines + 1 {
	// I mean ... yeah ...
	#{makeFlagCountSelector($count)} .infos .counter:before { content: '🖕'; }
}

$vals: ();
$victorySelector: ();
@for $i from 1 through $count {
	$val: 0;
	@if index($pos, $i) != null {
		$val: -1;
		.grid label:nth-child(#{$i}):before {
			content: '💣';
			font-size: .75rem;
		}
		#c#{$i}:checked {
			~ .grid {
				.error { opacity: 1; visibility: visible; }
				> label:after { visibility: hidden; }
				label:nth-child(#{$i}) { background-color: #f00; }
				&:active ~ .infos .timer .digit {
					&, &:after {
						animation: none;
					}
				}
			}
			~ .infos {
				.counter:before { content: '😣' !important; }
				.timer .digit {
					&, &:after {
						animation-play-state: paused;
					}
				}
			}
		}
	} @else {
		$x: ($i - 1) % $cols;
		$y: floor(($i - 1) / $cols);
		$neighbours: 0;
		@for $dx from -1 through 1 {
			@for $dy from -1 through 1 {
				$nx: $x + $dx; $ny: $y + $dy;
				@if ($dx != 0 or $dy != 0) and
					$nx >= 0 and $nx < $cols and
					$ny >= 0 and $ny < $rows {
						$ni: $ny * $cols + $nx + 1;
						@if index($pos, $ni) { $neighbours: $neighbours + 1; }
				}
			}
		}
		$val: $neighbours;
		@if $neighbours > 0 {
			.grid label:nth-child(#{$i}):before {
				content: '#{$neighbours}';
				color: nth($colors, $neighbours);
			}
		}
	}
	$vals: append($vals, $val);
	$victorySelector: append($victorySelector, "#f#{$i}" + if($val == -1, ":checked", ":not(:checked)") + " ~", space);
}

#{$victorySelector} .grid {
	> label:after { visibility: hidden; }
	.victory { opacity: 1; visibility: visible; }
	&:active ~ .infos .timer .digit {
		&, &:after {
			animation: none;
		}
	}
}
#{$victorySelector} .infos {
	.counter:before { content: '😎'; }
	.timer .digit {
		&, &:after {
			animation-play-state: paused;
		}
	}
}

$handled: ();
@function uncoverSelector($i, $direct: true) {
	$val: nth($vals, $i);
	$psel: if($direct or $val == 0, ("#c#{$i}:checked"), ());
	$sel: ("label:nth-child(#{$i})");
	@if $val == 0 {
		@if index($handled, $i) != null { @return null; }
		$handled: append($handled, $i) !global;
		$x: ($i - 1) % $cols;
		$y: floor(($i - 1) / $cols);
		@for $dx from -1 through 1 {
			@for $dy from -1 through 1 {
				$nx: $x + $dx; $ny: $y + $dy;
				@if ($dx != 0 or $dy != 0) and
					$nx >= 0 and $nx < $cols and
					$ny >= 0 and $ny < $rows {
						$result: uncoverSelector($ny * $cols + $nx + 1, false);
						// $result: null;
						@if $result != null {
							$psel: join($psel, nth($result, 1), comma);
							$sel: join($sel, nth($result, 2), comma);
						}
				}
			}
		}
	}
	@return ($psel, $sel);
}
@for $i from 1 through $count {
	#f#{$i}:checked {
		~ .grid label:nth-child(#{$i}):after { content: '🚩'; pointer-events:none; visibility: visible !important; }
		~ #modeFlag:checked ~ .grid .flags label:nth-child(#{$i}):after { pointer-events: auto; }
	}
	$result: uncoverSelector($i);
	@if $result != null {
		// using @each splits the selector into smaller blocks
		// (if a selector is too long it can break in some browsers)
		@each $psel in nth($result, 1) {
			#{$psel} ~ .grid {
				#{nth($result, 2)} {
					&:after {
						pointer-events: none;
						visibility: hidden;
					}
				}
			}
		}
	}
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.