var rackArray = new Array("", "", "", "", "", "", "", "", "");	// Array of tiles player can place

var placedArray = new Array();	// Array of tiles that have already been played

var tileScore = new Array();	// Array of scores for letters
tileScore["A"] = 1;
tileScore["B"] = 3;
tileScore["C"] = 3;
tileScore["D"] = 2;
tileScore["E"] = 1;
tileScore["F"] = 4;
tileScore["G"] = 2;
tileScore["H"] = 4;
tileScore["I"] = 1;
tileScore["J"] = 8;
tileScore["K"] = 5;
tileScore["L"] = 1;
tileScore["M"] = 3;
tileScore["N"] = 1;
tileScore["O"] = 1;
tileScore["P"] = 3;
tileScore["Q"] = 10;
tileScore["R"] = 1;
tileScore["S"] = 1;
tileScore["T"] = 1;
tileScore["U"] = 1;
tileScore["V"] = 4;
tileScore["W"] = 4;
tileScore["X"] = 8;
tileScore["Y"] = 4;
tileScore["Z"] = 10;
// Now all the zero scores for blank tiles
tileScore[" "] = 0;
tileScore["a"] = 0;
tileScore["b"] = 0;
tileScore["c"] = 0;
tileScore["d"] = 0;
tileScore["e"] = 0;
tileScore["f"] = 0;
tileScore["g"] = 0;
tileScore["h"] = 0;
tileScore["i"] = 0;
tileScore["j"] = 0;
tileScore["k"] = 0;
tileScore["l"] = 0;
tileScore["m"] = 0;
tileScore["n"] = 0;
tileScore["o"] = 0;
tileScore["p"] = 0;
tileScore["q"] = 0;
tileScore["r"] = 0;
tileScore["s"] = 0;
tileScore["t"] = 0;
tileScore["u"] = 0;
tileScore["v"] = 0;
tileScore["w"] = 0;
tileScore["x"] = 0;
tileScore["y"] = 0;
tileScore["z"] = 0;

var multiplierArray = new Array(); // Array of square positions that multiply scores
multiplierArray["c1r1"] = "3W";
multiplierArray["c1r8"] = "3W";
multiplierArray["c1rF"] = "3W";
multiplierArray["c8r1"] = "3W";
multiplierArray["c8rF"] = "3W";
multiplierArray["cFr1"] = "3W";
multiplierArray["cFr8"] = "3W";
multiplierArray["cFrF"] = "3W";
multiplierArray["c2r2"] = "2W";
multiplierArray["c2rE"] = "2W";
multiplierArray["c3r3"] = "2W";
multiplierArray["c3rD"] = "2W";
multiplierArray["c4r4"] = "2W";
multiplierArray["c4rC"] = "2W";
multiplierArray["c5r5"] = "2W";
multiplierArray["c5rB"] = "2W";
multiplierArray["c8r8"] = "2W";
multiplierArray["cBr5"] = "2W";
multiplierArray["cBrB"] = "2W";
multiplierArray["cCr4"] = "2W";
multiplierArray["cCrC"] = "2W";
multiplierArray["cDr3"] = "2W";
multiplierArray["cDrD"] = "2W";
multiplierArray["cEr2"] = "2W";
multiplierArray["cErE"] = "2W";
multiplierArray["c2r6"] = "3L";
multiplierArray["c2rA"] = "3L";
multiplierArray["c6r2"] = "3L";
multiplierArray["c6r6"] = "3L";
multiplierArray["c6rA"] = "3L";
multiplierArray["c6rE"] = "3L";
multiplierArray["cAr2"] = "3L";
multiplierArray["cAr6"] = "3L";
multiplierArray["cArA"] = "3L";
multiplierArray["cArE"] = "3L";
multiplierArray["cEr6"] = "3L";
multiplierArray["cErA"] = "3L";
multiplierArray["c1r4"] = "2L";
multiplierArray["c1rC"] = "2L";
multiplierArray["c3r7"] = "2L";
multiplierArray["c3r9"] = "2L";
multiplierArray["c4r1"] = "2L";
multiplierArray["c4r8"] = "2L";
multiplierArray["c4rF"] = "2L";
multiplierArray["c7r3"] = "2L";
multiplierArray["c7r7"] = "2L";
multiplierArray["c7r9"] = "2L";
multiplierArray["c7rD"] = "2L";
multiplierArray["c8r4"] = "2L";
multiplierArray["c8rC"] = "2L";
multiplierArray["c9r3"] = "2L";
multiplierArray["c9r7"] = "2L";
multiplierArray["c9r9"] = "2L";
multiplierArray["c9rD"] = "2L";
multiplierArray["cCr1"] = "2L";
multiplierArray["cCr8"] = "2L";
multiplierArray["cCrF"] = "2L";
multiplierArray["cDr7"] = "2L";
multiplierArray["cDr9"] = "2L";
multiplierArray["cFr4"] = "2L";
multiplierArray["cFrC"] = "2L";

