#!/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); }); }); var childpids=[]; process.on("exit",function(){ console.log("Exiting:"); console.log.apply(console,arguments); childpids.forEach(function(pid){process.kill(pid);}); process.exit(); }); process.on("SIGINT",function(){ console.log("SIGINT:"); console.log.apply(console,arguments); childpids.forEach(function(pid){process.kill(pid);}); process.exit(); }); function stopproc(proc){ var pid=proc.pid; console.log("Stopping proc with pid "+pid+", childpids="+JSON.stringify(childpids)); proc.stdin.write("Quit\n"); var timeout=setTimeout(function(){ console.log("Proc with pid "+pid+" didn't stop in time, killing"); proc.kill(); var idx=childpids.indexOf(pid); if(idx!=-1)childpids.splice(idx,1); },500); proc.on("exit",function(){ clearTimeout(timeout); var idx=childpids.indexOf(pid); if(idx!=-1)childpids.splice(idx,1); }); } io.on("connection",function(conn){ var id=uniqid(); console.log("New IO connection id "+id); var pingtimeout=null; var bd=emptyboard(); var aiplayer=0; conn.on("disconnect",function(){ console.log("Disconnect id "+id); if(proc){stopproc(proc);proc=null;} if(pingtimeout)clearTimeout(pingtimeout); }); conn.on("ping",function(){ //only enable ping timeouting if a first ping received if(pingtimeout)clearTimeout(pingtimeout); pingtimeout=setTimeout(function(){ console.log("Ping timeout on id "+id+"!"); if(proc){stopproc(proc);proc=null;} conn.disconnect(true); },4000); }); 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); if(countballs(bd)>2){ var win=checkwin(bd); if(win!=-1){ conn.emit("win",1-aiplayer); if(proc){stopproc(proc);proc=null;} return; } } proc.stdin.write(x+" "+y+"\n"); conn.emit("setonturn",aiplayer); }); var proc=spawn("sh",["-c",aicmd],{ stdio:["pipe","pipe",process.stderr] }); childpids.push(proc.pid); var buffer=""; proc.stdout.on("data",function(data){ var idx,line,mv,win; 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}); if(countballs(bd)>2){ win=checkwin(bd); if(win!=-1){ conn.emit("win",aiplayer); if(proc){stopproc(proc);proc=null;} return; } } 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); });