├── .gitignore ├── Gruntfile.js ├── README.md ├── css └── style.css ├── index.html ├── js ├── app │ └── simon.js ├── lib │ ├── jquery.min.js │ └── require.min.js └── main.js ├── package.json └── sounds ├── 1.mp3 ├── 1.ogg ├── 2.mp3 ├── 2.ogg ├── 3.mp3 ├── 3.ogg ├── 4.mp3 └── 4.ogg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | var file_list = [ 5 | 'js/simon.js', 6 | 'Gruntfile.js' 7 | ]; 8 | 9 | // Project configuration. 10 | grunt.initConfig({ 11 | pkg: grunt.file.readJSON('package.json'), 12 | 13 | jshint: { 14 | options: { 15 | // Enforcing options 16 | curly: true 17 | // ,forin: true 18 | ,eqeqeq: true 19 | ,immed: true 20 | ,latedef: 'nofunc' 21 | ,newcap: true 22 | ,noarg: true 23 | ,noempty: true 24 | ,quotmark: 'single' 25 | // ,undef: true 26 | ,unused: true 27 | ,strict: true 28 | ,trailing: true 29 | ,browser: true 30 | ,globals: { 31 | node: true 32 | ,jQuery: true 33 | ,global: true 34 | ,Meteor: true 35 | ,Accounts: true 36 | ,_: true 37 | ,Ideas: true 38 | ,RouteController: true 39 | ,HomeController: true 40 | ,using: true 41 | ,define: true 42 | ,Notifications: true 43 | ,Session: true 44 | ,Router: true 45 | ,Template: true 46 | ,process: true 47 | } 48 | 49 | // Relaxing options 50 | ,boss: true 51 | ,laxcomma: true 52 | ,globalstrict: true 53 | }, 54 | uses_defaults: file_list.concat(['Gruntfile.js']) 55 | }, 56 | 57 | watch: { 58 | scripts: { 59 | files: file_list, 60 | tasks: ['jshint'], 61 | options: { 62 | spawn: false 63 | ,interrupt: true 64 | }, 65 | }, 66 | }, 67 | }); 68 | 69 | // Load tasks 70 | grunt.loadNpmTasks('grunt-contrib-jshint'); 71 | grunt.loadNpmTasks('grunt-contrib-watch'); 72 | 73 | // Default task(s). 74 | grunt.registerTask('default', ['jshint']); 75 | 76 | // On file change only run jshint on the changed file not the whole thing 77 | grunt.event.on('watch', function(action, filepath) { 78 | grunt.config('jshint.uses_defaults', filepath); 79 | }); 80 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Javascript Simon 2 | ================ 3 | 4 | Demo at: 5 | http://kellyking.me/projects/simon -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, serif; 3 | color: #333; 4 | -webkit-user-select: none; /* Chrome/Safari */ 5 | -moz-user-select: none; /* Firefox */ 6 | -ms-user-select: none; /* IE10+ */ 7 | -o-user-select: none; 8 | user-select: none; 9 | } 10 | 11 | h1 { 12 | margin: 1em 0 2em; 13 | text-align: center; 14 | } 15 | 16 | ul { 17 | list-style: none; 18 | } 19 | 20 | ul, li { 21 | padding: 0; 22 | margin: 0; 23 | } 24 | 25 | p[data-action="lose"] { 26 | display: none; 27 | } 28 | 29 | .active { 30 | opacity: 1 !important; 31 | } 32 | 33 | .clearfix { 34 | width: 100%; 35 | clear: both; 36 | } 37 | 38 | .wrapper { 39 | width: 540px; 40 | margin: 0 auto; 41 | } 42 | .container { 43 | width: 305px; 44 | } 45 | 46 | .simon { 47 | background: #fff; 48 | position: relative; 49 | float: left; 50 | margin-right: 3em; 51 | width: 302px; 52 | height: 295px; 53 | -webkit-border-radius: 150px 150px 150px 150px; 54 | border-radius: 150px 150px 150px 150px; 55 | -moz-box-shadow: 2px 1px 12px #aaa; 56 | -webkit-box-shadow: 2px 1px 12px #aaa; 57 | -o-box-shadow: 2px 1px 12px #aaa; 58 | box-shadow: 2px 1px 12px #aaa; 59 | } 60 | 61 | .tile { 62 | opacity: 0.6; 63 | -webkit-transition: opacity 250ms ease; 64 | -moz-transition: opacity 250ms ease; 65 | -ms-transition: opacity 250ms ease; 66 | -o-transition: opacity 250ms ease; 67 | transition: opacity 250ms ease; 68 | } 69 | 70 | .tile.lit { 71 | opacity: 1; 72 | } 73 | 74 | .red, .blue, .yellow, .green { 75 | height: 290px; 76 | -webkit-border-radius: 150px 150px 150px 150px; 77 | border-radius: 150px 150px 150px 150px; 78 | position: absolute; 79 | text-indent: 10000px; 80 | } 81 | 82 | .red:hover, .blue:hover, .yellow:hover, .green:hover { 83 | border: 2px solid black 84 | } 85 | 86 | .red { 87 | background: #FF5643; 88 | clip: rect(0px, 300px, 150px, 150px); 89 | width: 296px; 90 | } 91 | 92 | .blue { 93 | background: dodgerblue; 94 | clip: rect(0px, 150px, 150px, 0px); 95 | width: 300px; 96 | } 97 | 98 | .yellow { 99 | background: #FEEF33; 100 | clip: rect(150px, 150px, 300px, 0px); 101 | width: 300px; 102 | } 103 | 104 | .green { 105 | background: #BEDE15; 106 | clip: rect(150px,300px, 300px, 150px); 107 | width: 296px; 108 | } 109 | 110 | .game-info { 111 | margin-top: 90px; 112 | } 113 | 114 | .game-info button { 115 | width: 5em; 116 | box-sizing: border-box; 117 | font-size: 1.4em; 118 | -webkit-border-radius: 10px 10px 10px 10px; 119 | border-radius: 10px 10px 10px 10px; 120 | background: #6DABE8; 121 | border: none; 122 | padding: 0.3em 0.6em; 123 | } 124 | 125 | .game-info button:hover { 126 | background: #78BCFF; 127 | } 128 | 129 | .game-options h2 { 130 | margin-top: 30px; 131 | margin-bottom: 0; 132 | } 133 | 134 | .game-options input[type="radio"] { 135 | margin-right: 10px; 136 | } 137 | 138 | .hoverable:hover { 139 | cursor: pointer; 140 | } 141 | 142 | footer { 143 | position: absolute; 144 | bottom: 20px; 145 | width: 540px; 146 | clear: both; 147 | text-align: center; 148 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simon Says - JavaScript 7 | 8 | 9 | 10 | 11 | Fork me on GitHub 12 |
13 |

