4
\$\begingroup\$

This is a game of Simon using plain Javascript. I would welcome any comments, suggestions, insights or ideas for improvement. One choice that I made was to have the player's turn compare values against a game array ( simonSays ) by using the increment variable.
If there is a better way to do this I would be very interested. This program also makes extensive use of the setTimeout function, if there are other ways to achieve this effect I would also be very interested.

Also when I was refactoring and trying to cut unnecessary code I was unable to re-use the call to the mute(color) function from the playSound(soundValue, i) and was forced to use a separate function, changeColorOff(i) in this one place. I left the commented code in to show what I tried. I am not clear why this was necessary. Any insights would be appreciated.

The working game is on CodePen.

var $game = document.getElementById('game'); 
var $sound = document.getElementById('sound');
var $display = document.getElementById('display');
var $displayText = document.getElementById('displayText');
var youWin = null, winnerText = null;

var $yellow = document.getElementById("yellow");
var $red = document.getElementById("red");
var $green = document.getElementById("green");
var $blue = document.getElementById("blue");

var $gameOnBtn = document.getElementById('gameOnBtn');
var $startBtn = document.getElementById('startBtn');
var $strictModeBtn = document.getElementById('strictModeBtn');

var powerSound = "http://soundbible.com/grab.php?id=19&type=mp3";
var redSound = " https://s3.amazonaws.com/freecodecamp/simonSound2.mp3";
var blueSound = "https://s3.amazonaws.com/freecodecamp/simonSound4.mp3";
var greenSound = "https://s3.amazonaws.com/freecodecamp/simonSound1.mp3";
var yellowSound = "https://s3.amazonaws.com/freecodecamp/simonSound3.mp3";
var wrongAnsSound = "http://soundbible.com/grab.php?id=1501&type=mp3";
var clickSound = "http://soundbible.com/grab.php?id=1705&type=mp3";
var victorySound = "http://soundbible.com/grab.php?id=1947&type=mp3";

var powerOn = false;
var gameInPlay = false;
var strictMode = false;
var victory = false;

var buttonTimeInterval = 1000;

var simonSays = [];
var currentValue = null;
var increment = 0;

var soundOptions = {
  0: yellowSound,
  1: redSound,
  2: greenSound,
  3: blueSound 
};

var colorOptions = {
  0: $yellow,
  1: $red,
  2: $green,
  3: $blue
};
function flash(color){
  color.style.opacity = 1;
}

function mute(color){
  color.style.opacity = 0.6;
}

function flashBoard(i, l){
  if (i == l) return;

  setTimeout("flash($yellow)", 500);
  setTimeout("flash($red)", 500);
  setTimeout("flash($green)", 500);
  setTimeout("flash($blue)", 500);

  setTimeout("mute($yellow)", 750);
  setTimeout("mute($red)", 750);
  setTimeout("mute($green)", 750);
  setTimeout("mute($blue)", 750);

  i += 1;
  setTimeout("flashBoard(" + i + "," + l + ")", 500);
 } 

function gameReset(){
  flashBoard(0, 3);
  justSound(powerSound);
  $displayText.textContent = '-- --';
}

function powerOnGame(){
  if (!powerOn){
    $gameOnBtn.style.fill = "rgb(250, 0, 20)"; 
    powerOn = true;
    gameReset();
  } else {
    gameOff();
  }
}

function startGame(){
  if (!powerOn){
    return;
  }
  justSound(clickSound);
  if (!gameInPlay){
    $startBtn.style.fill = "rgb(20, 200, 50)"; 
  } else {
    $startBtn.style.fill = "rgb(0, 0, 0)"
    setTimeout(function(){ return $startBtn.style.fill = "rgb(20, 200, 50)";}, 200); 
  }
  resetDisplay(); 
  buttonTimeInterval = 1100;
  simonSays = [];
  playGame();
}

function useStrictMode(){
  if (!powerOn){
    return;
  }
  justSound(clickSound);  
  if (!strictMode){
    $strictModeBtn.style.fill = "rgb(250, 250, 250)"; 
    strictMode = true;
  } else {
    $strictModeBtn.style.fill = "rgb(0, 0, 0)"; 
    strictMode = false;
  }
}

//general button handler that take values from color buttons
function colorPress(colorNum){
  if (!powerOn){
    return;
  }
  var colorNo = colorNum; //not sure why this was needed but it was...
  if (currentValue !== colorNum){
    setTimeout(function(){fail()}, 500);
  } else {
    increment += 1;
    currentValue = simonSays[increment];
  }
  flash(colorOptions[colorNum]);
  setTimeout(function(colorNo){
    justSound(soundOptions[colorNum]);
    mute(colorOptions[colorNum]);
  }, 100);
  if (increment === simonSays.length){
    setTimeout(function(){playGame()}, 1000);
  }
}