var re = /(,[a-z]+)\+/g;
var re0 = /(,[a-z])([a-z]*)0/g;
var re1 = /(,[a-z]{2})([a-z]*)1/g;
var re2 = /(,[a-z]{3})([a-z]*)2/g;
var re3 = /(,[a-z]{4})([a-z]*)3/g;
var re4 = /(,[a-z]{5})([a-z]*)4/g;
var re5 = /(,[a-z]{6})([a-z]*)5/g;
var re6 = /(,[a-z]{7})([a-z]*)6/g;
var re7 = /(,[a-z]{8})([a-z]*)7/g;
var re8 = /(,[a-z]{9})([a-z]*)8/g;
var re9 = /(,[a-z]{10})([a-z]*)9/g;

var fromHex = new Array();
fromHex[""] = "";
fromHex["0"] = 0;
fromHex["1"] = 1;
fromHex["2"] = 2;
fromHex["3"] = 3;
fromHex["4"] = 4;
fromHex["5"] = 5;
fromHex["6"] = 6;
fromHex["7"] = 7;
fromHex["8"] = 8;
fromHex["9"] = 9;
fromHex["A"] = 10;
fromHex["B"] = 11;
fromHex["C"] = 12;
fromHex["D"] = 13;
fromHex["E"] = 14;
fromHex["F"] = 15;
var toHex = new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");

var tileBank = new Array("A", "A", "A", "A", "A", "A", "A", "A", "A", "B", "B", "C", "C", "D", "D", "D", "D", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "E", "F", "F", "G", "G", "G", "H", "H", "I", "I", "I", "I", "I", "I", "I", "I", "I", "J", "K", "L", "L", "L", "L", "M", "M", "N", "N", "N", "N", "N", "N", "O", "O", "O", "O", "O", "O", "O", "O", "P", "P", "Q", "R", "R", "R", "R", "R", "R", "S", "S", "S", "S", "T", "T", "T", "T", "T", "T", "U", "U", "U", "U", "V", "V", "W", "W", "X", "Y", "Y", "Z", " ", " ");	// The tiles which may be given to the player
var tilesRemaining = tileBank.length;	// Number of tiles remaining that may be given to a player

var selectedTile = -1;	// Index of tile in rackArray that has been clicked on (selected)

var wordHead = "";	// Head of the word that is currently being scanned
var wordTail = "";	// Tail of the word that is currently being scanned
var wordMin = "";	// Location of the first letter of a word
var wordMax = "";	// Location of the last letter of a word
var wordList = new Array();	// Array of words placed on the board

var touching = false;	// Whether the scanned word touches existing tiles on the board
var score = 0;	// Player's total score
var bestplay = getBestPlay();
var highscore = getHighScore();
var swaps = 0;	// Number of consecutive tile swaps

initTileStorage();	// Randomly pick player's first tiles

var undoSaved = gameToString();

// Install keypress handler
if (document.all)
{
	document.attachEvent("onkeypress", handleKeypress);
}
else
{
	document.onkeypress = handleKeypress;
}




function arrayRemoveItem(itemID, theArray)
{
	if (itemID < theArray.length)
	{
		for (i = itemID; i + 1 < theArray.length; ++i)
		{
			theArray[i] = theArray[i + 1];
		}

		theArray.length -= 1;
	}
	
	return true;
}





function checkColumn()
{
	var min = 16;
	var max = 0;
	var theColumn = 0;
	var i;
	
	for (i = 0; i < rackArray.length; i++)
	{
		if (getTilePosition(i) != "")
		{
			if (theColumn)
			{
				if (getTilePositionColumn(i) != theColumn) return false;
			}
			else
			{
				theColumn = getTilePositionColumn(i);
			}

			var tmp = getTilePositionRow(i);
			if (tmp < min) min = tmp;
			if (tmp > max) max = tmp;
		}
	}

	if (theColumn == 0) return false;
	
	for (i = min; i <= max; i++)
	{
		if (!placedArray[encodePos(theColumn, i)])
		{
			for (j = 0; j < rackArray.length; j++)
			{
				if (getTilePositionRow(j) == i)
				{
					break;
				}
				else if (j == rackArray.length - 1)
				{
					return false;
				}
			}
		}
	}

	return true;
}




