From c8a692cf39979962c72e8d3ec9108b85105feeb0 Mon Sep 17 00:00:00 2001
From: tomsmeding <tom.smeding@gmail.com>
Date: Fri, 23 Dec 2016 21:40:46 +0100
Subject: Some changes that were lying around

---
 .gitignore                 |   3 +
 fiforead.cpp               |  16 ++
 gameserver/gameserver.html |  18 +-
 gameserver/gameserver.js   |   1 +
 humanbot.cpp               | 254 +++++++++++++++++++++
 humanbot_index.html        | 543 +++++++++++++++++++++++++++++++++++++++++++++
 humanbot_server.js         |  74 ++++++
 7 files changed, 903 insertions(+), 6 deletions(-)
 create mode 100644 fiforead.cpp
 create mode 100644 humanbot.cpp
 create mode 100644 humanbot_index.html
 create mode 100755 humanbot_server.js

diff --git a/.gitignore b/.gitignore
index d4a5d5b..8e7121c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,9 @@ randino
 photon
 charm
 
+fiforead
+humanbot
+
 viewcompetition
 
 *.o
diff --git a/fiforead.cpp b/fiforead.cpp
new file mode 100644
index 0000000..6da0ae1
--- /dev/null
+++ b/fiforead.cpp
@@ -0,0 +1,16 @@
+#include <iostream>
+#include <fstream>
+#include <cassert>
+
+using namespace std;
+
+int main(int argc,char **argv){
+	if(argc!=2){
+		cerr<<"Pass fifo name as argument."<<endl;
+		return 1;
+	}
+	ifstream fifo(argv[1]);
+	assert(fifo.good());
+	while(!!fifo)cout<<(char)fifo.get()<<flush;
+	return 0;
+}
diff --git a/gameserver/gameserver.html b/gameserver/gameserver.html
index 7fc27fc..fdce5a8 100644
--- a/gameserver/gameserver.html
+++ b/gameserver/gameserver.html
@@ -39,6 +39,7 @@ function preinit(){
 		var spaceidx=msg.indexOf(" "),
 		    cmd=msg.slice(0,spaceidx),
 		    arg=JSON.parse(msg.slice(spaceidx+1));
+		var alerttext;
 		console.log(cmd,arg);
 		if(cmd=="nick"){
 			nick=arg;
@@ -96,10 +97,11 @@ function preinit(){
 		} else if(cmd=="game_win"){
 			bgcell(neuidx).classList.add("win");
 			if(arg==imPlayer){
-				alert("You have won the game!");
+				alerttext="You have won the game!";
 			} else {
-				alert("You have lost the game! Better luck next time.");
+				alerttext="You have lost the game! Better luck next time.";
 			}
+			setTimeout(function(){alert(alerttext);},canclick?500:1000);
 		} else alert("Unrecognised message "+msg);
 	});
 	ws.addEventListener("open",function(){
@@ -242,14 +244,18 @@ function enableClicking(en,disneu){
 	en=en?true:false;
 	disneu=disneu?true:false;
 	for(i=0;i<8;i++){
-		document.getElementById("input_neudir"+i)[en&&!disneu?"removeAttribute":"setAttribute"]("disabled");
-		document.getElementById("input_dir"+i)[en?"removeAttribute":"setAttribute"]("disabled");
+		if(en&&!disneu)document.getElementById("input_neudir"+i).removeAttribute("disabled");
+		else document.getElementById("input_neudir"+i).setAttribute("disabled","");
+		if(en)document.getElementById("input_dir"+i).removeAttribute("disabled");
+		else document.getElementById("input_dir"+i).setAttribute("disabled","");
 	}
 	l=document.getElementsByName("inputstoneradio");
 	for(i=0;i<l.length;i++){
-		l[i][en&&(imPlayer==-1||board[i]==imPlayer)?"removeAttribute":"setAttribute"]("disabled");
+		if(en&&(imPlayer==-1||board[i]==imPlayer))l[i].removeAttribute("disabled");
+		else l[i].setAttribute("disabled","");
 	}
-	document.getElementById("input_domovebtn")[en?"removeAttribute":"setAttribute"]("disabled");
+	if(en)document.getElementById("input_domovebtn").removeAttribute("disabled");
+	else document.getElementById("input_domovebtn").setAttribute("disabled","");
 	clickenabled=[en,disneu];
 }
 
diff --git a/gameserver/gameserver.js b/gameserver/gameserver.js
index 8ab4bbc..a9233e9 100755
--- a/gameserver/gameserver.js
+++ b/gameserver/gameserver.js
@@ -225,6 +225,7 @@ function handleMessage(conn,msg){
 		c2.ws.send("other_turn "+JSON.stringify(mv));
 		won=checkWin(game.board,3-game.onturn); //player that just moved
 		if(won){
+			saveFinishedBoard(game.board);
 			conn.ws.send("game_win "+won);
 			c2.ws.send("game_win "+won);
 			games.splice(findby(games,"id",conn.gameid),1);
diff --git a/humanbot.cpp b/humanbot.cpp
new file mode 100644
index 0000000..5902e24
--- /dev/null
+++ b/humanbot.cpp
@@ -0,0 +1,254 @@
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cassert>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#define S (5)
+
+using namespace std;
+
+bool should_flip_board=false;
+
+struct Move;
+class Board;
+
+Move protocol_get_move(istream &s);
+void protocol_put_move(ostream &s,Move m);
+
+int index_deltas[8][2]={{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}};
+
+struct Move{
+	int neudir,from,dir;
+	string str(void){
+		stringstream ss;
+		ss<<neudir<<' '<<from<<' '<<dir;
+		return ss.str();
+	}
+};
+
+class Board{
+public:
+	int grid[S*S];
+	int neuidx;
+	Board(void):neuidx(S*(S/2)+S/2){
+		memset(grid,0,S*S*sizeof(int));
+		grid[S*(S/2)+S/2]=3;
+		for(int i=0;i<S;i++){
+			grid[i]=1;
+			grid[S*(S-1)+i]=2;
+		}
+	}
+	int moved(int at,int dir){
+		int x=at%S,y=at/S,x2,y2;
+		while(true){
+			x2=x+index_deltas[dir][0];
+			y2=y+index_deltas[dir][1];
+			if(x2<0||x2>=S||y2<0||y2>=S||grid[S*y2+x2]!=0)return S*y+x;
+			x=x2;
+			y=y2;
+		}
+	}
+	bool move(int at,int dir){
+		int newat=moved(at,dir);
+		if(newat==at)return false;
+		grid[newat]=grid[at];
+		grid[at]=0;
+		if(grid[newat]==3)neuidx=newat;
+		return true;
+	}
+	void undomove(int at,int dir){
+		int movedidx=moved(at,dir);
+		movedidx+=S*index_deltas[dir][1]+index_deltas[dir][0];
+		grid[at]=grid[movedidx];
+		grid[movedidx]=0;
+		if(grid[at]==3)neuidx=at;
+	}
+	bool isvalid(Move mv){
+		int oldneuidx=neuidx;
+		//cerr<<"Called isvalid with move ";
+		//protocol_put_move(cerr,mv);
+		//print();
+		if(!move(oldneuidx,mv.neudir)){
+			//undomove(oldneuidx,mv.neudir);
+			return false;
+		}
+		if(!move(mv.from,mv.dir)){
+			//undomove(mv.from,mv.dir);
+			undomove(oldneuidx,mv.neudir);
+			return false;
+		}
+		undomove(mv.from,mv.dir);
+		undomove(oldneuidx,mv.neudir);
+		return true;
+	}
+	void print(void){
+		int x,y;
+		if(should_flip_board){
+			for(y=S-1;y>=0;y--){
+				for(x=S-1;x>=0;x--)cerr<<grid[S*y+x]<<' ';
+				cerr<<endl;
+			}
+		} else {
+			for(y=0;y<S;y++){
+				for(x=0;x<S;x++)cerr<<grid[S*y+x]<<' ';
+				cerr<<endl;
+			}
+		}
+	}
+};
+
+
+void restart_server(void);
+void kill_server(void);
+
+ofstream othermovefifo;
+ifstream mynewmovefifo;
+
+Move calculate_move(Board &bd){
+	cerr<<"c_m: restarting server..."<<endl;
+	restart_server();
+	cerr<<"c_m: Waiting for move from fifo..."<<endl;
+	Move mv=protocol_get_move(mynewmovefifo);
+	cerr<<"c_m: Got move from fifo."<<endl;
+	return mv;
+}
+
+long server_pid=-1;
+int server_port=-1;
+
+void setup_server(void){
+	unlink(".humanbot__.__othermove.fifo");
+	assert(mkfifo(".humanbot__.__othermove.fifo",0666)==0);
+
+	unlink(".humanbot__.__mynewmove.fifo");
+	assert(mkfifo(".humanbot__.__mynewmove.fifo",0666)==0);
+
+	server_port=42000+rand()%1000;
+
+	restart_server();
+
+	//only now can we do this, 'cause unix doesn't let a fifo exist with only one end
+	othermovefifo.open(".humanbot__.__othermove.fifo",ofstream::out|ofstream::app);
+	mynewmovefifo.open(".humanbot__.__mynewmove.fifo");
+	cerr<<"Opened fifo's!"<<endl;
+}
+
+void restart_server(void){
+	if(server_pid!=-1)kill_server();
+	pid_t cpid=fork();
+	assert(cpid!=-1);
+	if(cpid==0){ //child
+		cerr<<"Child!"<<endl;
+		server_pid=(long)getpid();
+		char *portstr;
+		asprintf(&portstr,"%d",server_port);
+		assert(!!portstr);
+		execl("/usr/bin/env","node","./humanbot_server.js",portstr,NULL);
+		perror("execlp");
+		exit(1);
+	}
+	cerr<<"Parent!"<<endl;
+}
+
+void kill_server(void){
+	kill(server_pid,SIGTERM); //stay friendly
+	usleep(100*1000); //100ms
+	kill(server_pid,SIGINT);
+	usleep(1000*1000); //1s
+}
+
+
+inline int flip_index(int idx){
+	return S*(S-1-idx/S)+S-1-idx%S;
+}
+inline int flip_dir(int dir){
+	return dir==-1?-1:(dir+4)%8;
+}
+
+Move protocol_get_move(istream &s){
+	Move m;
+	/*char c=s.peek();
+	if(c=='q'||c=='Q')exit(0);*/
+	cerr<<"Sleeping."<<endl;
+	system("sleep 10");
+	s>>m.neudir>>m.from>>m.dir;
+	if(should_flip_board){
+		m.neudir=flip_dir(m.neudir);
+		m.from=flip_index(m.from);
+		m.dir=flip_dir(m.dir);
+	}
+	cerr<<"Got: ";
+	protocol_put_move(cerr,m);
+	s.ignore(1024,'\n');
+	return m;
+}
+
+void protocol_put_move(ostream &s,Move m){
+	if(should_flip_board){
+		s<<flip_dir(m.neudir)<<' '<<flip_index(m.from)<<' '<<flip_dir(m.dir)<<endl;
+	} else {
+		s<<m.neudir<<' '<<m.from<<' '<<m.dir<<endl;
+	}
+}
+
+int main(void){
+	Board bd;
+	Move mv;
+	string line;
+	struct timeval tv;
+	gettimeofday(&tv,NULL);
+	cerr<<"seed="<<(1000000*tv.tv_sec+tv.tv_usec)<<endl;
+	srand(1000000*tv.tv_sec+tv.tv_usec);
+	//srand(1430130052185291);
+	setup_server();
+	getline(cin,line);
+	if(line=="go"){
+		othermovefifo<<"go"<<endl;
+		should_flip_board=false;
+		//mv.neudir=-1; mv.from=1; mv.dir=3;
+		mv=calculate_move(bd);
+		bd.move(mv.from,mv.dir);
+		protocol_put_move(cout,mv);
+	} else {
+		if(line!="nogo")cerr<<"no0b "<<line<<" not in (go,nogo)"<<endl;
+		othermovefifo<<"nogo"<<endl;
+		should_flip_board=true;
+		mv=protocol_get_move(cin);
+		protocol_put_move(othermovefifo,mv);
+		bd.move(mv.from,mv.dir);
+		bd.print();
+		mv=calculate_move(bd);
+		if(!bd.isvalid(mv)){
+			cerr<<"calculate_move gave invalid move "<<mv.str()<<endl;
+			return 1;
+		}
+		bd.move(bd.neuidx,mv.neudir);
+		bd.move(mv.from,mv.dir);
+		protocol_put_move(cout,mv);
+	}
+	while(true){
+		bd.print();
+		mv=protocol_get_move(cin);
+		protocol_put_move(othermovefifo,mv);
+		bd.move(bd.neuidx,mv.neudir);
+		bd.move(mv.from,mv.dir);
+		bd.print();
+		mv=calculate_move(bd);
+		if(!bd.isvalid(mv)){
+			cerr<<"calculate_move gave invalid move "<<mv.str()<<endl;
+			return 1;
+		}
+		bd.move(bd.neuidx,mv.neudir);
+		bd.move(mv.from,mv.dir);
+		protocol_put_move(cout,mv);
+	}
+	return 0;
+}
diff --git a/humanbot_index.html b/humanbot_index.html
new file mode 100644
index 0000000..7d05c7a
--- /dev/null
+++ b/humanbot_index.html
@@ -0,0 +1,543 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Game</title>
+<script>
+var S=5;
+var PLAYERS=["Player 1","Player 2"];
+var MOVES=[];//[[-1,1,3],[7,23,0],[0,0,3]];
+
+
+var moveidx=0;
+var neuidxhist=[];
+
+var board=Array.apply(null,new Array(S*S)).map(function(){return 0;});
+var fgcells=board.map(function(){return null;});
+var neuidx;
+
+var canclick=false;
+
+var imPlayer=-1;
+
+var othermovefifo_interval=null;
+
+var clickenabled=[false,false];
+var input_neudir_choice=null,input_dir_choice=null,input_stone_choice=null;
+
+function init(){
+	var tr,td,y,x,e,span,
+	    movelisttbody,
+	    header=document.getElementById("header"),
+	    status=document.getElementById("status"),
+	    movelist=document.getElementById("movelist"),
+	    bgtbody=document.getElementById("bgtbody");
+
+	document.title="Game: "+PLAYERS[0]+" vs "+PLAYERS[1];
+
+	header.appendChild(document.createTextNode("Game: "));
+	span=document.createElement("span");
+	span.classList.add("celltypeclr"); span.classList.add("celltypeclr_1");
+	span.appendChild(document.createTextNode(PLAYERS[0]));
+	header.appendChild(span);
+	header.appendChild(document.createTextNode(" vs "));
+	span=document.createElement("span");
+	span.classList.add("celltypeclr"); span.classList.add("celltypeclr_2");
+	span.appendChild(document.createTextNode(PLAYERS[1]));
+	header.appendChild(span);
+
+
+	e=document.createElement("table");
+	movelisttbody=document.createElement("tbody");
+	movelisttbody.id="movelisttbody";
+	tr=document.createElement("tr");
+	td=document.createElement("td"); td.innerHTML="Player"; tr.appendChild(td);
+	td=document.createElement("td"); td.innerHTML="Neutron"; tr.appendChild(td);
+	td=document.createElement("td"); td.innerHTML="Stone"; tr.appendChild(td);
+	movelisttbody.appendChild(tr);
+	e.appendChild(movelisttbody);
+	movelist.appendChild(e);
+
+	tr=document.createElement("tr");
+	td=document.createElement("td");
+	td.setAttribute("colspan","3");
+	td.innerHTML="&nbsp;";
+	tr.appendChild(td);
+	(function(tr){
+		tr.addEventListener("click",function(ev){
+			if(moveidx==0)return;
+			while(moveidx>0)prevmove(true);
+			setselectedmovelistline(0);
+		});
+	})(tr);
+	tr.classList.add("selected");
+	movelisttbody.appendChild(tr);
+
+	MOVES.forEach(function(mv,i){
+		addmovelistrow(mv,i);
+	});
+
+
+	var inputstonetbody=document.getElementById("inputstonetbody");
+	for(y=0;y<S;y++){
+		tr=document.createElement("tr");
+		for(x=0;x<S;x++){
+			td=document.createElement("td");
+			e=document.createElement("input");
+			e.setAttribute("type","radio");
+			e.setAttribute("name","inputstoneradio");
+			e.addEventListener("click",(function(x,y){return function(){
+				input_stone_choice=S*y+x;
+			};})(x,y));
+			td.appendChild(e);
+			tr.appendChild(td);
+		}
+		inputstonetbody.appendChild(tr);
+	}
+
+
+	for(y=0;y<S;y++){
+		tr=document.createElement("tr");
+		for(x=0;x<S;x++){
+			td=document.createElement("td");
+			td.classList.add("bgcell");
+			td.innerHTML=S*y+x;
+			tr.appendChild(td);
+		}
+		bgtbody.appendChild(tr);
+	}
+	for(x=0;x<S;x++){
+		board[x]=1;
+		e=makefgcell(x,1);
+		fgcells[x]=e;
+		document.body.appendChild(e);
+
+		board[S*(S-1)+x]=2;
+		e=makefgcell(S*(S-1)+x,2);
+		fgcells[S*(S-1)+x]=e;
+		document.body.appendChild(e);
+	}
+	neuidx=S*~~(S/2)+~~(S/2);
+	board[neuidx]=3;
+	e=makefgcell(neuidx,3);
+	fgcells[neuidx]=e;
+	document.body.appendChild(e);
+
+	setnextenabled(MOVES.length>0);
+	setprevenabled(false);
+	canclick=true;
+
+	othermovefifo_interval=setInterval(function(){
+		if(clickenabled[0]||clickenabled[1])return;
+		var xhr=new XMLHttpRequest();
+		xhr.addEventListener("readystatechange",function(){
+			if(this.readyState!=4)return;
+			console.log("othermove",this.status,this.responseText);
+			if(this.status!=200)return;
+			if(this.responseText=="go"){
+				enableClicking(true,true); //2nd true is to disable neutron dir
+				imPlayer=1;
+			} else if(this.responseText=="nogo"){
+				enableClicking(false);
+				imPlayer=2;
+			} else {
+				var mv=JSON.parse(this.responseText);
+				MOVES.push(mv);
+				addmovelistrow(mv);
+				nextmove();
+				enableClicking(true);
+			}
+		});
+		xhr.open("GET",location.protocol+"//"+location.host+"/othermove");
+		xhr.send();
+	},1500);
+
+	enableClicking(false);
+}
+
+function enableClicking(en,disneu){
+	var i,l;
+	en=en?true:false;
+	disneu=disneu?true:false;
+	for(i=0;i<8;i++){
+		document.getElementById("input_neudir"+i)[en&&!disneu?"removeAttribute":"setAttribute"]("disabled");
+		document.getElementById("input_dir"+i)[en?"removeAttribute":"setAttribute"]("disabled");
+	}
+	l=document.getElementsByName("inputstoneradio");
+	for(i=0;i<l.length;i++){
+		l[i][en&&(imPlayer==-1||board[i]==imPlayer)?"removeAttribute":"setAttribute"]("disabled");
+	}
+	document.getElementById("input_domovebtn")[en?"removeAttribute":"setAttribute"]("disabled");
+	clickenabled=[en,disneu];
+}
+
+function addmovelistrow(mv,i/*optional*/){
+	var tr,td;
+	var movelisttbody=document.getElementById("movelisttbody");
+	var i=i!=undefined?i:movelisttbody.children.length;
+	tr=document.createElement("tr");
+
+	td=document.createElement("td");
+	td.innerHTML=i%2+1;
+	tr.appendChild(td);
+
+	td=document.createElement("td");
+	if(mv[0]==-1)td.innerHTML="&nbsp;";
+	else td.innerHTML=arrowfor(mv[0]);
+	tr.appendChild(td);
+
+	td=document.createElement("td");
+	td.innerHTML=mv[1]+": "+arrowfor(mv[2]);
+	tr.appendChild(td);
+
+	tr.addEventListener("click",(function(target){return function(ev){
+		if(moveidx==target)return;
+		while(moveidx<target)nextmove(true);
+		while(moveidx>target)prevmove(true);
+		setselectedmovelistline(i+1);
+	};})(i+1));
+
+	movelisttbody.appendChild(tr);
+}
+
+function setselectedmovelistline(i){
+	var movelist=document.getElementById("movelist");
+	movelist.querySelectorAll("tr.selected")[0].classList.remove("selected");
+	document.getElementById("movelist").getElementsByTagName("tr")[i+1].classList.add("selected");
+}
+
+function arrowfor(dir){
+	return "&#x"+[2191,2197,2192,2198,2193,2199,2190,2196][dir]+";";
+}
+
+function bgcell(idx){
+	return document.getElementById("bgtbody").children[idx/S|0].children[idx%S];
+}
+function fgcellposition(idx){
+	var rect=bgcell(idx).getBoundingClientRect(),
+	    scrolltop=window.pageYOffset||document.documentElement.scrollTop;
+	return [rect.left,rect.top+scrolltop];
+}
+function movefgcell(e,idx){
+	var pos=fgcellposition(idx);
+	e.style.left=pos[0]+"px";
+	e.style.top=pos[1]+"px";
+	e.cell_index=idx;
+}
+function makefgcell(idx,type){ //type in [1,2,3]
+	var pos=fgcellposition(idx),
+	    e=document.createElement("div");
+	e.classList.add("fgcell");
+	e.classList.add("celltype_"+type);
+	e.setAttribute("style","left:"+pos[0]+"px;top:"+pos[1]+"px;");
+	e.cell_index=idx;
+	e.appendChild(document.createElement("div"));
+	return e;
+}
+
+var index_deltas=[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];
+function indexafterslide(from,dir,overshoot){
+	var x=from%S,y=~~(from/S),x2,y2;
+	while(true){
+		x2=x+index_deltas[dir][0];
+		y2=y+index_deltas[dir][1];
+		if(x2<0||x2>=S||y2<0||y2>=S||board[S*y2+x2]!=0){
+			if(overshoot)return S*y2+x2;
+			else return S*y+x;
+		}
+		x=x2;
+		y=y2;
+	}
+}
+
+function setnextenabled(en){
+	document.getElementById("nextmovebtn").disabled=!en;
+}
+function setprevenabled(en){
+	document.getElementById("prevmovebtn").disabled=!en;
+}
+
+function nextmove(notimeout){
+	var newidx;
+	if(moveidx==MOVES.length||!canclick)return false;
+
+	if(MOVES[moveidx][0]!=-1){
+		neuidxhist.push(neuidx);
+		newidx=indexafterslide(neuidx,MOVES[moveidx][0]);
+		movefgcell(fgcells[neuidx],newidx);
+		board[newidx]=board[neuidx];
+		board[neuidx]=0;
+		fgcells[newidx]=fgcells[neuidx];
+		fgcells[neuidx]=null;
+		neuidx=newidx;
+
+		if(moveidx==MOVES.length-1)bgcell(neuidx).classList.add("win");
+	}
+
+	newidx=indexafterslide(MOVES[moveidx][1],MOVES[moveidx][2]);
+	if(MOVES[moveidx][0]!=-1&&!notimeout){
+		setTimeout(
+			(function(e,idx){return function(){
+				movefgcell(e,idx);
+				canclick=true;
+			};})(fgcells[MOVES[moveidx][1]],newidx),
+			500
+		);
+		canclick=false;
+	} else {
+		movefgcell(fgcells[MOVES[moveidx][1]],newidx);
+	}
+	board[newidx]=board[MOVES[moveidx][1]];
+	board[MOVES[moveidx][1]]=0;
+	fgcells[newidx]=fgcells[MOVES[moveidx][1]];
+	fgcells[MOVES[moveidx][1]]=null;
+
+	moveidx++;
+	setprevenabled(true);
+	if(moveidx==MOVES.length)setnextenabled(false);
+	if(!notimeout)setselectedmovelistline(moveidx);
+	return true;
+}
+function prevmove(notimeout){
+	var item,oldneuidx,newidx,winGlowIdxToRemove=null;
+	if(moveidx==0||!canclick)return false;
+	moveidx--;
+
+	if(MOVES[moveidx][0]!=-1&&moveidx==MOVES.length-1)winGlowIdxToRemove=neuidx;
+
+	newidx=indexafterslide(MOVES[moveidx][1],MOVES[moveidx][2],true);
+	board[MOVES[moveidx][1]]=board[newidx];
+	board[newidx]=0;
+	fgcells[MOVES[moveidx][1]]=fgcells[newidx];
+	fgcells[newidx]=null;
+	movefgcell(fgcells[MOVES[moveidx][1]],MOVES[moveidx][1]);
+
+	if(MOVES[moveidx][0]!=-1){
+		oldneuidx=neuidxhist.pop();
+		board[oldneuidx]=board[neuidx];
+		board[neuidx]=0;
+		fgcells[oldneuidx]=fgcells[neuidx];
+		fgcells[neuidx]=null;
+		if(notimeout){
+			movefgcell(fgcells[oldneuidx],oldneuidx);
+			if(winGlowIdxToRemove!=null)bgcell(winGlowIdxToRemove).classList.remove("win");
+		} else {
+			setTimeout(
+				(function(e,idx){return function(){
+					movefgcell(e,idx);
+					canclick=true;
+					if(winGlowIdxToRemove!=null)bgcell(winGlowIdxToRemove).classList.remove("win");
+				};})(fgcells[oldneuidx],oldneuidx),
+				500
+			);
+			canclick=false;
+		}
+		neuidx=oldneuidx;
+	}
+
+	setnextenabled(true);
+	if(moveidx==0)setprevenabled(false);
+	if(!notimeout)setselectedmovelistline(moveidx);
+	return true;
+}
+
+
+function input_neudir(dir){
+	var i;
+	for(i=0;i<8;i++){
+		if(i==dir)document.getElementById("input_neudir"+i).classList.add("selected");
+		else document.getElementById("input_neudir"+i).classList.remove("selected");
+	}
+}
+
+function input_dir(dir){
+	var i;
+	for(i=0;i<8;i++){
+		if(i==dir)document.getElementById("input_dir"+i).classList.add("selected");
+		else document.getElementById("input_dir"+i).classList.remove("selected");
+	}
+}
+
+function input_domove(){
+	var i,l,mv=[-1,-1,-1];
+	for(i=0;i<8;i++){
+		if(document.getElementById("input_neudir"+i).classList.contains("selected")){
+			mv[0]=i;
+			break;
+		}
+	}
+	for(i=0;i<8;i++){
+		if(document.getElementById("input_dir"+i).classList.contains("selected")){
+			mv[2]=i;
+			break;
+		}
+	}
+	l=document.getElementsByName("inputstoneradio");
+	for(i=0;i<l.length;i++){
+		if(l[i].checked){
+			mv[1]=i;
+			break;
+		}
+	}
+	if(mv[0]==-1&&!clickenabled[1]){alert("Neutron direction not set!");return;}
+	if(mv[1]==-1){alert("Stone choice not set!");return;}
+	if(mv[2]==-1){alert("Stone direction not set!");return;}
+	var clickenabledcache=clickenabled;
+	enableClicking(false);
+	var xhr=new XMLHttpRequest();
+	xhr.addEventListener("readystatechange",function(){
+		if(this.readyState!=4)return;
+		console.log("mynewmove",this.status,this.responseText);
+		if(this.status!=200){
+			alert("Failed to submit move!");
+			enableClicking.apply(this,clickenabledcache);
+		} else {
+			MOVES.push(mv);
+			addmovelistrow(mv);
+			nextmove();
+		}
+	});
+	xhr.open("POST",location.protocol+"//"+location.host+"/mynewmove");
+	xhr.send(JSON.stringify(mv));
+}
+
+
+window.addEventListener("load",init,false);
+</script>
+<style>
+body{
+	font-family:Georgia,Times,Serif;
+	font-size:15px;
+}
+
+table#bgtab{
+	border-spacing:4px;
+}
+td.bgcell{
+	width:80px;
+	height:80px;
+	border:3px #ddd solid;
+	border-radius:12px;
+	transition:background-color 0.6s ease 0.2s, box-shadow 0.6s ease 0.2s, -webkit-box-shadow 0.6s ease 0.2s;
+
+	text-align:right;
+	vertical-align:bottom;
+	font-size:14px;
+}
+td.bgcell.win{
+	background-color:#8f8;
+	        box-shadow:inset 0 0 6px #6e6;
+	-webkit-box-shadow:inset 0 0 6px #6e6;
+}
+div.fgcell{
+	position:absolute;
+	width:82px;
+	height:82px;
+	border:3px rgba(0,0,0,0) solid;
+	text-align:center;
+	transition:left 0.6s ease 0s, top 0.6s ease 0s;
+}
+
+div.fgcell > div{
+	height:75%;
+	margin:10px;
+	border-radius:30px;
+}
+
+h1#header{
+	letter-spacing:0.5px;
+}
+span.celltypeclr{
+	border-radius:5px;
+	padding:4px;
+}
+
+div#status{
+	font-size:16px;
+	font-weight:bold;
+}
+
+div.fgcell.celltype_1 > div{background-color:#ddd;}
+span.celltypeclr_1         {background-color:#ddd;}
+div.fgcell.celltype_2 > div{background-color:#2a2a2a;}
+span.celltypeclr_2         {background-color:#2a2a2a;color:#eee;}
+div.fgcell.celltype_3 > div{background-color:#11e;}
+
+div#movelist{
+	border:1px black solid;
+	width:500px;
+	height:200px;
+	overflow-y:scroll;
+}
+div#movelist table{
+	border-spacing:0;
+	width:100%;
+}
+div#movelist tr:first-child{
+	font-weight:bold;
+	background-color:#ccc;
+}
+div#movelist tr:first-child > td{
+	border-bottom:1px black solid;
+}
+div#movelist tr:nth-child(odd){background-color:#e4e4e4;}
+div#movelist tr:nth-child(even){background-color:#fff;}
+div#movelist tr:nth-child(n+2):hover{background-color:#bbf;cursor:pointer;}
+div#movelist tr.selected{
+	background-color:#afa;
+}
+
+input[type=button].selected{
+	background-color:#aaf;
+}
+</style>
+</head>
+<body>
+<h1 id="header"></h1>
+<table id="bgtab" style="display:inline-block"><tbody id="bgtbody"></tbody></table>
+<table id="inputneudirtab" style="display:inline-block"><tbody id="inputneudirtbody">
+	<tr>
+		<td><input type="button" id="input_neudir7" value="&#x2196;" onclick="input_neudir(7)"></td>
+		<td><input type="button" id="input_neudir0" value="&#x2191;" onclick="input_neudir(0)"></td>
+		<td><input type="button" id="input_neudir1" value="&#x2197;" onclick="input_neudir(1)"></td>
+	</tr>
+	<tr>
+		<td><input type="button" id="input_neudir6" value="&#x2190;" onclick="input_neudir(6)"></td>
+		<td>Neu</td>
+		<td><input type="button" id="input_neudir2" value="&#x2192;" onclick="input_neudir(2)"></td>
+	</tr>
+	<tr>
+		<td><input type="button" id="input_neudir5" value="&#x2199;" onclick="input_neudir(5)"></td>
+		<td><input type="button" id="input_neudir4" value="&#x2193;" onclick="input_neudir(4)"></td>
+		<td><input type="button" id="input_neudir3" value="&#x2198;" onclick="input_neudir(3)"></td>
+	</tr>
+</tbody></table>
+<table id="inputdirtab" style="display:inline-block;margin-left:20px"><tbody id="inputdirtbody">
+	<tr>
+		<td><input type="button" id="input_dir7" value="&#x2196;" onclick="input_dir(7)"></td>
+		<td style="text-align:center"><input type="button" id="input_dir0" value="&#x2191;" onclick="input_dir(0)"></td>
+		<td><input type="button" id="input_dir1" value="&#x2197;" onclick="input_dir(1)"></td>
+	</tr>
+	<tr>
+		<td><input type="button" id="input_dir6" value="&#x2190;" onclick="input_dir(6)"></td>
+		<td>
+			<table id="inputstonetab" style="display:inline-block"><tbody id="inputstonetbody"></tbody></table>
+		</td>
+		<td><input type="button" id="input_dir2" value="&#x2192;" onclick="input_dir(2)"></td>
+	</tr>
+	<tr>
+		<td><input type="button" id="input_dir5" value="&#x2199;" onclick="input_dir(5)"></td>
+		<td style="text-align:center"><input type="button" id="input_dir4" value="&#x2193;" onclick="input_dir(4)"></td>
+		<td><input type="button" id="input_dir3" value="&#x2198;" onclick="input_dir(3)"></td>
+	</tr>
+</tbody></table>
+<div style="display:inline-block;margin-left:10px">
+	<input type="button" id="input_domovebtn" value="Do move" onclick="input_domove()">
+	<br>
+</div>
+<br>
+<input type="button" id="prevmovebtn" onclick="prevmove()" value="&larr;">
+<input type="button" id="nextmovebtn" onclick="nextmove()" value="&rarr;"><br>
+<div id="movelist" style="margin-top:30px"></div>
+</body>
+</html>
diff --git a/humanbot_server.js b/humanbot_server.js
new file mode 100755
index 0000000..3f38f66
--- /dev/null
+++ b/humanbot_server.js
@@ -0,0 +1,74 @@
+#!/usr/bin/env node
+var http=require("http"),fs=require("fs");
+var PORT=+process.argv[2];
+var movequeue=[];
+var httpserver=http.createServer(function(req,res){
+	var reqbody="";
+	console.log("req: "+req.url);
+	if(req.url=="/othermove"){
+		if(movequeue.length){
+			res.writeHead(200,{"Content-Type":"application/json"});
+			res.end(movequeue.shift());
+		} else {
+			res.writeHead(503,{"Content-Type":"text/plain"});
+			res.end("No move to unqueue");
+		}
+	} else if(req.url=="/mynewmove"){
+		req.on("data",function(data){reqbody+=data;});
+		req.on("end",function(){
+			var mv;
+			console.log("mynewmove: reqbody = "+reqbody);
+			try {mv=JSON.parse(reqbody);}
+			catch(e){mv=null;}
+			if(mv==null||
+				!Array.isArray(mv)||
+				mv.length!=3||
+				!mv.map(function(v){
+					return typeof(v)=="number"&&v%1==0;
+				}).reduce(function(a,b){return a&&b;})
+			){
+				res.writeHead(400,{"Content-Type":"text/plain"});
+				res.end("400 Bad request");
+				return;
+			}
+			mynewmovefifo.write(mv.join(" ")+"\n",function(){
+				console.log("server: ACTUALLY written new move to fifo");
+			});
+			res.writeHead(200,{"Content-Type":"text/plain"});
+			res.end("true");
+		});
+	} else if(req.url=="/"){
+		res.writeHead(200,{"Content-Type":"text/html"});
+		res.end(String(fs.readFileSync("humanbot_index.html")));
+	} else {
+		res.writeHead(404,{"Content-Type":"text/plain"});
+		res.end("Nothing here...");
+	}
+});
+var httpserverlisteninterval=setInterval(function(){
+	try {
+		httpserver.listen(PORT);
+		console.log("Server running at port "+PORT+". (http://localhost:"+PORT+")");
+		clearInterval(httpserverlisteninterval);
+	} catch(e){}
+},10);
+
+var othermovefifo,mynewmovefifo,othermovefifo_buffer="";
+othermovefifo=fs.createReadStream(".humanbot__.__othermove.fifo");
+othermovefifo.on("data",function(data){
+	var idx,v;
+	othermovefifo_buffer+=data;
+	idx=othermovefifo_buffer.indexOf("\n");
+	if(idx!=-1){
+		v=othermovefifo_buffer.slice(0,idx);
+		if(v!="go"&&v!="nogo")v="["+v.replace(/ /g,",")+"]";
+		movequeue.push(v);
+		othermovefifo_buffer=othermovefifo_buffer.slice(idx+1);
+	}
+});
+othermovefifo.on("end",function(){
+	console.log("othermovefifo closed. Exiting.");
+	process.exit();
+});
+mynewmovefifo=fs.createWriteStream(".humanbot__.__mynewmove.fifo",{flags:"a"});
+console.log("Fifo's opened.");
-- 
cgit v1.2.3-70-g09d2