├── .gitignore ├── img ├── logo.png ├── og-image.png └── default │ ├── logo-bw.png │ ├── monsters-01.png │ ├── monsters-02.png │ ├── monsters-03.png │ ├── monsters-04.png │ ├── monsters-05.png │ ├── monsters-06.png │ ├── monsters-07.png │ ├── monsters-08.png │ ├── monsters-09.png │ ├── monsters-10.png │ ├── monsters-11.png │ ├── monsters-12.png │ ├── monsters-13.png │ ├── monsters-14.png │ ├── monsters-15.png │ └── monsters-16.png ├── package.json ├── _sass ├── sass │ ├── _social.scss │ ├── _reset.scss │ ├── memory.scss │ ├── _base.scss │ ├── _fusionad.scss │ ├── _layout.scss │ ├── _config.scss │ └── _game.scss └── config.rb ├── Gruntfile.js ├── js ├── classList.min.js ├── classList.js ├── memory.min.js └── memory.js ├── README.md ├── css ├── memory.min.css └── memory.css └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | _sass/.sass-cache/ -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/og-image.png -------------------------------------------------------------------------------- /img/default/logo-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/logo-bw.png -------------------------------------------------------------------------------- /img/default/monsters-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-01.png -------------------------------------------------------------------------------- /img/default/monsters-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-02.png -------------------------------------------------------------------------------- /img/default/monsters-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-03.png -------------------------------------------------------------------------------- /img/default/monsters-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-04.png -------------------------------------------------------------------------------- /img/default/monsters-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-05.png -------------------------------------------------------------------------------- /img/default/monsters-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-06.png -------------------------------------------------------------------------------- /img/default/monsters-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-07.png -------------------------------------------------------------------------------- /img/default/monsters-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-08.png -------------------------------------------------------------------------------- /img/default/monsters-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-09.png -------------------------------------------------------------------------------- /img/default/monsters-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-10.png -------------------------------------------------------------------------------- /img/default/monsters-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-11.png -------------------------------------------------------------------------------- /img/default/monsters-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-12.png -------------------------------------------------------------------------------- /img/default/monsters-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-13.png -------------------------------------------------------------------------------- /img/default/monsters-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-14.png -------------------------------------------------------------------------------- /img/default/monsters-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-15.png -------------------------------------------------------------------------------- /img/default/monsters-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmenick/Memory/HEAD/img/default/monsters-16.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memory", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "grunt": "^0.4.5", 6 | "grunt-concurrent": "^1.0.0", 7 | "grunt-contrib-cssmin": "^0.10.0", 8 | "grunt-contrib-uglify": "^0.6.0", 9 | "grunt-contrib-watch": "^0.6.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /_sass/sass/_social.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | SOME SOCIAL STYLES 3 | ============================================================================= */ 4 | 5 | .fb-like, 6 | .twitter-share-button { 7 | display: inline-block; 8 | vertical-align: middle; 9 | } 10 | 11 | .fb-like { 12 | margin-right: 10px; 13 | } -------------------------------------------------------------------------------- /_sass/config.rb: -------------------------------------------------------------------------------- 1 | # syntax 2 | preferred_syntax = :scss 3 | 4 | # paths 5 | http_path = "/" 6 | css_dir = "../css" 7 | images_dir = "../img" 8 | javascripts_dir = "../js" 9 | sass_dir = "./sass" 10 | 11 | # style 12 | line_comments = false 13 | output_style = :expanded 14 | 15 | # requirements 16 | require 'sassy-math' 17 | 18 | # autoprefixer 19 | require 'autoprefixer-rails' 20 | on_stylesheet_saved do |file| 21 | css = File.read(file) 22 | File.open(file, 'w') do |io| 23 | io << AutoprefixerRails.process(css) 24 | end 25 | end -------------------------------------------------------------------------------- /_sass/sass/_reset.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | RESET, BOX SIZING, & CLEARFIX 3 | ============================================================================= */ 4 | 5 | html, body, div, span, h1, h2, h3, h4, h5, h6, p, a, small, img { 6 | margin: 0; 7 | padding: 0; 8 | border: 0; 9 | font: inherit; 10 | } 11 | 12 | *, 13 | *::before, 14 | *::after { 15 | box-sizing: border-box; 16 | } 17 | 18 | .clearfix:after { 19 | content: ""; 20 | display: table; 21 | clear: both; 22 | } -------------------------------------------------------------------------------- /_sass/sass/memory.scss: -------------------------------------------------------------------------------- 1 | // ============================================================================= 2 | // CONFIG 3 | // ============================================================================= 4 | 5 | @import "_config"; 6 | 7 | // ============================================================================= 8 | // PARTIALS 9 | // ============================================================================= 10 | 11 | @import "_reset"; 12 | @import "_base"; 13 | @import "_layout"; 14 | @import "_game"; 15 | @import "_social"; 16 | @import "_fusionad"; -------------------------------------------------------------------------------- /_sass/sass/_base.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | BASE 3 | ============================================================================= */ 4 | 5 | body, 6 | html { 7 | height: 100%; 8 | } 9 | 10 | body { 11 | color: $color--grey; 12 | background-color: rgb(40,170,220); 13 | font-family: $font-family--primary; 14 | font-size: $font-size--medium; 15 | } 16 | 17 | h1, 18 | h2, 19 | h3, 20 | h4, 21 | h5, 22 | h6 { 23 | font-weight: 400; 24 | } 25 | 26 | a { 27 | text-decoration: none; 28 | } 29 | 30 | img { 31 | display: block; 32 | max-width: 100%; 33 | height: auto; 34 | } -------------------------------------------------------------------------------- /_sass/sass/_fusionad.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | FUSION ADS 3 | ============================================================================= */ 4 | 5 | /** 6 | * Fusion ads styles 7 | * 8 | * These are all the styles for my fusion ads. I'd reccommend deleting them if 9 | * you are going to use this in your own app, because they are useless and you 10 | * shouldn't be displaying my ad on your app/site in the first place. Thanks! 11 | */ 12 | 13 | #fusionads { 14 | display: inline-block; 15 | padding: 5px; 16 | background: rgb(255,255,255); 17 | font-size: $font-size--xxsmall; 18 | line-height: $line-height--small; 19 | text-align: left; 20 | } 21 | 22 | #fusionads .fusion-wrap { 23 | display: block; 24 | margin: 0 0 5px 0;; 25 | width: 130px; 26 | } 27 | 28 | #fusionads a.fusion-text { 29 | display: block; 30 | color: $color--grey; 31 | } 32 | 33 | #fusionads a.fusion-img { 34 | display: block; 35 | margin-bottom: 5px; 36 | width: 130px; 37 | height: 100px; 38 | background-color: #fff; 39 | } 40 | 41 | #fusionads a.fusion-img img { 42 | display: block; 43 | margin: 0 0 10px 0; 44 | } 45 | 46 | #fusionads a.fusion-poweredby { 47 | color: $color--blue; 48 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | uglify: { 5 | dist: { 6 | files: { 7 | 'js/classList.min.js': ['js/classList.js'], 8 | 'js/memory.min.js': ['js/memory.js'] 9 | } 10 | } 11 | }, 12 | cssmin: { 13 | my_target: { 14 | files: [{ 15 | expand: true, 16 | cwd: 'css/', 17 | src: ['*.css', '!*.min.css'], 18 | dest: 'css/', 19 | ext: '.min.css' 20 | }] 21 | } 22 | }, 23 | watch: { 24 | 'jsfiles': { 25 | files: ['js/classList.js', 'js/memory.js'], 26 | tasks: ['uglify'] 27 | }, 28 | 'cssfiles': { 29 | files: ['css/memory.css'], 30 | tasks: ['cssmin'] 31 | } 32 | }, 33 | concurrent: { 34 | options: { 35 | logConcurrentOutput: true 36 | }, 37 | prod: { 38 | tasks: ["watch:jsfiles", "watch:cssfiles"] 39 | } 40 | } 41 | }); 42 | grunt.loadNpmTasks('grunt-contrib-uglify'); 43 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 44 | grunt.loadNpmTasks('grunt-contrib-watch'); 45 | grunt.loadNpmTasks('grunt-concurrent'); 46 | grunt.registerTask('default', ['uglify', 'cssmin']); 47 | grunt.registerTask('prod', ['concurrent:prod']); 48 | } -------------------------------------------------------------------------------- /_sass/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | LAYOUT 3 | ============================================================================= */ 4 | 5 | /* wrapper */ 6 | 7 | .wrapper { 8 | margin: 0 auto; 9 | width: 100%; 10 | min-width: 480px; 11 | } 12 | 13 | /* container */ 14 | 15 | .container { 16 | margin: 0 auto; 17 | width: 100%; 18 | max-width: 1024px; 19 | } 20 | 21 | /* header */ 22 | 23 | .header { 24 | padding: 10px 10px 10px 40px; 25 | } 26 | 27 | .header__logo { 28 | float: left; 29 | margin-left: -30px; 30 | width: 30px; 31 | height: 30px; 32 | } 33 | 34 | .header__title { 35 | float: right; 36 | color: #fff; 37 | font-size: $font-size--medium; 38 | line-height: 30px; 39 | } 40 | 41 | /* content */ 42 | 43 | .content { 44 | padding: 20px; 45 | background-color: #fff; 46 | } 47 | 48 | /* footer */ 49 | 50 | .footer { 51 | padding: 20px; 52 | font-size: $font-size--small; 53 | } 54 | 55 | .footer__left, 56 | .footer__right { 57 | width: 50%; 58 | } 59 | 60 | .footer__left { 61 | float: left; 62 | } 63 | 64 | .footer__right { 65 | float: right; 66 | text-align: right; 67 | } 68 | 69 | .footer__title { 70 | margin-bottom: 10px; 71 | color: $color--dkblue; 72 | } 73 | 74 | .footer__social { 75 | margin-bottom: 10px; 76 | color: #fff; 77 | } 78 | 79 | .footer__social--heading { 80 | margin-bottom: 5px; 81 | } 82 | 83 | .footer__copyright { 84 | color: #fff; 85 | a { 86 | color: $color--dkblue; 87 | } 88 | } 89 | 90 | .footer__social--icons { 91 | 92 | } -------------------------------------------------------------------------------- /_sass/sass/_config.scss: -------------------------------------------------------------------------------- 1 | // font families 2 | 3 | $font-family--primary : 'Roboto Slab', serif; 4 | 5 | // font sizes 6 | 7 | $font-size--xxsmall : 11px; 8 | $font-size--xsmall : 12px; 9 | $font-size--small : 14px; 10 | $font-size--medium : 18px; 11 | $font-size--large : 24px; 12 | $font-size--xlarge : 30px; 13 | $font-size--xxlarge : 36px; 14 | 15 | // line heights 16 | 17 | $line-height--small : 1.2; 18 | $line-height--medium : 1.5; 19 | $line-height--large : 1.8; 20 | 21 | // colours 22 | 23 | $color--dkgrey : rgb(40,42,47); 24 | $color--grey : rgb(120,122,128); 25 | $color--ltgrey : rgb(220,222,225); 26 | $color--xltgrey : rgb(248,250,252); 27 | 28 | $color--blue : rgb(40,170,220); 29 | $color--dkblue : darken($color--blue, 20%); 30 | $color--ltblue : lighten($color--blue, 20%); 31 | 32 | $color--red : rgb(255,60,80); 33 | $color--dkred : darken($color--red, 20%); 34 | $color--ltred : lighten($color--red, 20%); 35 | 36 | $color--green : rgb(145,251,173); 37 | 38 | $color--yellow : rgb(255,255,220); 39 | 40 | // game config variables 41 | 42 | $tile--transition-time : 0.2s; 43 | 44 | // levels -> used in _modules 45 | // 46 | // mapped out like: 47 | // LEVEL NUM : GRID LENGTH 48 | // 49 | // level 1 - 4 x 2 grid 50 | // level 2 - 6 x 3 grid 51 | // level 3 - 8 x 4 grid 52 | // 53 | // Add more levels if you want, but make sure you adjust the following: 54 | // 55 | // 1. Make sure grid aspect ratio is 2:1 56 | // 2. Make sure to allow more level selection on the start screen HTML 57 | // 3. Make sure that you add images for new levels 58 | 59 | $levels : ( 60 | 1 : 8, 61 | 2 : 18, 62 | 3 : 32 63 | ); -------------------------------------------------------------------------------- /js/classList.min.js: -------------------------------------------------------------------------------- 1 | "document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var a=document.createElement("_");if(a.classList.add("c1","c2"),!a.classList.contains("c2")){var b=function(a){var b=DOMTokenList.prototype[a];DOMTokenList.prototype[a]=function(a){var c,d=arguments.length;for(c=0;d>c;c++)a=arguments[c],b.call(this,a)}};b("add"),b("remove")}if(a.classList.toggle("c3",!1),a.classList.contains("c3")){var c=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(a,b){return 1 in arguments&&!this.contains(a)==!b?b:c.call(this,a)}}a=null}():!function(a){"use strict";if("Element"in a){var b="classList",c="prototype",d=a.Element[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(""===b)throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){for(var b=f.call(a.getAttribute("class")||""),c=b?b.split(/\s+/):[],d=0,e=c.length;e>d;d++)this.push(c[d]);this._updateClassName=function(){a.setAttribute("class",this.toString())}},k=j[c]=[],l=function(){return new j(this)};if(h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",-1!==i(this,a)},k.add=function(){var a,b=arguments,c=0,d=b.length,e=!1;do a=b[c]+"",-1===i(this,a)&&(this.push(a),e=!0);while(++c 17 | 18 | 19 | 24 | ``` 25 | 26 | ## Options 27 | 28 | Memory ships with 4 options. They are: 29 | 30 | 1. `wrapperID` - the ID of the div you want to attach Memory to 31 | 2. `cards` - the array of cards to use during Memory. More on this below. 32 | 3. `onGameStart` - a callback function that runs once the game starts, i.e. once a user clicks on a level. 33 | 4. `onGameEnd` - a callback function that runs once the game ends. More on this below. 34 | 35 | ## Defaults 36 | 37 | The default options for Memory are as follows: 38 | 39 | ```language-javascript 40 | wrapperID : "my-memory-game", 41 | cards : [ 42 | { 43 | id : 1, 44 | img: "img/default/monsters-01.png" 45 | }, 46 | { 47 | id : 2, 48 | img: "img/default/monsters-02.png" 49 | }, 50 | { 51 | id : 3, 52 | img: "img/default/monsters-03.png" 53 | }, 54 | { 55 | id : 4, 56 | img: "img/default/monsters-04.png" 57 | }, 58 | { 59 | id : 5, 60 | img: "img/default/monsters-05.png" 61 | }, 62 | { 63 | id : 6, 64 | img: "img/default/monsters-06.png" 65 | }, 66 | { 67 | id : 7, 68 | img: "img/default/monsters-07.png" 69 | }, 70 | { 71 | id : 8, 72 | img: "img/default/monsters-08.png" 73 | }, 74 | { 75 | id : 9, 76 | img: "img/default/monsters-09.png" 77 | }, 78 | { 79 | id : 10, 80 | img: "img/default/monsters-10.png" 81 | }, 82 | { 83 | id : 11, 84 | img: "img/default/monsters-11.png" 85 | }, 86 | { 87 | id : 12, 88 | img: "img/default/monsters-12.png" 89 | }, 90 | { 91 | id : 13, 92 | img: "img/default/monsters-13.png" 93 | }, 94 | { 95 | id : 14, 96 | img: "img/default/monsters-14.png" 97 | }, 98 | { 99 | id : 15, 100 | img: "img/default/monsters-15.png" 101 | }, 102 | { 103 | id : 16, 104 | img: "img/default/monsters-16.png" 105 | } 106 | ], 107 | onGameStart : function() { return false; }, 108 | onGameEnd : function() { return false; } 109 | ``` 110 | 111 | ## The Cards 112 | 113 | Right now, Memory's biggest game size is a 8 x 4 grid, meaning 32 cards. Therefore, users must specify 16 cards with unique id's and image paths in order for Memory to run at all three levels. If you don't specify any cards in the new instance of Memory, the default set will be used. 114 | 115 | ## onGameEnd Callback 116 | 117 | The `onGameEnd` callback gets triggered when the game has ended. If you don't specify a callback (i.e. the callback defaults to returns `false`), then when the game has ended, a default operation occurs. The board is cleared, and the user is presented with a "win" screen showing how many moves they completed the game in, and a button to start over. 118 | 119 | ## Public Functions 120 | 121 | There is one "public" function available for you to use, and that is `resetGame`. It basically clears the board and resets all variables, taking you back to the start screen. You can use it like this: 122 | 123 | ```language-javascript 124 | var myMem = new Memory; 125 | myMem.resetGame(); 126 | ``` 127 | 128 | ## Sass 129 | 130 | Sass was used quite a lot, as well as some related libraries like Auto Prefixer and Sassy Math. I also use Compass. In order for the Sass files to run as is, take a look at the compass config.rb file and make sure you have everything set up! Otherwise, plain old CSS for you. 131 | 132 | ## Support 133 | 134 | Memory has been tested in the following browsers: 135 | 136 | * Chrome - 37 137 | * Firefox - 32 138 | * Safari - 7 139 | 140 | Anyone who conducts tests and gets positive or negative results, please let me know! 141 | 142 | ## External Libs 143 | 144 | I used the `classList` property a bit throughout the script, and you can read more documentation on that [here](https://developer.mozilla.org/en-US/docs/Web/API/Element.classList). Because of poor IE support though (10 and up), I made sure to include the `classList` polyfill, which you can find [here](https://github.com/eligrey/classList.js/). 145 | 146 | ## License 147 | 148 | Licensed under the MIT license - http://www.opensource.org/licenses/mit-license.php -------------------------------------------------------------------------------- /css/memory.min.css: -------------------------------------------------------------------------------- 1 | a,body,div,h1,h2,h3,h4,h5,h6,html,img,p,small,span{margin:0;padding:0;border:0;font:inherit}*,::after,::before{-moz-box-sizing:border-box;box-sizing:border-box}.clearfix:after{content:"";display:table;clear:both}body,html{height:100%}body{color:#787a80;background-color:#28aadc;font-family:"Roboto Slab",serif;font-size:18px}h1,h2,h3,h4,h5,h6{font-weight:400}a{text-decoration:none}img{display:block;max-width:100%;height:auto}.wrapper{margin:0 auto;width:100%;min-width:480px}.container{margin:0 auto;width:100%;max-width:1024px}.header{padding:10px 10px 10px 40px}.header__logo{float:left;margin-left:-30px;width:30px;height:30px}.header__title{float:right;color:#fff;font-size:18px;line-height:30px}.content{padding:20px;background-color:#fff}.footer{padding:20px;font-size:14px}.footer__left,.footer__right{width:50%}.footer__left{float:left}.footer__right{float:right;text-align:right}.footer__title{margin-bottom:10px;color:#166888}.footer__social{margin-bottom:10px;color:#fff}.footer__social--heading{margin-bottom:5px}.footer__copyright{color:#fff}.footer__copyright a{color:#166888}.mg__meta{margin-bottom:10px;color:#28aadc}.mg__meta--item{display:inline-block}.mg__meta--left{float:left}.mg__meta--right{float:right}.mg__meta--level{margin-right:20px}.mg__start-screen{text-align:center;padding:80px 20px}.mg__start-screen--heading{margin-bottom:10px;color:#282a2f;font-size:30px}.mg__start-screen--sub-heading{font-size:24px;margin-bottom:10px;color:#28aadc}.mg__start-screen--sub-heading::after,.mg__start-screen--sub-heading::before{margin:0 5px;content:"-"}.mg__start-screen--text{margin-bottom:20px}.mg__start-screen--level-select{list-style:none;margin:0;padding:0}.mg__start-screen--level-select span{color:#ff3c50;font-size:18px;cursor:pointer}.mg__start-screen--level-select span:hover{color:#d50016}.mg__wrapper{margin:0 auto;width:100%}.mg__contents{position:relative;padding-bottom:50%;margin-left:-5px;margin-right:-5px}.mg__tile{position:absolute;padding:5px}.mg__level-1 .mg__tile{width:25%;height:50%}.mg__level-1 .mg__tile-1{top:0;left:0}.mg__level-1 .mg__tile-2{top:0;left:25%}.mg__level-1 .mg__tile-3{top:0;left:50%}.mg__level-1 .mg__tile-4{top:0;left:75%}.mg__level-1 .mg__tile-5{top:50%;left:0}.mg__level-1 .mg__tile-6{top:50%;left:25%}.mg__level-1 .mg__tile-7{top:50%;left:50%}.mg__level-1 .mg__tile-8{top:50%;left:75%}.mg__level-2 .mg__tile{width:16.66667%;height:33.33333%}.mg__level-2 .mg__tile-1{top:0;left:0}.mg__level-2 .mg__tile-2{top:0;left:16.66667%}.mg__level-2 .mg__tile-3{top:0;left:33.33333%}.mg__level-2 .mg__tile-4{top:0;left:50%}.mg__level-2 .mg__tile-5{top:0;left:66.66667%}.mg__level-2 .mg__tile-6{top:0;left:83.33333%}.mg__level-2 .mg__tile-7{top:33.33333%;left:0}.mg__level-2 .mg__tile-8{top:33.33333%;left:16.66667%}.mg__level-2 .mg__tile-9{top:33.33333%;left:33.33333%}.mg__level-2 .mg__tile-10{top:33.33333%;left:50%}.mg__level-2 .mg__tile-11{top:33.33333%;left:66.66667%}.mg__level-2 .mg__tile-12{top:33.33333%;left:83.33333%}.mg__level-2 .mg__tile-13{top:66.66667%;left:0}.mg__level-2 .mg__tile-14{top:66.66667%;left:16.66667%}.mg__level-2 .mg__tile-15{top:66.66667%;left:33.33333%}.mg__level-2 .mg__tile-16{top:66.66667%;left:50%}.mg__level-2 .mg__tile-17{top:66.66667%;left:66.66667%}.mg__level-2 .mg__tile-18{top:66.66667%;left:83.33333%}.mg__level-3 .mg__tile{width:12.5%;height:25%}.mg__level-3 .mg__tile-1{top:0;left:0}.mg__level-3 .mg__tile-2{top:0;left:12.5%}.mg__level-3 .mg__tile-3{top:0;left:25%}.mg__level-3 .mg__tile-4{top:0;left:37.5%}.mg__level-3 .mg__tile-5{top:0;left:50%}.mg__level-3 .mg__tile-6{top:0;left:62.5%}.mg__level-3 .mg__tile-7{top:0;left:75%}.mg__level-3 .mg__tile-8{top:0;left:87.5%}.mg__level-3 .mg__tile-9{top:25%;left:0}.mg__level-3 .mg__tile-10{top:25%;left:12.5%}.mg__level-3 .mg__tile-11{top:25%;left:25%}.mg__level-3 .mg__tile-12{top:25%;left:37.5%}.mg__level-3 .mg__tile-13{top:25%;left:50%}.mg__level-3 .mg__tile-14{top:25%;left:62.5%}.mg__level-3 .mg__tile-15{top:25%;left:75%}.mg__level-3 .mg__tile-16{top:25%;left:87.5%}.mg__level-3 .mg__tile-17{top:50%;left:0}.mg__level-3 .mg__tile-18{top:50%;left:12.5%}.mg__level-3 .mg__tile-19{top:50%;left:25%}.mg__level-3 .mg__tile-20{top:50%;left:37.5%}.mg__level-3 .mg__tile-21{top:50%;left:50%}.mg__level-3 .mg__tile-22{top:50%;left:62.5%}.mg__level-3 .mg__tile-23{top:50%;left:75%}.mg__level-3 .mg__tile-24{top:50%;left:87.5%}.mg__level-3 .mg__tile-25{top:75%;left:0}.mg__level-3 .mg__tile-26{top:75%;left:12.5%}.mg__level-3 .mg__tile-27{top:75%;left:25%}.mg__level-3 .mg__tile-28{top:75%;left:37.5%}.mg__level-3 .mg__tile-29{top:75%;left:50%}.mg__level-3 .mg__tile-30{top:75%;left:62.5%}.mg__level-3 .mg__tile-31{top:75%;left:75%}.mg__level-3 .mg__tile-32{top:75%;left:87.5%}.mg__tile--inner{position:relative;width:100%;height:100%;cursor:pointer}.mg__tile--inside,.mg__tile--outside{display:block;position:absolute;top:0;left:0;width:100%;height:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform .3s,background .3s;transition:transform .3s,background .3s}.mg__tile--outside{background:url(../img/default/logo-bw.png) 50% 50% no-repeat #dcdee1;box-shadow:0 0 0 1px #787a80}.mg__tile--inside{background-color:#f8fafc;box-shadow:0 0 0 1px #787a80;-webkit-transform:rotateY(-180deg);transform:rotateY(-180deg)}.mg__tile--inner.flipped .mg__tile--outside{-webkit-transform:rotateY(-180deg);transform:rotateY(-180deg)}.mg__tile--inner.flipped .mg__tile--inside{-webkit-transform:rotateY(0);transform:rotateY(0)}.mg__tile--inner.flipped.correct .mg__tile--inside{background-color:#ffffdc}.mg__onend{padding:80px 20px;text-align:center}.mg__onend--heading{margin-bottom:10px;color:#28aadc;font-size:30px}.mg__onend--message{margin-bottom:10px}.mg__button{margin:0;display:inline-block;padding:5px;color:#fff;font-family:"Roboto Slab",serif;font-size:14px;appearance:none;background:#ff3c50;border:none;border-radius:3px;box-shadow:none;cursor:pointer}.fb-like,.twitter-share-button{display:inline-block;vertical-align:middle}.fb-like{margin-right:10px}#fusionads{display:inline-block;padding:5px;background:#fff;font-size:11px;line-height:1.2;text-align:left}#fusionads .fusion-wrap{display:block;margin:0 0 5px;width:130px}#fusionads a.fusion-text{display:block;color:#787a80}#fusionads a.fusion-img{display:block;margin-bottom:5px;width:130px;height:100px;background-color:#fff}#fusionads a.fusion-img img{display:block;margin:0 0 10px}#fusionads a.fusion-poweredby{color:#28aadc} -------------------------------------------------------------------------------- /_sass/sass/_game.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | MEMORY GAME (mg) 3 | ============================================================================= */ 4 | 5 | /** 6 | * Game container 7 | * 8 | * This is the overall container for the game. Different things get addead and 9 | * removed from this container depending on the game state. 10 | */ 11 | 12 | .mg { 13 | /* blank */ 14 | } 15 | 16 | /** 17 | * Game meta 18 | * 19 | * The game meta is the section that displays the level and moves. It's appended 20 | * to the game container at the start, and shows the level the user selected 21 | * and the number of moves the user has played. 22 | */ 23 | 24 | .mg__meta { 25 | margin-bottom: 10px; 26 | color: $color--blue; 27 | } 28 | 29 | .mg__meta--item { 30 | display: inline-block; 31 | } 32 | 33 | .mg__meta--left { 34 | float: left; 35 | } 36 | 37 | .mg__meta--right { 38 | float: right; 39 | } 40 | 41 | .mg__meta--level { 42 | margin-right: 20px; 43 | } 44 | 45 | .mg__meta--moves { 46 | 47 | } 48 | 49 | /** 50 | * Game start screen 51 | * 52 | * The game start screen shows the "welcome" message and also a list for the 53 | * user to choose a level. It's appended to the game container at the start, 54 | * and once the user selects a level, it is removed from the container 55 | */ 56 | 57 | .mg__start-screen { 58 | text-align: center; 59 | padding: 80px 20px; 60 | } 61 | 62 | .mg__start-screen--heading { 63 | margin-bottom: 10px; 64 | color: $color--dkgrey; 65 | font-size: $font-size--xlarge; 66 | } 67 | 68 | .mg__start-screen--sub-heading { 69 | font-size: $font-size--large; 70 | margin-bottom: 10px; 71 | color: $color--blue; 72 | &::before, 73 | &::after { 74 | margin: 0 5px; 75 | content: "-"; 76 | } 77 | } 78 | 79 | .mg__start-screen--text { 80 | margin-bottom: 20px; 81 | } 82 | 83 | .mg__start-screen--level-select { 84 | list-style: none; 85 | margin: 0; 86 | padding: 0; 87 | span { 88 | color: $color--red; 89 | font-size: $font-size--medium; 90 | cursor: pointer; 91 | &:hover { 92 | color: $color--dkred; 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Game wrapper 99 | * 100 | * The game wrapper is where the actual game resides. Inside here, all the 101 | * memory tiles get arranged and ready for game play. 102 | */ 103 | 104 | .mg__wrapper { 105 | margin: 0 auto; 106 | width: 100%; 107 | 108 | } 109 | 110 | .mg__contents { 111 | position: relative; 112 | padding-bottom: 50%; 113 | margin-left: -5px; 114 | margin-right: -5px; 115 | } 116 | 117 | /** 118 | * Game tiles 119 | * 120 | * The game tiles are the tiles that are laid down on the memory game board. 121 | * These tiles are the ones that the user clicks on to flip and reveal some 122 | * images. The level the user selects determines the position and size of 123 | * the tiles. 124 | */ 125 | 126 | .mg__tile { 127 | position: absolute; 128 | padding: 5px; 129 | } 130 | 131 | @each $level, $tiles in $levels { 132 | 133 | $y : sqrt($tiles/2); 134 | $x : $tiles/$y; 135 | 136 | /* game__level-#{$level} styles */ 137 | .mg__level-#{$level} .mg__tile { 138 | width: (1 / $x) * 100%; 139 | height: (1 / $y) * 100%; 140 | } 141 | 142 | .mg__level-#{$level} { 143 | @for $i from 1 through $tiles { 144 | $top : (floor(($i - 1) / $x) / $y) * 100%; 145 | $left : ((($i - 1) % $x) / $x) * 100%; 146 | .mg__tile-#{$i} { 147 | top: $top; 148 | left: $left; 149 | } 150 | } 151 | } 152 | 153 | } 154 | 155 | /** 156 | * The tile inside 157 | * 158 | * The "tile inner" is the part of the tile that serves as the card. Inside 159 | * this part, there's an outside and inside part. The outside of the card 160 | * is the part that has the logo or the pattern or whatever...basically the 161 | * part that doesn't show the content to be matched. The inside part has the 162 | * actual images / info to be matched. 163 | */ 164 | 165 | .mg__tile--inner { 166 | position: relative; 167 | width: 100%; 168 | height: 100%; 169 | cursor: pointer; 170 | } 171 | 172 | .mg__tile--outside, 173 | .mg__tile--inside { 174 | display: block; 175 | position: absolute; 176 | top: 0; left: 0; 177 | width: 100%; 178 | height: 100%; 179 | backface-visibility: hidden; 180 | transition: transform 0.3s, background 0.3s; 181 | } 182 | 183 | .mg__tile--outside { 184 | background: url('../img/default/logo-bw.png') 50% 50% no-repeat; 185 | background-color: $color--ltgrey; 186 | box-shadow: 0 0 0 1px $color--grey; 187 | } 188 | 189 | .mg__tile--inside { 190 | background-color: $color--xltgrey; 191 | box-shadow: 0 0 0 1px $color--grey; 192 | transform: rotateY(-180deg); 193 | } 194 | 195 | /* some transforms for flipped cards */ 196 | 197 | .mg__tile--inner.flipped .mg__tile--outside { 198 | transform: rotateY(-180deg); 199 | } 200 | 201 | .mg__tile--inner.flipped .mg__tile--inside { 202 | transform: rotateY(0); 203 | } 204 | 205 | /* some transitions for correct guesses - only needs to happen on card inside */ 206 | 207 | .mg__tile--inner.flipped.correct .mg__tile--inside { 208 | background-color: $color--yellow; 209 | } 210 | 211 | /** 212 | * Game message 213 | * 214 | * The game message area is an area to display game messages. It's used in the 215 | * default set up where no callback is set in the JS. If a callback is set up, 216 | * then this message area likely won't display. Unless you decide to display it 217 | * in your own custom callback though! 218 | */ 219 | 220 | .mg__onend { 221 | padding: 80px 20px; 222 | text-align: center; 223 | } 224 | 225 | .mg__onend--heading { 226 | margin-bottom: 10px; 227 | color: $color--blue; 228 | font-size: $font-size--xlarge; 229 | } 230 | 231 | .mg__onend--message { 232 | margin-bottom: 10px; 233 | } 234 | 235 | /** 236 | * Game buttons 237 | * 238 | * A simple helper class for game buttons. Edit at your will. 239 | */ 240 | .mg__button { 241 | margin: 0; 242 | display: inline-block; 243 | padding: 5px; 244 | color: #fff; 245 | font-family: $font-family--primary; 246 | font-size: $font-size--small; 247 | appearance: none; 248 | background: $color--red; 249 | border: none; 250 | border-radius: 3px; 251 | box-shadow: none; 252 | cursor: pointer; 253 | } -------------------------------------------------------------------------------- /js/classList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * classList.js: Cross-browser full element.classList implementation. 3 | * 2014-07-23 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * Public Domain. 7 | * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | */ 9 | 10 | /*global self, document, DOMException */ 11 | 12 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 13 | 14 | if ("document" in self) { 15 | 16 | // Full polyfill for browsers with no classList support 17 | if (!("classList" in document.createElement("_"))) { 18 | 19 | (function (view) { 20 | 21 | "use strict"; 22 | 23 | if (!('Element' in view)) return; 24 | 25 | var 26 | classListProp = "classList" 27 | , protoProp = "prototype" 28 | , elemCtrProto = view.Element[protoProp] 29 | , objCtr = Object 30 | , strTrim = String[protoProp].trim || function () { 31 | return this.replace(/^\s+|\s+$/g, ""); 32 | } 33 | , arrIndexOf = Array[protoProp].indexOf || function (item) { 34 | var 35 | i = 0 36 | , len = this.length 37 | ; 38 | for (; i < len; i++) { 39 | if (i in this && this[i] === item) { 40 | return i; 41 | } 42 | } 43 | return -1; 44 | } 45 | // Vendors: please allow content code to instantiate DOMExceptions 46 | , DOMEx = function (type, message) { 47 | this.name = type; 48 | this.code = DOMException[type]; 49 | this.message = message; 50 | } 51 | , checkTokenAndGetIndex = function (classList, token) { 52 | if (token === "") { 53 | throw new DOMEx( 54 | "SYNTAX_ERR" 55 | , "An invalid or illegal string was specified" 56 | ); 57 | } 58 | if (/\s/.test(token)) { 59 | throw new DOMEx( 60 | "INVALID_CHARACTER_ERR" 61 | , "String contains an invalid character" 62 | ); 63 | } 64 | return arrIndexOf.call(classList, token); 65 | } 66 | , ClassList = function (elem) { 67 | var 68 | trimmedClasses = strTrim.call(elem.getAttribute("class") || "") 69 | , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] 70 | , i = 0 71 | , len = classes.length 72 | ; 73 | for (; i < len; i++) { 74 | this.push(classes[i]); 75 | } 76 | this._updateClassName = function () { 77 | elem.setAttribute("class", this.toString()); 78 | }; 79 | } 80 | , classListProto = ClassList[protoProp] = [] 81 | , classListGetter = function () { 82 | return new ClassList(this); 83 | } 84 | ; 85 | // Most DOMException implementations don't allow calling DOMException's toString() 86 | // on non-DOMExceptions. Error's toString() is sufficient here. 87 | DOMEx[protoProp] = Error[protoProp]; 88 | classListProto.item = function (i) { 89 | return this[i] || null; 90 | }; 91 | classListProto.contains = function (token) { 92 | token += ""; 93 | return checkTokenAndGetIndex(this, token) !== -1; 94 | }; 95 | classListProto.add = function () { 96 | var 97 | tokens = arguments 98 | , i = 0 99 | , l = tokens.length 100 | , token 101 | , updated = false 102 | ; 103 | do { 104 | token = tokens[i] + ""; 105 | if (checkTokenAndGetIndex(this, token) === -1) { 106 | this.push(token); 107 | updated = true; 108 | } 109 | } 110 | while (++i < l); 111 | 112 | if (updated) { 113 | this._updateClassName(); 114 | } 115 | }; 116 | classListProto.remove = function () { 117 | var 118 | tokens = arguments 119 | , i = 0 120 | , l = tokens.length 121 | , token 122 | , updated = false 123 | , index 124 | ; 125 | do { 126 | token = tokens[i] + ""; 127 | index = checkTokenAndGetIndex(this, token); 128 | while (index !== -1) { 129 | this.splice(index, 1); 130 | updated = true; 131 | index = checkTokenAndGetIndex(this, token); 132 | } 133 | } 134 | while (++i < l); 135 | 136 | if (updated) { 137 | this._updateClassName(); 138 | } 139 | }; 140 | classListProto.toggle = function (token, force) { 141 | token += ""; 142 | 143 | var 144 | result = this.contains(token) 145 | , method = result ? 146 | force !== true && "remove" 147 | : 148 | force !== false && "add" 149 | ; 150 | 151 | if (method) { 152 | this[method](token); 153 | } 154 | 155 | if (force === true || force === false) { 156 | return force; 157 | } else { 158 | return !result; 159 | } 160 | }; 161 | classListProto.toString = function () { 162 | return this.join(" "); 163 | }; 164 | 165 | if (objCtr.defineProperty) { 166 | var classListPropDesc = { 167 | get: classListGetter 168 | , enumerable: true 169 | , configurable: true 170 | }; 171 | try { 172 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 173 | } catch (ex) { // IE 8 doesn't support enumerable:true 174 | if (ex.number === -0x7FF5EC54) { 175 | classListPropDesc.enumerable = false; 176 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 177 | } 178 | } 179 | } else if (objCtr[protoProp].__defineGetter__) { 180 | elemCtrProto.__defineGetter__(classListProp, classListGetter); 181 | } 182 | 183 | }(self)); 184 | 185 | } else { 186 | // There is full or partial native classList support, so just check if we need 187 | // to normalize the add/remove and toggle APIs. 188 | 189 | (function () { 190 | "use strict"; 191 | 192 | var testElement = document.createElement("_"); 193 | 194 | testElement.classList.add("c1", "c2"); 195 | 196 | // Polyfill for IE 10/11 and Firefox <26, where classList.add and 197 | // classList.remove exist but support only one argument at a time. 198 | if (!testElement.classList.contains("c2")) { 199 | var createMethod = function(method) { 200 | var original = DOMTokenList.prototype[method]; 201 | 202 | DOMTokenList.prototype[method] = function(token) { 203 | var i, len = arguments.length; 204 | 205 | for (i = 0; i < len; i++) { 206 | token = arguments[i]; 207 | original.call(this, token); 208 | } 209 | }; 210 | }; 211 | createMethod('add'); 212 | createMethod('remove'); 213 | } 214 | 215 | testElement.classList.toggle("c3", false); 216 | 217 | // Polyfill for IE 10 and Firefox <24, where classList.toggle does not 218 | // support the second argument. 219 | if (testElement.classList.contains("c3")) { 220 | var _toggle = DOMTokenList.prototype.toggle; 221 | 222 | DOMTokenList.prototype.toggle = function(token, force) { 223 | if (1 in arguments && !this.contains(token) === !force) { 224 | return force; 225 | } else { 226 | return _toggle.call(this, token); 227 | } 228 | }; 229 | 230 | } 231 | 232 | testElement = null; 233 | }()); 234 | 235 | } 236 | 237 | } -------------------------------------------------------------------------------- /js/memory.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";function b(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function c(a){for(var b,c,d=a.length;d;b=parseInt(Math.random()*d),c=a[--d],a[d]=a[b],a[b]=c);return a}function d(a){this.options=b({},this.options),b(this.options,a),this._init()}d.prototype.options={wrapperID:"container",cards:[{id:1,img:"img/default/monsters-01.png"},{id:2,img:"img/default/monsters-02.png"},{id:3,img:"img/default/monsters-03.png"},{id:4,img:"img/default/monsters-04.png"},{id:5,img:"img/default/monsters-05.png"},{id:6,img:"img/default/monsters-06.png"},{id:7,img:"img/default/monsters-07.png"},{id:8,img:"img/default/monsters-08.png"},{id:9,img:"img/default/monsters-09.png"},{id:10,img:"img/default/monsters-10.png"},{id:11,img:"img/default/monsters-11.png"},{id:12,img:"img/default/monsters-12.png"},{id:13,img:"img/default/monsters-13.png"},{id:14,img:"img/default/monsters-14.png"},{id:15,img:"img/default/monsters-15.png"},{id:16,img:"img/default/monsters-16.png"}],onGameStart:function(){return!1},onGameEnd:function(){return!1}},d.prototype._init=function(){this.game=document.createElement("div"),this.game.id="mg",this.game.className="mg",document.getElementById(this.options.wrapperID).appendChild(this.game),this.gameMeta=document.createElement("div"),this.gameMeta.className="mg__meta clearfix",this.gameStartScreen=document.createElement("div"),this.gameStartScreen.id="mg__start-screen",this.gameStartScreen.className="mg__start-screen",this.gameWrapper=document.createElement("div"),this.gameWrapper.id="mg__wrapper",this.gameWrapper.className="mg__wrapper",this.gameContents=document.createElement("div"),this.gameContents.id="mg__contents",this.gameWrapper.appendChild(this.gameContents),this.gameMessages=document.createElement("div"),this.gameMessages.id="mg__onend",this.gameMessages.className="mg__onend",this._setupGame()},d.prototype._setupGame=function(){var a=this;this.gameState=1,this.cards=c(this.options.cards),this.card1="",this.card2="",this.card1id="",this.card2id="",this.card1flipped=!1,this.card2flipped=!1,this.flippedTiles=0,this.chosenLevel="",this.numMoves=0,this.gameMetaHTML='
Level: '+this.chosenLevel+' Moves: '+this.numMoves+'
',this.gameMeta.innerHTML=this.gameMetaHTML,this.game.appendChild(this.gameMeta),this.gameStartScreenHTML='