function checkDictionary(theWord)
{
	theWord = theWord.toLowerCase();
	if (theWord.length == 2) return (D2.indexOf(theWord) != -1);
	first3 = theWord.replace(/^(...).*/, "$1");
	if (typeof(D[first3]) == "undefined") return false;
	var theEntry = D[first3];
	if (!theEntry.match(/,$/)) {
		// We've not looked at this entry before - uncompress it, etc.
		theEntry = theEntry.replace(/W/g, "le");
		theEntry = theEntry.replace(/K/g, "al");
		theEntry = theEntry.replace(/F/g, "man");
		theEntry = theEntry.replace(/U/g, "ous");
		theEntry = theEntry.replace(/M/g, "ment");
		theEntry = theEntry.replace(/B/g, "able");
		theEntry = theEntry.replace(/C/g, "ic");
		theEntry = theEntry.replace(/X/g, "on");
		theEntry = theEntry.replace(/Q/g, "ng");
		theEntry = theEntry.replace(/R/g, "ier");
		theEntry = theEntry.replace(/S/g, "st");
		theEntry = theEntry.replace(/Y/g, "ly");
		theEntry = theEntry.replace(/J/g, "ally");
		theEntry = theEntry.replace(/E/g, "es");
		theEntry = theEntry.replace(/L/g, "less");
		theEntry = theEntry.replace(/Z/g, "ies");
		theEntry = theEntry.replace(/P/g, "tic");
		theEntry = theEntry.replace(/I/g, "iti");
		theEntry = theEntry.replace(/V/g, "tion");
		theEntry = theEntry.replace(/H/g, "zation");
		theEntry = theEntry.replace(/A/g, "abiliti");
		theEntry = theEntry.replace(/O/g, "ologi");
		theEntry = theEntry.replace(/T/g, "est");
		theEntry = theEntry.replace(/D/g, "ed");
		theEntry = theEntry.replace(/N/g, "ness");
		theEntry = theEntry.replace(/G/g, "ing");
		theEntry = "," + theEntry + ",";
		// May have prefixes on prefixes, so need to repeat the replace.
		var more = true;
		while (more) {
			var theLength = theEntry.length;
			theEntry = theEntry.replace(re, "$1$1");
			theEntry = theEntry.replace(re0, "$1$2$1");
			theEntry = theEntry.replace(re1, "$1$2$1");
			theEntry = theEntry.replace(re2, "$1$2$1");
			theEntry = theEntry.replace(re3, "$1$2$1");
			theEntry = theEntry.replace(re4, "$1$2$1");
			theEntry = theEntry.replace(re5, "$1$2$1");
			theEntry = theEntry.replace(re6, "$1$2$1");
			theEntry = theEntry.replace(re7, "$1$2$1");
			theEntry = theEntry.replace(re8, "$1$2$1");
			theEntry = theEntry.replace(re9, "$1$2$1");
			more = (theLength != theEntry.length);
		}
		if (theEntry.match(/[0-@+]/)) {
			alert("decompression oops!");
		}
		D[first3] = theEntry;
	}
	rest = theWord.replace(/^...?/, "");
	return (D[first3].indexOf("," + rest + ",") != -1);
}

function uncompressDict(str, code) {
	if (code == '+') {
alert("["+str+"] ["+code+"] -> [" + str + str + "]");
	    return str + str;
	}
	var xcode = code;
	code = 2 + "0123456789:;<=>?@".indexOf(code);
alert("["+str+"] ["+xcode+"] -> [" + str + str.substr(0, code) + "]");
	return str + str.substr(0, code);
}




function checkPlaced()
{
	for (i = 0; i < rackArray.length; i++)
	{
		if (getTilePosition(i))
		{
			return true;
		}
	}

	return false;
}




function checkRow()
{
	var min = 16;
	var max = 0;
	var theRow = 0;
	
	for (i = 0; i < rackArray.length; i++)
	{
		if (getTilePosition(i) != "")
		{
			if (theRow)
			{
				if (getTilePositionRow(i) != theRow) return false;
			}
			else
			{
				theRow = getTilePositionRow(i);
			}

			var tmp = getTilePositionColumn(i);
			if (tmp < min) min = tmp;
			if (tmp > max) max = tmp;
		}
	}

	if (theRow == 0) return false;

	for (i = min; i <= max; i++)
	{
		if (!placedArray[encodePos(i, theRow)])
		{
			for (j = 0; j < rackArray.length; j++)
			{
				if (getTilePositionColumn(j) == i)
				{
					break;
				}
				else if (j == rackArray.length - 1)
				{
					return false;
				}
			}
		}
	}

	return true;
}




