From e22b0edd4822ad5030d19d8fdb61511690e34239 Mon Sep 17 00:00:00 2001
From: tomsmeding <hallo@tomsmeding.nl>
Date: Fri, 23 Oct 2015 12:23:11 +0200
Subject: handwapper

---
 interactor/.gitignore    |   1 +
 interactor/common.js     |  43 ++++++++++
 interactor/index.html    | 213 +++++++++++++++++++++++++++++++++++++++++++++++
 interactor/interactor.js |  96 +++++++++++++++++++++
 interactor/package.json  |  15 ++++
 5 files changed, 368 insertions(+)
 create mode 100644 interactor/.gitignore
 create mode 100644 interactor/common.js
 create mode 100644 interactor/index.html
 create mode 100755 interactor/interactor.js
 create mode 100644 interactor/package.json

(limited to 'interactor')

diff --git a/interactor/.gitignore b/interactor/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/interactor/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/interactor/common.js b/interactor/common.js
new file mode 100644
index 0000000..dbc3484
--- /dev/null
+++ b/interactor/common.js
@@ -0,0 +1,43 @@
+var W=7,H=8;
+
+function emptyboard(){
+	return new Array(H).fill(0).map(function(){
+		return new Array(W).fill(0).map(function(){
+			return {n:0,c:0};
+		});
+	});
+}
+
+function bdcopy(bd){
+	return bd.map(function(r){
+		return r.map(function(c){
+			return {n:c.n,c:c.c};
+		});
+	});
+}
+
+function stabilise(bd){
+	var newbd;
+	var changes;
+	var x,y,nnei,quo;
+	do {
+		changes=false;
+		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;}
+					if(x>0)  {newbd[y][x-1].n+=quo;newbd[y][x-1].c=bd[y][x].c;}
+					if(y<H-1){newbd[y+1][x].n+=quo;newbd[y+1][x].c=bd[y][x].c;}
+					if(x<W-1){newbd[y][x+1].n+=quo;newbd[y][x+1].c=bd[y][x].c;}
+					changes=true;
+				}
+			}
+		}
+		bd=newbd;
+	} while(changes);
+	return bd;
+}
diff --git a/interactor/index.html b/interactor/index.html
new file mode 100644
index 0000000..c861c82
--- /dev/null
+++ b/interactor/index.html
@@ -0,0 +1,213 @@
+<!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:.12;
+			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)]);
+	} 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;
+						if(applymove_queue.length){
+							setTimeout(function(){
+								applymove(applymove_queue[0][0],applymove_queue[0][1]);
+								applymove_queue.shift();
+							},50);
+						}
+					}
+				}
+				time++;
+			},30);
+		},500*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){
+	onturn=player;
+	drawboard(bd,onturn);
+});
+socket.on("getusermove",function(){
+	getusermove();
+});
+</script>
+</head>
+<body onload="init()">
+<h3>Interactor for Chain Reaction</h3>
+<canvas id="cvs"></canvas><br>
+<span id="statustext"></span>
+</body>
+</html>
\ No newline at end of file
diff --git a/interactor/interactor.js b/interactor/interactor.js
new file mode 100755
index 0000000..2d38cbb
--- /dev/null
+++ b/interactor/interactor.js
@@ -0,0 +1,96 @@
+#!/usr/bin/env node
+
+var aicmd;
+
+if(process.argv.length!=3){
+	console.log("Give AI command to run as command line argument.");
+	process.exit(1);
+}
+aicmd=process.argv[2];
+console.log("Using AI command '"+aicmd+"'");
+
+
+var app=require("express")(),
+    http=require("http").Server(app),
+    io=require("socket.io")(http),
+    fs=require("fs"),
+    spawn=require("child_process").spawn;
+
+eval(String(fs.readFileSync("common.js")));
+
+
+var HTTPPORT=8080;
+
+var uniqid=(function(){
+	var id=0;
+	return function(){return id++;};
+})();
+
+app.get("/",function(req,res){
+	res.sendFile(__dirname+"/index.html");
+});
+
+["index.html","common.js"].forEach(function(fname){
+	app.get("/"+fname,function(req,res){
+		res.sendFile(__dirname+"/"+fname);
+	});
+});
+
+io.on("connection",function(conn){
+	var id=uniqid();
+	console.log("New IO connection id "+id);
+
+	var bd=emptyboard();
+	var aiplayer=0;
+
+	conn.on("disconnect",function(){
+		console.log("Disconnect id "+id);
+	});
+	conn.on("usermove",function(idx){
+		idx=+idx;
+		if(idx<0||idx>=W*H){
+			conn.emit("alert","You sent an invalid move, index "+idx);
+			conn.emit("getusermove");
+			return;
+		}
+		var x=idx%W,y=~~(idx/W);
+		bd[y][x].c=1-aiplayer;
+		bd[y][x].n++;
+		bd=stabilise(bd);
+		proc.stdin.write(x+" "+y+"\n");
+	});
+
+
+	var proc=spawn("sh",["-c",aicmd],{
+		stdio:["pipe","pipe","inherit"]
+	});
+	var buffer="";
+	proc.stdout.on("data",function(data){
+		var idx,line,mv;
+		buffer+=data;
+		while((idx=buffer.indexOf("\n"))!=-1){
+			line=buffer.slice(0,idx);
+			buffer=buffer.slice(idx+1);
+			mv=line.split(" ").map(function(s){return parseInt(s,10);});
+			if(mv.length!=2||isNaN(mv[0])||isNaN(mv[1])){
+				console.log("Invalid move written by AI: '"+line+"'");
+				conn.emit("alert","Invalid move written by AI: '"+line+"'");
+				mv[0]=0;
+				mv[1]=0;
+			}
+			bd[mv[1]][mv[0]].c=aiplayer;
+			bd[mv[1]][mv[0]].n++;
+			bd=stabilise(bd);
+			conn.emit("applymove",{index:W*mv[1]+mv[0],player:aiplayer});
+			conn.emit("setonturn",1-aiplayer);
+			conn.emit("getusermove");
+		}
+	});
+	proc.stdin.write("A\n-1 -1\n");
+	conn.emit("emptyboard");
+	conn.emit("setonturn",0);
+});
+
+http.listen(HTTPPORT,function(){
+	console.log("Listening on http://localhost:"+HTTPPORT);
+});
diff --git a/interactor/package.json b/interactor/package.json
new file mode 100644
index 0000000..38f7446
--- /dev/null
+++ b/interactor/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "interactor",
+  "version": "1.0.0",
+  "description": "",
+  "main": "interactor.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "Tom Smeding <hallo@tomsmeding.nl> (http://tomsmeding.com)",
+  "license": "MIT",
+  "dependencies": {
+    "express": "^4.13.3",
+    "socket.io": "^1.3.7"
+  }
+}
-- 
cgit v1.2.3-70-g09d2