Working 2048 Script With a Delay Functionality


SUBMITTED BY: AshuGopal

DATE: June 27, 2016, 11:43 a.m.

FORMAT: Text only

SIZE: 18.5 kB

HITS: 3257

  1. /*Its not mine script but I do have updated some of it to work on 2048.com*/
  2. function shuru() {
  3. // Bot flags.
  4. var EVALUATE_ONLY = false;
  5. var AUTO_RETRY = false;
  6. // Search constants.
  7. var SEARCH_DEPTH = 4;
  8. var SEARCH_TIME = 1000;
  9. var RETRY_TIME = 1000;
  10. var ACCEPT_DEFEAT_VALUE = -999999;
  11. // Evaluation constants.
  12. var NUM_EMPTY_WEIGHT = 5;
  13. var ADJ_DIFF_WEIGHT = -0.5;
  14. var INSULATION_WEIGHT = -2;
  15. var POSITION_WEIGHT = 0.04;
  16. var POSITION_VALUE = [
  17. 0, 0, 0, 10,
  18. 0, 0, 0, 15,
  19. 0, 0, -5, 20,
  20. 10, 15, 20, 50
  21. ];
  22. var LOG2 = {};
  23. for (var i = 0 ; i < 20; i++)
  24. LOG2[1 << i] = i;
  25. // Game constants.
  26. var GRID_SIZE = 4;
  27. var PROB_2 = 0.9;
  28. var MOVE_UP = {
  29. drow: -1,
  30. dcol: 0,
  31. dir: 0,
  32. keyCode: 38,
  33. key: 'Up'
  34. };
  35. var MOVE_DOWN = {
  36. drow: 1,
  37. dcol: 0,
  38. dir: 1,
  39. keyCode: 40,
  40. key: 'Down'
  41. };
  42. var MOVE_LEFT = {
  43. drow: 0,
  44. dcol: -1,
  45. dir: 0,
  46. keyCode: 37,
  47. key: 'Left'
  48. };
  49. var MOVE_RIGHT = {
  50. drow: 0,
  51. dcol: 1,
  52. dir: 1,
  53. keyCode: 39,
  54. key: 'Right'
  55. };
  56. // If EVALUATE_ONLY flag is not set, play the game. If the flag is set (for
  57. // development purposes), just print detailed evaluation output.
  58. if (EVALUATE_ONLY) {
  59. var grid = getGrid();
  60. print(grid);
  61. evaluate(grid, true);
  62. }
  63. else
  64. setInterval(nextMove, SEARCH_TIME);
  65. // Press continue to keep playing if we win the game.
  66. setInterval(function() {
  67. if (gameWon())
  68. keepPlaying();
  69. }, RETRY_TIME);
  70. // If AUTO_RETRY flag is set, print statistics and automatically retry after
  71. // losses.
  72. if (AUTO_RETRY) {
  73. var games = 0;
  74. var bestScore = 0;
  75. var averageScore = 0;
  76. var bestLargestTile = 0;
  77. var averageLargestTile = 0;
  78. setInterval(function() {
  79. if (gameLost()) {
  80. var score = getScore();
  81. bestScore = Math.max(bestScore, score);
  82. var grid = getGrid();
  83. var largestTile = 0;
  84. for (var i = 0; i < grid.length; i++)
  85. largestTile = Math.max(largestTile, grid[i]);
  86. bestLargestTile = Math.max(bestLargestTile, largestTile);
  87. averageScore = (averageScore * games + score) / (games + 1);
  88. averageLargestTile = (averageLargestTile * games + largestTile) / (games + 1);
  89. games++;
  90. console.log('Game ' + games + '\n' +
  91. 'Score ' + score + '\n' +
  92. 'Largest tile ' + largestTile + '\n' +
  93. 'Average score ' + Math.round(averageScore) + '\n' +
  94. 'Average largest tile ' + Math.round(averageLargestTile) + '\n' +
  95. 'Best score ' + bestScore + '\n' +
  96. 'Best largest tile ' + bestLargestTile + '\n' +
  97. '\n');
  98. search.table = {};
  99. if (AUTO_RETRY)
  100. tryAgain();
  101. }
  102. }, RETRY_TIME);
  103. }
  104. /**
  105. * Chooses and the next move and plays it.
  106. */
  107. function nextMove() {
  108. var grid = getGrid();
  109. var move = search(grid, SEARCH_DEPTH, Number.NEGATIVE_INFINITY, true);
  110. setTimeout(function(){pressKey(move)},30);
  111. }
  112. /**
  113. * Searches for the best move with depth-first search.
  114. * @param grid: flat array representation of game grid.
  115. * @param depth: search tree depth, where leaves are at depth 0.
  116. * @param alpha: lower bound on search value.
  117. * @param root: whether to treat the node as the root node.
  118. * @return best move at root nodes, value of best move at other nodes.
  119. */
  120. function search(grid, depth, alpha, root) {
  121. if (depth <= 0)
  122. return evaluate(grid);
  123. if (!search.table)
  124. search.table = {};
  125. var key = getGridKey(grid);
  126. var entry = search.table[key];
  127. if (entry && entry.depth >= depth && (!entry.isBound || entry.value <= alpha))
  128. return root ? entry.move : entry.value;
  129. var moves = [ MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT, MOVE_UP ];
  130. if (entry) {
  131. var index = moves.indexOf(entry.move);
  132. var temp = moves[index];
  133. moves[index] = moves[0];
  134. moves[0] = temp;
  135. }
  136. var bestMove = undefined;
  137. var alphaImproved = false;
  138. for (var i = 0; i < moves.length; i++) {
  139. var copyGrid = copy(grid);
  140. var move = moves[i];
  141. if (make(copyGrid, move)) {
  142. bestMove = bestMove || move;
  143. var value = Number.POSITIVE_INFINITY;
  144. for (var j = copyGrid.length - 1; j >= 0 && value > alpha; j--) {
  145. if (!copyGrid[j]) {
  146. copyGrid[j] = 2;
  147. value = Math.min(value, search(copyGrid, depth - 1, alpha));
  148. copyGrid[j] = 0;
  149. }
  150. }
  151. if (value > alpha) {
  152. alpha = value;
  153. bestMove = move;
  154. alphaImproved = true;
  155. }
  156. }
  157. }
  158. if (!bestMove)
  159. return root ? MOVE_LEFT : ACCEPT_DEFEAT_VALUE + evaluate(grid);
  160. search.table[key] = {
  161. depth: depth,
  162. value: alpha,
  163. move: bestMove,
  164. isBound: !alphaImproved
  165. };
  166. return root ? bestMove : alpha;
  167. }
  168. /**
  169. * Evaluates the given grid state.
  170. * @param grid: flat array representation of game grid.
  171. * @param logging: whether to log evaluation computation.
  172. * @return estimated value of grid state.
  173. */
  174. function evaluate(grid, logging) {
  175. var value = 0;
  176. var positionValue = 0;
  177. var adjDiffValue = 0;
  178. var insulationValue = 0;
  179. var numEmpty = 0;
  180. for (var r = 0; r < GRID_SIZE; r++) {
  181. for (var c = 0; c < GRID_SIZE; c++) {
  182. var tile = get(grid, r, c);
  183. if (!tile) {
  184. numEmpty++;
  185. }
  186. else {
  187. positionValue += tile * POSITION_VALUE[r * GRID_SIZE + c];
  188. if (c < GRID_SIZE - 1) {
  189. var adjTile = get(grid, r, c + 1);
  190. if (adjTile) {
  191. adjDiffValue += levelDifference(tile, adjTile) * Math.log(tile + adjTile);
  192. if (c < GRID_SIZE - 2) {
  193. var thirdTile = get(grid, r, c + 2);
  194. if (thirdTile && levelDifference(tile, thirdTile) <= 1.1) {
  195. var smallerTile = Math.min(tile, thirdTile);
  196. insulationValue += levelDifference(smallerTile, adjTile) * Math.log(smallerTile);
  197. }
  198. }
  199. }
  200. }
  201. if (r < GRID_SIZE - 1) {
  202. adjTile = get(grid, r + 1, c);
  203. if (adjTile) {
  204. adjDiffValue += levelDifference(tile, adjTile) * Math.log(tile + adjTile);
  205. if (c < GRID_SIZE - 2) {
  206. var thirdTile = get(grid, r + 2, c);
  207. if (thirdTile && levelDifference(tile, thirdTile) <= 1.1) {
  208. var smallerTile = Math.min(tile, thirdTile);
  209. insulationValue += levelDifference(smallerTile, adjTile) * Math.log(smallerTile);
  210. }
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. var numEmptyValue = 11.12249 + (0.05735587 - 11.12249) / (1 + Math.pow((numEmpty / 2.480941), 2.717769));
  218. value += POSITION_WEIGHT * positionValue;
  219. value += NUM_EMPTY_WEIGHT * numEmptyValue;
  220. value += ADJ_DIFF_WEIGHT * adjDiffValue;
  221. value += INSULATION_WEIGHT * insulationValue;
  222. if (logging) {
  223. console.log('EVALUATION ' + value + '\n' +
  224. ' position ' + (POSITION_WEIGHT * positionValue) + '\n' +
  225. ' numEmpty ' + (NUM_EMPTY_WEIGHT * numEmptyValue) + '\n' +
  226. ' adjDiff ' + (ADJ_DIFF_WEIGHT * adjDiffValue) + '\n' +
  227. ' insulation ' + (INSULATION_WEIGHT * insulationValue) + '\n'
  228. );
  229. }
  230. return value;
  231. }
  232. /**
  233. * Computes the stack level difference between two tiles.
  234. * @param tile1: first tile value.
  235. * @param tile2: second tile value.
  236. * @return stack level difference between two given tiles.
  237. */
  238. function levelDifference(tile1, tile2) {
  239. return tile1 > tile2 ? LOG2[tile1] - LOG2[tile2] : LOG2[tile2] - LOG2[tile1];
  240. }
  241. /**
  242. * Returns the tile value in the grid for a given position.
  243. * @param grid: flat array representation of game grid.
  244. * @param row: position row.
  245. * @param col: position column.
  246. * @return tile value in the grid for given position.
  247. */
  248. function get(grid, row, col) {
  249. return grid[row * GRID_SIZE + col];
  250. }
  251. /**
  252. * Sets the tile value in the grid for a given position.
  253. * @param grid: flat array representation of game grid.
  254. * @param row: position row.
  255. * @param col: position column.
  256. * @param tile: new tile value to assign.
  257. */
  258. function set(grid, row, col, tile) {
  259. grid[row * GRID_SIZE + col] = tile;
  260. }
  261. /**
  262. * Prints the given grid to the console.
  263. * @param grid: flat array representation of game grid.
  264. */
  265. function print(grid) {
  266. function pad(str, len) {
  267. len -= str.length;
  268. while (len-- > 0)
  269. str = ' ' + str;
  270. return str;
  271. }
  272. var result = '';
  273. for (var r = 0; r < GRID_SIZE; r++) {
  274. for (var c = 0; c < GRID_SIZE; c++) {
  275. var tile = get(grid, r, c);
  276. result += tile ? pad(tile + '', 5) : ' .';
  277. }
  278. result += '\n';
  279. }
  280. console.log(result);
  281. }
  282. /**
  283. * Copies the given grid.
  284. * @param grid: flat array representation of game grid.
  285. * @return copy of given grid.
  286. */
  287. function copy(grid) {
  288. return grid.slice();
  289. }
  290. /**
  291. * Determines whether the given location is within grid bounds.
  292. * @param row: position row.
  293. * @param col: position column.
  294. * @return whether the given location is within grid bounds.
  295. */
  296. function inBounds(row, col) {
  297. return 0 <= row && row < GRID_SIZE && 0 <= col && col < GRID_SIZE;
  298. }
  299. /**
  300. * Makes the given move on the grid without inserting new tile.
  301. * @param grid: flat array representation of game grid.
  302. * @param move: object containing move vectors.
  303. * @return whether the move was made successfully.
  304. */
  305. function make(grid, move) {
  306. var start = move.dir * (GRID_SIZE - 1);
  307. var end = (1 - move.dir) * (GRID_SIZE + 1) - 1;
  308. var inc = 1 - 2 * move.dir;
  309. var anyMoved = false;
  310. for (var r = start; r != end; r += inc) {
  311. for (var c = start; c != end; c += inc) {
  312. if (get(grid, r, c)) {
  313. var newr = r + move.drow;
  314. var newc = c + move.dcol;
  315. var oldr = r;
  316. var oldc = c;
  317. while (inBounds(newr, newc)) {
  318. var target = get(grid, newr, newc);
  319. var tile = get(grid, oldr, oldc);
  320. if (!target) {
  321. set(grid, newr, newc, tile);
  322. set(grid, oldr, oldc, 0);
  323. anyMoved = true;
  324. }
  325. else if (target === tile) {
  326. set(grid, newr, newc, -2 * tile);
  327. set(grid, oldr, oldc, 0);
  328. anyMoved = true;
  329. break;
  330. }
  331. oldr = newr;
  332. oldc = newc;
  333. newr += move.drow;
  334. newc += move.dcol;
  335. }
  336. }
  337. }
  338. }
  339. if (!anyMoved)
  340. return false;
  341. var numEmpty = 0;
  342. for (var i = 0; i < grid.length; i++) {
  343. if (grid[i] < 0)
  344. grid[i] *= -1;
  345. else if (!grid[i])
  346. numEmpty++;
  347. }
  348. if (numEmpty === 0)
  349. throw 'No empty squares after making move.';
  350. return true;
  351. }
  352. /**
  353. * Computes hash key for the given game grid.
  354. * @param grid: flat array representation of game grid.
  355. * @return hash key for the given game grid.
  356. */
  357. function getGridKey(grid) {
  358. if (!getGridKey.table1) {
  359. getGridKey.table1 = {};
  360. getGridKey.table2 = {};
  361. for (var i = 0; i < grid.length; i++) {
  362. for (var t = 2; t <= 8192; t *= 2) {
  363. var key = t * grid.length + i;
  364. getGridKey.table1[key] = Math.round(0xffffffff * Math.random());
  365. getGridKey.table2[key] = Math.round(0xffffffff * Math.random());
  366. }
  367. }
  368. }
  369. var value1 = 0;
  370. var value2 = 0;
  371. for (var i = 0; i < grid.length; i++) {
  372. var tile = grid[i];
  373. if (tile) {
  374. var key = tile * grid.length + i;
  375. value1 ^= getGridKey.table1[key];
  376. value2 ^= getGridKey.table2[key];
  377. }
  378. }
  379. return value1 + '' + value2;
  380. }
  381. /**
  382. * Constructs current game grid from DOM.
  383. * @return flat array representation of game grid.
  384. */
  385. function getGrid() {
  386. var tileContainer = document.getElementsByClassName('tile-container')[0];
  387. var tileList = [];
  388. for (var i = 0 ; i < tileContainer.children.length; i++) {
  389. var tile = tileContainer.children[i];
  390. var tileInner = tile.children[0];
  391. var value = parseInt(tileInner.innerHTML);
  392. var className = tile.className;
  393. var positionPrefix = 'tile-position-';
  394. var positionIndex = className.indexOf(positionPrefix) + positionPrefix.length;
  395. var positionStr = className.substring(positionIndex, positionIndex + 3);
  396. var row = parseInt(positionStr[2]) - 1;
  397. var col = parseInt(positionStr[0]) - 1;
  398. tileList.push({
  399. value: value,
  400. row: row,
  401. col: col
  402. });
  403. }
  404. var grid = new Array(GRID_SIZE * GRID_SIZE);
  405. for (var i = 0; i < grid.length; i++) {
  406. grid[i] = 0;
  407. }
  408. for (var i = 0; i < tileList.length; i++) {
  409. var tile = tileList[i];
  410. set(grid, tile.row, tile.col, tile.value);
  411. }
  412. return grid;
  413. }
  414. /**
  415. * Emulates a keypress for a given move.
  416. * @param move: object containing key information.
  417. */
  418. function pressKey(move) {
  419. var event = new Event('keydown', {
  420. bubbles: true,
  421. cancelable: true
  422. });
  423. event.altKey = false;
  424. event.char = '';
  425. event.charCode = 0;
  426. event.ctrlKey = false;
  427. event.defaultPrevented = false;
  428. event.eventPhase = 3;
  429. event.isTrusted = true;
  430. event.key = move.key;
  431. event.keyCode = move.keyCode;
  432. event.locale = 'en-CA';
  433. event.location = 0;
  434. event.metaKey = false;
  435. event.repeat = false;
  436. event.shiftKey = false;
  437. event.which = move.keyCode;
  438. document.body.dispatchEvent(event);
  439. }
  440. /**
  441. * Determines whether the current game has been lost from the DOM.
  442. * @return whether current game has concluded.
  443. */
  444. function gameLost() {
  445. var gameMessage = document.getElementsByClassName('game-message')[0];
  446. return gameMessage.className.indexOf('game-over') >= 0;
  447. }
  448. /**
  449. * Determines whether the current game has been won from the DOM.
  450. * @return whether current game has concluded.
  451. */
  452. function gameWon() {
  453. var gameMessage = document.getElementsByClassName('game-message')[0];
  454. return gameMessage.className.indexOf('game-won') >= 0;
  455. }
  456. /**
  457. * Starts a new game when the current game has concluded.
  458. */
  459. function tryAgain() {
  460. var retryButton = document.getElementsByClassName('retry-button')[0];
  461. retryButton.click();
  462. }
  463. /**
  464. * Continues the game when the current game has been won.
  465. */
  466. function keepPlaying() {
  467. var keepPlayingButton = document.getElementsByClassName('keep-playing-button')[0];
  468. keepPlayingButton.click();
  469. }
  470. /**
  471. * Gets the current score from the DOM.
  472. * @return current score of game.
  473. */
  474. function getScore() {
  475. var scoreContainer = document.getElementsByClassName('score-container')[0];
  476. return parseInt(scoreContainer.innerHTML);
  477. }
  478. /**
  479. * Gets the best score from the DOM.
  480. * @return best score in all games.
  481. */
  482. function getBestScore() {
  483. var bestContainer = document.getElementsByClassName('best-container')[0];
  484. return parseInt(bestContainer.innerHTML);
  485. }
  486. }shuru();

comments powered by Disqus