diff options
-rwxr-xr-x | client.js | 210 | ||||
-rw-r--r-- | keyboard.js | 31 | ||||
-rw-r--r-- | package-lock.json | 21 | ||||
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | protocol.js | 9 | ||||
-rwxr-xr-x | server.js | 3 |
6 files changed, 240 insertions, 39 deletions
@@ -21,47 +21,193 @@ function fatalError(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 playerToString(player) { + return player.nick; +} + +function roomToString(room) { + let res = ` '${room.name}' (${room.id})`; + if (room.hidden) res += " (HIDDEN)"; + res += "\n"; + res += " Players:"; + for (const player of room.players) { + if (player.nick == room.admin) res += "\x1B[4m"; + res += " " + playerToString(player); + if (player.nick == room.admin) res += "\x1B[0m"; + } + return res; +} + +async function showMenu(title, options) { + 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) { + return num - 1; + break; } - }); + console.log( + "That's an invalid choice. " + + "Please enter a number between 1 and " + options.length + "."); + } } function messageHandler(msg) { + console.log("ATTENTION: UNHANDLED INCOMING MESSAGE"); console.log("Message: " + util.inspect(msg)); } + +let currentRoom = null; +let currentGame = null; + + +// Returns: 0 for re-loop, 1 for quit, 2 for roomMenu +async function mainMenu() { + const title = "Main Menu"; + const options = [ + "Create a room", + "Join an existing room", + "Search for rooms", + ]; + + switch (await showMenu(title, options)) { + case 0: { + const roomName = await keyboard.prompt("Room name to make: "); + const shown = await showMenu("Should the room be shown in searches?", ["Yes", "No"]) == 0; + const reply = await conn.send(new protocol.Message("make-room", roomName, !shown, {})); + if (reply.err) { + console.log("Error: " + reply.err); + } else { + currentRoom = reply.res; + return 2; + } + break; + } + + case 1: { + const roomName = await keyboard.prompt("Room id to join: "); + const reply = await conn.send(new protocol.Message("join-room", roomName)); + if (reply.err) { + console.log("Error: " + reply.err); + } else { + currentRoom = reply.res; + return 2; + } + break; + } + + case 2: { + const query = await keyboard.prompt("String to search for: "); + const reply = await conn.send(new protocol.Message("search-rooms", query)); + if (reply.err) { + console.log("Error: " + reply.err); + } else { + console.log(reply.res.length + " results:"); + for (const room of reply.res) { + console.log(roomToString(room)); + } + } + break; + } + } + return 0; +} + +// Returns: 0 for re-loop, 1 for quit, 2 for mainMenu, 3 for gameMenu +async function roomMenu() { + const title = + "Room " + currentRoom.name + + " (" + currentRoom.players.map(p => p.nick).join(", ") + ")"; + const options = [ + "Start a game", + "Send room chat message", + "Invite a player into this room", + "Kick a player from the room", + "Leave the room", + ]; + + switch (await showMenu(title, options)) { + case 0: { + break; + } + + case 1: { + const line = await keyboard.prompt("Message to send: "); + const reply = await conn.send(new protocol.Message("send-room-chat", line)); + if (reply.err) fatalError(reply.err); + break; + } + + case 2: { + break; + } + + case 3: { + const name = await keyboard.prompt("Player name: "); + const reply = await conn.send(new protocol.Message("kick-player", name)); + if (reply.err) { + console.log(err); + } else { + console.log("Player successfully kicked."); + } + break; + } + + case 4: { + const reply = await conn.send(new protocol.Message("leave-room")); + if (reply.err) fatalError(reply.err); + return 2; + } + } +} + 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); + try { + console.log("Connected to Doomrooms."); + let choice = await showMenu("Register or log in?", [ + "Register new account", + "Log in to existing account", + "Quit", + ]); + if (choice == 2) process.exit(0); - msg = await conn.send(new protocol.Message("set-game", GAME_ID)); - if (msg.err) fatalError(msg.err); + const username = await keyboard.prompt("Username: "); + const password = await keyboard.promptPassword("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); + + msg = await conn.send(new protocol.Message("set-game", GAME_ID)); + if (msg.err) fatalError(msg.err); - setTimeout(() => {process.exit(0);}, 2000); + let inGame = false; + let doQuit = false; + while (!doQuit) { + if (inGame) { + switch (await roomMenu()) { + case 0: break; + case 1: doQuit = true; break; + case 2: inGame = false; break; + } + } else { + switch (await mainMenu()) { + case 0: break; + case 1: doQuit = true; break; + case 2: inGame = true; break; + } + } + } + } catch(e) { + console.log(e); + } }); diff --git a/keyboard.js b/keyboard.js index e1101d8..1cd14dd 100644 --- a/keyboard.js +++ b/keyboard.js @@ -1,3 +1,5 @@ +const termios = require("node-termios"); + module.exports = {}; let inputBuffer = ""; @@ -58,12 +60,41 @@ function getline() { } module.exports.getline = getline; +function getpassword() { + return new Promise(async (resolve, reject) => { + const origtty = new termios.Termios(0); + + const tty = new termios.Termios(0); + tty.c_lflag &= ~(termios.native.ALL_SYMBOLS.ECHO | termios.native.ALL_SYMBOLS.ECHONL); + tty.writeTo(0); + + let handler; + + try { + const line = await getline(); + handler = () => resolve(line); + } catch (e) { + handler = () => reject(e); + } + console.log(); // ~ECHO eats newline + origtty.writeTo(0); + handler(); + }); +} +module.exports.getpassword = getpassword; + async function prompt(str) { process.stdout.write(str); return await getline(); } module.exports.prompt = prompt; +async function promptPassword(str) { + process.stdout.write(str); + return await getpassword(); +} +module.exports.promptPassword = promptPassword; + function eof() { return haveEOF; } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fb2a937 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "doomrooms-ttt", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "nan": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", + "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==" + }, + "node-termios": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/node-termios/-/node-termios-0.0.12.tgz", + "integrity": "sha1-CU93zsG+tDnTt8JmEtp7dUpSdAU=", + "requires": { + "nan": "2.9.2" + } + } + } +} diff --git a/package.json b/package.json index c13ac27..faf3b08 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", - "license": "MIT" + "license": "MIT", + "dependencies": { + "node-termios": "0.0.12" + } } diff --git a/protocol.js b/protocol.js index a1be542..22baf03 100644 --- a/protocol.js +++ b/protocol.js @@ -68,6 +68,7 @@ class Connection { constructor(conn, msgCb) { this.conn = conn; lineReader(conn, (line) => { + console.log("Received line '" + line + "'"); let obj; try { obj = JSON.parse(line); @@ -75,7 +76,9 @@ class Connection { if (module.exports.debug) console.log("Invalid JSON received: " + line); return; } - if (obj.res || obj.err) { + if (obj.method) { + msgCb(new Message(obj.method, ...obj.args)); + } else { const msg = new Message(obj.id, obj.res, obj.err); if (this.replyHandlers[msg.id]) { this.replyHandlers[msg.id](msg); @@ -83,8 +86,6 @@ class Connection { } else { if (module.exports.debug) console.log("Reply for unexpected message id " + msg.id); } - } else { - msgCb(new Message(obj.method, ...obj.args)); } }); @@ -94,7 +95,7 @@ class Connection { this.pingInterval = setInterval(async () => { const msg = await this.send(new Message("ping")); if (module.exports.debug) console.log("Ping reply: " + util.inspect(msg)); - }, 30000); + }, 600000); } send(msg) { @@ -14,7 +14,7 @@ const GAME_ID = "tictactoe-tom"; const GAME_NAME = "Tic Tac Toe"; function messageHandler(msg) { - console.log("Message: " + util.inspect(msg)); + console.log("Message:", msg); } async function registerGame(conn) { @@ -26,7 +26,6 @@ async function registerGame(conn) { process.exit(1); } } - console.log(msg); } const conn = new protocol.Connection(net.createConnection(+process.argv[3], process.argv[2]), messageHandler); |