|
1 <!DOCTYPE html> |
|
2 <html> |
|
3 <head> |
|
4 <title>2048 AI</title> |
|
5 <meta name="viewport" content="width=device-width; initial-scale=1.0"/> |
|
6 <meta charset="utf-8"/> |
|
7 |
|
8 <style> |
|
9 body { |
|
10 width: 100%; |
|
11 } |
|
12 h1, .score-area, .control-area, #message-area { |
|
13 text-align: center; |
|
14 } |
|
15 #board { |
|
16 margin: 10px auto; |
|
17 } |
|
18 #board td { |
|
19 width: 40px; |
|
20 height: 40px; |
|
21 border: 1px solid red; |
|
22 margin: 0; |
|
23 text-align: center; |
|
24 } |
|
25 </style> |
|
26 </head> |
|
27 <body> |
|
28 |
|
29 <h1>2048</h1> |
|
30 |
|
31 <div class="score-area">Score: <span id="score">0</span>, Max: <span id="max">0</span></div> |
|
32 |
|
33 <div id="message-area"></div> |
|
34 |
|
35 <table id="board"> |
|
36 <tr> |
|
37 <td></td> |
|
38 <td></td> |
|
39 <td></td> |
|
40 <td></td> |
|
41 </tr> |
|
42 <tr> |
|
43 <td></td> |
|
44 <td></td> |
|
45 <td></td> |
|
46 <td></td> |
|
47 </tr> |
|
48 <tr> |
|
49 <td></td> |
|
50 <td></td> |
|
51 <td></td> |
|
52 <td></td> |
|
53 </tr> |
|
54 <tr> |
|
55 <td></td> |
|
56 <td></td> |
|
57 <td></td> |
|
58 <td></td> |
|
59 </tr> |
|
60 </table> |
|
61 |
|
62 <div class="control-area"> |
|
63 <div> |
|
64 <button id="start">Start</button> |
|
65 <button id="step">Step</button> |
|
66 <button id="loop">Loop</button> |
|
67 <button id="finish">Finish</button> |
|
68 </div> |
|
69 <div> |
|
70 <button id="left">left</button> |
|
71 <button id="up">up</button> |
|
72 <button id="down">down</button> |
|
73 <button id="right">right</button> |
|
74 </div> |
|
75 <h1>AI</h1> |
|
76 <div> |
|
77 <button id="ai-random">random</button> |
|
78 <button id="ai-next-max-score">next max score</button> |
|
79 <button id="ai-next-max-value">next max value</button> |
|
80 </div> |
|
81 </div> |
|
82 |
|
83 <script> |
|
84 "use strict"; |
|
85 |
|
86 var board = {}; |
|
87 board.create = function() { |
|
88 var brd = []; |
|
89 for (var i = 0; i < 4; i++) { |
|
90 brd[i] = []; |
|
91 for (var j = 0; j < 4; j++) { |
|
92 brd[i][j] = 0; |
|
93 } |
|
94 } |
|
95 return brd; |
|
96 } |
|
97 board.copy = function(from, to) { |
|
98 for (var i = 0; i < 4; i++) { |
|
99 for (var j = 0; j < 4; j++) { |
|
100 to[i][j] = from[i][j]; |
|
101 } |
|
102 } |
|
103 } |
|
104 board.freeCnt = function(brd) { |
|
105 var cnt = 0; |
|
106 for (var i = 0; i < 4; i++) { |
|
107 for (var j = 0; j < 4; j++) { |
|
108 if (brd[i][j] === 0) |
|
109 cnt++; |
|
110 } |
|
111 } |
|
112 return cnt; |
|
113 } |
|
114 board.gameOver = function(brd) { |
|
115 if (board.freeCnt(brd) > 0) |
|
116 return false; |
|
117 for (var i = 0; i < 4; i++) { |
|
118 for (var j = 0; j < 3; j++) { |
|
119 if (brd[i][j] === brd[i][j+1]) |
|
120 return false; |
|
121 } |
|
122 } |
|
123 for (var j = 0; j < 4; j++) { |
|
124 for (var i = 0; i < 3; i++) { |
|
125 if (brd[i][j] === brd[i+1][j]) |
|
126 return false; |
|
127 } |
|
128 } |
|
129 return true; |
|
130 } |
|
131 board.random = function(brd) { |
|
132 var cnt = board.freeCnt(brd); |
|
133 cnt = Math.floor(Math.random() * cnt)+1; |
|
134 for (var i = 0; i < 4 && cnt > 0; i++) { |
|
135 for (var j = 0; j < 4 && cnt > 0; j++) { |
|
136 if (brd[i][j] !== 0) |
|
137 continue; |
|
138 if (cnt === 1) |
|
139 brd[i][j] = 2; |
|
140 cnt--; |
|
141 } |
|
142 } |
|
143 } |
|
144 /* http://www.reddit.com/r/2048/comments/214njx/highest_possible_score_for_2048_warning_math */ |
|
145 board.score = function(brd) { |
|
146 var score = 0; |
|
147 var max = 0; |
|
148 for (var i = 0; i < 4; i++) { |
|
149 for (var j = 0; j < 4; j++) { |
|
150 var val = brd[i][j]; |
|
151 if (val > 2) |
|
152 score += Math.log2(val) * val; |
|
153 if (max < val) |
|
154 max = val; |
|
155 } |
|
156 } |
|
157 return {score: score, max: max}; |
|
158 } |
|
159 |
|
160 board.row = {}; |
|
161 board.row.init = function() { |
|
162 return {stack: [], curr: 0}; |
|
163 } |
|
164 board.row.push = function(state, val) { |
|
165 if (val === 0) |
|
166 return; |
|
167 if (state.curr === 0) { |
|
168 state.curr = val; |
|
169 return; |
|
170 } |
|
171 if (state.curr === val) { |
|
172 state.stack.push(state.curr*2); |
|
173 state.curr = 0; |
|
174 } else { |
|
175 state.stack.push(state.curr); |
|
176 state.curr = val; |
|
177 } |
|
178 } |
|
179 board.row.finish = function(state) { |
|
180 if (state.curr !== 0) |
|
181 state.stack.push(state.curr); |
|
182 } |
|
183 board.move = {}; |
|
184 board.move.up = function(brd) { |
|
185 var updated = false; |
|
186 for (var j = 0; j < 4; j++) { |
|
187 var state = board.row.init(); |
|
188 for (var i = 0; i < 4; i++) { |
|
189 board.row.push(state, brd[i][j]); |
|
190 } |
|
191 board.row.finish(state); |
|
192 for (var i = 0; i < state.stack.length; i++) { |
|
193 if (brd[i][j] !== state.stack[i]) |
|
194 updated = true; |
|
195 brd[i][j] = state.stack[i]; |
|
196 } |
|
197 for (; i < 4; i++) { |
|
198 if (brd[i][j] !== 0) |
|
199 updated = true; |
|
200 brd[i][j] = 0; |
|
201 } |
|
202 } |
|
203 return updated; |
|
204 }; |
|
205 board.move.down = function(brd) { |
|
206 var updated = false; |
|
207 for (var j = 0; j < 4; j++) { |
|
208 var state = board.row.init(); |
|
209 for (var i = 3; i >= 0; i--) { |
|
210 board.row.push(state, brd[i][j]); |
|
211 } |
|
212 board.row.finish(state); |
|
213 for (var i = 0; i < state.stack.length; i++) { |
|
214 if (brd[3-i][j] !== state.stack[i]) |
|
215 updated = true; |
|
216 brd[3-i][j] = state.stack[i]; |
|
217 } |
|
218 for (; i < 4; i++) { |
|
219 if (brd[3-i][j] !== 0) |
|
220 updated = true; |
|
221 brd[3-i][j] = 0; |
|
222 } |
|
223 } |
|
224 return updated; |
|
225 }; |
|
226 board.move.left = function(brd) { |
|
227 var updated = false; |
|
228 for (var i = 0; i < 4; i++) { |
|
229 var state = board.row.init(); |
|
230 for (var j = 0; j < 4; j++) { |
|
231 board.row.push(state, brd[i][j]); |
|
232 } |
|
233 board.row.finish(state); |
|
234 for (var j = 0; j < state.stack.length; j++) { |
|
235 if (brd[i][j] !== state.stack[j]) |
|
236 updated = true; |
|
237 brd[i][j] = state.stack[j]; |
|
238 } |
|
239 for (; j < 4; j++) { |
|
240 if (brd[i][j] !== 0) |
|
241 updated = true; |
|
242 brd[i][j] = 0; |
|
243 } |
|
244 } |
|
245 return updated; |
|
246 }; |
|
247 board.move.right = function(brd) { |
|
248 var updated = false; |
|
249 for (var i = 0; i < 4; i++) { |
|
250 var state = board.row.init(); |
|
251 for (var j = 3; j >= 0; j--) { |
|
252 board.row.push(state, brd[i][j]); |
|
253 } |
|
254 board.row.finish(state); |
|
255 for (var j = 0; j < state.stack.length; j++) { |
|
256 if (brd[i][3-j] !== state.stack[j]) |
|
257 updated = true; |
|
258 brd[i][3-j] = state.stack[j]; |
|
259 } |
|
260 for (; j < 4; j++) { |
|
261 if (brd[i][3-j] !== 0) |
|
262 updated = true; |
|
263 brd[i][3-j] = 0; |
|
264 } |
|
265 } |
|
266 return updated; |
|
267 }; |
|
268 |
|
269 var boardDom = document.getElementById("board"); |
|
270 var ui = {}; |
|
271 ui.board = {}; |
|
272 ui.board.set = function(i, j, val) { |
|
273 boardDom.querySelectorAll("tr")[i].querySelectorAll("td")[j].innerHTML = val; |
|
274 } |
|
275 ui.board.update = function(brd) { |
|
276 for (var i = 0; i < 4; i++) { |
|
277 for (var j = 0; j < 4; j++) { |
|
278 ui.board.set(i, j, (brd[i][j] >= 2) ? brd[i][j] : ""); |
|
279 } |
|
280 } |
|
281 } |
|
282 ui.score = {}; |
|
283 var scoreDom = document.getElementById("score"); |
|
284 var maxDom = document.getElementById("max"); |
|
285 ui.score.clear = function(brd) { |
|
286 scoreDom.innerHTML = '0'; |
|
287 maxDom.innerHTML = '0'; |
|
288 } |
|
289 ui.score.update = function(brd) { |
|
290 var score = board.score(brd); |
|
291 scoreDom.innerHTML = '' + score.score; |
|
292 maxDom.innerHTML = '' + score.max; |
|
293 } |
|
294 |
|
295 function start() { |
|
296 ui.score.clear(); |
|
297 ui.message.clear(); |
|
298 board.current = board.create(); |
|
299 board.random(board.current); |
|
300 ui.board.update(board.current); |
|
301 } |
|
302 document.getElementById("start").addEventListener("click", start); |
|
303 |
|
304 function up() { |
|
305 var updated = board.move.up(board.current); |
|
306 if (updated) { |
|
307 board.random(board.current); |
|
308 ui.board.update(board.current); |
|
309 ui.score.update(board.current); |
|
310 } |
|
311 } |
|
312 document.getElementById("up").addEventListener("click", up); |
|
313 function down() { |
|
314 var updated = board.move.down(board.current); |
|
315 if (updated) { |
|
316 board.random(board.current); |
|
317 ui.board.update(board.current); |
|
318 ui.score.update(board.current); |
|
319 } |
|
320 } |
|
321 document.getElementById("down").addEventListener("click", down); |
|
322 function left() { |
|
323 var updated = board.move.left(board.current); |
|
324 if (updated) { |
|
325 board.random(board.current); |
|
326 ui.board.update(board.current); |
|
327 ui.score.update(board.current); |
|
328 } |
|
329 } |
|
330 document.getElementById("left").addEventListener("click", left); |
|
331 function right() { |
|
332 var updated = board.move.right(board.current); |
|
333 if (updated) { |
|
334 board.random(board.current); |
|
335 ui.board.update(board.current); |
|
336 ui.score.update(board.current); |
|
337 } |
|
338 } |
|
339 document.getElementById("right").addEventListener("click", right); |
|
340 |
|
341 document.body.addEventListener("keydown", function(event) { |
|
342 var key = event.keyCode || event.which; |
|
343 switch (key) { |
|
344 case 38: up(); break; |
|
345 case 40: down(); break; |
|
346 case 37: left(); break; |
|
347 case 39: right(); break; |
|
348 } |
|
349 return false; |
|
350 }); |
|
351 |
|
352 ui.message = {}; |
|
353 var messageDom = document.getElementById("message-area"); |
|
354 ui.message.clear = function() { |
|
355 messageDom.innerHTML = ""; |
|
356 } |
|
357 ui.message.set = function(msg) { |
|
358 messageDom.innerHTML = msg; |
|
359 } |
|
360 |
|
361 function step() { |
|
362 ui.message.clear(); |
|
363 if (board.gameOver(board.current)) { |
|
364 ui.message.set("Game over!"); |
|
365 return; |
|
366 } |
|
367 var tmpBrd = board.create(); |
|
368 board.copy(board.current, tmpBrd); |
|
369 var fn = ai.current(tmpBrd); |
|
370 if (typeof fn === 'undefined') { |
|
371 ui.message.set("I don't know how to move!"); |
|
372 return; |
|
373 } |
|
374 var updated = board.move[fn].call(null, board.current); |
|
375 if (updated) { |
|
376 board.random(board.current); |
|
377 ui.board.update(board.current); |
|
378 ui.score.update(board.current); |
|
379 } else { |
|
380 ui.message.set("Wrong move!"); |
|
381 } |
|
382 } |
|
383 document.getElementById("step").addEventListener("click", step); |
|
384 |
|
385 function finish() { |
|
386 ui.message.clear(); |
|
387 while (!board.gameOver(board.current)) { |
|
388 var tmpBrd = board.create(); |
|
389 board.copy(board.current, tmpBrd); |
|
390 var fn = ai.current(tmpBrd); |
|
391 if (typeof fn === 'undefined') { |
|
392 ui.message.set("I don't know how to move!"); |
|
393 return; |
|
394 } |
|
395 var updated = board.move[fn].call(null, board.current); |
|
396 if (updated) { |
|
397 board.random(board.current); |
|
398 } else { |
|
399 ui.board.update(board.current); |
|
400 ui.score.update(board.current); |
|
401 ui.message.set("Wrong move!"); |
|
402 return; |
|
403 } |
|
404 } |
|
405 ui.board.update(board.current); |
|
406 ui.score.update(board.current); |
|
407 ui.message.set("Game over!"); |
|
408 } |
|
409 document.getElementById("finish").addEventListener("click", finish); |
|
410 |
|
411 var ai = {}; |
|
412 |
|
413 ai.random = function(brd) { |
|
414 var tmpBrd = board.create(); |
|
415 do { |
|
416 var action = ["up", "down", "left", "right"][Math.floor(Math.random()*4)]; |
|
417 board.copy(brd, tmpBrd); |
|
418 } while (!board.move[action](tmpBrd)); |
|
419 return action; |
|
420 } |
|
421 document.getElementById("ai-random").addEventListener("click", function() { |
|
422 ai.current = ai.random; |
|
423 }); |
|
424 |
|
425 ai.nextMaxScore = function(brd) { |
|
426 var tmpBrd = board.create(); |
|
427 board.copy(brd, tmpBrd); |
|
428 var maxScore = -1; |
|
429 var action; |
|
430 if (board.move.up(tmpBrd)) { |
|
431 maxScore = board.score(tmpBrd).score; |
|
432 action = "up"; |
|
433 } |
|
434 board.copy(brd, tmpBrd); |
|
435 if (board.move.left(tmpBrd)) { |
|
436 var score = board.score(tmpBrd).score; |
|
437 if (maxScore < score) { |
|
438 action = "left"; |
|
439 maxScore = score; |
|
440 } |
|
441 } |
|
442 board.copy(brd, tmpBrd); |
|
443 if (board.move.down(tmpBrd)) { |
|
444 var score = board.score(tmpBrd).score; |
|
445 if (maxScore < score) { |
|
446 action = "down"; |
|
447 maxScore = score; |
|
448 } |
|
449 } |
|
450 board.copy(brd, tmpBrd); |
|
451 if (board.move.right(tmpBrd)) { |
|
452 var score = board.score(tmpBrd).score; |
|
453 if (maxScore < score) { |
|
454 action = "right"; |
|
455 maxScore = score; |
|
456 } |
|
457 } |
|
458 return action; |
|
459 } |
|
460 document.getElementById("ai-next-max-score").addEventListener("click", function() { |
|
461 ai.current = ai.nextMaxScore; |
|
462 }); |
|
463 |
|
464 |
|
465 ai.nextMaxValue = function(brd) { |
|
466 var tmpBrd = board.create(); |
|
467 board.copy(brd, tmpBrd); |
|
468 var maxMax = -1; |
|
469 var action; |
|
470 if (board.move.up(tmpBrd)) { |
|
471 maxMax = board.score(tmpBrd).max; |
|
472 action = "up"; |
|
473 } |
|
474 board.copy(brd, tmpBrd); |
|
475 if (board.move.left(tmpBrd)) { |
|
476 var max = board.score(tmpBrd).max; |
|
477 if (maxMax < max) { |
|
478 action = "left"; |
|
479 maxMax = max; |
|
480 } |
|
481 } |
|
482 board.copy(brd, tmpBrd); |
|
483 if (board.move.down(tmpBrd)) { |
|
484 var max = board.score(tmpBrd).max; |
|
485 if (maxMax < max) { |
|
486 action = "down"; |
|
487 maxMax = max; |
|
488 } |
|
489 } |
|
490 board.copy(brd, tmpBrd); |
|
491 if (board.move.right(tmpBrd)) { |
|
492 var max = board.score(tmpBrd).max; |
|
493 if (maxMax < max) { |
|
494 action = "right"; |
|
495 maxMax = max; |
|
496 } |
|
497 } |
|
498 return action; |
|
499 } |
|
500 document.getElementById("ai-next-max-value").addEventListener("click", function() { |
|
501 ai.current = ai.nextMaxValue; |
|
502 }); |
|
503 |
|
504 </script> |
|
505 |
|
506 </body> |
|
507 </html> |