Initial commit

This commit is contained in:
Reimar 2025-04-12 13:41:28 +02:00
commit 6f358ec977
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
6 changed files with 686 additions and 0 deletions

103
generator.js Normal file
View File

@ -0,0 +1,103 @@
function myFunction(elmnt) {
console.log(elmnt.style.backgroundColor);
if (elmnt.style.backgroundColor == "white") {
elmnt.style.backgroundColor = "hsl(120, 100%, 75%)";
} else {
elmnt.style.backgroundColor = "white";
}
}
var værdi;
var celle;
function nyfunktion(værdi, celle) {
celle.innerHTML = værdi;
}
function gen_int(i) {
var number = Math.floor(Math.random() * 10 + i * 10);
while (number === 0) {
var number = Math.floor(Math.random() * 10 + i * 10);
}
if (i === 8) {
var number = Math.floor(Math.random() * 11 + 80);
}
if (i === 100) {
var number = Math.ceil(Math.random() * 9);
}
return number;
}
function hasDuplicates(array) {
return new Set(array).size !== array.length;
}
function generate_col(i) {
var col = Array.from({ length: 3 }, () => gen_int(i));
while (hasDuplicates(col) == true) {
var col = Array.from({ length: 3 }, () => gen_int(i));
}
return col.sort();
}
function generate_row() {
var row = Array.from({ length: 5 }, () => gen_int(100));
while (hasDuplicates(row) == true) {
var row = Array.from({ length: 5 }, () => gen_int(100));
}
return row.sort();
}
function contains_digits(rows) {
var concat = rows[0].concat(rows[1], rows[2]);
for (var i = 1; i < 9; i++) {
if (concat.indexOf(i) === -1) {
return false;
}
}
if (concat.indexOf(9) === -1) {
return false;
}
return true;
}
function generate_rows_check() {
console.log(rows);
var rows = new Array(generate_row(), generate_row(), generate_row());
while (!contains_digits(rows)) {
var rows = new Array(generate_row(), generate_row(), generate_row());
}
return rows;
}
function update_plates() {
for (var x2 = 1; x2 <= 3; x2++) {
for (var x3 = 1; x3 <= 9; x3++) {
var celle = "p1" + String(x2) + String(x3);
document.getElementById(celle).innerHTML = "";
}
}
var dict = {};
for (var n = 1; n <= 3; n++) {
var cols = [];
for (var i = 0; i < 9; i++) {
var col = generate_col(i);
cols.push(col);
}
var rows_choose = generate_rows_check();
var chosen_row;
for (var j = 0; j < 3; j++) {
for (var i = 0; i < rows_choose[j].length; i++) {
var k = rows_choose[j][i];
var celle = "p" + String(n) + String(j + 1) + String(k);
dict[celle] = cols[k - 1][j];
}
}
}
for (var key in dict) {
var value = dict[key];
nyfunktion(value, document.getElementById(key));
}
}

61
index.html Normal file
View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MAGS Banko Cheat</title>
<link rel="stylesheet" href="style.css">
<script defer src="main.js"></script>
</head>
<body>
<h1>Banko Cheat</h1>
<main>
<div>
<fieldset class="input-container">
<legend>Win type</legend>
<label>
<input type="radio" name="win-type" value="row" checked>
One row
</label>
<label>
<input type="radio" name="win-type" value="two-rows">
Two rows
</label>
<label>
<input type="radio" name="win-type" value="full">
Full board
</label>
</fieldset>
<fieldset class="input-container">
<legend>Workers</legend>
<input id="workers" type="number" min="1" value="1">
</fieldset>
</div>
<div>
<fieldset id="numbers-container" class="input-container">
<legend>Numbers</legend>
<input type="number" min="1" max="90">
</fieldset>
</div>
<div id="results">
<button id="pause">Pause</button>
&emsp;
<b>
<span id="attempts">--</span> plates tried &emsp;
(<span id="speed">--</span>/sec)
</b>
</div>
<fieldset>
<legend>Log</legend>
<div id="log"></div>
</fieldset>
</main>
</body>
</html>

