// FIXME: Documentation!!!

function YahtzeeGame() {
	this.dice = [0,0,0,0,0];
	this.keep = [false,false,false,false,false];

	this.score1 = 0;
	this.score2 = 0;
	this.score3 = 0;
	this.score4 = 0;
	this.score5 = 0;
	this.score6 = 0;

	this.score3kind = 0;
	this.score4kind = 0;
	this.scoreFullHouse = 0;
	this.scoreSmStraight = 0;
	this.scoreLgStraight = 0;
	this.scoreYahtzee = 0;
	this.scoreBonus = 0;
	this.scoreChance = 0;

	this.used1 = false;
	this.used2 = false;
	this.used3 = false;
	this.used4 = false;
	this.used5 = false;
	this.used6 = false;

	this.used3kind = false;
	this.used4kind = false;
	this.usedFullHouse = false;
	this.usedSmStraight = false;
	this.usedLgStraight = false;
	this.usedYahtzee = false;
	this.usedChance = false;

	this.rolls = 0;

	this.MAX_ROLLS = 3;
	this.UPPER_BONUS = 35;
	this.YAHTZEE_BONUS = 100;

// Public Methods

	this.fullState = function() {
		return {
			"dice": this.dice,
			"keep": this.keep,

			"score1": this.score1,
			"score2": this.score2,
			"score3": this.score3,
			"score4": this.score4,
			"score5": this.score5,
			"score6": this.score6,

			"score3kind": this.score3kind,
			"score4kind": this.score4kind,
			"scoreFullHouse": this.scoreFullHouse,
			"scoreSmStraight": this.scoreSmStraight,
			"scoreLgStraight": this.scoreLgStraight,
			"scoreYahtzee": this.scoreYahtzee,
			"scoreBonus": this.scoreBonus,
			"scoreChance": this.scoreChance,

			"used1": this.used1,
			"used2": this.used2,
			"used3": this.used3,
			"used4": this.used4,
			"used5": this.used5,
			"used6": this.used6,

			"used3kind": this.used3kind,
			"used4kind": this.used4kind,
			"usedFullHouse": this.usedFullHouse,
			"usedSmStraight": this.usedSmStraight,
			"usedLgStraight": this.usedLgStraight,
			"usedYahtzee": this.usedYahtzee,
			"usedChance": this.usedChance,

			"rolls": this.rolls
		};
	}

	this.state = function() {
		return {
			"score1": this.score1,
			"score2": this.score2,
			"score3": this.score3,
			"score4": this.score4,
			"score5": this.score5,
			"score6": this.score6,

			"score3kind": this.score3kind,
			"score4kind": this.score4kind,
			"scoreFullHouse": this.scoreFullHouse,
			"scoreSmStraight": this.scoreSmStraight,
			"scoreLgStraight": this.scoreLgStraight,
			"scoreYahtzee": this.scoreYahtzee,
			"scoreBonus": this.scoreBonus,
			"scoreChance": this.scoreChance
		};
	}

	// Roll the dice that aren't held; return the number of rolls left or false if roll limit is reached
	this.roll = function() {
		if (this.rolls == this.MAX_ROLLS) return false;
		else if (this.rolls == 0) this.keep[0] = this.keep[1] = this.keep[2] = this.keep[3] = this.keep[4] = false;

		++this.rolls;

		this.keep[0] || (this.dice[0] = this.rollDie());
		this.keep[1] || (this.dice[1] = this.rollDie());
		this.keep[2] || (this.dice[2] = this.rollDie());
		this.keep[3] || (this.dice[3] = this.rollDie());
		this.keep[4] || (this.dice[4] = this.rollDie());

		return this.MAX_ROLLS - this.rolls;
	};

	this.rolled = function() {
		return this.rolls > 0;
	};

	this.rollsLeft = function() {
		return this.MAX_ROLLS - this.rolls;
	};

	//this.rollYahtzee = function() {
	//	this.keep[0] = this.keep[1] = this.keep[2] = this.keep[3] = this.keep[4] = false;
	//
	//	var number = this.rollDie();
	//	this.dice[0] = number;
	//	this.dice[1] = number;
	//	this.dice[2] = number;
	//	this.dice[3] = number;
	//	this.dice[4] = number;
	//
	//	return this.MAX_ROLLS - this.rolls;
	//};

	this.die = function(n) { return (n >= 0 && n < 5) ? this.dice[n] : 0; };
	this.held = function(n) { return (n >= 0 && n < 5) ? this.keep[n] : false; };
	this.hold = function(n) { if (n >= 0 && n < 5) this.keep[n] = true; };
	this.unhold = function(n) { if (n >= 0 && n < 5) this.keep[n] = false; };
	this.toggleHeld = function(n) { if (n >= 0 && n < 5) this.keep[n] = !this.keep[n]; };

	this.diceTotal = function() {
		return this.dice[0] + this.dice[1] + this.dice[2] + this.dice[3] + this.dice[4];
	};

	this.gameOver = function() {
		return this.used1 && this.used2 && this.used3 && this.used4 && this.used5 && this.used6 &&
			this.used3kind && this.used4kind && this.usedFullHouse && this.usedSmStraight && this.usedLgStraight && this.usedYahtzee && this.usedChance;
	};

	this.upperScore1 = function() { return this.score1; };
	this.upperScore2 = function() { return this.score2; };
	this.upperScore3 = function() { return this.score3; };
	this.upperScore4 = function() { return this.score4; };
	this.upperScore5 = function() { return this.score5; };
	this.upperScore6 = function() { return this.score6; };

	this.upperScoreN = function(n) {
		switch (n) {
		case 1: return this.score1;
		case 2: return this.score2;
		case 3: return this.score3;
		case 4: return this.score4;
		case 5: return this.score5;
		case 6: return this.score6;
		default: return 0;
		}
	};

	this.upperSubtotal = function() {
		return this.score1 + this.score2 + this.score3 + this.score4 + this.score5 + this.score6;
	};
	this.upperScore = function() {
		return this.upperSubtotal() + this.upperBonusScore();
	};

	this.upperBonusScore = function() {
		return this.gotUpperBonus() ? this.UPPER_BONUS : 0;
	};

	this.threeKindScore = function() { return this.score3kind; };
	this.fourKindScore = function() { return this.score4kind; };
	this.fullHouseScore = function() { return this.scoreFullHouse; };
	this.smStraightScore = function() { return this.scoreSmStraight; };
	this.lgStraightScore = function() { return this.scoreLgStraight; };
	this.yahtzeeScore = function() { return this.scoreYahtzee; };
	this.bonusScore = function() { return this.scoreBonus; };
	this.chanceScore = function() { return this.scoreChance; };

	this.totalScore = function() {
		return this.upperScore() + this.score3kind + this.score4kind + this.scoreFullHouse + this.scoreSmStraight + this.scoreLgStraight + this.scoreYahtzee + this.scoreBonus + this.scoreChance;
	};

	this.got1 = function() { return this.used1; };
	this.got2 = function() { return this.used2; };
	this.got3 = function() { return this.used3; };
	this.got4 = function() { return this.used4; };
	this.got5 = function() { return this.used5; };
	this.got6 = function() { return this.used6; };
	this.gotN = function(n) { return this.gotBasic(n); };

	this.got3kind = function() { return this.used3kind; };
	this.got4kind = function() { return this.used4kind; };
	this.gotFullHouse = function() { return this.usedFullHouse; };
	this.gotSmStraight = function() { return this.usedSmStraight; };
	this.gotLgStraight = function() { return this.usedLgStraight; };
	this.gotYahtzee = function() { return this.usedYahtzee; };
	this.gotChance = function() { return this.usedChance; };

	this.gotUpperBonus = function() {
		return this.upperSubtotal() >= (3 * (1 + 2 + 3 + 4 + 5 + 6));
	};

	this.yahtzeeBonuses = function() {
		return this.scoreBonus / this.YAHTZEE_BONUS;
	};

	this.calc1 = function() { return this.calcBasic(1); };
	this.calc2 = function() { return this.calcBasic(2); };
	this.calc3 = function() { return this.calcBasic(3); };
	this.calc4 = function() { return this.calcBasic(4); };
	this.calc5 = function() { return this.calcBasic(5); };
	this.calc6 = function() { return this.calcBasic(6); };

	this.calc3kind = function() {
		return this.isNKind(3) ? sum(this.dice) : 0;
	};

	this.calc4kind = function() {
		return this.isNKind(4) ? sum(this.dice) : 0;
	};

	this.calcFullHouse = function() {
		var rolled3 = false;
		var rolled2 = false;

		var count1 = this.countDice(1);
		var count2 = this.countDice(2);
		var count3 = this.countDice(3);
		var count4 = this.countDice(4);
		var count5 = this.countDice(5);
		var count6 = this.countDice(6);

		if      (count1 == 3) rolled3 = true;
		else if (count2 == 3) rolled3 = true;
		else if (count3 == 3) rolled3 = true;
		else if (count4 == 3) rolled3 = true;
		else if (count5 == 3) rolled3 = true;
		else if (count6 == 3) rolled3 = true;

		if      (count1 == 2) rolled2 = true;
		else if (count2 == 2) rolled2 = true;
		else if (count3 == 2) rolled2 = true;
		else if (count4 == 2) rolled2 = true;
		else if (count5 == 2) rolled2 = true;
		else if (count6 == 2) rolled2 = true;

		return ((rolled2 && rolled3) || this.isNKind(5)) ? 25 : 0;
	};

	this.calcSmStraight = function() {
		if (this.checkStraight() >= 4 || (this.checkJoker() && this.gotBasic(this.dice[0])))
			return 30;
		return 0;
	};

	this.calcLgStraight = function() {
		if (this.checkStraight() == 5 || (this.checkJoker() && this.gotBasic(this.dice[0])))
			return 40;
		return 0;
	};

	this.calcYahtzee = function() {
		return this.isNKind(5) ? 50 : 0;
	};

	this.calcChance = function() {
		return sum(this.dice);
	};

	this.apply1 = function() {
		if (this.used1) throw "Already used one";
		this.score1 = this.calcBasic(1);
		this.used1 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score1;
	};
	this.apply2 = function() {
		if (this.used2) throw "Already used two";
		this.score2 = this.calcBasic(2);
		this.used2 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score2;
	};
	this.apply3 = function() {
		if (this.used3) throw "Already used three";
		this.score3 = this.calcBasic(3);
		this.used3 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score3;
	};
	this.apply4 = function() {
		if (this.used4) throw "Already used four";
		this.score4 = this.calcBasic(4);
		this.used4 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score4;
	};
	this.apply5 = function() {
		if (this.used5) throw "Already used five";
		this.score5 = this.calcBasic(5);
		this.used5 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score5;
	};
	this.apply6 = function() {
		if (this.used6) throw "Already used six";
		this.score6 = this.calcBasic(6);
		this.used6 = true;
		this.applyBonus();
		this.resetRolls();
		return this.score6;
	};

	this.apply3kind = function() {
		if (this.used3kind) throw "Already used three of a kind";
		this.score3kind = this.calc3kind();
		this.used3kind = true;
		this.applyBonus();
		this.resetRolls();
		return this.score3kind;
	};

	this.apply4kind = function() {
		if (this.used4kind) throw "Already used four of a kind";
		this.score4kind = this.calc4kind();
		this.used4kind = true;
		this.applyBonus();
		this.resetRolls();
		return this.score4kind;
	};

	this.applyFullHouse = function() {
		if (this.usedFullHouse) throw "Already used full house";
		this.scoreFullHouse = this.calcFullHouse();
		this.usedFullHouse = true;
		this.applyBonus();
		this.resetRolls();
		return this.scoreFullHouse;
	};

	this.applySmStraight = function() {
		if (this.usedSmStraight) throw "Already used small straight";
		this.scoreSmStraight = this.calcSmStraight();
		this.usedSmStraight = true;
		this.applyBonus();
		this.resetRolls();
		return this.scoreSmStraight;
	};

	this.applyLgStraight = function() {
		if (this.usedLgStraight) throw "Already used large straight";
		this.scoreLgStraight = this.calcLgStraight();
		this.usedLgStraight = true;
		this.applyBonus();
		this.resetRolls();
		return this.scoreLgStraight;
	};

	this.applyYahtzee = function() {
		if (this.usedYahtzee) throw "Already used Yahtzee";
		this.scoreYahtzee = this.calcYahtzee();
		this.usedYahtzee = true;
		this.resetRolls();
		return this.scoreYahtzee;
	};

	this.applyChance = function() {
		if (this.usedChance) throw "Already used chance";
		this.scoreChance = this.calcChance();
		this.usedChance = true;
		this.applyBonus();
		this.resetRolls();
		return this.scoreChance;
	};


// Private methods
	this.rollDie = function() {
		return Math.floor(1 + Math.random() * 6);
	};

	this.resetRolls = function() {
		this.rolls = 0;
		this.keep[0] = this.keep[1] = this.keep[2] = this.keep[3] = this.keep[4] = false;
		this.dice[0] = this.dice[1] = this.dice[2] = this.dice[3] = this.dice[4] = 0;
	};

	this.countDice = function(number) {
		var count = 0;
		if (this.dice[0] == number) ++count;
		if (this.dice[1] == number) ++count;
		if (this.dice[2] == number) ++count;
		if (this.dice[3] == number) ++count;
		if (this.dice[4] == number) ++count;
		return count;
	};

	this.gotBasic = function(n) {
		switch(n) {
		case 1: return this.used1;
		case 2: return this.used2;
		case 3: return this.used3;
		case 4: return this.used4;
		case 5: return this.used5;
		case 6: return this.used6;
		default: return false;
		}
	};

	this.calcBasic = function(number) {
		return this.countDice(number) * number;
	};

	this.isNKind = function(n) {
		var count = [0, 0, 0, 0, 0, 0, 0];

		for (var i=0; i<this.dice.length; i++)
			count[this.dice[i]]++;
	
		// Start at 1 so we don't count zeros
		for (var i = 1; i < count.length; i++)
			if (count[i] >= n)
				return true;
		return false;
	};

	this.checkStraight = function() {
		var found = new Array(NaN, false, false, false, false, false, false);
	
		for (var index=0; index<this.dice.length; index++)
			found[this.dice[index]] = true;
	
		var run = 0;
		var largestRun = 0;
		for (var i = 1; i <= 6; i++) {
			if (found[i])
				run++;
			else
				run = 0;
			if (run >= largestRun)
				largestRun = run;
		}
		return largestRun;
	};

	this.checkJoker = function() {
		return this.isNKind(5) && this.usedYahtzee;
	};

	this.applyBonus = function() {
		if (this.scoreYahtzee != 0 && this.isNKind(5)) this.scoreBonus += this.YAHTZEE_BONUS;
	};

	this.loadGameState = function(gs) {
		for (var i in gs)
			this[i] = gs[i];
	};
};

