var my={};var g_startOffset=null;var g_selectedPiece=null;var moveNumber=1;var g_allMoves=[];var g_playerWhite=true;var g_changingFen=false;var g_analyzing=false;var g_uiBoard;function init(){var version='0.835';my.sttTime=performance.now() my.bdSize=8 my.bdLblQ=true my.pcWd=Math.min(60,(window.innerWidth-30)/my.bdSize,(window.innerHeight-150)/my.bdSize) my.tileSz=my.pcWd my.bdWd=my.pcWd*my.bdSize my.bdHt=my.bdWd my.flipQ=true my.dragQ=false;my.players=[{name:'White',clr:'white',typ:0,time:'endless',style:'drop-shadow(2px 2px 5px #88f) drop-shadow(-2px -2px 5px #88f)'},{name:'Black',clr:'black',typ:1,time:'endless',style:'drop-shadow(2px 2px 5px #f88) drop-shadow(-2px -2px 5px #f88)'}];my.playerNum=0 my.playerTypes=[{name:'Human',aiQ:false,sockQ:false},{name:'Computer (Beginner)',aiQ:true,aiLevel:1,timeMax:3000,sockQ:false},{name:'Computer (Novice)',aiQ:true,aiLevel:2,sockQ:false},{name:'Computer (Skilled)',aiQ:true,aiLevel:3,sockQ:false},{name:'Computer (Hard)',aiQ:true,aiLevel:4,sockQ:false},{name:'Computer (Ruthless)',aiQ:true,aiLevel:5,sockQ:false}] my.imgSets=[{name:'Standard',prefix:'std',ext:'.png'},{name:'Maya',prefix:'maya',ext:'.svg'},{name:'Leipzig',prefix:'lpz',ext:'.svg'},{name:'Merida',prefix:'mer',ext:'.svg'},];my.imgSetN=0 my.times=[{name:'Endless',mm:60},{name:'1 Hour',mm:60},{name:'30 min',mm:30},{name:'20 min',mm:20},{name:'15 min',mm:15},{name:'10 min',mm:10},{name:'7 min',mm:7},{name:'5 min',mm:5}];my.timeN=0 my.imgHome=((document.domain=='localhost')?'/mathsisfun':'')+'/games/images/chess/' my.sndHome=((document.domain=='localhost')?'/mathsisfun':'')+'/images/sounds/' var s='' s+='' s+='' s+='' my.snds=[];s+='
';s+='
';s+='
';s+='';s+='
';s+='
';s+='';my.soundQ=true s+=soundBtnHTML() s+='
';s+='
';s+='
';s+='' s+='
';s+='
';s+='
';s+='
';s+='
';s+=optPopHTML();s+='
';s+='';s+='
';if(false){s+='
';s+='Analysis: Off';s+='
';} s+='';s+='';s+=wrap({cls:'copyrt',style:'margin: 15px 0 0 0;'},`© 2021 MathsIsFun.com v${version}`) s+='
';docInsert(s);soundToggle() for(var i=0;i0){my.dragQ=true;my.dragDiv=document.getElementById('drag');my.dragDiv.innerHTML='';my.dragDiv.style.left=(mouseX-my.dragHoldX)+'px';my.dragDiv.style.top=(mouseY-my.dragHoldY)+'px';my.dragDiv.style.visibility='visible';hilite();for(i=0;i>8)&0xFF)==MakeSquare(my.endY,my.endX)){move=moves[i];}} if(!(my.startX==my.endX&&my.startY==my.endY)&&move!=null){my.players[my.playerNum].clock.stop() UpdatePgnTextBox(move);g_allMoves[g_allMoves.length]=move;MakeMove(move);my.playerNum=1-my.playerNum my.players[my.playerNum].clock.resume() console.log('player changed to:',my.playerNum,my.players[my.playerNum].name,'type'+my.players[my.playerNum].typ,elapsed()) var currType=my.players[my.playerNum].typ console.log('currType',currType,my.playerTypes[currType]) if(my.playerTypes[currType].aiQ){if(InitializeBackgroundEngine()){g_backgroundEngine.postMessage('move '+FormatMove(move));} UpdateFromMove(move);document.getElementById("FenTextBox").value=GetFen() aiMove()}else{RedrawPieces();}}else{RedrawPieces();} hilite();} function setTime(ms){my.timeout=ms;} function hilite(td,clr){if(td==null){for(y=0;y<8;++y){for(x=0;x<8;++x){var td=g_uiBoard[bdIndex(x,y)];td.style.boxShadow='';}}}else{var inset='inset 0px 0px 18px '+clr;td.style.boxShadow=inset+', '+inset;}} function move2td(move,fromQ){var n=fromQ?move&0xFF:(move>>8)&0xFF;var row=((n-36)/16)<<0;var col=(n-4)%16;var td=g_uiBoard[bdIndex(col,row)];return td;} function getRandomInt(min,max){return Math.floor(Math.random()*(max-min+1))+min;} function soundBtnHTML(){var s='' s+='' s+='
' return s} function soundToggle(){var btn='sound' if(my.soundQ){my.soundQ=false document.getElementById(btn).classList.add("mute")}else{my.soundQ=true document.getElementById(btn).classList.remove("mute")}} function soundPlay(id,simulQ){if(!my.soundQ)return simulQ=typeof simulQ!=='undefined'?simulQ:false if(simulQ){if(id.length>0)document.getElementById(id).play()}else{my.snds.push(id) soundPlayQueue(id)}} function soundPlayQueue(id){var div=document.getElementById(my.snds[0]) div.play() div.onended=function(){my.snds.shift();if(my.snds.length>0)soundPlayQueue(my.snds[0]);};} function optPopHTML(){var s='';s+='
';var times=[] for(var i=0;i=0;i--){var p=my.players[i];s+='
';s+='
'+p.name+' player:
';s+='Type: ';s+=getDropdownHTML(playerTypes,'','playerType'+i,p.typ);s+='
' s+='Time: ' s+=getDropdownHTML(times,'','timeType'+i,0);s+='
';s+='
';} s+='
';s+='
';var imgSets=[] for(var i=0;i';s+='
';s+='';s+='';s+='
';s+='
';return s;} function getDropdownHTML(opts,funcName,id,chkNo){var s='';s+='';return s;} function optPop(){var pop=document.getElementById('optpop');pop.style.transitionDuration="0.3s";pop.style.opacity=1;pop.style.zIndex=12;pop.style.left='10px';} function optYes(){var pop=document.getElementById('optpop');pop.style.opacity=0;pop.style.zIndex=1;pop.style.left='-999px';gameNew();} function optNo(){var pop=document.getElementById('optpop');pop.style.opacity=0;pop.style.zIndex=1;pop.style.left='-999px';} function EnsureAnalysisStopped(){if(g_analyzing&&g_backgroundEngine!=null){g_backgroundEngine.terminate();g_backgroundEngine=null;}} function UIAnalyzeToggle(){if(InitializeBackgroundEngine()){if(!g_analyzing){g_backgroundEngine.postMessage("analyze");}else{EnsureAnalysisStopped();} g_analyzing=!g_analyzing;document.getElementById("AnalysisToggleLink").innerText=g_analyzing?"Analysis: On":"Analysis: Off";}else{alert("Your browser must support web workers for analysis - (chrome4, ff4, safari)");}} function UIChangeFEN(){if(!g_changingFen){var fenTextBox=document.getElementById("FenTextBox");var result=InitializeFromFen(fenTextBox.value);if(result.length!=0){UpdatePVDisplay(result);return;}else{UpdatePVDisplay('');} g_allMoves=[];EnsureAnalysisStopped();InitializeBackgroundEngine();g_playerWhite=!!g_toMove;g_backgroundEngine.postMessage("position "+GetFen());bdDraw();}} function UIChangeStartPlayer(){g_playerWhite=!g_playerWhite;bdDraw();} function moveListAdd(s){var div=document.getElementById("movelist") div.value+=s+' '} function UpdatePgnTextBox(move){var s='' if(g_toMove!=0){if(moveNumber>1)s+=' ' s+=moveNumber+'. ' moveNumber++;} s+=GetMoveSAN(move) moveListAdd(s) var mv=GetMoveSAN(move);if(mv.charAt(mv.length-1)=='+'){msg('Check') soundPlay('sndcheck',true)} if(mv.charAt(mv.length-1)=='#'){msg('CheckMate!') console.log('CheckMate!') gameOver() soundPlay('sndcheck',true)} if(mv.indexOf('x')>0){soundPlay('sndtake',true)}} function msg(s){document.getElementById("msg").innerHTML=s} function UIChangeTimePerMove(){var timePerMove=document.getElementById("TimePerMove");g_timeout=parseInt(timePerMove.value,10);} function FinishMove(bestMove,value,timeTaken,ply){if(bestMove!=null){UIPlayMove(bestMove,BuildPVMessage(bestMove,value,timeTaken,ply));}else{alert("Checkmate!");}} function UIPlayMove(move,pv){UpdatePgnTextBox(move);g_allMoves[g_allMoves.length]=move;MakeMove(move);UpdatePVDisplay(pv);UpdateFromMove(move);td=move2td(move,false);hilite(td,'#dd0');td=move2td(move,true);hilite(td,'green');} function UIUndoMove(){if(g_allMoves.length==0){return;} if(g_backgroundEngine!=null){g_backgroundEngine.terminate();g_backgroundEngine=null;} var curTyp=my.players[my.playerNum].typ var nxtTyp=my.players[1-my.playerNum].typ if(curTyp==0&&nxtTyp==0){UnmakeMove(g_allMoves[g_allMoves.length-1]);g_allMoves.pop();moveListAdd('Undo') my.playerNum=1-my.playerNum}else{if(g_allMoves.length>=2){UnmakeMove(g_allMoves[g_allMoves.length-1]);g_allMoves.pop();UnmakeMove(g_allMoves[g_allMoves.length-1]);g_allMoves.pop();moveListAdd('Undo2')}} bdDraw();} function UpdatePVDisplay(pv){if(pv!=null){var outputDiv=document.getElementById("output");if(outputDiv.firstChild!=null){outputDiv.removeChild(outputDiv.firstChild);} outputDiv.appendChild(document.createTextNode(pv));}} function aiMove(){console.log('aiMove',moveNumber,my.playerNum,my.players[my.playerNum].name,elapsed()) if(moveNumber>250){msg('Draw (too many moves)') return;} if(my.gameOverQ)return hilite() if(g_analyzing){EnsureAnalysisStopped();InitializeBackgroundEngine();g_backgroundEngine.postMessage("position "+GetFen());g_backgroundEngine.postMessage("analyze");return;} var currType=my.players[my.playerNum].typ;var playerType=my.playerTypes[currType];var aiLevel=playerType.aiLevel console.log('ai',currType,aiLevel) var maxPly=1;var randYN='n';switch(aiLevel){case 1:randYN=randRateAim(my.randCount/moveNumber,0.1,0.25,0.4)?'y':'n' maxPly=1;g_timeout=40;break;case 2:randYN=randRateAim(my.randCount/moveNumber,0.1,0.2,0.3)?'y':'n' maxPly=getRandomInt(1,3);g_timeout=100;break;case 3:maxPly=getRandomInt(1,3);g_timeout=100;break;case 4:maxPly=getRandomInt(2,5);g_timeout=500;break;case 5:maxPly=99;g_timeout=3000;break;} if(randYN=='y')my.randCount++;if(InitializeBackgroundEngine()){console.log('aiMove postMessage ',g_timeout,maxPly,randYN,elapsed()) g_backgroundEngine.postMessage("search "+g_timeout+' '+maxPly+' '+randYN);}else{console.log('QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ') Search(FinishMove,2,null);} setTimeout("aiDone()",g_timeout+300);} function randRateAim(actual,lo,aim,hi){console.log('randRateAim',actual,lo,aim,hi) if(actual>hi){return false}else{if(actual>4)&0xF)-2;var toX=((move>>8)&0xF)-4;var toY=((move>>12)&0xF)-2;if(!g_playerWhite){fromY=7-fromY;toY=7-toY;fromX=7-fromX;toX=7-toX;} if((move&moveflagCastleKing)||(move&moveflagCastleQueen)||(move&moveflagEPC)||(move&moveflagPromotion)){RedrawPieces();}else{var fromSquare=g_uiBoard[bdIndex(fromX,fromY)];var toDiv=g_uiBoard[bdIndex(toX,toY)];toDiv.innerHTML='';RedrawPieces();}} function pcName(piece){var pcTyp=null;switch(piece&0x7){case piecePawn:pcTyp='p';break;case pieceKnight:pcTyp='n';break;case pieceBishop:pcTyp='b';break;case pieceRook:pcTyp='r';break;case pieceQueen:pcTyp='q';break;case pieceKing:pcTyp='k';break;} if(pcTyp==null){return null;}else{var imgSet=my.imgSets[my.imgSetN];var pcClr=(piece&0x8)?"w":"b";return my.imgHome+imgSet.prefix+pcClr+pcTyp+imgSet.ext}} function bdIndex(xn,yn,flipQ){flipQ=typeof flipQ!=='undefined'?flipQ:false if(flipQ){return yn*8+xn}else{return(7-yn)*8+(7-xn)}} function RedrawPieces(){for(y=0;y<8;++y){for(x=0;x<8;++x){var td=g_uiBoard[bdIndex(x,y)];var pieceY=g_playerWhite?y:7-y;var piece=g_board[((pieceY+2)*0x10)+(g_playerWhite?x:7-x)+4];var pieceName=pcName(piece);if(pieceName!=null){var img=document.createElement("div");var setNo=1;var pcClr=(piece&0x8)?"w":"b";img.style.backgroundImage='url("'+pieceName+'")';img.style.backgroundSize=my.tileSz+'px '+my.tileSz+'px';img.style.width=my.tileSz+'px';img.style.height=my.tileSz+'px';var divimg=document.createElement("div");divimg.appendChild(img);td.innerHTML='';td.appendChild(divimg);}else{td.innerHTML='';}}}} function bdDraw(){var bdDiv=document.getElementById('board');while(bdDiv.firstChild){bdDiv.removeChild(bdDiv.firstChild);} bdDiv.style.margin='0px' bdDiv.style.padding='0px' g_uiBoard=[];for(y=0;y<8;++y){var tr=document.createElement("div");tr.style.height=my.tileSz+"px";for(x=0;x<8;++x){var td=document.createElement("div");td.style.display='inline-block' td.style.width=my.tileSz+"px";td.style.height=my.tileSz+"px";td.style.backgroundColor=((y^x)&1)?"#b58863":"#f0d9b5";tr.appendChild(td);g_uiBoard[bdIndex(x,y)]=td;} bdDiv.appendChild(tr);} RedrawPieces();bdLbls() g_changingFen=true;document.getElementById("FenTextBox").value=GetFen();g_changingFen=false;} function bdLbls(){let bdDiv=document.getElementById('board');for(let i=0;i<8;i++){let lbl=document.createElement("div");lbl.style.position='absolute' lbl.innerHTML=8-i lbl.style.textAlign='right' lbl.style.font='18px Arial' lbl.style.left=(-5-my.tileSz)+"px";lbl.style.top=(15+my.tileSz*i)+"px";lbl.style.width=my.tileSz+"px";lbl.style.height=my.tileSz+"px";bdDiv.appendChild(lbl);} for(let i=0;i<8;i++){let lbl=document.createElement("div");lbl.style.position='absolute' lbl.innerHTML=String.fromCharCode(97+i) lbl.style.textAlign='center' lbl.style.font='18px Arial' lbl.style.left=(-5+my.tileSz*i)+"px";lbl.style.top=(1+my.tileSz*8)+"px";lbl.style.width=my.tileSz+"px";lbl.style.height=my.tileSz+"px";bdDiv.appendChild(lbl);} document.getElementById('moves').style.marginTop="23px"} function timesUp(me){console.log('timesUp',me.id) var p=my.players[me.id] msg(p.name+"'s Time is Up! ")} function DigiClock(ht,mode,divName){this.numHt=ht this.mode=mode this.upQ=true this.fromTime=0 this.hhQ=false this.mmQ=true this.ssQ=true this.msQ=false this.numWd=this.numHt*0.45 this.numGap=this.numWd*0.5 this.border=this.numHt*0.3 this.ht=this.numHt+this.border*2 var wd=0 var colonWd=this.numWd*0.7 if(this.hhQ)wd+=2*(this.numWd+this.numGap)+colonWd if(this.mmQ)wd+=2*(this.numWd+this.numGap)+colonWd if(this.ssQ)wd+=2*(this.numWd+this.numGap)+colonWd if(this.msQ)wd+=3*(this.numWd+this.numGap)+colonWd wd-=this.numGap wd-=colonWd wd+=this.numGap/2 this.wd=wd+this.border*2 this.typ='led' switch(this.typ){case 'lcd':this.clr={bg:'#ccc',border:'2px solid #888',on:'#222',off:'#ccc',shadow:'#ccc',shadowBlur:0} break case 'led':this.clr={bg:'#222',border:'2px solid black',on:'rgb(100, 255, 0)',off:'rgb(50, 80, 0)',shadow:'rgb(100, 255, 0)',shadowBlur:33} break default:} var div=document.getElementById(divName) div.style.height=this.ht+'px' div.style.width=this.wd+'px' this.el=document.createElement("canvas");div.appendChild(this.el) this.el.style.backgroundColor=this.clr.bg;this.el.style.borderRadius="10px";var ratio=2;this.el.width=this.wd*ratio;this.el.height=this.ht*ratio;this.el.style.width=this.wd+"px";this.el.style.height=this.ht+"px";this.g=this.el.getContext("2d");this.g.setTransform(ratio,0,0,ratio,0,0);this.numbers={n0:[1,1,1,0,1,1,1],n1:[0,0,1,0,0,1,0],n2:[1,0,1,1,1,0,1],n3:[1,0,1,1,0,1,1],n4:[0,1,1,1,0,1,0],n5:[1,1,0,1,0,1,1],n6:[0,1,0,1,1,1,1],n7:[1,0,1,0,0,1,0],n8:[1,1,1,1,1,1,1],n9:[1,1,1,1,0,1,1],A:[1,1,1,1,1,1,0],P:[1,1,1,1,1,0,0]};this.stt=performance.now();this.sofar=0 this.update()} DigiClock.prototype.setTime=function(h,m,s){this.fromTime=(((h*60)+m)*60+s)*1000 this.sofar=0 this.stt=performance.now();this.update()} DigiClock.prototype.start=function(){this.stt=performance.now();this.stopQ=false;this.sofar=0 this.loop()} DigiClock.prototype.stop=function(){this.stopQ=true this.sofar=this.total() console.log('stop',this.id,this.sofar/1000,elapsed(),this.stop.caller.name)} DigiClock.prototype.resume=function(){this.stt=performance.now() this.stopQ=false console.log('resume',this.id,this.sofar/1000,elapsed(),this.resume.caller.name) this.loop()} DigiClock.prototype.loop=function(){if(!this.stopQ){this.update() requestAnimationFrame(this.loop.bind(this));}} DigiClock.prototype.total=function(){var elapsed=performance.now()-this.stt elapsed+=this.sofar return elapsed} DigiClock.prototype.clear=function(){this.g.clearRect(0,0,this.wd,this.ht);} DigiClock.prototype.update=function(){this.g.clearRect(0,0,this.wd,this.ht);var elapsed=this.total();if(!this.upQ){elapsed=this.fromTime-elapsed if(elapsed<0){if(!this.stopQ){this.stopQ=true timesUp(this);elapsed=0}}} var hours=elapsed/3.6e6|0;var minutes=elapsed%3.6e6/6e4|0;var seconds=elapsed%6e4/1e3|0;var millis=elapsed%1e3|0;if(hours<10){hours='0'+hours;} if(minutes<10){minutes='0'+minutes;} if(seconds<10){seconds='0'+seconds;} millis=pad('000',''+millis,true) var timeStr='' if(this.hhQ)timeStr+=hours if(this.mmQ)timeStr+=minutes if(this.ssQ)timeStr+=seconds if(this.msQ)timeStr+=millis var posX=this.border var posY=this.border for(var i=0;i';return s;} PlayBtn.prototype.toggle=function(){var div=document.getElementById(this.id) if(my.playQ){my.playQ=false;div.classList.add(this.offClass);div.classList.remove(this.onClass);my.digi.stop()}else{my.playQ=true;div.classList.add(this.onClass);div.classList.remove(this.offClass);my.digi.resume()}} function pad(padFull,str,leftPadded){if(str==undefined)return padFull;if(leftPadded){return(padFull+str).slice(-padFull.length);}else{return(str+padFull).substring(0,padFull.length);}} function elapsed(){return(performance.now()-my.sttTime)/1000} my.drag={n:0,onq:false,holdX:0,holdY:0} class Mouse{constructor(el){console.log('new moose') el.addEventListener('touchstart',this.onTouchStart.bind(this),false) el.addEventListener('touchmove',this.onTouchMove.bind(this),false) window.addEventListener('touchend',this.onTouchEnd.bind(this),false) el.addEventListener('mousedown',this.onMouseDown.bind(this),false) el.addEventListener('mousemove',this.onMouseMove.bind(this),false) window.addEventListener('mouseup',this.onMouseUp.bind(this),false) this.el=el this.ratio=1} onTouchStart(ev){console.log('onTouchStart',this) let touch=ev.targetTouches[0] ev.clientX=touch.clientX ev.clientY=touch.clientY ev.touchQ=true this.onMouseDown(ev)} onTouchMove(ev){let touch=ev.targetTouches[0] ev.clientX=touch.clientX ev.clientY=touch.clientY ev.touchQ=true this.onMouseMove(ev)} onTouchEnd(ev){my.moose.onMouseUp(ev)} onMouseDown(ev){document.getElementById('angA').focus() let mouse=this.mousePos(ev) console.log('moose doon',mouse.x,mouse.y,my.shapes) my.drag.onQ=false my.drag.n=this.hitFind(my.shapes,mouse) if(my.drag.n>=0){console.log('drrragin!',my.drag.n) let pt=my.shapes[my.drag.n] my.drag.holdX=mouse.x-pt.x my.drag.holdY=mouse.y-pt.y my.shapes[my.drag.n].shadQ=true my.drag.onQ=true} ev.preventDefault()} onMouseMove(ev){let mouse=this.mousePos(ev) if(my.drag.onQ){let shape=my.shapes[my.drag.n] let pt={x:mouse.x-my.drag.holdX,y:mouse.y-my.drag.holdY} shape.x=pt.x shape.y=pt.y shape.div.style.left=pt.x+'px' shape.div.style.top=pt.y+'px' shape.div.style.filter='drop-shadow(3px 3px 3px #229)'}else{if(this.hitFind(my.shapes,mouse)>=0){document.body.style.cursor='pointer'}else{document.body.style.cursor='default'}}} onMouseUp(){if(my.drag.onQ){my.shapes[my.drag.n].div.style.filter='none' my.drag.onQ=false} document.body.style.cursor='default'} mousePos(ev){let bRect=this.el.getBoundingClientRect() return{x:(ev.clientX-bRect.left)*(bRect.width/this.ratio/bRect.width),y:(ev.clientY-bRect.top)*(bRect.height/this.ratio/bRect.height),}} hitFind(pts,mouse){for(let i=0;ishape.x+shape.wd)return false if(mouse.y>shape.y+shape.ht)return false return true}} my.theme=localStorage.getItem('theme') my.lineClr=(my.theme=='dark')?'white':'black' my.opts={name:'user'} function optGet(name){let val=localStorage.getItem(`mif.${name}`) if(val==null)val=my.opts[name] return val} function optSet(name,val){localStorage.setItem(`mif.${name}`,val) my.opts[name]=val} function getJSQueryVar(varName,defaultVal){let scripts=document.getElementsByTagName('script');let lastScript=scripts[scripts.length-1];let scriptName=lastScript.src;let bits=scriptName.split('?') if(bits.length<2)return defaultVal let query=bits[1] console.log("query: ",query);let vars=query.split("&");for(let i=0;i{if(cls.length==0)cls='btn' return '