// Minesweeper class
/********************************************************************
* Minesweeper game created by Robert Donner und Curt Johnson, Microsoft (c) 1981
* game-code of this Minesweeper clone created by Jürgen Müller-Lütken, (c) 2008
* v4 changed the smiley at winning to 8); 20.12.2009
* v5 changed the method Shuffle(); changed the mine-symbol from ö to ó 21.12.2009
* v6 changed the method Shuffle() again; deleted the form element; returned false in the function newgame()
* Home: http://www.onlinespiele-sammlung.de/minesweeper/minesweeper-like-game.php
********************************************************************/
var gamePlace = "GameHolder"; // name of the div, where the game will be showed

var colorBg = "#ffc"; // the background-color of the opened fields
var color0 = "#fc0"; // the color of the default-values, which are written into the fields on game-start
var bombSymbol = "ó"; // the sign, which symbolizes the mines
// color 1 to 8, the colors of the digits showing the number of adjacent mines; 
var color1 = "#00f"; // blue
var color2 = "#008000"; // green
var color3 = "#f00"; // red
var color4 = "#000080"; // navy
var color5 = "#800000"; // maroon
var color6 = "#008080"; // purple
var color7 = "#800080"; // olive
var color8 = "#000"; // black
var color9 = "#000"; // the color of the mines

var className = (navigator.appName.indexOf("Microsoft") != -1) ? "className" : "class";
var MouseClick = "";
var myGame;//,NoB,Timer,Statuus;
function newGame(cols, rows, mines, squaresize)
{
	if(typeof(myGame) == "undefined") {
		myGame = new MineField(cols, rows, mines, squaresize);
		myGame.StartNewGame();
		}
	else
	{
		myGame.setCols(cols);
		myGame.setRows(rows);
		myGame.setMines(mines);
		myGame.setSquare(squaresize);
		myGame.StartNewGame();
	}
	return false;
}