function pressYellow(){
  colorPress(0);
}

function pressRed(){
  colorPress(1);
}

function pressGreen(){
  colorPress(2);
}

function pressBlue(){
  colorPress(3);
}

function justSound(soundValue){
  $sound.src = soundValue;
  $sound.load();
  $sound.play();
}

//this is the computer's sound function, incorporates the general sound function and an end to the color flash
//not sure why muteColor does not work... 
function playSound(soundValue, i){
  justSound(soundValue);
//  var muteColor = colorOptions[simonSays[i]];
//  setTimeout('mute(' + muteColor + ')', buttonTimeInterval - 300);
  setTimeout('changeColorOff('+ i + ')', buttonTimeInterval - 200);
}

function changeColorOff(i){
  colorOptions[simonSays[i]].style.opacity = 0.6;
}

function displayProgress(arrLen){
  $displayText.setAttribute('font-size', '50');
  $displayText.textContent = arrLen;

  if (arrLen < 10){
    $displayText.setAttribute('x', '227');
  } else {
    $displayText.setAttribute('x', '213');
  }
}

//iterates through the computer's moves
function startPlaying(i, l){
  if (i == l) return;
  increment = 0;
  currentValue = simonSays[0];
  var callSound = soundOptions[simonSays[i]];
  playSound(callSound, i);
  var flashColor = colorOptions[simonSays[i]];
  flash(flashColor);
  i += 1;
  setTimeout("startPlaying("+ i + ", " + l + ")", buttonTimeInterval);
};

function playGame(){
  gameInPlay = true;
  victory = false;
  if (simonSays.length === 20){
    return gameVictory();
  }
  var simonMove = Math.floor(Math.random() * 4);
  simonSays.push(simonMove);
  startPlaying(0, simonSays.length);
  var simonProgress = simonSays.length;
  if ((simonProgress === 5) || (simonProgress === 9) ){
    buttonTimeInterval -= 250;
  }
  if (simonProgress === 15){
    buttonTimeInterval -= 100;
  }
  displayProgress(simonProgress);
}

function fail(){
  justSound(wrongAnsSound);
  $display.setAttribute("fill", "rgb(200, 0, 0)");
  setTimeout(function(){$display.setAttribute("fill", "rgb(70, 70, 70)");
  }, 1500);
  if (strictMode){
    simonSays = [];
    setTimeout(function(){playGame()}, 2000);
  } else {
    setTimeout(function(){startPlaying(0, simonSays.length)}, 2000);
  }
}

function gameVictory(){
  flashBoard(0, 3);
  justSound(victorySound);
  victory = true;
  $display.setAttribute('fill', 'rgb(5, 17, 142)');
  $displayText.setAttribute('fill', 'rgb(250, 250, 250)');
  $displayText.textContent = "Win!";
  $displayText.setAttribute('font-size', '20');
  winnerText = document.createElementNS("http://www.w3.org/2000/svg", 'text');
  winnerText.setAttribute('x', '220');
  winnerText.setAttribute('y', '290');
  winnerText.setAttribute('fill', 'rgb(250, 250, 250)');
  winnerText.setAttribute('font-size', '20');
  youWin = document.createTextNode("You");
  winnerText.appendChild(youWin);
  $game.appendChild(winnerText);
}

function resetDisplay(){
  $display.setAttribute('fill', 'rgb(70, 70, 70)');
  $displayText.setAttribute('fill', 'rgb(0, 0, 0)');
  if (victory){
    winnerText.parentNode.removeChild(winnerText);
  }
  gameInPlay = true;
}

function gameOff(){
  justSound(clickSound);
  resetDisplay();
  $displayText.textContent = "Off";
  $displayText.setAttribute('font-size', '20');
  $gameOnBtn.style.fill = "rgb(0, 0, 0)"; 
  $strictModeBtn.style.fill = "rgb(0, 0, 0)"; 
  $startBtn.style.fill = "rgb(0, 0, 0)"; 
  simonSays = [];
  victory = false;
  gameInPlay = false;
  strictMode = false;
  powerOn = false;
}

window.onload = gameOff();
body {
  background-color: rgb(145, 163, 193);
}


#gameShell {
  position: absolute;
  top: 75px;
  margin: auto;
  width: 100%;
}

#inner {
  margin: auto;
  width: 70%;
  text-align: center;
}

#yellow {
  opacity: 0.5;
}

#red {
  opacity: 0.5;
}