Welcome to the Memory Game!

Flip the tiles and try to match them up in pairs. Pair up all the tiles to win. Try to complete the game in as few moves as possible!

Select Level

',this.gameStartScreen.innerHTML=this.gameStartScreenHTML,this.game.appendChild(this.gameStartScreen),document.getElementById("mg__button--restart").addEventListener("click",function(){a.resetGame()}),this._startScreenEvents()},d.prototype._startScreenEvents=function(){for(var a=this.gameStartScreen.querySelectorAll("ul.mg__start-screen--level-select span"),b=0,c=a.length;c>b;b++){var d=a[b];this._startScreenEventsHandler(d)}},d.prototype._startScreenEventsHandler=function(a){var b=this;a.addEventListener("click",function(){1===b.gameState&&b._setupGameWrapper(this)})},d.prototype._setupGameWrapper=function(a){this.level=a.getAttribute("data-level"),this.gameStartScreen.parentNode.removeChild(this.gameStartScreen),this.gameContents.className="mg__contents mg__level-"+this.level,this.game.appendChild(this.gameWrapper),this.chosenLevel=this.level,document.getElementById("mg__meta--level").innerHTML=this.chosenLevel,this._renderTiles()},d.prototype._renderTiles=function(){this.gridX=2*this.level+2,this.gridY=this.gridX/2,this.numTiles=this.gridX*this.gridY,this.halfNumTiles=this.numTiles/2,this.newCards=[];for(var a=0;a
'}this.gameContents.innerHTML=this.tilesHTML,this.gameState=2,this.options.onGameStart(),this._gamePlay()},d.prototype._gamePlay=function(){for(var a=document.querySelectorAll(".mg__tile--inner"),b=0,c=a.length;c>b;b++){var d=a[b];this._gamePlayEvents(d)}},d.prototype._gamePlayEvents=function(a){var b=this;a.addEventListener("click",function(){this.classList.contains("flipped")||(b.card1flipped===!1&&b.card2flipped===!1?(this.classList.add("flipped"),b.card1=this,b.card1id=this.getAttribute("data-id"),b.card1flipped=!0):b.card1flipped===!0&&b.card2flipped===!1&&(this.classList.add("flipped"),b.card2=this,b.card2id=this.getAttribute("data-id"),b.card2flipped=!0,b.card1id==b.card2id?b._gameCardsMatch():b._gameCardsMismatch()))})},d.prototype._gameCardsMatch=function(){var b=this;a.setTimeout(function(){b.card1.classList.add("correct"),b.card2.classList.add("correct")},300),a.setTimeout(function(){b.card1.classList.remove("correct"),b.card2.classList.remove("correct"),b._gameResetVars(),b.flippedTiles=b.flippedTiles+2,b.flippedTiles==b.numTiles&&b._winGame()},1500),this._gameCounterPlusOne()},d.prototype._gameCardsMismatch=function(){var b=this;a.setTimeout(function(){b.card1.classList.remove("flipped"),b.card2.classList.remove("flipped"),b._gameResetVars()},900),this._gameCounterPlusOne()},d.prototype._gameResetVars=function(){this.card1="",this.card2="",this.card1id="",this.card2id="",this.card1flipped=!1,this.card2flipped=!1},d.prototype._gameCounterPlusOne=function(){this.numMoves=this.numMoves+1,this.moveCounterUpdate=document.getElementById("mg__meta--moves").innerHTML=this.numMoves},d.prototype._clearGame=function(){null!==this.gameMeta.parentNode&&this.game.removeChild(this.gameMeta),null!==this.gameStartScreen.parentNode&&this.game.removeChild(this.gameStartScreen),null!==this.gameWrapper.parentNode&&this.game.removeChild(this.gameWrapper),null!==this.gameMessages.parentNode&&this.game.removeChild(this.gameMessages)},d.prototype._winGame=function(){var a=this;this.options.onGameEnd()===!1?(this._clearGame(),this.gameMessages.innerHTML='

