110 </div> |
112 </div> |
111 </div> |
113 </div> |
112 |
114 |
113 <script> |
115 <script> |
114 "use strict"; |
116 "use strict"; |
115 |
|
116 var board = {}; |
|
117 board.create = function() { |
|
118 var brd = []; |
|
119 for (var i = 0; i < 4; i++) { |
|
120 brd[i] = []; |
|
121 for (var j = 0; j < 4; j++) { |
|
122 brd[i][j] = 0; |
|
123 } |
|
124 } |
|
125 return brd; |
|
126 } |
|
127 board.copy = function(from, to) { |
|
128 for (var i = 0; i < 4; i++) { |
|
129 for (var j = 0; j < 4; j++) { |
|
130 to[i][j] = from[i][j]; |
|
131 } |
|
132 } |
|
133 } |
|
134 board.freeCnt = function(brd) { |
|
135 var cnt = 0; |
|
136 for (var i = 0; i < 4; i++) { |
|
137 for (var j = 0; j < 4; j++) { |
|
138 if (brd[i][j] === 0) |
|
139 cnt++; |
|
140 } |
|
141 } |
|
142 return cnt; |
|
143 } |
|
144 board.gameOver = function(brd) { |
|
145 if (board.freeCnt(brd) > 0) |
|
146 return false; |
|
147 for (var i = 0; i < 4; i++) { |
|
148 for (var j = 0; j < 3; j++) { |
|
149 if (brd[i][j] === brd[i][j+1]) |
|
150 return false; |
|
151 } |
|
152 } |
|
153 for (var j = 0; j < 4; j++) { |
|
154 for (var i = 0; i < 3; i++) { |
|
155 if (brd[i][j] === brd[i+1][j]) |
|
156 return false; |
|
157 } |
|
158 } |
|
159 return true; |
|
160 } |
|
161 board.putRandom = function(brd) { |
|
162 var cnt = board.freeCnt(brd); |
|
163 cnt = Math.floor(Math.random() * cnt)+1; |
|
164 for (var i = 0; i < 4 && cnt > 0; i++) { |
|
165 for (var j = 0; j < 4 && cnt > 0; j++) { |
|
166 if (brd[i][j] !== 0) |
|
167 continue; |
|
168 if (cnt === 1) |
|
169 brd[i][j] = (Math.random() > .9) ? 2 : 1; |
|
170 cnt--; |
|
171 } |
|
172 } |
|
173 } |
|
174 /* http://www.reddit.com/r/2048/comments/214njx/highest_possible_score_for_2048_warning_math */ |
|
175 var boardScoreTbl = [0]; |
|
176 for (var i = 1, exp = 2; i < 16; i++, exp *= 2) { |
|
177 boardScoreTbl[i] = (i-1)*exp; |
|
178 } |
|
179 board.score = function(brd) { |
|
180 var score = 0; |
|
181 var max = 0; |
|
182 for (var i = 0; i < 4; i++) { |
|
183 for (var j = 0; j < 4; j++) { |
|
184 var val = brd[i][j]; |
|
185 score += boardScoreTbl[val]; |
|
186 if (max < val) |
|
187 max = val; |
|
188 } |
|
189 } |
|
190 return {score: score, max: Math.pow(2, max)}; |
|
191 } |
|
192 |
|
193 board.row = {}; |
|
194 board.row.init = function() { |
|
195 return {stack: [], curr: 0}; |
|
196 } |
|
197 board.row.push = function(state, val) { |
|
198 if (val === 0) |
|
199 return; |
|
200 if (state.curr === 0) { |
|
201 state.curr = val; |
|
202 return; |
|
203 } |
|
204 if (state.curr === val) { |
|
205 state.stack.push(state.curr+1); |
|
206 state.curr = 0; |
|
207 } else { |
|
208 state.stack.push(state.curr); |
|
209 state.curr = val; |
|
210 } |
|
211 } |
|
212 board.row.finish = function(state) { |
|
213 if (state.curr !== 0) |
|
214 state.stack.push(state.curr); |
|
215 } |
|
216 board.move = {}; |
|
217 board.move.up = function(brd) { |
|
218 var updated = false; |
|
219 for (var j = 0; j < 4; j++) { |
|
220 var state = board.row.init(); |
|
221 for (var i = 0; i < 4; i++) { |
|
222 board.row.push(state, brd[i][j]); |
|
223 } |
|
224 board.row.finish(state); |
|
225 for (var i = 0; i < state.stack.length; i++) { |
|
226 if (brd[i][j] !== state.stack[i]) |
|
227 updated = true; |
|
228 brd[i][j] = state.stack[i]; |
|
229 } |
|
230 for (; i < 4; i++) { |
|
231 if (brd[i][j] !== 0) |
|
232 updated = true; |
|
233 brd[i][j] = 0; |
|
234 } |
|
235 } |
|
236 return updated; |
|
237 }; |
|
238 board.move.down = function(brd) { |
|
239 var updated = false; |
|
240 for (var j = 0; j < 4; j++) { |
|
241 var state = board.row.init(); |
|
242 for (var i = 3; i >= 0; i--) { |
|
243 board.row.push(state, brd[i][j]); |
|
244 } |
|
245 board.row.finish(state); |
|
246 for (var i = 0; i < state.stack.length; i++) { |
|
247 if (brd[3-i][j] !== state.stack[i]) |
|
248 updated = true; |
|
249 brd[3-i][j] = state.stack[i]; |
|
250 } |
|
251 for (; i < 4; i++) { |
|
252 if (brd[3-i][j] !== 0) |
|
253 updated = true; |
|
254 brd[3-i][j] = 0; |
|
255 } |
|
256 } |
|
257 return updated; |
|
258 }; |
|
259 board.move.left = function(brd) { |
|
260 var updated = false; |
|
261 for (var i = 0; i < 4; i++) { |
|
262 var state = board.row.init(); |
|
263 for (var j = 0; j < 4; j++) { |
|
264 board.row.push(state, brd[i][j]); |
|
265 } |
|
266 board.row.finish(state); |
|
267 for (var j = 0; j < state.stack.length; j++) { |
|
268 if (brd[i][j] !== state.stack[j]) |
|
269 updated = true; |
|
270 brd[i][j] = state.stack[j]; |
|
271 } |
|
272 for (; j < 4; j++) { |
|
273 if (brd[i][j] !== 0) |
|
274 updated = true; |
|
275 brd[i][j] = 0; |
|
276 } |
|
277 } |
|
278 return updated; |
|
279 }; |
|
280 board.move.right = function(brd) { |
|
281 var updated = false; |
|
282 for (var i = 0; i < 4; i++) { |
|
283 var state = board.row.init(); |
|
284 for (var j = 3; j >= 0; j--) { |
|
285 board.row.push(state, brd[i][j]); |
|
286 } |
|
287 board.row.finish(state); |
|
288 for (var j = 0; j < state.stack.length; j++) { |
|
289 if (brd[i][3-j] !== state.stack[j]) |
|
290 updated = true; |
|
291 brd[i][3-j] = state.stack[j]; |
|
292 } |
|
293 for (; j < 4; j++) { |
|
294 if (brd[i][3-j] !== 0) |
|
295 updated = true; |
|
296 brd[i][3-j] = 0; |
|
297 } |
|
298 } |
|
299 return updated; |
|
300 }; |
|
301 |
117 |
302 var boardDom = document.getElementById("board"); |
118 var boardDom = document.getElementById("board"); |
303 var ui = {}; |
119 var ui = {}; |
304 ui.board = {}; |
120 ui.board = {}; |
305 ui.board.set = function(i, j, val) { |
121 ui.board.set = function(i, j, val) { |
405 ui.message.set("Game over!"); |
221 ui.message.set("Game over!"); |
406 return; |
222 return; |
407 } |
223 } |
408 var tmpBrd = board.create(); |
224 var tmpBrd = board.create(); |
409 board.copy(board.current, tmpBrd); |
225 board.copy(board.current, tmpBrd); |
410 var fn = ai.current(tmpBrd); |
226 var move = ui.ai.analyse(tmpBrd); |
411 if (typeof fn === 'undefined') { |
227 if (typeof move === 'undefined') { |
412 ui.message.set("I don't know how to move!"); |
228 ui.message.set("I don't know how to move!"); |
413 return; |
229 return; |
414 } |
230 } |
415 var updated = board.move[fn].call(null, board.current); |
231 var updated = board.move[move].call(null, board.current); |
416 if (updated) { |
232 if (updated) { |
417 board.putRandom(board.current); |
233 board.putRandom(board.current); |
418 ui.board.update(board.current); |
234 ui.board.update(board.current); |
419 ui.score.update(board.current); |
235 ui.score.update(board.current); |
420 } else { |
236 } else { |
428 var step = 0; |
244 var step = 0; |
429 var tsFrom = new Date().getTime(); |
245 var tsFrom = new Date().getTime(); |
430 while (!board.gameOver(board.current)) { |
246 while (!board.gameOver(board.current)) { |
431 var tmpBrd = board.create(); |
247 var tmpBrd = board.create(); |
432 board.copy(board.current, tmpBrd); |
248 board.copy(board.current, tmpBrd); |
433 var fn = ai.current(tmpBrd); |
249 var move = ui.ai.analyse(tmpBrd); |
434 if (typeof fn === 'undefined') { |
250 if (typeof move === 'undefined') { |
435 ui.message.set("I don't know how to move!"); |
251 ui.message.set("I don't know how to move!"); |
436 return; |
252 return; |
437 } |
253 } |
438 var updated = board.move[fn].call(null, board.current); |
254 var updated = board.move[move].call(null, board.current); |
439 if (updated) { |
255 if (updated) { |
440 board.putRandom(board.current); |
256 board.putRandom(board.current); |
441 } else { |
257 } else { |
442 ui.board.update(board.current); |
258 ui.board.update(board.current); |
443 ui.score.update(board.current); |
259 ui.score.update(board.current); |
452 ui.score.speed(step*1000.0/(tsTo-tsFrom), step); |
268 ui.score.speed(step*1000.0/(tsTo-tsFrom), step); |
453 ui.message.set("Game over!"); |
269 ui.message.set("Game over!"); |
454 } |
270 } |
455 document.getElementById("finish").addEventListener("click", finish); |
271 document.getElementById("finish").addEventListener("click", finish); |
456 |
272 |
457 var ai = {}; |
273 //////////////////////////////////////////////////////////////// |
458 |
274 // Register AIs. |
459 ai.random = function(brd) { |
275 |
460 var tmpBrd = board.create(); |
276 ui.brdEngine = BoardArr2d; // TODO make user selectable |
461 do { |
277 |
462 var action = ["up", "down", "left", "right"][Math.floor(Math.random()*4)]; |
|
463 board.copy(brd, tmpBrd); |
|
464 } while (!board.move[action](tmpBrd)); |
|
465 return action; |
|
466 } |
|
467 document.getElementById("ai-random").addEventListener("click", function() { |
278 document.getElementById("ai-random").addEventListener("click", function() { |
468 ai.current = ai.random; |
279 ui.ai = new ai.random(ui.brdEngine); |
469 }); |
280 }); |
470 |
|
471 ai.nextMaxScore = function(brd) { |
|
472 var tmpBrd = board.create(); |
|
473 board.copy(brd, tmpBrd); |
|
474 var maxScore = -1; |
|
475 var action; |
|
476 if (board.move.up(tmpBrd)) { |
|
477 maxScore = board.score(tmpBrd).score; |
|
478 action = "up"; |
|
479 } |
|
480 board.copy(brd, tmpBrd); |
|
481 if (board.move.left(tmpBrd)) { |
|
482 var score = board.score(tmpBrd).score; |
|
483 if (maxScore < score) { |
|
484 action = "left"; |
|
485 maxScore = score; |
|
486 } |
|
487 } |
|
488 board.copy(brd, tmpBrd); |
|
489 if (board.move.down(tmpBrd)) { |
|
490 var score = board.score(tmpBrd).score; |
|
491 if (maxScore < score) { |
|
492 action = "down"; |
|
493 maxScore = score; |
|
494 } |
|
495 } |
|
496 board.copy(brd, tmpBrd); |
|
497 if (board.move.right(tmpBrd)) { |
|
498 var score = board.score(tmpBrd).score; |
|
499 if (maxScore < score) { |
|
500 action = "right"; |
|
501 maxScore = score; |
|
502 } |
|
503 } |
|
504 return action; |
|
505 } |
|
506 document.getElementById("ai-next-max-score").addEventListener("click", function() { |
281 document.getElementById("ai-next-max-score").addEventListener("click", function() { |
507 ai.current = ai.nextMaxScore; |
282 ui.ai = new ai.nextMaxScore(ui.brdEngine); |
508 }); |
283 }); |
509 |
|
510 |
|
511 ai.nextMaxValue = function(brd) { |
|
512 var tmpBrd = board.create(); |
|
513 board.copy(brd, tmpBrd); |
|
514 var maxMax = -1; |
|
515 var action; |
|
516 if (board.move.up(tmpBrd)) { |
|
517 maxMax = board.score(tmpBrd).max; |
|
518 action = "up"; |
|
519 } |
|
520 board.copy(brd, tmpBrd); |
|
521 if (board.move.left(tmpBrd)) { |
|
522 var max = board.score(tmpBrd).max; |
|
523 if (maxMax < max) { |
|
524 action = "left"; |
|
525 maxMax = max; |
|
526 } |
|
527 } |
|
528 board.copy(brd, tmpBrd); |
|
529 if (board.move.down(tmpBrd)) { |
|
530 var max = board.score(tmpBrd).max; |
|
531 if (maxMax < max) { |
|
532 action = "down"; |
|
533 maxMax = max; |
|
534 } |
|
535 } |
|
536 board.copy(brd, tmpBrd); |
|
537 if (board.move.right(tmpBrd)) { |
|
538 var max = board.score(tmpBrd).max; |
|
539 if (maxMax < max) { |
|
540 action = "right"; |
|
541 maxMax = max; |
|
542 } |
|
543 } |
|
544 return action; |
|
545 } |
|
546 document.getElementById("ai-next-max-value").addEventListener("click", function() { |
284 document.getElementById("ai-next-max-value").addEventListener("click", function() { |
547 ai.current = ai.nextMaxValue; |
285 ui.ai = new ai.nextMaxValue(ui.brdEngine); |
548 }); |
286 }); |
549 |
287 |
550 </script> |
288 </script> |
551 |
289 |
552 </body> |
290 </body> |