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