BvS Candyween Helper

Calculates probabilities of each choice in Candyween houses

// ==UserScript==
// @name           BvS Candyween Helper
// @namespace      Garyzx
// @description    Calculates probabilities of each choice in Candyween houses
// @include        http*://*animecubed.com/billy/bvs/villagecandyween.html
// @include        http*://*animecubedgaming.com/billy/bvs/villagecandyween.html
// @version        1.08
// @history        1.08 New domain - animecubedgaming.com - Channel28
// @history        1.07 Now https compatible (Updated by Channel28)
// @history        1.06 Recognise Retail Power (thanks to Terrec)
// @history        1.05 Displays chances without visiting each house
// @history        1.04 Display data at the end, even if ads are turned off (thanks Thosha!)
// @history        1.04 Update probabilities
// @history        1.03 Replace eval() with closures to satisfy Greasemonkey restrictions (thanks to portwizard for finding the problem)
// @history        1.02 Remove debugging output (oops)
// @history        1.01 Auto-select first unvisited house
// @history        1.01 Log Party/Psycho data and format data for forums
// @history        1.01 Allow Candying at Party houses
// @history        1.00 Initial version
// @grant          none
// ==/UserScript==

var exts={};
exts["Decrepit"]="old";
exts["Creepy"]="creepy";
exts["Typical"]="typical";
exts["Spooky"]="spooky";
exts["Bright"]="silly";

var ints={};
ints["Candy"]=["big handfuls", "just giving", "tasty candy"];
ints["Party"]=["rocking party", "Come on in", "new dance"];
ints["Psycho"]=["wangly eye", "claw your way", "trigger a trapdoor"];

var cond={};
cond["Candy"]={Decrepit: 678, Creepy: 766, Typical: 700, Spooky: 691, Bright: 763};
cond["Party"]={Decrepit: 517, Creepy: 476, Typical: 478, Spooky: 444, Bright: 0};
cond["Psycho"]={Decrepit: 0, Creepy: 173, Typical: 165, Spooky: 141, Bright: 167};

var actions={};
actions["Party"]=["Hit On Somebody", "Rock Out", "Mope", "Tell Funny Stories"];
actions["Psycho"]=["Hide In The Closet", "Get An Axe", "Burn the Place Down", "Scream and Cower"];

function normalize(obj){
	var total=0;
	for(var i in obj)
		total+=obj[i];
	for(var i in obj)
		obj[i]/=total;
}

function get(key, def){
	var val;
	if(val=localStorage.getItem("candyween."+key))
		return val;
	return def;
}

function set(key, val){
	localStorage.setItem("candyween."+key, val)
}

var data=get("data", ",;,;,;,;,;,;,;,;,;,").split(";");
for(var n=0; n<10; n++)
	data[n]=data[n].split(",");

