// Append ?raw to the URL of this page to view the raw source (for 
// comments, copyright & licence), or ?pretty for a colorized version.

function Minesweeper(){var self=this;this.SHOW_MINES_DELAY=2;this.VIRAL_CELL_INTERVAL=200;var cells=[];var currentMineMap=[];var currentNumMines=0;var currentNumFlags=0;var gameStatus=false;var gameStartTime=null;var mineMap=null;var cellMousedOver=null;var containerMouseDown=false;this.domNode=document.createElement("div");this.domNode.className="mns-window";var controls=document.createElement("div");controls.className="mns-controls";this.domNode.appendChild(controls);var flagsdisp=new LedDisplay(3,"000");flagsdisp.display.className+=" mns-nummines";controls.appendChild(flagsdisp.display);var smiley=document.createElement("div");smiley.className="mns-smiley mnshappy";controls.appendChild(smiley);var timedisp=new LedDisplay(3,"000");timedisp.display.className+=" mns-timedisp"
controls.appendChild(timedisp.display);var container=document.createElement("div");container.className="mns-container";this.domNode.appendChild(container);this.generate=function(numRows,numCols,numMines,mineMap){if(isNaN(numRows)||isNaN(numCols)||isNaN(numMines))throw"Invalid argument to Minesweeper.generate()";if(gameStatus){logEvent("gameend",{"date":todaysDateString(),"type":"abort"});}
numRows=(numRows<2)?2:numRows;numCols=(numCols<2)?2:numCols;self.domNode.removeChild(container);while(container.hasChildNodes()){container.removeChild(container.lastChild);}
container.style.width=numCols+"em";cells=new Array(numRows);container.style.height=numRows+"em";for(var i=0;i<numRows;i++){cells[i]=new Array(numCols);var cellsi=cells[i];for(var x=0;x<numCols;x++){var cell=document.createElement("div");cell.className="mns-cell";cell.style.top=i+"em";cell.style.left=x+"em";cell.handler=cellHandler;cell.toggle=cellFlagToggle;cell.onmouseover=cellMouseOver;cell.onmouseout=cellMouseOut;cell.colNum=x;cell.rowNum=i;cell.flag=false;cell.isMine=false;cell.checked=false;cellsi[x]=cell;container.appendChild(cell);}}
var mineMap=self.regenerate(numMines,mineMap,false);self.domNode.appendChild(container);logEvent("generate",{"date":todaysDateString(),"numRows":numRows,"numCols":numCols,"numMines":numMines,"genMines":mineMap});};this.regenerate=function(numMines,mineMap,userInit){if(isNaN(numMines))throw numMines+" is NaN -- can't be used by Minesweeper.regenerate()";numMines=(numMines<2)?2:numMines;container.className=container.className.replace(/ ?xflags/g,"");if(gameStatus&&userInit){logEvent("gameend",{"date":todaysDateString(),"type":"abort"});}
var rowFactor=cells.length-1;var cellFactor=cells[0].length-1;if((cells.length*cells[0].length)/2<numMines){numMines=(cells.length*cells[0].length)/2;}
if(!mineMap){mineMap=new Array(numMines);for(var i=0;i<numMines;i++){mineMap[i]={"row":Math.round(Math.random()*rowFactor),"col":Math.round(Math.random()*cellFactor)}}}
if(userInit){for(var i=0;i<cells.length;i++){var cellsi=cells[i];for(var x=0;x<cells[i].length;x++){cellsi[x].checked=false;cellsi[x].isMine=false;cellsi[x].flag=false;cellsi[x].className="mns-cell";while(cellsi[x].hasChildNodes()){cellsi[x].removeChild(cellsi[x].childNodes[0]);}}}}
gameStatus=true;setSmiley("happy");var tempMineMap=applyMineMap(0,mineMap);while(typeof tempMineMap!="object"){var tempMineMap=applyMineMap(tempMineMap,mineMap);}
currentMineMap=tempMineMap;currentNumMines=numMines;currentNumFlags=0;refreshFlagsInicator();self.clearTimerDisplay();gameStatus=true;if(userInit){logEvent("regenerate",{"date":todaysDateString(),"numRows":cells.length,"numCols":cells[0].length,"numMines":numMines,"genMines":currentMineMap});}
return tempMineMap;};function applyMineMap(offset,mineMap){for(var i=offset;i<mineMap.length;i++){var isMine=cells[mineMap[i].row][mineMap[i].col].isMine;if(isMine){mineMap[i]["row"]=Math.round(Math.random()*(cells.length-1));mineMap[i]["col"]=Math.round(Math.random()*(cells[0].length-1));return i;}
cells[mineMap[i]["row"]][mineMap[i]["col"]].isMine=true;}
return mineMap;};this.startGameIfNotStarted=function(){if(gameStartTime==null){container.className=container.className.replace(/ ?xflags/g,"");gameStartTime=new Date();self.timerIncrementer();window.setTimeout(function(){self.timerIncrementer();},1000);}};this.clearTimerDisplay=function(){timedisp.setString("000");};this.timerIncrementer=function(){if(gameStatus==true){var time=Math.floor(((new Date()).getTime()-gameStartTime.getTime())/1000);timedisp.setString(leadingZero(time,3));window.setTimeout(function(){self.timerIncrementer();},1010);}else{gameStartTime=null;}};container.onmousedown=function(event){if(!gameStatus)return;event=(!event)?window.event:event;setSmiley("scary");containerMouseDown=true;if(cellMousedOver&&cellMousedOver.flag!="isMine"&&(event.button!=2&&!event.ctrlKey)){cellMousedOver.className+=" mnsfocused";}};container.onmouseup=function(event){if(!gameStatus)return;event=(!event)?window.event:event;setSmiley("happy");if(cellMousedOver&&containerMouseDown){if(event.button==2||event.ctrlKey){cellMousedOver.toggle();}else if(cellMousedOver.flag!="isMine"){cellMousedOver.handler(false);cellMousedOver.className=cellMousedOver.className.replace(/mnsfocused/g,"");}}
containerMouseDown=false;return false;};container.oncontextmenu=function(){return false;};container.onselectstart=function(){return false;};container.onmouseout=function(event){event=(!event)?window.event:event;var target=(event.srcElement)?event.srcElement:event.target;if(this!=target)return;containerMouseDown=false;};function cellMouseOver(){cellMousedOver=this;if(containerMouseDown&&this.flag!="isMine"){this.className+=" mnsfocused";}};function cellMouseOut(){cellMousedOver=null;this.className=this.className.replace(/mnsfocused/g,"");};function cellHandler(isViral){if(!gameStatus)return;self.startGameIfNotStarted();if(this.checked||this.flag=="isMine")return;this.toggle(true,true);if(!isViral)logEvent("cellhandle",{"row":this.rowNum,"col":this.colNum});if(!this.isMine){var nearCells=[];var prevRow=cells[this.rowNum-1];if(prevRow){if(prevRow[this.colNum-1])nearCells.push(prevRow[this.colNum-1]);if(prevRow[this.colNum])nearCells.push(prevRow[this.colNum]);if(prevRow[this.colNum+1])nearCells.push(prevRow[this.colNum+1]);}
var thisRow=cells[this.rowNum];if(thisRow[this.colNum-1])nearCells.push(thisRow[this.colNum-1]);if(thisRow[this.colNum+1])nearCells.push(thisRow[this.colNum+1]);var nextRow=cells[this.rowNum+1];if(nextRow){if(nextRow[this.colNum-1])nearCells.push(nextRow[this.colNum-1]);if(nextRow[this.colNum])nearCells.push(nextRow[this.colNum]);if(nextRow[this.colNum+1])nearCells.push(nextRow[this.colNum+1]);}
var numMines=0;for(var i=0;i<nearCells.length;i++){if(nearCells[i].isMine){numMines++;}}
this.checked=true;this.className+=" checked";if(numMines==0){for(var i=0;i<nearCells.length;i++){if(!nearCells[i].isMine&&!nearCells[i].checked){(function(){var cell=nearCells[i];window.setTimeout(function(){cell.handler(true);},self.VIRAL_CELL_INTERVAL);})();}}}else{this.className+=" mnscell"+numMines;this.appendChild(document.createTextNode(numMines));}}else{this.className+=" mnsminehit";self.looseGame();}};function cellFlagToggle(forceClear,isViral){if(isViral!=true&&!this.checked){logEvent("celltoggle",{"row":this.rowNum,"col":this.colNum});}
if(forceClear){this.className=this.className.replace(/mnsflagged-(unknown|flag)/g,"");if(this.flag=="isMine"){currentNumFlags--;refreshFlagsInicator();}
this.flag=false;}else{if(this.flag==false&&!this.checked){this.flag="isMine";this.className+=" mnsflagged-flag";currentNumFlags++;refreshFlagsInicator();}else if(this.flag=="isMine"){this.flag="isUnknown";currentNumFlags--;refreshFlagsInicator();this.className=this.className.replace(/ mnsflagged-flag/g,"");this.className+=" mnsflagged-unknown";}else if(this.flag=="isUnknown"){this.className=this.className.replace(/ mnsflagged-unknown/g,"");this.flag=false;}}
checkMinesVsFlags();};function checkMinesVsFlags(){if(currentNumFlags!=currentNumMines)return false;for(var i=0;i<currentMineMap.length;i++){var cell=cells[currentMineMap[i]["row"]][currentMineMap[i]["col"]];if(cell.flag!="isMine"){return false;}}
alert("You win!");gameStatus=false;logEvent("gameend",{"date":todaysDateString(),"type":"win"})};function refreshFlagsInicator(){var dispNum=currentNumMines-currentNumFlags;if(dispNum<0){var dispString="-"+leadingZero(dispNum*-1,2);}else{var dispString=leadingZero(dispNum,3);}
flagsdisp.setString(dispString);};smiley.onmousedown=function(){this.className+=" mnssmileydown";};smiley.onmouseout=function(){this.className=this.className.replace(/ mnssmileydown/g,"");};smiley.onmouseup=function(){this.className=this.className.replace(/ mnssmileydown/g,"");self.regenerate(currentNumMines,null,true);};this.looseGame=function(){gameStatus=false;setSmiley("dead");container.className+=" xflags";logEvent("gameend",{"date":todaysDateString(),"type":"loss"});var i=0;function step(){if(i<currentMineMap.length){var cell=cells[currentMineMap[i]["row"]][currentMineMap[i]["col"]];var clss=(cell.flag=="isMine")?"mnsflagged-trueflag":"mnsmine";cell.className=cell.className.replace(/ ?mnsflagged-(flag|unknown)/,"")+" "+clss;i++;window.setTimeout(step,self.SHOW_MINES_DELAY);}};step();};function setSmiley(to){smiley.className=smiley.className.replace(/mns(smiley|scary|dead)/g,"");if(to=="happy"){smiley.className+=" mnshappy";}else if(to=="scary"){smiley.className+=" mnsscary";}else if(to=="dead"){smiley.className+=" mnsdead";}}
var log=new Array();var logStart=null;this.onlog=function(){};var logEvent=function(type,values){if(logStart==null){logStart=new Date();log[log.length]={"type":"start","offset":0,"values":{"date":todaysDateString(logStart)}}
self.onlog(log);}
var offset=(new Date()).getTime()-logStart.getTime();log[log.length]={"type":type,"offset":offset,"values":values}
self.onlog(log);};this.getLog=function(){return log;};this.pullLog=function(){return log.splice(0,log.length);};this.clearLog=function(){log.splice(0,log.length);return true;};var playbackLog=null;var playbackLogStep=0;var playbackLogIncrement=100;var playbackLogFlag=0;var playbackLogBuffer=0;this.getPlaybackLog=function(){return(playbackLog)?playbackLog:false;};this.getCurrentPlaybackLogEntry=function(){return(playbackLog[playbackLogStep])?playbackLog[playbackLogStep]:false;};this.playbackLog=function(thisLog,increment){if(gameStatus!=false){return false;}
playbackLog=(thisLog)?thisLog:log;playbackLogIncrement=(increment)?increment:playbackLogIncrement;playbackLogStep=0;self.onplaybackstart();self.logStep();};this.logStep=function(){if(playbackLogFlag>0){playbackLogBuffer[playbackLogBuffer.length]++;return;}
processLogStep(playbackLog[playbackLogStep]);playbackLogStep++;while(playbackLogBuffer>0){processLogStep(playbackLog[playbackLogStep]);playbackLogStep++;playbackLogBuffer--;}
if(playbackLog[playbackLogStep]){window.setTimeout(function(){self.logStep();},playbackLogIncrement);}else{self.onlogmessage("End of Log");self.onplaybackend();}};this.onplaybackstart=function(){};this.onplaybackend=function(){};this.onplaybackstepstart=function(){};this.onplaybackstep=function(){};this.onplaybackstepend=function(){};this.onlogmessage=function(){};function processLogStep(logEntry){self.onplaybackstepstart();playbackLogFlag++;switch(logEntry.type){case"start":break;case"generate":self.onlogmessage("New "+logEntry.values.numRows+" x "+logEntry.values.numCols+" game board.  Contains "+logEntry.values.numMines+" mines.");self.generate(logEntry.values.numRows,logEntry.values.numCols,logEntry.values.numMines,logEntry.values.genMines);break;case"regenerate":self.regenerate(logEntry.values.numMines,logEntry.values.genMines,true);self.onlogmessage("New "+logEntry.values.numRows+" x "+logEntry.values.numCols+" mine map.  Contains "+logEntry.values.numMines+" mines.");self.generate(logEntry.values.numRows,logEntry.values.numCols,logEntry.values.numMines,logEntry.values.genMines);break;case"cellhandle":cells[logEntry.values.row][logEntry.values.col].handler();break;case"celltoggle":cells[logEntry.values.row][logEntry.values.col].toggle();break;case"gameend":switch(logEntry.values.type){case"win":self.onlogmessage("Win!");break;case"loss":self.onlogmessage("Loss.");break;case"abort":self.onlogmessage("Game aborted.");break;}
break;}
playbackLogFlag--;self.onplaybackstepend();self.onplaybackstep();};function leadingZero(string,places){while(string.toString().length<places){string="0"+string;}
return string;};function todaysDateString(d){if(!d){d=new Date();}
return d.getUTCFullYear()+"-"+leadingZero(d.getUTCMonth(),2)+"-"+leadingZero(d.getUTCDate(),2)+"T"+leadingZero(d.getUTCHours(),2)+":"+leadingZero(d.getUTCMinutes(),2)+":"+leadingZero(d.getUTCSeconds(),2)+"."+leadingZero(d.getUTCMilliseconds(),3)+" UTC";};this.preload=function(){var files=["flag.png","mine.png","smiley-dead.png","smiley-happy.png","smiley-scary.png","unknown.png","xmine.png"];var images=[],loaded=0;for(var i=0;i<files.length;i++){images[i]=new Image(22,22);images[i].src=files[i];images[i].onload=function(){loaded++;if(loaded>=files.length)self.onpreloadcomplete();};}};this.onpreloadcomplete=function(){};this.toString=function(){var s="[object Minesweeper";alert(mineMap);if(mineMap){s+="("+mineMap.numCols+"x"+mineMap.numRows+" with "+mineMap.numMines+" mines: "+gameStatus+")";}else{s+="(no board)";}
return s+"]"};this.preload();};function LedDisplay(numDigits,startingString){var self=this;this.display=document.createElement("div");this.display.className="led-display";var digits=new Array(numDigits);for(var i=0;i<digits.length;i++){digits[i]=new LedDigit();self.display.appendChild(digits[i].el);}
this.setString=function(newString){newString=newString.toString().split("");for(var i=0;i<numDigits;i++){digits[i].set(newString[i]);}};if(startingString!="undefined"){self.setString(startingString);}};function LedDigit(){var self=this;this.el=document.createElement("div");this.el.className="led-digit";var bars=new Array(7);for(var i=0;i<7;i++){bars[i]=document.createElement("div");bars[i].className="digit-bar"+(i+1)+" off";self.el.appendChild(bars[i]);bars[i].on=function(){this.className=this.className.replace(/off/,"on");};bars[i].off=function(){this.className=this.className.replace(/on/,"off");};}
this.barsSet=function(array){for(var i=0;i<7;i++){bars[i][array[i]]();}};this.set=function(chr){switch(chr){case"-":self.barsSet(["off","off","off","on","off","off","off"]);break;case"0":self.barsSet(["on","on","on","off","on","on","on"]);break;case"1":self.barsSet(["off","off","on","off","off","on","off"]);break;case"2":self.barsSet(["on","off","on","on","on","off","on"]);break;case"3":self.barsSet(["on","off","on","on","off","on","on"]);break;case"4":self.barsSet(["off","on","on","on","off","on","off"]);break;case"5":self.barsSet(["on","on","off","on","off","on","on"]);break;case"6":self.barsSet(["on","on","off","on","on","on","on"]);break;case"7":self.barsSet(["on","off","on","off","off","on","off"]);break;case"8":self.barsSet(["on","on","on","on","on","on","on"]);break;case"9":self.barsSet(["on","on","on","on","off","on","on"]);break;default:self.barsSet(["off","off","off","off","off","off","off"]);break;}};};
