Skip to content

Instantly share code, notes, and snippets.

@KDCinfo
Last active December 24, 2017 11:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KDCinfo/271d88aa7630559a29a9402eb16d6208 to your computer and use it in GitHub Desktop.
Save KDCinfo/271d88aa7630559a29a9402eb16d6208 to your computer and use it in GitHub Desktop.
Simon - JavaScript Game

Simon 20 (JavaScript / Responsive)

Demos and Source Code

Simon is the result of the final coding challenge for Free Code Camp's Front-End Development Certification course.

In addition to the requirements provided by the challenge, I added 3 speed levels, and an "Insights" panel (cheat mode). I then took another couple days and converted it to be responsive.

Rules

Successfully remember (or guess) up to 20 in a row to win. With 'Strict' mode turned off, you get 1 second chance if you mess up (Simon will automatically show you the way... yet again). Now go play and have fun!

Keyboard Navigation

  • Enter: Begin a new game.
  • Space: Begin a new game.
  • Escape: Begin a new game.
  • S: Toggle Strict Mode and begin a new game.
  • R | 1: Red cell.
  • B | 2: Blue cell.
  • G | 3: Green cell.
  • Y | 4: Yellow cell.

Initial Development Time

  • 2017-11-17 : 7:30 PM - 4:30 AM = 8 hours (functionally complete)
  • 2017-11-18 : 4:00 PM - 6:00 AM = 14 hours === ~ 3 days development

FCC Certification

Free Code Camp Front-End Development Certification Achieved (291 coding challenges completed).

Project History

// @DONE: Show updated text when win or lose. (Also added a couple other communique along the way.)
// @DONE: Double-triple-clicks will overplay.
    // Disabled board immediately after clicks. Reenabled when ready (selectively).
// @DONE: Added toggle for expert (yolo/strict) level.
// @DONE: Added toggle for speed (1-3).
// @DONE: Added sound.
// @DONE: Prettified scoreboard.
// @DONE: Shared scoreboard with 'Cheat Mode' (a.k.a., Insights).
    // Added flip animation
    // A little thanks to: http://webcodingschool.com/2017/04/04/card-flipping-effect-by-css/
    // Had to completely refactor HTML and CSS due to
        // scoreboard and sub-divs bleeding out into the cell click space via their rectangular corners.
        // Set outside DIVs to 'inline' and sub-divs to 'inline-block' - but took a lot of doing.
// @DONE: Prettified the stringifies.
// @DONE: Added keyboard navigation.
// @DONE: Tested in Chrome, Firefox, Opera, and IE Edge.
// @DONE: 2017-11-19 @ 6:00 AM