sum = function(array) {
	var theSum = 0;
	for (var i=0; i<array.length; i++)
		theSum += array[i];
	return theSum;
}

function assert(cond, message) {
	if (!cond) {
		message = "Assertion error: " + (message || "Assertion failure");
		alert(message);
		throw message;
	}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



var keep0;
var keep1;
var keep2;
var keep3;
var keep4;

var die0;
var die1;
var die2;
var die3;
var die4;

var dieTotal;
var rollBtn;
var rollsLeft;

var score1;
var score2;
var score3;
var score4;
var score5;
var score6;

var fullHouse;
var yahtzee;
var threeOfAKind;
var fourOfAKind;
var smallStraight;
var largeStraight;
var chance;
var bonus;

var upperSubtotal;
var upperTotal;

var currentScore;

var button1;
var button2;
var button3;
var button4;
var button5;
var button6;

var fullBtn;
var yahtzeeBtn;
var threeBtn;
var fourBtn;
var smallBtn;
var largeBtn;
var chanceBtn;

var yahtzeeBonus;

var newGameBtn;

var upperColorNone;
var upperColorSubtotal;
var upperColorAll;

var potentialPoints;

var game;

function get(id) {
	return document.getElementById(id);
}

$.fn.check = function(mode) {
	var mode = mode || 'on'; // if mode is undefined, use 'on' as default
	return this.each(function() {
		switch(mode) {
		case 'on':
			this.checked = true;
			break;
		case 'off':
			this.checked = false;
			break;
		case 'toggle':
			this.checked = !this.checked;
			break;
		}
	});
};

$(document).ready(function() {
	keep0 = get("keep0");
	keep1 = get("keep1");
	keep2 = get("keep2");
	keep3 = get("keep3");
	keep4 = get("keep4");

	die0 = get("die0");
	die1 = get("die1");
	die2 = get("die2");
	die3 = get("die3");
	die4 = get("die4");

	dieTotal = get("dieTotal");
	rollBtn = get("rollBtn");
	rollsLeft = get("rollsLeft");

	score1 = get("score1");
	score2 = get("score2");
	score3 = get("score3");
	score4 = get("score4");
	score5 = get("score5");
	score6 = get("score6");
	bonus = get("bonus");

	threeOfAKind = get("threeOfAKind");
	fourOfAKind = get("fourOfAKind");
	fullHouse = get("fullHouse");
	smallStraight = get("smallStraight");
	largeStraight = get("largeStraight");
	yahtzee = get("yahtzee");
	yahtzeeBonus = get("yahtzeeBonus");
	chance = get("chance");

	upperSubtotal = get("upperSubtotal");
	upperTotal = get("upperTotal");

	currentScore = get("currentScore");

	button1 = get("button1");
	button2 = get("button2");
	button3 = get("button3");
	button4 = get("button4");
	button5 = get("button5");
	button6 = get("button6");

	fullBtn = get("fullBtn");
	yahtzeeBtn = get("yahtzeeBtn");
	threeBtn = get("threeBtn");
	fourBtn = get("fourBtn");
	smallBtn = get("smallBtn");
	largeBtn = get("largeBtn");
	chanceBtn = get("chanceBtn");

	newGameBtn = get("newGameBtn");

	upperColorNone = get("upperColorNone");
	upperColorSubtotal = get("upperColorSubtotal");
	upperColorAll = get("upperColorAll");

	var upperColors = readCookie("upperColors");
	if (upperColors == null)
		upperColorAll.checked = true;
	else
		$("#" + upperColors).check();

	potentialPoints = get("potentialPoints");

	var showPoints = readCookie("showPoints");
	if (showPoints == null || showPoints == "true")
		potentialPoints.checked = true;

	resetGame();

	$("#button1").click(makeHandler("calc1", "1's", "apply1", score1, "upperScore1", button1));
	$("#button2").click(makeHandler("calc2", "2's", "apply2", score2, "upperScore2", button2));
	$("#button3").click(makeHandler("calc3", "3's", "apply3", score3, "upperScore3", button3));
	$("#button4").click(makeHandler("calc4", "4's", "apply4", score4, "upperScore4", button4));
	$("#button5").click(makeHandler("calc5", "5's", "apply5", score5, "upperScore5", button5));
	$("#button6").click(makeHandler("calc6", "6's", "apply6", score6, "upperScore6", button6));

	$("#threeBtn").click(makeHandler("calc3kind", "three of a kind", "apply3kind", threeOfAKind, "threeKindScore", threeBtn));
	$("#fourBtn").click(makeHandler("calc4kind", "four of a kind", "apply4kind", fourOfAKind, "fourKindScore", fourBtn));
	$("#fullBtn").click(makeHandler("calcFullHouse", "full house", "applyFullHouse", fullHouse, "fullHouseScore", fullBtn));
	$("#smallBtn").click(makeHandler("calcSmStraight", "small straight", "applySmStraight", smallStraight, "smStraightScore", smallBtn));
	$("#largeBtn").click(makeHandler("calcLgStraight", "large straight", "applyLgStraight", largeStraight, "lgStraightScore", largeBtn));
	$("#yahtzeeBtn").click(makeHandler("calcYahtzee", "yahtzee", "applyYahtzee", yahtzee, "yahtzeeScore", yahtzeeBtn));
	$("#chanceBtn").click(makeHandler("calcChance", "chance", "applyChance", chance, "chanceScore", chanceBtn));

	$("#newGameBtn").click(function() {
		if (confirm("Click OK to start a new game"))
			resetGame();
	});
	$("img.dice,input.keep").click(toggleKeep);
	$("#rollBtn").click(roll);
	$("input[@name='upperColor']").click(function() {createCookie("upperColors", this.id, 365); updateBonusStatus(); });
	$("#potentialPoints").click(function() { createCookie("showPoints", this.checked ? "true" : "false", 365); updatePotentialPoints(); });

	//$("h2").click(function() {
	//	rollYahtzee();
	//});

	$.post("highscore.php", {"action":"list","format":"json"}, showHighScores);
});

function makeHandler(pointfn, slot, applyfn, scoreField, scorefn, button) {
	return function() {
		if (!game.rolled()) {
			alert("You have to roll first.");
			return;
		}
		// If they will get points, there is no way to get points, or they say they want the zero
		if (game[pointfn]() > 0 || ($(".potential").size() == 0 && game.rollsLeft() == 0) || confirmZero(slot)) {
			resetPotentialPoints();
			game[applyfn]();
			scoreField.innerHTML = game[scorefn]();
			button.disabled = true;
			rollBtn.disabled = false;
			updateScores();
			updateDice();
			if (game.gameOver()) {
				rollBtn.disabled = true;
				endGame();
			}
		}
	}
}

var first = true;
function resetGame() {
	if (!first) {
		//if (!confirm("Click OK to start a new game")) return;
	}

	first = false;

	$("input.slot").removeAttr("disabled");
	$(".text").empty();
	rollBtn.disabled = false;

	game = new YahtzeeGame();
	updateDice();
	resetPotentialPoints();
	updateBonusStatus();
}

function toggleKeep() {
	var id = this.id;

	var number = -1;
	if (id.indexOf("die") == 0) // img#dieX
		number = parseInt(id.substring(3));
	else if (id.indexOf("keep") == 0) // input$keepX
		number = parseInt(id.substring(4));

	assert(number >= 0 && number < 5, "tried to (un)hold a non-existent die (" + number + ")");

	game.toggleHeld(number);
	updateDice();
}

function roll() {
	if (game.rollsLeft() == 0)
		throw assert(false, "Trying to roll with no rolls left");

	if (game.roll() == 0)
		rollBtn.disabled = true;

	updateDice();
	updatePotentialPoints();
}

//function rollYahtzee() {
//	if (game.rollYahtzee() == 0)
//		rollBtn.disabled = true;
//
//	updateDice();
//	updatePotentialPoints();
//}

function resetPotentialPoints() {
	$(".potential").removeClass("potential").empty();
}

function updatePotentialPoints() {
	resetPotentialPoints();
	var showPotentialPoints = potentialPoints.checked;

	if (!game.got1() && game.calc1() > 0) {
		$("#score1").addClass("potential");
		if (showPotentialPoints)
			$("#score1").html(game.calc1());
	}
	if (!game.got2() && game.calc2() > 0) {
		$("#score2").addClass("potential");
		if (showPotentialPoints)
			$("#score2").html(game.calc2());
	}
	if (!game.got3() && game.calc3() > 0) {
		$("#score3").addClass("potential");
		if (showPotentialPoints)
			$("#score3").html(game.calc3());
	}
	if (!game.got4() && game.calc4() > 0) {
		$("#score4").addClass("potential");
		if (showPotentialPoints)
			$("#score4").html(game.calc4());
	}
	if (!game.got5() && game.calc5() > 0) {
		$("#score5").addClass("potential");
		if (showPotentialPoints)
			$("#score5").html(game.calc5());
	}
	if (!game.got6() && game.calc6() > 0) {
		$("#score6").addClass("potential");
		if (showPotentialPoints)
			$("#score6").html(game.calc6());
	}

	if (!game.got3kind() && game.calc3kind() > 0) {
		$("#threeOfAKind").addClass("potential");
		if (showPotentialPoints)
			$("#threeOfAKind").html(game.calc3kind());
	}
	if (!game.got4kind() && game.calc4kind() > 0) {
		$("#fourOfAKind").addClass("potential");
		if (showPotentialPoints)
			$("#fourOfAKind").html(game.calc4kind());
	}
	if (!game.gotFullHouse() && game.calcFullHouse() > 0) {
		$("#fullHouse").addClass("potential");
		if (showPotentialPoints)
			$("#fullHouse").html(game.calcFullHouse());
	}
	if (!game.gotSmStraight() && game.calcSmStraight() > 0) {
		$("#smallStraight").addClass("potential");
		if (showPotentialPoints)
			$("#smallStraight").html(game.calcSmStraight());
	}
	if (!game.gotLgStraight() && game.calcLgStraight() > 0) {
		$("#largeStraight").addClass("potential");
		if (showPotentialPoints)
			$("#largeStraight").html(game.calcLgStraight());
	}
	if (!game.gotYahtzee() && game.calcYahtzee() > 0) {
		$("#yahtzee").addClass("potential");
		if (showPotentialPoints)
			$("#yahtzee").html(game.calcYahtzee());
	}
	if (!game.gotChance() && game.calcChance() > 0) {
		$("#chance").addClass("potential");
		if (showPotentialPoints)
			$("#chance").html(game.calcChance());
	}
}

function updateDice() {
	var die0src = "die" + game.die(0) + (game.held(0) ? "_keep" : "") + ".png";
	var die1src = "die" + game.die(1) + (game.held(1) ? "_keep" : "") + ".png";
	var die2src = "die" + game.die(2) + (game.held(2) ? "_keep" : "") + ".png";
	var die3src = "die" + game.die(3) + (game.held(3) ? "_keep" : "") + ".png";
	var die4src = "die" + game.die(4) + (game.held(4) ? "_keep" : "") + ".png";

	if (die0src != die0.src) die0.src = die0src;
	if (die1src != die1.src) die1.src = die1src;
	if (die2src != die2.src) die2.src = die2src;
	if (die3src != die3.src) die3.src = die3src;
	if (die4src != die4.src) die4.src = die4src;

	keep0.checked = game.held(0);
	keep1.checked = game.held(1);
	keep2.checked = game.held(2);
	keep3.checked = game.held(3);
	keep4.checked = game.held(4);

	rollsLeft.innerHTML = game.rollsLeft();

	if (game.diceTotal() == 0)
		dieTotal.innerHTML = "";
	else
		dieTotal.innerHTML = game.diceTotal();
}

function updateBonusStatus() {
	if (!upperColorAll.checked) {
		$(".upperScore").removeClass("low").removeClass("exact").removeClass("high");
	}

	if (upperColorNone.checked) {
		$("#upperSubtotal").removeClass("low").removeClass("exact").removeClass("high");
		return;
	}

	if (upperColorAll.checked) {
		for (var i=1; i<=6; i++) {
			var el = $("#score" + i);

			//alert(i + "'s => need " + targetScore + ", have " + score);
			if (game.gotN(i)) {
				var score = game.upperScoreN(i);
				var targetScore = i * 3;

				if (score > targetScore)
					el.removeClass("low").removeClass("exact").addClass("high");
				else if (score == targetScore)
					el.removeClass("low").addClass("exact").removeClass("high");
				else
					el.addClass("low").removeClass("exact").removeClass("high");
			}
			else {
				el.removeClass("low").removeClass("exact").removeClass("high");
			}
		}
	}

	if (upperColorAll.checked || upperColorSubtotal.checked) {
		var targetSubtotal = 0;
		if (game.got1()) targetSubtotal += 1;
		if (game.got2()) targetSubtotal += 2;
		if (game.got3()) targetSubtotal += 3;
		if (game.got4()) targetSubtotal += 4;
		if (game.got5()) targetSubtotal += 5;
		if (game.got6()) targetSubtotal += 6;
		targetSubtotal *= 3;

		if (targetSubtotal > 0) {
			var subtotal = game.upperSubtotal();
			if (subtotal > targetSubtotal)
				$("#upperSubtotal").removeClass("low").removeClass("exact").addClass("high");
			else if (subtotal == targetSubtotal)
				$("#upperSubtotal").removeClass("low").addClass("exact").removeClass("high");
			else
				$("#upperSubtotal").addClass("low").removeClass("high").removeClass("exact");
		}
		else {
			$("#upperSubtotal").removeClass("low").removeClass("exact").removeClass("high");
		}
	}
}

function updateScores() {
	var upperScore = game.upperSubtotal();
	var targetSubtotal = 0;
		if (game.got1()) targetSubtotal += 1;
		if (game.got2()) targetSubtotal += 2;
		if (game.got3()) targetSubtotal += 3;
		if (game.got4()) targetSubtotal += 4;
		if (game.got5()) targetSubtotal += 5;
		if (game.got6()) targetSubtotal += 6;
		targetSubtotal *= 3;
	if (targetSubtotal > 0)
		upperSubtotal.innerHTML = upperScore + " (" + (upperScore > targetSubtotal ? "+":"") + (upperScore - targetSubtotal) + ")";
	else
		upperSubtotal.innerHTML = "";

	var bonusScore = game.upperBonusScore();
	if (bonusScore > 0)
		bonus.innerHTML = bonusScore;

	upperScore = game.upperScore();
	if (upperScore > 0)
		upperTotal.innerHTML = upperScore;
	else
		upperTotal.innerHTML = "";

	var yahtzeeBonusScore = game.bonusScore();
	if (yahtzeeBonusScore > 0)
		yahtzeeBonus.innerHTML = yahtzeeBonusScore;

	currentScore.innerHTML = game.totalScore();

	updateBonusStatus();
}

function confirmZero(slot) {
	return window.confirm("Using this roll as " + slot + " will give you 0 points.");
}

function endGame() {
	//alert("Your final score is " + game.totalScore());
	$.post("highscore.php", {"action":"check","format":"json","score":game.totalScore()}, processHighScore);
}

function processHighScore(data, status) {
	var isHighScore = parseJSON(data).result;
	if (isHighScore) {
		TB_show("Congratulations!", "#TB_callback?height=110&width=400&close=no", false, generateForm);
	}
	else {
		TB_show("Game Over", "#TB_callback?height=100&width=400", false, generateFinalScore);
		$.post("highscore.php", {"action":"add","format":"json","name":"","score":game.totalScore(),"game":serialize(game.state())}, showHighScores);
	}
}

function generateFinalScore(el) {
	el.html('<h3>Your final score is ' + game.totalScore() + '.</h3><a href="#" id="newGameLink">New Game</a>');
	$("#newGameLink").click(function() {
		resetGame();
		TB_remove();
		return false;
	});
}

function generateForm(el) {
	el.html(
		'<h3>You earned a high score of ' + game.totalScore() +
		'.</h3><form action="#"><label for="name">Please enter your name:</label><input type="text" size="50" maxlength="50" id="name" /><input type="submit" value="Submit" id="submit_score" /></form>');

	get("name").onkeypress = function() {
		if (window.event && window.event.keyCode == 13) {
			var name = $("#name").get(0).value;
			TB_remove();
			var gameState = serialize(game.state());
			$.post("highscore.php", {"action":"add","format":"json","name":name,"score":game.totalScore(),"game":gameState}, showHighScores);
			return false;
		}
		else
			return true;
	};

	$("#submit_score").click(function() {
		var name = $("#name").get(0).value;
		TB_remove();
		var gameState = serialize(game.state());
		$.post("highscore.php", {"action":"add","format":"json","name":name,"score":game.totalScore(),"game":gameState}, showHighScores);
		return false;
	});
	
	//$("#cancel_score").click(function() {
	//	TB_remove();
	//	$.post("highscore.php", {"action":"add","format":"json","name":"","score":game.totalScore(),"game":serialize(game.state())}, showHighScores);
	//});

	setTimeout(function() {
		get("name").focus();
	}, 1);
}

// Cookie processing from quirksmode.org -- Thanks PPK
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

function eraseCookie(name) {
	createCookie(name,"",-1);
}



function quote(s) {
	var c, i, l = s.length, o = "\"";
	for (i =0; i<l; i+=1) {
		c = s.charAt(i);
		if (c >= ' ') {
			if (c == '\\' || c == "\"") {
				o += '\\';
			}
			o += c;
		}
		else {
			switch (c) {
			case '\b':
				o += '\\b';
				break;
			case '\f':
				o += '\\f';
				break;
			case '\n':
				o += '\\n';
				break;
			case '\r':
				o += '\\r';
				break;
			case '\t':
				o += '\\t';
				break;
			default:
				c = c.charCodeAt();
				o += '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
			}
		}
	}
	return o + '"';
}

function mySerialize(obj, isArray) {
	var first = true;

	var str = isArray ? "[" : "{";

	for (var prop in obj) {
		var objtype = typeof(obj[prop]);
		if (objtype != "function") {
			if (!first) str += ",";
			if (!isArray) str += quote(prop) + ":";

			if (objtype == "string")
				str += quote(obj[prop]);
			else if ((typeof(obj[prop]) == "object") && (obj[prop].constructor == Array))
				str += serialize(obj[prop], true);
			else if (objtype == "object")
				str += serialize(obj[prop], false);
			else
				str += obj[prop];
			first = false;
		}
	}

	str += isArray ? "]" : "}";
	return str;
}

/*
    json.js

    This file adds these methods to JavaScript:

        serialize()

            This method produces a JSON text from an object. The
            object must not contain any cyclical references.

        serializeArray()

            This method produces a JSON text from an array. The
            array must not contain any cyclical references.

        parseJSON()

            This method parses a JSON text to produce an object or
            array. It will return false if there is an error.
*/
(function () {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            array: function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            number: function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            object: function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                        v = x[i];
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            string: function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            }
        };

    window.serialize = function(o) {
        return s.object(o);
    };

    window.serializeArray = function(a) {
        return s.array(a);
    };
})();

