BvS WK Sync

Attempts to solve World Kaiju sync.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           BvS WK Sync
// @namespace      BvS
// @version	   1.3
// @history        1.3 New domain - animecubedgaming.com - Channel28
// @history        1.2 Now https compatible (Updated by Channel28)
// @history        1.1 Added grant permissions (Updated by Channel28)
// @history        1.0 Initial Release
// @description    Attempts to solve World Kaiju sync.
// @include        http*://*animecubed.com/billy/bvs/worldkaiju-group.html
// @include        http*://*animecubedgaming.com/billy/bvs/worldkaiju-group.html
// @licence        MIT; http://www.opensource.org/licenses/mit-license.php
// @copyright      2010, Daniel Karlsson
// @grant          GM_log
// ==/UserScript==

const TIMELIMIT = 5000; // ms

var options = document.evaluate("//form[@name='groupcheck']/select[@name='c1']/option",
	document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

var colours = [];
for (var i = 0; i < options.snapshotLength; i++) {
	var value = parseInt(options.snapshotItem(i).value);
	var colour = options.snapshotItem(i).textContent;
	colours[value] = colour;
}

function find(val, arr)
{
	for (var i in arr)
		if (arr[i] == val)
			return i;
	return -1;
}
function print(arr)
{
	var xarr = [];
	for (var i in arr)
		xarr.push(colours[arr[i]]);
	return xarr.join(",");
}
	
function Mastermind(pegs, colours)
{
	var my = this;
	
	my.colours = colours;
	my.pegs = pegs;
	my.prevGuesses = [];
	
	// Create array of all possible combinations
	my.combinations = [];
	for (var i = 0; i < Math.pow(my.colours, my.pegs); i++) {
		var n = i;
		var comb = [];
		for (var p = 0; p < my.pegs; p++) {
			comb.push(n % my.colours);
			n = Math.floor(n / my.colours);
		}
		my.combinations.push(comb);
	}
	
	my.appendGuess = function(g, s) {
		my.prevGuesses.push({guess: g, score: s});
	}
	
	// Determine score as [<correct colour, correct position>, <correct colour, wrong position>]
	my.score = function(guess, board) {
		var boardColours = [];
		var guessColours = [];
		
		for (var i = 0; i < my.colours; i++) {
			boardColours.push(0);
			guessColours.push(0);
		}

		var correctPosition = 0;
		for (var i = 0; i < my.pegs; i++) {
			if (board[i] == guess[i])
				correctPosition++;
			else {
				boardColours[board[i]]++;
				guessColours[guess[i]]++;
			}
		}

		var correctColour = 0;
		for (var c = 0; c < my.colours; c++)
			correctColour += Math.min(guessColours[c], boardColours[c]);

		return [correctPosition, correctColour];
	}
	
	my.possibleScores = [];
	for (var c = 0; c <= my.pegs; c++)
		for (var p = 0; p <= my.pegs - c; p++)
			my.possibleScores.push([p, c]);

	// Remove combinations based on guess and score
	my.eliminateCombinations = function(guess, score, splice) {
		var keepers = [];
		var removals = 0;
		for (var c in my.combinations) {
			var s = my.score(guess, my.combinations[c]);
			if (s[0] == score[0] && s[1] == score[1]) {
				if (splice)
					keepers.push(my.combinations[c]);
			} else {
				removals++;
			}
		}
		if (splice)
			my.combinations = keepers;
		return removals;
	}
	
	// Calculate minimum number of eliminations
	my.guessScore = function(guess) {
		var score = my.combinations.length + 1;
		for (var s in my.possibleScores) {
			var removals = my.eliminateCombinations(guess, my.possibleScores[s], false);
			score = Math.min(removals, score);
		}
		return score;
	}
	
	// Estimate time of bsetGuess brute force method
	my.estimateTime = function() {
		var t1 = new Date();
		var score = my.guessScore(my.combinations[0]);
		var t2 = new Date();
		var t = t2.getTime() - t1.getTime();
		return t * my.combinations.length;
	}
	
	// Brute force search of all possibilities
	my.bestGuess = function() {
		var bestGuess;
		var bestScore = -1;
		for (var guess in my.combinations) {
			var score = my.guessScore(my.combinations[guess]);
			if (score > bestScore) {
				bestScore = score;
				bestGuess = my.combinations[guess];
			}
		}
		my.bestScore = bestScore;
		my.bestGuess = bestGuess;
		return bestGuess;
	}

	// Try random guesses until we run out of time
	my.fastGuess = function(limit) {
		var t1 = new Date();
		t1 = t1.getTime();
		var bestGuess;
		var bestScore = -1;
		var time = 0;
		var tried = [];
		do {
			var guess;
			guess = Math.floor(Math.random() * my.combinations.length);
			while (find(guess, tried) >= 0)
				guess = (guess + 1) % my.combinations.length;
			tried.push(guess);
			
			var score = my.guessScore(my.combinations[guess]);
			if (score > bestScore) {
				bestScore = score;
				bestGuess = my.combinations[guess];
			}
			var t2 = new Date();
			time = t2.getTime() - t1;

			if (tried.length >= my.combinations.length)
				break;
		} while (time < limit)
		my.bestScore = bestScore;
		my.bestGuess = bestGuess;
		return bestGuess;
	}
}

var form = document.evaluate("//form[@name='groupcheck']",
	document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null);
if (form) {
	form = form.singleNodeValue;
	var guesses = document.evaluate("//table/tbody/tr[count(descendant::td)=5]",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
		
	var prevGuesses = 0;
	var board = new Mastermind(4, colours.length);

	// Legacy hint
	if (/You have a strong feeling that the first Chakra isn.t (\w+)/.test(form.textContent)) {
		GM_log("Legacy: " + RegExp.lastParen);
		var chakra = find(RegExp.lastParen, colours);
		
		if (chakra >= 0) {
			keepers = [];
			for (var i in board.combinations)
				if (board.combinations[i][0] != chakra)
					keepers.push(board.combinations[i]);
			GM_log("Kept " + keepers.length + " / " + board.combinations.length);
			board.combinations = keepers;
		}
	}

	for (var g = 0; g < guesses.snapshotLength; g++) {
		var tds = guesses.snapshotItem(g).getElementsByTagName("td");
		var guess = [];
		var res = []
		for (var i = 0; i < 4; i++) {
			var col = tds[i].textContent;
			if (find(col, colours))
				guess.push(find(col, colours));
			else
				continue;
		}
		var match = tds[4].textContent.match(/(\d+)[^\d]+(\d+)/);
		if (match && guess.length == 4) {
			res = [parseInt(match[1]), parseInt(match[2])];
			board.appendGuess(guess, res);
			board.eliminateCombinations(guess, res, true);
			prevGuesses++;
		}
	}
	
	var div = document.createElement("div");
	form.parentNode.insertBefore(div, form.nextSibling);
	div.textContent = "Searching...";
	setTimeout(function() {
		var limited = false;
		var bestGuess;
		if (board.estimateTime() > TIMELIMIT) {
			bestGuess = board.fastGuess(TIMELIMIT);
			limited = true;
		} else
			bestGuess = board.bestGuess();

		if (board.combinations.length > 0) {
			if (limited)
				div.textContent = "Guess (" + Math.round(TIMELIMIT / 1000) + " s search): " + print(bestGuess);
			else
				div.textContent = "Best guess: " + print(bestGuess);
		}
	}, 100);
}