134
main.js Normal file
View File

@ -0,0 +1,134 @@
let paused = false;
let workers = [];
let totalTime = 0;
let totalAttempts = 0;
function showStats() {
document.getElementById("attempts").innerText = totalAttempts;
document.getElementById("speed").innerText = (totalAttempts / totalTime).toFixed(2);
}
function log(text, workerId) {
const time = new Date().toLocaleTimeString("en-GB");
let html = `<time>${time}</time> `;
if (workerId) html += `<span class="worker-id">[${workerId}]</span> `;
html += `${text}<br>`;
document.getElementById("log").innerHTML += html;
document.getElementById("log").scrollTo(0, document.getElementById("log").scrollHeight);
}
function resetWorkers() {
log("Terminating workers");
for (const worker of workers) {
worker.postMessage({ type: "terminate" });
}
totalTime = 0;
totalAttempts = 0;
}
function runWorkers() {
resetWorkers();
const amount = document.getElementById("workers").valueAsNumber;
log(`Creating ${amount} workers`);
for (let i = 0; i < amount; i++) {
const worker = new Worker("worker.js");
worker.addEventListener("message", message => {
switch (message.data.type) {
case "ready":
if (!paused) worker.postMessage({ type: "run" });
break;
case "stats":
totalTime += message.data.time;
totalAttempts += message.data.attempts;
showStats();
break;
case "log":
log(message.data.text, message.data.id);
break;
case "terminate":
worker.terminate();
workers.splice(workers.indexOf(worker), 1);
break;
}
});
worker.postMessage({ type: "init", id: i + 1 });
workers.push(worker);
}
}
document.getElementById("numbers-container").addEventListener("keydown", event => {
// Add new input field on enter
if (event.key === "Enter") {
const input = document.createElement("input");
input.type = "number";
input.min = 1;
input.max = 90;
event.target.parentElement.appendChild(input);
input.focus();
return;
}
// Remove empty input field on backspace
if (event.key === "Backspace" || event.key === "Delete") {
if (event.target.value !== "" || document.querySelectorAll("#numbers-container input").length < 2)
return;
if (event.target.previousElementSibling.localName === "input")
event.target.previousElementSibling.focus();
else
event.target.nextElementSibling.focus();
event.target.parentElement.removeChild(event.target);
event.preventDefault();
return;
}
// Move between inputs on arrow up/down
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
event.preventDefault();
const elem = event.key === "ArrowUp"
? event.target.previousElementSibling
: event.target.nextElementSibling;
if (!elem || elem.localName !== "input") return;
elem.focus();
return;
}
// Disallow non-digits
const allowed = [/^Digit/, /^Arrow/, /Tab/];
if (!event.altKey && !event.ctrlKey && !allowed.some(regex => regex.test(event.code))) {
event.preventDefault();
}
});
document.getElementById("pause").addEventListener("click", event => {
paused = !paused;
for (const worker of workers) {
worker.postMessage({ type: paused ? "stop" : "run" });
}
event.target.innerText = paused ? "Start" : "Pause";
});
document.getElementById("workers").value = navigator.hardwareConcurrency ?? 1;
for (const container of document.getElementsByClassName("input-container")) {
container.addEventListener("change", runWorkers);
}

236
seedrandom.js Normal file
View File

