<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Interactor for Chain Reaction</title> <script src="/socket.io/socket.io.js"></script> <script src="/common.js"></script> <script> var CVSH=500; var COLOURS=["#00F","#F00","#0CC"]; var socket=io(); var CELLSZ=~~(CVSH/(H+1)); var CVSW=CELLSZ*(W+1); var CELL0X=~~(CVSW/2-W/2*CELLSZ)+.5,CELL0Y=~~(CVSH/2-H/2*CELLSZ)+.5; var bd; var cvs,ctx; var onturn; var usercanmove; function init(){ cvs=document.getElementById("cvs"); cvs.width=CVSW; cvs.height=CVSH; ctx=cvs.getContext("2d"); cvs.addEventListener("click",function(ev){ if(!usercanmove)return; var bbox=ev.target.getBoundingClientRect(); var cx=ev.clientX-bbox.left,cy=ev.clientY-bbox.top; var x=~~((cx-CELL0X)/CELLSZ),y=~~((cy-CELL0Y)/CELLSZ); if(x<0||y<0||x>=W||y>=H)return; if(bd[y][x].n&&bd[y][x].c!=onturn)return; applymove(W*y+x,onturn); socket.emit("usermove",W*y+x); usercanmove=false; setstatustext("<i>Waiting for other move...</i>"); }); onturn=0; usercanmove=false; bd=emptyboard(); drawboard(bd,onturn); setstatustext("<i>Initialised.</i>"); } function drawboard(bd,clr){ var x,y,i,angle,radius; ctx.clearRect(0,0,CVSW,CVSH); ctx.strokeStyle=COLOURS[+clr]; ctx.beginPath(); for(y=0;y<H;y++){ for(x=0;x<W;x++){ ctx.moveTo(CELL0X+x*CELLSZ,CELL0Y+y*CELLSZ); ctx.lineTo(CELL0X+(x+1)*CELLSZ,CELL0Y+y*CELLSZ); ctx.lineTo(CELL0X+(x+1)*CELLSZ,CELL0Y+(y+1)*CELLSZ); ctx.lineTo(CELL0X+x*CELLSZ,CELL0Y+(y+1)*CELLSZ); ctx.lineTo(CELL0X+x*CELLSZ,CELL0Y+y*CELLSZ); } } ctx.stroke(); for(y=0;y<H;y++){ for(x=0;x<W;x++){ ctx.fillStyle=COLOURS[bd[y][x].c]; angle=1/bd[y][x].n*2*Math.PI; radius=bd[y][x].n==1?0:.15; for(i=0;i<bd[y][x].n;i++){ ctx.beginPath(); ctx.arc( CELL0X+(x+.5+radius*Math.cos(angle*i))*CELLSZ, CELL0Y+(y+.5+radius*Math.sin(angle*i))*CELLSZ, CELLSZ*.2,0,2*Math.PI,1); ctx.fill(); } } } } function stabiliseanims(bd){ var anims=[],stage; var newbd; var x,y,nnei,quo; do { stage=[]; newbd=bdcopy(bd); for(y=0;y<H;y++){ for(x=0;x<W;x++){ nnei=(y>0)+(x>0)+(y<H-1)+(x<W-1); if(bd[y][x].n>=nnei){ quo=~~(bd[y][x].n/nnei); newbd[y][x].n-=quo*nnei; if(y>0) { newbd[y-1][x].n+=quo; newbd[y-1][x].c=bd[y][x].c; stage.push([W*y+x,W*(y-1)+x]); } if(x>0) { newbd[y][x-1].n+=quo; newbd[y][x-1].c=bd[y][x].c; stage.push([W*y+x,W*y+x-1]); } if(y<H-1){ newbd[y+1][x].n+=quo; newbd[y+1][x].c=bd[y][x].c; stage.push([W*y+x,W*(y+1)+x]); } if(x<W-1){ newbd[y][x+1].n+=quo; newbd[y][x+1].c=bd[y][x].c; stage.push([W*y+x,W*y+x+1]); } } } } bd=newbd; anims.push([stage,bdcopy(bd)]); if(checkwin(bd)!=-1)break; } while(stage.length); return anims; } var applymove_queue=[]; var applymove_busy=false; function applymove(idx,c){ assert(idx>=0&&idx<W*H); applymove_busy=true; bd[~~(idx/W)][idx%W].n++; bd[~~(idx/W)][idx%W].c=c; drawboard(bd,onturn); var anims=stabiliseanims(bd); anims.forEach(function(pair,i){ var stage=pair[0],newbd=pair[1]; var finalstage=i==anims.length-1; setTimeout(function(){ var time=0; var interval=setInterval(function(){ var i,fx,fy,tx,ty; for(i=0;i<stage.length;i++){ fx=stage[i][0]%W; fy=~~(stage[i][0]/W); bd[fy][fx].n--; } drawboard(bd,onturn); for(i=0;i<stage.length;i++){ fx=stage[i][0]%W; fy=~~(stage[i][0]/W); tx=stage[i][1]%W; ty=~~(stage[i][1]/W); ctx.fillStyle=COLOURS[bd[fy][fx].c]; ctx.beginPath(); ctx.arc( CELL0X+(fx*(1-time/10)+tx*time/10+.5)*CELLSZ, CELL0Y+(fy*(1-time/10)+ty*time/10+.5)*CELLSZ, CELLSZ*.2,0,2*Math.PI,1); ctx.fill(); } if(time==10){ clearInterval(interval); bd=newbd; drawboard(bd,onturn); if(finalstage){ applymove_busy=false; while(applymove_queue.length&&typeof applymove_queue[0]=="function"){ applymove_queue[0](); applymove_queue.shift(); } if(applymove_queue.length){ setTimeout(function(){ applymove(applymove_queue[0][0],applymove_queue[0][1]); applymove_queue.shift(); },50); } } } time++; },20); },350*i+1); }); } function queueapplymove(idx,c){ if(applymove_busy)applymove_queue.push([idx,c]); else applymove(idx,c); } function getusermove(){ usercanmove=true; setstatustext("<b>Your turn!</b>"); } function setstatustext(text){ var elem=document.getElementById("statustext"); elem.innerHTML=text; } function assert(cond){if(!cond)throw new Error("Assertion failed");} socket.on("emptyboard",function(){ bd=emptyboard(); }); socket.on("applymove",function(obj){ queueapplymove(obj.index,obj.player); }); socket.on("alert",function(text){ alert(text); }); socket.on("setonturn",function(player){ if(applymove_busy)applymove_queue.push(function(){ onturn=player; drawboard(bd,onturn); }); else { onturn=player; drawboard(bd,onturn); } }); socket.on("getusermove",function(){ if(applymove_busy)applymove_queue.push(getusermove); else getusermove(); }); socket.on("win",function(player){ setstatustext("<b><i>Player "+(player+1)+" won!</i></b>"); }); </script> </head> <body onload="init()"> <h3>Interactor for Chain Reaction</h3> <canvas id="cvs"></canvas><br> <span id="statustext"></span> </body> </html>