BvS WK Sync

Attempts to solve World Kaiju sync.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
}