├── favicon.ico ├── js ├── config.js ├── main.js ├── data.js ├── event.js ├── utils.js ├── view.js └── game.js ├── README.md ├── .gitignore ├── font ├── ClearSans-Bold-webfont.woff ├── ClearSans-Regular-webfont.woff └── clear-sans.css ├── .history ├── README_20190809111156.md ├── README_20190809111214.md └── README_20190809111215.md ├── css ├── keyframes.css ├── media.css └── style.css ├── LICENSE └── index.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gd4Ark/2048/HEAD/favicon.ico -------------------------------------------------------------------------------- /js/config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | bonus_point: 4, 3 | max : 2048, 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2048 游戏 2 | 3 | 在线玩:https://4ark.me/2048 4 | 5 | 如果对您有帮助欢迎 Star!😋 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/ 7 | .deploy*/ 8 | .history -------------------------------------------------------------------------------- /font/ClearSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gd4Ark/2048/HEAD/font/ClearSans-Bold-webfont.woff -------------------------------------------------------------------------------- /font/ClearSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gd4Ark/2048/HEAD/font/ClearSans-Regular-webfont.woff -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | on(window, 'load', function () { 2 | var view = new View(); 3 | var game = new Game(); 4 | game.init(view); 5 | event(game); 6 | }); -------------------------------------------------------------------------------- /.history/README_20190809111156.md: -------------------------------------------------------------------------------- 1 | # 2048 游戏 2 | 3 | 在线玩:https://gd4ark.github.io/2048/ 4 | 5 | 游戏截图: 6 | 7 | ![](https://ws1.sinaimg.cn/large/006mS5wEgy1fzy0t6zcp4j31200n30tt.jpg) 8 | 9 | 如果对您有帮助欢迎Star!😋 -------------------------------------------------------------------------------- /.history/README_20190809111214.md: -------------------------------------------------------------------------------- 1 | # 2048 游戏 2 | 3 | 在线玩:https://4ark.me/2048 4 | 5 | 游戏截图: 6 | 7 | ![](https://ws1.sinaimg.cn/large/006mS5wEgy1fzy0t6zcp4j31200n30tt.jpg) 8 | 9 | 如果对您有帮助欢迎 Star!😋 10 | -------------------------------------------------------------------------------- /.history/README_20190809111215.md: -------------------------------------------------------------------------------- 1 | # 2048 游戏 2 | 3 | 在线玩:https://4ark.me/2048 4 | 5 | 游戏截图: 6 | 7 | ![](https://ws1.sinaimg.cn/large/006mS5wEgy1fzy0t6zcp4j31200n30tt.jpg) 8 | 9 | 如果对您有帮助欢迎 Star!😋 10 | -------------------------------------------------------------------------------- /css/keyframes.css: -------------------------------------------------------------------------------- 1 | @keyframes moveup{ 2 | 0%{ 3 | transform: translateY(0); 4 | opacity: 1; 5 | } 6 | 100%{ 7 | transform: translateY(-150px); 8 | opacity: 0; 9 | } 10 | } 11 | @keyframes appear{ 12 | 0%{ 13 | transform: scale(0); 14 | } 15 | 100%{ 16 | transform: scale(1); 17 | } 18 | } 19 | @keyframes add{ 20 | 0%{ 21 | transform: scale(1); 22 | box-shadow: 0 0 1px red; 23 | } 24 | 50%{ 25 | transform: scale(1.2); 26 | } 27 | 100%{ 28 | transform: scale(1); 29 | } 30 | } -------------------------------------------------------------------------------- /js/data.js: -------------------------------------------------------------------------------- 1 | var data = { 2 | score: 0, 3 | best: 0, 4 | cell: [ 5 | 6 | ] 7 | } 8 | var indexs = [ 9 | // left 10 | [ 11 | [0, 1, 2, 3], 12 | [4, 5, 6, 7], 13 | [8, 9, 10, 11], 14 | [12, 13, 14, 15], 15 | ], 16 | // top 17 | [ 18 | [0, 4, 8, 12], 19 | [1, 5, 9, 13], 20 | [2, 6, 10, 14], 21 | [3, 7, 11, 15], 22 | ], 23 | // right 24 | [ 25 | [3, 2, 1, 0], 26 | [7, 6, 5, 4], 27 | [11, 10, 9, 8], 28 | [15, 14, 13, 12], 29 | ], 30 | // bottom 31 | [ 32 | [12, 8, 4, 0], 33 | [13, 9, 5, 1], 34 | [14, 10, 6, 2], 35 | [15, 11, 7, 3], 36 | ] 37 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4Ark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /js/event.js: -------------------------------------------------------------------------------- 1 | function event(game) { 2 | 3 | var down = false; 4 | 5 | var gameContainer = $('.game-container')[0]; 6 | 7 | on(window, 'keydown', function (e) { 8 | if (down) return; 9 | down = true; 10 | var num = e.keyCode - 37; 11 | if (num >= 0 && num <= 3) { 12 | game.move(num); 13 | } 14 | }); 15 | 16 | on(window, 'keyup', function () { 17 | down = false; 18 | }); 19 | 20 | touchMoveDir(gameContainer, 15, function (dir) { 21 | game.move(dir); 22 | }); 23 | 24 | on($('.restart-btn')[0], 'click', function (e) { 25 | e.preventDefault(); 26 | game.restart(); 27 | }); 28 | 29 | on(window, 'resize', function () { 30 | game.view.resize(); 31 | }); 32 | 33 | // 自动测试 34 | var autoTest = false; 35 | 36 | if (autoTest) { 37 | (function () { 38 | var timer = setInterval(function () { 39 | var moveInfo = game.move(random(0, 3)); 40 | if (!moveInfo) { 41 | clearInterval(timer); 42 | } 43 | }, 20); 44 | })(); 45 | } 46 | } -------------------------------------------------------------------------------- /css/media.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width:1250px){ 2 | html,body{ 3 | font-size: 15px; 4 | } 5 | .container{ 6 | width: 280px; 7 | } 8 | h1.title{ 9 | font-size: 31px; 10 | } 11 | .score-container, 12 | .best-container{ 13 | padding: 3px 10px; 14 | } 15 | .game-container{ 16 | margin-top: 10px; 17 | width: 280px; 18 | height: 280px; 19 | } 20 | .grid-container, 21 | .tile-container{ 22 | width: 260px; 23 | height: 260px; 24 | } 25 | .grid-row, 26 | .tile{ 27 | height: 22.75%; 28 | } 29 | .grid-cell, 30 | .tile{ 31 | width: 22.75%; 32 | } 33 | .grid-row{ 34 | margin-bottom: 3%; 35 | } 36 | .grid-cell{ 37 | margin-right: 3%; 38 | } 39 | .tile{ 40 | font-size: 35px; 41 | } 42 | .tile[data-val="128"], 43 | .tile[data-val="256"], 44 | .tile[data-val="512"]{ 45 | font-size: 25px; 46 | } 47 | .tile[data-val="1024"], 48 | .tile[data-val="2048"]{ 49 | font-size: 21px; 50 | } 51 | 52 | .pop-container p{ 53 | font-size: 30px; 54 | } 55 | } -------------------------------------------------------------------------------- /font/clear-sans.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Clear Sans"; 3 | src: url("ClearSans-Light-webfont.eot"); 4 | src: url("ClearSans-Light-webfont.eot?#iefix") format("embedded-opentype"), 5 | url("ClearSans-Light-webfont.svg#clear_sans_lightregular") format("svg"), 6 | url("ClearSans-Light-webfont.woff") format("woff"); 7 | font-weight: 200; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: "Clear Sans"; 13 | src: url("ClearSans-Regular-webfont.eot"); 14 | src: url("ClearSans-Regular-webfont.eot?#iefix") format("embedded-opentype"), 15 | url("ClearSans-Regular-webfont.svg#clear_sansregular") format("svg"), 16 | url("ClearSans-Regular-webfont.woff") format("woff"); 17 | font-weight: normal; 18 | font-style: normal; 19 | } 20 | 21 | @font-face { 22 | font-family: "Clear Sans"; 23 | src: url("ClearSans-Bold-webfont.eot"); 24 | src: url("ClearSans-Bold-webfont.eot?#iefix") format("embedded-opentype"), 25 | url("ClearSans-Bold-webfont.svg#clear_sansbold") format("svg"), 26 | url("ClearSans-Bold-webfont.woff") format("woff"); 27 | font-weight: 700; 28 | font-style: normal; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | var log = console.log.bind(console); 2 | var random = function (start, end) { 3 | start = start === void 0 ? 0 : start; 4 | end = end === void 0 ? 1 : end; 5 | end = end + 1; 6 | var rand = Math.random() * (end - start) + start; 7 | return Math.floor(rand); 8 | }; 9 | var $ = function (elem) { 10 | return document.querySelectorAll(elem); 11 | } 12 | var on = function (elem, type, callback) { 13 | elem.addEventListener(type, function (e) { 14 | callback(e); 15 | }); 16 | } 17 | 18 | var indexToPos = function (index) { 19 | return { 20 | x: index % 4, 21 | y: Math.floor(index / 4), 22 | } 23 | } 24 | 25 | var getLocalStorage = function (key) { 26 | return localStorage[key] ? 27 | JSON.parse(localStorage[key]) : null; 28 | } 29 | 30 | var touchMoveDir = function (elem, min, callback) { 31 | var touchPos = { 32 | beforeX: 0, 33 | beforeY: 0, 34 | afterX: 0, 35 | afterY: 0, 36 | } 37 | var move = false; 38 | var dir; 39 | on(elem, 'touchstart', function (e) { 40 | touchPos.beforeX = e.touches[0].clientX; 41 | touchPos.beforeY = e.touches[0].clientY; 42 | }); 43 | on(elem, 'touchmove', function (e) { 44 | move = true; 45 | touchPos.afterX = e.touches[0].clientX; 46 | touchPos.afterY = e.touches[0].clientY; 47 | }); 48 | on(elem, 'touchend', function (e) { 49 | if (!move) return; 50 | var x = touchPos.beforeX - touchPos.afterX; 51 | var y = touchPos.beforeY - touchPos.afterY; 52 | log(x, y); 53 | if (Math.abs(x) < min && Math.abs(y) < min) { 54 | return; 55 | } 56 | if (Math.abs(x) > Math.abs(y)) { 57 | dir = x > 0 ? 0 : 2; 58 | } else { 59 | dir = y > 0 ? 1 : 3; 60 | } 61 | move = false; 62 | callback(dir); 63 | }); 64 | }; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 2048 13 | 14 | 15 | 16 |
17 |
18 |