function checkWords()
{
	var blanks = new Array;
	for (var i = 0; i < rackArray.length; i++)
	{
		var thePos = getTilePosition(i);
		if (thePos && getTileLabel(i) == ' ')
		{
			blanks[blanks.length] = thePos + "," + i;
		}
	}
	if (blanks.length) {
		var theLabels;
		if (blanks.length == 1) {
			theLabels = prompt("What letter is the blank?");
		} else {
			blanks.sort(); // Expect blanks in left-right or top-bottom order
			theLabels = prompt("What letters are the blanks (in order from left to right or top to bottom)?");
		}
		if (!theLabels || theLabels.length != blanks.length) return false;
		for (var i = 0; i < blanks.length; i++) {
			var ch = theLabels.charAt(i).toLowerCase();
			if (ch < 'a' || ch > 'z') return false;
			var j = blanks[i];
			j = j.charAt(j.length - 1);
			rackArray[j] = rackArray[j].replace(/.*,/, ch + ",");
		}
	}
	for (var i = 0; i < rackArray.length; i++)
	{
		var thePosition = getTilePosition(i);
		
		if (thePosition)
		{
			var theRow = getTilePositionRow(i);
			var theColumn = getTilePositionColumn(i);
			var theLabel = getTileLabel(i);
			
			wordHead = "";
			wordTail = "";
			
			getAdjacent(theColumn, theRow, "left");
			getAdjacent(theColumn, theRow, "right");
			
			if (wordHead != "" || wordTail != "")
			{
				if (!checkDictionary(wordHead + theLabel + wordTail))
				{
					alert("\"" + wordHead + theLabel + wordTail + "\" is not in the dictionary.");
					return false;
				}
				
				wordList[wordList.length] = wordMin + "," + wordMax;
			}

			wordHead = "";
			wordTail = "";
			
			getAdjacent(theColumn, theRow, "above");
			getAdjacent(theColumn, theRow, "below");

			if (wordHead != "" || wordTail != "")
			{
				if (!checkDictionary(wordHead + theLabel + wordTail))
				{
					alert("\"" + wordHead + theLabel + wordTail + "\" is not in the dictionary.");
					return false;
				}
				
				wordList[wordList.length] = wordMin + "," + wordMax;
			}
		}
	}
	
	if (placedArray["c8r8"] && !touching)
	{
		alert("At least one of your placed tiles must touch an existing tile.");
	
		return false;
	}
	
	return true;
}




function drawBoard()
{

	for (row = 1; row <= 15; row++)
	{
		for (column = 1; column <= 15; column++)
		{
			var theLocation = encodePos(column, row);

			if (placedArray[theLocation])
			{
				var theCell = document.getElementById(theLocation);
				theCell.innerHTML = '<span class="tile">' + tileHtml(placedArray[theLocation]) + '</span>';
			}
		}
	}
	
	return true;
}





function drawTileStorage()
{
	for (i = 0; i < rackArray.length; i++)
	{
		var theStore = document.getElementById("tileStorage" + i);
		var tileLabel = getTileLabel(i);
		var tilePosition = getTilePosition(i);
		
		if (!tilePosition)
		{
			if (tileLabel == "")
			{
				theStore.innerHTML = '<a class="empty" href="#" onclick="returnTile(' + i + ')\; return false\;"></a>';
			}
			else
			{
				theStore.innerHTML = '<a id="tile' + i + '" class="tile" href="#" onclick="selectTile(' + i + ')\; return false\;">' + tileHtml(tileLabel) + '</a>';
			}
		}
		else
		{
			var theCell = document.getElementById(tilePosition);

			theCell.innerHTML = '<a id="tile' + i + '" class="tile placed" href="#" onclick="selectTile(' + i + ')\; return false\;">' + tileHtml(tileLabel) + '</a>';
			theStore.innerHTML = '<a class="empty" href="#" onclick="returnTile(' + i + ')\; return false\;"></a>';
		}
	}
	
	var theStats = document.getElementById("stats");
	
	var stats = "Tiles remaining: " + tilesRemaining + " | Current score: " + score;
	if (highscore > 0) stats += " | High score: " + highscore;
	if (bestplay > 0) stats += " | Best play: " + bestplay;
	theStats.innerHTML = stats;
	
	return true;
}




