SCSS
$size : 500px;
$green : rgba(38, 166, 91, 1);
$red : rgba(166, 38, 43, 1);
$yellow: rgba(244, 208, 63, 1);
$blue : rgba(65, 131, 215, 1);
@mixin border-radius-bottom-right($radius) {
-webkit-border-radius: 0 0 $radius 0;
-moz-border-radius: 0 0 $radius 0;
-ms-border-radius: 0 0 $radius 0;
border-radius: 0 0 $radius 0;
}
@mixin border-radius-bottom-left($radius) {
-webkit-border-radius: 0 0 0 $radius;
-moz-border-radius: 0 0 0 $radius;
-ms-border-radius: 0 0 0 $radius;
border-radius: 0 0 0 $radius;
}
@mixin border-radius-top-right($radius) {
-webkit-border-radius: 0 $radius 0 0;
-moz-border-radius: 0 $radius 0 0;
-ms-border-radius: 0 $radius 0 0;
border-radius: 0 $radius 0 0;
}
@mixin border-radius-top-left($radius) {
-webkit-border-radius: $radius 0 0 0;
-moz-border-radius: $radius 0 0 0;
-ms-border-radius: $radius 0 0 0;
border-radius: $radius 0 0 0;
}
@mixin lighten-button($color) {
background-color: $color;
&.highlighted { background-color: lighten($color, 20%); }
&.transparent { background-color: transparent; }
&.lose { background-color: $red; }
&.win { background-color: $green; }
}
body {
background: #ddd;
font-family: 'Roboto';
}
#game {
position: absolute;
width: $size;
height: $size;
top: 50%;
left: 50%;
background: #444;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
z-index: 1;
}
.quarter-circle {
position: absolute;
width: $size / 2.5;
height: $size / 2.5;
cursor: pointer;
z-index: 2;
}
.top-left {
top: 8%;
left: 8%;
@include lighten-button($green);
@include border-radius-top-left($size);
}
.top-right {
top: 8%;
right: 8%;
@include lighten-button($red);
@include border-radius-top-right($size);
}
.bottom-left {
bottom: 8%;
left: 8%;
@include lighten-button($yellow);
@include border-radius-bottom-left($size);
}
.bottom-right {
bottom: 8%;
right: 8%;
@include lighten-button($blue);
@include border-radius-bottom-right($size);
}
#center {
position: relative;
top: 50%;
width: 45%;
height: 45%;
margin: 0 auto;
background: #ddd;
color: #444;
font-weight: bold;
border-radius: 50%;
border: 20px solid #444;
transform: translateY(-50%);
z-index: 3;
}
#start-btn {
width: 40px;
height: 40px;
margin-top: 40px;
background-color: #D24D57;
border-radius: 50%;
border: 3px solid #444;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.4);
}
#reset-btn {
position: absolute;
right: 36%;
bottom: 20%;
}
#lives-wrap, #level-wrap {
position: fixed;
font-weight: bold;
}
#lives-wrap {
top: 10%;
right: 10%;
}
#level-wrap {
top: 10%;
left: 10%;
}
#lives, #level {
width: 80px;
height: 60px;
padding: 5px;
background-color: #444;
text-align: center;
line-height: 1.75em;
font-size: 2em;
border-radius: 10%;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.4);
}
#lives {
color: #D24D57;
}
#level {
color: #36D7B7;
}
#lives + p, #level + p {
margin-top: 5px;
}
#start-btn:focus {
outline: 0;
}
#center h1 {
position: relative;
top: 5%;
transform: translateY(-50%);
}
JAVASCRIPT
(function() {
var buttons = $(".quarter-circle"),
lives = $("#lives"),
level = $("#level");
/* [Game] *********************************************************/
function Game(config) {
this.lives = config.lives;
this.level = config.level;
this.sequence = [];
this.attempt = [];
this.time = 0;
this.step = config.step;
this.flashTime = config.flashTime;
this.status = 'off';
this.config = config;
$this = this;
}
Game.prototype = {
constructor: Game,
start: function() {
this.status = 'on';
lives.text(this.lives);
level.text(this.level);
setTimeout(function() {
$this.playSequence();
}, 1500);
},
addStep: function() {
this.sequence.push(Math.floor(Math.random() * 4));
},
playSequence: function() {
this.status = 'teach';
if (this.sequence.length === 0) {
for (var i = 0; i < this.level; i++) {
this.addStep();
}
}
if (this.level === 5) this.speedUp(20);
if (this.level === 9) this.speedUp(25);
if (this.level === 13) this.speedUp(33);
/* Play the sequence */
for (var i = 0; i < this.sequence.length; i++) {
(function(button) {
setTimeout(function() {
$this.pressButton(button);
}, $this.time);
})($(buttons[this.sequence[i]]));
this.time += this.step;
/* Disable user clicks until sequence is finished. */
setTimeout(function() {
$this.status = 'play';
}, this.level * this.step);
}
},
sendClick: function(button) {
this.attempt.push(buttons.index(button));
this.verify();
},
verify: function() {
/* If last button pressed does not match corresponding button in the sequence */
if (this.attempt[this.attempt.length - 1] !== this.sequence[this.attempt.length - 1]) {
this.status = 'check';
this.lives -= 1;
this.updateLives();
if (this.lives === 0) this.status = 'lose';
setTimeout(function() {
$this.fail();
}, 2000);
/* Otherwise it's the correct sequence */
} else if (this.attempt.length === this.sequence.length) {
this.status = 'check';
this.level += 1;
this.updateLevel();
if (this.level > 20) this.status = 'win';
setTimeout(function() {
$this.success();
}, 3000);
}
},
success: function() {
this.time = 0;
this.attempt = [];
if (this.status === 'win') {
this.animate();
this.reset();
} else {
this.addStep();
this.playSequence();
}
},
fail: function() {
this.time = 0;
this.attempt = [];
if (this.status === 'lose') {
this.animate();
this.reset();
} else {
this.playSequence();
}
},
pressButton: function(button) {
this.highlight(button);
this.playSound(button);
},
highlight: function(button, highlight) {
var classes = ['highlighted', highlight].join(' ');
button.addClass(classes);
setTimeout(function() {
button.removeClass(classes);
}, this.flashTime);
},
playSound: function(button) {
var index = buttons.index(button);
document.getElementsByTagName('audio')[index].play();
},
updateLives: function() {
lives.text(this.lives);
lives.addClass("animated shake");
setTimeout(function() {
lives.removeClass("animated shake");
}, 1500);
},
updateLevel: function() {
level.text(this.level);
level.addClass("animated tada");
setTimeout(function() {
level.removeClass("animated tada");
}, 1500);
},
speedUp: function(percent) {
this.step -= this.step / (100 / percent);
this.flashTime -= this.flashTime / (100 / percent);
},
reset: function() {
this.lives = this.config.lives;
this.level = this.config.level;
this.step = this.config.step;
this.flashTime = this.config.flashTime;
this.sequence = [];
this.attempt = [];
this.time = 0;
this.status = 'on';
lives.text(this.lives);
level.text(this.level);
},
animate: function() {
var i = 0,
rounds = 0,
order = [0, 1, 3, 2],
step = 200,
status = this.status;
buttons.addClass('transparent');
var spin = function() {
if (rounds === 2) {
clearTimeout(timer);
}
/* Color each button depending on whether the user won or lost the game */
while (i < 4) {
(function(button) {
setTimeout(function() {
$this.highlight(button, status);
}, $this.time);
})($(buttons[order[i]]));
$this.time += step;
i++;
}
i = 0;
rounds++;
}
/* Color the 4 buttons 3 times */
var timer = setInterval(spin, 0);
/* Reset the button colours after 3 spins have occurred */
setTimeout(function() {
buttons.removeClass('transparent');
}, step * 13);
}
};
/* [Event Handlers] **************************************/
buttons
.on("click", function() {
if (game.status !== 'play') return;
game.pressButton($(buttons[buttons.index($(this))]));
game.sendClick($(this));
});
$("#start-btn")
.mousedown(function() {
$(this).css('transform', 'translateY(1px)');
game.start();
})
.mouseup(function() {
$(this).css('transform', 'translateY(-1px)');
});
$("#reset-btn")
.on("click", function() {
game.reset();
})
/* [Main] **********************************************/
/* Pass in a config to the game so that the game can reset to those values */
var cfg = {
lives: 3,
level: 1,
step: 1000,
flashTime: 300
}
var game = new Game(cfg);
})();