Simon Says

14 |
15 |
16 |
    17 |
  • 18 |
  • 19 |
  • 20 |
  • 21 |
22 |
23 |
24 |
25 |

Round: 0

26 | 27 |

Sorry, you lost after rounds!

28 |
29 |
30 |

Game Options:

31 | Normal
32 | Sound Only
33 | Light Only
34 | Free board 35 |
36 |
37 | 40 |
41 | 42 | 52 | 53 | -------------------------------------------------------------------------------- /js/app/simon.js: -------------------------------------------------------------------------------- 1 | define(['jquery'], function($) { 2 | 'use strict'; 3 | 4 | var Simon = { 5 | sequence: [], 6 | copy: [], 7 | round: 0, 8 | active: true, 9 | mode: 'normal', 10 | 11 | init: function() { 12 | var that = this; 13 | $('[data-action=start]').on('click', function() { 14 | that.startGame(); 15 | }); 16 | $('input[name=mode]').on('change', function(e) { 17 | that.changeMode(e); 18 | }); 19 | }, 20 | 21 | startGame: function() { 22 | this.sequence = []; 23 | this.copy = []; 24 | this.round = 0; 25 | this.active = true; 26 | $('p[data-action="lose"]').hide(); 27 | this.newRound(); 28 | }, 29 | 30 | // add a new color to the sequence and animate it to the user 31 | newRound: function() { 32 | $('[data-round]').text(++this.round); 33 | this.sequence.push(this.randomNumber()); 34 | this.copy = this.sequence.slice(0); 35 | this.animate(this.sequence); 36 | }, 37 | 38 | // the game is controlled primarily through this function, along with checkLose(). 39 | // Since the player can never actually "win", we just listen for clicks as the user 40 | // plays the sequence and each time, check if they lost 41 | registerClick: function(e) { 42 | var desiredResponse = this.copy.shift(); 43 | var actualResponse = $(e.target).data('tile'); 44 | this.active = (desiredResponse === actualResponse); 45 | this.checkLose(); 46 | }, 47 | 48 | // three possible situations: 49 | // 1. The user clicked the wrong color (end the game) 50 | // 2. The user entered the right color, but is not finished with the sequence (do nothing) 51 | // 3. The user entered the right color and just completed the sequence (start a new round) 52 | checkLose: function() { 53 | // copy array will be empty when user has successfully completed sequence 54 | if (this.copy.length === 0 && this.active) { 55 | this.deactivateSimonBoard(); 56 | this.newRound(); 57 | 58 | } else if (!this.active) { // user lost 59 | this.deactivateSimonBoard(); 60 | this.endGame(); 61 | } 62 | }, 63 | 64 | endGame: function() { 65 | // notify the user that they lost and change the "round" text to zero 66 | $('p[data-action=lose]').show(); 67 | $($('[data-round]').get(0)).text('0'); 68 | }, 69 | 70 | changeMode: function(e) { 71 | this.mode = e.target.value; 72 | }, 73 | 74 | /*----------------- Helper functions -------------------*/ 75 | 76 | // allow user to interact with the game 77 | activateSimonBoard: function() { 78 | var that = this; 79 | $('.simon') 80 | .on('click', '[data-tile]', function(e) { 81 | that.registerClick(e); 82 | }) 83 | 84 | .on('mousedown', '[data-tile]', function(){ 85 | $(this).addClass('active'); 86 | that.playSound($(this).data('tile')); 87 | }) 88 | 89 | .on('mouseup', '[data-tile]', function(){ 90 | $(this).removeClass('active'); 91 | }); 92 | 93 | $('[data-tile]').addClass('hoverable'); 94 | }, 95 | 96 | // prevent user from interacting until sequence is done animating 97 | deactivateSimonBoard: function() { 98 | if (this.mode !== 'free-board') { 99 | $('.simon') 100 | .off('click', '[data-tile]') 101 | .off('mousedown', '[data-tile]') 102 | .off('mouseup', '[data-tile]'); 103 | 104 | $('[data-tile]').removeClass('hoverable'); 105 | } 106 | }, 107 | 108 | animate: function(sequence) { 109 | var i = 0; 110 | var that = this; 111 | var interval = setInterval(function() { 112 | that.playSound(sequence[i]); 113 | that.lightUp(sequence[i]); 114 | 115 | i++; 116 | if (i >= sequence.length) { 117 | clearInterval(interval); 118 | that.activateSimonBoard(); 119 | } 120 | }, 600); 121 | }, 122 | 123 | lightUp: function(tile) { 124 | if (this.mode !== 'sound-only') { 125 | var $tile = $('[data-tile=' + tile + ']').addClass('lit'); 126 | window.setTimeout(function() { 127 | $tile.removeClass('lit'); 128 | }, 300); 129 | } 130 | 131 | }, 132 | 133 | // we are embedding the sound file on the fly for the following benefits: 134 | // 1. ability to play multiple sounds in a row without waiting for the first to complete, 135 | // 2.