├── 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 | 
8 |
9 | 如果对您有帮助欢迎Star!😋
--------------------------------------------------------------------------------
/.history/README_20190809111214.md:
--------------------------------------------------------------------------------
1 | # 2048 游戏
2 |
3 | 在线玩:https://4ark.me/2048
4 |
5 | 游戏截图:
6 |
7 | 
8 |
9 | 如果对您有帮助欢迎 Star!😋
10 |
--------------------------------------------------------------------------------
/.history/README_20190809111215.md:
--------------------------------------------------------------------------------
1 | # 2048 游戏
2 |
3 | 在线玩:https://4ark.me/2048
4 |
5 | 游戏截图:
6 |
7 | 
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 |
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 | })();
--------------------------------------------------------------------------------