Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div class="simon-game" ng-app="simonGame" ng-controller="SimonController as game" ng-class="{on: game.getPowerState(), strict: game.getStrictState()}">
  <button class="top-left" ng-mousedown="game.pressColor(0)" ng-class="{lit: game.buttons[0].lit}"></button>
  <button class="top-right" ng-mousedown="game.pressColor(1)" ng-class="{lit: game.buttons[1].lit}"></button>
  <button class="bottom-right" ng-mousedown="game.pressColor(2)" ng-class="{lit: game.buttons[2].lit}"></button>
  <button class="bottom-left" ng-mousedown="game.pressColor(3)" ng-class="{lit: game.buttons[3].lit}"></button>
  <div class="spacer-vertical center-x"></div>
  <div class="spacer-horizontal center-y"></div>
  <div class="middle center-both">
    <h1>Simon<sup>&reg;</sup></h1>
    <div class="center-controls center-x">
      <div class="control control-lg">
        <div class="count-display"><span class="center-both" ng-bind="game.getCountText()"></span></div>
        <label>Count</label>
      </div>
      <div class="control control-sm">
        <button class="start" ng-click="game.start()"></button>
        <label>Start</label>
      </div>
      <div class="control control-sm">
        <button class="strict-toggler" ng-click="game.toggleStrict()"></button>
        <label>Strict</label>
      </div>
      <div class="strict-indicator"></div>
    </div>
    <div class="control on-off center-x">
      <label>Off</label>
      <div ng-click="game.togglePower()" class="on-off-wrapper">
        <button ng-class="{on: game.getPowerState()}"></button>
      </div>
      <label>On</label>
    </div>
  </div>
</div>
              
            
!

CSS

              
                /****************************
* RESET
*****************************/
/* https://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

/****************************
* GENERAL & GAME BACKGROUND
*****************************/
.simon-game {
  margin: auto;
  margin-top: 20px;
  height: 400px;
  width: 400px;
  border-radius: 50%;
  padding: 20px;
  position: relative;
  box-shadow: 0 0 20px 5px #888;
}

.spacer-vertical {
  width: 25px;
  height: 400px;
}

.spacer-horizontal {
  width: 400px;
  height: 25px;
}

.simon-game,
.spacer-vertical,
.spacer-horizontal {
  background-color: #222;
}

.simon-game>div,
.simon-game>button {
  position: absolute;
}

button {
  border: 0;
  outline: 0 !important;
  cursor: pointer;
}

/************************
* COLOR BUTTONS
************************/
.simon-game>button {
  width: 200px;
  height: 200px;
}

button.top-left {
  background-color: #090;
  border-radius: 100% 0 0 0;
  right: 50%;
}

.simon-game.on button.top-left.lit {
  background-color: #0c0;
}

button.top-right {
  background-color: #bc2000;
  left: 50%;
  border-radius: 0 100% 0 0;
}

.simon-game.on button.top-right.lit {
  background-color: red;
}

button.bottom-right {
  background-color: #0002bc;
  top: 50%;
  left: 50%;
  border-radius: 0 0 100% 0;
}

.simon-game.on button.bottom-right.lit {
  background-color: blue;
}

button.bottom-left {
  background-color: #bcbc20;
  top: 50%;
  border-radius: 0 0 0 100%;
}

.simon-game.on button.bottom-left.lit {
  background-color: yellow;
}

/************************
* MIDDLE
************************/
.middle {
  height: 200px;
  width: 200px;
  background-color: #bbb;
  border: 15px solid #222;
  border-radius: 50%;
  font-family: 'Roboto', sans-serif;
}

.middle label {
  text-transform: uppercase;
  font-size: 12px;
}

h1 {
  text-align: center;
  font-family: 'Ultra', serif;
  color: #111;
  font-size: 36px;
  margin-top: 36px;
  margin-bottom: 14px;
}

h1 sup {
  font-weight: normal;
  font-size: 55%;
  vertical-align: super;
}

.control.on-off {
  position: absolute;
  bottom: 15px;
}

.on-off-wrapper {
  display: inline-block;
  position: relative;
  top: 4px;
  width: 35px;
  height: 19px;
  background-color: #0a0a0a;
  cursor: pointer;
}

.on-off button {
  position: absolute;
  display: inline-block;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  background-color: #0089ff;
  transition: left 0.1s;
}

.on-off button.on {
  left: 18px;
}