function parseJSON(s) {
    try {
        return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
                s.replace(/"(\\.|[^"\\])*"/g, ''))) &&
            eval('(' + s + ')');
    } catch (e) {
        return false;
    }
};

/* Hack to make Quanta color-code this correctly: ]*/

function createTable() {
	/*<table>
		<thead>
			<tr>
				<th colspan="3" class="title">High Scores</th>
			</tr>
			<tr>
				<th></th>
				<th>Score</th>
				<th>Name</th>
			</tr>
		</thead>
		<tbody>
		...
		</tbody>
	</table>*/

	var tr1 = document.createElement("tr");
	var th = document.createElement("th");
	th.setAttribute("colspan", "3");
	th.setAttribute("class", "title");
	th.appendChild(document.createTextNode("High Scores"));
	tr1.appendChild(th);

	var tr2 = document.createElement("tr");
	th = document.createElement("th");
	tr2.appendChild(th);

	th = document.createElement("th");
	th.appendChild(document.createTextNode("Score"));
	tr2.appendChild(th);

	th = document.createElement("th");
	th.appendChild(document.createTextNode("Name"));
	tr2.appendChild(th);

	var thead = document.createElement("thead");
	thead.appendChild(tr1, tr2);

	var tbody = document.createElement("tbody");

	var table = document.createElement("table");
	table.appendChild(thead);
	table.appendChild(tbody);

	return table;
}

