/**
 * this is the game of mines implemented with javascript/css/html
 *
 * Released under terms of the MIT license
 * The original source code is at http://github.com/MadRabbit/jsminer/tree/master
 *
 * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov-gm-ail-com>
 */

var JSMiner=new Class({element:null,opts:null,game:null,ui:null,initialize:function(element,options){this.element=$(element);this.game=new JSMiner.Game();this.opts=new JSMiner.Options(this,options);this.ui=new JSMiner.UI(this);this.rebuild();},setSize:function(width,height){var allowed=true;if(this.game.isActive()){var allowed=window.confirm("This will reset the current game\nProcess?");}if(allowed){this.opts.setSize(width,height);this.rebuild();}return this;},getSize:function(){return this.opts.getSize();},setBlockSize:function(size){this.opts.setBlockSize(size);this.ui.update();return this;},getBlockSize:function(){return this.opts.getBlockSize();},setLevel:function(level){var allowed=true;if(this.game.isActive()){var allowed=window.confirm("This will reset the curren game\nProcess?");}if(allowed){this.opts.setLevel(level);this.reset();}return this;},getLevel:function(){return this.opts.getLevel();},reset:function(){this.game.reset();this.ui.update();return this;},rebuild:function(){this.game.reset();this.ui.build();return this;},pause:function(){this.game.pause();this.ui.update();},paused:function(){return this.game.paused;},active:function(){return this.game.isActive();},failed:function(){return this.game.isFailed();},won:function(){return this.game.won;},getMinesMap:function(){return this.game.map;},getMinesNum:function(){return this.game.mines;},hitCell:function(cell){this.game.hitCell(cell);this.ui.update(cell);},markCell:function(cell){this.game.markCell(cell);this.ui.update(cell);}});
JSMiner.extend({DEFAULT_WIDTH:8,DEFAULT_HEIGHT:8,DEFAULT_LEVEL:'normal',DEFAULT_BLOCK_SIZE:'normal',BLOCK_SIZES:['big','small','normal'],LEVELS:{'easy':9,'normal':6,'hard':3}});JSMiner.Options=new Class({controller:null,fieldElement:null,timerElement:null,scoreElement:null,smileElement:null,sizeOptionsElement:null,blockOptionsElement:null,levelOptionsElement:null,width:null,height:null,blockSize:null,level:null,initialize:function(controller,options){var options=options||{};this.controller=controller;this.findPageElements(options);this.setDefaults(options);this.initOptionElements();},setSize:function(width,height){var width=parseInt(width);var height=parseInt(height);this.width=(isNaN(width)||width<1)?(this.width||JSMiner.DEFAULT_WIDTH):width;this.height=(isNaN(height)||height<1)?(this.height||JSMiner.DEFAULT_HEIGHT):height;if(this.width!=JSMiner.DEFAULT_WIDTH||this.height!=JSMiner.DEFAULT_HEIGHT){Cookie.write('jsminer_size',this.width+'x'+this.height);}else{Cookie.dispose('jsminer_size');}this.controller.game.setSize(this.width,this.height);},getSize:function(){return[this.width,this.height];},setLevel:function(level){this.level=JSMiner.LEVELS[level]?level:(this.level||JSMiner.DEFAULT_LEVEL);if(this.level!=JSMiner.DEFAULT_LEVEL){Cookie.write('jsminer_level',this.level);}else{Cookie.dispose('jsminer_level');}this.controller.game.setLevel(JSMiner.LEVELS[this.level]);},getLevel:function(){return this.level;},setBlockSize:function(size){this.blockSize=JSMiner.BLOCK_SIZES.contains(size)?size:(this.blockSize||JSMiner.DEFAULT_BLOCK_SIZE);if(this.blockSize!=JSMiner.DEFAULT_BLOCK_SIZE){Cookie.write('jsminer_block_size',this.blockSize);}else{Cookie.dispose('jsminer_block_size');}},getBlockSize:function(){return this.blockSize;},findPageElements:function(options){var element=this.controller?this.controller.element:new Element('div');['field','timer','score','smile','size-options','block-options','level-options'].each(function(id){var name=id.camelCase();this[name+'Element']=options[name]?$(options[name]):(element.getElementById(id)||element.getFirst('#'+id));},this);},setDefaults:function(options){var size=Cookie.read('jsminer_size');size=size?size.split('x'):[];if(size.length==2){this.setSize(size[0],size[1]);}else{this.setSize(options['width'],options['height']);}var block_size=Cookie.read('jsminer_block_size');if(block_size&&JSMiner.BLOCK_SIZES.contains(block_size)){this.setBlockSize(block_size);}else{this.setBlockSize(options['blockSize']);}var level=Cookie.read('jsminer_level');if(level&&JSMiner.LEVELS[level]){this.setLevel(level);}else{this.setLevel(options['level']);}},initOptionElements:function(){if(this.sizeOptionsElement){this.initSizeOptions();}if(this.blockOptionsElement){this.initBlockOptions();}if(this.levelOptionsElement){this.initLevelOptions();}},initSizeOptions:function(){var size=this.getSize(),controller=this.controller;this.sizeOptionsElement.value=size[0]+'x'+size[1];this.sizeOptionsElement.onchange=function(){var size=this.value.split('x');controller.setSize(size[0],size[1]);var size=controller.getSize();this.value=size[0]+'x'+size[1]};},initBlockOptions:function(){var controller=this.controller;this.blockOptionsElement.value=this.getBlockSize();this.blockOptionsElement.onchange=function(){controller.setBlockSize(this.value);this.value=controller.getBlockSize();};},initLevelOptions:function(){var controller=this.controller;this.levelOptionsElement.value=this.getLevel();this.levelOptionsElement.onchange=function(){controller.setLevel(this.value);this.value=controller.getLevel();};}});
JSMiner.Cell=new Class({mined:null,boomed:null,marked:null,markedWrong:null,nearMinesNum:null,top:null,left:null,explored:null,initialize:function(top,left){this.top=top;this.left=left;this.reset();},reset:function(){this.mined=false;this.boomed=false;this.marked=false;this.markedWrong=false;this.nearMinesNum=0;this.explored=false;}});
JSMiner.Game=new Class({height:null,width:null,level:null,mines:null,timer:null,map:null,paused:null,filledUp:null,over:null,won:null,initialize:function(){this.level=JSMiner.LEVELS[JSMiner.DEFAULT_LEVEL];this.setSize(JSMiner.DEFAULT_WIDTH,JSMiner.DEFAULT_HEIGHT);},setSize:function(width,height){if(this.width!=width||this.height!=height){this.height=height;this.width=width;this.map=null;}this.reset();},setLevel:function(level){this.level=level;this.reset();},reset:function(){this.mines=Math.floor(this.height*this.width / this.level);this.timer=0;this.paused=null;this.filledUp=false;this.over=false;this.won=false;if(this.map){this.resetMap();}else{this.buildMap();}if(this.updateTimerInterval){window.clearInterval(this.updateTimerInterval);}},run:function(top,left){var top=arguments.length<2?$random(0,this.height):top;var left=arguments.length<2?$random(0,this.width):left;this.fillMap(top,left);this.updateTimerInterval=this.updateTimer.periodical(1000,this);this.paused=false;},pause:function(){this.paused=!this.paused;},isActive:function(){return!this.paused&&!this.over&&this.filledUp;},isFailed:function(){return this.over&&!this.won;},hitCell:function(cell){if(this.over){return false;}if(!this.filledUp){this.run(cell.top,cell.left);}if(!cell.marked){if(cell.explored){this.trySiblingsAutoExplore(cell);}else{cell.explored=true;if(cell.mined){cell.boomed=true;return this.gameOver();}else if(cell.nearMinesNum==0){this.trySiblingsAutoExplore(cell);}this.checkForWin();}}},markCell:function(cell){if(this.over){return false;}cell.marked=cell.explored?false:!cell.marked;this.checkForWin();},flatCells:function(){this.flattenCells=this.flattenCells||this.map.flatten();return this.flattenCells;},updateTimer:function(){if(!this.paused&&!this.over){this.timer++;this.updateTimerCallback(this.timer);}},updateTimerCallback:function(time){},areaCells:function(top,left){if(top instanceof JSMiner.Cell){var left=top.left;var top=top.top;}var cells=[];var start_x=top>0?top-1:0;var start_y=left>0?left-1:0;var end_x=top<this.height-1?top+2:this.height;var end_y=left<this.width-1?left+2:this.width;for(var x=start_x;x<end_x;x++){for(var y=start_y;y<end_y;y++){cells.push(this.map[x][y]);}}return cells;},resetMap:function(){for(var i=0;i<this.height;i++){for(var j=0;j<this.width;j++){this.map[i][j].reset();}}},buildMap:function(){this.map=[];for(var i=0;i<this.height;i++){this.map[i]=[];for(var j=0;j<this.width;j++){this.map[i][j]=new JSMiner.Cell(i,j);}}this.filledUp=false;this.flattenCells=null;},fillMap:function(top,left){var exclude=(arguments.length==2?this.areaCells(top,left):[]).map(function(cell){return cell.top+"-"+cell.left;});var aviable_cells=[];for(var i=0;i<this.height;i++){for(var j=0;j<this.width;j++){if(!exclude.contains(i+"-"+j)){aviable_cells.push([i,j]);}}}this.mineMap(aviable_cells);this.calculateNearMinesNums();this.filledUp=true;},trySiblingsAutoExplore:function(cell){var siblings=this.areaCells(cell).erase(cell);var autoexplorable=true;for(var i=0;i<siblings.length;i++){var cell=siblings[i];if((cell.mined&&!cell.marked)||(cell.marked&&!cell.mined)){autoexplorable=false;break;}}if(autoexplorable){for(var i=0;i<siblings.length;i++){var cell=siblings[i];if(!(cell.explored||cell.mined||cell.marked)){this.hitCell(cell);}}}},checkForWin:function(){var finished=true,cells=this.flatCells();for(var i=0;i<cells.length;i++){var cell=cells[i];if(!(cell.explored||cell.marked)||(cell.marked&&!cell.mined)){finished=false;break;}}if(finished){this.over=true;this.won=true;}},gameOver:function(){this.over=true;this.won=false;this.flatCells().each(function(cell){cell.explored=true;if(cell.marked&&!cell.mined){cell.markedWrong=true;}});},mineMap:function(aviable_cells){for(var i=0;i<this.mines;i++){var pos=aviable_cells.getRandom();if(pos){this.map[pos[0]][pos[1]].mined=true;aviable_cells.erase(pos);}}},calculateNearMinesNums:function(){for(var i=0;i<this.height;i++){for(var j=0;j<this.width;j++){if(!this.map[i][j].mined){this.map[i][j].nearMinesNum=0;var area_cells=this.areaCells(i,j);for(var k=0;k<area_cells.length;k++){if(area_cells[k].mined){this.map[i][j].nearMinesNum++;}}}}}}});
JSMiner.UI=new Class({controller:null,opts:null,initialize:function(controller){this.controller=controller;this.opts=controller.opts;},build:function(){if(this.opts.fieldElement){this.buildField(this.opts.fieldElement,this.controller.getMinesMap());}if(this.opts.scoreElement){this.updateScore(0);}if(this.opts.timerElement){this.controller.game.updateTimerCallback=this.updateTimer.bind(this);this.updateTimer(0);}if(this.opts.smileElement){this.opts.smileElement.onclick=this.controller.reset.bind(this.controller);this.updateSmile();}},update:function(last_cell){if(this.opts.fieldElement){var markers_num=this.updateField(this.controller.getMinesMap());if(this.opts.scoreElement){this.updateScore(markers_num);}}if(this.opts.timerElement){this.updateTimer(this.controller.game.timer);}if(this.opts.smileElement){this.updateSmile();}if(this.controller.game.over){if(!this.finalSalutPlayed){this.showFinalSalut(last_cell);}}else{this.finalSalutPlayed=false;}},updateSmile:function(){var class_name='';if(this.controller.active()){class_name='active';}else if(this.controller.paused()){class_name='paused';}else if(this.controller.failed()){class_name='failed';}else if(this.controller.won()){class_name='won';}this.opts.smileElement.className='jsminer-smile '+class_name;},updateTimer:function(time){var hours=Math.floor(time/3600);var minutes=Math.floor(time/60%60);var seconds=time % 60;this.opts.timerElement.innerHTML=''+(hours<10?'0':'')+hours+':'+(minutes<10?'0':'')+minutes+':'+(seconds<10?'0':'')+seconds;},updateScore:function(markers_num){this.opts.scoreElement.innerHTML=''+markers_num+'/'+this.controller.getMinesNum();},updateField:function(map){this.updateFieldStyle();var markers_num=0;for(var i=0;i<map.length;i++){for(var j=0;j<map[i].length;j++){this.updateCell(map[i][j]);if(map[i][j].marked){markers_num++;}}}return markers_num;},updateCell:function(cell){var class_name='';cell.element.innerHTML='';if(cell.explored){if(cell.boomed){class_name='boomed';}else if(cell.mined){class_name=cell.marked?'marked':'mined';}else if(cell.markedWrong){class_name='marked-wrong';}else{class_name='near-mines-'+cell.nearMinesNum;cell.element.innerHTML=cell.nearMinesNum==0?' ':cell.nearMinesNum;}cell.element.addClass(class_name);}else if(cell.marked){class_name='marked';}cell.element.className='cell '+class_name;},updateFieldStyle:function(){var block_size=this.opts.getBlockSize();this.opts.fieldElement[block_size=='big'?'addClass':'removeClass']('big-blocks');this.opts.fieldElement[block_size=='small'?'addClass':'removeClass']('small-blocks');var row_width=(this.opts.fieldElement.getFirst('div.row').getFirst('div.cell').offsetWidth*this.opts.getSize()[0])+'px';this.opts.fieldElement.getChildren('div.row').each(function(row){row.style.width=row_width;});},buildField:function(element,map){element.innerHTML='';for(var i=0;i<map.length;i++){var row=new Element('div',{'class':'row'});for(var j=0;j<map[i].length;j++){row.appendChild(this.buildCell(map[i][j]));}element.appendChild(row);}element.addClass('jsminer-field');this.updateFieldStyle();},buildCell:function(cell){cell.element=new Element('div',{'class':'cell','events':{'click':this.handleCellClick.bindWithEvent(this,[cell]),'contextmenu':this.handleCellContextClick.bindWithEvent(this,[cell])}});return cell.element;},handleCellClick:function(event,cell){event.stop();var button=event.event['which']?event.event.which:1;if(event.shift||event.control||event.meta||button!=1){this.controller.markCell(cell);}else{this.controller.hitCell(cell);}},handleCellContextClick:function(event,cell){if(navigator.userAgent.indexOf("MSIE")!=-1){event.event['which']=3;}this.handleCellClick(event,cell);},showFinalSalut:function(last_cell){var show_class=this.controller.failed()?'boomed':'marked';var map=this.controller.getMinesMap();var effect_duration=400;var effect_width=1.2;var x_distance=last_cell.left>map[0].length/2?last_cell.left+1:Math.round(map[0].length/2);var y_distance=last_cell.top>map.length/2?last_cell.top+1:Math.round(map.length/2);var distance=x_distance>y_distance?x_distance:y_distance;var step_timeout=effect_duration / distance;var step_duration=step_timeout*effect_width;for(var i=0;i<map.length;i++){for(var j=0;j<map[i].length;j++){var x_diff=Math.abs(map[i][j].left-last_cell.left);var y_diff=Math.abs(map[i][j].top-last_cell.top);var diff=x_diff>y_diff?x_diff:y_diff;var timeout=diff*step_timeout;(function(cell,timeout){cell._oldClassName=cell.element.className;cell._formerHTML=cell.element.innerHTML;window.setTimeout(function(){cell.element.innerHTML=' ';cell.element.className='cell '+show_class;},timeout);window.setTimeout(function(){cell.element.innerHTML=cell._formerHTML;cell.element.className=cell._oldClassName;},timeout+step_duration);}).apply(this,[map[i][j],timeout]);}}this.finalSalutPlayed=true;}});