/************************
* CENTER CONTROLS
************************/
.center-controls {
  width: 155px;
  position: absolute;
}

.center-controls .control {
  text-align: center;
  display: inline-block;
  margin-left: 5px;
}

div.control:first-of-type {
  margin-left: 0;
}

.center-controls .control>div,
.center-controls .control>button {
  border: 4px solid #222;
}

.control-lg {
  width: 60px;
}

.count-display {
  position: relative;
  width: 50px;
  height: 30px;
  border-radius: 10px;
  background-color: #300;
}

.count-display>span {
  position: absolute;
  font-size: 20px;
  font-family: monospace;
  font-weight: bold;
  color: red;
  cursor: default;
}

.control-sm button {
  display: block;
  margin: auto;
  height: 25px;
  width: 25px;
  border-radius: 50%;
}

button.start {
  background-color: red;
}

button.strict-toggler {
  background-color: yellow;
}

.strict-indicator {
  position: relative;
  left: 146px;
  top: -50px;
  background: #300;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  border: 3px solid #222;
}

.simon-game.strict .strict-indicator {
  background-color: red;
}

/************************
* UTILITY
************************/
/* 
.center-* rules assume 'position: absolute' and a non-static ancestor */
.center-x {
  left: 50%;
  transform: translateX(-50%);
}

.center-y {
  top: 50%;
  transform: translateY(-50%);
}

.center-both {
  top: 50%;
  left: 50%;
  transform: translateX(-50%) translateY(-50%);
}
              
            
!

JS

              
                /*
simonGame object closure.
This object encapsulates the game logic, exposing necessary information about the current game state and data to the UI.

The gameState object within serves as an interface between the UI and simonGame.
The UI can set the state as necessary when a game event is waiting on user interaction.

PROPERTIES
  * states - Available states in form {stateName: intCode, ...}. 
             Alias to gameState.states. Should be treated as read-only.

METHODS
  * getRound()    - Return current round number.
  
  * getSequence() - Return entire current light sequence, as array of integers 0-3 for button indices.
  
  * getState()    - Return current state, as integer code (a value from states)
  
  * getStrict()   - Return status of strict play, as Boolean
  
  * isState(s)    - Return true if current state is s, a state code.
                    Equivalent to (simonGame.getState() == simonGame.states.someState)
                    
  * nextRound()   - Start next round. Should be called once the player has correctly input the sequence.
  
  * press(btnI)   - Should be called when the user presses a button, represented by its integer code 0-3.
                    Returns true if the press was registered, i.e. the game is in a state where a user press
                    can be responded to by the UI.
                    
  * setState(s)   - Set the current game state. Should be passed a value from simonGame.states.
  
  * start()       - Starts the game.
  
  * togglePower() - Toggle 'power' on or off, which resets game data and state appropriately.
  
  * toggleStrict() - Toggle strict play on or off. Can be called at any time.
*/

