I resolved to do the whole thing with just vanilla Javascript as a challenge, but man, Knockout's binding and state management is just too easy.
A tiny bit of callback hell to get all the flashing / guessing synchronized, but what I'd really like people to look into is the OO-style of this. I try to avoid anemic models wherever possible, and sometimes it makes my classes look ... bulky? So while it works, could it be improved from a code maintenance perspective if I (for example) broke out Pattern, Guess, etc into their own objects.
var simonSquare = function(color) {
var self = this;
self.color = ko.observable(color);
self.isActive = ko.observable(false);
self.activeCSS = ko.computed(function() {
return self.isActive() ? "active" : "";
});
self.flash = function(callback) {
self.isActive(!self.isActive());
setTimeout(function() {self.isActive(!self.isActive());
if (callback) {
callback();
}}, 300);
};
};
var simon = function() {
var self = this;
self.Squares = ko.observableArray([
new simonSquare('red'),
new simonSquare('blue'),
new simonSquare('green'),
new simonSquare('yellow')
]);
self.Pattern = [];
self.playPattern = function(index) {
var index = index || 0;
self.Pattern[index].flash(function() {
if (self.Pattern[index + 1]) {
setTimeout(function() {
self.playPattern(index + 1);
}, 200) ;
}
else {
self.canGuess = true;
}
});
};
self.highScore = ko.observable(0);
//Stack Snippets are sandboxed, no local storage
//self.highScore = ko.observable(localStorage.getItem("SimonSaysHighScore"));
self.canGuess = false;
self.gameOver = ko.observable(false);
self.currentPositionBeingGuessed = 0;
self.guess = function(guessedSquare) {
if (!self.canGuess) return;
guessedSquare.flash(function() {
if (self.Pattern[self.currentPositionBeingGuessed] == guessedSquare) {
if (self.currentPositionBeingGuessed == (self.Pattern.length -1 )) { // completed pattern
if (self.Pattern.length > self.highScore()) {
self.highScore(self.Pattern.length);
}
self.newRound();
}
else {
self.currentPositionBeingGuessed++;
}
}
else {
self.gameOver(true);
/* Stack Snippets are sandboxed, no local storage
if(typeof(Storage) !== "undefined") {
localStorage.setItem("SimonSaysHighScore", self.Pattern.length -1);
} */
self.canGuess = false;
}
});
}
self.newRound = function() {
self.canGuess = false;
self.currentPositionBeingGuessed = 0;
self.Pattern.push(self.Squares()[Math.floor(Math.random()*self.Squares().length)]);
setTimeout(self.playPattern, 500);
}
self.newGame = function() {
self.gameOver(false);
self.Pattern = [];
self.newRound();
};
};
simon = new simon();
ko.applyBindings(simon);
.active {
background-color: white !important;
color: black;
}
#gameBoard {
width: 500px;
}
.simonSquare {
width: 40%;
float: left;
margin: 5%;
height: 250px;
}
<!-- Create a UI with four colored buttons that light up in a random
pattern. After displaying the pattern, the player must repeat
the pattern by clicking the buttons in proper order.
The pattern gets longer each time the player completes the pattern.
If the player presses a wrong button, the game ends.-->
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
</head>
<body>
<div id='simon'>
<div>
<input type='button' value='New Game' data-bind='click: newGame' />
</div>
<div id='gameOverMessage' data-bind='visible: gameOver'>Game Over!</div>
<div id='highScore'>High Score:<span data-bind='text: highScore'></span></div>
<div id='gameBoard'>
<!--ko foreach: Squares -->
<div class='simonSquare' data-bind="
style: { backgroundColor: color() } ,
css: activeCSS,
click: $root.guess">
</div>
<!-- /ko -->
</div>
</div>
</body>