if(document.body.innerHTML.match("Visit a House")){
   var table_items = document.evaluate("//form[@name='cw1']/table/tbody/tr/td", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
   for (var i = 0; i < 10; i++){
      for(var n in exts)
         if(table_items.snapshotItem(i).innerHTML.match(exts[n]))
            data[i][0]=n;
   }
   var retail = document.body.textContent.match(/House #(\d+) is a Candy/);
   if(retail) {
      data[retail[1]-1][1]="Candy";
   }
}

if(document.body.innerHTML.match(/>House #[0-9]+<\/font>/)){
	var houseNum=parseInt(document.body.innerHTML.match(/>House #([0-9]+)<\/font>/)[1])-1;
	if(document.body.innerHTML.match("think"))
	for(var i in ints){
		if(document.body.innerHTML.match("IS a "+i))
		data[houseNum][1]=i;
		else if(document.body.innerHTML.match("a "+i))
		data[houseNum][1]="Not "+i;
		}
	for(var i in ints)
	for(var j in ints[i])
	if(document.body.innerHTML.match(ints[i][j]))
	data[houseNum][1]=i;
	set("lastHouse", houseNum);
	set("lastAct", "");
}

if(document.body.innerHTML.match("Pick an Action")){
	for(var n=1; n<=4; n++)
		document.getElementById("spact"+n).addEventListener("focus", function(){
			set("lastAct", this.value);
		}, false);
}

if(get("lastAct")){
	var n=parseInt(get("lastAct"));
	var result="";
	if(document.body.innerHTML.match(/That choice's result:<br><b>[^<]+</))
		result=document.body.innerHTML.match(/That choice's result:<br><b>([^<]+)</)[1];
	else if(document.body.innerHTML.match(/You Got [^!]+!/))
		result=document.body.innerHTML.match(/You Got ([^!]+)!/)[1];
	else if(document.body.innerHTML.match(/4 Time/))
		result="-4 Time";
	if(result){
		set("lastAct", "");
		var games=JSON.parse(get("minigames", "[]"));
		games.push([parseInt(get("lastHouse")), n-1, result]);
		set("minigames", JSON.stringify(games));
	}
}

for(var i in cond)
	normalize(cond[i]);

var probs=[];
for(var n=0; n<10; n++){
	probs[n]={};
	for(var i in ints)
		probs[n][i]=0;
}

var notCandy=[];
for(notCandy[0]=0; notCandy[0]<7; notCandy[0]++)
for(notCandy[1]=notCandy[0]+1; notCandy[1]<8; notCandy[1]++)
for(notCandy[2]=notCandy[1]+1; notCandy[2]<9; notCandy[2]++)
for(notCandy[3]=notCandy[2]+1; notCandy[3]<10; notCandy[3]++)
for(var psycho=0; psycho<4; psycho++){
	var houses=[];
	for(var n=0; n<10; n++)
		houses[n]="Candy";
	for(var n=0; n<4; n++)
		houses[notCandy[n]]="Party";
	houses[notCandy[psycho]]="Psycho";
	var prob=1;
	for(var n=0; n<10; n++){
		if(data[n][0])
			prob*=cond[houses[n]][data[n][0]];
		if(data[n][1]){
			if(data[n][1].match("Not")){
				if(data[n][1].match(houses[n])){
					prob=0;
					break;
				}
			}
			else
				if(!data[n][1].match(houses[n])){
					prob=0;
					break;
				}
		}
	}
	for(var n=0; n<10; n++)
		probs[n][houses[n]]+=prob;
}

for(var n=0; n<10; n++){
	normalize(probs[n]);
	for(var i in probs[n])
		if(probs[n][i]>0.9999)
			data[n][1]=i;
	var body="";
	for(var i in probs[n])
		if(probs[n][i]>0.0001)
			body+=Math.round(probs[n][i]*1000)/10+"% "+i+", ";
	body=body.substr(0, body.length-2);
	var header="?", color="redswitch=[1]";
	if(data[n][0]){
		header=data[n][0];
		color="";
	}
	if(data[n][1]){
		header+=", "+data[n][1];
		if(!data[n][1].match("Not"))
			color="greenswitch=[1]";
	}
	if(document.body.innerHTML.match("Visit a House"))
		document.evaluate("//label[@for='house"+(n+1)+"']", document, null, XPathResult.ANY_TYPE, null).iterateNext().title
				="header=["+header+"] body=["+body+"] "+color;
}

var maximized="";
function maximize(int){
	if(avoiding==int){
		maximized=int;
		avoid(avoiding);
		return;
	}
	var max=0;
	for(var n=0; n<10; n++)
		if(!document.getElementById("house"+(n+1)).disabled && probs[n][int]>max){
			max=probs[n][int];
			document.getElementById("house"+(n+1)).click();
		}
	if(max==0){
		if(avoiding)
			alert("All "+int+" houses are "+avoiding+" houses!");
		else
			alert("No more "+int+" houses!");
	}
	else
		maximized=int;
}

function minimize(int){
	var min=1;
	for(var n=0; n<10; n++)
		if(!document.getElementById("house"+(n+1)).disabled && probs[n][int]<min){
			min=probs[n][int];
			document.getElementById("house"+(n+1)).click();
		}
	if(min==1)
		alert("All remaining houses are "+int+"houses!");
}

var avoiding="";
function avoid(int){
	if(avoiding==int){
		for(var n=0; n<10; n++)
			document.getElementById("house"+(n+1)).disabled=visited[n];
		avoiding="";
		if(maximized)
			window.setTimeout(function() {maximize(maximized);}, 100);
		document.getElementById("avoid"+int).value="Avoid "+int+" houses";
	}
	else{
		avoiding=int;
		for(var n=0; n<10; n++)
			if(probs[n][int]>0.0001){
				document.getElementById("house"+(n+1)).disabled=true;
				document.getElementById("house"+(n+1)).checked=false;
			}
			else
				document.getElementById("house"+(n+1)).disabled=visited[n];
		if(maximized && maximized!=int)
			window.setTimeout(function() {maximize(maximized);}, 100);
		for(var i in ints)
			if(document.getElementById("avoid"+i))
				document.getElementById("avoid"+i).value="Avoid "+i+" houses";
		document.getElementById("avoid"+int).value="Unavoid "+int+" houses";
	}
}

if(document.body.innerHTML.match("Perform")){
	var houseNum=parseInt(document.body.innerHTML.match(/>House #([0-9]+)<\/font>/)[1])-1;
	var results=["Candy", "Party", "Psycho"];
	for(var n=0; n<3; n++){
		document.evaluate("//*[@for='ha"+(n+1)+"']", document, null, XPathResult.ANY_TYPE, null)
				.iterateNext().innerHTML+=" ("+Math.round(probs[houseNum][results[n]]*1000)/10+"%)";
		if(probs[houseNum][results[n]]<0.0001 && !(results[n]=="Candy" && probs[houseNum]["Party"]>0.9999))
			document.getElementById("ha"+(n+1)).disabled=true;
		else if(probs[houseNum][results[n]]>0.9999)
			document.getElementById("ha"+(n+1)).click();
	}
}
else if(document.body.innerHTML.match("Visit")){
	var visited=[];
	for(var n=0; n<10; n++)
		visited[n]=document.getElementById("house"+(n+1)).disabled;
	var div=document.createElement("div");
	document.forms.namedItem("cw1").appendChild(div);
	div.appendChild(document.createElement("br"));
	var table=document.createElement("table");
	for(var n=0; n<2; n++){
		var tr=document.createElement("tr");
		for(var i in ints){
			var td=document.createElement("td");
			var a=document.createElement("input");
			a.type="button";
			if(n==0){
				a.value="Maximize "+i+" chance";
				(function(j){a.addEventListener("click", function(){maximize(j);}, true)})(i);
			}
			else{
				var avoidable=false;
				for(var j=0; j<10; j++)
					if(!document.getElementById("house"+(j+1)).disabled && probs[j][i]<0.0001)
						avoidable=true;
				if(avoidable){
					a.value="Avoid "+i+" houses";
					a.id="avoid"+i;
					(function(j){a.addEventListener("click", function(){avoid(j);}, true)})(i);
				}
				else{
					a.value="Minimize "+i+" chance";
					(function(j){a.addEventListener("click", function(){minimize(j);}, true)})(i);
				}
			}
			td.appendChild(a);
			tr.appendChild(td);
		}
		table.appendChild(tr);
	}
	div.appendChild(table);
	var complete=true, next=-1;
	for(var i=0; i<10; i++)
		if(!data[i][0]){
			complete=false;
			if(next==-1)
				next=i+1;
		}
	if(!complete){
		div.appendChild(document.createElement("br"));
		var note=document.createElement("div");
		note.innerHTML="For optimal results, visit all houses once first.";
		note.style.fontWeight="bold";
		note.style.fontVariant="small-caps";
		note.style.fontStyle="italic";
		div.appendChild(note);
		document.getElementById("house"+next).click();
	}
	div.appendChild(document.createElement("br"));
	var a=document.createElement("a");
	a.innerHTML="Clear data";
	a.href="#";
	a.addEventListener("click", function(){
		if(confirm("Are you sure you want to clear all data?\nThis cannot be undone.")){
			for(var i in data)
				for(var j in data[i])
					data[i][j]="";
			set("data", ",;,;,;,;,;,;,;,;,;,");
			set("minigames", "[]");
			alert("Data cleared.");
		}
	}, true);
	a.style.fontSize="12px";
	a.style.color="black";
	a.style.fontWeight="bold";
	div.appendChild(a);
}
else if(document.body.innerHTML.match("See you")){
	var div=document.createElement("div");
	var area=document.createElement("textarea");
	var minigames=JSON.parse(get("minigames", "[]"));
	var output=[];
	for(var n=0; n<10; n++){
		output[n]=(n+1)+". ";
		if(data[n][0])
			output[n]+=data[n][0];
		if(data[n][1])
			output[n]+=" - "+data[n][1];
	}
	for(var n in minigames)
		output[minigames[n][0]]+=" ("+actions[data[minigames[n][0]][1]][minigames[n][1]]+": "+minigames[n][2]+")";
	area.value="Ally: "+get("ally")+"\n\n";
	for(var n=0; n<10; n++)
		area.value+=output[n]+"\n";
	area.rows=14;
	area.cols=60;
	area.addEventListener("focus", function(){this.select()}, false);
	div.innerHTML="Please <a href='/billy/forum/posting.php?mode=reply&f=36&t=16645' target='_blank'>post</a>"+
			" the below into <a href='/billy/forum/viewtopic.php?f=36&t=16645' target='_blank'>this thread</a>.<br><br>";
	div.appendChild(area);
	document.evaluate("//table[@width=510]/tbody/tr/td",
			document, null, XPathResult.ANY_TYPE, null ).iterateNext().appendChild(div);
}
else if(document.body.innerHTML.match("Choose Partner") || document.body.innerHTML.match("You chose")){
	for(var i in data)
		for(var j in data[i])
			data[i][j]="";
	set("minigames", "[]");
	if(document.body.innerHTML.match("You chose")){
		set("ally", document.body.innerHTML.match(/You chose ([^!]+)!/)[1]);
	}
}

for(var n=0; n<10; n++)
	data[n]=data[n].join(",");
set("data", data.join(";"));