// @DONE: Added responsiveness. (Took 1+ days.)
// @DONE: Added zoom options (only available when viewing width >=800px).
// @DONE: Refactored audio button assignments (code) into a for-loop.
// @DONE: 2017-11-20/21
body {
font-family: sans-serif;
margin: 5px auto 2rem;
padding: 1rem;
text-align: center;
}
h1 {
margin: 0 auto 1rem;
}
p.pojs {
font-size: 0.95rem;
margin: 0 auto 1.5rem;
}
.container {
margin: 0 auto;
position: relative;
width: 90%;
/*height: 90%;*/
min-width: 285px; /* 200px was good, but FireFox had issues */
max-width: 800px;
}
.outer {
border: 1px dotted gray;
box-shadow: 10px 10px gray;
margin: 0 auto;
position: relative;
text-align: center;
width: 100%;
}
.outer.buzz::after {
background-color: transparent;
border: 0px solid transparent;
content: '';
margin: 0 auto;
position: absolute;
top: 0;
left: 0;
z-index: 100; /* Should go above cells, but below scoreboard. */
width: 100%;
height: 100%;
}
.sq-setter-w {
width: 100%;
height: auto;
visibility: hidden;
}
.inside {
/*outline: 1px solid blue;*/
margin: 0 auto;
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 0;
z-index: 1;
min-width: 200px;
max-width: 800px;
max-height: 800px;
}
.inside2 {
/*outline: 1px solid orange;*/
font-size: 3vw;
margin: 1em auto 0;
display: inline-block;
position: relative;
height: 90%;
width: 90%;
}
/*#scoreboard {
perspective: 600px;
-webkit-perspective: 600px;
-moz-perspective: 600px;
}*/
.scoreboard {
backface-visibility: hidden;
transform: perspective(600px) rotateY(0deg);
transition: transform .5s linear 0s;
}
.scoreboard.flip {
transform: perspective(600px) rotateY(-180deg);
}
.cheat {
backface-visibility: hidden;
transform: perspective(600px) rotateY(180deg);
transition: transform .5s linear 0s;
}
.cheat.flip {
box-shadow: inset 1px 1px 5px darkgray;
/*display: inline-block;*/
transform: perspective(600px) rotateY(0deg);
z-index: 150;
}
/* Scoreboard Panel */
.scoreboard {
/*outline: 3px solid green;*/
background-color: gold;
border: 5px solid #ffffff;
border-radius: 100%;
box-shadow: inset 1px 1px 5px darkgray;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
overflow: hidden;
position: absolute;
bottom: 24%;
top: 24%;
left: 23%;
right: 23%;
height: 50%;
width: 50%;
z-index: 5;
}
.scoreboard > div {
display: inline;
}
.scoreboard-top > div,
.scoreboard-middle > div,
.scoreboard-bottom > div {
outline: 0px solid blue;
display: inline-block;
margin: 0.25em 0 0;
padding: 0;
}
.scoreboard-top input {
vertical-align: middle;
}
/* START BUTTON */
.scoreboard-top .div-start {
font-weight: bold;
margin: 0 0 0.35em;
padding: 0;
}
#start {
background-color: red;
border: 2px solid green;
border-radius: 50%;
box-shadow: 0.1em 0.1em darkgray;
cursor: pointer;
display: inline-block;
vertical-align: bottom;
width: 3vw;
height: 3vw;
max-width: 22px;
min-width: 10px;
max-height: 22px;
min-height: 10px;
}
#start:hover,
#start:active,
#start:focus {
border: 2px solid darkblue;
outline: 0;
}
#start:hover {
background-color: orangered;
/*box-shadow: 0.5em 0.5em inset white;*/
}
#simon-speed {
height: 14px;
text-align: center;
vertical-align: baseline;
width: 40px;
}
input[type="number"] {
font-size: 0.7em;
padding: 0.2em 0;
}
.scoreboard-middle {
font-size: 1.1em;
font-weight: bold;
}
.scoreboard-middle > div {
height: 2em;
margin: 0.75em auto 0.25em;
min-width: 10em;
position: relative;
text-align: center;
vertical-align: bottom;
}
.game-is {
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translate(0, -50%);
}
.scoreboard-bottom {
/*margin: 0.35em 0 0;*/
}
/*.scoreboard-bottom > div.clearb*/
div.clearb {
clear: both;
display: block;
height: 0.1em;
margin: 0 auto;
outline: 0px solid black;
width: 1px;
}
.cheat {
/*outline: 3px solid green;*/
background-color: gold;
border: 5px solid #ffffff;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
overflow: hidden;
position: absolute;
bottom: 24%;
top: 24%;
left: 23%;
right: 23%;
height: 50%;
width: 50%;
z-index: 5;
}
.cheat .cheat-first {
margin: 1em 0 0.75em;
}
.cheat > div {
display: inline;
/* font-size: 2.5vw; */
margin: 0.25em 1em;
}
.cells {
display: flex;
flex-wrap: wrap;
position: relative;
height: 100%;
width: 100%;
}
.cell {
border: 2px solid #fff;
/* border-radius: 50%; */
box-shadow: 0px 0px gray;
cursor: pointer;
/*display: inline-block;*/
opacity: 0.75;
position: relative;
width: 48%;
min-width: 100px;
min-height: 100px;
}
.cell:hover,
.cell:active,
.cell:focus {
outline: 0;
opacity: 0.85;
}
.cell.buzzed {
opacity: 1;
}
#ul {
background-color: red;
border-top-left-radius: 100%;
border-top-width: 3px;
border-left-width: 3px;
border-top-color: gray;
border-left-color: gray;
}
#ur {
background-color: blue;
border-top-right-radius: 100%;
border-top-width: 3px;
border-right-width: 3px;
border-top-color: gray;
border-right-color: gray;
}
#ll {
background-color: green;
border-bottom-left-radius: 100%;
border-bottom-width: 3px;
border-left-width: 3px;
border-bottom-color: gray;
border-left-color: gray;
}
#lr {
background-color: orange;
border-bottom-right-radius: 100%;
border-bottom-width: 3px;
border-right-width: 3px;
border-bottom-color: gray;
border-right-color: gray;
}
.cell.buzz::after {
background-color: #ffffff;
border: 0.75em solid yellow;
content: ' ';
opacity: 1;
position: absolute;
top: -1em;
left: -1em;
width: 98%;
height: 98%;
/*min-width: 100px;*/
/*min-height: 100px;*/
z-index: 3;
}
#ul.buzz::after {
background-color: red;
border-top-left-radius: 95%;
}
#ur.buzz::after {
background-color: blue;
border-top-right-radius: 95%;
}
#ll.buzz::after {
background-color: green;
border-bottom-left-radius: 95%;
}
#lr.buzz::after {
background-color: orange;
border-bottom-right-radius: 95%;
}
.rules {
border: 0px dotted yellow;
margin-top: 1.5rem;
padding: 0 1rem;
text-align: left;
}
.keynav {
line-height: 1.5;
}
footer {
background-color: rgba(235, 245, 255, 1);
border-top: 1px dotted darkgray;
font-size: 11px;
margin: 0 auto;
padding: 0.25rem 0;
position: fixed;
bottom: 0;
left: 0;
right: 0;
text-align: center;
z-index: 1;
}
.hide,
.hidden {
display: none;
}
ul {
list-style: square;
}
ul.attribution {
font-size: 0.9rem;
line-height: 1.5;
list-style: square;
margin: 0 auto;
padding: 0 1rem;
text-align: left;
width: 95%;
max-width: 420px;
}
ul.fcc-links {
border: 1px dotted gray;
border-radius: 2px;
font-size: 0.9rem;
line-height: 1.5;
list-style: none;
margin: 1rem auto 0;
padding: 1rem;
text-align: left;
width: 95%;
max-width: 420px;
}
.zoom {
display: none;
margin: 0.5em 0;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 200;
}
.zoom-0-50 {
zoom: 0.5;
-moz-transform: scale(0.5);
-moz-transform-origin: 50 0;
}
.zoom-0-75 {
zoom: 0.75;
-moz-transform: scale(0.75);
-moz-transform-origin: 50 0;
}
.outer-h-0-75 {
height: 39em;
}
.outer-h-0-50 {
height: 26em;
}
.zoom button {
cursor: pointer;
font-size: 0.9em;
}
@media screen and (min-width: 800px) {
/* .container max-width: 800px; */
.inside2 {
font-size: 20px;
}
.scoreboard-middle {
font-size: 30px;
font-weight: bold;
}
input[type="number"] {
font-size: 20px;
}
.cheat.flip > div {
font-size: 20px;
}
.zoom {
display: block;
}
.scoreboard,
.cheat {
position: absolute;
top: 25%;
}
}
// console.clear();
var cellConvert = [ // Convert cell quadrants to array-like indexes.
'ul', // 0
'ur', // 1
'll', // 2
'lr' // 3
],
gameSpeeds = {
1: { each: 900, wait: 700 }, // Slow each; slow wait
2: { each: 600, wait: 400 }, // Default
3: { each: 300, wait: 200 }, // Quick each; quick wait
// 1 2 3
// revealSimon - setInterval - every 600 buzzIt [400 / 600 / 800]
// buzzIt - setTimeout - wait 400 then buzzOff [200 / 400 / 600]
},
game = {
currentCell: 9, // 9 is just a temporary empty placeholder (to keep the var an INT)
// [0-3] reserved for cells;
whoClicked: 's', // 's'|'p' (Simon or Player) - A controlled variable to be handled each click.
yolo: false, // Strict Mode (no 2nd chance after a wrong click).
hasTried: 0, // 0|1 - Allowed 1 retry (if yolo isn't true).
playerArray: [], // Cells the player has clicked.
simonArray: [], // Cells the game has chosen (added post-click).
winArray: [], // [3,2,5,6,1,...] Number of successful clicks in previous games.
gameOver: true,
gameSpeed: 2, // [1-3] Slow, Medium*, Fast
},
buttons = {
0: '',
1: '',
2: '',
3: '',
};
const ui = { keyspecs: [
{ name: 'SPACE', code: ' ', cell: 'start' }, // Must stay #1
{ name: 'ENTER', code: 'enter', cell: 'start' },
{ name: 'ESC', code: 'escape', cell: 'start' },
{ name: 's', code: 's', cell: 'simon-yolo' },
{ name: 'r', code: 'r', cell: 'ul' }, { name: '1', code: '1', cell: 'ul' },
{ name: 'b', code: 'b', cell: 'ur' }, { name: '2', code: '2', cell: 'ur' },
{ name: 'g', code: 'g', cell: 'll' }, { name: '3', code: '3', cell: 'll' },
{ name: 'y', code: 'y', cell: 'lr' }, { name: '4', code: '4', cell: 'lr' }
]
};
window.onload = function() {
// AUDIO HANDLING
// Setting 'autoplay' to false in the <audio> tag doesn't always work,
// whether setting to false, "false", 0, or "0", so instead...
for (var i = 0; i < 4; i++) {
buttons[i] = document.getElementById('simon-audio-' + i);
buttons[i].src = 'https://s3.amazonaws.com/freecodecamp/simonSound' + (i+1) + '.mp3';
buttons[i].autoplay = false;
}
// ADD EVENT LISTENERS
document.getElementById('simon-speed').addEventListener('change', function(e) {
game.gameSpeed = (e.target.value === "1") ? 1 : (e.target.value === "3") ? 3 : 2;
//
// Ternary (above) vs. if-then-else (below)
//
// if (e.target.value === "1") {
// game.gameSpeed = 1;
// } else if (e.target.value === "3") {
// game.gameSpeed = 3;
// } else {
// game.gameSpeed = 2; // Fall-through (default)
// }
document.getElementById('start').click();
});
document.getElementById('simon-yolo').addEventListener('change', function(e) {
e.preventDefault();
game.yolo = !game.yolo;
document.getElementById(ui.keyspecs[3].cell).checked = game.yolo;
document.getElementById('start').click();
});
document.getElementById('simon-cheat').addEventListener('change', function(e) {
document.getElementById('simon-cheat-reverse').checked = true;
Array.from(document.getElementsByClassName('scoreboard'))[0].classList.add('flip');
Array.from(document.getElementsByClassName('cheat'))[0].classList.add('flip');
});
document.getElementById('simon-cheat-reverse').addEventListener('change', function(e) {
// It was in cheat mode, now remove it and show the normal scoreboard.
document.getElementById('simon-cheat').checked = false;
Array.from(document.getElementsByClassName('scoreboard'))[0].classList.remove('flip');
Array.from(document.getElementsByClassName('cheat'))[0].classList.remove('flip');
});
var insideDiv = Array.from(document.getElementsByClassName('inside'))[0],
outerDiv = Array.from(document.getElementsByClassName('outer'))[0],
zoom100 = Array.from(document.getElementsByClassName('click-zoom-1-00'))[0],
zoom050 = Array.from(document.getElementsByClassName('click-zoom-0-50'))[0],
zoom075 = Array.from(document.getElementsByClassName('click-zoom-0-75'))[0];
zoom100.addEventListener('click', function(e) {
console.log('here 1');
insideDiv.classList.remove('zoom-0-50');
insideDiv.classList.remove('zoom-0-75');
outerDiv.classList.remove('outer-h-0-50');
outerDiv.classList.remove('outer-h-0-75');
});
zoom050.addEventListener('click', function(e) {
console.log('here 2');
insideDiv.classList.remove('zoom-0-75');
insideDiv.classList.add('zoom-0-50');
outerDiv.classList.remove('outer-h-0-75');
outerDiv.classList.add('outer-h-0-50');
});
zoom075.addEventListener('click', function(e) {
console.log('here 3');
insideDiv.classList.remove('zoom-0-50');
insideDiv.classList.add('zoom-0-75');
outerDiv.classList.remove('outer-h-0-50');
outerDiv.classList.add('outer-h-0-75');
});
document.getElementById('start').addEventListener('click', function(e) {
disableCells();
game.currentCell = getRandomCell(); // Get the first cell.
game.whoClicked = 's'; // Control the var!
game.hasTried = 0;
game.gameOver = false;
document.getElementById('game-over').innerText = 'In Play';
resetCells();
generateNextCell();
});
document.getElementById('ul').addEventListener('click', function(e) {
registerClick(e.target.id);
});
document.getElementById('ur').addEventListener('click', function(e) {
registerClick(e.target.id);
});
document.getElementById('ll').addEventListener('click', function(e) {
registerClick(e.target.id);
});
document.getElementById('lr').addEventListener('click', function(e) {
registerClick(e.target.id);
});
document.getElementById('start').addEventListener('keypress', function(e) {
document.getElementById('start').click();
});
document.addEventListener('keydown', function(e) {
// [event.keyCode] is deprecated.
// [event.key] is its replacement.
// [event.code] is not supported well (2017-11-07).
var eKey = e.key.toLowerCase(); // / * - + Enter . Space
if (eKey === ui.keyspecs[0].code || // SPACE
eKey === ui.keyspecs[1].code || // ENTER
eKey === ui.keyspecs[2].code) { // ESCAPE
e.preventDefault();
document.getElementById('start').click();
}
if (eKey === ui.keyspecs[3].code) { // S (strict mode toggle)
e.preventDefault();
game.yolo = !game.yolo;
document.getElementById(ui.keyspecs[3].cell).checked = game.yolo;
document.getElementById('start').click();
}
if (eKey === ui.keyspecs[4].code || eKey === ui.keyspecs[5].code) { // Red or 1
e.preventDefault();
document.getElementById(ui.keyspecs[4].cell).click();
}
if (eKey === ui.keyspecs[6].code || eKey === ui.keyspecs[7].code) { // Blue or 2
e.preventDefault();
document.getElementById(ui.keyspecs[6].cell).click();
}
if (eKey === ui.keyspecs[8].code || eKey === ui.keyspecs[9].code) { // Green or 3
e.preventDefault();
document.getElementById(ui.keyspecs[8].cell).click();
}
if (eKey === ui.keyspecs[10].code || eKey === ui.keyspecs[11].code) { // Yellow or 4
e.preventDefault();
document.getElementById(ui.keyspecs[10].cell).click();
}
});
document.getElementById('start').focus();
updateDisplays();
}
// FUNCTIONS
function resetCells() {
game.playerArray = [];
game.simonArray = [];
// game.simonArray = [1,2,3,2,0,3,2,1,0,1]; // Was for testing.
updateDisplays();
}
function updateDisplays() {
updatePlayCountDisplay(); // Should be run whenever simonArray is changed.
updateGameWinsDisplay(); // Should be run whenever a game is won.
updateGamesPreviousDisplay();
updateSimonPlaysDisplay();
updatePlayerArray();
}
function generateNextCell() {
// Wait 3/4 of a sec, then buzz the cell with [0-3].
setTimeout( () => { buzzCell(getRandomCell()) }, 750);
}
function registerClick(cell) {
// console.log('Welcome, Cell # ', cell, cellConvert.indexOf(cell));
disableCells();
// [cellConvert] Maps the cell ID ('ul') to its index [0-3].
buzzCell(cellConvert.indexOf(cell));
}
function disableCells() {
Array.from(document.getElementsByClassName('outer'))[0].classList.add('buzz');
}
function enableCells() {
Array.from(document.getElementsByClassName('outer'))[0].classList.remove('buzz');
}
function getRandomCell() {
return Math.floor(Math.random() * 4);
}
function revealSimon() {
var i = 0;
var showThem = setInterval( function() {
if (i < game.simonArray.length) {
// Grab each index from the 'simonArray', and convert it to a cell ID.
buzzIt(game.simonArray[i]);
} else {
clearInterval(showThem);
document.getElementById('game-over').innerText = 'Waiting...\r\n(it\'s your turn)';
game.playerArray = [];
enableCells();
}
i++;
// console.log('revealSimon: ', game.gameSpeed);
// console.log('setInterval: ', gameSpeeds[game.gameSpeed].each);
}, gameSpeeds[game.gameSpeed].each);
// gameSpeeds = {
// 1: { each: 400, wait: 200 },
}
function updatePlayCountDisplay() {
// Should be run whenever simonArray is changed.
document.getElementById('play-count').innerText = game.simonArray.length;
updateSimonPlaysDisplay();
}
function updateGameWinsDisplay() {
document.getElementById('game-wins').innerText = game.winArray.reduce((sum, score) => {
return (score === 20) ? sum + 1 : sum;
}, 0);
}
function updateSimonPlaysDisplay() {
// document.getElementById('simon-plays').innerText = JSON.stringify(game.simonArray);
document.getElementById('simon-plays').innerText = game.simonArray.map( cell => {
return cell === 0 ? ' R' :
cell === 1 ? ' B' :
cell === 2 ? ' G' : ' Y';
});
updatePlayerArray();
}
function updatePlayerArray() {
// document.getElementById('player-plays').innerText = JSON.stringify(game.playerArray);
document.getElementById('player-plays').innerText = game.playerArray.map( cell => {
return cell === 0 ? ' R' :
cell === 1 ? ' B' :
cell === 2 ? ' G' : ' Y';
});
}
function updateGamesPreviousDisplay() {
document.getElementById('games-previous').innerText = JSON.stringify(game.winArray);
}
function buzzCell(cell) {
// This is the 'brains' of the app (i.e., the Controller).
// cell = number [0-3]
// game.currentCell = getRandomCell(); // Get the first cell.
var cellId = cellConvert[cell], // cellId = 'ul'|'ur'|'ll'|'lr'
turnSimon = game.whoClicked === 's',
gameOver = game.gameOver;
if (gameOver) {
buzzIt(cell); // Just for fun... game is over.
setTimeout( () => enableCells(), 100);
} else if (turnSimon) {
game.simonArray.push(cell); // [0,1,2,3,0,1,2,3]
updatePlayCountDisplay(); // Should be run whenever simonArray is changed.
// Show all of Simon's moves.
revealSimon();
// Swap side back to player.
game.whoClicked = 'p'; // Control the var!
} else {
// Player's Turn
game.playerArray.push(cell); // [] ... [3]
updatePlayerArray();
buzzIt(cell);
for (var i = 0; i < game.playerArray.length; i++) {
if (game.playerArray[i] !== game.simonArray[i]) {
// Incorrect - Reset
game.playerArray = [];
updatePlayerArray();
break;
}
}
if (game.playerArray.length === 0) {
// An incorrect cell was clicked (and playerArray was zeroed out).
if (game.hasTried === 0 && !game.yolo) {
document.getElementById('game-over').innerText = '...whoops!!!';
game.hasTried = 1;
revealSimon();
} else {
game.gameOver = true;
document.getElementById('game-over').innerText = 'Lost';
game.hasTried = 0;
game.whoClicked = 's'; // Control the var!
game.winArray.push(game.simonArray.length);
enableCells();
resetCells();
}
} else if (game.playerArray.length === 20) {
// You Won!!!
game.winArray.push(20);
game.gameOver = true;
game.hasTried = 0;
document.getElementById('game-over').innerText = 'Won!!!';
enableCells();
updateDisplays();
} else if (game.playerArray.length === game.simonArray.length) {
// You survived another round!!!
document.getElementById('game-over').innerText = 'In Play';
game.hasTried = 0;
game.whoClicked = 's'; // Control the var!
game.playerArray = [];
updatePlayerArray();
generateNextCell();
} else {
enableCells();
}
}
}
// Pure (aesthetic) Functions to Highlight Cells
function buzzIt(cellId) {
buzzOn(cellConvert[cellId]);
setTimeout( function() {
buzzOff(cellConvert[cellId]);
}, gameSpeeds[game.gameSpeed].wait); // Show cell for partial second
// gameSpeeds = { 1: { each: 400, wait: 200 },
}
function buzzOn(cellId) {
document.getElementById(cellId).classList.add('buzz','buzzed');
// Sound /* id = simon-audio-[0-3] */
// Pausing and setting currentTime helps fix non-playing overlaps on fast mode.
// Thanks to: https://stackoverflow.com/a/32041746/638153
buttons[cellConvert.indexOf(cellId)].pause();
buttons[cellConvert.indexOf(cellId)].currentTime = 0;
buttons[cellConvert.indexOf(cellId)].play();
}
function buzzOff(cellId) {
document.getElementById(cellId).classList.remove('buzz','buzzed');
}
// ... Older ... Stuff ...
// Free Code Camp Front-End Development Certification Achieved
// https://www.freecodecamp.org/kdcinfo/front-end-certification
// https://www.freecodecamp.org/kdcinfo
// 2017-11-17 : 7:30 PM - 4:30 AM = 8 hours (functionally complete)
// 2017-11-18 : 4:00 PM - 6:00 AM = 14 hours === ~ 3 days development
// @DONE: Show updated text when win or lose. (Also added a couple other communique along the way.)
// @DONE: Double-triple-clicks will overplay.
// Disabled board immediately after clicks. Reenabled when ready (selectively).
// @DONE: Added toggle for expert (yolo/strict) level.
// @DONE: Added toggle for speed (1-3).
// @DONE: Added sound.
// @DONE: Prettified scoreboard.
// @DONE: Shared scoreboard with 'Cheat Mode' (a.k.a., Insights).
// Added flip animation
// A little thanks to: http://webcodingschool.com/2017/04/04/card-flipping-effect-by-css/
// Had to completely refactor HTML and CSS due to
// scoreboard and sub-divs bleeding out into the cell click space via their rectangular corners.
// Set outside DIVs to 'inline' and sub-divs to 'inline-block' - but took a lot of doing.
// @DONE: Prettified the stringifies.
// @DONE: Added keyboard navigation.
// @DONE: Tested in Chrome, Firefox, Opera, and IE Edge.
// @DONE: 2017-11-19 @ 6:00 AM
// @DONE: Added responsiveness. (Took 1+ days.)
// @DONE: Added zoom options (only available when viewing width >=800px).
// @DONE: Refactored audio button assignments (code) into a for-loop.
// @DONE: 2017-11-21
<!--
// Simon 20 // Keith D Commiskey // 2017-11-19
//
// * [CodePen: React + TypeScript](https://codepen.io/KeithDC/full/XVNgQr)
// * [Gist: React + TypeScript](https://gist.github.com/KDCinfo/c1a65aa240c8653052f092a7d74882bb)
//
// * [CodePen: React](https://codepen.io/KeithDC/full/KyoQQE)
// * [Gist: React](https://gist.github.com/KDCinfo/2bc1be7f4f0527a4cbbff421d4e8464b)
//
// * [CodePen: JavaScript](https://codepen.io/KeithDC/full/dZJoVm)
// * [Gist: JavaScript](https://gist.github.com/KDCinfo/271d88aa7630559a29a9402eb16d6208)
//
// Simon is the result of the final coding challenge for
// [Free Code Camp's Front-End Development Certification course](https://www.freecodecamp.org).
//
// Development took three days (1-2 days too long!?).
// Put another 1-1/2 days into making it responsive.
//
-->
<main>
<div class="container">
<div class="outer">
<img src="http://dummyimage.com/50x50/000/fff.gif&text=50x50" class="sq-setter-w" />
<div class="inside">
<div class="inside2">
<div class="scoreboard">
<div class="scoreboard-top">
<div class="div-start">Start: <div id="start" tabindex="0"></div></div>
<div class="clearb"></div>
<div>
<span title="In Strict mode, no 2nd chances if you mess up.">Strict: <input id="simon-yolo" type="checkbox" value="on" /></span>&nbsp;
<span title="Turn on cheat mode">Insights: <input id="simon-cheat" type="checkbox" value="off" /></span>
</div>
<div class="clearb"></div>
<div>Speed <small>(1-3)</small>: <input id="simon-speed" type="number" min="1" max="3" value="2" /></div>
</div>
<div class="scoreboard-middle">
<div><div class="game-is"><span>Game is: <span id="game-over">Ready</span></span></div></div>
</div>
<div class="scoreboard-bottom">
<div>Play Count: <span id="play-count"></span></div>
<div class="clearb"></div>
<div>Wins: <span id="game-wins"></span></div>
</div>
</div>
<div class="cheat">
<div class="cheat-first" title="Turn off cheat mode">Insights: <input id="simon-cheat-reverse" type="checkbox" value="on" /></div>
<div>Simon's Plays: <span id="simon-plays"></span></div>
<div>Player's Steps: <span id="player-plays"></span></div>
<div>Previous Scores: <span id="games-previous"></span></div>
</div>
<div class="cells">
<div id="ul" class="cell" tabindex="0"></div>
<div id="ur" class="cell" tabindex="0"></div>
<div id="ll" class="cell" tabindex="0"></div>
<div id="lr" class="cell" tabindex="0"></div>
</div>
</div><!-- inside2 -->
</div><!-- inside -->
<div class="zoom">
<button class="click-zoom-1-00">Zoom Full</button>&nbsp;
<button class="click-zoom-0-75">Zoom 3/4</button>
<button class="click-zoom-0-50">Zoom 1/2</button>&nbsp;
</div>
</div>
<div class="rules">
<p>Simon is now responsive and should work well both full screen and on mobile.</p>
</div>
<div class="rules">
<b>Rules:</b>
Successfully remember (or guess) up to 20 in a row to win.
With 'Strict' mode turned off, you get 1 second chance if you mess up
(Simon will automatically show you the way... <i>yet again</i>).
Now go play and have fun!
</div>
<div class="rules keynav" tabindex="0">
<b><u>Keyboard Navigation:</u></b><br/>
<b>Enter</b>: Begin a new game.<br/>
<b>Space</b>: Begin a new game.<br/>
<b>Escape</b>: Begin a new game.<br/>
<b>S</b>: Toggle Strict Mode and begin a new game.<br/>
<b>R</b> | <b>1</b>: Red cell.<br/>
<b>B</b> | <b>2</b>: Blue cell.<br/>
<b>G</b> | <b>3</b>: Green cell.<br/>
<b>Y</b> | <b>4</b>: Yellow cell.<br/>
</div>
<div class="rules">
<b>History:</b>
<ul>
<li><u>2017-11-19</u></li>
<li>Completed game.</li>
<li>Added keyboard navigation.</li>
<li>Tested in Chrome, Firefox, Opera, and IE Edge.</li>
</ul>
<ul>
<li><u>2017-11-21</u></li>
<li>Added responsiveness. (Took 1+ days.)</li>
<li>Added zoom options (only available when viewing width >=800px).</li>
<li>Refactored audio button assignments (code) into a for-loop.</li>
</ul>
</div>
<div class="rules">
<b>Attribution:</b>
<ul>
<li>Learn More about <a href="https://en.wikipedia.org/wiki/Simon_(game)" target="kdcNewWin">Simon the Game</a>.</li>
<li><a href="https://www.hasbro.com/en-us/product/simon-game:6B0A06E3-5056-9047-F532-6A891FAEBA15" target="kdcNewWin">Buy Simon</a> from Hasbro.</li>
</ul>
</div>
<audio id="simon-audio-0" src="" preload="auto" autoplay="0" type="audio/mpeg"></audio>
<audio id="simon-audio-1" src="" preload="auto" autoplay="0" type="audio/mpeg"></audio>
<audio id="simon-audio-2" src="" preload="auto" autoplay="0" type="audio/mpeg"></audio>
<audio id="simon-audio-3" src="" preload="auto" autoplay="0" type="audio/mpeg"></audio>
</div>
<div>
<ul class="fcc-links">
<li><b>Free Code Camp - KDCinfo</b> (Keith D Commiskey)</li>
<li><a target="kdcNewWin" href="https://www.freecodecamp.org/kdcinfo/front-end-certification">Front-End Development Certificate</a></li>
<li><a target="kdcNewWin" href="https://www.freecodecamp.org/kdcinfo">FCC Public Profile</a></li>
</ul>
</div>
</main>
<footer>
<a href="https://www.freecodecamp.org">FCC Challenge #291</a> - Build a Simon Game
</footer>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment