diff --git a/README.md b/README.md index 8e21c7477b850e697020ae885abe7bdde83dd178..f2cdfa8ef25262d3cd625b6419c624fbb936edb6 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,15 @@ Made just for fun. [Play it here!](http://gabrielecirulli.github.io/2048/) [![Screenshot](http://pictures.gabrielecirulli.com/2048-20140309-234100.png)](http://pictures.gabrielecirulli.com/2048-20140309-234100.png) -That screenshot is fake by, the way. I never reached 2048 :smile: +That screenshot is fake, by the way. I never reached 2048 :smile: ## Contributing -Changes and improvements are more than welcome! Feel free to fork and open a pull request. Please make your changes in a specifically made branch and request to pull on `master`! If you can, please make sure the game fully works before sending the PR, as that will help speed up the process. +Changes and improvements are more than welcome! Feel free to fork and open a pull request. Please make your changes in a specific branch and request to pull into `master`! If you can, please make sure the game fully works before sending the PR, as that will help speed up the process. You can find the same information in the [contributing guide.](https://github.com/gabrielecirulli/2048/blob/master/CONTRIBUTING.md) ## License 2048 is licensed under the [MIT license.](https://github.com/gabrielecirulli/2048/blob/master/LICENSE.txt) + +## Donations +I made this in my spare time, and it's hosted on GitHub (which means I don't have any hosting costs), but if you enjoyed the game and feel like buying me coffee, you can donate at my BTC address: `1Ec6onfsQmoP9kkL3zkpB6c5sA4PVcXU2i`. Thank you very much! diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..22109e04a9f44bde18ec7b7a4b7410d0246521bc Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html index cf017fc99fbaa58e48e1db9d28fbfda3f2cedd33..32c86ba07a50519e83fec6b45946c8d2b030ed09 100644 --- a/index.html +++ b/index.html @@ -5,15 +5,7 @@ 2048 - - - - - - - - - + @@ -75,8 +67,18 @@


- Created by Gabriele Cirulli. Based on 1024 by Veewo Studio. + Created by Gabriele Cirulli. Based on 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer.

+ + + + + + + + + + diff --git a/js/animframe_polyfill.js b/js/animframe_polyfill.js new file mode 100644 index 0000000000000000000000000000000000000000..c45a13e204478ffd468beebcb10bb143a283b34b --- /dev/null +++ b/js/animframe_polyfill.js @@ -0,0 +1,26 @@ +(function() { + var lastTime = 0; + var vendors = ['webkit', 'moz']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = + window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +}()); diff --git a/js/application.js b/js/application.js index 036ca3a2be6ade5598c7c86cfcdc4905ed5c6a31..a4d310a2579bf9940e3844e9e169d65b705865fe 100644 --- a/js/application.js +++ b/js/application.js @@ -1,6 +1,4 @@ -document.addEventListener("DOMContentLoaded", function () { - // Wait till the browser is ready to render the game (avoids glitches) - window.requestAnimationFrame(function () { - new GameManager(4, KeyboardInputManager, HTMLActuator, LocalScoreManager); - }); +// Wait till the browser is ready to render the game (avoids glitches) +window.requestAnimationFrame(function () { + new GameManager(4, KeyboardInputManager, HTMLActuator, LocalScoreManager); }); diff --git a/js/keyboard_input_manager.js b/js/keyboard_input_manager.js index 613360b8ba5318cf036b75bf029b5700ca93d561..d91e7f3b376106b93dd1cf0273d9451a59eb5efd 100644 --- a/js/keyboard_input_manager.js +++ b/js/keyboard_input_manager.js @@ -31,11 +31,15 @@ KeyboardInputManager.prototype.listen = function () { 75: 0, // vim keybindings 76: 1, 74: 2, - 72: 3 + 72: 3, + 87: 0, // W + 68: 1, // D + 83: 2, // S + 65: 3 // A }; document.addEventListener("keydown", function (event) { - var modifiers = event.altKey && event.ctrlKey && event.metaKey && + var modifiers = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; var mapped = map[event.which]; diff --git a/style/helpers.scss b/style/helpers.scss index 6bab89e8eb8dc65f5a799c29068c2fc1ce1297ba..a8f108c695aced23d16fbc14bd8e4e894d20e566 100644 --- a/style/helpers.scss +++ b/style/helpers.scss @@ -53,3 +53,10 @@ -webkit-animation-fill-mode: #{$str}; -moz-animation-fill-mode: #{$str}; } + +// Media queries +@mixin smaller($width) { + @media screen and (max-width: $width) { + @content; + } +} diff --git a/style/main.css b/style/main.css index 784aeb5dabcc93e4cad30efb957e0865e0dd049c..07ed983cf8d6d0965364f744d86e70c1384098a6 100644 --- a/style/main.css +++ b/style/main.css @@ -140,6 +140,16 @@ hr { 100% { opacity: 1; } } +.game-container .game-message a { + display: inline-block; + background: #8f7a66; + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: #f9f6f2; + height: 40px; + line-height: 42px; } + .game-container { margin-top: 40px; position: relative; @@ -179,14 +189,6 @@ hr { display: block; margin-top: 59px; } .game-container .game-message a { - display: inline-block; - background: #8f7a66; - border-radius: 3px; - padding: 0 20px; - text-decoration: none; - color: #f9f6f2; - height: 40px; - line-height: 42px; margin-left: 9px; } .game-container .game-message.game-won { background: rgba(237, 194, 46, 0.5); @@ -222,20 +224,9 @@ hr { z-index: 2; } .tile { - background: red; width: 106.25px; height: 106.25px; - border-radius: 3px; - background: #eee4da; - text-align: center; - line-height: 116.25px; - font-size: 55px; - font-weight: bold; - z-index: 10; - -webkit-transition: 100ms ease-in-out; - -moz-transition: 100ms ease-in-out; - -webkit-transition-property: top, left; - -moz-transition-property: top, left; } + line-height: 116.25px; } .tile.tile-position-1-1 { position: absolute; left: 0px; @@ -300,6 +291,18 @@ hr { position: absolute; left: 364px; top: 364px; } + +.tile { + border-radius: 3px; + background: #eee4da; + text-align: center; + font-weight: bold; + z-index: 10; + font-size: 55px; + -webkit-transition: 100ms ease-in-out; + -moz-transition: 100ms ease-in-out; + -webkit-transition-property: top, left; + -moz-transition-property: top, left; } .tile.tile-2 { background: #eee4da; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } @@ -323,26 +326,41 @@ hr { background: #edcf72; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286); font-size: 45px; } + @media screen and (max-width: 480px) { + .tile.tile-128 { + font-size: 25px; } } .tile.tile-256 { color: #f9f6f2; background: #edcc61; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); font-size: 45px; } + @media screen and (max-width: 480px) { + .tile.tile-256 { + font-size: 25px; } } .tile.tile-512 { color: #f9f6f2; background: #edc850; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); font-size: 45px; } + @media screen and (max-width: 480px) { + .tile.tile-512 { + font-size: 25px; } } .tile.tile-1024 { color: #f9f6f2; background: #edc53f; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); font-size: 35px; } + @media screen and (max-width: 480px) { + .tile.tile-1024 { + font-size: 15px; } } .tile.tile-2048 { color: #f9f6f2; background: #edc22e; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); font-size: 35px; } + @media screen and (max-width: 480px) { + .tile.tile-2048 { + font-size: 15px; } } @-webkit-keyframes appear { 0% { @@ -434,3 +452,180 @@ hr { .game-explanation { margin-top: 50px; } + +@media screen and (max-width: 480px) { + html, body { + font-size: 15px; } + + body { + margin: 20px 0; + padding: 0 20px; } + + h1.title { + font-size: 50px; } + + .container { + width: 280px; + margin: 0 auto; } + + .score-container { + margin-top: 0; } + + .heading { + margin-bottom: 10px; } + + .game-container { + margin-top: 40px; + position: relative; + padding: 10px; + cursor: default; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + background: #bbada0; + border-radius: 6px; + width: 280px; + height: 280px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + .game-container .game-message { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(238, 228, 218, 0.5); + z-index: 100; + text-align: center; + -webkit-animation: fade-in 800ms ease 1200ms; + -moz-animation: fade-in 800ms ease 1200ms; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; } + .game-container .game-message p { + font-size: 60px; + font-weight: bold; + height: 60px; + line-height: 60px; + margin-top: 222px; } + .game-container .game-message .lower { + display: block; + margin-top: 59px; } + .game-container .game-message a { + margin-left: 9px; } + .game-container .game-message.game-won { + background: rgba(237, 194, 46, 0.5); + color: #f9f6f2; } + .game-container .game-message.game-won, .game-container .game-message.game-over { + display: block; } + + .grid-container { + position: absolute; + z-index: 1; } + + .grid-row { + margin-bottom: 10px; } + .grid-row:last-child { + margin-bottom: 0; } + .grid-row:after { + content: ""; + display: block; + clear: both; } + + .grid-cell { + width: 57.5px; + height: 57.5px; + margin-right: 10px; + float: left; + border-radius: 3px; + background: rgba(238, 228, 218, 0.35); } + .grid-cell:last-child { + margin-right: 0; } + + .tile-container { + position: absolute; + z-index: 2; } + + .tile { + width: 57.5px; + height: 57.5px; + line-height: 67.5px; } + .tile.tile-position-1-1 { + position: absolute; + left: 0px; + top: 0px; } + .tile.tile-position-1-2 { + position: absolute; + left: 0px; + top: 68px; } + .tile.tile-position-1-3 { + position: absolute; + left: 0px; + top: 135px; } + .tile.tile-position-1-4 { + position: absolute; + left: 0px; + top: 203px; } + .tile.tile-position-2-1 { + position: absolute; + left: 68px; + top: 0px; } + .tile.tile-position-2-2 { + position: absolute; + left: 68px; + top: 68px; } + .tile.tile-position-2-3 { + position: absolute; + left: 68px; + top: 135px; } + .tile.tile-position-2-4 { + position: absolute; + left: 68px; + top: 203px; } + .tile.tile-position-3-1 { + position: absolute; + left: 135px; + top: 0px; } + .tile.tile-position-3-2 { + position: absolute; + left: 135px; + top: 68px; } + .tile.tile-position-3-3 { + position: absolute; + left: 135px; + top: 135px; } + .tile.tile-position-3-4 { + position: absolute; + left: 135px; + top: 203px; } + .tile.tile-position-4-1 { + position: absolute; + left: 203px; + top: 0px; } + .tile.tile-position-4-2 { + position: absolute; + left: 203px; + top: 68px; } + .tile.tile-position-4-3 { + position: absolute; + left: 203px; + top: 135px; } + .tile.tile-position-4-4 { + position: absolute; + left: 203px; + top: 203px; } + + .game-container { + margin-top: 20px; } + + .tile { + font-size: 35px; } + + .game-message p { + font-size: 30px !important; + height: 30px !important; + line-height: 30px !important; + margin-top: 90px !important; } + .game-message .lower { + margin-top: 30px !important; } } diff --git a/style/main.scss b/style/main.scss index 96cdc7b1c353a001da69d47dd0cc58f66ba41609..4ffe138eee3e57ffb943994a629b4d74eaf99b5f 100644 --- a/style/main.scss +++ b/style/main.scss @@ -153,145 +153,159 @@ hr { } } -.game-container { - margin-top: 40px; - position: relative; - padding: $grid-spacing; - - cursor: default; - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; +// Styles for buttons +@mixin button { + display: inline-block; + background: darken($game-container-background, 20%); + border-radius: 3px; + padding: 0 20px; + text-decoration: none; + color: $bright-text-color; + height: 40px; + line-height: 42px; +} - background: $game-container-background; - border-radius: $tile-border-radius * 2; - width: $field-width; - height: $field-width; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; +// Game field mixin used to render CSS at different width +@mixin game-field { + .game-container { + margin-top: 40px; + position: relative; + padding: $grid-spacing; + + cursor: default; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + + background: $game-container-background; + border-radius: $tile-border-radius * 2; + width: $field-width; + height: $field-width; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + .game-message { + display: none; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba($tile-color, .5); + z-index: 100; + + text-align: center; + + p { + font-size: 60px; + font-weight: bold; + height: 60px; + line-height: 60px; + margin-top: 222px; + // height: $field-width; + // line-height: $field-width; + } - .game-message { - display: none; + .lower { + display: block; + margin-top: 59px; + } - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: rgba($tile-color, .5); - z-index: 100; + a { + @include button; + margin-left: 9px; + // margin-top: 59px; + } - text-align: center; + @include animation(fade-in 800ms ease $transition-speed * 12); + @include animation-fill-mode(both); - p { - font-size: 60px; - font-weight: bold; - height: 60px; - line-height: 60px; - margin-top: 222px; - // height: $field-width; - // line-height: $field-width; - } + &.game-won { + background: rgba($tile-gold-color, .5); + color: $bright-text-color; + } - .lower { - display: block; - margin-top: 59px; + &.game-won, &.game-over { + display: block; + } } + } - a { - display: inline-block; - background: darken($game-container-background, 20%); - border-radius: 3px; - padding: 0 20px; - text-decoration: none; - color: $bright-text-color; - height: 40px; - line-height: 42px; - margin-left: 9px; - // margin-top: 59px; - } + .grid-container { + position: absolute; + z-index: 1; + } - @include animation(fade-in 800ms ease $transition-speed * 12); - @include animation-fill-mode(both); + .grid-row { + margin-bottom: $grid-spacing; - &.game-won { - background: rgba($tile-gold-color, .5); - color: $bright-text-color; + &:last-child { + margin-bottom: 0; } - &.game-won, &.game-over { + &:after { + content: ""; display: block; + clear: both; } } -} -.grid-container { - position: absolute; - z-index: 1; -} + .grid-cell { + width: $tile-size; + height: $tile-size; + margin-right: $grid-spacing; + float: left; -.grid-row { - margin-bottom: $grid-spacing; + border-radius: $tile-border-radius; - &:last-child { - margin-bottom: 0; - } + background: rgba($tile-color, .35); - &:after { - content: ""; - display: block; - clear: both; + &:last-child { + margin-right: 0; + } } -} - -.grid-cell { - width: $tile-size; - height: $tile-size; - margin-right: $grid-spacing; - float: left; - border-radius: $tile-border-radius; - - background: rgba($tile-color, .35); + .tile-container { + position: absolute; + z-index: 2; + } - &:last-child { - margin-right: 0; + .tile { + width: $tile-size; + height: $tile-size; + line-height: $tile-size + 10px; + + // Build position classes + @for $x from 1 through $grid-row-cells { + @for $y from 1 through $grid-row-cells { + &.tile-position-#{$x}-#{$y} { + position: absolute; + left: round(($tile-size + $grid-spacing) * ($x - 1)); + top: round(($tile-size + $grid-spacing) * ($y - 1)); + } + } + } } } -.tile-container { - position: absolute; - z-index: 2; -} +// End of game-field mixin +@include game-field; .tile { - background: red; - width: $tile-size; - height: $tile-size; border-radius: $tile-border-radius; background: $tile-color; text-align: center; - line-height: $tile-size + 10px; - font-size: 55px; font-weight: bold; z-index: 10; + font-size: 55px; + @include transition($transition-speed ease-in-out); @include transition-property(top, left); - // Build position classes - @for $x from 1 through $grid-row-cells { - @for $y from 1 through $grid-row-cells { - &.tile-position-#{$x}-#{$y} { - position: absolute; - left: round(($tile-size + $grid-spacing) * ($x - 1)); - top: round(($tile-size + $grid-spacing) * ($y - 1)); - } - } - } - $base: 2; $exponent: 1; $limit: 11; @@ -345,8 +359,17 @@ hr { // Adjust font size for bigger numbers @if $power >= 100 and $power < 1000 { font-size: 45px; + + // Media queries placed here to avoid carrying over the rest of the logic + @include smaller(480px) { + font-size: 25px; + } } @else if $power >= 1000 { font-size: 35px; + + @include smaller(480px) { + font-size: 15px; + } } } @@ -403,3 +426,63 @@ hr { .game-explanation { margin-top: 50px; } + +@include smaller(480px) { + // Redefine variables for smaller screens + $field-width: 280px; + $grid-spacing: 10px; + $grid-row-cells: 4; + $tile-size: ($field-width - $grid-spacing * ($grid-row-cells + 1)) / $grid-row-cells; + $tile-border-radius: 3px; + + html, body { + font-size: 15px; + } + + body { + margin: 20px 0; + padding: 0 20px; + } + + h1.title { + font-size: 50px; + } + + .container { + width: $field-width; + margin: 0 auto; + } + + .score-container { + margin-top: 0; + } + + .heading { + margin-bottom: 10px; + } + + // Render the game field at the right width + @include game-field; + + .game-container { + margin-top: 20px; + } + + // Rest of the font-size adjustments in the tile class + .tile { + font-size: 35px; + } + + .game-message { + p { + font-size: 30px !important; + height: 30px !important; + line-height: 30px !important; + margin-top: 90px !important; + } + + .lower { + margin-top: 30px !important; + } + } +}