// 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.