@ -0,0 +1,236 @@
(function (global, pool, math, width, chunks, digits, module, define, rngname) {
//
// The following constants are related to IEEE 754 limits.
//
var startdenom = math.pow(width, chunks),
significance = math.pow(2, digits),
overflow = significance * 2,
mask = width - 1,
nodecrypto;
//
// seedrandom()
// This is the seedrandom function described above.
//
var impl = (math["seed" + rngname] = function (seed, options, callback) {
var key = [];
options = options == true ? { entropy: true } : options || {};
// Flatten the seed string or build one from local entropy if needed.
var shortseed = mixkey(
flatten(
options.entropy
? [seed, tostring(pool)]
: seed == null
? autoseed()
: seed,
3
),
key
);
// Use the seed to initialize an ARC4 generator.
var arc4 = new ARC4(key);
// Mix the randomness into accumulated entropy.
mixkey(tostring(arc4.S), pool);
// Calling convention: what to return as a function of prng, seed, is_math.
return (
options.pass ||
callback ||
// If called as a method of Math (Math.seedrandom()), mutate Math.random
// because that is how seedrandom.js has worked since v1.0. Otherwise,
// it is a newer calling convention, so return the prng directly.
function (prng, seed, is_math_call) {
if (is_math_call) {
math[rngname] = prng;
return seed;
} else return prng;
}
)(
// This function returns a random double in [0, 1) that contains
// randomness in every bit of the mantissa of the IEEE 754 value.
function () {
var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48
d = startdenom, // and denominator d = 2 ^ 48.
x = 0; // and no 'extra last byte'.
while (n < significance) {
// Fill up all significant digits by
n = (n + x) * width; // shifting numerator and
d *= width; // denominator and generating a
x = arc4.g(1); // new least-significant-byte.
}
while (n >= overflow) {
// To avoid rounding up, before adding
n /= 2; // last byte, shift everything
d /= 2; // right using integer math until
x >>>= 1; // we have exactly the desired bits.
}
return (n + x) / d; // Form the number within [0, 1).
},
shortseed,
"global" in options ? options.global : this == math
);
});
//
// ARC4
//
// An ARC4 implementation. The constructor takes a key in the form of
// an array of at most (width) integers that should be 0 <= x < (width).
//
// The g(count) method returns a pseudorandom integer that concatenates
// the next (count) outputs from ARC4. Its return value is a number x
// that is in the range 0 <= x < (width ^ count).
//
/** @constructor */
function ARC4(key) {
var t,
keylen = key.length,
me = this,
i = 0,
j = (me.i = me.j = 0),
s = (me.S = []);
// The empty key [] is treated as [0].
if (!keylen) {
key = [keylen++];
}
// Set up S using the standard key scheduling algorithm.
while (i < width) {
s[i] = i++;
}
for (i = 0; i < width; i++) {
s[i] = s[(j = mask & (j + key[i % keylen] + (t = s[i])))];
s[j] = t;
}
// The "g" method returns the next (count) outputs as one number.
(me.g = function (count) {
// Using instance members instead of closure state nearly doubles speed.
var t,
r = 0,
i = me.i,
j = me.j,
s = me.S;
while (count--) {
t = s[(i = mask & (i + 1))];
r =
r * width + s[mask & ((s[i] = s[(j = mask & (j + t))]) + (s[j] = t))];
}
me.i = i;
me.j = j;
return r;
// For robust unpredictability, the function call below automatically
// discards an initial batch of values. This is called RC4-drop[256].
// See http://google.com/search?q=rsa+fluhrer+response&btnI
})(width);
}
//
// flatten()
// Converts an object tree to nested arrays of strings.
//
function flatten(obj, depth) {
var result = [],
typ = typeof obj,
prop;
if (depth && typ == "object") {
for (prop in obj) {
try {
result.push(flatten(obj[prop], depth - 1));
} catch (e) {}
}
}
return result.length ? result : typ == "string" ? obj : obj + "\0";
}
//
// mixkey()
// Mixes a string seed into a key that is an array of integers, and
// returns a shortened string seed that is equivalent to the result key.
//
function mixkey(seed, key) {
var stringseed = seed + "",
smear,
j = 0;
while (j < stringseed.length) {
key[mask & j] =
mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++));
}
return tostring(key);
}
//
// autoseed()
// Returns an object for autoseeding, using window.crypto if available.
//
/** @param {Uint8Array|Navigator=} seed */
function autoseed(seed) {
try {
if (nodecrypto) return tostring(nodecrypto.randomBytes(width));
global.crypto.getRandomValues((seed = new Uint8Array(width)));
return tostring(seed);
} catch (e) {
return [
+new Date(),
global,
(seed = global.navigator) && seed.plugins,
global.screen,
tostring(pool),
];
}
}
//
// tostring()
// Converts an array of charcodes to a string
//
function tostring(a) {
return String.fromCharCode.apply(0, a);
}
//
// When seedrandom.js is loaded, we immediately mix a few bits
// from the built-in RNG into the entropy pool. Because we do
// not want to interfere with deterministic PRNG state later,
// seedrandom will not call math.random on its own again after
// initialization.
//
mixkey(math[rngname](), pool);
//
// Nodejs and AMD support: export the implementation as a module using
// either convention.
//
if (module && module.exports) {
module.exports = impl;
try {
// When in node.js, try using crypto package for autoseeding.
nodecrypto = require("crypto");
} catch (ex) {}
} else if (define && define.amd) {
define(function () {
return impl;
});
}
//
// Node.js native crypto support.
//
// End anonymous scope, and pass initial values.
})(
this, // global window object
[], // pool: entropy pool starts empty
Math, // math: package containing random, pow, and seedrandom
256, // width: each RC4 output is 0 <= x < 256
6, // chunks: at least six RC4 outputs for each double
52, // digits: there are 52 significant digits in a double
typeof module == "object" && module, // present in node.js
typeof define == "function" && define, // present with an AMD loader
"random" // rngname: name for Math.random and Math.seedrandom
);