var simonGame = (function() {
  /*
  gameState closure. Any outside interaction with this object is via simonGame methods described above.
  
  PROPERTIES
    * states       - Available states in form {stateName: intCode, ...}. 
    * (each state) - Each state from 'states' is made available as a property of this object for ease of use.
                     Example: gameState.states.powerOn is equivalent to gameState.powerOn
  
  METHODS
    * getState()   - Return current state code.
    * setState(s)  - Set current state code. Should be passed a value from `states`
                     Example: ganeState.setState(gameState.powerOn)
  */
  var gameState = (function() {
    var states = {
      powerOff: 0,
      powerOn: 1,
      start: 2,
      addRandom: 3,
      comDemo: 4,
      playerSequence: 5,
      mistake: 6,
      allCorrect: 7,
      win: 8
    };

    var state = states.powerOff;

    var retObj = {
      getState: function() {
        return state;
      },
      setState: function(n) {
        console.log('set state ' + n);
        if (n >= 0 && n <= states.win) {
          state = n;
        }
      },
      states: states,
    };

    for (var s in states)
      retObj[s] = states[s];

    return retObj;
  })(); // End gameState closure

  var gs = gameState;
  var round = 0;
  var pressN = 0;
  var strict = false;
  var sequence = [];
  var MAXROUND = 20;

  /*
  STATE SETS
  comDemo
  win
  */
  function nextRound() {
    pressN = 0;

    if (round + 1 > MAXROUND) {
      gs.setState(gs.win);
    } else {
      round += 1;
      sequence.push(Math.floor(Math.random() * 3.99));
      gs.setState(gs.comDemo);
    }
  }

  /*
  Return true if press registered, false otherwise.
  Valid states for a press are powerOn and playerSequence.
  Presses are registed at powerOn state so the user can "test" the buttons, but no change is made to game data or state.
  
  STATE SETS
  mistake
  allCorrect
  */
  function press(btnI) {
    var s = gs.getState();

    if (!(s == gs.powerOn || s == gs.playerSequence)) {
      return false;
    }

    if (s != gs.playerSequence)
      return true;

    pressN += 1;

    if (btnI != sequence[pressN - 1]) {
      pressN = 0;
      gs.setState(gs.mistake);
    } else if (pressN == sequence.length) {
      gs.setState(gs.allCorrect);
    }

    return true;
  }

  // Reset game to initial state
  function reset() {
    sequence = [];
    round = 0;
    pressN = 0;
  }

  function start() {
    reset();
    nextRound();
  }

  /*
  STATE SETS
  powerOn
  powerOff
  */
  function togglePower() {
    if (gs.getState() == gs.powerOff) {
      gs.setState(gs.powerOn);
      reset();
    } else {
      gs.setState(gs.powerOff);
      strict = false;
    }
  }

  // See closure documentation above.
  return {
    getRound: function() {
      return round;
    },
    getSequence: function() {
      return sequence
    },
    getState: gs.getState,
    getStrict: function() {
      return strict;
    },
    isState: function(s) {
      return gs.getState() == s;
    },
    nextRound: nextRound,
    press: press,
    setState: gs.setState,
    start: start,
    states: gs.states,
    togglePower: togglePower,
    toggleStrict: function() {
      strict = !strict;
    },
  };
})();