function finalise()
{
	var undoSavedNew = gameToString();
	var tilesPlayed = 0;
	touching = false;
	wordList.length = 0;
	var usedMultipliers = new Array();	// Temporary array of bonus squares used in words

	if (!placedArray["c8r8"])
	{
		for (var i = 0; i < rackArray.length; i++)
		{
			if (getTilePosition(i) == "c8r8")
			{
				break;
			}
			else
			{
				if (i == rackArray.length - 1)
				{
					alert("When starting, one of your tiles must be placed on the centre square.");
					
					return false;
				}
			}
		}
	}
	
	if (!checkPlaced())
	{
		alert("You haven't placed any tiles.");
		
		return false;
	}
	
	if (!checkRow() && !checkColumn())
	{
		alert("Tiles must be placed in a continuous horizontal or vertical line.");
		
		return false;
	}
	
	if (!checkWords())
	{
		// Reset blanks.
		for (var i = 0; i < rackArray.length; i++)
		{
			rackArray[i] = rackArray[i].replace(/[a-z],/, " ,");
		}
		return false;
	}
	
	for (var i = 0; i < rackArray.length; i++)	// Move used tiles from rackArray to placedArray
	{
		if (getTilePosition(i))
		{
			placedArray[getTilePosition(i)] = getTileLabel(i);
			
			rackArray[i] = "";
			tilesPlayed++;
		}
	}

	for (var i = 0; i < wordList.length; i++)	// Remove duplicates from wordList
	{
		for (var j = 0; j < wordList.length; j++)
		{
			if (wordList[i] == wordList[j] && i != j)
			{
				arrayRemoveItem(j, wordList);
				
				i = -1;
			}
		}
	}

	var allScores = "";
	var totalScore = 0;
	for (var i = 0; i < wordList.length; i++)	// Score each word
	{
		var fromTile = wordList[i].replace(/,.*/, "");
		var fromTileColumn = fromHex[fromTile.replace(/c(.*)r.*/, "$1")];
		var fromTileRow = fromHex[fromTile.replace(/c.*r/, "")];
		
		var toTile = wordList[i].replace(/^.*,/, "");
		var toTileColumn = fromHex[toTile.replace(/c(.*)r.*/, "$1")];
		var toTileRow = fromHex[toTile.replace(/c.*r/, "")];
		
		var displayScore = "";
		var displayWord = "";
		var subScore = 0;
		var wordMultiplier = 1;
			
		if (fromTileColumn == toTileColumn)	// If word is vertically aligned
		{
			for (var j = fromTileRow; j <= toTileRow; j++)
			{
				currTile = encodePos(fromTileColumn, j);
				displayWord += placedArray[currTile];
				displayScore += "[";
				
				if (multiplierArray[currTile] == "2L")
				{
					subScore += tileScore[placedArray[currTile]] * 2;
					displayScore += "+" + tileScore[placedArray[currTile]] + " x 2L";
					
					usedMultipliers[usedMultipliers.length] = currTile;
				}
				else if (multiplierArray[currTile] == "3L")
				{
					subScore += tileScore[placedArray[currTile]] * 3;
					displayScore += "+" + tileScore[placedArray[currTile]] + " x 3L";
					
					usedMultipliers[usedMultipliers.length] = currTile;
				}
				else
				{
					subScore += tileScore[placedArray[currTile]];
					displayScore += "+" + tileScore[placedArray[currTile]];
					
					if (multiplierArray[currTile] == "2W")
					{
						wordMultiplier *= 2;
					
						usedMultipliers[usedMultipliers.length] = currTile;
					}
					else if (multiplierArray[currTile] == "3W")
					{
						wordMultiplier *= 3;
					
						usedMultipliers[usedMultipliers.length] = currTile;
					}
				}
				
				displayScore += "]   ";
			}
		}
		else
		{
			for (var j = fromTileColumn; j <= toTileColumn; j++)
			{
				currTile = encodePos(j, fromTileRow);
				displayWord += placedArray[currTile];
				displayScore += "[";
				var mult = multiplierArray[currTile];
				
				if (mult && mult.charAt(1) == "L")
				{
					subScore += tileScore[placedArray[currTile]] * parseInt(mult.charAt(0));
					displayScore += "+" + tileScore[placedArray[currTile]] + " x " + mult;
					
					usedMultipliers[usedMultipliers.length] = currTile;
				}
				else
				{
					subScore += tileScore[placedArray[currTile]];
					displayScore += "+" + tileScore[placedArray[currTile]];
					
					if (mult && mult.charAt(1) == "W")
					{
						wordMultiplier *= parseInt(mult.charAt(0));
					
						usedMultipliers[usedMultipliers.length] = currTile;
					}
				}
				
				displayScore += "]   ";
			}
		}
			
		if (wordMultiplier > 1)
		{
			subScore *= wordMultiplier;
			displayScore = "(   " + displayScore + ")   x   " + wordMultiplier + "   ";
		}

		displayScore += "=   " + subScore;
		if (subScore == 1) {
			displayScore += " point";
		} else {
			displayScore += " points";
		}
		displayScore = "Word score \"" + displayWord + "\":  " + displayScore;
		totalScore += subScore;

		allScores += displayScore + "\n";
	}
	
	// Bonus points for using all 7 tiles
	if (tilesPlayed == 7)
	{
		totalScore += 50;
		
		allScores += "You get a 50 point BONUS for using all 7 of your tiles!\n";
	}
	if (allScores.match("\n.*\n")) {
		allScores += "Total score for this turn: " + totalScore;
		if (totalScore == 1) {
			allScores += " point\n";
		} else {
			allScores += " points\n";
		}
	}
	if (totalScore > bestplay) {
		if (bestplay > 0) allScores += "This was your highest scoring turn ever!";
		setBestPlay(totalScore);
		bestplay = totalScore;
	}

	alert(allScores);

	score += totalScore;
	
	for (var i = 0; i < usedMultipliers.length; i++)	// Remove bonuses from used bonus tiles
	{
		multiplierArray[usedMultipliers[i]] = "";
	}

	swaps = 0;
	undoSaved = undoSavedNew;

	if (tilesRemaining > 0)
	{
		initTileStorage();
		drawTileStorage();
		drawBoard();
	}
	else
	{
		for (var i = 0; i < rackArray.length; i++)
		{
			if (rackArray[i] == "")
			{
				if (i == rackArray.length - 1)
				{
					if (highscore > 0 && score > highscore) {
						alert("CONGRATULATIONS! You beat your highscore by finishing with a score of " + score + " points.");
					} else {
						alert("CONGRATULATIONS! You finished with a score of " + score + " points.");
					}
					if (score > highscore) {
						setHighScore(score);
						highscore = score;
					}
				}
			}
			else
			{
				break;
			}
		}
		
		drawTileStorage();
		drawBoard();
		
	}
	
	return true;
}





