NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name halla_bot // @namespace halla_bot // @description Bot for the game 2048 // @version 0.2.0 // @grant none // @include https://oispahalla.com/ // @match https://oispahalla.com/ // @updateURL https://openuserjs.org/meta/jonnelafin/halla_bot.meta.js // @downloadURL https://openuserjs.org/install/jonnelafin/halla_bot.user.js // @copyright 2021, jonnelafin (https://openuserjs.org/users/jonnelafin) // @license MIT // ==/UserScript== var UP = 0; var RIGHT = 1; var DOWN = 2; var LEFT = 3; var MOVES = [UP, RIGHT, DOWN, LEFT]; var MAXWEIGHT = 16 var DESIRED_DEPTH = 6 // Generate empty matrix (square) function generate_matrix(size) { var matrix = []; for(var i=0; i<size; i++) { matrix[i] = []; for(var j=0; j<size; j++) { matrix[i][j] = 0; } } return matrix; } // Create a new matrix with the same values function copy_matrix(matrix, size) { var new_matrix = []; for(var i=0; i<size; i++) { new_matrix[i] = []; for(var j=0; j<size; j++) { new_matrix[i][j] = matrix[i][j]; } } return new_matrix; } // Checks whether the two matrix are equal function equals_matrix(m1, m2, size) { for(var i=0; i<size; i++) { for(var j=0; j<size; j++) { if (m1[i][j] != m2[i][j]) { return false; } } } return true; } // Refresh matrix with raw cells function refresh_matrix(matrix, raw_cells, size) { for(var i=0; i<size; i++) { for(var j=0; j<size; j++) { if (raw_cells[j][i]) { matrix[i][j] = raw_cells[j][i].value; } else { matrix[i][j] = 0; } } } } // Refresh with game data function refresh(matrix, game_manager, size) { var raw_cells = eval('('+game_manager.storageManager.storage.gameState+')').grid.cells; refresh_matrix(matrix, raw_cells, size); } // Is the game over? function over(game_manager) { return eval('('+game_manager.storageManager.storage.gameState+')').over; } // Get the current best possible move function get_best_move(matrix, size) { var best = get_best_move_aux(matrix, size, DESIRED_DEPTH); console.log(best[0]); console.log(best[2]); return best[1]; } // Recursive function for getting the best move function get_best_move_aux(matrix, size, depth) { var best_move = Math.floor(Math.random()*4); var best_matrix = simulate_move(matrix, size, move); var best_weight = 0; if (equals_matrix(best_matrix, matrix, size)) { best_weight = MAXWEIGHT + 1; // do not play that move } else { best_weight = get_weight(best_matrix, size); } for (var i=0; i<4; i++) { var move = MOVES[i]; var matrix_move = simulate_move(matrix, size, move); var weight = MAXWEIGHT + 1; if (equals_matrix(matrix_move, matrix, size)) { continue; // do not play that move } if (depth == 0) { weight = get_weight(matrix_move, size); } else { weight = get_best_move_aux(matrix_move, size, depth - 1)[2]; } if (weight < best_weight) { best_weight = weight; best_matrix = matrix_move; best_move = move; } } return [best_matrix, best_move, best_weight]; } // Generate the new matrix with the given move function simulate_move(matrix, size, move) { var new_matrix = copy_matrix(matrix, size); for (var i = 0; i < size; i++) { var pos = 0; var value = 0; for (var j = 0; j < size; j++) { var new_value = get_at(new_matrix, i, j, move); if (new_value == 0) { // Nothing to do here } else if (value == new_value) { set_at(new_matrix, i, pos, move, 2*value); pos++; value = 0; } else { if (value == 0) { set_at(new_matrix, i, pos, move, new_value); } else { //Was not able to merge. pos++; } value = new_value; } } var start = pos+1; if (value == 0) { start = pos; } for (var j = start; j < size; j++) { set_at(new_matrix, i, j, move, 0); } } return new_matrix; } // Get the real position according to move function get_pos(i, j, move) { var pos = [i, j]; if (move == RIGHT) { pos[1] = 3 - pos[1]; } else if (move == DOWN) { var tmp = pos[1]; pos[1] = 3 - pos[0]; pos[0] = 3 - tmp; } else if (move == UP) { var tmp = pos[1]; pos[1] = pos[0]; pos[0] = tmp; } return pos; } // Set the value to the real pos function set_at(matrix, i, j, move, value) { var pos = get_pos(i, j, move); matrix[pos[0]][pos[1]] = value; } // Set the value to the real pos function get_at(matrix, i, j, move) { var pos = get_pos(i, j, move); return matrix[pos[0]][pos[1]]; } // Get the weight of the matrix. Metric to be able to compare solutions. function get_weight(matrix, size) { var weight = 0; var cell_map = {}; for(var i=0; i<size; i++) { for(var j=0; j<size; j++) { if (matrix[i][j] != 0) { weight++; } } } return weight; } function loop() { if (!over(gm)) { refresh(grid, gm, size); gm.move(get_best_move(grid, size)); } } // Play mode (greasemonkey) function run() { console.log("### Running 2048 bot ###"); size = 4; gm = new GameManager(size, KeyboardInputManager, HTMLActuator, LocalStorageManager); grid = generate_matrix(size); setInterval(loop, 250); } //---------------------------------------------------------------------- //---TESTING------------------------------------------------------------ function function_name(fun) { var ret = fun.toString(); ret = ret.substr('function '.length); ret = ret.substr(0, ret.indexOf('(')); return ret; } function assert_matrix(in_m, out_m, exp_m, arg, size) { if (!equals_matrix(out_m, exp_m, size)) { console.log("*** Test failed for data", in_m, "and argument", arg, "expecting", exp_m, "returned", out_m); return 1; } return 0; } // Test simulate_move function. Returns number of failed tests. function test_simulate_move() { var count = 0; var ref = [[0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 2, 0], [0, 0, 0, 0]]; var one_move = [[[0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 2, 2, 0]], [[0, 0, 0, 0], [2, 0, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0]]]; var multi_moves = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [4, 0, 0, 0]]; var multi_merge = [[2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2]]; var multi_merge_down = [[0, 0, 0, 0], [0, 0, 0, 0], [4, 4, 4, 4], [4, 4, 4, 4]]; var multi_merge_up = [[8, 8, 8, 8], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; var multi_merge_left = [[16, 16, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; var multi_merge_right = [[0, 0, 0, 32], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; for (var move in MOVES) { // Test one move var test_matrix = simulate_move(ref, 4, move); count += assert_matrix(ref, test_matrix, one_move[move], move, 4); // Test the same move (should not change anything) var second_test_matrix = simulate_move(test_matrix, 4, move); count += assert_matrix(ref, second_test_matrix, one_move[move], move, 4); } var test1_matrix = simulate_move(ref, 4, UP); var test2_matrix = simulate_move(test1_matrix, 4, LEFT); var test3_matrix = simulate_move(test2_matrix, 4, DOWN); count += assert_matrix(0, test3_matrix, multi_moves, 0, 4); var moves = [DOWN, UP, LEFT, RIGHT]; var multi_merge_array = [multi_merge_down, multi_merge_up, multi_merge_left, multi_merge_right]; var curr_mat = multi_merge; for (var i=0; i<4; i++) { var test_matrix = simulate_move(curr_mat, 4, moves[i]); count += assert_matrix(curr_mat, test_matrix, multi_merge_array[i], moves[i], 4); curr_mat = multi_merge_array[i]; } return count; } // Test function test_get_best_move_depth_1() { var count = 0; var ref1 = [[2, 2, 2, 2], [2, 2, 2, 2], [0, 0, 0, 0], [0, 0, 0, 0]]; var weight1 = get_weight(get_best_move_aux(ref1, 4, 0)[0], 4); if (weight1 != 4) { console.log("*** Test failed for", ref1, "and depth", 0, "returned weight of", weight1, "instead of", 4); count++; } var weight2 = get_best_move_aux(ref1, 4, 1)[2]; if (weight2 != 2) { console.log("*** Test failed for", ref1, "and depth", 1, "returned weight of", weight2, "instead of", 2); count++; } return count; } // Test mode (with node.js) function test() { console.log("### Testing 2048 bot functions"); var functions = [test_simulate_move, test_get_best_move_depth_1]; var failed_tests = 0; for (var i=0; i<functions.length; i++) { var curr_failed_tests = functions[i](); if (curr_failed_tests != 0) { console.log("===", curr_failed_tests, "test(s) failed for", function_name(functions[i])); } else { console.log("=== All tests for", function_name(functions[i]), "passed"); } failed_tests += curr_failed_tests; } if (failed_tests != 0) { console.log("###", failed_tests, "test(s) failed in overall"); } else { console.log("### All tests passed"); } return failed_tests; } // Main function function main() { // If running with node, this is for tests if (typeof process != "undefined") { var failed_tests = test(); //process.exit(code = failed_tests); } else { run(); } } main();