diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rwxr-xr-x | client.js | 67 | ||||
-rw-r--r-- | keyboard.js | 70 | ||||
-rw-r--r--[-rwxr-xr-x] | protocol.js (renamed from main.js) | 58 | ||||
-rwxr-xr-x | server.js | 42 |
5 files changed, 204 insertions, 35 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef5f69b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +node_modules/ diff --git a/client.js b/client.js new file mode 100755 index 0000000..9493789 --- /dev/null +++ b/client.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +const net = require("net"); +const util = require("util"); +const protocol = require("./protocol.js"); +const keyboard = require("./keyboard.js"); + +if (process.argv.length != 4) { + console.log("Usage: ./client.js <hostname> <port>"); + console.log("Pass address of doomrooms server as arguments."); + process.exit(1); +} + +const GAME_ID = "tictactoe-tom"; + +keyboard.init(); + +function fatalError(err) { + console.log("A fatal error occurred:"); + console.log(" ", err); + process.exit(1); +} + +function showMenu(title, options) { + return new Promise(async (resolve, reject) => { + console.log(title); + for (let i = 0; i < options.length; i++) { + console.log(" " + (i + 1) + ") " + options[i]); + } + while (true) { + const line = await keyboard.prompt("> "); + const num = parseInt(line.trim(), 10); + if (num >= 1 && num <= options.length) { + resolve(num - 1); + break; + } + console.log( + "That's an invalid choice. " + + "Please enter a number between 1 and " + options.length + "."); + } + }); +} + +function messageHandler(msg) { + console.log("Message: " + util.inspect(msg)); +} + +const conn = new protocol.Connection(net.createConnection(+process.argv[3], process.argv[2]), messageHandler); +conn.conn.on("connect", async () => { + console.log("Connected to Doomrooms."); + let choice = await showMenu("Register or log in?", ["Register new account", "Log in to existing account"]) + const username = await keyboard.prompt("Username: "); + const password = await keyboard.prompt("Password: "); + let msg; + if (choice == 0) { + msg = await conn.send(new protocol.Message("make-player", username, password)); + } else { + msg = await conn.send(new protocol.Message("login", username, password)); + } + if (msg.err) fatalError(msg.err); + console.log(msg); + + msg = await conn.send(new protocol.Message("set-game", GAME_ID)); + if (msg.err) fatalError(msg.err); + + setTimeout(() => {process.exit(0);}, 2000); +}); diff --git a/keyboard.js b/keyboard.js new file mode 100644 index 0000000..e1101d8 --- /dev/null +++ b/keyboard.js @@ -0,0 +1,70 @@ +module.exports = {}; + +let inputBuffer = ""; +let initialised = false, haveEOF = false; +let waitQueue = []; + +function stdinDataListener(data) { + data = data.toString(); + inputBuffer += data; + + if (waitQueue.length == 0) return; + + const idx = inputBuffer.indexOf("\n"); + if (idx == -1) return; + + waitQueue.shift()[0](inputBuffer.slice(0, idx)); + inputBuffer = inputBuffer.slice(idx + 1); +} + +function stdinEndListener() { + haveEOF = true; + for (const [resolve, reject] of waitQueue) { + reject("End-of-file reached"); + } + waitQueue = []; +} + +function init() { + process.stdin.on("data", stdinDataListener); + process.stdin.on("end", stdinEndListener); + initialised = true; +} +module.exports.init = init; + +function end() { + process.stdin.removeListener("data", stdinDataListener); + process.stdin.removeListener("end", stdinEndListener); + process.stdin.end(); +} +module.exports.end = end; + +function getline() { + return new Promise((resolve, reject) => { + if (haveEOF) { + reject("End-of-file reached"); + return; + } + + const idx = inputBuffer.indexOf("\n"); + if (idx != -1) { + const line = inputBuffer.slice(0, idx); + inputBuffer = inputBuffer.slice(idx + 1); + resolve(line); + } else { + waitQueue.push([resolve, reject]); + } + }); +} +module.exports.getline = getline; + +async function prompt(str) { + process.stdout.write(str); + return await getline(); +} +module.exports.prompt = prompt; + +function eof() { + return haveEOF; +} +module.exports.eof = eof; diff --git a/main.js b/protocol.js index b19143a..a1be542 100755..100644 --- a/main.js +++ b/protocol.js @@ -1,16 +1,8 @@ -#!/usr/bin/env node - -const net = require("net"); const util = require("util"); -if (process.argv.length != 4) { - console.log("Usage: ./main.js <hostname> <port>"); - console.log("Pass address of doomrooms server as arguments."); - process.exit(1); -} +module.exports = {}; -const GAME_ID = "tictactoe-tom"; -const GAME_NAME = "Tic Tac Toe"; +module.exports.debug = true; { let id = 0; @@ -18,6 +10,7 @@ const GAME_NAME = "Tic Tac Toe"; return id++; } } +module.exports.uniqid = uniqid; function lineReader(conn, lineCb) { let buffer = ""; @@ -31,6 +24,7 @@ function lineReader(conn, lineCb) { } }); } +module.exports.lineReader = lineReader; class Message { constructor(/*...*/) { @@ -68,6 +62,7 @@ class Message { } } } +module.exports.Message = Message; class Connection { constructor(conn, msgCb) { @@ -77,7 +72,7 @@ class Connection { try { obj = JSON.parse(line); } catch (e) { - console.log("Invalid JSON received: " + line); + if (module.exports.debug) console.log("Invalid JSON received: " + line); return; } if (obj.res || obj.err) { @@ -86,7 +81,7 @@ class Connection { this.replyHandlers[msg.id](msg); this.replyHandlers[msg.id] = null; } else { - console.log("Reply for unexpected message id " + msg.id); + if (module.exports.debug) console.log("Reply for unexpected message id " + msg.id); } } else { msgCb(new Message(obj.method, ...obj.args)); @@ -94,33 +89,26 @@ class Connection { }); this.replyHandlers = {}; + + // Send a ping once in a while + this.pingInterval = setInterval(async () => { + const msg = await this.send(new Message("ping")); + if (module.exports.debug) console.log("Ping reply: " + util.inspect(msg)); + }, 30000); } - send(msg, replyCb) { + send(msg) { const data = msg.toJSON(); - console.log("Writing to conn: " + data); + if (module.exports.debug) console.log("Writing to conn: " + data); this.conn.write(data + "\n"); - if (replyCb && msg.isCommand) { - this.replyHandlers[msg.id] = replyCb; - } + return new Promise((resolve, reject) => { + this.replyHandlers[msg.id] = resolve; + }); } -} -function messageHandler(msg) { - console.log("Message: " + util.inspect(msg)); + end() { + clearInterval(this.pingInterval); + this.conn.end(); + } } - -const conn = new Connection(net.createConnection(+process.argv[3], process.argv[2]), messageHandler); -conn.conn.on("connect", () => { - console.log("Connected to roomserver"); - - conn.conn.on("end", () => { - console.log("Connection with roomserver unexpectedly closed!"); - }); - - setInterval(() => { - conn.send(new Message("ping"), (msg) => console.log("Ping reply: " + util.inspect(msg))); - }, 30000); - - conn.send(new Message("make-game", GAME_ID, GAME_NAME), (msg) => console.log(msg)); -}); +module.exports.Connection = Connection; diff --git a/server.js b/server.js new file mode 100755 index 0000000..996bb92 --- /dev/null +++ b/server.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +const net = require("net"); +const util = require("util"); +const protocol = require("./protocol.js"); + +if (process.argv.length != 4) { + console.log("Usage: ./server.js <hostname> <port>"); + console.log("Pass address of doomrooms server as arguments."); + process.exit(1); +} + +const GAME_ID = "tictactoe-tom"; +const GAME_NAME = "Tic Tac Toe"; + +function messageHandler(msg) { + console.log("Message: " + util.inspect(msg)); +} + +async function registerGame(conn) { + let msg = await conn.send(new protocol.Message("make-game", GAME_ID, GAME_NAME)); + if (msg.err) { + msg = await conn.send(new protocol.Message("attach-game", GAME_ID, false)); + if (msg.err) { + console.log("Game already managed by another gameserver, and not forcing"); + process.exit(1); + } + } + console.log(msg); +} + +const conn = new protocol.Connection(net.createConnection(+process.argv[3], process.argv[2]), messageHandler); +conn.conn.on("connect", () => { + console.log("Connected to roomserver"); + + conn.conn.on("end", () => { + console.log("Connection with roomserver unexpectedly closed!"); + process.exit(1); + }); + + registerGame(conn); +}); |