function getAdjacent(column, row, direction)
{
	var theLabel = "";
	var theColumn = column;
	var theRow = row;
	var theDirection = direction;
	
	if (theDirection == "above")
	{
		wordMin = encodePos(column, row);
		theRow--;
	}
	else if (theDirection == "below")
	{
		wordMax = encodePos(column, row);
		theRow++;
	}
	else if (theDirection == "left")
	{
		wordMin = encodePos(column, row);
		theColumn--;
	}
	else if (theDirection == "right")
	{
		wordMax = encodePos(column, row);
		theColumn++;
	}
	
	var currLocation = encodePos(theColumn, theRow);
	
	if (placedArray[currLocation])
	{
		touching = true;
		
		theLabel = placedArray[currLocation];
	}
	else
	{
		for (var i = 0; i < rackArray.length; i++)
		{
			if (getTilePosition(i) == currLocation)
			{
				theLabel = getTileLabel(i);
			}
		}
	}
	
	if (theLabel)
	{
		if (theDirection == "above" || theDirection == "left")
		{
			wordHead = theLabel + wordHead;
		}
		else
		{
			wordTail += theLabel;
		}

		getAdjacent(theColumn, theRow, theDirection);
		
		return true;
	}

	return false;
}

function encodePos(column, row)
{
	return "c" + toHex[column] + "r" + toHex[row];
}



function getTileLabel(tileID)
{
	return rackArray[tileID].replace(/,.*/, "");
}





function getTilePosition(tileID)
{
	return rackArray[tileID].replace(/.*,/, "");
}




function getTilePositionColumn(tileID)
{
	var thePosition = getTilePosition(tileID);
	
	return fromHex[thePosition.replace(/c(.*)r.*/, "$1")];
}




function getTilePositionRow(tileID)
{
	var thePosition = getTilePosition(tileID);
	
	return fromHex[thePosition.replace(/c.*r(.*)/, "$1")];
}




function initTileStorage()
{
	var tiles = 0;
	for (var i = 0; i < rackArray.length && tiles < 7 && tilesRemaining > 0; i++)
	{
		if (rackArray[i] != "") ++tiles;
	}

	for (var i = 0; i < rackArray.length && tiles < 7 && tilesRemaining > 0; i++)
	{
		if (rackArray[i] == "")
		{
			var randomTile = randomInt(tileBank.length);
			rackArray[i] = tileBank[randomTile] + ",";
			arrayRemoveItem(randomTile, tileBank);

			tilesRemaining = tileBank.length;
			++tiles;
		}
	}
	return true;
}

function randomInt(N)
{
	// % 1 is needed because some implementations of Math.random() can
	// actually return 1 (early version of Opera for example).
	// | 0 does the same as Math.floor() would here, but is probably
	// slightly quicker.
	// For details, see: http://www.merlyn.demon.co.uk/js-randm.htm
	return (N * (Math.random() % 1)) | 0;
}


function pass()
{
	var swapped = 0;
	
	if (tilesRemaining < 7)
	{
		alert("You may not swap tiles when there are fewer than 7 remaining in the bag.");
		
		return false;
	}
	if (swaps > 1)
	{
		alert("You may not swap tiles more than twice without playing any words.");
		
		return false;
	}
	for (var i = 0; i < rackArray.length; i++)
	{
		if (getTilePosition(i))
		{
			emptyCell(getTilePosition(i));

			swapped++;
			
			tileBank[tileBank.length] = getTileLabel(i);
			rackArray[i] = "";
		}
	}
	
	if (swapped == 0)
	{
		alert('To swap tiles, place them anywhere on the board then click "swap".');
		
		return false;
	}
	
	swaps++;
	
	for (var i = 0; i < rackArray.length && swapped > 0 && tilesRemaining > 0; i++)
	{
		if (rackArray[i] == "" && tilesRemaining > 0)
		{
			var randomTile = Math.floor((Math.random() % 1) * (tileBank.length - swapped));
			rackArray[i] = tileBank[randomTile] + ",";
			arrayRemoveItem(randomTile, tileBank);

			tilesRemaining = tileBank.length;
			--swapped;
		}
	}
	drawBoard();
	drawTileStorage();
	
	return true;
}