19 | 2048 20 |

21 |
22 |
23 |

SCORE

24 |

0

25 |
26 | +4 27 |
28 |
29 |
30 |

BEST

31 |

66666

32 |
33 |
34 |
35 |
36 | New Game 37 |

38 | Play 2048 Game Online 39 |

40 |

41 | Join the numbers and get to the 2048 tile! 42 |

43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 |
74 |
75 |

:(

76 |

FIALURE

77 |
78 |
79 |

:)

80 |

WINNING

81 |
82 |
83 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js/view.js: -------------------------------------------------------------------------------- 1 | var View = (function () { 2 | 3 | var tileContainer = $('.tile-container')[0]; 4 | var scoreContainer = $('.score-container')[0]; 5 | var scoreDom = $('.score-container .score')[0]; 6 | var scoreAddition = $('.score-addition')[0]; 7 | var bestDom = $('.best-container .score')[0]; 8 | var failureContainer = $('.failure-container')[0]; 9 | var winningContainer = $('.winning-container')[0]; 10 | 11 | var View = function () { 12 | 13 | }; 14 | 15 | View.prototype = { 16 | setup: function () { 17 | failureContainer.classList.remove('action'); 18 | winningContainer.classList.remove('action'); 19 | this.updateScore(data.score); 20 | this.updateBest(); 21 | }, 22 | restart: function () { 23 | tileContainer.innerHTML = ""; 24 | }, 25 | resize: function () { 26 | var _this = this; 27 | data.cell.forEach(function (el, index) { 28 | var tile = _this.getTile(index); 29 | if (!tile) return; 30 | var pos = _this.getPos(indexToPos(index)); 31 | _this.setPos(tile, pos); 32 | }); 33 | }, 34 | failure: function () { 35 | failureContainer.classList.add('action'); 36 | }, 37 | winning: function () { 38 | winningContainer.classList.add('action'); 39 | }, 40 | restoreTile: function () { 41 | var _this = this; 42 | data.cell.forEach(function (el, index) { 43 | if (el.val !== 0) { 44 | _this.appear(index); 45 | } 46 | }); 47 | }, 48 | addScoreAnimation: function (score) { 49 | if (!score) return; 50 | scoreAddition.innerHTML = '+' + score; 51 | scoreAddition.classList.add('action'); 52 | setTimeout(function () { 53 | scoreAddition.classList.remove('action'); 54 | }, 500); 55 | }, 56 | updateScore: function (score) { 57 | scoreDom.innerHTML = data.score; 58 | this.addScoreAnimation(score); 59 | }, 60 | updateBest: function () { 61 | bestDom.innerHTML = data.best; 62 | }, 63 | setInfo: function (elem, pos, index) { 64 | elem.style.left = pos.left + 'px'; 65 | elem.style.top = pos.top + 'px'; 66 | elem.setAttribute('data-index', index); 67 | }, 68 | getTile: function (index) { 69 | return $(`.tile[data-index='${index}']`)[0]; 70 | }, 71 | getPos: function (pos) { 72 | var gridCell = $(`.grid-row:nth-child(${pos.y+1}) .grid-cell:nth-child(${pos.x+1})`)[0]; 73 | return { 74 | left: gridCell.offsetLeft, 75 | top: gridCell.offsetTop, 76 | } 77 | }, 78 | setPos: function (elem, pos) { 79 | elem.style.left = pos.left + 'px'; 80 | elem.style.top = pos.top + 'px'; 81 | }, 82 | createTileHTML: function (obj) { 83 | var tile = document.createElement('div'); 84 | tile.className = obj.classNames; 85 | tile.innerHTML = obj.val; 86 | tile.setAttribute('data-index', obj.index); 87 | tile.setAttribute('data-val', obj.val); 88 | this.setPos(tile, obj.pos); 89 | return tile; 90 | }, 91 | appear: function (index) { 92 | var last = data.cell[index]; 93 | var pos = this.getPos(indexToPos(index)); 94 | var newTile = this.createTileHTML({ 95 | val: last.val, 96 | pos: pos, 97 | index: index, 98 | classNames: " tile new-tile", 99 | }); 100 | tileContainer.appendChild(newTile); 101 | }, 102 | remove: function (index) { 103 | var tile = this.getTile(index); 104 | tile.parentElement.removeChild(tile); 105 | }, 106 | move: function (old_index, index) { 107 | var tile = this.getTile(old_index); 108 | var pos = this.getPos(indexToPos(index)); 109 | this.setInfo(tile, pos, index); 110 | }, 111 | updateVal: function (index) { 112 | var tile = this.getTile(index); 113 | var val = data.cell[index].val; 114 | tile.setAttribute('data-val', val); 115 | tile.innerHTML = val; 116 | tile.classList.add('addition'); 117 | setTimeout(function () { 118 | tile.classList.remove('addition'); 119 | tile.classList.remove('new-tile'); 120 | }, 300); 121 | }, 122 | } 123 | 124 | return View; 125 | 126 | })(); -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @import "../font/clear-sans.css"; 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | html, 10 | body { 11 | font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif; 12 | color: #776e65; 13 | font-size: 18px; 14 | background: rgba(238, 228, 218, 0.5); 15 | overflow: hidden; 16 | height: 100%; 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | color: inherit; 22 | cursor: pointer; 23 | } 24 | 25 | .container { 26 | position: relative; 27 | margin: 0 auto; 28 | display: flex; 29 | flex-direction: column; 30 | width: 450px; 31 | height: 100vh; 32 | box-sizing: content-box; 33 | } 34 | 35 | .heading { 36 | margin: 20px 0; 37 | } 38 | 39 | .heading { 40 | display: flex; 41 | align-items: center; 42 | justify-content: space-between; 43 | flex-wrap: wrap; 44 | } 45 | 46 | h1.title { 47 | font-size: 74px; 48 | line-height: 66px; 49 | font-weight: bold; 50 | } 51 | 52 | .scores-container { 53 | float: right; 54 | } 55 | 56 | .score-container, 57 | .best-container { 58 | float: left; 59 | padding: 3px 20px; 60 | background: #bbada0; 61 | border-radius: 3px; 62 | font-size: 25px; 63 | font-weight: bold; 64 | color: white; 65 | text-align: center; 66 | } 67 | 68 | .score-container { 69 | position: relative; 70 | margin-right: 15px; 71 | margin-bottom: 5px; 72 | } 73 | 74 | .score-container .title, 75 | .best-container .title { 76 | font-size: 13px; 77 | color: #eee4da; 78 | } 79 | 80 | .score-addition { 81 | position: absolute; 82 | left: 0; 83 | bottom: 0; 84 | width: 100%; 85 | color: rgba(119, 110, 101, 0.9); 86 | z-index: 100; 87 | opacity: 0; 88 | font-size: 1rem; 89 | } 90 | 91 | .score-addition.action { 92 | animation: 2s moveup; 93 | } 94 | 95 | .above-game { 96 | font-size: 0.9rem; 97 | } 98 | 99 | .restart-btn { 100 | margin: 5px 0 0 5px; 101 | float: right; 102 | padding: 0 8px; 103 | background: #8f7a66; 104 | color: white; 105 | height: 40px; 106 | line-height: 40px; 107 | border-radius: 3px; 108 | font-weight: bold; 109 | } 110 | 111 | h2.subtitle { 112 | font-size: 1em; 113 | } 114 | 115 | .game-container { 116 | position: relative; 117 | margin-top: 15px; 118 | width: 450px; 119 | height: 450px; 120 | background: #bbada0; 121 | border-radius: 6px; 122 | touch-action: none; 123 | } 124 | 125 | .grid-container, 126 | .tile-container, 127 | .pop-container { 128 | position: absolute; 129 | top: 0; 130 | left: 0; 131 | right: 0; 132 | bottom: 0; 133 | margin: auto; 134 | width: 426px; 135 | height: 426px; 136 | } 137 | 138 | 139 | .grid-row { 140 | display: flex; 141 | margin-bottom: 2%; 142 | } 143 | 144 | .grid-row, 145 | .tile { 146 | height: 23.5%; 147 | } 148 | 149 | .grid-cell, 150 | .tile { 151 | width: 23.5%; 152 | border-radius: 3px; 153 | } 154 | 155 | .grid-cell { 156 | margin-right: 2%; 157 | background: rgba(238, 228, 218, 0.35); 158 | } 159 | 160 | .grid-cell:nth-of-type(4n) { 161 | margin-right: 0; 162 | } 163 | 164 | .tile { 165 | position: absolute; 166 | display: flex; 167 | align-items: center; 168 | justify-content: center; 169 | font-size: 53px; 170 | font-weight: bold; 171 | background: #eee4da; 172 | transition: all 0.15s ease; 173 | } 174 | 175 | .tile.new-tile { 176 | animation: 0.5s appear; 177 | } 178 | 179 | .tile.addition { 180 | animation: 0.3s add; 181 | } 182 | 183 | .tile[data-val] { 184 | color: #f9f6f2; 185 | } 186 | 187 | .tile[data-val="2"], 188 | .tile[data-val="4"] { 189 | color: #776e65; 190 | } 191 | 192 | .tile[data-val="128"], 193 | .tile[data-val="256"], 194 | .tile[data-val="512"] { 195 | font-size: 45px; 196 | } 197 | 198 | .tile[data-val="1024"], 199 | .tile[data-val="2048"] { 200 | font-size: 33px; 201 | } 202 | 203 | .tile[data-val="4"] { 204 | background: #ede0c8; 205 | } 206 | 207 | .tile[data-val="8"] { 208 | background: #f2b179; 209 | } 210 | 211 | .tile[data-val="16"] { 212 | background: #f59563; 213 | } 214 | 215 | .tile[data-val="32"] { 216 | background: #f67c5f; 217 | } 218 | 219 | .tile[data-val="64"] { 220 | background: #f65e3b; 221 | } 222 | 223 | .tile[data-val="128"] { 224 | background: #edcf72; 225 | } 226 | 227 | .tile[data-val="256"] { 228 | background: #edcc61; 229 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); 230 | } 231 | 232 | .tile[data-val="512"] { 233 | background: #edc850; 234 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); 235 | } 236 | 237 | .tile[data-val="1024"] { 238 | background: #edc53f; 239 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); 240 | } 241 | 242 | .tile[data-val="2048"] { 243 | background: #edc22e; 244 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); 245 | } 246 | 247 | .pop-container { 248 | display: flex; 249 | flex-direction: column; 250 | align-items: center; 251 | justify-content: center; 252 | width: 100%; 253 | height: 100%; 254 | background: rgba(238, 228, 218, 0.8); 255 | z-index: 100; 256 | opacity: 0; 257 | transition: all 1s ease; 258 | } 259 | 260 | .winning-container p:nth-child(1){ 261 | transform: rotate(90deg); 262 | } 263 | 264 | .winning-container.action p:nth-child(1){ 265 | transform-origin: center; 266 | animation: winning 1.5s infinite; 267 | } 268 | 269 | .failure-container.action p:nth-child(1){ 270 | transform-origin: center; 271 | animation: failure 5s infinite; 272 | } 273 | 274 | @keyframes winning{ 275 | 0%,25%,50%,75%,100%{ 276 | transform: rotate(90deg) translateX(-5px); 277 | } 278 | 12.5%{ 279 | transform: rotate(80deg); 280 | } 281 | 37.5%{ 282 | transform: rotate(70deg); 283 | } 284 | 62.5%{ 285 | transform: rotate(100deg); 286 | } 287 | 87.5%{ 288 | transform: rotate(120deg); 289 | } 290 | } 291 | 292 | @keyframes failure{ 293 | 0%,25%,50%,75%,100%{ 294 | transform: rotate(90deg) scale(0.7,1) rotateX(40deg); 295 | } 296 | 12.5%{ 297 | transform: rotate(90deg); 298 | } 299 | 37.5%{ 300 | transform: rotate(90deg); 301 | } 302 | 62.5%{ 303 | transform: rotate(90deg); 304 | } 305 | 87.5%{ 306 | transform: rotate(90deg); 307 | } 308 | } 309 | 310 | .pop-container.action { 311 | opacity: 1; 312 | } 313 | 314 | .pop-container p { 315 | font-size: 60px; 316 | font-weight: bold; 317 | } 318 | 319 | .footer { 320 | flex: 1; 321 | padding: 10px 0; 322 | text-align: center; 323 | display: flex; 324 | justify-content: space-around; 325 | align-items: flex-end; 326 | font-size: 0.75rem; 327 | color: #333; 328 | opacity: 0.7; 329 | } -------------------------------------------------------------------------------- /js/game.js: -------------------------------------------------------------------------------- 1 | var Game = (function () { 2 | 3 | var cell = data.cell; 4 | var over = false; 5 | var move = false; 6 | 7 | var Game = function (view) { 8 | 9 | }; 10 | Game.prototype = { 11 | init: function (view) { 12 | var _this = this; 13 | this.view = view; 14 | var history = this.getHistory(); 15 | if (history) { 16 | this.restoreHistory(history); 17 | } else { 18 | this.initCell(); 19 | this.start(); 20 | } 21 | this.setBest(); 22 | setTimeout(function () { 23 | _this.view.setup(); 24 | }); 25 | }, 26 | start: function () { 27 | for (var i = 0; i < 2; i++) { 28 | this.randomAddItem(); 29 | } 30 | }, 31 | restart: function () { 32 | var _this = this; 33 | over = false; 34 | this.initCell(); 35 | this.view.restart(); 36 | this.start(); 37 | data.score = 0; 38 | this.save(); 39 | setTimeout(function () { 40 | _this.view.setup(); 41 | }); 42 | }, 43 | save: function () { 44 | localStorage.bestScore = data.best; 45 | localStorage.gameState = JSON.stringify({ 46 | cell: data.cell, 47 | socre: data.score, 48 | }); 49 | }, 50 | winning(){ 51 | over = true; 52 | localStorage.gameState = ''; 53 | this.view.winning(); 54 | }, 55 | checkWinning(){ 56 | var isWinning = cell.find(function(el){ 57 | return el.val === config.max 58 | }); 59 | if (isWinning){ 60 | this.winning(); 61 | } 62 | }, 63 | failure: function () { 64 | over = true; 65 | localStorage.gameState = ''; 66 | this.view.failure(); 67 | }, 68 | checkfailure: function () { 69 | var _this = this; 70 | var same = false; 71 | var called = function (arr, str) { 72 | if (same) return; 73 | same = arr.some(function (el) { 74 | return _this.checkSame(el); 75 | }); 76 | }; 77 | called(this.chunkX(), 'x'); 78 | called(this.chunkY(), 'y'); 79 | setTimeout(function () { 80 | if (!same) { 81 | _this.failure(); 82 | } 83 | }); 84 | }, 85 | checkSame: function (arr, index) { 86 | same = arr.some(function (el, index, arr) { 87 | if (index === arr.length - 1) return; 88 | return el.val === arr[index + 1].val; 89 | return true; 90 | }); 91 | return same; 92 | }, 93 | setBest: function () { 94 | var best = getLocalStorage('bestScore'); 95 | data.best = best || 0; 96 | }, 97 | getHistory: function () { 98 | var gameState = getLocalStorage('gameState'); 99 | if (gameState && gameState.socre && gameState.cell) { 100 | return gameState; 101 | } 102 | }, 103 | restoreHistory: function (history) { 104 | data.cell = history.cell; 105 | data.score = history.socre; 106 | cell = data.cell; 107 | this.view.restoreTile(); 108 | }, 109 | initCell: function () { 110 | for (var i = 0; i < 16; i++) { 111 | cell[i] = { 112 | val: 0, 113 | index: i, 114 | }; 115 | } 116 | }, 117 | addScore: function (score) { 118 | data.score += score; 119 | if (data.best < data.score) { 120 | data.best = data.score; 121 | this.view.updateBest(); 122 | } 123 | this.view.updateScore(score); 124 | }, 125 | chunkX: function () { 126 | var new_cell = []; 127 | for (var i = 0; i < cell.length; i += 4) { 128 | new_cell.push(cell.slice(i, i + 4)); 129 | } 130 | return new_cell; 131 | }, 132 | chunkY: function () { 133 | var arr = this.chunkX(); 134 | var new_cell = [ 135 | [], 136 | [], 137 | [], 138 | [] 139 | ]; 140 | for (var i = 0; i < arr.length; i++) { 141 | for (var j = 0; j < arr[i].length; j++) { 142 | new_cell[j][i] = arr[i][j]; 143 | } 144 | } 145 | return new_cell; 146 | }, 147 | arrayInnerReverse: function (arr) { 148 | arr.forEach(function (el, index) { 149 | arr[index] = el.reverse(); 150 | }); 151 | return arr; 152 | }, 153 | updatePos: function (old_index, index) { 154 | cell[index].val = cell[old_index].val; 155 | cell[old_index].val = 0; 156 | move = true; 157 | return old_index; 158 | }, 159 | updateVal: function (index, val) { 160 | var _this = this; 161 | cell[index].val = val; 162 | setTimeout(function () { 163 | _this.view.updateVal(index); 164 | }, 0); 165 | }, 166 | updateItem: function (old_index, index) { 167 | if (cell[old_index] === cell[index]) return; 168 | var old_index = this.updatePos(old_index, index); 169 | this.view.move(old_index, index); 170 | }, 171 | removeItem: function (index) { 172 | cell[index].val = 0; 173 | this.view.remove(index); 174 | }, 175 | getSum: function (obj, i, j) { 176 | return obj[i].val + obj[j].val; 177 | }, 178 | move: function (dir) { 179 | if (over) return; 180 | var _this = this; 181 | var _score = 0; 182 | var _move = false; 183 | var new_cell = []; 184 | if (dir === 0 || dir === 2) { 185 | new_cell = this.chunkX(); 186 | } else if (dir === 1 || dir === 3) { 187 | new_cell = this.chunkY(); 188 | } 189 | if (dir === 2 || dir === 3) { 190 | new_cell = this.arrayInnerReverse(new_cell); 191 | } 192 | new_cell.forEach(function (arr, index) { 193 | var moveInfo = _this.moving(arr, indexs[dir][index]); 194 | _score += moveInfo.score; 195 | }); 196 | this.addScore(_score); 197 | if (move) { 198 | this.randomAddItem(); 199 | _move = true; 200 | move = false; 201 | } 202 | this.save(); 203 | this.checkWinning(); 204 | if (this.isFull()) { 205 | this.checkfailure(); 206 | } 207 | return { 208 | move: _move, 209 | }; 210 | }, 211 | mergeMove: function (_cell, index, num1, num2, num3) { 212 | var sum = this.getSum(_cell, num1, num2); 213 | this.removeItem(_cell[num1].index); 214 | this.updateItem(_cell[num2].index, index[num3]); 215 | this.updateVal(index[num3], sum); 216 | }, 217 | normalMove: function (_cell, index) { 218 | var _this = this; 219 | _cell.forEach(function (el, i) { 220 | _this.updateItem(_cell[i].index, index[i]); 221 | }); 222 | }, 223 | moving: function (arr, index) { 224 | var _this = this; 225 | var _score = 0; 226 | var _cell = arr.filter(function (el) { 227 | return el.val !== 0; 228 | }); 229 | if (_cell.length === 0) { 230 | return { 231 | score: 0, 232 | } 233 | }; 234 | var calls = [ 235 | function () { 236 | _this.normalMove(_cell, index); 237 | }, 238 | function () { 239 | if (_cell[0].val === _cell[1].val) { 240 | _this.mergeMove(_cell, index, 0, 1, 0); 241 | _score += config.bonus_point; 242 | } else { 243 | _this.normalMove(_cell, index); 244 | } 245 | }, 246 | function () { 247 | if (_cell[0].val === _cell[1].val) { 248 | _this.mergeMove(_cell, index, 0, 1, 0); 249 | _this.updateItem(_cell[2].index, index[1]); 250 | _score += config.bonus_point; 251 | } else if (_cell[1].val === _cell[2].val) { 252 | _this.updateItem(_cell[0].index, index[0]); 253 | _this.mergeMove(_cell, index, 1, 2, 1); 254 | _score += config.bonus_point; 255 | } else { 256 | _this.normalMove(_cell, index); 257 | } 258 | }, 259 | function () { 260 | if (_cell[0].val === _cell[1].val) { 261 | _this.mergeMove(_cell, index, 0, 1, 0); 262 | _score += config.bonus_point; 263 | if (_cell[2].val === _cell[3].val) { 264 | _this.mergeMove(_cell, index, 2, 3, 1); 265 | _score += config.bonus_point; 266 | } else { 267 | _this.updateItem(_cell[2].index, index[1]); 268 | _this.updateItem(_cell[3].index, index[2]); 269 | } 270 | } else if (_cell[1].val === _cell[2].val) { 271 | _this.mergeMove(_cell, index, 1, 2, 1); 272 | _this.updateItem(_cell[3].index, index[2]); 273 | _score += config.bonus_point; 274 | } else if (_cell[2].val === _cell[3].val) { 275 | _this.mergeMove(_cell, index, 2, 3, 2); 276 | _score += config.bonus_point; 277 | } 278 | } 279 | ]; 280 | calls[_cell.length - 1](); 281 | return { 282 | score: _score, 283 | }; 284 | }, 285 | isFull: function () { 286 | var full = cell.filter(function (el) { 287 | return el.val === 0; 288 | }); 289 | return full.length === 0; 290 | }, 291 | randomAddItem: function () { 292 | if (this.isFull()) return; 293 | while (true) { 294 | var index = random(0, data.cell.length - 1); 295 | var exist = data.cell[index].val !== 0; 296 | if (!exist) { 297 | this.addItem(index, 2); 298 | break; 299 | } 300 | } 301 | }, 302 | addItem: function (index, val) { 303 | data.cell[index] = { 304 | val: val, 305 | index: index 306 | }; 307 | this.view.appear(index); 308 | } 309 | }; 310 | 311 | return Game; 312 | 313 | })(); --------------------------------------------------------------------------------