function clearElement(el) {
	while (el.firstChild)
		el.removeChild(el.firstChild);
}

function showHighScores(highScores) {
	highScores = highScores ? parseJSON(highScores) : ({"lastScore":0,"scores":[]});
	if (!highScores) {
		alert("Error getting high scores");
		return;
	}

	var totalScore = highScores.lastScore;
	highScores = highScores.scores;

	var foundNew = false;
	var list = document.getElementById('highScoreList');
	var table = createTable();
	var tbody = table.lastChild;

	for (var i=0; i<highScores.length; i++) {
		if (isNaN(parseInt(i)))
			break;

		var theClass = (parseInt(i) & 1) ? "odd" : "even";
		if (!foundNew && highScores[i].score == totalScore) {
			theClass += " current";
			foundNew = true;
		}
		var tr = document.createElement("tr");
		tr.setAttribute("class", theClass);

		var td = document.createElement("td");
		td.setAttribute("class", "position");
		td.appendChild(document.createTextNode((parseInt(i) + 1).toString()));
		tr.appendChild(td);

		td = document.createElement("td");
		td.setAttribute("class", "score");
		td.appendChild(document.createTextNode(highScores[i].score.toString()));
		tr.appendChild(td);

		td = document.createElement("td");
		//td.appendChild(document.createTextNode(highScores[i].name));
		td.innerHTML = highScores[i].name;
		tr.appendChild(td);

		tbody.appendChild(tr);
	}

	clearElement(list);
	list.appendChild(table);
}