45
style.css Normal file
View File

@ -0,0 +1,45 @@
body {
margin: 2rem;
}
* {
box-sizing: border-box;
}
input[type=number] {
width: 150px;
}
main {
display: flex;
gap: 1rem;
}
.input-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
background-color: #EEE;
margin: 0.5rem;
}
#results {
margin-top: 1rem;
width: 400px;
}
#log {
height: 200px;
width: 400px;
font-family: monospace;
overflow-y: scroll;
}
#log time {
color: #BDBDBD;
}
#log .worker-id {
color: #2E7D32;
}

107
worker.js Normal file
View File

@ -0,0 +1,107 @@
importScripts("seedrandom.js");
importScripts("generator.js");
console.log("worker");
let workerId;
let currentInput = "";
let attempts = 0;
let startedAt;
let statsInterval;
let guessInterval;
function getNumbers(name) {
Math.seedrandom(name);
let numbers = [];
for (let i = 0; i < 9; i++) {
numbers = numbers.concat(generate_col(i));
}
const activatedCols = generate_rows_check();
const activatedNumbers = [];
for (let row = 0; row < 3; row++) {
for (const col of activatedCols[row]) {
activatedNumbers.push(numbers[(col - 1) * 3 + row]);
}
}
return activatedNumbers;
}
function sendStats() {
if (!startedAt) return;
self.postMessage({ type: "stats", attempts, time: Date.now() - startedAt });
resetStats();
}
function resetStats() {
attempts = 0;
startedAt = Date.now();
}
function generateNextInput(input) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
if (input.length === 0)
return alphabet[0];
for (let i = input.length - 1; i >= 0; i--) {
const index = alphabet.indexOf(input[i]);
if (index < alphabet.length - 1) {
return input.substring(0, i) + alphabet[index + 1] + alphabet[0].repeat(input.length - i - 1);
}
}
return alphabet[0] + input;
}
function guess() {
currentInput = generateNextInput(currentInput);
attempts++;
// TODO
}
function run() {
statsInterval = setInterval(sendStats, 1000);
guessInterval = setInterval(guess, 0);
resetStats();
self.postMessage({ type: "log", text: "Starting worker", id: workerId });
}
function stop() {
clearInterval(statsInterval);
clearInterval(guessInterval);
sendStats();
self.postMessage({ type: "log", text: "Stopping worker", id: workerId });
}
self.onmessage = message => {
switch (message.data.type) {
case "init":
workerId = message.data.id;
self.postMessage({ type: "ready" });
break;
case "run":
run();
break;
case "stop":
stop();
break;
case "terminate":
stop();
self.postMessage({ type: "terminate" });
break;
}
};