summaryrefslogtreecommitdiff
path: root/capturego_server.js
diff options
context:
space:
mode:
Diffstat (limited to 'capturego_server.js')
-rwxr-xr-xcapturego_server.js176
1 files changed, 176 insertions, 0 deletions
diff --git a/capturego_server.js b/capturego_server.js
new file mode 100755
index 0000000..b5d938e
--- /dev/null
+++ b/capturego_server.js
@@ -0,0 +1,176 @@
+#!/usr/bin/env node
+
+const naampje=require("naampje");
+
+const PORT=8080;
+
+// Game: {id: Int, size: Int, board: Board, onturn: Int, players: [Player]}
+// 'onturn' is index into 'players', or -1 if game is finished
+// Player: {id: Int, name: String, socket: SocketIO_Socket, games: [Game]}
+// Board: [(0,1,-1) * (size*size)]
+const games=new Map(); // game_id => Game
+const players=new Map(); // player_id => Player
+
+{let id=0; function uniqid(){return id++;}}
+
+function mkPlayer(socket){
+ const id=uniqid();
+ let name;
+ while(true){try {name=naampje.name();} catch(e){continue;} break;}
+ return {id, name, socket, games: []};
+}
+
+function mkGame(size,players){
+ const id=uniqid();
+ const board=new Array(size*size).fill(0);
+ return {id, size, board, onturn: 0, players};
+}
+
+function safePlayer(p){
+ return {id: p.id, name: p.name, games: p.games.map(g=>g.id)};
+}
+
+function safeGame(g){
+ return {id: g.id, size: g.size, board: g.board, onturn: g.onturn, players: g.players.map(safePlayer)};
+}
+
+function playerLeavesGame(player,game){
+ game.onturn=-1;
+ const newp=[];
+ for(const p of game.players){
+ if(p!=player){
+ p.socket.emit("game_leave",safeGame(game),safePlayer(player));
+ newp.push(p);
+ }
+ }
+ if(newp.length==0)games.delete(game.id);
+ else game.players=newp;
+}
+
+// Returns winning player (0 or 1) or -1 if none yet
+function gameBoardFinished(game){
+ const B=game.board,S=game.size;
+ for(let i=0;i<S*S;i++){
+ if(B[i]==0)continue;
+ const flags=new Array(S*S).fill(false);
+ const queue=[i];
+ let nb0=false;
+ while(queue.length){
+ const at=queue.shift(),x=at%S,y=~~(at/S);
+ flags[at]=true;
+ if(x>0&&!flags[at-1]){if(B[at-1]==B[i])queue.push(at-1); else if(B[at-1]==0){nb0=true; break;}}
+ if(y>0&&!flags[at-S]){if(B[at-S]==B[i])queue.push(at-S); else if(B[at-S]==0){nb0=true; break;}}
+ if(x<S-1&&!flags[at+1]){if(B[at+1]==B[i])queue.push(at+1); else if(B[at+1]==0){nb0=true; break;}}
+ if(y<S-1&&!flags[at+S]){if(B[at+S]==B[i])queue.push(at+S); else if(B[at+S]==0){nb0=true; break;}}
+ }
+ if(!nb0)return 1-B[i]==1?1:0;
+ }
+ return -1;
+}
+
+
+const app=require("express")();
+const httpServer=require("http").Server(app);
+const io=require("socket.io")(httpServer);
+httpServer.listen(PORT,()=>console.log(`Listening on port ${PORT}`));
+
+
+app.get("/",(req,res)=>{
+ res.sendFile(__dirname+"/index.html");
+});
+
+io.on("connection",(socket)=>{
+ const player=mkPlayer(socket);
+ console.log(`connect ${player.id}`);
+ players.set(player.id,player);
+ for(const p of players.values()){
+ if(p!=player)p.socket.emit("player_list_change");
+ }
+
+ socket.on("my_info",(cb)=>{
+ if(typeof cb!="function")return;
+ cb(safePlayer(player));
+ });
+
+ socket.on("player_info",(pi,cb)=>{
+ if(typeof cb!="function")return;
+ const p=players.get(pi);
+ if(!p)cb(null);
+ else cb(safePlayer(p));
+ });
+
+ socket.on("list_players",(cb)=>{
+ if(typeof cb!="function")return;
+ const l=[];
+ for(const p of players.values()){
+ l.push(safePlayer(p));
+ }
+ cb(l);
+ });
+
+ socket.on("disconnect",()=>{
+ console.log(`disconnect ${player.id}`);
+ for(const g of player.games)playerLeavesGame(player,g);
+ players.delete(player.id);
+ for(const p of players.values()){
+ p.socket.emit("player_list_change");
+ }
+ });
+
+ socket.on("create_game",(otherid,size)=>{
+ const otherp=players.get(+otherid);
+ if(!otherp){
+ socket.emit("err",`Player with id ${otherid} not found`);
+ return;
+ }
+ if(typeof size!="number"||isNaN(size)||size<5||size>99||size%1!=0)return;
+
+ const arr=Math.random()<0.5?[player,otherp]:[otherp,player];
+ const game=mkGame(size,arr);
+ player.games.push(game);
+ otherp.games.push(game);
+ games.set(game.id,game);
+
+ for(const p of game.players){
+ p.socket.emit("game_create",safeGame(game));
+ }
+ game.players[0].socket.emit("game_turn",safeGame(game));
+ game.players[1].socket.emit("game_other_turn",safeGame(game));
+ });
+
+ socket.on("leave_game",(gi)=>{
+ const g=games.get(gi);
+ if(!g)return;
+ if(player.games.indexOf(g)==-1)return;
+ playerLeavesGame(player,g);
+ });
+
+ socket.on("game_move",(gi,idx)=>{
+ const g=games.get(gi);
+ if(!g)return;
+ if(player.games.indexOf(g)==-1)return;
+ if(typeof idx!="number"||isNaN(idx)||idx<0||idx>=g.size*g.size||idx%1!=0){
+ socket.emit("err","Invalid position sent");
+ return;
+ }
+ if(g.players[g.onturn]!=player){
+ socket.emit("err","Not on turn");
+ return;
+ }
+ if(g.board[idx]!=0){
+ socket.emit("err","Position already taken");
+ return;
+ }
+ g.board[idx]=[1,-1][g.onturn];
+ const win=gameBoardFinished(g)
+ if(win!=-1){
+ for(const p of g.players){
+ p.socket.emit("game_win",safeGame(g),safePlayer(g.players[win]));
+ }
+ } else {
+ g.onturn=1-g.onturn;
+ g.players[g.onturn].socket.emit("game_turn",safeGame(g));
+ g.players[1-g.onturn].socket.emit("game_other_turn",safeGame(g));
+ }
+ });
+});