Skip to content

Instantly share code, notes, and snippets.

@jerrylow
Created January 29, 2018 17:52
Show Gist options
  • Save jerrylow/f0f39cc3d1036515e4f23944516d5f2a to your computer and use it in GitHub Desktop.
Save jerrylow/f0f39cc3d1036515e4f23944516d5f2a to your computer and use it in GitHub Desktop.
Pure CSS Game Stacker
@import url('https://fonts.googleapis.com/css?family=VT323');
$block-gap: 8px;
$block-size: 25px;
$blocks-per-row: 7;
$blocks-color: #233f5a;
$blocks-active: #fabc7f;
$blocks-bg: #172031;
$speed: 2.5s;
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
align-items: center;
background: $blocks-bg;
background-attachment: fixed;
display: flex;
flex-direction: column;
font-family: 'VT323', monospace;
justify-content: center;
}
h1 {
color: white;
font-size: 30px !important;
letter-spacing: 0.05em;
margin: 20px auto 0 !important;
text-align: center;
text-transform: uppercase;
}
form {
border: 6px solid #2f5d83;
padding: 25px 20px 80px;
position: relative;
}
input[type=radio] {
appearance: none;
font-size: 9px;
left: -10px;
opacity: 0.01;
position: fixed;
top: -10px;
}
@keyframes blockExplode {
0% {
background: $blocks-active;
box-shadow: none;
}
40% {
box-shadow: 0 0 5px lighten($blocks-active, 20%);
}
50% {
background: white;
box-shadow: 0 0 30px lighten($blocks-active, 20%);
}
60% {
background: $blocks-color;
box-shadow: none;
}
100% {
background: $blocks-color;
box-shadow: none;
}
}
@mixin blockInactive () {
animation-name: blockExplode;
animation-duration: .75s;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@mixin blockActive () {
background: $blocks-active;
}
.bs {
background: $blocks-bg;
display: flex;
flex-direction: column-reverse;
height: ($block-size + $block-gap) * 12;
position: relative;
width: ($block-size + $block-gap) * $blocks-per-row;
.r {
display: flex;
margin: 0;
width: 100%;
&:nth-of-type(8) .b {
background: lighten($blocks-color, 8%);
}
&:nth-of-type(12) .b {
background: lighten($blocks-color, 15%);
}
}
.b {
background: $blocks-color;
height: $block-size;
margin: 0 $block-gap / 2 $block-gap;
width: $block-size;
}
}
/**
* Global Functions
**/
@function rowName ($number) {
@if $number == 1 {
@return 'on';
}
@elseif $number == 2 {
@return 'tw';
}
@elseif $number == 3 {
@return 'thr';
}
@elseif $number == 4 {
@return 'fr';
}
@elseif $number == 5 {
@return 'fv';
}
@elseif $number == 6 {
@return 'sx';
}
@elseif $number == 7 {
@return 'svn';
}
@elseif $number == 8 {
@return 'ght';
}
@elseif $number == 9 {
@return 'nn';
}
@elseif $number == 10 {
@return 'tn';
}
@elseif $number == 11 {
@return 'lvn';
}
@elseif $number == 12 {
@return 'twlv';
}
}
@function rowBlocks ($number) {
@if $number == 1 or $number == 2 or $number == 3 {
@return 3;
}
@elseif $number == 4 or $number == 5 or $number == 6 {
@return 2;
}
@else {
@return 1;
}
}
@function rowMovements ($rowBlocks) {
@if $rowBlocks == 3 {
@return 5;
}
@elseif $rowBlocks == 2 {
@return 6;
}
@else {
@return 7;
}
}
@function rowSpeed ($row) {
@if $row > 10 {
@return $speed - 1.5s;
}
@elseif $row > 8 {
@return $speed - 1.25s;
}
@elseif $row > 5 {
@return $speed - 1s;
}
@elseif $row > 3 {
@return $speed - .5s;
}
@else {
@return $speed;
}
}
/**
* Light Up
**/
@mixin lightUpSelector ($rowName, $row, $activeNumber, $activeBlocks) {
$selector: '##{$rowName}-#{$activeBlocks}-#{$activeNumber}:checked ~ .bs .r:nth-of-type(#{$row}) .b:nth-of-type(#{$activeNumber})';
@if $activeBlocks >= 2 {
$baseSelector: $selector;
$selector: $selector + ', ' + $baseSelector + ' + .b';
@if $activeBlocks == 3 {
$selector: $selector + ', ' + $baseSelector + ' + .b + .b';
}
}
#{$selector} {
@content;
}
};
@mixin targetRemove ($number) {
@if $number == 1 {
width: $block-size * 2 + $block-gap;
}
@elseif $number == 2 {
width: $block-size;
}
}
@mixin targetAnimations ($row, $blocks) {
$targetBottom: (($row - 1) * ($block-size + $block-gap)) + $block-gap;
$rowMovements: rowMovements($blocks);
$totalMovements: 2 * ($rowMovements - 1);
$increments: 100 / $totalMovements;
@keyframes target-#{$row}-#{$blocks} {
0% {
bottom: $targetBottom;
transform: translateX(0px);
@include targetRemove(3 - $blocks);
}
@for $i from 1 through $totalMovements {
$currentIncrement: $i * $increments;
$previousShift: ($i - 1) * ($block-size + $block-gap);
$currentShift: $i * ($block-size + $block-gap);
@if ($i > ($totalMovements / 2)) {
$previousShift: ($totalMovements - ($i - 1)) * ($block-size + $block-gap);
$currentShift: ($totalMovements - $i) * ($block-size + $block-gap);
}
#{$currentIncrement - 0.1}% {
transform: translateX($previousShift);
}
#{$currentIncrement}% {
transform: translateX($currentShift);
@if $i == $totalMovements {
bottom: $targetBottom;
@include targetRemove(3 - $blocks);
}
}
}
}
}
@mixin buildElements ($row, $rowBlocks) {
$rowName: rowName($row);
$rowMovements: rowMovements($rowBlocks);
@for $i from 1 through $rowBlocks {
@include targetAnimations($row, $i);
@for $j from 1 through $rowMovements {
@include lightUpSelector($rowName, $row, $j, $i) {
@include blockActive;
}
}
}
}
// Build Elements
@for $i from 1 through 12 {
$rowBlocks: rowBlocks($i);
@include buildElements ($i, $rowBlocks);
}
/**
* Line
**/
.line {
animation-name: target-1-3;
animation-duration: $speed;
animation-iteration-count: infinite;
bottom: $block-gap;
display: flex;
left: ($block-gap / 2);
overflow: hidden;
position: absolute;
background: $blocks-active;
height: $block-size;
margin-right: $block-gap;
width: ($block-size + $block-gap) * 2 + $block-size;
&:before,
&:after {
background: $blocks-active;
border-left: $block-gap solid $blocks-bg;
content: '';
display: block;
flex-shrink: 0;
height: $block-size;
width: $block-size;
}
&:before {
margin-left: #{$block-size};
}
}
@mixin nextTurn ($selector, $row, $blocks) {
$nextRow: $row + 1;
$nextRowBlocks: rowBlocks($nextRow);
@if $nextRowBlocks > $blocks {
$nextRowBlocks: $blocks;
}
$nextRowSpeed: rowSpeed($row);
$important: '';
@if $nextRow > 8 {
$important: ' !important';
}
#{$selector} ~ .bs .line,
#{$selector} ~ .controls .rs {
animation-duration: $nextRowSpeed #{$important};
animation-name: target-#{$nextRow}-#{$nextRowBlocks} #{$important};
}
$hideSelector: '#{$selector} ~ .controls div[class*="r-#{$row}-"]';
@if $nextRowBlocks >= 2 {
$hideSelector: $hideSelector + ', #{$selector} ~ .controls div[class*="r-#{$nextRow}-1"]';
@if $nextRowBlocks > 2 {
$hideSelector: $hideSelector + ', #{$selector} ~ .controls div[class*="r-#{$nextRow}-2"]';
}
}
#{$hideSelector} {
display: none;
}
}
/**
* Game Logic
**/
@mixin gameLogicRemove ($selector, $row, $number, $direction: 'before', $remove: 1) {
$removeSelector: '#{$selector} ~ .bs .r:nth-of-type(#{$row}) .b:nth-of-type(#{$number})';
@if $remove == 2 {
$secondNumber: $number - 1;
@if $direction == 'after' {
$secondNumber: $number + 1;
}
$removeSelector: $removeSelector + ', #{$selector} ~ .bs .r:nth-of-type(#{$row}) .b:nth-of-type(#{$secondNumber})';
}
#{$removeSelector} {
@include blockInactive;
}
}
@mixin gameLogicOver ($selector) {
#{$selector} ~ .results .go {
display: flex;
}
#{$selector} ~ .bs .line {
display: none;
}
}
@function gameLogicCheck ($prevBlocks, $nextBlocks, $prevPosition, $nextPosition) {
$prevLastPosition: ($prevPosition - 1) + $prevBlocks;
$nextLastPosition: ($nextPosition - 1) + $nextBlocks;
@if $nextLastPosition < $prevPosition or $nextPosition > $prevLastPosition {
@return 'out';
}
@elseif $nextPosition == ($prevPosition - 1) {
@return '1-before';
}
@elseif $nextPosition == ($prevPosition - 2) {
@return '2-before';
}
@elseif $nextLastPosition == ($prevLastPosition + 1) {
@return '1-after';
}
@elseif $nextLastPosition == ($prevLastPosition + 2) {
@return '2-after';
}
@else {
@return 'stack';
}
}
@mixin gameLogic($prevSelector, $blocks, $row, $position) {
@if $row < 7 {
$nextRow: $row + 1;
$nextRowName: rowName($nextRow);
$nextRowBlocks: rowBlocks($nextRow);
@if $nextRowBlocks > $blocks {
$nextRowBlocks: $blocks;
}
$nextRowMovements: rowMovements($nextRowBlocks);
@for $j from 1 through $nextRowMovements {
$combinedSelector: '#{$prevSelector} ~ ##{$nextRowName}-#{$nextRowBlocks}-#{$j}:checked';
$status: gameLogicCheck($blocks, $nextRowBlocks, $position, $j);
@if $status == '1-before' {
@include gameLogicRemove($combinedSelector, $nextRow, $j);
@include nextTurn($combinedSelector, $nextRow, $nextRowBlocks - 1);
@include gameLogic($combinedSelector, $nextRowBlocks - 1, $nextRow, $j + 1);
}
@elseif $status == '2-before' {
@include gameLogicRemove($combinedSelector, $nextRow, ($j + 1), 'before', 2);
@include nextTurn($combinedSelector, $nextRow, $nextRowBlocks - 2);
@include gameLogic($combinedSelector, $nextRowBlocks - 2, $nextRow, $j + 2);
}
@elseif $status == '1-after' {
@include gameLogicRemove($combinedSelector, $nextRow, ($j - 1 + $nextRowBlocks), 'after');
@include nextTurn($combinedSelector, $nextRow, $nextRowBlocks - 1);
@include gameLogic($combinedSelector, $nextRowBlocks - 1, $nextRow, $j);
}
@elseif $status == '2-after' {
@include gameLogicRemove($combinedSelector, $nextRow, ($j - 2 + $nextRowBlocks), 'after', 2);
@include nextTurn($combinedSelector, $nextRow, $nextRowBlocks - 2);
@include gameLogic($combinedSelector, $nextRowBlocks - 2, $nextRow, $j);
}
@elseif $status == 'out' {
@include gameLogicOver($combinedSelector);
}
@elseif $status == 'stack' {
@include nextTurn($combinedSelector, $nextRow, $nextRowBlocks);
@include gameLogic($combinedSelector, $nextRowBlocks, $nextRow, $j);
}
}
}
}
// Start
@for $i from 1 through 5 {
$selector: '#on-3-#{$i}:checked';
@include gameLogic($selector, 3, 1, $i);
}
*[id^="on-3-"]:checked ~ .bs .line,
*[id^="on-3-"]:checked ~ .controls .rs {
animation-name: target-2-3;
}
*[id^="on-3-"]:checked ~ .controls div[class*="r-1-"] {
display: none;
}
// Rows 7 - 12
@for $i from 7 through 11 {
$rowName: rowName($i);
$nextRowName: rowName($i + 1);
@for $j from 1 through 7 {
##{$rowName}-1-#{$j}:checked ~ *[id^="#{$nextRowName}-"]:checked:not(##{$nextRowName}-1-#{$j}) ~ .results .go {
display: flex;
}
##{$rowName}-1-#{$j}:checked ~ *[id^="#{$nextRowName}-"]:checked:not(##{$nextRowName}-1-#{$j}) ~ .bs .line {
display: none;
}
$continueSelector: '##{$rowName}-1-#{$j}:checked ~ ##{$nextRowName}-1-#{$j}:checked';
@include nextTurn($continueSelector, ($i + 1), 1);
}
}
// Minor
$secondLastRowMinor: rowName(7);
$lastRowMinor: rowName(8);
@for $i from 1 through 7 {
##{$secondLastRowMinor}-1-#{$i}:checked ~ ##{$lastRowMinor}-1-#{$i}:checked ~ .results .go .minor {
display: block;
}
}
// Win
$secondLastRow: rowName(11);
$lastRow: rowName(12);
@for $i from 1 through 7 {
##{$secondLastRow}-1-#{$i}:checked ~ ##{$lastRow}-1-#{$i}:checked ~ .results .win {
display: flex;
}
}
/**
* Results
**/
.results {
.go,
.win {
color: white;
display: none;
position: fixed;
}
.go,
.win {
align-items: center;
background: rgba(0,0,0,.45);
bottom: 0;
flex-direction: column;
font-size: 35px;
justify-content: center;
left: 0;
padding-bottom: 55px;
right: 0;
text-align: center;
text-transform: uppercase;
top: 0;
span {
line-height: 1;
}
button {
appearance: none;
background: white;
border: none;
cursor: pointer;
font-family: 'VT323', monospace;
font-size: 18px;
margin: 20px;
opacity: .9;
padding: 5px 10px;
text-transform: uppercase;
}
}
.go .minor,
.win .major {
background: url('http://www.jerrylow.com/demo/stacker/minor.png');
background-position: center;
background-repeat: no-repeat;
background-size: 100% auto;
display: none;
height: 70px;
width: 50px;
}
.win .major {
background-image: url('http://www.jerrylow.com/demo/stacker/major.png');
display: block;
height: 100px;
width: 50px;
}
}
/**
* Controls
**/
$control-size: $block-size + $block-gap;
$control-bg: #fa7f7f;
.controls {
bottom: 25px;
display: flex;
justify-content: center;
left: 0;
position: absolute;
right: 0;
&:active {
.control .rs,
~ .bs .line {
animation-play-state: paused;
}
}
.control {
background: $control-bg;
border: 2px solid $control-bg;
border-radius: 5px;
height: $control-size + 4px;
overflow: hidden;
width: $control-size + 4px;
}
.rs {
animation-duration: $speed;
animation-name: target-1-3;
animation-iteration-count: infinite;
}
.r {
display: flex;
flex-direction: row-reverse;
white-space: nowrap;
&.r-1-1,
&.r-1-2,
&.r-2-1,
&.r-2-2 {
display: none;
}
&[class$="-1"] {
margin-left: -#{($block-size + $block-gap) * 6};
}
&[class$="-2"] {
margin-left: -#{($block-size + $block-gap) * 5};
}
&[class$="-3"] {
margin-left: -#{($block-size + $block-gap) * 4};
}
}
@for $i from 1 through 12 {
$rowBlocks: rowBlocks($i);
@for $j from 1 through $rowBlocks {
$movements: rowMovements($j);
.r-#{$i}-#{$j} {
width: ($block-size + $block-gap) * $movements;
}
}
}
label {
cursor: pointer;
flex-shrink: 0;
height: $control-size;
width: $control-size;
&:first-of-type {
margin-right: auto;
}
}
}
form
- for i in (1..5)
input id='on-3-#{i}' type='radio' name='r-1' value='1-#{i}'
- for i in (1..5)
input id='tw-3-#{i}' type='radio' name='r-2' value='2-#{i}'
- for i in (1 .. 3)
- last = 5 + (3 - i);
- for j in (1 .. last)
input id='thr-#{i}-#{j}' type='radio' name='r-3' value='3-#{i}-#{j}'
- for h in (4 .. 6)
- if h === 4
- name = 'fr'
- if h === 5
- name = 'fv'
- if h === 6
- name = 'sx'
- for i in (1 .. 2)
- last = 6 + (2 - i);
- for j in (1 .. last)
input id='#{name}-#{i}-#{j}' type='radio' name='r-#{h}' value='#{h}-#{i}-#{j}'
- for h in (7 .. 12)
- if h === 7
- name = 'svn'
- if h === 8
- name = 'ght'
- if h === 9
- name = 'nn'
- if h === 10
- name = 'tn'
- if h === 11
- name = 'lvn'
- if h === 12
- name = 'twlv'
- for i in (1 .. 7)
input id='#{name}-1-#{i}' type='radio' name='r-#{h}' value='#{h}-1-#{i}'
div.controls
div.control
div.rs
- for h in (1 .. 12)
- if h === 1
- name = 'on'
- if h === 2
- name = 'tw'
- if h === 3
- name = 'thr'
- if h === 4
- name = 'fr'
- if h === 5
- name = 'fv'
- if h === 6
- name = 'sx'
- if h === 7
- name = 'svn'
- if h === 8
- name = 'ght'
- if h === 9
- name = 'nn'
- if h === 10
- name = 'tn'
- if h === 11
- name = 'lvn'
- if h === 12
- name = 'twlv'
- if h <= 3
- blocks = 3
- if h > 3 and h <= 6
- blocks = 2
- if h > 6
- blocks = 1
- for i in (1 .. blocks)
div class='r r-#{h}-#{i}'
- if i === 3
- movements = 5
- if i === 2
- movements = 6
- if i === 1
- movements = 7
- for j in (1 .. movements)
label for='#{name}-#{i}-#{j}'
div.bs
- for i in (1 .. 12)
.div.r
- for i in (1 .. 7)
div.b
div.line
div.results
div.go
span
| Game
br>/
| Over
div.minor
button Again
div.win
span
| You
br>/
| Win
div.major
button Again
h1 Stacker
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment