aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--gameserver/gameserver.html613
-rwxr-xr-xgameserver/gameserver.js251
-rw-r--r--gameserver/package.json15
4 files changed, 882 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 99f7820..d4a5d5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,6 @@ viewcompetition
*.o
competition.log
+
+
+node_modules
diff --git a/gameserver/gameserver.html b/gameserver/gameserver.html
new file mode 100644
index 0000000..d8528ab
--- /dev/null
+++ b/gameserver/gameserver.html
@@ -0,0 +1,613 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Game</title>
+<script>
+var S=5;
+var PLAYERS=["Player 1","Player 2"];
+var MOVES=[];
+
+var ws=null;
+var nick=null;
+var onturn=null;
+
+
+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 isFirstMove=true;
+
+var imPlayer=-1;
+
+//var othermovefifo_interval=null;
+
+var clickenabled=[false,false];
+var input_neudir_choice=null,input_dir_choice=null,input_stone_choice=null;
+
+var hasInited=false,canAlreadyTurn=false;
+
+function preinit(){
+ ws=new WebSocket("ws://"+location.hostname+":8383");
+ ws.addEventListener("message",function(msg){
+ msg=msg.data;
+ var spaceidx=msg.indexOf(" "),
+ cmd=msg.slice(0,spaceidx),
+ arg=JSON.parse(msg.slice(spaceidx+1));
+ console.log(cmd,arg);
+ if(cmd=="nick"){
+ nick=arg;
+ document.getElementById("nickname").innerHTML=nick;
+ var choice=prompt("Your nickname is \""+nick+"\".\nEither you or your friend has to enter the other's nickname below; the other can press cancel or leave the field blank.");
+ if(!choice)return;
+ ws.send("game_create "+choice);
+ } else if(cmd=="game_create"){
+ if(arg==true){
+ ws.send("game_info");
+ imPlayer=1;
+ } else alert("ERROR:\n"+arg[1]);
+ } else if(cmd=="joined_game"){
+ ws.send("game_info");
+ imPlayer=2;
+ } else if(cmd=="game_info"){
+ if(arg==null){
+ alert("Not in a game!");
+ return;
+ }
+ PLAYERS=arg[0];
+ board=arg[1];
+ onturn=arg[2];
+ init(); //the magic call
+ hasInited=true;
+ if(canAlreadyTurn){
+ enableClicking(true,true);
+ isFirstMove=false;
+ canAlreadyTurn=false;
+ }
+ } else if(cmd=="game_doturn"){
+ if(arg[0]){
+ MOVES.push(arg[1]);
+ addmovelistrow(arg[1]);
+ nextmove();
+ } else alert(arg[1]);
+ } else if(cmd=="your_turn"){
+ canAlreadyTurn=true;
+ if(hasInited){
+ enableClicking(true,isFirstMove&&imPlayer==1);
+ isFirstMove=false;
+ }
+ if(arg){
+ MOVES.push(arg);
+ addmovelistrow(arg);
+ nextmove();
+ }
+ }
+ });
+ ws.addEventListener("open",function(){
+ ws.send("nick");
+ });
+}
+
+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));*/
+ ws.send("game_doturn "+mv.join(" "));
+}
+
+
+window.addEventListener("load",preinit,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>
+<div id="nickname"></div>
+</body>
+</html>
diff --git a/gameserver/gameserver.js b/gameserver/gameserver.js
new file mode 100755
index 0000000..ddc6f0b
--- /dev/null
+++ b/gameserver/gameserver.js
@@ -0,0 +1,251 @@
+#!/usr/bin/env node
+
+"use strict";
+
+var fs=require("fs"),
+ http=require("http"),
+ util=require("util"),
+ assert=require("assert"),
+ WebSocketServer=require("ws").Server,
+ Naampje=require("naampje").name;
+var HTTPPORT=83,WSPORT=8383;
+
+var conns=[],games=[];
+
+var genid=(function(){
+ var $=1;
+ return function(){return $++;};
+})();
+
+function findby(a,key,val){
+ for(var i=0;i<a.length;i++)if(a[i][key]==val)return i;
+ return -1;
+}
+
+function nickIsTaken(nick){
+ var i;
+ for(i=0;i<conns.length;i++)if(conns[i].nick==nick)return true;
+ return false;
+}
+
+function numberiseNick(nick){
+ var i;
+ for(i=0;;i++){
+ if(!nickIsTaken(nick+i))return nick+i;
+ }
+}
+
+function gennick(){
+ var i,nick;
+ for(i=0;i<10;i++){
+ nick=Naampje();
+ if(!nickIsTaken(nick))break;
+ }
+ if(i<10)return nick;
+ else return numberiseNick(nick);
+}
+
+function Connection(ws){
+ if(!(this instanceof Connection))return new Connection(ws);
+ this.id=genid();
+ this.nick=gennick();
+ this.ws=ws;
+ this.gameid=null;
+}
+
+function Game(c1,c2){
+ if(!(this instanceof Game))return new Game(c1,c2);
+ this.id=genid();
+ this.i1=c1.id;
+ this.i2=c2.id;
+ this.board=[1,1,1,1,1,
+ 0,0,0,0,0,
+ 0,0,3,0,0,
+ 0,0,0,0,0,
+ 2,2,2,2,2];
+ this.onturn=1; //1 or 2
+ this.firstmove=true; //in first move, neudir==-1
+}
+
+function saveFinishedBoard(bd){
+ var fname=process.hrtime();
+ if(fs.existsSync("winboards")){
+ if(!fs.lstatPath("winboards").isDirectory()){
+ console.log("winboards is not a directory. Bleh.");
+ return;
+ }
+ } else {
+ fs.mkdirSync("winboards");
+ }
+ fname="winboards/"+fname[0]+("000000000"+fname[1]).slice(-9)+".txt";
+ if(fs.existsSync(fname)){ //don't think this'll happen often
+ console.log("saveFinishedBoard: file '"+fname+"' already exists!");
+ return;
+ }
+ fs.writeFileSync(fname,bd.join(""));
+}
+
+var idxAfterSlide__xy_deltas=[[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1]];
+function idxAfterSlide(bd,idx,dir){
+ var x=idx%5,y=~~(idx/5),x2,y2,d=idxAfterSlide__xy_deltas[dir],dx=d[0],dy=d[1];
+ while(true){
+ x2=x+dx; y2=y+dy;
+ if(x2<0||x2>=5||y2<0||y2>=5||bd[5*y2+x2]!=0)return 5*y+x;
+ x=x2; y=y2;
+ }
+}
+
+function applyMove(bd,mv){
+ var neuidx=-1,newidx,i;
+ bd=bd.slice();
+ for(i=0;i<25;i++)if(bd[i]==3){neuidx=i;break;}
+ assert(neuidx!=-1);
+ if(mv[0]!=-1){
+ newidx=idxAfterSlide(bd,neuidx,mv[0]);
+ bd[newidx]=3;
+ bd[neuidx]=0;
+ }
+ if(mv[1]!=-1&&mv[2]!=-1){
+ newidx=idxAfterSlide(bd,mv[1],mv[2]);
+ bd[newidx]=bd[mv[1]];
+ bd[mv[1]]=0;
+ }
+ return bd;
+}
+
+function moveIsLegal(bd,mv,forpl){
+ var neuidx=-1,newidx,i;
+ if(bd[mv[1]]!=forpl)return false;
+ for(i=0;i<25;i++)if(bd[i]==3){neuidx=i;break;}
+ assert(neuidx!=-1);
+ if(mv[0]!=-1){
+ newidx=idxAfterSlide(bd,neuidx,mv[0]);
+ if(newidx==neuidx)return false;
+ }
+ newidx=idxAfterSlide(applyMove(bd,[mv[0],-1,-1]),mv[1],mv[2]);
+ if(newidx==mv[1])return false;
+ return true;
+}
+
+function handleMessage(conn,msg){
+ var cmd=msg.split(" "),list,i,game,c2,mv;
+ switch(cmd[0]){
+ case "nick":
+ conn.ws.send("nick \""+conn.nick+"\"");
+ break;
+ /*case "list_free_users":
+ list=[];
+ for(i=0;i<conns.length;i++)if(conns[i].gameid==null)list.push(conns[i].nick);
+ conn.ws.send("list_free_users "+JSON.stringify(list));
+ break;*/
+ case "game_create":
+ c2=cmd.slice(1).join(" ");
+ if(conn.gameid!=null){
+ conn.ws.send("game_create [false,\"Already in a game\"]");
+ break;
+ }
+ if(c2==conn.nick){
+ conn.ws.send("game_create [false,\"Cannot create a game with yourself\"]");
+ break;
+ }
+ c2=findby(conns,"nick",c2);
+ if(c2==-1){
+ conn.ws.send("game_create [false,\"Non-existent nick\"]");
+ break;
+ }
+ c2=conns[c2];
+ if(c2.gameid!=null){
+ conn.ws.send("game_create [false,\"Other already in a game\"]");
+ break;
+ }
+ console.log("new game between "+conn.nick+" and "+c2.nick);
+ game=new Game(conn,c2);
+ conn.gameid=game.id;
+ c2.gameid=game.id;
+ games.push(game);
+ conn.ws.send("game_create true");
+ c2.ws.send("joined_game true");
+ conn.ws.send("your_turn false");
+ break;
+ case "game_info":
+ if(conn.gameid==null){
+ conn.ws.send("game_info null");
+ break;
+ }
+ game=games[findby(games,"id",conn.gameid)];
+ conn.ws.send("game_info "+JSON.stringify([
+ [
+ conns[findby(conns,"id",game.i1)].nick,
+ conns[findby(conns,"id",game.i2)].nick
+ ],
+ game.board,
+ game.onturn
+ ]));
+ break;
+ case "game_doturn": //neudir stoneidx stonedir
+ if(conn.gameid==null){
+ conn.ws.send("game_doturn [false,\"Not in a game\"]");
+ break;
+ }
+ game=games[findby(games,"id",conn.gameid)];
+ mv=[+cmd[1],+cmd[2],+cmd[3]];
+ if(mv[0]==undefined||isNaN(mv[0])||mv[0]%1!=0||
+ (game.firstmove ? mv[0]!=-1 : mv[0]<0||mv[0]>=8) ||
+ mv[1]==undefined||isNaN(mv[1])||mv[1]%1!=0||mv[1]<0||mv[1]>=25||
+ mv[2]==undefined||isNaN(mv[2])||mv[2]%1!=0||mv[2]<0||mv[2]>=8){
+ conn.ws.send("game_doturn [false,\"Invalid move\"]");
+ break;
+ }
+ if(!moveIsLegal(game.board,mv,game.onturn)){
+ conn.ws.send("game_doturn [false,\"Illegal move\"]");
+ break;
+ }
+ game.board=applyMove(game.board,mv);
+ console.log("new board:");
+ console.log(game.board);
+ game.onturn=3-game.onturn;
+ conn.ws.send("game_doturn "+JSON.stringify([true,mv]));
+ c2=conns[findby(conns,"id",conn.id==game.i1?game.i2:game.i1)];
+ c2.ws.send("your_turn "+JSON.stringify(mv));
+ game.firstmove=false;
+ break;
+ default:
+ conn.ws.send("error "+cmd[0]);
+ break;
+ }
+}
+
+new WebSocketServer({port:WSPORT}).on("connection",function(ws){
+ var conn=new Connection(ws);
+ conns.push(conn);
+ ws.on("message",function(msg){
+ console.log(msg,util.inspect(conn,{depth:0}));
+ handleMessage(conn,msg);
+ });
+ ws.on("open",function(){
+ //hi!
+ });
+ ws.on("close",function(){
+ if(conn.gameid!=null){
+ var gameidx=findby(games,"id",conn.gameid);
+ var game=games[gameidx];
+ var otherplayeridx=findby(conns,"id",game.i1==conn.id?game.i2:game.i1)
+ console.log("otherplayeridx="+otherplayeridx);
+ conns[otherplayeridx].gameid=null;
+ conn.gameid=null;
+ games.splice(gameidx,1);
+ }
+ });
+});
+console.log("Websocket server started on port "+WSPORT+".");
+
+http.createServer(function(req,res){
+ if(req.url=="/"){
+ res.writeHead(200,{"Content-Type":"text/html"});
+ res.end(String(fs.readFileSync("gameserver.html")));
+ } else {
+ res.writeHead(404,{"Content-Type":"text/plain"});
+ res.end("404 Not found");
+ }
+}).listen(HTTPPORT);
+console.log("HTTP server started on port "+HTTPPORT+".");
diff --git a/gameserver/package.json b/gameserver/package.json
new file mode 100644
index 0000000..0ef588f
--- /dev/null
+++ b/gameserver/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "gameserver",
+ "version": "1.0.0",
+ "description": "",
+ "main": "gameserver.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Tom Smeding <hallo@tomsmeding.nl> (http://tomsmeding.com)",
+ "license": "ISC",
+ "dependencies": {
+ "naampje": "^1.0.1",
+ "ws": "^0.7.1"
+ }
+}