function MineField(cols, rows, mines, squaresize) // the mindfield class
{
	var TotalFields,TotalMines,EmptyCells,MixedMines,GameOn,ClockIsRunning,MouseClick,NoB,Statuus,Timer,Clock;
	setCols(cols);
	setRows(rows);
	setMines(mines);
	setSquare(squaresize);
	this.Cols = getCols();
	this.Rows = getRows();
	this.Mines = getMines();
	this.Square = getSquare();
	this.setCols = setCols;
	this.setRows = setRows;
	this.setMines = setMines;
	this.setSquare = setSquare;
	this.StartNewGame = CreateGameField;
	var IsCreated = 0;
	
	// public; set the number of columns
	function setCols(value)
	{
		this.Cols = value;
	}
	// private; get the number of columns
	function getCols()
	{
		return this.Cols;
	}
	// public; set the number of rows
	function setRows(value)
	{
		this.Rows = value;
	}
	// private; get the number of rows
	function getRows()
	{
		return this.Rows;
	}
	// public; set the number of mines
	function setMines(value)
	{
		this.Mines = value;
	}
	// private; get the number of mines
	function getMines()
	{
		return this.Mines;
	}
	// public; set the value for width and height of the field-squares
	function setSquare(value)
	{
		this.Square = value;
	}
	// private; get the value for width and height of the field-squares
	function getSquare()
	{
		return this.Square;
	}
	// private; set the value for the total of marked mines
	function setTotalMines(value, start)
	{
		TotalMines = (!TotalMines || start) ? Number(value) :  Number(TotalMines)+Number(value);
	}
	// private; get the value for the total of marked mines
	function getTotalMines()
	{
		return TotalMines;
	}
	// private; set the value for the total of opened squares
	function setTotalFields(value, start)
	{
		TotalFields = (!TotalFields || start) ? Number(value) :  Number(TotalFields)+Number(value);
	}
	// private; get the value for the total of opened squares
	function getTotalFields()
	{
		return TotalFields;
	}
	// private; create a new array for empty squares, which have to be opened or append an empty square to the array
	function setEmptyCells(value)
	{
		if(!EmptyCells || !value)
			EmptyCells = new Array();
		else
			EmptyCells.push(value);
	}
	//private; get the array of empty squares
	function getEmptyCells()
	{
		return EmptyCells;
	}
	// private; remove the first element of the array EmptyFields
	function shiftEmptyCells()
	{
		EmptyCells.shift();
	}
	// private; set the boolean value of the game status
	function setGameStatus(value)
	{
		GameOn = value;
	}
	// private; get the boolean value of the game status
	function getGameStatus()
	{
		return GameOn;
	}
	// private; set the boolean value of the timer status
	function setTimerStatus(value)
	{
		ClockIsRunning = value;
	}
	// private; get the boolean value of the timer status
	function getTimerStatus()
	{
		return ClockIsRunning;
	}
	// private; set a string value of the mouse, which was used (Right - R or Left - L)
	function setMouseStatus(value)
	{
		MouseClick = value;
	}
	// private; get the string value of the mouse-key
	function getMouseStatus()
	{
		return MouseClick;
	}
	
	// convert style to camel case
	// http://www.ruzee.com/blog/2006/07/retrieving-css-styles-via-javascript/#more-107
	function rzCC(s)
	{
		for(var exp=/-([a-z])/; exp.test(s); s=s.replace(exp,RegExp.$1.toUpperCase()));
		return s;
	}
	// http://www.peterbe.com/plog/setAttribute-style-IE (comment from Luis Serrano - 29th July 2008)
	function setStyle(aElement /* object */, aDeclaration /* CSS Declaration */)
	{
		try 
		{
			if (aElement) 
			{
				//aDeclaration = aDeclaration.replace(/\s+/g,''); // remove all white spaces; not useable with border styles like border: 1px solid #000;
				if (document.all) 
				{ // IE Hack
					if (aDeclaration.charAt(aDeclaration.length-1)==';')
						aDeclaration = aDeclaration.slice(0, -1);
					var k, v;
					var splitted = aDeclaration.split(';');
					for (var i=0; i<=splitted.length; i++) 
					{
						k = rzCC(splitted[i].split(':')[0]);
						v = splitted[i].split(':')[1];
						 eval("aElement.style."+k+"='"+v+"'");
					}
					return (true);
				}
				else 
				{ // The clean way
					aElement.setAttribute ('style', aDeclaration);
					return (true);
				}
			}
		} 
		catch (e) 
		{
			return (false);
		}
	}
	
	// public; create a new game
	function CreateGameField()
	{
		// set the necessary variables for a new game
		var target = "GameHall";
		setCols(this.Cols);
		setRows(this.Rows)
		setMines(this.Mines);
		setSquare(this.Square);
		if(this.Mines > (this.Cols * this.Rows))
			setTotalMines((this.Cols * this.Rows), 1);
		else
			setTotalMines(this.Mines, 1);
		setTotalFields((this.Cols * this.Rows) - this.Mines, 1);
		setEmptyCells();
		// create an array with a 0-value for each square of the gamefield and a 9-value for each mine
		MixedMines = MineLayer(); 
		if(getTimerStatus())
			Clock.stopChronometer();
		GameOn = setGameStatus(false);
		ClockIsRunning = setTimerStatus(false);
		MouseClick = setMouseStatus("");
		var Counter = 0;
		if(IsCreated == 1)
		{
			var el = document.getElementById("GameTable");
			el.parentNode.removeChild(el);
		}
		
		var T1 = document.createElement("table");
		T1.setAttribute("id", "GameTable");
		T1.setAttribute("cellSpacing", 0);
		T1.setAttribute("cellPadding", 0);
		document.getElementById(gamePlace).appendChild(T1);
		var T2 = document.createElement("tbody");
		T1.appendChild(T2);
		var TR = document.createElement("tr");
		T2.appendChild(TR);
		var TD = document.createElement("td");
		TD.setAttribute("id", "GameTableTopTd");
		TR.appendChild(TD);
		
		
		
		var T11 = document.createElement("table");
		T11.setAttribute("cellSpacing", 0);
		T11.setAttribute("cellPadding", 0);
		T11.setAttribute("id", "Displays");
		T11.setAttribute("align", "center");
		var T22 = document.createElement("tbody");
		T11.appendChild(T22);
		
		TR = document.createElement("tr");
		
		var TDintern = document.createElement("td");
		TDintern.setAttribute("align", "left");
		var P = document.createElement("p");
		P.setAttribute("id", "NumberOfMines");
		TDintern.appendChild(P);
		TR.appendChild(TDintern);
		
		TDintern = document.createElement("td");
		TDintern.setAttribute("align", "center");
		P = document.createElement("p");
		P.setAttribute("id", "Statuus");
		// dirty, but working; FROM: http://justinfrench.com/index.php?id=25 - instead of P.setAttribute("onclick", "myGame.StartNewGame()");
		P.onclick = function() {myGame.StartNewGame() };
		TDintern.appendChild(P);
		TR.appendChild(TDintern);
		
		TDintern = document.createElement("td");
		TDintern.setAttribute("align", "right");
		P = document.createElement("p");
		P.setAttribute("id", "Time");
		TDintern.appendChild(P);
		TR.appendChild(TDintern);
		T22.appendChild(TR);
		TD.appendChild(T11);
		
		
		TR = document.createElement("tr");
		TD = document.createElement("td");
		TD.setAttribute("id", "GameHall");
		TD.setAttribute("align", "center");
		TR.appendChild(TD);
		T2.appendChild(TR);
		NoB = document.getElementById("NumberOfMines");
		Statuus = document.getElementById("Statuus");
		Timer = window.document.getElementById('Time');
		Clock = new clock('Time');
		NoB.InnerText = InnerText;
		Statuus.InnerText = InnerText;
		Timer.InnerText = InnerText;
		
		var T12 = document.createElement("table");
		T12.setAttribute("cellSpacing", 0);
		T12.setAttribute("cellPadding", 0);
		T12.setAttribute("border", 0);
		document.getElementById(target).appendChild(T12);
		setStyle(T12, "width:" + ((this.Cols * (this.Square + 2)) + 6) + "px;height:" + ((this.Rows * (this.Square + 2)) + 6) + "px");
		T2 = document.createElement("tbody");
		T12.appendChild(T2);
		for(var r=0;r<this.Rows;r++)
		{
			TR = document.createElement("tr");
			T2.appendChild(TR);
			for(var c=0;c<this.Cols;c++)
			{
				var myTd = CreateCell(this, r, c, MixedMines[Counter++]);
				TR.appendChild(myTd);
				var Text = document.createTextNode("o");
				myTd.appendChild(Text);
			}
		}
		for(r=0;r<this.Rows;r++)
		{
			for(c=0;c<this.Cols;c++)
			{
				CheckSurroundingSquares(r, c, 9);
			}
		}
		IsCreated = 1;
		
		Statuus.InnerText(":)");
		NoB.InnerText(formatMinesString(TotalMines));
		Timer.InnerText("000");
		setGameStatus(true);
	}
	// set new text
	function InnerText(text)
	{
		var Text = document.createTextNode(text);
		if(this.firstChild)
		{
			this.replaceChild(Text, this.firstChild);
		}
		else
			this.appendChild(Text);
	}
	// private; set all values and methods for each square
	function CreateCell(el, r, c, mine)
	{
		var TD = document.createElement("td");
		TD.setAttribute("id", "R" + r + "C" + c);
		setStyle(TD, "width:" + this.Square + "px;height:" + this.Square + "px;text-align:center");
		TD.UncoverCell = UncoverCell;
		TD.onmouseup = UncoverCell;
		TD.onmousedown = ToggleFlag;
		TD.StartClick = StartClick;
		TD.InnerText = InnerText;
		TD.r = r;
		TD.c = c;
		TD.mine = mine;
		TD.status = 0;
		TD.oncontextmenu = function(){return false;};
		return TD;
	}
	// public; all this have to be done after a click on a square
	function UncoverCell()
	{
		var eC = getEmptyCells();
		if(getGameStatus())
		{
			Statuus.InnerText(":)");
			if(!getTimerStatus())
			{
				Clock.startChronometer();
				setTimerStatus(true);
			}
			// on a mouse-left-click
			if((this.status == 0 || this.status == 5) && getMouseStatus() == "L")
			{
				// change some styles and values of the opened square (table-cell)
				this.status = 1;
				this.style.backgroundColor = colorBg;
				this.style.border = "none";
				this.style.borderRight = "1px dotted #999";
				this.style.borderBottom = "1px dotted #999";
				this.InnerText(this.mine);
				setTotalFields(-1);
				// depending on the number of surrounding mines or the inner mine itself
				switch(this.mine)
				{
					// no mine around
					case 0:
					// open this square and all squares around this one; these ones cannot be opened here directly because of "Stack overflow" depending on exponential growing of squares which have to be opened;  so I collect all surrounding squares in an array to open them one after the other at the end of this method; in IE there is still a Stack overflow here
					// TODO: fix the Stack overflow in IE
					CheckSurroundingSquares(this.r, this.c, 0);
					this.style.color = colorBg;
					break;
					
					case 1:
					this.style.color = color1;
					break;
					
					case 2:
					this.style.color = color2;
					break;
					
					case 3:
					this.style.color = color3;
					break;
					
					case 4:
					this.style.color = color4;
					break;
					
					case 5:
					this.style.color = color5;
					break;
					
					case 6:
					this.style.color = color6;
					break;
					
					case 7:
					this.style.color = color7;
					break;
					
					case 8:
					this.style.color = color8;
					break;
					// this square contains a mine
					case 9:
					setGameStatus(false);
					this.style.color = color9;
					this.style.backgroundColor = "#f00";
					this.InnerText(bombSymbol);
					Clock.stopChronometer();
					setTimerStatus(false);
					Statuus.InnerText(":(");
					UncoverAllMines();
					break;
				}
				
			}
			// this happens after a right-mouse-click
			else if(getMouseStatus() == "R" && this.status != 1)
			{
				this.style.borderTop = "1px solid #fff";
				this.style.borderLeft = "1px solid #fff";
				this.style.borderRight = "1px solid #999";
				this.style.borderBottom = "1px solid #999";
				// if the square is not open, set a flag
				if(this.status == 0)
				{
					this.style.color = "#f00";
					this.InnerText("F");
					this.status = 2;
					setTotalMines(-1);
					NoB.InnerText(formatMinesString(TotalMines));
				}
				// if it has a flag, change it to a question-mark
				else if(this.status == 2)
				{
					this.style.color = color4;
					this.InnerText("?");
					this.status = 3;
					setTotalMines(1);
					NoB.InnerText(formatMinesString(TotalMines));
				}
				// or clear the question-mark
				else
				{
					this.style.color = color0;
					this.InnerText("o");
					this.status = 0;
				}
			}
			// if any empty square (with no surrounding mines) is opened, open the surrounding squares here
			if(getEmptyCells().length > 0)
			{
				var cell = eC[0];
				shiftEmptyCells();
				if(className == "className")
					setTimeout(function() {cell.UncoverCell(); }, 1);
				else
					cell.UncoverCell();
				return;
			}
			
		}
		// set the focus to the opened square (necessary in Firefox)
		this.focus();
		// if all squares without mines are opened the game is over, the player wins
		if(TotalFields <= 0 && getGameStatus() == true)
		{
			setGameStatus(false);
			Clock.stopChronometer();
			setTimerStatus(false);
			Statuus.InnerText("8)");
		}
	}
	// private; due to the original Microsoft game the display of mines will be formatted in the same way
	function formatMinesString(total)
	{
		var minesStr = "";
		if(total == 0)
			minesStr = "000";
		else if(total < 0 && total > -10)
			minesStr = "-00" + Math.abs(total);
		else if(total <= -10 && total > -100)
			minesStr = "-0" + Math.abs(total);
		else if(total <= -100)
			minesStr = total;
		else if(total >= 0 && total < 10)
			minesStr = "00" + total;
		else if(total >= 10 && total < 100)
			minesStr = "0" + total;
		else
			minesStr = total;
		return minesStr;
	}
	// private; a block of 9 squares (the center is the checked square) get checked for mines or empty squares
	function CheckSurroundingSquares(r, c, s)
	 {
		var sq = 9;
		var rr = cc = counter = 0;
		var d = -1;
		
		while(sq > 0)
		{
			rr = r + d;
			for(var i=-1; i<2; i++)
			{
				cc = c + i;
				if(cc > -1 && cc < this.Cols && rr >-1 && rr < this.Rows)
				{
					var cell = document.getElementById("R" + rr + "C" + cc);
					switch(s)
					{
						case 9:
						if(cell.mine == 9)
							counter++;
						break;
						
						case 0:
						if(cell.mine != 9 && cell.status != 1 && cell.status != 5)
						{
							cell.status = 5;
							setEmptyCells(cell);
						}
						break;
					}
				}
				sq--;
			}
			d++;
		}
		// set the number of surrounding mines
		if(s == 9)
		{
			var checkedCell = document.getElementById("R" + r + "C" + c);
			if(checkedCell.mine != 9)
				checkedCell.mine = counter;
		}
	 }
	// public; change the value of the variable MouseClick depending of which mouse-key is clicked
	function ToggleFlag(e)
	{
		if(!e)
			var e=window.event;
		if(e.which)
			setMouseStatus((e.which<=1)?"L":"R");

		else if(e.button)
			setMouseStatus((e.button==1)?"L":"R");
		this.StartClick();
	}
	// change the face's look according to the original Microsoft game, and the borders of the clicked square
	function StartClick()
	{
		if(getMouseStatus() == "L" && getGameStatus() && this.status == 0)
		{
			Statuus.InnerText(":o");
			this.style.borderTop = "1px solid #666";
			this.style.borderLeft = "1px solid #666";
			this.style.borderRight = "1px solid #ccc";
			this.style.borderBottom = "1px solid #ccc";
		}
	}
	
	// private; the game is over - a square with a mine was clicked, all mines get opened	
	function UncoverAllMines()
	{
		for(var r=0; r<this.Rows; r++)
		{
			for(var c=0; c<this.Cols; c++)
			{
				var cell = document.getElementById("R" + r + "C" + c);
				if(cell.mine == 9 && cell.status != 1 && cell.status != 2)
				{
					cell.style.color = color9;
					cell.style.backgroundColor = colorBg;
					cell.style.border = "none";
					cell.style.borderRight = "1px dotted #999";
					cell.style.borderBottom = "1px dotted #999";
					cell.InnerText(bombSymbol);
				}
				else if(cell.mine != 9 && cell.status == 2)
				{
					cell.style.color = "#f00";
					cell.style.backgroundColor = colorBg;
					cell.style.border = "none";
					cell.style.borderRight = "1px dotted #999";
					cell.style.borderBottom = "1px dotted #999";
					cell.InnerText("X");
				}
			}
		}
	}
	// private; the mines get a random distribution
	/*
	Array.prototype.Shuffle = function() 
	{
		this.sort(function() 
		{
			return 0.5 - Math.random();
		});
	};
	*/
	Array.prototype.Shuffle = function()
	{
		var tmp, rand;
		for(var i =0; i < this.length; i++)
		{
			rand = Math.floor(Math.random() * this.length);
			tmp = this[i]; 
			this[i] = this[rand]; 
			this[rand] = tmp;
		}
	}
	 // TODO: find a better way for placing the mines
	// create an array of all squares and mix the mines between them
	function MineLayer()
	{
		var bombsArray = new Array(this.Rows*this.Cols);
		var bombsTemp = getMines();
		for(var i=0; i<(this.Rows*this.Cols); i++)
		{
			bombsArray[i] = (bombsTemp > 0) ? 9 : 0;
			bombsTemp--;
		}
		bombsArray.Shuffle();
		return bombsArray;
	}
}