#green {
  opacity: 0.5;
}

#blue {
  opacity: 0.5;
}
<!doctype html>
<html>
<head>
<title>Draft 1 Simon Shell</title>
<link rel="stylesheet" href="styles.css" >
</head>


<body>
<audio id="sound"></audio>
<div id="gameShell" >
<div id="inner">
<svg id="game" width="600" height="600" viewBox="0 0 600 600">

  <circle cx="300" cy="300" r="270" fill="black" />

  <path id="yellow" d="M300 50 A250 250, 0, 0,1, 550 300
           L 300 300 Z" stroke="black" stroke-width="9" fill="yellow"
           onClick="pressYellow()" />
  <path id="red" d="M550 300 A250 250, 0, 0,1, 300 550
           L 300 300 Z" stroke="black" stroke-width="9" fill="red"
           onClick="pressRed()" />
  <path id="green" d="M300 550 A250 250, 0, 0,1, 50 300
           L 300 300 Z" stroke="black" stroke-width="9" fill="green"
           onClick="pressGreen()" />
  <path id="blue" d="M50 300 A250 250, 0, 0,1, 300 50
           L 300 300 Z" stroke="black" stroke-width="9" fill="blue"
           onClick="pressBlue()" />

  <circle id="innerBackground" cx="300" cy="300" r="120" fill="rgb(244, 244, 20)" stroke="black" stroke-width-"15" />
  <text x="255" y="235" font-family="arial" font-size="30" font-weight="bold" >Simon</text>
  <text x="290" y="320" font-family="arial" font-size="20"  >Start</text>
  <text x="350" y="320" font-family="arial" font-size="20"  >Strict</text>
  <text x="270" y="395" font-family="arial" font-size="20"  >On/Off</text>

  <circle id="startBtn" cx="315" cy="285" r="10" fill="black" stroke="black" stroke-width="5"
    onClick="startGame()" />
  <circle id="strictModeBtn" cx="375" cy="285" r="10" fill="black" stroke="black" stroke-width="5"
    onClick="useStrictMode()" />
  <circle id="gameOnBtn" cx="300" cy="360" r="10" fill="black"
   stroke-width="5"stroke="black" onClick="powerOnGame()" />

  <rect id="display" x="205" y="260" rx="10" ry="20" width="70" height="70" fill="rgb(70, 70, 70)" stroke="black" />
  <text id="displayText" x='225' y='310' font-size='20' >Off</text>

</svg>
</div>
</div>

<h1>Simon</h1>
<script src="logic.js" ></script>
</body>
</html>

\$\endgroup\$
2
  • \$\begingroup\$ If you use a snippet, this would actually be playable right here on CR! \$\endgroup\$
    – RubberDuck
    Commented May 19, 2017 at 15:47
  • \$\begingroup\$ Thanks for the heads up, I assumed that the editors had inset the snippets and I wasn't aware of how to use that feature myself. I will definitely use it in the future, much easier than an external link...thank you. \$\endgroup\$
    – Cameron
    Commented May 21, 2017 at 1:01

1 Answer 1

3
\$\begingroup\$

General Feedback

The code appears to work acceptable. The HTML does have javascript calls inline (e.g. onclick="pressYellow()"). It would be better to separate the event handling logic out into the Javascript.

I see it caches DOM element references instead of querying for DOM elements each time they are needed. This is great.

Suggestions

There are a couple different usages of setTimeout() used. The first is okay:

setTimeout(function(){playGame()}, 1000);

But the extra anonymous/lambda/closure function is unnecessary. A reference to the function can be passed instead:

setTimeout(playGame, 1000);

Then there are places where a string literal is passed - e.g.

setTimeout("flash($yellow)", 500);

but it is recommended not to use that format - see the MDN documentation for setTimeout()

code

An alternative syntax that allows you to include a string instead of a function, which is compiled and executed when the timer expires. This syntax is not recommended for the same reasons that make using eval() a security risk.1

To specify parameters to be passed to the function when it is called, use a partially-applied function:

setTimeout(flash.bind(null, $yellow));

That should also allow you to use the mute function in playSound() as you originally wanted.

setTimeout(mute.bind(null, muteColor), buttonTimeInterval - 300);

Instead of four separate rulesets in the CSS for the opacity:

#yellow {
  opacity: 0.5;
}
#red {
  opacity: 0.5;
}
#green {
  opacity: 0.5;
}
#blue {
  opacity: 0.5;
}

The CSS selectors can be combined into a single ruleset:

#yellow,
#red , 
#green, 
#blue {
  opacity: 0.5;
}

1https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Parameters

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.