function emptyCell(theLocation) {
	var theCell = document.getElementById(theLocation);
	var multiplyLabel = "";
	if (multiplierArray[theLocation] && theLocation != "c8r8")
	{
		multiplyLabel = multiplierArray[theLocation];
	}
	theCell.innerHTML = '<a class="empty" href="#" onclick="placeTile(\'' + theLocation + '\')\; return false\;"><span>' + multiplyLabel + '</span></a>';
}



function placeTile(boardCell)
{
	if (selectedTile != -1)
	{
		var tileLabel = getTileLabel(selectedTile);
		var tilePosition = getTilePosition(selectedTile);

		if (tilePosition)
		{
			emptyCell(tilePosition);
		}
		
		rackArray[selectedTile] = tileLabel + "," + boardCell;
		
		var theCell = document.getElementById(boardCell);
	
		theCell.innerHTML = '<a id="tile' + selectedTile + '" class="tile placed" href="#" onclick="selectTile(' + selectedTile + ')\; return false\;">' + tileHtml(tileLabel) + '</a>';
		
		selectedTile = -1;
		
		if (!tilePosition)
		{
			drawTileStorage();
		}
	}
	
	return true;
}

function tileHtml(tileLabel)
{
	if (tileLabel == ' ') return "";
	var theScore = tileScore[tileLabel];
	if (theScore == 0) return '<span class="blank">' + tileLabel.toUpperCase() + '<span class="score">0</span></span>';
	return tileLabel + '<span class="score">' + theScore + '</span>';
}



function returnTile(rackPos)
{
	if (selectedTile != -1)
	{
		var tileLabel = getTileLabel(selectedTile);
		var tilePosition = getTilePosition(selectedTile);
	
		if (rackPos == -1) {
			rackPos = selectedTile;
			for (var i = 0; i < rackArray.length; ++i)
			{
				if (getTilePosition(i) != "") {
					rackPos = i;
					break;
				}
			}
		}

		if (tilePosition) emptyCell(tilePosition);

		if (rackPos != selectedTile) {
			rackArray[selectedTile] = rackArray[rackPos];
		}
		rackArray[rackPos] = tileLabel + ",";

		drawTileStorage();

		selectedTile = -1;
	}
}





function selectTile(tileID)
{
	// Clicking a selected tile returns it to the rack.
	if (tileID == selectedTile)
	{
		returnTile(-1);
		return;
	}
	drawTileStorage();
	
	selectedTile = tileID;
	
	var theTile = document.getElementById("tile" + selectedTile);
	theTile.className = "tile on";
	
	return true;
}

function handleKeypress(event)
{
	// NN4 passes the event as a parameter.  For MSIE4 (and others)
	// we need to get the event from the window.
	if (document.all)
	{
		event = window.event;
	}
	if (event.ctrlKey || event.altKey)
	{
		return true;
	}

	var key = event.which;
	if (!key)
	{
		key = event.keyCode;
	}

	if (key > 96)
	{
		key -= 32;
	}

	if (key != 13 && key != 32 && (key < 65 || key > 65 + 26))
	{
		return true;
	}

	if (key == 13)
	{
		// enter -> submit word(s)
		finalise();
	}
	else
	{
		key = String.fromCharCode(key);
		var onBoard = -1;
		for (var j = 0; j < rackArray.length; j++)
		{	
			var i = j;
			if (selectedTile > 0)
			{
				i = (i + selectedTile) % rackArray.length;
			}
			if (i != selectedTile && key == rackArray[i].replace (/,.*/, ""))
			{
				if (getTilePosition(i) != "")
				{
					onBoard = i;
				}
				else
				{
					selectTile(i);
					onBoard = -1;
					break;
				}
			}
		}
		if (onBoard != -1)
		{
			selectTile(onBoard);
		}
	}
	if (document.all)
	{
		event.cancelBubble = true;
		event.returnValue = false;
	}
	else
	{
		event.stopPropagation();
		event.preventDefault();
	}
		
	return false;
}

function getCookie(cookieName) {
	var theCookie = document.cookie;
	if (!theCookie) return 0;
	var cookies = theCookie.split("; ");
	for (var i = 0; i < cookies.length; ++i) {
		var nameVal = cookies[i].split("=");
		if (nameVal[0] == cookieName) return nameVal[1];
	}
	return 0;
}

function getHighScore() {
	return getCookie("sscrable_highscore");
}

function setHighScore(value) {
	document.cookie = "sscrable_highscore=" + value + ";expires=Tue, 19-Jan-2038 03:14:07 GMT";
}