Sweet!

You won the round in '+this.numMoves+' moves. Go you.

',this.game.appendChild(this.gameMessages),document.getElementById("mg__onend--restart").addEventListener("click",function(){a.resetGame()})):this.options.onGameEnd()},d.prototype.resetGame=function(){this._clearGame(),this._setupGame()},a.Memory=d}(window); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Memory Game! 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 34 |

Memory Game!

35 |
36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 | 45 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 153 | 154 | 155 | 204 | 205 |
206 | 207 | 208 | -------------------------------------------------------------------------------- /css/memory.css: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | RESET, BOX SIZING, & CLEARFIX 3 | ============================================================================= */ 4 | html, body, div, span, h1, h2, h3, h4, h5, h6, p, a, small, img { 5 | margin: 0; 6 | padding: 0; 7 | border: 0; 8 | font: inherit; 9 | } 10 | 11 | *, 12 | *::before, 13 | *::after { 14 | -moz-box-sizing: border-box; 15 | box-sizing: border-box; 16 | } 17 | 18 | .clearfix:after { 19 | content: ""; 20 | display: table; 21 | clear: both; 22 | } 23 | 24 | /* ============================================================================= 25 | BASE 26 | ============================================================================= */ 27 | body, 28 | html { 29 | height: 100%; 30 | } 31 | 32 | body { 33 | color: #787a80; 34 | background-color: #28aadc; 35 | font-family: "Roboto Slab", serif; 36 | font-size: 18px; 37 | } 38 | 39 | h1, 40 | h2, 41 | h3, 42 | h4, 43 | h5, 44 | h6 { 45 | font-weight: 400; 46 | } 47 | 48 | a { 49 | text-decoration: none; 50 | } 51 | 52 | img { 53 | display: block; 54 | max-width: 100%; 55 | height: auto; 56 | } 57 | 58 | /* ============================================================================= 59 | LAYOUT 60 | ============================================================================= */ 61 | /* wrapper */ 62 | .wrapper { 63 | margin: 0 auto; 64 | width: 100%; 65 | min-width: 480px; 66 | } 67 | 68 | /* container */ 69 | .container { 70 | margin: 0 auto; 71 | width: 100%; 72 | max-width: 1024px; 73 | } 74 | 75 | /* header */ 76 | .header { 77 | padding: 10px 10px 10px 40px; 78 | } 79 | 80 | .header__logo { 81 | float: left; 82 | margin-left: -30px; 83 | width: 30px; 84 | height: 30px; 85 | } 86 | 87 | .header__title { 88 | float: right; 89 | color: #fff; 90 | font-size: 18px; 91 | line-height: 30px; 92 | } 93 | 94 | /* content */ 95 | .content { 96 | padding: 20px; 97 | background-color: #fff; 98 | } 99 | 100 | /* footer */ 101 | .footer { 102 | padding: 20px; 103 | font-size: 14px; 104 | } 105 | 106 | .footer__left, 107 | .footer__right { 108 | width: 50%; 109 | } 110 | 111 | .footer__left { 112 | float: left; 113 | } 114 | 115 | .footer__right { 116 | float: right; 117 | text-align: right; 118 | } 119 | 120 | .footer__title { 121 | margin-bottom: 10px; 122 | color: #166888; 123 | } 124 | 125 | .footer__social { 126 | margin-bottom: 10px; 127 | color: #fff; 128 | } 129 | 130 | .footer__social--heading { 131 | margin-bottom: 5px; 132 | } 133 | 134 | .footer__copyright { 135 | color: #fff; 136 | } 137 | .footer__copyright a { 138 | color: #166888; 139 | } 140 | 141 | /* ============================================================================= 142 | MEMORY GAME (mg) 143 | ============================================================================= */ 144 | /** 145 | * Game container 146 | * 147 | * This is the overall container for the game. Different things get addead and 148 | * removed from this container depending on the game state. 149 | */ 150 | .mg { 151 | /* blank */ 152 | } 153 | 154 | /** 155 | * Game meta 156 | * 157 | * The game meta is the section that displays the level and moves. It's appended 158 | * to the game container at the start, and shows the level the user selected 159 | * and the number of moves the user has played. 160 | */ 161 | .mg__meta { 162 | margin-bottom: 10px; 163 | color: #28aadc; 164 | } 165 | 166 | .mg__meta--item { 167 | display: inline-block; 168 | } 169 | 170 | .mg__meta--left { 171 | float: left; 172 | } 173 | 174 | .mg__meta--right { 175 | float: right; 176 | } 177 | 178 | .mg__meta--level { 179 | margin-right: 20px; 180 | } 181 | 182 | /** 183 | * Game start screen 184 | * 185 | * The game start screen shows the "welcome" message and also a list for the 186 | * user to choose a level. It's appended to the game container at the start, 187 | * and once the user selects a level, it is removed from the container 188 | */ 189 | .mg__start-screen { 190 | text-align: center; 191 | padding: 80px 20px; 192 | } 193 | 194 | .mg__start-screen--heading { 195 | margin-bottom: 10px; 196 | color: #282a2f; 197 | font-size: 30px; 198 | } 199 | 200 | .mg__start-screen--sub-heading { 201 | font-size: 24px; 202 | margin-bottom: 10px; 203 | color: #28aadc; 204 | } 205 | .mg__start-screen--sub-heading::before, .mg__start-screen--sub-heading::after { 206 | margin: 0 5px; 207 | content: "-"; 208 | } 209 | 210 | .mg__start-screen--text { 211 | margin-bottom: 20px; 212 | } 213 | 214 | .mg__start-screen--level-select { 215 | list-style: none; 216 | margin: 0; 217 | padding: 0; 218 | } 219 | .mg__start-screen--level-select span { 220 | color: #ff3c50; 221 | font-size: 18px; 222 | cursor: pointer; 223 | } 224 | .mg__start-screen--level-select span:hover { 225 | color: #d50016; 226 | } 227 | 228 | /** 229 | * Game wrapper 230 | * 231 | * The game wrapper is where the actual game resides. Inside here, all the 232 | * memory tiles get arranged and ready for game play. 233 | */ 234 | .mg__wrapper { 235 | margin: 0 auto; 236 | width: 100%; 237 | } 238 | 239 | .mg__contents { 240 | position: relative; 241 | padding-bottom: 50%; 242 | margin-left: -5px; 243 | margin-right: -5px; 244 | } 245 | 246 | /** 247 | * Game tiles 248 | * 249 | * The game tiles are the tiles that are laid down on the memory game board. 250 | * These tiles are the ones that the user clicks on to flip and reveal some 251 | * images. The level the user selects determines the position and size of 252 | * the tiles. 253 | */ 254 | .mg__tile { 255 | position: absolute; 256 | padding: 5px; 257 | } 258 | 259 | /* game__level-1 styles */ 260 | .mg__level-1 .mg__tile { 261 | width: 25%; 262 | height: 50%; 263 | } 264 | 265 | .mg__level-1 .mg__tile-1 { 266 | top: 0%; 267 | left: 0%; 268 | } 269 | .mg__level-1 .mg__tile-2 { 270 | top: 0%; 271 | left: 25%; 272 | } 273 | .mg__level-1 .mg__tile-3 { 274 | top: 0%; 275 | left: 50%; 276 | } 277 | .mg__level-1 .mg__tile-4 { 278 | top: 0%; 279 | left: 75%; 280 | } 281 | .mg__level-1 .mg__tile-5 { 282 | top: 50%; 283 | left: 0%; 284 | } 285 | .mg__level-1 .mg__tile-6 { 286 | top: 50%; 287 | left: 25%; 288 | } 289 | .mg__level-1 .mg__tile-7 { 290 | top: 50%; 291 | left: 50%; 292 | } 293 | .mg__level-1 .mg__tile-8 { 294 | top: 50%; 295 | left: 75%; 296 | } 297 | 298 | /* game__level-2 styles */ 299 | .mg__level-2 .mg__tile { 300 | width: 16.66667%; 301 | height: 33.33333%; 302 | } 303 | 304 | .mg__level-2 .mg__tile-1 { 305 | top: 0%; 306 | left: 0%; 307 | } 308 | .mg__level-2 .mg__tile-2 { 309 | top: 0%; 310 | left: 16.66667%; 311 | } 312 | .mg__level-2 .mg__tile-3 { 313 | top: 0%; 314 | left: 33.33333%; 315 | } 316 | .mg__level-2 .mg__tile-4 { 317 | top: 0%; 318 | left: 50%; 319 | } 320 | .mg__level-2 .mg__tile-5 { 321 | top: 0%; 322 | left: 66.66667%; 323 | } 324 | .mg__level-2 .mg__tile-6 { 325 | top: 0%; 326 | left: 83.33333%; 327 | } 328 | .mg__level-2 .mg__tile-7 { 329 | top: 33.33333%; 330 | left: 0%; 331 | } 332 | .mg__level-2 .mg__tile-8 { 333 | top: 33.33333%; 334 | left: 16.66667%; 335 | } 336 | .mg__level-2 .mg__tile-9 { 337 | top: 33.33333%; 338 | left: 33.33333%; 339 | } 340 | .mg__level-2 .mg__tile-10 { 341 | top: 33.33333%; 342 | left: 50%; 343 | } 344 | .mg__level-2 .mg__tile-11 { 345 | top: 33.33333%; 346 | left: 66.66667%; 347 | } 348 | .mg__level-2 .mg__tile-12 { 349 | top: 33.33333%; 350 | left: 83.33333%; 351 | } 352 | .mg__level-2 .mg__tile-13 { 353 | top: 66.66667%; 354 | left: 0%; 355 | } 356 | .mg__level-2 .mg__tile-14 { 357 | top: 66.66667%; 358 | left: 16.66667%; 359 | } 360 | .mg__level-2 .mg__tile-15 { 361 | top: 66.66667%; 362 | left: 33.33333%; 363 | } 364 | .mg__level-2 .mg__tile-16 { 365 | top: 66.66667%; 366 | left: 50%; 367 | } 368 | .mg__level-2 .mg__tile-17 { 369 | top: 66.66667%; 370 | left: 66.66667%; 371 | } 372 | .mg__level-2 .mg__tile-18 { 373 | top: 66.66667%; 374 | left: 83.33333%; 375 | } 376 | 377 | /* game__level-3 styles */ 378 | .mg__level-3 .mg__tile { 379 | width: 12.5%; 380 | height: 25%; 381 | } 382 | 383 | .mg__level-3 .mg__tile-1 { 384 | top: 0%; 385 | left: 0%; 386 | } 387 | .mg__level-3 .mg__tile-2 { 388 | top: 0%; 389 | left: 12.5%; 390 | } 391 | .mg__level-3 .mg__tile-3 { 392 | top: 0%; 393 | left: 25%; 394 | } 395 | .mg__level-3 .mg__tile-4 { 396 | top: 0%; 397 | left: 37.5%; 398 | } 399 | .mg__level-3 .mg__tile-5 { 400 | top: 0%; 401 | left: 50%; 402 | } 403 | .mg__level-3 .mg__tile-6 { 404 | top: 0%; 405 | left: 62.5%; 406 | } 407 | .mg__level-3 .mg__tile-7 { 408 | top: 0%; 409 | left: 75%; 410 | } 411 | .mg__level-3 .mg__tile-8 { 412 | top: 0%; 413 | left: 87.5%; 414 | } 415 | .mg__level-3 .mg__tile-9 { 416 | top: 25%; 417 | left: 0%; 418 | } 419 | .mg__level-3 .mg__tile-10 { 420 | top: 25%; 421 | left: 12.5%; 422 | } 423 | .mg__level-3 .mg__tile-11 { 424 | top: 25%; 425 | left: 25%; 426 | } 427 | .mg__level-3 .mg__tile-12 { 428 | top: 25%; 429 | left: 37.5%; 430 | } 431 | .mg__level-3 .mg__tile-13 { 432 | top: 25%; 433 | left: 50%; 434 | } 435 | .mg__level-3 .mg__tile-14 { 436 | top: 25%; 437 | left: 62.5%; 438 | } 439 | .mg__level-3 .mg__tile-15 { 440 | top: 25%; 441 | left: 75%; 442 | } 443 | .mg__level-3 .mg__tile-16 { 444 | top: 25%; 445 | left: 87.5%; 446 | } 447 | .mg__level-3 .mg__tile-17 { 448 | top: 50%; 449 | left: 0%; 450 | } 451 | .mg__level-3 .mg__tile-18 { 452 | top: 50%; 453 | left: 12.5%; 454 | } 455 | .mg__level-3 .mg__tile-19 { 456 | top: 50%; 457 | left: 25%; 458 | } 459 | .mg__level-3 .mg__tile-20 { 460 | top: 50%; 461 | left: 37.5%; 462 | } 463 | .mg__level-3 .mg__tile-21 { 464 | top: 50%; 465 | left: 50%; 466 | } 467 | .mg__level-3 .mg__tile-22 { 468 | top: 50%; 469 | left: 62.5%; 470 | } 471 | .mg__level-3 .mg__tile-23 { 472 | top: 50%; 473 | left: 75%; 474 | } 475 | .mg__level-3 .mg__tile-24 { 476 | top: 50%; 477 | left: 87.5%; 478 | } 479 | .mg__level-3 .mg__tile-25 { 480 | top: 75%; 481 | left: 0%; 482 | } 483 | .mg__level-3 .mg__tile-26 { 484 | top: 75%; 485 | left: 12.5%; 486 | } 487 | .mg__level-3 .mg__tile-27 { 488 | top: 75%; 489 | left: 25%; 490 | } 491 | .mg__level-3 .mg__tile-28 { 492 | top: 75%; 493 | left: 37.5%; 494 | } 495 | .mg__level-3 .mg__tile-29 { 496 | top: 75%; 497 | left: 50%; 498 | } 499 | .mg__level-3 .mg__tile-30 { 500 | top: 75%; 501 | left: 62.5%; 502 | } 503 | .mg__level-3 .mg__tile-31 { 504 | top: 75%; 505 | left: 75%; 506 | } 507 | .mg__level-3 .mg__tile-32 { 508 | top: 75%; 509 | left: 87.5%; 510 | } 511 | 512 | /** 513 | * The tile inside 514 | * 515 | * The "tile inner" is the part of the tile that serves as the card. Inside 516 | * this part, there's an outside and inside part. The outside of the card 517 | * is the part that has the logo or the pattern or whatever...basically the 518 | * part that doesn't show the content to be matched. The inside part has the 519 | * actual images / info to be matched. 520 | */ 521 | .mg__tile--inner { 522 | position: relative; 523 | width: 100%; 524 | height: 100%; 525 | cursor: pointer; 526 | } 527 | 528 | .mg__tile--outside, 529 | .mg__tile--inside { 530 | display: block; 531 | position: absolute; 532 | top: 0; 533 | left: 0; 534 | width: 100%; 535 | height: 100%; 536 | -webkit-backface-visibility: hidden; 537 | backface-visibility: hidden; 538 | -webkit-transition: -webkit-transform 0.3s, background 0.3s; 539 | transition: transform 0.3s, background 0.3s; 540 | } 541 | 542 | .mg__tile--outside { 543 | background: url("../img/default/logo-bw.png") 50% 50% no-repeat; 544 | background-color: #dcdee1; 545 | box-shadow: 0 0 0 1px #787a80; 546 | } 547 | 548 | .mg__tile--inside { 549 | background-color: #f8fafc; 550 | box-shadow: 0 0 0 1px #787a80; 551 | -webkit-transform: rotateY(-180deg); 552 | transform: rotateY(-180deg); 553 | } 554 | 555 | /* some transforms for flipped cards */ 556 | .mg__tile--inner.flipped .mg__tile--outside { 557 | -webkit-transform: rotateY(-180deg); 558 | transform: rotateY(-180deg); 559 | } 560 | 561 | .mg__tile--inner.flipped .mg__tile--inside { 562 | -webkit-transform: rotateY(0); 563 | transform: rotateY(0); 564 | } 565 | 566 | /* some transitions for correct guesses - only needs to happen on card inside */ 567 | .mg__tile--inner.flipped.correct .mg__tile--inside { 568 | background-color: #ffffdc; 569 | } 570 | 571 | /** 572 | * Game message 573 | * 574 | * The game message area is an area to display game messages. It's used in the 575 | * default set up where no callback is set in the JS. If a callback is set up, 576 | * then this message area likely won't display. Unless you decide to display it 577 | * in your own custom callback though! 578 | */ 579 | .mg__onend { 580 | padding: 80px 20px; 581 | text-align: center; 582 | } 583 | 584 | .mg__onend--heading { 585 | margin-bottom: 10px; 586 | color: #28aadc; 587 | font-size: 30px; 588 | } 589 | 590 | .mg__onend--message { 591 | margin-bottom: 10px; 592 | } 593 | 594 | /** 595 | * Game buttons 596 | * 597 | * A simple helper class for game buttons. Edit at your will. 598 | */ 599 | .mg__button { 600 | margin: 0; 601 | display: inline-block; 602 | padding: 5px; 603 | color: #fff; 604 | font-family: "Roboto Slab", serif; 605 | font-size: 14px; 606 | appearance: none; 607 | background: #ff3c50; 608 | border: none; 609 | border-radius: 3px; 610 | box-shadow: none; 611 | cursor: pointer; 612 | } 613 | 614 | /* ============================================================================= 615 | SOME SOCIAL STYLES 616 | ============================================================================= */ 617 | .fb-like, 618 | .twitter-share-button { 619 | display: inline-block; 620 | vertical-align: middle; 621 | } 622 | 623 | .fb-like { 624 | margin-right: 10px; 625 | } 626 | 627 | /* ============================================================================= 628 | FUSION ADS 629 | ============================================================================= */ 630 | /** 631 | * Fusion ads styles 632 | * 633 | * These are all the styles for my fusion ads. I'd reccommend deleting them if 634 | * you are going to use this in your own app, because they are useless and you 635 | * shouldn't be displaying my ad on your app/site in the first place. Thanks! 636 | */ 637 | #fusionads { 638 | display: inline-block; 639 | padding: 5px; 640 | background: white; 641 | font-size: 11px; 642 | line-height: 1.2; 643 | text-align: left; 644 | } 645 | 646 | #fusionads .fusion-wrap { 647 | display: block; 648 | margin: 0 0 5px 0; 649 | width: 130px; 650 | } 651 | 652 | #fusionads a.fusion-text { 653 | display: block; 654 | color: #787a80; 655 | } 656 | 657 | #fusionads a.fusion-img { 658 | display: block; 659 | margin-bottom: 5px; 660 | width: 130px; 661 | height: 100px; 662 | background-color: #fff; 663 | } 664 | 665 | #fusionads a.fusion-img img { 666 | display: block; 667 | margin: 0 0 10px 0; 668 | } 669 | 670 | #fusionads a.fusion-poweredby { 671 | color: #28aadc; 672 | } 673 | -------------------------------------------------------------------------------- /js/memory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Memory Game 3 | * 4 | * This is the wrapper function for my memory game! It contains all of the core 5 | * functionality for the game to run. 6 | * 7 | * Licensed under the MIT license. 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * Copyright 2014, Call Me Nick 11 | * http://callmenick.com 12 | */ 13 | 14 | ;(function( window ) { 15 | 16 | 'use strict'; 17 | 18 | /** 19 | * Extend object function 20 | * 21 | */ 22 | 23 | function extend( a, b ) { 24 | for( var key in b ) { 25 | if( b.hasOwnProperty( key ) ) { 26 | a[key] = b[key]; 27 | } 28 | } 29 | return a; 30 | } 31 | 32 | /** 33 | * Shuffle array function 34 | * 35 | */ 36 | 37 | function shuffle(o) { 38 | for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); 39 | return o; 40 | }; 41 | 42 | /** 43 | * Memory constructor 44 | * 45 | */ 46 | 47 | function Memory( options ) { 48 | this.options = extend( {}, this.options ); 49 | extend( this.options, options ); 50 | this._init(); 51 | } 52 | 53 | /** 54 | * Memory options 55 | * 56 | * Memory default options. Available options are: 57 | * 58 | * wrapperID: the element in which Memory gets built 59 | * cards: the array of cards 60 | * onGameStart: callback for when game starts 61 | * onGameEnd: callback for when game ends 62 | */ 63 | 64 | Memory.prototype.options = { 65 | wrapperID : "container", 66 | cards : [ 67 | { 68 | id : 1, 69 | img: "img/default/monsters-01.png" 70 | }, 71 | { 72 | id : 2, 73 | img: "img/default/monsters-02.png" 74 | }, 75 | { 76 | id : 3, 77 | img: "img/default/monsters-03.png" 78 | }, 79 | { 80 | id : 4, 81 | img: "img/default/monsters-04.png" 82 | }, 83 | { 84 | id : 5, 85 | img: "img/default/monsters-05.png" 86 | }, 87 | { 88 | id : 6, 89 | img: "img/default/monsters-06.png" 90 | }, 91 | { 92 | id : 7, 93 | img: "img/default/monsters-07.png" 94 | }, 95 | { 96 | id : 8, 97 | img: "img/default/monsters-08.png" 98 | }, 99 | { 100 | id : 9, 101 | img: "img/default/monsters-09.png" 102 | }, 103 | { 104 | id : 10, 105 | img: "img/default/monsters-10.png" 106 | }, 107 | { 108 | id : 11, 109 | img: "img/default/monsters-11.png" 110 | }, 111 | { 112 | id : 12, 113 | img: "img/default/monsters-12.png" 114 | }, 115 | { 116 | id : 13, 117 | img: "img/default/monsters-13.png" 118 | }, 119 | { 120 | id : 14, 121 | img: "img/default/monsters-14.png" 122 | }, 123 | { 124 | id : 15, 125 | img: "img/default/monsters-15.png" 126 | }, 127 | { 128 | id : 16, 129 | img: "img/default/monsters-16.png" 130 | } 131 | ], 132 | onGameStart : function() { return false; }, 133 | onGameEnd : function() { return false; } 134 | } 135 | 136 | /** 137 | * Memory _init - initialise Memory 138 | * 139 | * Creates all the game content areas, adds the id's and classes, and gets 140 | * ready for game setup. 141 | */ 142 | 143 | Memory.prototype._init = function() { 144 | this.game = document.createElement("div"); 145 | this.game.id = "mg"; 146 | this.game.className = "mg"; 147 | document.getElementById(this.options.wrapperID).appendChild(this.game); 148 | 149 | this.gameMeta = document.createElement("div"); 150 | this.gameMeta.className = "mg__meta clearfix"; 151 | 152 | this.gameStartScreen = document.createElement("div"); 153 | this.gameStartScreen.id = "mg__start-screen"; 154 | this.gameStartScreen.className = "mg__start-screen"; 155 | 156 | this.gameWrapper = document.createElement("div"); 157 | this.gameWrapper.id = "mg__wrapper"; 158 | this.gameWrapper.className = "mg__wrapper"; 159 | this.gameContents = document.createElement("div"); 160 | this.gameContents.id = "mg__contents"; 161 | this.gameWrapper.appendChild(this.gameContents); 162 | 163 | this.gameMessages = document.createElement("div"); 164 | this.gameMessages.id = "mg__onend"; 165 | this.gameMessages.className = "mg__onend"; 166 | 167 | this._setupGame(); 168 | }; 169 | 170 | /** 171 | * Memory _setupGame - Sets up the game 172 | * 173 | * We're caching all game related variables, and by default, displaying the 174 | * meta info bar and start screen HTML. 175 | * 176 | * A NOTE ABOUT GAME STATES: 177 | * 178 | * There are 4 game states in total, governed by the variable this.gameState. 179 | * Each game state allows for a certain series of functions to be performed. 180 | * The gameStates are as follows: 181 | * 182 | * 1 : default, allows user to choose level 183 | * 2 : set when user chooses level, and game is in play 184 | * 3 : game is finished 185 | */ 186 | 187 | Memory.prototype._setupGame = function() { 188 | var self = this; 189 | this.gameState = 1; 190 | this.cards = shuffle(this.options.cards); 191 | this.card1 = ""; 192 | this.card2 = ""; 193 | this.card1id = ""; 194 | this.card2id = ""; 195 | this.card1flipped = false; 196 | this.card2flipped = false; 197 | this.flippedTiles = 0; 198 | this.chosenLevel = ""; 199 | this.numMoves = 0; 200 | 201 | this.gameMetaHTML = '
\ 202 | Level: \ 203 | ' + this.chosenLevel + '\ 204 | \ 205 | Moves: \ 206 | ' + this.numMoves + '\ 207 | \ 208 |
\ 209 |
\ 210 | \ 211 |
'; 212 | this.gameMeta.innerHTML = this.gameMetaHTML; 213 | this.game.appendChild(this.gameMeta); 214 | 215 | this.gameStartScreenHTML = '

Welcome to the Memory Game!

\ 216 |

Flip the tiles and try to match them up in pairs. Pair up all the tiles to win. Try to complete the game in as few moves as possible!

\ 217 |

Select Level

\ 218 | '; 223 | this.gameStartScreen.innerHTML = this.gameStartScreenHTML; 224 | this.game.appendChild(this.gameStartScreen); 225 | 226 | document.getElementById("mg__button--restart").addEventListener( "click", function(e) { 227 | self.resetGame(); 228 | }); 229 | 230 | this._startScreenEvents(); 231 | } 232 | 233 | /** 234 | * Memory _startScreenEvents 235 | * 236 | * We're now listening for events on the start screen. That is, we're waiting 237 | * for when a user chooses a level. 238 | */ 239 | 240 | Memory.prototype._startScreenEvents = function() { 241 | var levelsNodes = this.gameStartScreen.querySelectorAll("ul.mg__start-screen--level-select span"); 242 | for ( var i = 0, len = levelsNodes.length; i < len; i++ ) { 243 | var levelNode = levelsNodes[i]; 244 | this._startScreenEventsHandler(levelNode); 245 | } 246 | }; 247 | 248 | /** 249 | * Memoery _startScreenEventsHandler 250 | * 251 | * A helper function to handle the click of the level inside the events 252 | * function. 253 | */ 254 | 255 | Memory.prototype._startScreenEventsHandler = function(levelNode) { 256 | var self = this; 257 | levelNode.addEventListener( "click", function(e) { 258 | if (self.gameState === 1) { 259 | self._setupGameWrapper(this); 260 | } 261 | }); 262 | } 263 | 264 | /** 265 | * Memory _setupGameWrapper 266 | * 267 | * This function sets up the game wrapper, which is where the actual memory 268 | * tiles will reside and where all the game play happens. 269 | */ 270 | 271 | Memory.prototype._setupGameWrapper = function(levelNode) { 272 | this.level = levelNode.getAttribute("data-level"); 273 | this.gameStartScreen.parentNode.removeChild(this.gameStartScreen); 274 | this.gameContents.className = "mg__contents mg__level-"+this.level; 275 | this.game.appendChild(this.gameWrapper); 276 | 277 | this.chosenLevel = this.level; 278 | document.getElementById("mg__meta--level").innerHTML = this.chosenLevel; 279 | 280 | this._renderTiles(); 281 | }; 282 | 283 | 284 | /** 285 | * Memory _renderTiles 286 | * 287 | * This renders the actual tiles with content. A few thing happen here: 288 | * 289 | * 1. Calculate grid X and Y based on user level selection 290 | * 2. Calculate num tiles 291 | * 3. Create new cards array based on level, and draw cards from original array 292 | * 4. Shuffle the new cards array 293 | * 5. Cards get distributed into tiles 294 | * 6. gamePlay function gets triggered, taking care of all the game play action. 295 | */ 296 | 297 | Memory.prototype._renderTiles = function() { 298 | this.gridX = this.level * 2 + 2; 299 | this.gridY = this.gridX / 2; 300 | this.numTiles = this.gridX * this.gridY; 301 | this.halfNumTiles = this.numTiles/2; 302 | this.newCards = []; 303 | for ( var i = 0; i < this.halfNumTiles; i++ ) { 304 | this.newCards.push(this.cards[i], this.cards[i]); 305 | } 306 | this.newCards = shuffle(this.newCards); 307 | this.tilesHTML = ''; 308 | for ( var i = 0; i < this.numTiles; i++ ) { 309 | var n = i + 1; 310 | this.tilesHTML += '
\ 311 |
\ 312 | \ 313 | \ 314 |
\ 315 |
'; 316 | } 317 | this.gameContents.innerHTML = this.tilesHTML; 318 | this.gameState = 2; 319 | this.options.onGameStart(); 320 | this._gamePlay(); 321 | } 322 | 323 | /** 324 | * Memory _gamePlay 325 | * 326 | * Now that all the HTML is set up, the game is ready to be played. In this 327 | * function, we loop through all the tiles (goverend by the .mg__tile--inner) 328 | * class, and for each tile, we run the _gamePlayEvents function. 329 | */ 330 | 331 | Memory.prototype._gamePlay = function() { 332 | var tiles = document.querySelectorAll(".mg__tile--inner"); 333 | for (var i = 0, len = tiles.length; i < len; i++) { 334 | var tile = tiles[i]; 335 | this._gamePlayEvents(tile); 336 | }; 337 | }; 338 | 339 | /** 340 | * Memory _gamePlayEvents 341 | * 342 | * This function takes care of the "events", which is basically the clicking 343 | * of tiles. Tiles need to be checked if flipped or not, flipped if possible, 344 | * and if zero, one, or two cards are flipped. When two cards are flipped, we 345 | * have to check for matches and mismatches. The _gameCardsMatch and 346 | * _gameCardsMismatch functions perform two separate sets of functions, and are 347 | * thus separated below. 348 | */ 349 | 350 | Memory.prototype._gamePlayEvents = function(tile) { 351 | var self = this; 352 | tile.addEventListener( "click", function(e) { 353 | if (!this.classList.contains("flipped")) { 354 | if (self.card1flipped === false && self.card2flipped === false) { 355 | this.classList.add("flipped"); 356 | self.card1 = this; 357 | self.card1id = this.getAttribute("data-id"); 358 | self.card1flipped = true; 359 | } else if( self.card1flipped === true && self.card2flipped === false ) { 360 | this.classList.add("flipped"); 361 | self.card2 = this; 362 | self.card2id = this.getAttribute("data-id"); 363 | self.card2flipped = true; 364 | if ( self.card1id == self.card2id ) { 365 | self._gameCardsMatch(); 366 | } else { 367 | self._gameCardsMismatch(); 368 | } 369 | } 370 | } 371 | }); 372 | } 373 | 374 | /** 375 | * Memory _gameCardsMatch 376 | * 377 | * This function runs if the cards match. The "correct" class is added briefly 378 | * which fades in a background green colour. The times set on the two timeout 379 | * functions are chosen based on transition values in the CSS. The "flip" has 380 | * a 0.3s transition, so the "correct" class is added 0.3s later, shown for 381 | * 1.2s, then removed. The cards remain flipped due to the activated "flip" 382 | * class from the gamePlayEvents function. 383 | */ 384 | 385 | Memory.prototype._gameCardsMatch = function() { 386 | // cache this 387 | var self = this; 388 | 389 | // add correct class 390 | window.setTimeout( function(){ 391 | self.card1.classList.add("correct"); 392 | self.card2.classList.add("correct"); 393 | }, 300 ); 394 | 395 | // remove correct class and reset vars 396 | window.setTimeout( function(){ 397 | self.card1.classList.remove("correct"); 398 | self.card2.classList.remove("correct"); 399 | self._gameResetVars(); 400 | self.flippedTiles = self.flippedTiles + 2; 401 | if (self.flippedTiles == self.numTiles) { 402 | self._winGame(); 403 | } 404 | }, 1500 ); 405 | 406 | // plus one on the move counter 407 | this._gameCounterPlusOne(); 408 | }; 409 | 410 | /** 411 | * Memory _gameCardsMismatch 412 | * 413 | * This function runs if the cards mismatch. If the cards mismatch, we leave 414 | * them flipped for a little while so the user can see and remember what cards 415 | * they actually are. Then after that slight delay, we removed the flipped 416 | * class so they flip back over, and reset the vars. 417 | */ 418 | 419 | Memory.prototype._gameCardsMismatch = function() { 420 | // cache this 421 | var self = this; 422 | 423 | // remove "flipped" class and reset vars 424 | window.setTimeout( function(){ 425 | self.card1.classList.remove("flipped"); 426 | self.card2.classList.remove("flipped"); 427 | self._gameResetVars(); 428 | }, 900 ); 429 | 430 | // plus one on the move counter 431 | this._gameCounterPlusOne(); 432 | }; 433 | 434 | /** 435 | * Memory _gameResetVars 436 | * 437 | * For each turn, some variables are updated for reference. After the turn is 438 | * over, we need to reset these variables and get ready for the next turn. 439 | * This function handles all of that. 440 | */ 441 | 442 | Memory.prototype._gameResetVars = function() { 443 | this.card1 = ""; 444 | this.card2 = ""; 445 | this.card1id = ""; 446 | this.card2id = ""; 447 | this.card1flipped = false; 448 | this.card2flipped = false; 449 | } 450 | 451 | /** 452 | * Memory _gameCounterPlusOne 453 | * 454 | * Each turn, the user completes 1 "move". The obective of memory is to 455 | * complete the game in as few moves as possible. Users need to know how many 456 | * moves they've had so far, so this function updates that number and updates 457 | * the HTML also. 458 | */ 459 | 460 | Memory.prototype._gameCounterPlusOne = function() { 461 | this.numMoves = this.numMoves + 1; 462 | this.moveCounterUpdate = document.getElementById("mg__meta--moves").innerHTML = this.numMoves; 463 | }; 464 | 465 | /** 466 | * Memory _clearGame 467 | * 468 | * This function clears the game wrapper, by removing it from the game div. It 469 | * allows us to rerun setupGame, and clears the air for other info like 470 | * victory messages etc. 471 | */ 472 | 473 | Memory.prototype._clearGame = function() { 474 | if (this.gameMeta.parentNode !== null) this.game.removeChild(this.gameMeta); 475 | if (this.gameStartScreen.parentNode !== null) this.game.removeChild(this.gameStartScreen); 476 | if (this.gameWrapper.parentNode !== null) this.game.removeChild(this.gameWrapper); 477 | if (this.gameMessages.parentNode !== null) this.game.removeChild(this.gameMessages); 478 | } 479 | 480 | /** 481 | * Memoray _winGame 482 | * 483 | * You won the game! This function runs the "onGameEnd" callback, which by 484 | * default clears the game div entirely and shows a "play again" button. 485 | */ 486 | 487 | Memory.prototype._winGame = function() { 488 | var self = this; 489 | if (this.options.onGameEnd() === false) { 490 | this._clearGame(); 491 | this.gameMessages.innerHTML = '

Sweet!

\ 492 |

You won the round in ' + this.numMoves + ' moves. Go you.

\ 493 | '; 494 | this.game.appendChild(this.gameMessages); 495 | document.getElementById("mg__onend--restart").addEventListener( "click", function(e) { 496 | self.resetGame(); 497 | }); 498 | } else { 499 | // run callback 500 | this.options.onGameEnd(); 501 | } 502 | } 503 | 504 | /** 505 | * Memory resetGame 506 | * 507 | * This function resets the game. It can run at the end of the game when the 508 | * user is presented the option to play again, or at any time like a reset 509 | * button. It is a public function, and can be used in whatever custom calls 510 | * in your markup. 511 | */ 512 | 513 | Memory.prototype.resetGame = function() { 514 | this._clearGame(); 515 | this._setupGame(); 516 | }; 517 | 518 | /** 519 | * Add Memory to global namespace 520 | */ 521 | 522 | window.Memory = Memory; 523 | 524 | })( window ); --------------------------------------------------------------------------------