├── .gitignore
├── .jshintrc
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── Rakefile
├── favicon.ico
├── index.html
├── js
├── animframe_polyfill.js
├── application.js
├── bind_polyfill.js
├── classlist_polyfill.js
├── game_manager.js
├── grid.js
├── html_actuator.js
├── keyboard_input_manager.js
├── local_storage_manager.js
└── tile.js
├── meta
├── apple-touch-icon.png
├── apple-touch-startup-image-640x1096.png
├── apple-touch-startup-image-640x920.png
└── og.png
└── style
├── fonts
├── ClearSans-Bold-webfont.eot
├── ClearSans-Bold-webfont.svg
├── ClearSans-Bold-webfont.woff
├── ClearSans-Light-webfont.eot
├── ClearSans-Light-webfont.svg
├── ClearSans-Light-webfont.woff
├── ClearSans-Regular-webfont.eot
├── ClearSans-Regular-webfont.svg
├── ClearSans-Regular-webfont.woff
└── clear-sans.css
└── main.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .sass-cache/
2 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "indent": 2,
4 | "maxlen": 80,
5 | "freeze": true,
6 | "camelcase": true,
7 | "unused": true,
8 | "eqnull": true,
9 | "proto": true,
10 | "supernew": true,
11 | "noyield": true,
12 | "evil": true,
13 | "node": true,
14 | "boss": true,
15 | "expr": true,
16 | "loopfunc": true,
17 | "white": true,
18 | "maxdepth": 4
19 | }
20 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | Changes and improvements are more than welcome! Feel free to fork and open a pull request.
3 |
4 | Please follow the house rules to have a bigger chance of your contribution being merged.
5 |
6 | ## House rules
7 |
8 | ### How to make changes
9 | - To make changes, create a new branch based on `master` (do not create one from `gh-pages` unless strictly necessary) and make them there, then create a Pull Request to master.
10 | `gh-pages` is different from master in that it contains sharing features, analytics and other things that have no direct bearing with the game. `master` is the "pure" version of the game.
11 | - If you want to modify the CSS, please edit the SCSS files present in `style/`: `main.scss` and others. Don't edit the `main.css`, because it's supposed to be generated.
12 | In order to compile your SCSS modifications, you need to use the `sass` gem (install it by running `gem install sass` once Ruby is installed).
13 | To run SASS, simply use the following command:
14 | `sass --unix-newlines --watch style/main.scss`
15 | SASS will automatically recompile your css when changed.
16 | - `Rakefile` contains some tasks that help during development. Feel free to add useful tasks if needed.
17 | - Please use 2-space indentation when editing the JavaScript. A `.jshintrc` file is present, which will help your code to follow the guidelines if you install and run `jshint`.
18 | - Please test your modification thoroughly before submitting your Pull Request.
19 |
20 | ### Changes that might not be accepted
21 | We have to be conservative with the core game. This means that some modifications won't be merged, or will have to be evaluated carefully before being merged:
22 |
23 | - Undo/redo features
24 | - Save/reload features
25 | - Changes to how the tiles look or their contents
26 | - Changes to the layout
27 | - Changes to the grid size
28 |
29 | ### Changes that are welcome
30 | - Bug fixes
31 | - Compatibility improvements
32 | - "Under the hood" enhancements
33 | - Small changes that don't have an impact on the core gameplay
34 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Gabriele Cirulli
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 併校模擬器 | Merge Simulator
2 |
3 | https://splitline.github.io/merge-simulator/
4 |
5 | ## How To Play
6 | 併校刷國際排名!
7 |
8 | 不用當校長也能體驗到併校的快感。併得越多排名便會越高,試著刷到世界第一吧🐱
9 |
10 | > Fork From https://github.com/gabrielecirulli/2048/
11 |
12 | ---
13 |
14 | 我的 commit message 好醜 = =
15 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "date"
2 |
3 | namespace :appcache do
4 | desc "update the date in the appcache file (in the gh-pages branch)"
5 | task :update do
6 | appcache = File.read("cache.appcache")
7 | updated = "# Updated: #{DateTime.now}"
8 |
9 | File.write("cache.appcache", appcache.sub(/^# Updated:.*$/, updated))
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
19 | 併校模擬器 | Merge Simulator
20 |
21 |
22 |
23 |
24 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
58 |
59 |
60 |
67 |
68 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | How to play: 併校刷 QS 排名!
102 |
103 |
104 |
105 | Star
107 |
108 |
109 |
110 | Fork from gabrielecirulli/2048
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/js/animframe_polyfill.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var lastTime = 0;
3 | var vendors = ['webkit', 'moz'];
4 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
5 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
6 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
7 | window[vendors[x] + 'CancelRequestAnimationFrame'];
8 | }
9 |
10 | if (!window.requestAnimationFrame) {
11 | window.requestAnimationFrame = function (callback) {
12 | var currTime = new Date().getTime();
13 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
14 | var id = window.setTimeout(function () {
15 | callback(currTime + timeToCall);
16 | },
17 | timeToCall);
18 | lastTime = currTime + timeToCall;
19 | return id;
20 | };
21 | }
22 |
23 | if (!window.cancelAnimationFrame) {
24 | window.cancelAnimationFrame = function (id) {
25 | clearTimeout(id);
26 | };
27 | }
28 | }());
29 |
--------------------------------------------------------------------------------
/js/application.js:
--------------------------------------------------------------------------------
1 | // Wait till the browser is ready to render the game (avoids glitches)
2 | window.requestAnimationFrame(function () {
3 | new GameManager(4, KeyboardInputManager, HTMLActuator, LocalStorageManager);
4 | });
5 |
--------------------------------------------------------------------------------
/js/bind_polyfill.js:
--------------------------------------------------------------------------------
1 | Function.prototype.bind = Function.prototype.bind || function (target) {
2 | var self = this;
3 | return function (args) {
4 | if (!(args instanceof Array)) {
5 | args = [args];
6 | }
7 | self.apply(target, args);
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/js/classlist_polyfill.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | if (typeof window.Element === "undefined" ||
3 | "classList" in document.documentElement) {
4 | return;
5 | }
6 |
7 | var prototype = Array.prototype,
8 | push = prototype.push,
9 | splice = prototype.splice,
10 | join = prototype.join;
11 |
12 | function DOMTokenList(el) {
13 | this.el = el;
14 | // The className needs to be trimmed and split on whitespace
15 | // to retrieve a list of classes.
16 | var classes = el.className.replace(/^\s+|\s+$/g, '').split(/\s+/);
17 | for (var i = 0; i < classes.length; i++) {
18 | push.call(this, classes[i]);
19 | }
20 | }
21 |
22 | DOMTokenList.prototype = {
23 | add: function (token) {
24 | if (this.contains(token)) return;
25 | push.call(this, token);
26 | this.el.className = this.toString();
27 | },
28 | contains: function (token) {
29 | return this.el.className.indexOf(token) != -1;
30 | },
31 | item: function (index) {
32 | return this[index] || null;
33 | },
34 | remove: function (token) {
35 | if (!this.contains(token)) return;
36 | for (var i = 0; i < this.length; i++) {
37 | if (this[i] == token) break;
38 | }
39 | splice.call(this, i, 1);
40 | this.el.className = this.toString();
41 | },
42 | toString: function () {
43 | return join.call(this, ' ');
44 | },
45 | toggle: function (token) {
46 | if (!this.contains(token)) {
47 | this.add(token);
48 | } else {
49 | this.remove(token);
50 | }
51 |
52 | return this.contains(token);
53 | }
54 | };
55 |
56 | window.DOMTokenList = DOMTokenList;
57 |
58 | function defineElementGetter(obj, prop, getter) {
59 | if (Object.defineProperty) {
60 | Object.defineProperty(obj, prop, {
61 | get: getter
62 | });
63 | } else {
64 | obj.__defineGetter__(prop, getter);
65 | }
66 | }
67 |
68 | defineElementGetter(HTMLElement.prototype, 'classList', function () {
69 | return new DOMTokenList(this);
70 | });
71 | })();
72 |
--------------------------------------------------------------------------------
/js/game_manager.js:
--------------------------------------------------------------------------------
1 | function GameManager(size, InputManager, Actuator, StorageManager) {
2 | this.size = size; // Size of the grid
3 | this.inputManager = new InputManager;
4 | this.storageManager = new StorageManager;
5 | this.actuator = new Actuator;
6 |
7 | this.startTiles = 2;
8 |
9 | this.inputManager.on("move", this.move.bind(this));
10 | this.inputManager.on("restart", this.restart.bind(this));
11 | this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
12 |
13 | this.setup();
14 | }
15 |
16 | // Restart the game
17 | GameManager.prototype.restart = function () {
18 | this.storageManager.clearGameState();
19 | this.actuator.continueGame(); // Clear the game won/lost message
20 | this.setup();
21 | };
22 |
23 | // Keep playing after winning (allows going over 2048)
24 | GameManager.prototype.keepPlaying = function () {
25 | this.keepPlaying = true;
26 | this.actuator.continueGame(); // Clear the game won/lost message
27 | };
28 |
29 | // Return true if the game is lost, or has won and the user hasn't kept playing
30 | GameManager.prototype.isGameTerminated = function () {
31 | return this.over || (this.won && !this.keepPlaying);
32 | };
33 |
34 | // Set up the game
35 | GameManager.prototype.setup = function () {
36 | var previousState = this.storageManager.getGameState();
37 |
38 | // Reload the game from a previous game if present
39 | if (previousState) {
40 | this.grid = new Grid(previousState.grid.size,
41 | previousState.grid.cells); // Reload grid
42 | this.score = previousState.score;
43 | this.over = previousState.over;
44 | this.won = previousState.won;
45 | this.keepPlaying = previousState.keepPlaying;
46 | } else {
47 | this.grid = new Grid(this.size);
48 | this.score = 0;
49 | this.over = false;
50 | this.won = false;
51 | this.keepPlaying = false;
52 |
53 | // Add the initial tiles
54 | this.addStartTiles();
55 | }
56 |
57 | // Update the actuator
58 | this.actuate();
59 | };
60 |
61 | // Set up the initial tiles to start the game with
62 | GameManager.prototype.addStartTiles = function () {
63 | for (var i = 0; i < this.startTiles; i++) {
64 | this.addRandomTile();
65 | }
66 | };
67 |
68 | // Adds a tile in a random position
69 | GameManager.prototype.addRandomTile = function () {
70 | if (this.grid.cellsAvailable()) {
71 | var value = Math.random() < 0.9 ? 2 : 4;
72 | var tile = new Tile(this.grid.randomAvailableCell(), value);
73 |
74 | this.grid.insertTile(tile);
75 | }
76 | };
77 |
78 | // Sends the updated grid to the actuator
79 | GameManager.prototype.actuate = function () {
80 | if (this.storageManager.getBestScore() < this.score) {
81 | this.storageManager.setBestScore(this.score);
82 | }
83 |
84 | // Clear the state when the game is over (game over only, not win)
85 | if (this.over) {
86 | this.storageManager.clearGameState();
87 | } else {
88 | this.storageManager.setGameState(this.serialize());
89 | }
90 |
91 | this.actuator.actuate(this.grid, {
92 | score: this.score,
93 | over: this.over,
94 | won: this.won,
95 | bestScore: this.storageManager.getBestScore(),
96 | terminated: this.isGameTerminated()
97 | });
98 |
99 | };
100 |
101 | // Represent the current game as an object
102 | GameManager.prototype.serialize = function () {
103 | return {
104 | grid: this.grid.serialize(),
105 | score: this.score,
106 | over: this.over,
107 | won: this.won,
108 | keepPlaying: this.keepPlaying
109 | };
110 | };
111 |
112 | // Save all tile positions and remove merger info
113 | GameManager.prototype.prepareTiles = function () {
114 | this.grid.eachCell(function (x, y, tile) {
115 | if (tile) {
116 | tile.mergedFrom = null;
117 | tile.savePosition();
118 | }
119 | });
120 | };
121 |
122 | // Move a tile and its representation
123 | GameManager.prototype.moveTile = function (tile, cell) {
124 | this.grid.cells[tile.x][tile.y] = null;
125 | this.grid.cells[cell.x][cell.y] = tile;
126 | tile.updatePosition(cell);
127 | };
128 |
129 | // Move tiles on the grid in the specified direction
130 | GameManager.prototype.move = function (direction) {
131 | // 0: up, 1: right, 2: down, 3: left
132 | var self = this;
133 |
134 | if (this.isGameTerminated()) return; // Don't do anything if the game's over
135 |
136 | var cell, tile;
137 |
138 | var vector = this.getVector(direction);
139 | var traversals = this.buildTraversals(vector);
140 | var moved = false;
141 |
142 | // Save the current tile positions and remove merger information
143 | this.prepareTiles();
144 |
145 | // Traverse the grid in the right direction and move tiles
146 | traversals.x.forEach(function (x) {
147 | traversals.y.forEach(function (y) {
148 | cell = { x: x, y: y };
149 | tile = self.grid.cellContent(cell);
150 |
151 | if (tile) {
152 | var positions = self.findFarthestPosition(cell, vector);
153 | var next = self.grid.cellContent(positions.next);
154 |
155 | // Only one merger per row traversal?
156 | if (next && next.value === tile.value && !next.mergedFrom) {
157 | var merged = new Tile(positions.next, tile.value * 2);
158 | merged.mergedFrom = [tile, next];
159 |
160 | self.grid.insertTile(merged);
161 | self.grid.removeTile(tile);
162 |
163 | // Converge the two tiles' positions
164 | tile.updatePosition(positions.next);
165 |
166 | // Update the score
167 | self.score += merged.value;
168 |
169 | // The mighty 2048 tile
170 | if (merged.value === 2048) self.won = true;
171 | } else {
172 | self.moveTile(tile, positions.farthest);
173 | }
174 |
175 | if (!self.positionsEqual(cell, tile)) {
176 | moved = true; // The tile moved from its original cell!
177 | }
178 | }
179 | });
180 | });
181 |
182 | if (moved) {
183 | this.addRandomTile();
184 |
185 | if (!this.movesAvailable()) {
186 | this.over = true; // Game over!
187 | }
188 |
189 | this.actuate();
190 | }
191 | };
192 |
193 | // Get the vector representing the chosen direction
194 | GameManager.prototype.getVector = function (direction) {
195 | // Vectors representing tile movement
196 | var map = {
197 | 0: { x: 0, y: -1 }, // Up
198 | 1: { x: 1, y: 0 }, // Right
199 | 2: { x: 0, y: 1 }, // Down
200 | 3: { x: -1, y: 0 } // Left
201 | };
202 |
203 | return map[direction];
204 | };
205 |
206 | // Build a list of positions to traverse in the right order
207 | GameManager.prototype.buildTraversals = function (vector) {
208 | var traversals = { x: [], y: [] };
209 |
210 | for (var pos = 0; pos < this.size; pos++) {
211 | traversals.x.push(pos);
212 | traversals.y.push(pos);
213 | }
214 |
215 | // Always traverse from the farthest cell in the chosen direction
216 | if (vector.x === 1) traversals.x = traversals.x.reverse();
217 | if (vector.y === 1) traversals.y = traversals.y.reverse();
218 |
219 | return traversals;
220 | };
221 |
222 | GameManager.prototype.findFarthestPosition = function (cell, vector) {
223 | var previous;
224 |
225 | // Progress towards the vector direction until an obstacle is found
226 | do {
227 | previous = cell;
228 | cell = { x: previous.x + vector.x, y: previous.y + vector.y };
229 | } while (this.grid.withinBounds(cell) &&
230 | this.grid.cellAvailable(cell));
231 |
232 | return {
233 | farthest: previous,
234 | next: cell // Used to check if a merge is required
235 | };
236 | };
237 |
238 | GameManager.prototype.movesAvailable = function () {
239 | return this.grid.cellsAvailable() || this.tileMatchesAvailable();
240 | };
241 |
242 | // Check for available matches between tiles (more expensive check)
243 | GameManager.prototype.tileMatchesAvailable = function () {
244 | var self = this;
245 |
246 | var tile;
247 |
248 | for (var x = 0; x < this.size; x++) {
249 | for (var y = 0; y < this.size; y++) {
250 | tile = this.grid.cellContent({ x: x, y: y });
251 |
252 | if (tile) {
253 | for (var direction = 0; direction < 4; direction++) {
254 | var vector = self.getVector(direction);
255 | var cell = { x: x + vector.x, y: y + vector.y };
256 |
257 | var other = self.grid.cellContent(cell);
258 |
259 | if (other && other.value === tile.value) {
260 | return true; // These two tiles can be merged
261 | }
262 | }
263 | }
264 | }
265 | }
266 |
267 | return false;
268 | };
269 |
270 | GameManager.prototype.positionsEqual = function (first, second) {
271 | return first.x === second.x && first.y === second.y;
272 | };
273 |
--------------------------------------------------------------------------------
/js/grid.js:
--------------------------------------------------------------------------------
1 | function Grid(size, previousState) {
2 | this.size = size;
3 | this.cells = previousState ? this.fromState(previousState) : this.empty();
4 | }
5 |
6 | // Build a grid of the specified size
7 | Grid.prototype.empty = function () {
8 | var cells = [];
9 |
10 | for (var x = 0; x < this.size; x++) {
11 | var row = cells[x] = [];
12 |
13 | for (var y = 0; y < this.size; y++) {
14 | row.push(null);
15 | }
16 | }
17 |
18 | return cells;
19 | };
20 |
21 | Grid.prototype.fromState = function (state) {
22 | var cells = [];
23 |
24 | for (var x = 0; x < this.size; x++) {
25 | var row = cells[x] = [];
26 |
27 | for (var y = 0; y < this.size; y++) {
28 | var tile = state[x][y];
29 | row.push(tile ? new Tile(tile.position, tile.value) : null);
30 | }
31 | }
32 |
33 | return cells;
34 | };
35 |
36 | // Find the first available random position
37 | Grid.prototype.randomAvailableCell = function () {
38 | var cells = this.availableCells();
39 |
40 | if (cells.length) {
41 | return cells[Math.floor(Math.random() * cells.length)];
42 | }
43 | };
44 |
45 | Grid.prototype.availableCells = function () {
46 | var cells = [];
47 |
48 | this.eachCell(function (x, y, tile) {
49 | if (!tile) {
50 | cells.push({ x: x, y: y });
51 | }
52 | });
53 |
54 | return cells;
55 | };
56 |
57 | // Call callback for every cell
58 | Grid.prototype.eachCell = function (callback) {
59 | for (var x = 0; x < this.size; x++) {
60 | for (var y = 0; y < this.size; y++) {
61 | callback(x, y, this.cells[x][y]);
62 | }
63 | }
64 | };
65 |
66 | // Check if there are any cells available
67 | Grid.prototype.cellsAvailable = function () {
68 | return !!this.availableCells().length;
69 | };
70 |
71 | // Check if the specified cell is taken
72 | Grid.prototype.cellAvailable = function (cell) {
73 | return !this.cellOccupied(cell);
74 | };
75 |
76 | Grid.prototype.cellOccupied = function (cell) {
77 | return !!this.cellContent(cell);
78 | };
79 |
80 | Grid.prototype.cellContent = function (cell) {
81 | if (this.withinBounds(cell)) {
82 | return this.cells[cell.x][cell.y];
83 | } else {
84 | return null;
85 | }
86 | };
87 |
88 | // Inserts a tile at its position
89 | Grid.prototype.insertTile = function (tile) {
90 | this.cells[tile.x][tile.y] = tile;
91 | };
92 |
93 | Grid.prototype.removeTile = function (tile) {
94 | this.cells[tile.x][tile.y] = null;
95 | };
96 |
97 | Grid.prototype.withinBounds = function (position) {
98 | return position.x >= 0 && position.x < this.size &&
99 | position.y >= 0 && position.y < this.size;
100 | };
101 |
102 | Grid.prototype.serialize = function () {
103 | var cellState = [];
104 |
105 | for (var x = 0; x < this.size; x++) {
106 | var row = cellState[x] = [];
107 |
108 | for (var y = 0; y < this.size; y++) {
109 | row.push(this.cells[x][y] ? this.cells[x][y].serialize() : null);
110 | }
111 | }
112 |
113 | return {
114 | size: this.size,
115 | cells: cellState
116 | };
117 | };
118 |
--------------------------------------------------------------------------------
/js/html_actuator.js:
--------------------------------------------------------------------------------
1 | function HTMLActuator() {
2 | this.tileContainer = document.querySelector(".tile-container");
3 | this.scoreContainer = document.querySelector(".score-container");
4 | this.bestContainer = document.querySelector(".best-container");
5 | this.messageContainer = document.querySelector(".game-message");
6 |
7 | this.score = 0;
8 | }
9 |
10 | HTMLActuator.prototype.actuate = function (grid, metadata) {
11 | var self = this;
12 |
13 | window.requestAnimationFrame(function () {
14 | self.clearContainer(self.tileContainer);
15 |
16 | grid.cells.forEach(function (column) {
17 | column.forEach(function (cell) {
18 | if (cell) {
19 | self.addTile(cell);
20 | }
21 | });
22 | });
23 |
24 | self.updateScore(metadata.score);
25 | self.updateBestScore(metadata.bestScore);
26 |
27 | if (metadata.terminated) {
28 | if (metadata.over) {
29 | self.message(false); // You lose
30 | } else if (metadata.won) {
31 | self.message(true); // You win!
32 | }
33 | }
34 |
35 | });
36 | };
37 |
38 | // Continues the game (both restart and keep playing)
39 | HTMLActuator.prototype.continueGame = function () {
40 | this.clearMessage();
41 | };
42 |
43 | HTMLActuator.prototype.clearContainer = function (container) {
44 | while (container.firstChild) {
45 | container.removeChild(container.firstChild);
46 | }
47 | };
48 |
49 | HTMLActuator.prototype.addTile = function (tile) {
50 | var self = this;
51 |
52 | var wrapper = document.createElement("div");
53 | var inner = document.createElement("div");
54 | var position = tile.previousPosition || {
55 | x: tile.x,
56 | y: tile.y
57 | };
58 | var positionClass = this.positionClass(position);
59 |
60 | // We can't use classlist because it somehow glitches when replacing classes
61 | var classes = ["tile", "tile-" + tile.value, positionClass];
62 |
63 | if (tile.value > 2048) classes.push("tile-super");
64 |
65 | this.applyClasses(wrapper, classes);
66 |
67 | const mapping = {
68 | 2: "南台科大",
69 | 4: "中台科大",
70 | 8: "台北城市科大",
71 | 16: "台灣大學",
72 | 32: "台灣首府大學",
73 | 64: "亞洲大學",
74 | 128: "屏科",
75 | 256: "虎科",
76 | 512: "雲科",
77 | 1024: "台科",
78 | 2048: "開山科大"
79 | }
80 |
81 | inner.classList.add("tile-inner");
82 | if (tile.value <= 2048)
83 | inner.textContent = mapping[tile.value];
84 | else
85 | inner.textContent = "開山科大 lv." + (Math.log2(tile.value) - 10);
86 |
87 | if (tile.previousPosition) {
88 | // Make sure that the tile gets rendered in the previous position first
89 | window.requestAnimationFrame(function () {
90 | classes[2] = self.positionClass({
91 | x: tile.x,
92 | y: tile.y
93 | });
94 | self.applyClasses(wrapper, classes); // Update the position
95 | });
96 | } else if (tile.mergedFrom) {
97 | classes.push("tile-merged");
98 | this.applyClasses(wrapper, classes);
99 |
100 | // Render the tiles that merged
101 | tile.mergedFrom.forEach(function (merged) {
102 | self.addTile(merged);
103 | });
104 | } else {
105 | classes.push("tile-new");
106 | this.applyClasses(wrapper, classes);
107 | }
108 |
109 | // Add the inner part of the tile to the wrapper
110 | wrapper.appendChild(inner);
111 |
112 | // Put the tile on the board
113 | this.tileContainer.appendChild(wrapper);
114 | };
115 |
116 | HTMLActuator.prototype.applyClasses = function (element, classes) {
117 | element.setAttribute("class", classes.join(" "));
118 | };
119 |
120 | HTMLActuator.prototype.normalizePosition = function (position) {
121 | return {
122 | x: position.x + 1,
123 | y: position.y + 1
124 | };
125 | };
126 |
127 | HTMLActuator.prototype.positionClass = function (position) {
128 | position = this.normalizePosition(position);
129 | return "tile-position-" + position.x + "-" + position.y;
130 | };
131 |
132 | HTMLActuator.prototype.score2rank = function (score) {
133 | return Math.max(Math.floor(
134 | 1000 - (Math.min(score, 5000) * 0.1 + Math.max(score - 5000, 0) * 0.03)
135 | ), 1);
136 | }
137 | HTMLActuator.prototype.updateScore = function (score) {
138 | this.clearContainer(this.scoreContainer);
139 |
140 | var difference = this.score2rank(this.score) - this.score2rank(score);
141 | this.score = score;
142 |
143 | this.scoreContainer.textContent = this.score2rank(this.score) >= 1000 ? "1000+ 名" : this.score2rank(this.score) + "名";
144 |
145 | if (difference > 0) {
146 | var addition = document.createElement("div");
147 | addition.classList.add("score-addition");
148 | addition.textContent = "⬆" + difference;
149 |
150 | this.scoreContainer.appendChild(addition);
151 | }
152 | };
153 |
154 | HTMLActuator.prototype.updateBestScore = function (bestScore) {
155 | this.bestContainer.textContent = this.score2rank(this.score) >= 1000 ? "1000+ 名" : this.score2rank(this.score) + "名";
156 | };
157 |
158 | HTMLActuator.prototype.message = function (won) {
159 | var type = won ? "game-won" : "game-over";
160 | var message = won ? "乂國立開山科大乂" : "垃圾學店下去888";
161 |
162 | this.messageContainer.classList.add(type);
163 | this.messageContainer.getElementsByTagName("p")[0].textContent = message;
164 | };
165 |
166 | HTMLActuator.prototype.clearMessage = function () {
167 | // IE only takes one value to remove at a time.
168 | this.messageContainer.classList.remove("game-won");
169 | this.messageContainer.classList.remove("game-over");
170 | };
--------------------------------------------------------------------------------
/js/keyboard_input_manager.js:
--------------------------------------------------------------------------------
1 | function KeyboardInputManager() {
2 | this.events = {};
3 |
4 | if (window.navigator.msPointerEnabled) {
5 | //Internet Explorer 10 style
6 | this.eventTouchstart = "MSPointerDown";
7 | this.eventTouchmove = "MSPointerMove";
8 | this.eventTouchend = "MSPointerUp";
9 | } else {
10 | this.eventTouchstart = "touchstart";
11 | this.eventTouchmove = "touchmove";
12 | this.eventTouchend = "touchend";
13 | }
14 |
15 | this.listen();
16 | }
17 |
18 | KeyboardInputManager.prototype.on = function (event, callback) {
19 | if (!this.events[event]) {
20 | this.events[event] = [];
21 | }
22 | this.events[event].push(callback);
23 | };
24 |
25 | KeyboardInputManager.prototype.emit = function (event, data) {
26 | var callbacks = this.events[event];
27 | if (callbacks) {
28 | callbacks.forEach(function (callback) {
29 | callback(data);
30 | });
31 | }
32 | };
33 |
34 | KeyboardInputManager.prototype.listen = function () {
35 | var self = this;
36 |
37 | var map = {
38 | 38: 0, // Up
39 | 39: 1, // Right
40 | 40: 2, // Down
41 | 37: 3, // Left
42 | 75: 0, // Vim up
43 | 76: 1, // Vim right
44 | 74: 2, // Vim down
45 | 72: 3, // Vim left
46 | 87: 0, // W
47 | 68: 1, // D
48 | 83: 2, // S
49 | 65: 3 // A
50 | };
51 |
52 | // Respond to direction keys
53 | document.addEventListener("keydown", function (event) {
54 | var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
55 | event.shiftKey;
56 | var mapped = map[event.which];
57 |
58 | if (!modifiers) {
59 | if (mapped !== undefined) {
60 | event.preventDefault();
61 | self.emit("move", mapped);
62 | }
63 | }
64 |
65 | // R key restarts the game
66 | if (!modifiers && event.which === 82) {
67 | self.restart.call(self, event);
68 | }
69 | });
70 |
71 | // Respond to button presses
72 | this.bindButtonPress(".retry-button", this.restart);
73 | this.bindButtonPress(".restart-button", this.restart);
74 | this.bindButtonPress(".keep-playing-button", this.keepPlaying);
75 |
76 | // Respond to swipe events
77 | var touchStartClientX, touchStartClientY;
78 | var gameContainer = document.getElementsByClassName("game-container")[0];
79 |
80 | gameContainer.addEventListener(this.eventTouchstart, function (event) {
81 | if ((!window.navigator.msPointerEnabled && event.touches.length > 1) ||
82 | event.targetTouches.length > 1) {
83 | return; // Ignore if touching with more than 1 finger
84 | }
85 |
86 | if (window.navigator.msPointerEnabled) {
87 | touchStartClientX = event.pageX;
88 | touchStartClientY = event.pageY;
89 | } else {
90 | touchStartClientX = event.touches[0].clientX;
91 | touchStartClientY = event.touches[0].clientY;
92 | }
93 |
94 | event.preventDefault();
95 | });
96 |
97 | gameContainer.addEventListener(this.eventTouchmove, function (event) {
98 | event.preventDefault();
99 | });
100 |
101 | gameContainer.addEventListener(this.eventTouchend, function (event) {
102 | if ((!window.navigator.msPointerEnabled && event.touches.length > 0) ||
103 | event.targetTouches.length > 0) {
104 | return; // Ignore if still touching with one or more fingers
105 | }
106 |
107 | var touchEndClientX, touchEndClientY;
108 |
109 | if (window.navigator.msPointerEnabled) {
110 | touchEndClientX = event.pageX;
111 | touchEndClientY = event.pageY;
112 | } else {
113 | touchEndClientX = event.changedTouches[0].clientX;
114 | touchEndClientY = event.changedTouches[0].clientY;
115 | }
116 |
117 | var dx = touchEndClientX - touchStartClientX;
118 | var absDx = Math.abs(dx);
119 |
120 | var dy = touchEndClientY - touchStartClientY;
121 | var absDy = Math.abs(dy);
122 |
123 | if (Math.max(absDx, absDy) > 10) {
124 | // (right : left) : (down : up)
125 | self.emit("move", absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
126 | }
127 | });
128 | };
129 |
130 | KeyboardInputManager.prototype.restart = function (event) {
131 | event.preventDefault();
132 | this.emit("restart");
133 | };
134 |
135 | KeyboardInputManager.prototype.keepPlaying = function (event) {
136 | event.preventDefault();
137 | this.emit("keepPlaying");
138 | };
139 |
140 | KeyboardInputManager.prototype.bindButtonPress = function (selector, fn) {
141 | var button = document.querySelector(selector);
142 | button.addEventListener("click", fn.bind(this));
143 | button.addEventListener(this.eventTouchend, fn.bind(this));
144 | };
145 |
--------------------------------------------------------------------------------
/js/local_storage_manager.js:
--------------------------------------------------------------------------------
1 | window.fakeStorage = {
2 | _data: {},
3 |
4 | setItem: function (id, val) {
5 | return this._data[id] = String(val);
6 | },
7 |
8 | getItem: function (id) {
9 | return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
10 | },
11 |
12 | removeItem: function (id) {
13 | return delete this._data[id];
14 | },
15 |
16 | clear: function () {
17 | return this._data = {};
18 | }
19 | };
20 |
21 | function LocalStorageManager() {
22 | this.bestScoreKey = "bestScore";
23 | this.gameStateKey = "gameState";
24 |
25 | var supported = this.localStorageSupported();
26 | this.storage = supported ? window.localStorage : window.fakeStorage;
27 | }
28 |
29 | LocalStorageManager.prototype.localStorageSupported = function () {
30 | var testKey = "test";
31 |
32 | try {
33 | var storage = window.localStorage;
34 | storage.setItem(testKey, "1");
35 | storage.removeItem(testKey);
36 | return true;
37 | } catch (error) {
38 | return false;
39 | }
40 | };
41 |
42 | // Best score getters/setters
43 | LocalStorageManager.prototype.getBestScore = function () {
44 | return this.storage.getItem(this.bestScoreKey) || 0;
45 | };
46 |
47 | LocalStorageManager.prototype.setBestScore = function (score) {
48 | this.storage.setItem(this.bestScoreKey, score);
49 | };
50 |
51 | // Game state getters/setters and clearing
52 | LocalStorageManager.prototype.getGameState = function () {
53 | var stateJSON = this.storage.getItem(this.gameStateKey);
54 | return stateJSON ? JSON.parse(stateJSON) : null;
55 | };
56 |
57 | LocalStorageManager.prototype.setGameState = function (gameState) {
58 | this.storage.setItem(this.gameStateKey, JSON.stringify(gameState));
59 | };
60 |
61 | LocalStorageManager.prototype.clearGameState = function () {
62 | this.storage.removeItem(this.gameStateKey);
63 | };
64 |
--------------------------------------------------------------------------------
/js/tile.js:
--------------------------------------------------------------------------------
1 | function Tile(position, value) {
2 | this.x = position.x;
3 | this.y = position.y;
4 | this.value = value || 2;
5 |
6 | this.previousPosition = null;
7 | this.mergedFrom = null; // Tracks tiles that merged together
8 | }
9 |
10 | Tile.prototype.savePosition = function () {
11 | this.previousPosition = { x: this.x, y: this.y };
12 | };
13 |
14 | Tile.prototype.updatePosition = function (position) {
15 | this.x = position.x;
16 | this.y = position.y;
17 | };
18 |
19 | Tile.prototype.serialize = function () {
20 | return {
21 | position: {
22 | x: this.x,
23 | y: this.y
24 | },
25 | value: this.value
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/meta/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/meta/apple-touch-icon.png
--------------------------------------------------------------------------------
/meta/apple-touch-startup-image-640x1096.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/meta/apple-touch-startup-image-640x1096.png
--------------------------------------------------------------------------------
/meta/apple-touch-startup-image-640x920.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/meta/apple-touch-startup-image-640x920.png
--------------------------------------------------------------------------------
/meta/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/meta/og.png
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Bold-webfont.eot
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Bold-webfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Bold-webfont.woff
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Light-webfont.eot
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Light-webfont.woff
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Regular-webfont.eot
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Regular-webfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
--------------------------------------------------------------------------------
/style/fonts/ClearSans-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/splitline/merge-simulator/HEAD/style/fonts/ClearSans-Regular-webfont.woff
--------------------------------------------------------------------------------
/style/fonts/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 |
--------------------------------------------------------------------------------
/style/main.css:
--------------------------------------------------------------------------------
1 | @import url(fonts/clear-sans.css);
2 | html, body {
3 | margin: 0;
4 | padding: 0;
5 | background: #faf8ef;
6 | color: #776e65;
7 | font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
8 | font-size: 18px; }
9 |
10 | body {
11 | margin: 80px 0; }
12 |
13 | .heading:after {
14 | content: "";
15 | display: block;
16 | clear: both; }
17 |
18 | h1.title {
19 | font-size: 48px;
20 | font-weight: bold;
21 | margin: 0;
22 | display: block;
23 | float: left; }
24 |
25 | @-webkit-keyframes move-up {
26 | 0% {
27 | top: 25px;
28 | opacity: 1; }
29 |
30 | 100% {
31 | top: -50px;
32 | opacity: 0; } }
33 | @-moz-keyframes move-up {
34 | 0% {
35 | top: 25px;
36 | opacity: 1; }
37 |
38 | 100% {
39 | top: -50px;
40 | opacity: 0; } }
41 | @keyframes move-up {
42 | 0% {
43 | top: 25px;
44 | opacity: 1; }
45 |
46 | 100% {
47 | top: -50px;
48 | opacity: 0; } }
49 | .scores-container {
50 | float: right;
51 | text-align: right; }
52 |
53 | .score-container, .best-container {
54 | position: relative;
55 | display: inline-block;
56 | background: #bbada0;
57 | padding: 15px 25px;
58 | font-size: 25px;
59 | height: 25px;
60 | line-height: 47px;
61 | font-weight: bold;
62 | border-radius: 3px;
63 | color: white;
64 | margin-top: 8px;
65 | text-align: center; }
66 | .score-container:after, .best-container:after {
67 | position: absolute;
68 | width: 100%;
69 | top: 10px;
70 | left: 0;
71 | text-transform: uppercase;
72 | font-size: 13px;
73 | line-height: 13px;
74 | text-align: center;
75 | color: #eee4da; }
76 | .score-container .score-addition, .best-container .score-addition {
77 | position: absolute;
78 | right: 30px;
79 | color: red;
80 | font-size: 25px;
81 | line-height: 25px;
82 | font-weight: bold;
83 | color: rgba(119, 110, 101, 0.9);
84 | z-index: 100;
85 | -webkit-animation: move-up 600ms ease-in;
86 | -moz-animation: move-up 600ms ease-in;
87 | animation: move-up 600ms ease-in;
88 | -webkit-animation-fill-mode: both;
89 | -moz-animation-fill-mode: both;
90 | animation-fill-mode: both; }
91 |
92 | .score-container:after {
93 | content: "QS 國際排名"; }
94 |
95 | .best-container:after {
96 | content: "最佳排名"; }
97 |
98 | p {
99 | margin-top: 0;
100 | margin-bottom: 10px;
101 | line-height: 1.65; }
102 |
103 | a {
104 | color: #776e65;
105 | font-weight: bold;
106 | text-decoration: underline;
107 | cursor: pointer; }
108 |
109 | strong.important {
110 | text-transform: uppercase; }
111 |
112 | hr {
113 | border: none;
114 | border-bottom: 1px solid #d8d4d0;
115 | margin-top: 20px;
116 | margin-bottom: 30px; }
117 |
118 | .container {
119 | width: 500px;
120 | margin: 0 auto; }
121 |
122 | @-webkit-keyframes fade-in {
123 | 0% {
124 | opacity: 0; }
125 |
126 | 100% {
127 | opacity: 1; } }
128 | @-moz-keyframes fade-in {
129 | 0% {
130 | opacity: 0; }
131 |
132 | 100% {
133 | opacity: 1; } }
134 | @keyframes fade-in {
135 | 0% {
136 | opacity: 0; }
137 |
138 | 100% {
139 | opacity: 1; } }
140 | .game-container {
141 | margin-top: 40px;
142 | position: relative;
143 | padding: 15px;
144 | cursor: default;
145 | -webkit-touch-callout: none;
146 | -ms-touch-callout: none;
147 | -webkit-user-select: none;
148 | -moz-user-select: none;
149 | -ms-user-select: none;
150 | -ms-touch-action: none;
151 | touch-action: none;
152 | background: #bbada0;
153 | border-radius: 6px;
154 | width: 500px;
155 | height: 500px;
156 | -webkit-box-sizing: border-box;
157 | -moz-box-sizing: border-box;
158 | box-sizing: border-box; }
159 | .game-container .game-message {
160 | display: none;
161 | position: absolute;
162 | top: 0;
163 | right: 0;
164 | bottom: 0;
165 | left: 0;
166 | background: rgba(238, 228, 218, 0.5);
167 | z-index: 100;
168 | text-align: center;
169 | -webkit-animation: fade-in 800ms ease 1200ms;
170 | -moz-animation: fade-in 800ms ease 1200ms;
171 | animation: fade-in 800ms ease 1200ms;
172 | -webkit-animation-fill-mode: both;
173 | -moz-animation-fill-mode: both;
174 | animation-fill-mode: both; }
175 | .game-container .game-message p {
176 | font-size: 60px;
177 | font-weight: bold;
178 | height: 60px;
179 | line-height: 60px;
180 | margin-top: 222px; }
181 | .game-container .game-message .lower {
182 | display: block;
183 | margin-top: 59px; }
184 | .game-container .game-message a {
185 | display: inline-block;
186 | background: #8f7a66;
187 | border-radius: 3px;
188 | padding: 0 20px;
189 | text-decoration: none;
190 | color: #f9f6f2;
191 | height: 40px;
192 | line-height: 42px;
193 | margin-left: 9px; }
194 | .game-container .game-message a.keep-playing-button {
195 | display: none; }
196 | .game-container .game-message.game-won {
197 | background: rgba(237, 194, 46, 0.5);
198 | color: #f9f6f2; }
199 | .game-container .game-message.game-won a.keep-playing-button {
200 | display: inline-block; }
201 | .game-container .game-message.game-won, .game-container .game-message.game-over {
202 | display: block; }
203 |
204 | .grid-container {
205 | position: absolute;
206 | z-index: 1; }
207 |
208 | .grid-row {
209 | margin-bottom: 15px; }
210 | .grid-row:last-child {
211 | margin-bottom: 0; }
212 | .grid-row:after {
213 | content: "";
214 | display: block;
215 | clear: both; }
216 |
217 | .grid-cell {
218 | width: 106.25px;
219 | height: 106.25px;
220 | margin-right: 15px;
221 | float: left;
222 | border-radius: 3px;
223 | background: rgba(238, 228, 218, 0.35); }
224 | .grid-cell:last-child {
225 | margin-right: 0; }
226 |
227 | .tile-container {
228 | position: absolute;
229 | z-index: 2; }
230 |
231 | .tile, .tile .tile-inner {
232 | width: 107px;
233 | height: 107px;
234 | line-height: 107px; }
235 | .tile.tile-position-1-1 {
236 | -webkit-transform: translate(0px, 0px);
237 | -moz-transform: translate(0px, 0px);
238 | -ms-transform: translate(0px, 0px);
239 | transform: translate(0px, 0px); }
240 | .tile.tile-position-1-2 {
241 | -webkit-transform: translate(0px, 121px);
242 | -moz-transform: translate(0px, 121px);
243 | -ms-transform: translate(0px, 121px);
244 | transform: translate(0px, 121px); }
245 | .tile.tile-position-1-3 {
246 | -webkit-transform: translate(0px, 242px);
247 | -moz-transform: translate(0px, 242px);
248 | -ms-transform: translate(0px, 242px);
249 | transform: translate(0px, 242px); }
250 | .tile.tile-position-1-4 {
251 | -webkit-transform: translate(0px, 363px);
252 | -moz-transform: translate(0px, 363px);
253 | -ms-transform: translate(0px, 363px);
254 | transform: translate(0px, 363px); }
255 | .tile.tile-position-2-1 {
256 | -webkit-transform: translate(121px, 0px);
257 | -moz-transform: translate(121px, 0px);
258 | -ms-transform: translate(121px, 0px);
259 | transform: translate(121px, 0px); }
260 | .tile.tile-position-2-2 {
261 | -webkit-transform: translate(121px, 121px);
262 | -moz-transform: translate(121px, 121px);
263 | -ms-transform: translate(121px, 121px);
264 | transform: translate(121px, 121px); }
265 | .tile.tile-position-2-3 {
266 | -webkit-transform: translate(121px, 242px);
267 | -moz-transform: translate(121px, 242px);
268 | -ms-transform: translate(121px, 242px);
269 | transform: translate(121px, 242px); }
270 | .tile.tile-position-2-4 {
271 | -webkit-transform: translate(121px, 363px);
272 | -moz-transform: translate(121px, 363px);
273 | -ms-transform: translate(121px, 363px);
274 | transform: translate(121px, 363px); }
275 | .tile.tile-position-3-1 {
276 | -webkit-transform: translate(242px, 0px);
277 | -moz-transform: translate(242px, 0px);
278 | -ms-transform: translate(242px, 0px);
279 | transform: translate(242px, 0px); }
280 | .tile.tile-position-3-2 {
281 | -webkit-transform: translate(242px, 121px);
282 | -moz-transform: translate(242px, 121px);
283 | -ms-transform: translate(242px, 121px);
284 | transform: translate(242px, 121px); }
285 | .tile.tile-position-3-3 {
286 | -webkit-transform: translate(242px, 242px);
287 | -moz-transform: translate(242px, 242px);
288 | -ms-transform: translate(242px, 242px);
289 | transform: translate(242px, 242px); }
290 | .tile.tile-position-3-4 {
291 | -webkit-transform: translate(242px, 363px);
292 | -moz-transform: translate(242px, 363px);
293 | -ms-transform: translate(242px, 363px);
294 | transform: translate(242px, 363px); }
295 | .tile.tile-position-4-1 {
296 | -webkit-transform: translate(363px, 0px);
297 | -moz-transform: translate(363px, 0px);
298 | -ms-transform: translate(363px, 0px);
299 | transform: translate(363px, 0px); }
300 | .tile.tile-position-4-2 {
301 | -webkit-transform: translate(363px, 121px);
302 | -moz-transform: translate(363px, 121px);
303 | -ms-transform: translate(363px, 121px);
304 | transform: translate(363px, 121px); }
305 | .tile.tile-position-4-3 {
306 | -webkit-transform: translate(363px, 242px);
307 | -moz-transform: translate(363px, 242px);
308 | -ms-transform: translate(363px, 242px);
309 | transform: translate(363px, 242px); }
310 | .tile.tile-position-4-4 {
311 | -webkit-transform: translate(363px, 363px);
312 | -moz-transform: translate(363px, 363px);
313 | -ms-transform: translate(363px, 363px);
314 | transform: translate(363px, 363px); }
315 |
316 | .tile {
317 | position: absolute;
318 | -webkit-transition: 100ms ease-in-out;
319 | -moz-transition: 100ms ease-in-out;
320 | transition: 100ms ease-in-out;
321 | -webkit-transition-property: -webkit-transform;
322 | -moz-transition-property: -moz-transform;
323 | transition-property: transform; }
324 | .tile .tile-inner {
325 | border-radius: 3px;
326 | background: #eee4da;
327 | text-align: center;
328 | font-weight: bold;
329 | z-index: 10;
330 | font-size: 15px; }
331 | .tile.tile-2 .tile-inner {
332 | background: #eee4da;
333 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
334 | .tile.tile-4 .tile-inner {
335 | background: #ede0c8;
336 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); }
337 | .tile.tile-8 .tile-inner {
338 | color: #f9f6f2;
339 | background: #f2b179; }
340 | .tile.tile-16 .tile-inner {
341 | color: #f9f6f2;
342 | background: #f59563; }
343 | .tile.tile-32 .tile-inner {
344 | color: #f9f6f2;
345 | background: #f67c5f; }
346 | .tile.tile-64 .tile-inner {
347 | color: #f9f6f2;
348 | background: #f65e3b; }
349 | .tile.tile-128 .tile-inner {
350 | color: #f9f6f2;
351 | background: #edcf72;
352 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286);
353 | font-size: 35px; }
354 | @media screen and (max-width: 520px) {
355 | .tile.tile-128 .tile-inner {
356 | font-size: 10px; } }
357 | .tile.tile-256 .tile-inner {
358 | color: #f9f6f2;
359 | background: #edcc61;
360 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048);
361 | font-size: 35px; }
362 | @media screen and (max-width: 520px) {
363 | .tile.tile-256 .tile-inner {
364 | font-size: 10px; } }
365 | .tile.tile-512 .tile-inner {
366 | color: #f9f6f2;
367 | background: #edc850;
368 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381);
369 | font-size: 35px; }
370 | @media screen and (max-width: 520px) {
371 | .tile.tile-512 .tile-inner {
372 | font-size: 10px; } }
373 | .tile.tile-1024 .tile-inner {
374 | color: #f9f6f2;
375 | background: #edc53f;
376 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571);
377 | font-size: 35px; }
378 | @media screen and (max-width: 520px) {
379 | .tile.tile-1024 .tile-inner {
380 | font-size: 10px; } }
381 | .tile.tile-2048 .tile-inner {
382 | color: #f9f6f2;
383 | background: #edc22e;
384 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333);
385 | font-size: 40px;
386 | line-height: 52px;}
387 | @media screen and (max-width: 520px) {
388 | .tile.tile-2048 .tile-inner {
389 | font-size: 10px; } }
390 | .tile.tile-super .tile-inner {
391 | color: #f9f6f2;
392 | background: #3c3a32;
393 | font-size: 15px; }
394 | @media screen and (max-width: 520px) {
395 | .tile.tile-super .tile-inner {
396 | font-size: 10px; } }
397 |
398 | @-webkit-keyframes appear {
399 | 0% {
400 | opacity: 0;
401 | -webkit-transform: scale(0);
402 | -moz-transform: scale(0);
403 | -ms-transform: scale(0);
404 | transform: scale(0); }
405 |
406 | 100% {
407 | opacity: 1;
408 | -webkit-transform: scale(1);
409 | -moz-transform: scale(1);
410 | -ms-transform: scale(1);
411 | transform: scale(1); } }
412 | @-moz-keyframes appear {
413 | 0% {
414 | opacity: 0;
415 | -webkit-transform: scale(0);
416 | -moz-transform: scale(0);
417 | -ms-transform: scale(0);
418 | transform: scale(0); }
419 |
420 | 100% {
421 | opacity: 1;
422 | -webkit-transform: scale(1);
423 | -moz-transform: scale(1);
424 | -ms-transform: scale(1);
425 | transform: scale(1); } }
426 | @keyframes appear {
427 | 0% {
428 | opacity: 0;
429 | -webkit-transform: scale(0);
430 | -moz-transform: scale(0);
431 | -ms-transform: scale(0);
432 | transform: scale(0); }
433 |
434 | 100% {
435 | opacity: 1;
436 | -webkit-transform: scale(1);
437 | -moz-transform: scale(1);
438 | -ms-transform: scale(1);
439 | transform: scale(1); } }
440 | .tile-new .tile-inner {
441 | -webkit-animation: appear 200ms ease 100ms;
442 | -moz-animation: appear 200ms ease 100ms;
443 | animation: appear 200ms ease 100ms;
444 | -webkit-animation-fill-mode: backwards;
445 | -moz-animation-fill-mode: backwards;
446 | animation-fill-mode: backwards; }
447 |
448 | @-webkit-keyframes pop {
449 | 0% {
450 | -webkit-transform: scale(0);
451 | -moz-transform: scale(0);
452 | -ms-transform: scale(0);
453 | transform: scale(0); }
454 |
455 | 50% {
456 | -webkit-transform: scale(1.2);
457 | -moz-transform: scale(1.2);
458 | -ms-transform: scale(1.2);
459 | transform: scale(1.2); }
460 |
461 | 100% {
462 | -webkit-transform: scale(1);
463 | -moz-transform: scale(1);
464 | -ms-transform: scale(1);
465 | transform: scale(1); } }
466 | @-moz-keyframes pop {
467 | 0% {
468 | -webkit-transform: scale(0);
469 | -moz-transform: scale(0);
470 | -ms-transform: scale(0);
471 | transform: scale(0); }
472 |
473 | 50% {
474 | -webkit-transform: scale(1.2);
475 | -moz-transform: scale(1.2);
476 | -ms-transform: scale(1.2);
477 | transform: scale(1.2); }
478 |
479 | 100% {
480 | -webkit-transform: scale(1);
481 | -moz-transform: scale(1);
482 | -ms-transform: scale(1);
483 | transform: scale(1); } }
484 | @keyframes pop {
485 | 0% {
486 | -webkit-transform: scale(0);
487 | -moz-transform: scale(0);
488 | -ms-transform: scale(0);
489 | transform: scale(0); }
490 |
491 | 50% {
492 | -webkit-transform: scale(1.2);
493 | -moz-transform: scale(1.2);
494 | -ms-transform: scale(1.2);
495 | transform: scale(1.2); }
496 |
497 | 100% {
498 | -webkit-transform: scale(1);
499 | -moz-transform: scale(1);
500 | -ms-transform: scale(1);
501 | transform: scale(1); } }
502 | .tile-merged .tile-inner {
503 | z-index: 20;
504 | -webkit-animation: pop 200ms ease 100ms;
505 | -moz-animation: pop 200ms ease 100ms;
506 | animation: pop 200ms ease 100ms;
507 | -webkit-animation-fill-mode: backwards;
508 | -moz-animation-fill-mode: backwards;
509 | animation-fill-mode: backwards; }
510 |
511 | .above-game:after {
512 | content: "";
513 | display: block;
514 | clear: both; }
515 |
516 | .game-intro {
517 | float: left;
518 | line-height: 42px;
519 | margin-bottom: 0; }
520 |
521 | .restart-button {
522 | display: inline-block;
523 | background: #8f7a66;
524 | border-radius: 3px;
525 | padding: 0 20px;
526 | text-decoration: none;
527 | color: #f9f6f2;
528 | height: 40px;
529 | line-height: 42px;
530 | display: block;
531 | text-align: center;
532 | float: right; }
533 |
534 | .game-explanation {
535 | margin-top: 50px; }
536 |
537 | @media screen and (max-width: 520px) {
538 | html, body {
539 | font-size: 15px; }
540 |
541 | body {
542 | margin: 20px 0;
543 | padding: 0 20px; }
544 |
545 | h1.title {
546 | font-size: 27px;
547 | margin-top: 15px; }
548 |
549 | .container {
550 | width: 280px;
551 | margin: 0 auto; }
552 |
553 | .score-container, .best-container {
554 | margin-top: 0;
555 | padding: 15px 10px;
556 | min-width: 40px; }
557 |
558 | .heading {
559 | margin-bottom: 10px; }
560 |
561 | .game-intro {
562 | width: 55%;
563 | display: block;
564 | box-sizing: border-box;
565 | line-height: 1.65; }
566 |
567 | .restart-button {
568 | width: 42%;
569 | padding: 0;
570 | display: block;
571 | box-sizing: border-box;
572 | margin-top: 2px; }
573 |
574 | .game-container {
575 | margin-top: 17px;
576 | position: relative;
577 | padding: 10px;
578 | cursor: default;
579 | -webkit-touch-callout: none;
580 | -ms-touch-callout: none;
581 | -webkit-user-select: none;
582 | -moz-user-select: none;
583 | -ms-user-select: none;
584 | -ms-touch-action: none;
585 | touch-action: none;
586 | background: #bbada0;
587 | border-radius: 6px;
588 | width: 280px;
589 | height: 280px;
590 | -webkit-box-sizing: border-box;
591 | -moz-box-sizing: border-box;
592 | box-sizing: border-box; }
593 | .game-container .game-message {
594 | display: none;
595 | position: absolute;
596 | top: 0;
597 | right: 0;
598 | bottom: 0;
599 | left: 0;
600 | background: rgba(238, 228, 218, 0.5);
601 | z-index: 100;
602 | text-align: center;
603 | -webkit-animation: fade-in 800ms ease 1200ms;
604 | -moz-animation: fade-in 800ms ease 1200ms;
605 | animation: fade-in 800ms ease 1200ms;
606 | -webkit-animation-fill-mode: both;
607 | -moz-animation-fill-mode: both;
608 | animation-fill-mode: both; }
609 | .game-container .game-message p {
610 | font-size: 60px;
611 | font-weight: bold;
612 | height: 60px;
613 | line-height: 60px;
614 | margin-top: 222px; }
615 | .game-container .game-message .lower {
616 | display: block;
617 | margin-top: 59px; }
618 | .game-container .game-message a {
619 | display: inline-block;
620 | background: #8f7a66;
621 | border-radius: 3px;
622 | padding: 0 20px;
623 | text-decoration: none;
624 | color: #f9f6f2;
625 | height: 40px;
626 | line-height: 42px;
627 | margin-left: 9px; }
628 | .game-container .game-message a.keep-playing-button {
629 | display: none; }
630 | .game-container .game-message.game-won {
631 | background: rgba(237, 194, 46, 0.5);
632 | color: #f9f6f2; }
633 | .game-container .game-message.game-won a.keep-playing-button {
634 | display: inline-block; }
635 | .game-container .game-message.game-won, .game-container .game-message.game-over {
636 | display: block; }
637 |
638 | .grid-container {
639 | position: absolute;
640 | z-index: 1; }
641 |
642 | .grid-row {
643 | margin-bottom: 10px; }
644 | .grid-row:last-child {
645 | margin-bottom: 0; }
646 | .grid-row:after {
647 | content: "";
648 | display: block;
649 | clear: both; }
650 |
651 | .grid-cell {
652 | width: 57.5px;
653 | height: 57.5px;
654 | margin-right: 10px;
655 | float: left;
656 | border-radius: 3px;
657 | background: rgba(238, 228, 218, 0.35); }
658 | .grid-cell:last-child {
659 | margin-right: 0; }
660 |
661 | .tile-container {
662 | position: absolute;
663 | z-index: 2; }
664 |
665 | .tile, .tile .tile-inner {
666 | width: 58px;
667 | height: 58px;
668 | line-height: 58px; }
669 | .tile.tile-position-1-1 {
670 | -webkit-transform: translate(0px, 0px);
671 | -moz-transform: translate(0px, 0px);
672 | -ms-transform: translate(0px, 0px);
673 | transform: translate(0px, 0px); }
674 | .tile.tile-position-1-2 {
675 | -webkit-transform: translate(0px, 67px);
676 | -moz-transform: translate(0px, 67px);
677 | -ms-transform: translate(0px, 67px);
678 | transform: translate(0px, 67px); }
679 | .tile.tile-position-1-3 {
680 | -webkit-transform: translate(0px, 135px);
681 | -moz-transform: translate(0px, 135px);
682 | -ms-transform: translate(0px, 135px);
683 | transform: translate(0px, 135px); }
684 | .tile.tile-position-1-4 {
685 | -webkit-transform: translate(0px, 202px);
686 | -moz-transform: translate(0px, 202px);
687 | -ms-transform: translate(0px, 202px);
688 | transform: translate(0px, 202px); }
689 | .tile.tile-position-2-1 {
690 | -webkit-transform: translate(67px, 0px);
691 | -moz-transform: translate(67px, 0px);
692 | -ms-transform: translate(67px, 0px);
693 | transform: translate(67px, 0px); }
694 | .tile.tile-position-2-2 {
695 | -webkit-transform: translate(67px, 67px);
696 | -moz-transform: translate(67px, 67px);
697 | -ms-transform: translate(67px, 67px);
698 | transform: translate(67px, 67px); }
699 | .tile.tile-position-2-3 {
700 | -webkit-transform: translate(67px, 135px);
701 | -moz-transform: translate(67px, 135px);
702 | -ms-transform: translate(67px, 135px);
703 | transform: translate(67px, 135px); }
704 | .tile.tile-position-2-4 {
705 | -webkit-transform: translate(67px, 202px);
706 | -moz-transform: translate(67px, 202px);
707 | -ms-transform: translate(67px, 202px);
708 | transform: translate(67px, 202px); }
709 | .tile.tile-position-3-1 {
710 | -webkit-transform: translate(135px, 0px);
711 | -moz-transform: translate(135px, 0px);
712 | -ms-transform: translate(135px, 0px);
713 | transform: translate(135px, 0px); }
714 | .tile.tile-position-3-2 {
715 | -webkit-transform: translate(135px, 67px);
716 | -moz-transform: translate(135px, 67px);
717 | -ms-transform: translate(135px, 67px);
718 | transform: translate(135px, 67px); }
719 | .tile.tile-position-3-3 {
720 | -webkit-transform: translate(135px, 135px);
721 | -moz-transform: translate(135px, 135px);
722 | -ms-transform: translate(135px, 135px);
723 | transform: translate(135px, 135px); }
724 | .tile.tile-position-3-4 {
725 | -webkit-transform: translate(135px, 202px);
726 | -moz-transform: translate(135px, 202px);
727 | -ms-transform: translate(135px, 202px);
728 | transform: translate(135px, 202px); }
729 | .tile.tile-position-4-1 {
730 | -webkit-transform: translate(202px, 0px);
731 | -moz-transform: translate(202px, 0px);
732 | -ms-transform: translate(202px, 0px);
733 | transform: translate(202px, 0px); }
734 | .tile.tile-position-4-2 {
735 | -webkit-transform: translate(202px, 67px);
736 | -moz-transform: translate(202px, 67px);
737 | -ms-transform: translate(202px, 67px);
738 | transform: translate(202px, 67px); }
739 | .tile.tile-position-4-3 {
740 | -webkit-transform: translate(202px, 135px);
741 | -moz-transform: translate(202px, 135px);
742 | -ms-transform: translate(202px, 135px);
743 | transform: translate(202px, 135px); }
744 | .tile.tile-position-4-4 {
745 | -webkit-transform: translate(202px, 202px);
746 | -moz-transform: translate(202px, 202px);
747 | -ms-transform: translate(202px, 202px);
748 | transform: translate(202px, 202px); }
749 |
750 | .tile .tile-inner {
751 | font-size: 10px; }
752 |
753 | .game-message p {
754 | font-size: 30px !important;
755 | height: 30px !important;
756 | line-height: 30px !important;
757 | margin-top: 90px !important; }
758 | .game-message .lower {
759 | margin-top: 30px !important; } }
--------------------------------------------------------------------------------