2048 Game

HTML
<div class="container"> <div class="heading"> <h1 class="title">2048</h1> <div class="scores-container"> <div class="score-container">104<div class="score-addition">+24</div></div> <div class="best-container">1420</div> </div> </div> <div class="above-game"> <a class="restart-button">New Game</a> </div> <div class="game-container"> <div class="game-message"> <p> </p> <div class="lower"> <a class="keep-playing-button">Keep Going</a> <a class="retry-button">Try Again?</a> <div class="score-sharing"></div> </div> </div> <div class="grid-container"> <div class="grid-row"> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> </div> <div class="grid-row"> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> </div> <div class="grid-row"> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> </div> <div class="grid-row"> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> <div class="grid-cell"> </div> </div> </div> <div class="tile-container"><div class="tile tile-2 tile-position-1-1"><div class="tile-inner">2</div></div><div class="tile tile-8 tile-position-1-2"><div class="tile-inner">6</div></div><div class="tile tile-8 tile-position-1-2"><div class="tile-inner">6</div></div><div class="tile tile-16 tile-position-1-2 tile-merged"><div class="tile-inner">8</div></div><div class="tile tile-16 tile-position-1-3"><div class="tile-inner">8</div></div><div class="tile tile-2 tile-position-1-4"><div class="tile-inner">2</div></div><div class="tile tile-4 tile-position-2-1"><div class="tile-inner">4</div></div><div class="tile tile-4 tile-position-2-1"><div class="tile-inner">4</div></div><div class="tile tile-8 tile-position-2-1 tile-merged"><div class="tile-inner">6</div></div><div class="tile tile-2 tile-position-3-1"><div class="tile-inner">2</div></div><div class="tile tile-2 tile-position-3-3 tile-new"><div class="tile-inner">2</div></div></div> </div> <script src="./data/bind_polyfill.js"></script> </div>
CSS
html, body { margin: 0; padding: 0; background: #faf8ef; color: #776e65; font-family: "Microsoft YaHei", sans-serif, 'Microsoft Sans Serif','Microsoft JhengHei UI'; font-size: 18px; } body { margin: 80px 0; } input { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; font: inherit; border: none; outline: none; box-sizing: border-box; font-weight: bold; margin: 0; -webkit-appearance: none; -moz-appearance: none; appearance: none; } input[type="text"], input[type="email"] { cursor: auto; background: #fcfbf9; font-weight: normal; color: #776e65; padding: 0 15px; } input[type="text"]::-webkit-input-placeholder, input[type="email"]::-webkit-input-placeholder { color: #9d948c; } input[type="text"]::-moz-placeholder, input[type="email"]::-moz-placeholder { color: #9d948c; } input[type="text"]:-ms-input-placeholder, input[type="email"]:-ms-input-placeholder { color: #9d948c; } .heading:after { content: ""; display: block; clear: both; } h1.title { font-size: 80px; font-weight: bold; margin: 0; display: block; float: left; } @-webkit-keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } @-moz-keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } @keyframes move-up { 0% { top: 25px; opacity: 1; } 100% { top: -50px; opacity: 0; } } .scores-container { float: right; text-align: right; } .score-container, .best-container { position: relative; display: inline-block; background: #bbada0; padding: 15px 25px; font-size: 25px; height: 25px; line-height: 47px; font-weight: bold; border-radius: 3px; color: white; margin-top: 8px; text-align: center; } .score-container:after, .best-container:after { position: absolute; width: 100%; top: 10px; left: 0; text-transform: uppercase; font-size: 13px; line-height: 13px; text-align: center; color: #eee4da; } .score-container .score-addition, .best-container .score-addition { position: absolute; right: 30px; color: red; font-size: 25px; line-height: 25px; font-weight: bold; color: rgba(119, 110, 101, 0.9); z-index: 100; -webkit-animation: move-up 600ms ease-in; -moz-animation: move-up 600ms ease-in; animation: move-up 600ms ease-in; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .score-container:after { content: "Score"; } .best-container:after { content: "Best"; } p { margin-top: 0; margin-bottom: 10px; line-height: 1.65; } a { color: #776e65; font-weight: bold; text-decoration: underline; cursor: pointer; } strong.important { text-transform: uppercase; } hr { border: none; border-bottom: 1px solid #d8d4d0; margin-top: 20px; margin-bottom: 30px; } .container { width: 500px; margin: 0 auto; } @-webkit-keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } @-moz-keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } @-webkit-keyframes slide-up { 0% { margin-top: 32%; } 100% { margin-top: 20%; } } @-moz-keyframes slide-up { 0% { margin-top: 32%; } 100% { margin-top: 20%; } } @keyframes slide-up { 0% { margin-top: 32%; } 100% { margin-top: 20%; } } .game-container { margin-top: 40px; position: relative; padding: 15px; cursor: default; -webkit-touch-callout: none; -ms-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -ms-touch-action: none; touch-action: none; background: #bbada0; border-radius: 6px; width: 500px; height: 500px; -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(238, 228, 218, 0.73); z-index: 100; padding-top: 40px; text-align: center; -webkit-animation: fade-in 800ms ease 1200ms; -moz-animation: fade-in 800ms ease 1200ms; animation: fade-in 800ms ease 1200ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-message p { font-size: 60px; font-weight: bold; height: 60px; line-height: 60px; margin-top: 222px; } .game-message .lower { display: block; margin-top: 29px; } .game-message .mailing-list { margin-top: 52px; } .game-message .mailing-list strong { display: block; margin-bottom: 10px; } .game-message .mailing-list .mailing-list-email-field { width: 230px; margin-right: 5px; } .game-message a { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; margin-left: 9px; } .game-message a.keep-playing-button { display: none; } .game-message .score-sharing { display: inline-block; vertical-align: middle; margin-left: 10px; } .game-message.game-won { background: rgba(237, 194, 46, 0.5); color: #f9f6f2; } .game-message.game-won a.keep-playing-button { display: inline-block; } .game-message.game-won, .game-message.game-over { display: block; } .game-message.game-won p, .game-message.game-over p { -webkit-animation: slide-up 1.5s ease-in-out 2500ms; -moz-animation: slide-up 1.5s ease-in-out 2500ms; animation: slide-up 1.5s ease-in-out 2500ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-message.game-won .mailing-list, .game-message.game-over .mailing-list { -webkit-animation: fade-in 1.5s ease-in-out 2500ms; -moz-animation: fade-in 1.5s ease-in-out 2500ms; animation: fade-in 1.5s ease-in-out 2500ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .grid-container { position: absolute; z-index: 1; } .grid-row { margin-bottom: 15px; } .grid-row:last-child { margin-bottom: 0; } .grid-row:after { content: ""; display: block; clear: both; } .grid-cell { width: 106.25px; height: 106.25px; margin-right: 15px; 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, .tile .tile-inner { width: 107px; height: 107px; line-height: 116.25px; } .tile.tile-position-1-1 { -webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); transform: translate(0px, 0px); } .tile.tile-position-1-2 { -webkit-transform: translate(0px, 121px); -moz-transform: translate(0px, 121px); transform: translate(0px, 121px); } .tile.tile-position-1-3 { -webkit-transform: translate(0px, 242px); -moz-transform: translate(0px, 242px); transform: translate(0px, 242px); } .tile.tile-position-1-4 { -webkit-transform: translate(0px, 363px); -moz-transform: translate(0px, 363px); transform: translate(0px, 363px); } .tile.tile-position-2-1 { -webkit-transform: translate(121px, 0px); -moz-transform: translate(121px, 0px); transform: translate(121px, 0px); } .tile.tile-position-2-2 { -webkit-transform: translate(121px, 121px); -moz-transform: translate(121px, 121px); transform: translate(121px, 121px); } .tile.tile-position-2-3 { -webkit-transform: translate(121px, 242px); -moz-transform: translate(121px, 242px); transform: translate(121px, 242px); } .tile.tile-position-2-4 { -webkit-transform: translate(121px, 363px); -moz-transform: translate(121px, 363px); transform: translate(121px, 363px); } .tile.tile-position-3-1 { -webkit-transform: translate(242px, 0px); -moz-transform: translate(242px, 0px); transform: translate(242px, 0px); } .tile.tile-position-3-2 { -webkit-transform: translate(242px, 121px); -moz-transform: translate(242px, 121px); transform: translate(242px, 121px); } .tile.tile-position-3-3 { -webkit-transform: translate(242px, 242px); -moz-transform: translate(242px, 242px); transform: translate(242px, 242px); } .tile.tile-position-3-4 { -webkit-transform: translate(242px, 363px); -moz-transform: translate(242px, 363px); transform: translate(242px, 363px); } .tile.tile-position-4-1 { -webkit-transform: translate(363px, 0px); -moz-transform: translate(363px, 0px); transform: translate(363px, 0px); } .tile.tile-position-4-2 { -webkit-transform: translate(363px, 121px); -moz-transform: translate(363px, 121px); transform: translate(363px, 121px); } .tile.tile-position-4-3 { -webkit-transform: translate(363px, 242px); -moz-transform: translate(363px, 242px); transform: translate(363px, 242px); } .tile.tile-position-4-4 { -webkit-transform: translate(363px, 363px); -moz-transform: translate(363px, 363px); transform: translate(363px, 363px); } .tile { position: absolute; -webkit-transition: 100ms ease-in-out; -moz-transition: 100ms ease-in-out; transition: 100ms ease-in-out; -webkit-transition-property: -webkit-transform; -moz-transition-property: -moz-transform; transition-property: transform; } .tile .tile-inner { border-radius: 3px; background: #eee4da; text-align: center; font-weight: bold; z-index: 10; font-size: 40px; } .tile.tile-2 .tile-inner { background: #eee4da; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } .tile.tile-4 .tile-inner { background: #ede0c8; box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); } .tile.tile-8 .tile-inner { color: #f9f6f2; background: #f2b179; } .tile.tile-16 .tile-inner { color: #f9f6f2; background: #f59563; } .tile.tile-32 .tile-inner { color: #f9f6f2; background: #f67c5f; } .tile.tile-64 .tile-inner { color: #f9f6f2; background: #f65e3b; font-size: 30px; } @media screen and (max-width: 520px) { .tile.tile-64 .tile-inner { font-size: 17px; } } .tile.tile-128 .tile-inner { color: #f9f6f2; 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: 25px; } @media screen and (max-width: 520px) { .tile.tile-128 .tile-inner { font-size: 13px; } } .tile.tile-256 .tile-inner { 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: 30px; } @media screen and (max-width: 520px) { .tile.tile-256 .tile-inner { font-size: 17px; } } .tile.tile-512 .tile-inner { 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: 30px; } @media screen and (max-width: 520px) { .tile.tile-512 .tile-inner { font-size: 17px; } } .tile.tile-1024 .tile-inner { 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: 30px; } @media screen and (max-width: 520px) { .tile.tile-1024 .tile-inner { font-size: 17px; } } .tile.tile-2048 .tile-inner { 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: 30px; } @media screen and (max-width: 520px) { .tile.tile-2048 .tile-inner { font-size: 17px; } } .tile.tile-super .tile-inner { color: #f9f6f2; background: #3c3a32; font-size: 30px; } @media screen and (max-width: 520px) { .tile.tile-super .tile-inner { font-size: 17px; } } @-webkit-keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @-moz-keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @keyframes appear { 0% { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 100% { opacity: 1; -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } .tile-new .tile-inner { -webkit-animation: appear 200ms ease 100ms; -moz-animation: appear 200ms ease 100ms; animation: appear 200ms ease 100ms; -webkit-animation-fill-mode: backwards; -moz-animation-fill-mode: backwards; animation-fill-mode: backwards; } @-webkit-keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @-moz-keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } @keyframes pop { 0% { -webkit-transform: scale(0); -moz-transform: scale(0); transform: scale(0); } 50% { -webkit-transform: scale(1.2); -moz-transform: scale(1.2); transform: scale(1.2); } 100% { -webkit-transform: scale(1); -moz-transform: scale(1); transform: scale(1); } } .tile-merged .tile-inner { z-index: 20; -webkit-animation: pop 200ms ease 100ms; -moz-animation: pop 200ms ease 100ms; animation: pop 200ms ease 100ms; -webkit-animation-fill-mode: backwards; -moz-animation-fill-mode: backwards; animation-fill-mode: backwards; } .above-game:after { content: ""; display: block; clear: both; } .game-intro { float: left; line-height: 42px; margin-bottom: 0; } .restart-button { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; display: block; text-align: center; float: right; } .game-explanation { margin-top: 50px; } .sharing { margin-top: 20px; text-align: center; } .sharing > iframe, .sharing > span, .sharing > form { display: inline-block; vertical-align: middle; } @media screen and (max-width: 520px) { html, body { font-size: 15px; } body { margin: 20px 0; padding: 0 20px; } h1.title { font-size: 27px; margin-top: 15px; } .container { width: 280px; margin: 0 auto; } .score-container, .best-container { margin-top: 0; padding: 15px 10px; min-width: 40px; } .heading { margin-bottom: 10px; } .game-intro { width: 55%; display: block; box-sizing: border-box; line-height: 1.65; } .restart-button { width: 42%; padding: 0; display: block; box-sizing: border-box; margin-top: 2px; } .game-container { margin-top: 17px; position: relative; padding: 10px; cursor: default; -webkit-touch-callout: none; -ms-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -ms-touch-action: none; touch-action: 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-message { display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(238, 228, 218, 0.73); z-index: 100; padding-top: 40px; text-align: center; -webkit-animation: fade-in 800ms ease 1200ms; -moz-animation: fade-in 800ms ease 1200ms; animation: fade-in 800ms ease 1200ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-message p { font-size: 60px; font-weight: bold; height: 60px; line-height: 60px; margin-top: 222px; } .game-message .lower { display: block; margin-top: 29px; } .game-message .mailing-list { margin-top: 52px; } .game-message .mailing-list strong { display: block; margin-bottom: 10px; } .game-message .mailing-list .mailing-list-email-field { width: 230px; margin-right: 5px; } .game-message a { display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; margin-left: 9px; } .game-message a.keep-playing-button { display: none; } .game-message .score-sharing { display: inline-block; vertical-align: middle; margin-left: 10px; } .game-message.game-won { background: rgba(237, 194, 46, 0.5); color: #f9f6f2; } .game-message.game-won a.keep-playing-button { display: inline-block; } .game-message.game-won, .game-message.game-over { display: block; } .game-message.game-won p, .game-message.game-over p { -webkit-animation: slide-up 1.5s ease-in-out 2500ms; -moz-animation: slide-up 1.5s ease-in-out 2500ms; animation: slide-up 1.5s ease-in-out 2500ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .game-message.game-won .mailing-list, .game-message.game-over .mailing-list { -webkit-animation: fade-in 1.5s ease-in-out 2500ms; -moz-animation: fade-in 1.5s ease-in-out 2500ms; animation: fade-in 1.5s ease-in-out 2500ms; -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; animation-fill-mode: both; } .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, .tile .tile-inner { width: 58px; height: 58px; line-height: 67.5px; } .tile.tile-position-1-1 { -webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); transform: translate(0px, 0px); } .tile.tile-position-1-2 { -webkit-transform: translate(0px, 67px); -moz-transform: translate(0px, 67px); transform: translate(0px, 67px); } .tile.tile-position-1-3 { -webkit-transform: translate(0px, 135px); -moz-transform: translate(0px, 135px); transform: translate(0px, 135px); } .tile.tile-position-1-4 { -webkit-transform: translate(0px, 202px); -moz-transform: translate(0px, 202px); transform: translate(0px, 202px); } .tile.tile-position-2-1 { -webkit-transform: translate(67px, 0px); -moz-transform: translate(67px, 0px); transform: translate(67px, 0px); } .tile.tile-position-2-2 { -webkit-transform: translate(67px, 67px); -moz-transform: translate(67px, 67px); transform: translate(67px, 67px); } .tile.tile-position-2-3 { -webkit-transform: translate(67px, 135px); -moz-transform: translate(67px, 135px); transform: translate(67px, 135px); } .tile.tile-position-2-4 { -webkit-transform: translate(67px, 202px); -moz-transform: translate(67px, 202px); transform: translate(67px, 202px); } .tile.tile-position-3-1 { -webkit-transform: translate(135px, 0px); -moz-transform: translate(135px, 0px); transform: translate(135px, 0px); } .tile.tile-position-3-2 { -webkit-transform: translate(135px, 67px); -moz-transform: translate(135px, 67px); transform: translate(135px, 67px); } .tile.tile-position-3-3 { -webkit-transform: translate(135px, 135px); -moz-transform: translate(135px, 135px); transform: translate(135px, 135px); } .tile.tile-position-3-4 { -webkit-transform: translate(135px, 202px); -moz-transform: translate(135px, 202px); transform: translate(135px, 202px); } .tile.tile-position-4-1 { -webkit-transform: translate(202px, 0px); -moz-transform: translate(202px, 0px); transform: translate(202px, 0px); } .tile.tile-position-4-2 { -webkit-transform: translate(202px, 67px); -moz-transform: translate(202px, 67px); transform: translate(202px, 67px); } .tile.tile-position-4-3 { -webkit-transform: translate(202px, 135px); -moz-transform: translate(202px, 135px); transform: translate(202px, 135px); } .tile.tile-position-4-4 { -webkit-transform: translate(202px, 202px); -moz-transform: translate(202px, 202px); transform: translate(202px, 202px); } .tile .tile-inner { font-size: 25px; } .game-message { padding-top: 0; } .game-message p { font-size: 30px !important; height: 30px !important; line-height: 30px !important; margin-top: 32% !important; margin-bottom: 0 !important; } .game-message .lower { margin-top: 10px !important; } .game-message.game-won .score-sharing { margin-top: 10px; } .game-message.game-over .mailing-list { margin-top: 25px; } .game-message .mailing-list { margin-top: 10px; } .game-message .mailing-list .mailing-list-email-field { width: 180px; } .sharing > iframe, .sharing > span, .sharing > form { display: block; margin: 0 auto; margin-bottom: 20px; } } .pp-donate button { -webkit-appearance: none; -moz-appearance: none; appearance: none; border: none; font: inherit; color: inherit; display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; } .pp-donate button img { vertical-align: -4px; margin-right: 8px; } .btc-donate { position: relative; margin-left: 10px; display: inline-block; background: #8f7a66; border-radius: 3px; padding: 0 20px; text-decoration: none; color: #f9f6f2; height: 40px; line-height: 42px; cursor: pointer; } .btc-donate img { vertical-align: -4px; margin-right: 8px; } .btc-donate a { color: #f9f6f2; text-decoration: none; font-weight: normal; } .btc-donate .address { cursor: auto; position: absolute; width: 340px; right: 50%; margin-right: -170px; padding-bottom: 7px; top: -30px; opacity: 0; pointer-events: none; -webkit-transition: 400ms ease; -moz-transition: 400ms ease; transition: 400ms ease; -webkit-transition-property: top, opacity; -moz-transition-property: top, opacity; transition-property: top, opacity; } .btc-donate .address:after { position: absolute; border-top: 10px solid #bbada0; border-right: 7px solid transparent; border-left: 7px solid transparent; content: ""; bottom: 0px; left: 50%; margin-left: -7px; } .btc-donate .address code { background-color: #bbada0; padding: 10px 15px; width: 100%; border-radius: 3px; line-height: 1; font-weight: normal; font-size: 15px; font-family: Consolas, "Liberation Mono", Courier, monospace; text-align: center; } .btc-donate:hover .address, .btc-donate .address:hover .address { opacity: 1; top: -45px; pointer-events: auto; } @media screen and (max-width: 480px) { .btc-donate { width: 120px; } .btc-donate .address { margin-right: -150px; width: 300px; } .btc-donate .address code { font-size: 13px; } .btc-donate .address:after { left: 50%; bottom: 2px; } }
JAVASCRIPT
Function.prototype.bind = Function.prototype.bind || function (target) { var self = this; return function (args) { if (!(args instanceof Array)) { args = [args]; } self.apply(target, args); }; }; (function () { if (typeof window.Element === "undefined" || "classList" in document.documentElement) { return; } var prototype = Array.prototype, push = prototype.push, splice = prototype.splice, join = prototype.join; function DOMTokenList(el) { this.el = el; // The className needs to be trimmed and split on whitespace // to retrieve a list of classes. var classes = el.className.replace(/^\s+|\s+$/g, '').split(/\s+/); for (var i = 0; i < classes.length; i++) { push.call(this, classes[i]); } } DOMTokenList.prototype = { add: function (token) { if (this.contains(token)) return; push.call(this, token); this.el.className = this.toString(); }, contains: function (token) { return this.el.className.indexOf(token) != -1; }, item: function (index) { return this[index] || null; }, remove: function (token) { if (!this.contains(token)) return; for (var i = 0; i < this.length; i++) { if (this[i] == token) break; } splice.call(this, i, 1); this.el.className = this.toString(); }, toString: function () { return join.call(this, ' '); }, toggle: function (token) { if (!this.contains(token)) { this.add(token); } else { this.remove(token); } return this.contains(token); } }; window.DOMTokenList = DOMTokenList; function defineElementGetter(obj, prop, getter) { if (Object.defineProperty) { Object.defineProperty(obj, prop, { get: getter }); } else { obj.__defineGetter__(prop, getter); } } defineElementGetter(HTMLElement.prototype, 'classList', function () { return new DOMTokenList(this); }); })(); (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); }; } }()); function KeyboardInputManager() { this.events = {}; if (window.navigator.msPointerEnabled) { //Internet Explorer 10 style this.eventTouchstart = "MSPointerDown"; this.eventTouchmove = "MSPointerMove"; this.eventTouchend = "MSPointerUp"; } else { this.eventTouchstart = "touchstart"; this.eventTouchmove = "touchmove"; this.eventTouchend = "touchend"; } this.listen(); } KeyboardInputManager.prototype.on = function (event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }; KeyboardInputManager.prototype.emit = function (event, data) { var callbacks = this.events[event]; if (callbacks) { callbacks.forEach(function (callback) { callback(data); }); } }; KeyboardInputManager.prototype.listen = function () { var self = this; var map = { 38: 0, // Up 39: 1, // Right 40: 2, // Down 37: 3, // Left 75: 0, // Vim up 76: 1, // Vim right 74: 2, // Vim down 72: 3, // Vim left 87: 0, // W 68: 1, // D 83: 2, // S 65: 3 // A }; // Respond to direction keys document.addEventListener("keydown", function (event) { var modifiers = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; var mapped = map[event.which]; // Ignore the event if it's happening in a text field if (self.targetIsInput(event)) return; if (!modifiers) { if (mapped !== undefined) { event.preventDefault(); self.emit("move", mapped); } } // R key restarts the game if (!modifiers && event.which === 82) { self.restart.call(self, event); } }); // Respond to button presses this.bindButtonPress(".retry-button", this.restart); this.bindButtonPress(".restart-button", this.restart); this.bindButtonPress(".keep-playing-button", this.keepPlaying); // Respond to swipe events var touchStartClientX, touchStartClientY; var gameContainer = document.getElementsByClassName("game-container")[0]; gameContainer.addEventListener(this.eventTouchstart, function (event) { if ((!window.navigator.msPointerEnabled && event.touches.length > 1) || event.targetTouches > 1 || self.targetIsInput(event)) { return; // Ignore if touching with more than 1 finger or touching input } if (window.navigator.msPointerEnabled) { touchStartClientX = event.pageX; touchStartClientY = event.pageY; } else { touchStartClientX = event.touches[0].clientX; touchStartClientY = event.touches[0].clientY; } event.preventDefault(); }); gameContainer.addEventListener(this.eventTouchmove, function (event) { event.preventDefault(); }); gameContainer.addEventListener(this.eventTouchend, function (event) { if ((!window.navigator.msPointerEnabled && event.touches.length > 0) || event.targetTouches > 0 || self.targetIsInput(event)) { return; // Ignore if still touching with one or more fingers or input } var touchEndClientX, touchEndClientY; if (window.navigator.msPointerEnabled) { touchEndClientX = event.pageX; touchEndClientY = event.pageY; } else { touchEndClientX = event.changedTouches[0].clientX; touchEndClientY = event.changedTouches[0].clientY; } var dx = touchEndClientX - touchStartClientX; var absDx = Math.abs(dx); var dy = touchEndClientY - touchStartClientY; var absDy = Math.abs(dy); if (Math.max(absDx, absDy) > 10) { // (right : left) : (down : up) self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0)); } }); }; KeyboardInputManager.prototype.restart = function (event) { event.preventDefault(); this.emit("restart"); }; KeyboardInputManager.prototype.keepPlaying = function (event) { event.preventDefault(); this.emit("keepPlaying"); }; KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) { var button = document.querySelector(selector); button.addEventListener("click", fn.bind(this)); button.addEventListener(this.eventTouchend, fn.bind(this)); }; KeyboardInputManager.prototype.targetIsInput = function (event) { return event.target.tagName.toLowerCase() === "input"; }; function HTMLActuator() { this.tileContainer = document.querySelector(".tile-container"); this.scoreContainer = document.querySelector(".score-container"); this.bestContainer = document.querySelector(".best-container"); this.messageContainer = document.querySelector(".game-message"); this.sharingContainer = document.querySelector(".score-sharing"); this.score = 0; } HTMLActuator.prototype.actuate = function (grid, metadata) { var self = this; window.requestAnimationFrame(function () { self.clearContainer(self.tileContainer); grid.cells.forEach(function (column) { column.forEach(function (cell) { if (cell) { self.addTile(cell); } }); }); self.updateScore(metadata.score); self.updateBestScore(metadata.bestScore); if (metadata.terminated) { if (metadata.over) { self.message(false); // You lose } else if (metadata.won) { self.message(true); // You win! } } }); }; // Continues the game (both restart and keep playing) HTMLActuator.prototype.continueGame = function () { if (typeof ga !== "undefined") { ga("send", "event", "game", "restart"); } this.clearMessage(); }; HTMLActuator.prototype.clearContainer = function (container) { while (container.firstChild) { container.removeChild(container.firstChild); } }; //HTMLActuator.prototype.tileHTML = ["菜鸟", "入门", "码畜", "码奴", "码农", "IT民工", "IT工程师", "IT人才", "IT精英", "IT大哥", "IT领袖"]; HTMLActuator.prototype.tileHTML = ["2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048"]; //HTMLActuator.prototype.tileHTML = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "win"]; //HTMLActuator.prototype.tileHTML = ["工兵", "班长", "排长", "连长", "营长", "团长", "旅长", "师长", "军长", "司令", "军旗"]; HTMLActuator.prototype.addTile = function (tile) { var self = this; var wrapper = document.createElement("div"); var inner = document.createElement("div"); var position = tile.previousPosition || { x: tile.x, y: tile.y }; var positionClass = this.positionClass(position); // We can't use classlist because it somehow glitches when replacing classes var classes = ["tile", "tile-" + tile.value, positionClass]; if (tile.value > 2048) classes.push("tile-super"); this.applyClasses(wrapper, classes); inner.classList.add("tile-inner"); inner.textContent = HTMLActuator.prototype.tileHTML[Math.log(tile.value) / Math.LN2 - 1] || tile.value; if (tile.previousPosition) { // Make sure that the tile gets rendered in the previous position first window.requestAnimationFrame(function () { classes[2] = self.positionClass({ x: tile.x, y: tile.y }); self.applyClasses(wrapper, classes); // Update the position }); } else if (tile.mergedFrom) { classes.push("tile-merged"); this.applyClasses(wrapper, classes); // Render the tiles that merged tile.mergedFrom.forEach(function (merged) { self.addTile(merged); }); } else { classes.push("tile-new"); this.applyClasses(wrapper, classes); } // Add the inner part of the tile to the wrapper wrapper.appendChild(inner); // Put the tile on the board this.tileContainer.appendChild(wrapper); }; HTMLActuator.prototype.applyClasses = function (element, classes) { element.setAttribute("class", classes.join(" ")); }; HTMLActuator.prototype.normalizePosition = function (position) { return { x: position.x + 1, y: position.y + 1 }; }; HTMLActuator.prototype.positionClass = function (position) { position = this.normalizePosition(position); return "tile-position-" + position.x + "-" + position.y; }; HTMLActuator.prototype.updateScore = function (score) { this.clearContainer(this.scoreContainer); var difference = score - this.score; this.score = score; this.scoreContainer.textContent = this.score; if (difference > 0) { var addition = document.createElement("div"); addition.classList.add("score-addition"); addition.textContent = "+" + difference; this.scoreContainer.appendChild(addition); } }; HTMLActuator.prototype.updateBestScore = function (bestScore) { this.bestContainer.textContent = bestScore; }; HTMLActuator.prototype.message = function (won) { var type = won ? "game-won" : "game-over"; var message = won ? "You Win!" : "Game Over!"; if (typeof ga !== "undefined") { ga("send", "event", "game", "end", type, this.score); } this.messageContainer.classList.add(type); this.messageContainer.getElementsByTagName("p")[0].textContent = message; this.clearContainer(this.sharingContainer); this.sharingContainer.appendChild(this.scoreTweetButton()); //twttr.widgets.load(); }; HTMLActuator.prototype.clearMessage = function () { // IE only takes one value to remove at a time. this.messageContainer.classList.remove("game-won"); this.messageContainer.classList.remove("game-over"); }; HTMLActuator.prototype.scoreTweetButton = function () { var tweet = document.createElement("a"); tweet.classList.add("twitter-share-button"); tweet.setAttribute("href", "https://twitter.com/share"); tweet.setAttribute("data-via", "gabrielecirulli"); tweet.setAttribute("data-url", "https://git.io/2048"); tweet.setAttribute("data-counturl", "https://gabrielecirulli.github.io/2048/"); tweet.textContent = "Tweet"; var text = "I scored " + this.score + " points at 2048, a game where you " + "join numbers to score high! #2048game"; tweet.setAttribute("data-text", text); return tweet; }; function Grid(size, previousState) { this.size = size; this.cells = previousState ? this.fromState(previousState) : this.empty(); } // Build a grid of the specified size Grid.prototype.empty = function () { var cells = []; for (var x = 0; x < this.size; x++) { var row = cells[x] = []; for (var y = 0; y < this.size; y++) { row.push(null); } } return cells; }; Grid.prototype.fromState = function (state) { var cells = []; for (var x = 0; x < this.size; x++) { var row = cells[x] = []; for (var y = 0; y < this.size; y++) { var tile = state[x][y]; row.push(tile ? new Tile(tile.position, tile.value) : null); } } return cells; }; // Find the first available random position Grid.prototype.randomAvailableCell = function () { var cells = this.availableCells(); if (cells.length) { return cells[Math.floor(Math.random() * cells.length)]; } }; Grid.prototype.availableCells = function () { var cells = []; this.eachCell(function (x, y, tile) { if (!tile) { cells.push({ x: x, y: y }); } }); return cells; }; // Call callback for every cell Grid.prototype.eachCell = function (callback) { for (var x = 0; x < this.size; x++) { for (var y = 0; y < this.size; y++) { callback(x, y, this.cells[x][y]); } } }; // Check if there are any cells available Grid.prototype.cellsAvailable = function () { return !!this.availableCells().length; }; // Check if the specified cell is taken Grid.prototype.cellAvailable = function (cell) { return !this.cellOccupied(cell); }; Grid.prototype.cellOccupied = function (cell) { return !!this.cellContent(cell); }; Grid.prototype.cellContent = function (cell) { if (this.withinBounds(cell)) { return this.cells[cell.x][cell.y]; } else { return null; } }; // Inserts a tile at its position Grid.prototype.insertTile = function (tile) { this.cells[tile.x][tile.y] = tile; }; Grid.prototype.removeTile = function (tile) { this.cells[tile.x][tile.y] = null; }; Grid.prototype.withinBounds = function (position) { return position.x >= 0 && position.x < this.size && position.y >= 0 && position.y < this.size; }; Grid.prototype.serialize = function () { var cellState = []; for (var x = 0; x < this.size; x++) { var row = cellState[x] = []; for (var y = 0; y < this.size; y++) { row.push(this.cells[x][y] ? this.cells[x][y].serialize() : null); } } return { size: this.size, cells: cellState }; }; function Tile(position, value) { this.x = position.x; this.y = position.y; this.value = value || 2; this.previousPosition = null; this.mergedFrom = null; // Tracks tiles that merged together } Tile.prototype.savePosition = function () { this.previousPosition = { x: this.x, y: this.y }; }; Tile.prototype.updatePosition = function (position) { this.x = position.x; this.y = position.y; }; Tile.prototype.serialize = function () { return { position: { x: this.x, y: this.y }, value: this.value }; }; window.fakeStorage = { _data: {}, setItem: function (id, val) { return this._data[id] = String(val); }, getItem: function (id) { return this._data.hasOwnProperty(id) ? this._data[id] : undefined; }, removeItem: function (id) { return delete this._data[id]; }, clear: function () { return this._data = {}; } }; function LocalStorageManager() { this.bestScoreKey = "bestScore"; this.gameStateKey = "gameState"; var supported = this.localStorageSupported(); this.storage = supported ? window.localStorage : window.fakeStorage; } LocalStorageManager.prototype.localStorageSupported = function () { var testKey = "test"; var storage = window.localStorage; try { storage.setItem(testKey, "1"); storage.removeItem(testKey); return true; } catch (error) { return false; } }; // Best score getters/setters LocalStorageManager.prototype.getBestScore = function () { return this.storage.getItem(this.bestScoreKey) || 0; }; LocalStorageManager.prototype.setBestScore = function (score) { this.storage.setItem(this.bestScoreKey, score); }; // Game state getters/setters and clearing LocalStorageManager.prototype.getGameState = function () { var stateJSON = this.storage.getItem(this.gameStateKey); return stateJSON ? JSON.parse(stateJSON) : null; }; LocalStorageManager.prototype.setGameState = function (gameState) { this.storage.setItem(this.gameStateKey, JSON.stringify(gameState)); }; LocalStorageManager.prototype.clearGameState = function () { this.storage.removeItem(this.gameStateKey); }; function GameManager(size, InputManager, Actuator, StorageManager) { this.size = size; // Size of the grid this.inputManager = new InputManager; this.storageManager = new StorageManager; this.actuator = new Actuator; this.startTiles = 2; this.inputManager.on("move", this.move.bind(this)); this.inputManager.on("restart", this.restart.bind(this)); this.inputManager.on("keepPlaying", this.keepPlaying.bind(this)); this.setup(); } // Restart the game GameManager.prototype.restart = function () { this.storageManager.clearGameState(); this.actuator.continueGame(); // Clear the game won/lost message this.setup(); }; // Keep playing after winning (allows going over 2048) GameManager.prototype.keepPlaying = function () { this.keepPlaying = true; this.actuator.continueGame(); // Clear the game won/lost message }; // Return true if the game is lost, or has won and the user hasn't kept playing GameManager.prototype.isGameTerminated = function () { if (this.over || (this.won && !this.keepPlaying)) { return true; } else { return false; } }; // Set up the game GameManager.prototype.setup = function () { var previousState = this.storageManager.getGameState(); // Reload the game from a previous game if present if (previousState) { this.grid = new Grid(previousState.grid.size, previousState.grid.cells); // Reload grid this.score = previousState.score; this.over = previousState.over; this.won = previousState.won; this.keepPlaying = previousState.keepPlaying; } else { this.grid = new Grid(this.size); this.score = 0; this.over = false; this.won = false; this.keepPlaying = false; // Add the initial tiles this.addStartTiles(); } // Update the actuator this.actuate(); }; // Set up the initial tiles to start the game with GameManager.prototype.addStartTiles = function () { for (var i = 0; i < this.startTiles; i++) { this.addRandomTile(); } }; // Adds a tile in a random position GameManager.prototype.addRandomTile = function () { if (this.grid.cellsAvailable()) { var value = Math.random() < 0.9 ? 2 : 4; var tile = new Tile(this.grid.randomAvailableCell(), value); this.grid.insertTile(tile); } }; // Sends the updated grid to the actuator GameManager.prototype.actuate = function () { if (this.storageManager.getBestScore() < this.score) { this.storageManager.setBestScore(this.score); } // Clear the state when the game is over (game over only, not win) if (this.over) { this.storageManager.clearGameState(); } else { this.storageManager.setGameState(this.serialize()); } this.actuator.actuate(this.grid, { score: this.score, over: this.over, won: this.won, bestScore: this.storageManager.getBestScore(), terminated: this.isGameTerminated() }); }; // Represent the current game as an object GameManager.prototype.serialize = function () { return { grid: this.grid.serialize(), score: this.score, over: this.over, won: this.won, keepPlaying: this.keepPlaying }; }; // Save all tile positions and remove merger info GameManager.prototype.prepareTiles = function () { this.grid.eachCell(function (x, y, tile) { if (tile) { tile.mergedFrom = null; tile.savePosition(); } }); }; // Move a tile and its representation GameManager.prototype.moveTile = function (tile, cell) { this.grid.cells[tile.x][tile.y] = null; this.grid.cells[cell.x][cell.y] = tile; tile.updatePosition(cell); }; // Move tiles on the grid in the specified direction GameManager.prototype.move = function (direction) { // 0: up, 1: right, 2: down, 3: left var self = this; if (this.isGameTerminated()) return; // Don't do anything if the game's over var cell, tile; var vector = this.getVector(direction); var traversals = this.buildTraversals(vector); var moved = false; // Save the current tile positions and remove merger information this.prepareTiles(); // Traverse the grid in the right direction and move tiles traversals.x.forEach(function (x) { traversals.y.forEach(function (y) { cell = { x: x, y: y }; tile = self.grid.cellContent(cell); if (tile) { var positions = self.findFarthestPosition(cell, vector); var next = self.grid.cellContent(positions.next); // Only one merger per row traversal? if (next && next.value === tile.value && !next.mergedFrom) { var merged = new Tile(positions.next, tile.value * 2); merged.mergedFrom = [tile, next]; self.grid.insertTile(merged); self.grid.removeTile(tile); // Converge the two tiles' positions tile.updatePosition(positions.next); // Update the score self.score += merged.value; // The mighty 2048 tile if (merged.value === 2048) self.won = true; } else { self.moveTile(tile, positions.farthest); } if (!self.positionsEqual(cell, tile)) { moved = true; // The tile moved from its original cell! } } }); }); if (moved) { this.addRandomTile(); if (!this.movesAvailable()) { this.over = true; // Game over! } this.actuate(); } }; // Get the vector representing the chosen direction GameManager.prototype.getVector = function (direction) { // Vectors representing tile movement var map = { 0: { x: 0, y: -1 }, // Up 1: { x: 1, y: 0 }, // Right 2: { x: 0, y: 1 }, // Down 3: { x: -1, y: 0 } // Left }; return map[direction]; }; // Build a list of positions to traverse in the right order GameManager.prototype.buildTraversals = function (vector) { var traversals = { x: [], y: [] }; for (var pos = 0; pos < this.size; pos++) { traversals.x.push(pos); traversals.y.push(pos); } // Always traverse from the farthest cell in the chosen direction if (vector.x === 1) traversals.x = traversals.x.reverse(); if (vector.y === 1) traversals.y = traversals.y.reverse(); return traversals; }; GameManager.prototype.findFarthestPosition = function (cell, vector) { var previous; // Progress towards the vector direction until an obstacle is found do { previous = cell; cell = { x: previous.x + vector.x, y: previous.y + vector.y }; } while (this.grid.withinBounds(cell) && this.grid.cellAvailable(cell)); return { farthest: previous, next: cell // Used to check if a merge is required }; }; GameManager.prototype.movesAvailable = function () { return this.grid.cellsAvailable() || this.tileMatchesAvailable(); }; // Check for available matches between tiles (more expensive check) GameManager.prototype.tileMatchesAvailable = function () { var self = this; var tile; for (var x = 0; x < this.size; x++) { for (var y = 0; y < this.size; y++) { tile = this.grid.cellContent({ x: x, y: y }); if (tile) { for (var direction = 0; direction < 4; direction++) { var vector = self.getVector(direction); var cell = { x: x + vector.x, y: y + vector.y }; var other = self.grid.cellContent(cell); if (other && other.value === tile.value) { return true; // These two tiles can be merged } } } } } return false; }; GameManager.prototype.positionsEqual = function (first, second) { return first.x === second.x && first.y === second.y; }; // Wait till the browser is ready to render the game (avoids glitches) window.requestAnimationFrame(function () { new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager); });
Expand for more options Login