function getBestPlay() {
	return getCookie("sscrable_bestplay");
}

function setBestPlay(value) {
	document.cookie = "sscrable_bestplay=" + value + ";expires=Tue, 19-Jan-2038 03:14:07 GMT";
}

function gameToString() {
	var str = score + "/" + swaps + "/" + rackArray.join(".") + "/" + tileBank.join("") + "/";
	for (var row = 1; row <= 15; ++row) {
		for (var col = 1; col <= 15; ++col) {
			var myPos = encodePos(col, row);
			if (placedArray[myPos]) {
				str += placedArray[myPos];
			} else if (multiplierArray[myPos]) {
				str += multiplierArray[myPos];
			}
			str += ".";
		}
	}
	return str;
}

function gameFromString(str) {
	var a = str.split("/");
	score = parseInt(a[0]);
	swaps = parseInt(a[1]);
	for (i = 0; i < rackArray.length; i++)
	{
		if (getTilePosition(i) != "")
		{
			emptyCell(getTilePosition(i));
		}
	}
	rackArray = a[2].split(".");
	tileBank = a[3].split("");
	a = a[4].split(".");
	var i = 0;
	for (var row = 1; row <= 15; ++row) {
		for (var col = 1; col <= 15; ++col) {
			var myPos = encodePos(col, row);
			if (a[i].length == 1) {
				multiplierArray[myPos] = "";
				placedArray[myPos] = a[i];
			} else {
				multiplierArray[myPos] = a[i]; // May be empty, or "2W", etc
				if (placedArray[myPos]) {
					emptyCell(myPos);
					placedArray[myPos] = "";
				}
			}
			++i;
		}
	}
	tilesRemaining = tileBank.length;
	selectedTile = -1;
	drawTileStorage();
	drawBoard();
}

function undoOrRedo() {
	var tmp = gameToString();
	gameFromString(undoSaved);
	undoSaved = tmp;
}

function saveGame() {
	document.cookie = "sscrable_savedgame=" + escape(gameToString()) + ";expires=Tue, 19-Jan-2038 03:14:07 GMT";
	alert("Game saved");
}

function loadGame() {
	var game = getCookie("sscrable_savedgame");
	if (game == 0) return;
	undoSaved = gameToString();
	gameFromString(unescape(game));
}

function sortRack() {
	undoSaved = gameToString();
	rackArray.sort(orderRack);
	drawTileStorage();
}

function orderRack(a, b) {
	// Empty rack spaces at the end.
	if (a == "") return 1;
	if (b == "") return -1;
	// Rack spaces corresponding to tiles on the board go after tiles
	// currently in the rack.
	if (a.match(/,./)) return 1;
	if (b.match(/,./)) return -1;
	// charCodeAt requires MSIE 5.5 or later: return (a.charCodeAt(0) - b.charCodeAt(0));
	if (a > b) return 1;
	if (a < b) return -1;
	return 0;
}

function giveUp() {
	if (tilesRemaining > 0)
	{
		// You could perhaps be stuck with between 1 and 6 tiles in the bag
		// (since you can't swap tiles then).  Or you could even end up in a
		// situation where the board layout is such that no unplayed tiles can
		// be played, although that's very unlikely to happen unless you're
		// deliberately trying to do it.
		if (!confirm("There are still tiles in the bag, really give up?"))
		{
			return;
		}
	}

	undoSaved = gameToString();

	var message = "";
	var penalty = 0;
	var tilesLeft = 0;
	for (var i = 0; i < rackArray.length; i++)
	{
		if (rackArray[i] != "")
		{
			var tileLabel = getTileLabel(i);
			var tilePosition = getTilePosition(i);

			if (tilePosition) emptyCell(tilePosition);

			++tilesLeft;
			penalty += tileScore[tileLabel];
			if (message.length) message += " + ";
			message += tileScore[tileLabel];
				
			rackArray[i] = "";
		}
	}
	if (message.length)
	{
		score -= penalty;
		if (tilesLeft > 1) message += " = " + penalty;
		alert("Penalty for unplayed tiles left on your rack: " + message);
		if (highscore > 0 && score > highscore) {
			alert("CONGRATULATIONS! You beat your highscore by finishing with a score of " + score + " points.");
		} else if (score <= 0) {
			alert("You finished with a score of " + score + " points.");
		} else {
			alert("CONGRATULATIONS! You finished with a score of " + score + " points.");
		}
		if (score > highscore) {
			setHighScore(score);
			highscore = score;
		}
		drawTileStorage();
		drawBoard();
	}
}

function withdrawTiles()
{
	// Return all tiles to the rack.
	for (var i = 0; i < rackArray.length; ++i)
	{
		if (getTilePosition(i))
		{
			selectedTile = i;
			returnTile(-1);
		}
	}
	drawTileStorage();
	
	selectedTile = -1;
}

