Freshman Simon Game tutorial

Learn how to build a Simon Game with JavaScript. Tutorial: https://freshman.tech/simon-game

by vishalvasani

HTML

<main class="game">
  <header>
    <h1 class="heading js-heading">Simon Game</h1>
  </header>

  <section class="tile-container js-container unclickable">
    <div class="tile tile-red" data-tile="red"></div>
    <div class="tile tile-green" data-tile="green"></div>
    <div class="tile tile-blue" data-tile="blue"></div>
    <div class="tile tile-yellow" data-tile="yellow"></div>
  </section>

  <footer class="info-section">
    <button class="start-button js-start">Start</button>
    <span class="info js-info hidden"></span>
  </footer>
</main>

<div class="hidden">
  <audio
    src="https://s3.amazonaws.com/freecodecamp/simonSound1.mp3"
    data-sound="red"
  ></audio>
  <audio
    src="https://s3.amazonaws.com/freecodecamp/simonSound2.mp3"
    data-sound="green"
  ></audio>
  <audio
    src="https://s3.amazonaws.com/freecodecamp/simonSound3.mp3"
    data-sound="blue"
  ></audio>
  <audio
    src="https://s3.amazonaws.com/freecodecamp/simonSound4.mp3"
    data-sound="yellow"
  ></audio>
</div>

CSS

html {
  box-sizing: border-box;
}

*,
*::after,
*::before {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

body {
  display: flex;
  justify-content: center;
  font-family: 'Rubik', sans-serif;
}

.game {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.hidden {
  display: none !important;
}

.invisble {
  visibility: hidden;
}

header {
  text-align: center;
  font-size: 18px;
}

.heading {
  font-size: 3em;
}

.tile-container {
  display: grid;
  grid-template-rows: 180px 180px;
  grid-template-columns: 180px 180px;
  grid-gap: 25px;
  position: relative;
  margin-top: 30px;
  margin-bottom: 40px;
}

.unclickable {
  pointer-events: none;
}

.tile-red {
  background-color: #f25022;
  box-shadow: 0 0 0 1px #f25022 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 8px 0 0 #c93a12, 0 8px 0 1px rgba(0, 0, 0, 0.4), 0 8px 8px 1px rgba(0, 0, 0, 0.5);
  transition: 0.15s;
}

.tile-red:hover,
.tile-red:focus {
  background-color: #ff451a;
  box-shadow: 0 0 0 1px #FF3000 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 10px 0 0 #e62b00, 0 10px 0 1px rgba(0, 0, 0, 0.4), 0 10px 8px 1px rgba(0, 0, 0, 0.6);
}

.tile-red:active,
.tile-red.activated {
  box-shadow: 0 0 0 1px #ff5c36 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 0 0 1px rgba(0, 0, 0, 0.4);
  background-color: #ff5c36;
  transform: translateY(10px);
}

.tile-green {
  background-color: #7fba00;
  box-shadow: 0 0 0 1px #7fba00 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 8px 0 0 #638f05, 0 8px 0 1px rgba(0, 0, 0, 0.4), 0 8px 8px 1px rgba(0, 0, 0, 0.5);
  transition: 0.15s;
}

.tile-green:hover,
.tile-green:focus {
  background-color: #8cc218;
  box-shadow: 0 0 0 1px #8cc218 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 10px 0 0 #74a802, 0 10px 0 1px rgba(0, 0, 0, 0.4), 0 10px 8px 1px rgba(0, 0, 0, 0.6)
}

.tile-green:active,
.tile-green.activated {
  box-shadow: 0 0 0 1px #a0ea00 inset, 0 0 0 2px rgba(255, 255, 255, 0.15) inset, 0 0 0 1px...

JavaScript

let sequence = [];
let humanSequence = [];
let level = 0;

const startButton = document.querySelector('.js-start');
const info = document.querySelector('.js-info');
const heading = document.querySelector('.js-heading');
const tileContainer = document.querySelector('.js-container');

function resetGame(text) {
  alert(text);
  sequence = [];
  humanSequence = [];
  level = 0;
  startButton.classList.remove('hidden');
  heading.textContent = 'Simon Game';
  info.classList.add('hidden');
  tileContainer.classList.add('unclickable');
}

function humanTurn(level) {
  tileContainer.classList.remove('unclickable');
  info.textContent = `Your turn: ${level} Tap${level > 1 ? 's' : ''}`;
}

function activateTile(color) {
  const tile = document.querySelector(`[data-tile='${color}']`);
  const sound = document.querySelector(`[data-sound='${color}']`);

  tile.classList.add('activated');
  sound.play();

  setTimeout(() => {
    tile.classList.remove('activated');
  }, 300);
}

function playRound(nextSequence) {
  nextSequence.forEach((color, index) => {
    setTimeout(() => {
      activateTile(color);
    }, (index + 1) * 600);
  });
}

function nextStep() {
  const tiles = ['red', 'green', 'blue', 'yellow'];
  const random = tiles[Math.floor(Math.random() * tiles.length)];

  return random;
}

function nextRound() {
  level += 1;

  tileContainer.classList.add('unclickable');
  info.textContent = 'Wait for the computer';
  heading.textContent = `Level ${level} of 20`;


  const nextSequence = [...sequence];
  nextSequence.push(nextStep());
  playRound(nextSequence);

  sequence = [...nextSequence];
  setTimeout(() => {
    humanTurn(level);
  }, level * 600 + 1000);
}

function handleClick(tile) {
  const index = humanSequence.push(tile) - 1;
  const sound = document.querySelector(`[data-sound='${tile}']`);
  sound.play();

  const remainingTaps = sequence.length - humanSequence.length;

  if (humanSequence[index] !== sequence[index]) {
    resetGame('Oops! Game over, you...