/**
 * Sudoku game implemented with JavaScript and RightJS
 *
 * The source code of the project are freely available at
 *   http://github.com/MadRabbit/right-sudoku
 *
 * @copyright 2009 Nikolay V. Nemshilov aka St. <nemshilov#gmail#com>
 */
var Sudoku=new Class({initialize:function(e){this.container=$E('div',{id:'rs-game'}).insertTo($(e));this.field=new Sudoku.Field();this.menu=new Sudoku.Menu();this.status=new Sudoku.Status();this.highscore=new Sudoku.Highscore();this.container.insert([this.field.element,$E('div',{id:'rs-sidebar'}).insert([this.menu.element,this.status.element,this.highscore.element]),$E('div',{style:{clear:'both'}})]);this.menu.on('level-changed',this.loadLevel.bind(this));this.menu.on('reset',this.reset.bind(this));this.menu.on('undo',this.field.undo.bind(this.field));this.field.on('finished',this.finished.bind(this));this.reset()},reset:function(){this.loadLevel(this.menu.currentLevel());this.status.resetTimer()},loadLevel:function(l){var b=Sudoku.Boards.random(l);this.status.setDifficulty(b.difficulty);this.highscore.load(l);this.field.loadPuzzle(b.puzzle)},finished:function(){this.status.stopTimer();this.field.showFinishAnimation();this.highscore.add(this.status.getTime())}});Sudoku.Boards={random:function(l){l=l||'normal';var e=this[l].random().split(';');var a=e.first();var p=[];for(var i=0;i<81;i++){var x=(i/9).floor();var y=i % 9;var s=a.charAt(i);p[x]=p[x]||[];p[x][y]=s=='.'?null:s.toInt()}return{difficulty:e.last().toInt()/100,puzzle:this.shuffle(p)}},shuffle:function(b){b=this.flip(b);(128).times(function(){[function(n,a){return[n,a,n+1,a]},function(n,a){return[a,n,a,n+1]}].each(function(a){var n=[0,1,3,4,6,7].random();for(var i=0;i<9;i++){var p=a(n,i);var v=b[p[0]][p[1]];b[p[0]][p[1]]=b[p[2]][p[3]];b[p[2]][p[3]]=v}})});return b},flip:function(d){var f=[function(a,b){return[a,b]},function(a,b){return[b,a]},function(a,b){return[8-a,b]},function(a,b){return[a,8-b]},function(a,b){return[8-a,8-b]}].random();var m=[];for(var x=0;x<9;x++)for(var y=0;y<9;y++){var p=f(x,y);var n=p.first();var c=p.last();m[n]=m[n]||[];m[n][c]=d[x][y]}return m},easy:['.1..4.56.23.615.8....8..1...5..2...86..781..59...6..2...6..8....8.473.56.45.9..1.;30','.17..256..34.15.8...9...14.45....6...2.781.9...8....21.96...2...8.47.95..452..81.;27','..7.4...3...6..7.9...83..4.451.2.67862.....95978.6.321.9..58...1.2..3...3...9.8..;28','.....9.565..78.1.....5.38.965.8.4792.2.....6.3976.2.184.31.5.....5.48..121.3.....;37','78.....5.5.97...2..4..2..796.....792..49.15..397.....846..9..8..7...86.1.1.....45;29','7..4.9..65.9.8612.14.......65.8......249.156......2.18.......87.7524.6.12..3.7..5;20','78.....56..9.8.1...46....7.65...4792...9.1...3976...18.6....28...5.4.6..21.....45;39','.4...91.6365.........26...54578..6.9..19.58..9.3..45275...91.........3817.63...5.;26','.4....1.63.51.72.....2....545.82.61...19.58...83.14.275....1.....47.63.17.6....5.;42','8.25..1......47..81.9.6.4354...23.1..2.....4..8.61...7538.9.7.22..75......6..29.4;21','84..391..36.1....81.92......5.8..6....19.58....3..4.2......17.22....6.81..638..54;32','..9..5.715..7.6.....849..35.3..182...9.342.6...296..4.92..743.....6.9..276.8..4..;15','6.9.....1.1...6.24..84..63..3.5..2.71.73.25.88.2..7.4..25..43..38.6...1.7.....4.9;24','..9.3.87.5.....9.427..91.3.4.6.182..1..3.2..8..296.1.3.2.17..863.4.....2.61.2.4..;14','..9..58.....7.69.42.849....43.51..97..73.25..85..67.43....743.63.46.9.....18..4..;28','62..5..9..15824.7..486....2.5..7...9...2.9...2...4..6.5....261..6.43792..9..1..84;22'],normal:['...81523....9....8..246.51.3.6...1...5.1.6.2...4...6.3.95.418..6....9....83652...;55','.1.4...3729..7.5........1.......8714..5...9..7213.......6........9.3..8548...5.7.;52','3..512..42..7.8..6.1........9.....62.2.9.6.8.57.....4........5.1..6.4..97..135..8;57','.2..514....5....76.4.69..5.1.6.78....8.....3....14.8.7.7..82.1.86....9....251..8.;48','.....149..15.2.37.74.6.....1.6..82.94..2.9..12.91..8.7.....2.13.61.3.92..925.....;54','.2.7.14...1..2..76....93.521...7.2...8.2.9.3...9.4...757.98....86..3..2...25.6.8.;48','4.6....2..2.68.1.5.18.....3.8.7.53....7.3.5....38.6.1.8.....47.6.1.74.3..4....6.1;46','4.61.....32.6.7..551.......2.4...3...679.258...3...2.7.......796..5.4.32.....86.1;47','.2..95..7.45.3216..7..6..4.49....7....3...2....7....96.3..7..5..6132.47.7..65..2.;55','..9......4.6.5.92.25.9.48..8.71......314.527......84.1..58.6.49.98.4.6.5......1..;45','.....2893.....3..7.679...42..3....8995.3.8.2668....1..23...597.1..2.....5798.....;45','.7....419..9.....2..5.79...7831.2.4....834....9.6.7831...76.5..3.....1..968....7.;55','24...97.1..1..6.4..36...2..5..9...16...6.2...69...7..4..3...68..5.8..4..8.27...35;46','1...76.93..63...4....41.7...7..9.4..2.9.6.3.7..3.5..1...2.37....1...56..34.68...9;46','17..34.9........2.5.6..7...3..1...87.47.5.31.82...9..5...4..9.3.6........3.79..61;45','7.2....1..162.4..3..3.6....294.3.15.63..2..74.75.9.362....4.6..4..3.582..2....5.7;45'],hard:['1..6.5.8.75..2..6..2.3..5..89.7...5...3...8...1...8.79..1..2.3..4..7..16.8.1.6..5;61','.9...3.1.2.8..73..4.....58...45.8....8.9.1.7....7.24...46.....2..96..8.1.2.3...5.;62','.4.2.63..326.1.5.41...4....5....42..71.5.8.36..47....5....3...16.1.8.953..39.1.4.;62','.754.68.28..7..1...4..9..7...19.4.......8.......5.36...1..4..8...4..9..12.73.195.;63','...7....51.38.46.9.7....13....47.5.8...2.9...7.6.58....97....5.2.85.39.74....7...;64','.3.9..7....5..2.94..7.456.2.4..6.2.1.........7.1.2..6.6.245.1..59.2..4....3..9.2.;65','........934.9..52.....21..38..412.7..7.5.9.1..2.876..44..29.....63..4.522........;66','6.94...8.8..9..5.4.5...79..7..63289....8.1....68549..3..41...5.3.6..8..9.8...47.6;67','.52..893.1..4...52.3.5..8......65..8....8....8..94......9..1.7.37...4..9.168..52.;68','.17..3...52.7...8.....2.3.....859.233..2.7..529.134.....6.1.....3...5.72...9..53.;69','3.45172.9..7..45.1....2.4..7.....6...413.589...5.....4..3.6....9.61..3..5.27439.6;69','.18...64.6..8....3..356179.48.9.3...............4.6.81.641923..1....8..9.32...81.;69','.1..2...44.7...2....984.31.7..1..6..39.2.8.51..1..4..9.75.819....8...1.31...9..6.;70','.9..7.2...63.......4751.3.9.29...1.....9.2.....5...69.1.4.8796.......72...2.3..4.;70','...95..4.9.8..7.124.6...7..5...6182..8.....6..1258...9..3...9.776.2..1.4.9..75...;70','.47.6.58...1.9436......34.1.62.....5...956...5.....63.2.46......7532.9...38.4.25.;75'],extreme:['.8...7.9532..85...75.36......6...4.3..3...9..5.2...7......13.84...74..3943.5...6.;76','6..2....42.84516..3...8..1.1..86.3....9...1....6.13..8.1..7...2..21485.35....2..1;78','.1.76.9....7..3.2.68..2.....2.8.5..98.5...2.19..2.1.4.....8..92.4.9..7....9.42.8.;78','2..3.1.45.....6..2.53.9....98....7.31.7...4.93.5....86....2.96.4..1.....79.6.5..4;78','..3...1..68..13...91.4...3.26..517.3..1...2..8.967..15.2...9.84...28..71..8...6..;78','.....5.7.2..3.95.115..2...9..8.63.5236.....1842.15.6..5...3..268.12.6..5.9.5.....;80','..91.8.4..4.6.57.8.......1.8..3...7.1.57.24.9.9...1..2.6.......9.38.4.2..5.2.61..;82','.14.6.3..62...4..9.8..5.6...6.2....3.7..1..5.5....9.6...6.2..3.1..5...92..7.9.41.;83','94.1.7..8.....35...6.48..1.5..87.3...7.....6...8.49..7.5..34.8...75.....3..7.8.95;84','6....8.3..3.6..59.4....58.1..4..3.799.......531.5..6..1.38....6.49..6.8..6.9....3;84','8461...3..5...21.....94....615....4...3...6...8....712....28.....23...6..3...1427;86','.....8..22.83..1...73.1.8.......1345...935...5317.......9.6.27...6..74.97..2.....;87','..9546.8...8...5.725........4.63...8...1.8...5...24.1........268.5...3...3.2897..;88','9..3....6.8......7..52.89....8..4.73.9.....4.76.1..2....78.14..1......5.8....9..1;92','8..1.3.5.31.4..8....5.......9.2....5.587.936.4....5.8.......2....2..1.37.3.8.2..1;92','..41..3...7...8..1......7.98.3.59..2..96.15..1..82.9.46.1......4..3...9...8..42..;95']};Sudoku.Field=new Class(Observer,{initialize:function(){this.element=$E('div',{id:'rs-field'});this.input=new Sudoku.Field.Input();this.element.insert(this.input.element);this.history=new Sudoku.Field.History();this.initCells()},loadPuzzle:function(p){for(var i=0;i<9;i++)for(var j=0;j<9;j++)this.rows[i][j].setNumber(p[i][j],true);return this},showInput:function(c){if(!c.isPreset())this.input.showAt(c);return this},undo:function(){this.history.undo();return this},showFinishAnimation:function(){this.element.highlight('pink').highlight('pink').highlight('pink');return this},initCells:function(){this.rows=[];this.cols=[];this.blocks=[];for(var i=0;i<9;i++){var x=i % 3,y=(i/3).floor();var b=new Sudoku.Field.Block(x,y);b.element.insertTo(this.element);this.blocks.push(b);for(var j=0;j<9;j++){var c=b.cells[j];this.rows[c.y]=this.rows[c.y]||[];this.cols[c.x]=this.cols[c.x]||[];this.rows[c.y][c.x]=this.cols[c.x][c.y]=c;c.on('mouseover',this.highlightCross.bind(this,c));c.on('select',this.showInput.bind(this,c));c.on('assign',this.cellChanged.bind(this,c))}}this.element.on('mouseout',this.fadeCross.bind(this))},highlightCross:function(c){this.fadeCross();for(var i=0;i<9;i++)this.rows[c.y][i].element.style.background=this.cols[c.x][i].element.style.background='#FEF';this.prevCell=c},fadeCross:function(){if(this.prevCell)for(var i=0;i<9;i++)this.rows[this.prevCell.y][i].element.style.background=this.cols[this.prevCell.x][i].element.style.background='transparent'},cellChanged:function(c){if(c.number)this.history.push(c);this.checkFieldValues()},checkFieldValues:function(){var f=true,a=true;for(var i=0;i<9;i++)for(var j=0;j<9;j++){var c=this.cols[i][j];if(c.number)f=f & this.checkCellValue(c);else{a=false;c.element.removeClass('rs-cell-wrong')}}if(!f)this.fire('error');else if(a)this.fire('finished')},checkCellValue:function(c){var r=this.rows[c.y];var d=this.cols[c.x];var b=[0,1,2,3,4,5,6,7,8].map(function(a){var x=(c.x/3).floor()*3+a % 3;var y=(c.y/3).floor()*3+(a/3).floor();return this.cols[x][y]},this);var f=true;[r,d,b].each(function(l){var n=l.map('number').compact();var u=n.uniq();if(n.length!=u.length)for(var i=0;i<n.length;i++)if(n[i]!=u[i]){if(n[i]==c.number)f=f & false;break}},this);c.element[f?'removeClass':'addClass']('rs-cell-wrong');return f}});Sudoku.Field.Block=new Class({initialize:function(a,b){this.element=$E('div',{'class':'rs-block'});this.x=a;this.y=b;this.cells=[];for(var i=0;i<9;i++){var c=new Sudoku.Field.Cell(this,i % 3,(i/3).floor());this.cells.push(c)}}});Sudoku.Field.Cell=new Class(Observer,{initialize:function(b,a,c){this.element=$E('div',{'class':'rs-cell'}).insertTo(b.element);this.x=b.x*3+a;this.y=b.y*3+c;this.block=b;this.element.on('mouseover',this.fire.bind(this,'mouseover'));this.element.on('click',(function(e){e.stop();this.fire('select')}).bind(this))},setNumber:function(n,p){this.element.update(n==null?'':''+n)[n==null?'removeClass':'addClass'](p?'rs-cell-preset':'rc-cell-assigned');this.number=n;if(!p)this.fire('assign');return this},reset:function(){return this.setNumber(null)},isPreset:function(){return this.element.hasClass('rs-cell-preset')}});Sudoku.Field.Input=new Class(Observer,{initialize:function(){this.element=$E('div',{'class':'rs-input','style':{display:'none'}});this.element.insert($E('div',{'class':'rs-input-shadow'}));for(var i=1;i<10;i++)$E('div',{html:''+i}).insertTo(this.element).on('click',this.select.bind(this,i));document.on('click',this.hide.bind(this))},showAt:function(c){this.element.show('fade',{duration:'short'});var a=c.element.position();this.element.setStyle({left:(a.x-this.element.offsetWidth/3+4)+'px',top:(a.y-this.element.offsetHeight/3+4)+'px'});this.currentCell=c;return this},hide:function(){this.element.hide();return this},select:function(n){if(this.currentCell)this.currentCell.setNumber(n)}});Sudoku.Field.History=new Class({initialize:function(){this.stack=[]},push:function(c){this.stack.push(c)},undo:function(){var c=this.stack.pop();if(c)c.reset()}});Sudoku.Menu=new Class(Observer,{LEVELS:['Easy','Normal','Hard','Extreme'],DEFAULT:'normal',initialize:function(){this.element=$E('div',{id:'rs-menu'});this.levels=$E('select',{id:'rs-levels'}).insertTo(this.element).update(this.LEVELS.map(function(l){return $E('option',{value:l.toLowerCase(),html:"Level: "+l})})).setValue(Cookie.get('rs-level')||this.DEFAULT).on('change',(function(){this.fire('level-changed',this.levels.value);Cookie.set('rs-level',this.levels.value)}).bind(this));this.reset=$E('input',{id:'rs-reset',type:'button',value:'Reset'}).insertTo(this.element).on('click',this.fire.bind(this,'reset'));this.undo=$E('input',{id:'rs-undo',type:'button',value:'Undo'}).insertTo(this.element).on('click',this.fire.bind(this,'undo'))},currentLevel:function(){return this.levels.value}});Sudoku.Status=new Class({initialize:function(){this.element=$E('div',{id:'rs-status',html:'<div><label>Difficulty:</label> <span id="rs-difficulty"></span></div>'+'<div><label>Time:</label> <span id="rs-timer"></span></div>'});this.difficulty=this.element.first('#rs-difficulty');this.timeEl=this.element.first('#rs-timer')},setDifficulty:function(v){this.difficulty.update((''+(v*10)).substr(0,3))},resetTimer:function(){if(this.timer)this.timer.stop();this.startTime=new Date();this.timer=this.updateTimer.bind(this).periodical(1000);this.updateTimer()},stopTimer:function(){this.timer.stop()},getTime:function(){return this.seconds||0},updateTimer:function(){this.seconds=((new Date()-this.startTime)/1000).round();var m=(this.seconds/60).floor();var s=this.seconds % 60;m=(m<10?'0':'')+m;s=(s<10?'0':'')+s;this.timeEl.update(m+':'+s)}});Sudoku.Highscore=new Class({LENGTH:7,initialize:function(){this.element=$E('div',{id:'rs-highscore',html:'<label>Highscore</label><ul id="rs-highscore-list"></ul></label>'});this.list=this.element.first("#rs-highscore-list");this.read()},load:function(l){this.levels[l]=this.levels[l]||[];this.results=this.levels[l];this.render();return this},add:function(t){this.results.push({time:t,name:'<input type="text" />'});this.results.sort(function(c,d){return c.time>d.time?1:c.time<d.time?-1:0});this.results.splice(this.LENGTH,this.results.length);return this.render()},read:function(){var b=Cookie.get('rs-result')||'';this.levels={};b.split('%').each(function(l){var a=l.split('#');if(a.length==2)this.levels[a.first()]=a.last().split(';').map(function(r){var e=r.split('|');return e.length==2?{time:e.first().toInt(),name:unescape(e.last())}:null}).compact()},this);return this},render:function(){this.list.update(this.results.map(function(r){var m=(r.time/60).floor();var s=r.time % 60;m=(m<10?'0':'')+m;s=(s<10?'0':'')+s;return '<li>'+m+':'+s+' '+r.name+'</li>'}).join('')||'Empty');var i=this.list.first('input');if(i){i.value=this.lastName||'';i.on('keydown',(function(e){if(e.keyCode==13){e.stop();this.acceptName(i.value)}}).bind(this)).focus()}return this},acceptName:function(n){var a=this.results.any(function(r){return r.name=='<input type="text" />'});if(a){this.lastName=n;a.name=n.replace('<','&lt;').replace('>','&gt;')}return this.render().save()},save:function(){Cookie.set('rs-result',Object.keys(this.levels).map(function(l){return l+"#"+this.levels[l].map(function(r){return r.time+"|"+escape(r.name)}).join(';')},this).join('%'),{duration:999});return this}});document.write("<style type=\"text/css\">div#game-container{padding:20pt}div#rs-game{font-family:Verdana;font-size:10pt}div#rs-game label{font-weight:bold;color:grey}div#rs-sidebar{width:120pt;float:left}div#rs-field{width:282px;height:282px;font-size:18px;float:left;margin-right:20pt;margin-bottom:20pt;border:1px solid #DDD;border-bottom:none;border-right:none}div#rs-field div.rs-block{float:left;width:93px;height:93px;border:1px solid #DDD;margin-left:-1px;margin-top:-1px}div#rs-field div.rs-block div.rs-cell{float:left;border:1px solid #CCC;width:30px;height:30px;line-height:30px;text-align:center;margin-left:-1px;margin-top:-1px;cursor:pointer}div#rs-field div.rs-block div.rs-cell:hover{background:pink !important}div.rs-cell-preset{color:brown;cursor:default !important}div.rs-cell-highlighted{background:#FEF}div.rs-cell-wrong{background:#D44 !important}div.rs-input{width:96px;height:96px;position:absolute;z-index:1}div.rs-input div{float:left;width:30px;height:30px;line-height:30px;text-align:center;border:1px solid #AAA;background:#FDD;cursor:pointer}div.rs-input div:hover{background:pink}div.rs-input div.rs-input-shadow{position:absolute;background:black;opacity:0.4;filter:alpha(opacity=40);width:100%;height:100%;margin-left:3px;margin-top:3px;-moz-border-radius:3px;-webkit-border-radius:3px;z-index:-1}div#rs-menu{margin-bottom:20pt}div#rs-menu select{width:120pt;margin-bottom:10pt}div#rs-menu input{width:59pt}div#rs-status{margin-bottom:20pt}div#rs-status label{display:block;float:left;width:70pt}div#rs-status div span{}div#rs-highscore{}div#rs-highscore ul{margin:0;padding:0;list-style:none;margin-top:5pt}div#rs-highscore ul li{margin:0;padding:0}div#rs-highscore ul li input{width:70pt}</style>");
