Skip to content

Instantly share code, notes, and snippets.

@Bloody-Badboy
Last active October 4, 2023 09:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bloody-Badboy/b956cbcacd337252548cb80fc55213cc to your computer and use it in GitHub Desktop.
Save Bloody-Badboy/b956cbcacd337252548cb80fc55213cc to your computer and use it in GitHub Desktop.
(function () {
"use strict";
if (window.Solver) {
return;
}
var Solver = (window.Solver = {});
Solver.LEFT = 37;
Solver.UP = 38;
Solver.RIGHT = 39;
Solver.DOWN = 40;
Solver.DIRECTIONS = [Solver.LEFT, Solver.UP, Solver.RIGHT, Solver.DOWN];
Solver.init = function () {
Solver.initUI();
Solver.initLookup();
};
Solver.initUI = function () {
if (document.getElementById("solver-buttons")) {
return;
}
var divStyle = [
"margin-bottom: 12px",
"display: flex",
"justify-content: center",
"align-items: center",
"gap: 12px",
].join(";");
var div = document.createElement("div");
div.id = "solver-buttons";
div.style.cssText = divStyle;
var buttonStyle = [
"padding: 9px 18px",
"border-radius: 18px",
"border: none",
"cursor: pointer",
"text-decoration: none",
"background-color: black",
"color: white",
"line-height: 18px",
"font-size: 14px",
].join(";");
div.innerHTML = [
`<button onclick="Solver.start()" style="${buttonStyle}">Start</button>`,
`<button onclick="Solver.stop()" style="${buttonStyle}">Stop</button>`,
].join("");
var board = document.getElementById("board");
board.parentNode.insertBefore(div, board);
};
function fireKeyboardEvent(type, code) {
var evt = document.createEvent("KeyboardEvent");
if (evt.initKeyEvent) {
evt.initKeyEvent(
type,
true,
true,
document.defaultView,
false,
false,
false,
false,
code,
code
);
} else if (evt.initKeyboardEvent) {
evt.initKeyboardEvent(
type,
true,
true,
document.defaultView,
code,
code,
false,
false,
false,
false,
false
);
}
Object.defineProperty(evt, "keyCode", {
get: function () {
return code;
},
});
Object.defineProperty(evt, "which", {
get: function () {
return code;
},
});
document.documentElement.dispatchEvent(evt);
}
function setRandomInterval(intervalFunction, minDelay, maxDelay) {
let timeout;
const runInterval = () => {
const timeoutFunction = () => {
intervalFunction();
runInterval();
};
const delay =
Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
console.log("Delay: " + delay + "ms");
timeout = setTimeout(timeoutFunction, delay);
};
runInterval();
return {
clear() {
clearTimeout(timeout);
},
};
}
Solver.move = function (dir) {
fireKeyboardEvent("keydown", dir);
fireKeyboardEvent("keyup", dir);
};
Solver.readTile = function (x, y) {
const board = document.querySelector(`#board`);
const row = board.querySelector(`.row-${y + 1}`);
const tiles = row.querySelectorAll(".tile");
const tile = tiles[x].querySelector(".value");
if (!tile) return 0;
var table = {
0: 0,
2: 1,
4: 2,
8: 3,
16: 4,
32: 5,
64: 6,
128: 7,
256: 8,
512: 9,
1024: 10,
2048: 11,
4096: 12,
8192: 13,
16384: 14,
32768: 15,
};
return table[parseInt(tile.innerHTML, 10)];
};
Solver.readBoard = function () {
var tiles = [];
for (var i = 0; i < 16; ++i) {
tiles[i] = Solver.readTile(i % 4, Math.floor(i / 4));
}
console.log(tiles);
return tiles;
};
Solver.transformLineLeft = function (line) {
var merged = [];
for (var i = 1; i < 4; ++i) {
var pos = i;
while (line[pos] !== 0 && pos > 0) {
if (line[pos - 1] === 0) {
line[pos - 1] = line[pos];
line[pos] = 0;
--pos;
continue;
}
if (!merged[pos - 1] && line[pos - 1] === line[pos]) {
++line[pos - 1];
line[pos] = 0;
merged[pos - 1] = true;
}
break;
}
}
};
Solver.getLookupIndex = function (a, b, c, d) {
return 4096 * a + 256 * b + 16 * c + d;
};
Solver.initLookup = function () {
Solver.lineLookup = [];
for (var i = 0; i < 1 << 16; ++i) {
var a = (i >> 12) & 0xf,
b = (i >> 8) & 0xf,
c = (i >> 4) & 0xf,
d = i & 0xf;
var line = [a, b, c, d];
Solver.transformLineLeft(line);
if (line[0] !== a || line[1] !== b || line[2] !== c || line[3] !== d) {
var index = Solver.getLookupIndex(a, b, c, d);
Solver.lineLookup[index] = line;
}
}
};
Solver.getTransformOrder = function (dir) {
switch (dir) {
case Solver.LEFT:
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
case Solver.RIGHT:
return [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12];
case Solver.UP:
return [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
case Solver.DOWN:
return [12, 8, 4, 0, 13, 9, 5, 1, 14, 10, 6, 2, 15, 11, 7, 3];
}
};
Solver.transform = function (board, dir) {
var changed = false,
src,
dst;
var order = Solver.getTransformOrder(dir);
for (var i = 0; i < 16; i += 4) {
src = Solver.getLookupIndex(
board[order[i]],
board[order[i + 1]],
board[order[i + 2]],
board[order[i + 3]]
);
dst = Solver.lineLookup[src];
if (dst) {
changed = true;
board[order[i]] = dst[0];
board[order[i + 1]] = dst[1];
board[order[i + 2]] = dst[2];
board[order[i + 3]] = dst[3];
}
}
return changed;
};
Solver.calculateScore = function (board) {
var distances = [0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0];
var score = 0;
var max = Math.max.apply(null, board);
for (var i = 0; i < board.length; ++i) {
var scale = board[i] / max;
score = score + (board[i] === 0 ? 1 : 0) - scale * distances[i];
}
// monotonicity
function isMonotone(a, b, c, d) {
return (a <= b && b <= c && c <= d) || (a >= b && b >= c && c >= d);
}
var mono = 0;
for (var o = 0; o < 4; ++o) {
if (isMonotone(board[o], board[o + 4], board[o + 8], board[o + 12])) {
++mono;
}
var y = o * 4;
if (isMonotone(board[y], board[y + 1], board[y + 2], board[y + 3])) {
++mono;
}
}
score += mono / 4;
return score;
};
Solver.pickDirection = function (board, levels) {
var result = { direction: Solver.LEFT, score: -Infinity };
Solver.DIRECTIONS.forEach(function (dir) {
var b = board.slice();
var changed = Solver.transform(b, dir);
if (!changed) {
return;
}
var score = Solver.calculateScore(b);
if (levels > 0) {
score += Solver.pickDirection(b, levels - 1).score * 0.5;
}
if (score > result.score) {
result.direction = dir;
result.score = score;
}
});
return result;
};
Solver.next = function () {
var result = Solver.pickDirection(Solver.readBoard(), 6);
Solver.move(result.direction);
};
var intval = null;
Solver.start = function () {
if (!intval) {
intval = setRandomInterval(Solver.next, 3000, 5000);
}
};
Solver.stop = function () {
if (intval) {
intval.clear();
}
intval = null;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment