From 50af3d69af6d697e5e331fd4df0885b764575608 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Wed, 14 Mar 2018 22:57:51 +0100 Subject: Improvements, reorganisation and client --- .gitignore | 2 + client.js | 67 ++++++++++++++++++++++++++++++++ keyboard.js | 70 +++++++++++++++++++++++++++++++++ main.js | 126 ------------------------------------------------------------ protocol.js | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.js | 42 ++++++++++++++++++++ 6 files changed, 295 insertions(+), 126 deletions(-) create mode 100644 .gitignore create mode 100755 client.js create mode 100644 keyboard.js delete mode 100755 main.js create mode 100644 protocol.js create mode 100755 server.js 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 "); + 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/main.js deleted file mode 100755 index b19143a..0000000 --- a/main.js +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env node - -const net = require("net"); -const util = require("util"); - -if (process.argv.length != 4) { - console.log("Usage: ./main.js "); - console.log("Pass address of doomrooms server as arguments."); - process.exit(1); -} - -const GAME_ID = "tictactoe-tom"; -const GAME_NAME = "Tic Tac Toe"; - -{ - let id = 0; - function uniqid() { - return id++; - } -} - -function lineReader(conn, lineCb) { - let buffer = ""; - conn.on("data", (data) => { - buffer += data.toString(); - let idx = buffer.indexOf("\n"); - while (idx != -1) { - lineCb(buffer.slice(0, idx)); - buffer = buffer.slice(idx + 1); - idx = buffer.indexOf("\n"); - } - }); -} - -class Message { - constructor(/*...*/) { - if (typeof arguments[0] == "number") this.makeReply(...arguments); - else this.makeCommand(...arguments); - } - - makeCommand(method, ...args) { - this.isCommand = true; - this.method = method; - this.args = args; - this.id = uniqid(); - } - - makeReply(id, res, err) { - this.isCommand = false; - this.id = id; - this.res = res; - this.err = err; - } - - toJSON() { - if (this.isCommand) { - return JSON.stringify({ - method: this.method, - args: this.args, - id: this.id, - }); - } else { - return JSON.stringify({ - res: this.res, - err: this.err, - id: this.id, - }); - } - } -} - -class Connection { - constructor(conn, msgCb) { - this.conn = conn; - lineReader(conn, (line) => { - let obj; - try { - obj = JSON.parse(line); - } catch (e) { - console.log("Invalid JSON received: " + line); - return; - } - if (obj.res || obj.err) { - const msg = new Message(obj.id, obj.res, obj.err); - if (this.replyHandlers[msg.id]) { - this.replyHandlers[msg.id](msg); - this.replyHandlers[msg.id] = null; - } else { - console.log("Reply for unexpected message id " + msg.id); - } - } else { - msgCb(new Message(obj.method, ...obj.args)); - } - }); - - this.replyHandlers = {}; - } - - send(msg, replyCb) { - const data = msg.toJSON(); - console.log("Writing to conn: " + data); - this.conn.write(data + "\n"); - if (replyCb && msg.isCommand) { - this.replyHandlers[msg.id] = replyCb; - } - } -} - -function messageHandler(msg) { - console.log("Message: " + util.inspect(msg)); -} - -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)); -}); diff --git a/protocol.js b/protocol.js new file mode 100644 index 0000000..a1be542 --- /dev/null +++ b/protocol.js @@ -0,0 +1,114 @@ +const util = require("util"); + +module.exports = {}; + +module.exports.debug = true; + +{ + let id = 0; + function uniqid() { + return id++; + } +} +module.exports.uniqid = uniqid; + +function lineReader(conn, lineCb) { + let buffer = ""; + conn.on("data", (data) => { + buffer += data.toString(); + let idx = buffer.indexOf("\n"); + while (idx != -1) { + lineCb(buffer.slice(0, idx)); + buffer = buffer.slice(idx + 1); + idx = buffer.indexOf("\n"); + } + }); +} +module.exports.lineReader = lineReader; + +class Message { + constructor(/*...*/) { + if (typeof arguments[0] == "number") this.makeReply(...arguments); + else this.makeCommand(...arguments); + } + + makeCommand(method, ...args) { + this.isCommand = true; + this.method = method; + this.args = args; + this.id = uniqid(); + } + + makeReply(id, res, err) { + this.isCommand = false; + this.id = id; + this.res = res; + this.err = err; + } + + toJSON() { + if (this.isCommand) { + return JSON.stringify({ + method: this.method, + args: this.args, + id: this.id, + }); + } else { + return JSON.stringify({ + res: this.res, + err: this.err, + id: this.id, + }); + } + } +} +module.exports.Message = Message; + +class Connection { + constructor(conn, msgCb) { + this.conn = conn; + lineReader(conn, (line) => { + let obj; + try { + obj = JSON.parse(line); + } catch (e) { + if (module.exports.debug) console.log("Invalid JSON received: " + line); + return; + } + if (obj.res || obj.err) { + const msg = new Message(obj.id, obj.res, obj.err); + if (this.replyHandlers[msg.id]) { + this.replyHandlers[msg.id](msg); + this.replyHandlers[msg.id] = null; + } else { + if (module.exports.debug) console.log("Reply for unexpected message id " + msg.id); + } + } else { + msgCb(new Message(obj.method, ...obj.args)); + } + }); + + 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) { + const data = msg.toJSON(); + if (module.exports.debug) console.log("Writing to conn: " + data); + this.conn.write(data + "\n"); + return new Promise((resolve, reject) => { + this.replyHandlers[msg.id] = resolve; + }); + } + + end() { + clearInterval(this.pingInterval); + this.conn.end(); + } +} +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 "); + 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); +}); -- cgit v1.2.3