/* 
simonGame Angular app closure.
Handles view/UI aspects of game.
*/
(function() {
  /************************
  * INITIALIZATION
  ************************/
  var app = angular.module('simonGame', []);
  
  var ColorButton = function(soundUrl) {
    this.lit = false;
    this.sound = new Audio(soundUrl);
    this.light = function(disableSound) {
      if (!disableSound)
        this.sound.play();
      this.lit = true;
    };
  };

  /* 
    CONFIG object.
    
    playerPressMS     - How long button remains lit when the player presses it.
    comPressMS        - How long button remains lit when the com lights it.
    comTimeoutMS      - How long between button lights when the com demos a sequence.
                        Set initially in reset()
    startComTimeoutMS - Initial setting for comTimeoutMS. Returned to when game reset.
    incrementRounds   - Round numbers at which comTimeoutMS is incremented (becomes faster)
    speedIncrement    - Time (in ms) to subtract from comTimeoutMS at rounds in incrementRounds.
  */
  var CONFIG = {
    playerPressMS: 400,
    comPressMS: 600,
    startComTimeoutMS: 1100,
    comTimeoutMS: null,
    speedIncrement: 150,
    incrementRounds: [5, 9, 13],
  };

  /************************
  * CONTROLLER
  ************************/
  app.controller('SimonController', function($timeout) {
    var ctrl = this;
    var sg = simonGame;
    var timeoutStack = [];
    
    // Sound modified from https://s3.amazonaws.com/freecodecamp/simonSound4.mp3
    var mistakeSound = new Audio('http://adamcot.com/static/audio/simon_mistake.mp3');
    
    // Sound modified from http://www.soundjay.com/switch/sounds/switch-4.mp3
    var switchSound = new Audio();
    switchSound.src = 'data:audio/mp3;base64,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';

    this.buttons = [
      new ColorButton('https://s3.amazonaws.com/freecodecamp/simonSound1.mp3'),
      new ColorButton('https://s3.amazonaws.com/freecodecamp/simonSound2.mp3'),
      new ColorButton('https://s3.amazonaws.com/freecodecamp/simonSound3.mp3'),
      new ColorButton('https://s3.amazonaws.com/freecodecamp/simonSound4.mp3'),
    ];

    /*
    Pop any timeouts (promise objects) from timeoutStack and cancel them.
    Should be called when power turned off or game reset with 'start'.
    */
    function clearTimeouts() {
      var ids = timeoutStack.length;
      for (var i = 0; i < ids; i++)
        $timeout.cancel(timeoutStack.pop());
    }

    this.getCountText = function() {
      var txt = '';
      if (this.getPowerState()) {
        txt = (sg.isState(sg.states.powerOn)) ? '--' : sg.getRound();
      }

      return txt;
    };

    function doComDemo() {
      lightSequence(sg.getSequence(), CONFIG.comPressMS, CONFIG.comTimeoutMS);
    };

    this.getPowerState = function() {
      return !sg.isState(sg.states.powerOff);
    }

    this.getStrictState = function() {
      return sg.getStrict();
    }

    /* 
    If round is in CONFIG.incrementRounds, increment comDemo speed by value in CONFIG.speedIncrement
    Should be called at the start of each round.
    */
    function incrementSpeed() {
      CONFIG.incrementRounds.map(function(r) {
        if (sg.getRound() == r) {
          CONFIG.comTimeoutMS -= CONFIG.speedIncrement;
        }
      });
    }

    /* 
    Light this.buttons[i] for `ms` miliseconds. 
    Options is an object as described below.
    
    OPTIONS
      disableSound (bool) - Light the button but don't play its sound
      nextState (int)     - After lighting the button, set game state to this value.
    */
    function lightColor(i, ms, options) {
      var btn = ctrl.buttons[i];
      options = (!options) ? {} : options;
      var nextState = options.nextState;
      btn.light(options.disableSound);

      timeout(function() {
        btn.lit = false;
        if (typeof nextState != 'undefined') {
          sg.setState(nextState);
        }
      }, ms);
    };

    /*
    Light each button index found in seq for lightMS miliseconds, with timeoutMS
    between each light. `i` is used for recursion and may be omitted by an outside caller.
    */
    function lightSequence(seq, lightMS, timeoutMS, i) {
      i = (typeof i == 'undefined') ? 0 : i;

      if (i < seq.length - 1) {
        lightColor(seq[i], lightMS);
        timeout(function() {
          lightSequence(seq, lightMS, timeoutMS, i + 1);
        }, timeoutMS);
      } else if (i == seq.length - 1) {
        lightColor(seq[i], lightMS, {
          nextState: sg.states.playerSequence
        });
      }
    }

    this.pressColor = function(i) {
      var ms = CONFIG.playerPressMS;

      if (!sg.press(i))
        return;

      switch (sg.getState()) {
        case sg.states.allCorrect:
          lightColor(i, ms);

          timeout(function() {
            sg.nextRound();
            incrementSpeed();
            if (sg.isState(sg.states.comDemo))
              doComDemo();
            else if (sg.isState(sg.states.win))
              win();
          }, 1600);
          break;
        case sg.states.mistake:
          lightColor(i, 1000, {
            disableSound: true
          });
          mistakeSound.play();

          timeout(function() {
            if (sg.getStrict()) {
              ctrl.start();
            } else {
              sg.setState(sg.states.comDemo);
              doComDemo();
            }
          }, 1500);
          break;
        default:
          lightColor(i, ms);
          break;
      }
    };

    /* Reset UI to initial state. */
    function reset() {
      clearTimeouts();
      CONFIG.comTimeoutMS = CONFIG.startComTimeoutMS;
      ctrl.buttons.map(function(btn) {
        btn.lit = false;
      });
    }

    this.start = function() {
      if (ctrl.getPowerState()) {
        reset();
        sg.start();
        doComDemo();
      }
    };

    /*
    Calls $timeout(func, ms) and pushes resulting promise object to timeoutStack.
    */
    function timeout(func, ms) {
      timeoutStack.push($timeout(func, ms));
    }

    this.togglePower = function() {
      switchSound.play();
      sg.togglePower();
      reset();
    };

    this.toggleStrict = function() {
      if (ctrl.getPowerState())
        sg.toggleStrict();
    }

    /* 
    Play custom light sequence twice, then reset game. 
    `second` used for recursion. Outside callers should omit it.
    */
    function win(second) {
      var buttons = ctrl.buttons;

      buttons[1].light();

      timeout(function() {
        buttons[2].light();
      }, 100);

      timeout(function() {
        buttons[3].light();
      }, 225);

      timeout(function() {
        buttons[0].light();
      }, 450);

      timeout(function() {
        buttons.map(function(btn) {
          btn.lit = false;
        })
      }, 1000);
      
      if (!second)
        timeout(function() { win(true); }, 2000);
      else
        timeout(ctrl.start, 2000);
    }
  });
})();
              
            
!
999px

Console