{"id":72342,"date":"2019-10-31T04:20:04","date_gmt":"2019-10-31T11:20:04","guid":{"rendered":"http:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/"},"modified":"2019-10-31T04:20:04","modified_gmt":"2019-10-31T11:20:04","slug":"writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3","status":"publish","type":"post","link":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/","title":{"rendered":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)"},"content":{"rendered":"<link rel=\"canonical\" href=\"https:\/\/www.smashingmagazine.com\/2019\/10\/multiplayer-text-adventure-engine-node-js-part-3\/\"><title>Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)<\/title><\/p>\n<article>\n<header>\n<h1>Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)<\/h1>\n<address>Fernando Doglio<\/address>\n<p>                  <time datetime=\"2019-10-30T13:00:59+02:00\">2019-10-30T13:00:59+02:00<\/time><time datetime=\"2019-10-31T11:05:57+00:00\">2019-10-31T11:05:57+00:00<\/time><\/header>\n<p>I first showed you how to <a href=\"https:\/\/www.smashingmagazine.com\/2018\/12\/multiplayer-text-adventure-engine-node-js\/\">define a project such as this one<\/a>, and gave you the basics of the architecture as well as the mechanics behind the game engine. Then, I showed you the <a href=\"https:\/\/www.smashingmagazine.com\/2019\/10\/multiplayer-text-adventure-engine-node-js-part-2\/\">basic implementation of the engine<\/a> \u2014 a basic REST API that allows you to traverse a JSON-defined world.<\/p>\n<p>Today, I\u2019m going to be showing you how to create an old-school text client for our API by using nothing other than Node.js.<\/p>\n<h3>Reviewing The Original Design<\/h3>\n<p>When I first proposed <a href=\"https:\/\/www.smashingmagazine.com\/2018\/12\/multiplayer-text-adventure-engine-node-js\/\">a basic wireframe for the UI<\/a>, I proposed four sections on the screen:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/ui-wireframe.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/ui-wireframe.png?w=900\" sizes=\"100vw\" alt=\"\"><\/a><figcaption>\n      (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3c479bc4-ee49-4faf-8fe3-2e0c7a1fe1ed\/ui-wireframe.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Although in theory that looks right, I missed the fact that switching between sending game commands and text messages would be a pain, so instead of having our players switching manually, we\u2019ll have our command parser make sure it is capable of discerning whether we\u2019re trying to communicate with the game or our friends.<\/p>\n<p>So, instead of having four sections in our screen, we\u2019ll now have three:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/client-ss2.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/client-ss2.png?w=900\" sizes=\"100vw\" alt=\"\"><\/a><figcaption>\n      (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d7f64125-6121-4ed0-b278-1dcdb1342c7a\/client-ss2.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>That is an actual screenshot of the final game client. You can see the game screen on the left, and the chat on the right, with a single, common input box at the bottom. The module we\u2019re using allows us to customize colors and some basic effects. You\u2019ll be able to clone this code from Github and do what you want with the look and feel.<\/p>\n<p>One caveat though: Although the above screenshot shows the chat working as part of the application, we\u2019ll keep this article focused on setting up the project and defining a framework where we can create a dynamic text-UI based application. We\u2019ll focus on adding chat support on the next and final chapter of this series.<\/p>\n<div data-component=\"FeaturePanel\" data-audience=\"non-subscriber\" data-remove=\"true\"><\/div>\n<h3>The Tools We\u2019ll Need<\/h3>\n<p>Although there are many libraries out there that let us create CLI tools with Node.js, adding a text-based UI is a completely different beast to tame. Particularly, I was able to find only one (very complete, mind you) library that would let me do exactly what I wanted: <a href=\"https:\/\/www.npmjs.com\/package\/blessed\">Blessed<\/a>.<\/p>\n<p>This library is very powerful and provides a lot of features we won\u2019t be using for this project (such as casting shadows, drag&#038;drop, and others). It basically re-implements the entire <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ncurses\">ncurses<\/a> library (a C library which allows developers to create text-based UIs) which has no Node.js bindings, and it does so directly in JavaScript; so, if we had to, we could very well check out its internal code (something I wouldn\u2019t recommend unless you absolutely had to).<\/p>\n<p>Although the documentation for Blessed is quite extensive, it mainly consists of individual details about each method provided (as opposed to having tutorials explaining how to actually use those methods together) and it is lacking examples everywhere, so it might be difficult to dig into it if you have to understand how a particular method works. With that being said, once you understand it for one, everything works the same way, which is a big plus since not every library or even language (I\u2019m looking at you, PHP) has a consistent syntax.<\/p>\n<p>But documentation aside; the big plus for this library is that it works based on JSON options. For example, if you wanted to draw a box on the top right corner of the screen, you would do something like this:<\/p>\n<pre><code>var box = blessed.box({\n  top: \u20180',\n  right: '0',\n  width: '50%',\n  height: '50%',\n  content: 'Hello {bold}world{\/bold}!',\n  tags: true,\n  border: {\n    type: 'line'\n  },\n  style: {\n    fg: 'white',\n    bg: 'magenta',\n    border: {\n      fg: '#f0f0f0'\n    },\n    hover: {\n      bg: 'green'\n    }\n  }\n});<\/code><\/pre>\n<p>As you can imagine, other aspects of the box are defined there as well (such as it\u2019s size), which can perfectly be dynamic based on the terminal\u2019s size, type of border and colors \u2014 even for hover events. If you\u2019ve done front-end development at some point, you\u2019ll find a lot of overlap between the two.<\/p>\n<p>The point I\u2019m trying to make here is that everything regarding the representation of the box is configured through the JSON object passed to the <code>box<\/code> method. That, to me, is perfect because I can easily extract that content into a configuration file, and create a business logic capable of reading it and deciding which elements to draw on screen. Most importantly, it will help us get a glimpse of how they\u2019ll look once they\u2019ve been drawn.<\/p>\n<p>This will be the base for the entire UI aspect of this module (<em>more on that in a second!<\/em>).<\/p>\n<h3>Architecture Of The Module<\/h3>\n<p>The main architecture of this module relies entirely on the UI widgets which we\u2019ll be showing. A group of these widgets is considered a screen, and all these screens are defined in a single JSON file (which you can find inside the <code>\/config<\/code> folder).<\/p>\n<p>This file has over 250 lines, so showing it here makes no sense. You can look at the full file online, but a small snippet from it looks like this:<\/p>\n<div>\n<pre><code>\"screens\": {\n        \"main-options\": {\n            \"file\": \".\/main-options.js\",\n            \"elements\": {\n                \"username-request\": {\n                    \"type\": \"input-prompt\",\n                    \"params\": {\n                        \"position\": {\n                            \"top\": \"0%\",\n                            \"left\": \"0%\",\n                            \"width\": \"100%\",\n                            \"height\": \"25%\"\n                        },\n                        \"content\": \"Input your username: \",\n                        \"inputOnFocus\": true,\n                        \"border\": {\n                          \"type\": \"line\"\n                        },\n                        \"style\": {\n                          \"fg\": \"white\",\n                          \"bg\": \"blue\",\n                          \"border\": {\n                              \"fg\": \"#f0f0f0\"\n                          },\n                          \"hover\": {\n                            \"bg\": \"green\"\n                          }\n                        }\n                    }\n                },\n                \"options\": {\n                    \"type\": \"window\",\n                    \"params\": {\n                        \"position\": {\n                            \"top\": \"25%\",\n                            \"left\": \"0%\",\n                            \"width\": \"100%\",\n                            \"height\": \"50%\"\n                        },\n                        \"content\": \"Please select an option: n1. Join an existing game.n2. Create a new game\",\n                        \"border\": {\n                          \"type\": \"line\"\n                        },\n                        \"style\": {\n                        \/\/...\n                        }\n                    }\n                },\n                \"input\": {\n                    \"type\": \"input\",\n                    \"handlerPath\": \"..\/lib\/main-options-handler\",\n                   \/\/...\n                }\n            }\n        }\n<\/code><\/pre>\n<\/div>\n<p>The \u201cscreens\u201d element will contain the list of screens inside the application. Each screen contains a list of widgets (which I\u2019ll cover in a bit) and every widget has its blesses-specific definition and related handler files (when applicable).<\/p>\n<p>You can see how every \u201cparams\u201d element (inside a particular widget) represents the actual set of parameters expected by the methods we saw earlier. The rest of the keys defined there help provide context about what type of widgets to render and their behavior.<\/p>\n<p>A few points of interest:<\/p>\n<div><\/div>\n<h4>Screen Handlers<\/h4>\n<p>Every screen element has file property which references the code associated to that screen. This code is nothing but an object that must have an <code>init<\/code> method (the initialization logic for that particular screen takes place inside of it). Particularly, the main UI engine, will call that <code>init<\/code> method of every screen, which in turn, should be responsible for initializing whatever logic it may need (i.e setting up the input boxes events).<\/p>\n<p>The following is the code for the main screen, where the application requests the player to select an option to either start a brand new game or join an existing one:<\/p>\n<div>\n<pre><code>const logger = require(\"..\/utils\/logger\")\n\nmodule.exports = {\n    init: function(elements, UI) {\n        this.elements = elements\n        this.UI = UI\n        this.id = \"main-options\"\n        this.setInput()\n    },\n\n    moveToIDRequest: function(handler) {\n        return this.UI.loadScreen('id-requests', (err, ) => {\n            \n        })\n    },\n\n    createNewGame: function(handler) {\n        handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => {\n              this.UI.gamestate.gameID = gameData._id\n              handler.joinGame(this.UI.gamestate, (err) => {\n                return this.UI.loadScreen('main-ui', {\n                    flashmessage: \"You've joined game \" + this.UI.gamestate.gameID + \" successfully\"\n                },  (err, ) => {\n                    \n                })\n              })\n            \n          })\n    },\n\n    setInput: function() {\n        \n        let handler = require(this.elements[\"input\"].meta.handlerPath)\n        let input = this.elements[\"input\"].obj\n        let usernameRequest = this.elements['username-request'].obj\n        let usernameRequestMeta = this.elements['username-request'].meta\n        let question = usernameRequestMeta.params.content.trim()\n\n\n        usernameRequest.setValue(question)\n\n        this.UI.renderScreen()\n\n         let validOptions =  {\n             1: this.moveToIDRequest.bind(this),\n             2: this.createNewGame.bind(this)\n         }\n\n        usernameRequest.on('submit', (username) => {\n\n            logger.info(\"Username:\" +username)\n            logger.info(\"Playername: \" + username.replace(question, ''))\n            this.UI.gamestate.playername = username.replace(question, '')\n\n            input.focus()\n\n            input.on('submit', (data) => {\n                let command = input.getValue()\n                  if(!validOptions[+command]) {\n                      this.UI.setUpAlert(\"Invalid option: \" + command)\n                      return this.UI.renderScreen()\n                  }\n                  return validOptions[+command](handler)\n            })\n\n\n        })\n        return input\n    }\n}\n<\/code><\/pre>\n<\/div>\n<p>As you can see, the <code>init<\/code> method calls the <code>setupInput<\/code> method which basically configures the right callback to handle user input. That callback holds the logic to decide what to do based on the user\u2019s input (either 1 or 2).<\/p>\n<h4>Widget Handlers<\/h4>\n<p>Some of the widgets (usually input widgets) have a <code>handlerPath<\/code> property, which references the file containing the logic behind that particular component. This is not the same as the previous screen handler. These don\u2019t care about the UI components that much. Instead, they handle the glue logic between the UI and whatever library we\u2019re using to interact with external services (such as the game engine\u2019s API).<\/p>\n<h4>Widget Types<\/h4>\n<p>Another minor addition to the JSON definition of the widgets is their types. Instead of going with the names Blessed defined for them, I\u2019m creating new ones in order to give me more wiggle room when it comes to their behavior. After all, a window widget might not always \u201cjust display information\u201d, or an input box might not always work the same way.<\/p>\n<p>This was mostly a preemptive move, just to ensure I have that ability if I ever need it in the future, but as you\u2019re about to see, I\u2019m not using that many different types of components anyway.<\/p>\n<h4>Multiple Screens<\/h4>\n<p>Though the main screen is the one I showed you in the screenshot above, the game requires a few other screens in order to request things such as your player name or whether you\u2019re creating a brand new game session or even joining an existing one. The way I handled that was, again, through the definition of all these screens in the same JSON file. And to move from one screen into the next one, we use the logic inside the screen handler files.<\/p>\n<p>We can do this simply by using the following line of code:<\/p>\n<pre><code>this.UI.loadScreen('main-ui', (err ) => {\n if(err) this.UI.setUpAlert(err)    \n })<\/code><\/pre>\n<p>I\u2019ll show you more details about the UI property in a second, but I\u2019m just using that <code>loadScreen<\/code> method to re-render the screen and picking the right components off of the JSON file using the string passed as parameter. Very straightforward.<\/p>\n<div><\/div>\n<h3>Code Samples<\/h3>\n<p>It\u2019s now time to check out the meat and potatoes of this article: the code samples. I\u2019m just going to highlight what I think are the small gems inside it, but you can always take a look at the full source code directly in the <a href=\"https:\/\/github.com\/deleteman\/tardis-project-client\">repository<\/a> anytime.<\/p>\n<h4>Using Config Files To Auto Generate The UI<\/h4>\n<p>I\u2019ve already covered part of this, but I think it\u2019s worth exploring the details behind this generator. The gist behind it (file <em>index.js<\/em> inside the <code>\/ui<\/code> folder) is that it is a wrapper around the Blessed object. And the most interesting method inside it, is the <code>loadScreen<\/code> method.<\/p>\n<p>This method grabs the configuration (through the config module) for one specific screen and goes through its content, trying to generate the right widgets based on each element\u2019s type.<\/p>\n<div>\n<pre><code>loadScreen: function(sname, extras, done) {\n        if(typeof extras == \"function\") {\n            done = extras\n        }\n\n        let screen = config.get('screens.' + sname)\n        let screenElems = {}\n   \n        if(this.screenElements.length > 0) { \/\/remove previous screen\n            this.screenElements.map( e => e.detach())\n            this.screen.realloc()\n        }\n\n        Object.keys(screen.elements).forEach( eName => {\n            let elemObj = null\n            let element = screen.elements[eName]\n            if(element.type == 'window') {\n                elemObj = this.setUpWindow(element)\n            }\n            if(element.type == 'input') {\n                elemObj = this.setUpInputBox(element)\n            }\n\n            if(element.type == 'input-prompt') {\n                elemObj = this.setUpInputBox(element)\n            }\n            screenElems[eName] = {\n                meta: element,\n                obj: elemObj\n            }\n        })\n\n        if(typeof extras === 'object' && extras.flashmessage) {\n            this.setUpAlert(extras.flashmessage)    \n        }\n\n\n        this.renderScreen()\n        let logicPath = require(screen.file)\n        logicPath.init(screenElems, this)\n        done()\n    },\n<\/code><\/pre>\n<\/div>\n<p>As you can see, the code is a bit lengthy, but the logic behind it is simple:<\/p>\n<ol>\n<li>It loads the configuration for the current specific screen;<\/li>\n<li>Cleans up any previously existing widgets;<\/li>\n<li>Goes over every widget and instantiates it;<\/li>\n<li>If an extra alert was passed as a flash message (which is basically a concept I stole from Web Dev in which you setup a message to be shown on screen until the next refresh);<\/li>\n<li>Render the actual screen;<\/li>\n<li>And finally, require the screen handler and execute it\u2019s \u201cinit\u201d method.<\/li>\n<\/ol>\n<p>That\u2019s it! You can check out the rest of the methods \u2014 they\u2019re mostly related to individual widgets and how to render them.<\/p>\n<h4>Communication Between UI And Business Logic<\/h4>\n<p>Although in the grand scale, the UI, the back-end and the chat server all have a somewhat layered-based communication; the front end itself needs at least a two-layered internal architecture in which the pure UI elements interact with a set of functions that represent the core logic inside this particular project.<\/p>\n<p>The following diagram shows the internal architecture for the text client we\u2019re building:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/client-arch.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/client-arch.png?w=900\" sizes=\"100vw\" alt=\"\"><\/a><figcaption>\n      (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/94d82068-91bb-4ddd-b84c-15f01ec8deba\/client-arch.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Let me explain it a bit further. As I mentioned above, the <code>loadScreenMethod<\/code> will create UI presentations of the widgets (these are Blessed objects). But they are contained as part of the screen logic object which is where we set up the basic events (such as <code>onSubmit<\/code> for input boxes).<\/p>\n<p>Allow me to give you a practical example. Here is the first screen you see when starting the UI client:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/screen-ss.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/screen-ss.png?w=900\" sizes=\"100vw\" alt=\"\"><\/a><figcaption>\n      (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/4d58239a-a6d0-417b-9c76-4fe04ee567fe\/screen-ss.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>There are three sections on this screen:<\/p>\n<ol>\n<li>Username request,<\/li>\n<li>Menu options \/ information,<\/li>\n<li>Input screen for the menu options.<\/li>\n<\/ol>\n<p>Basically, what we want to do is to request the username and then ask them to pick one of the two options (either starting up a brand new game or joining an existing one).<\/p>\n<p>The code that takes care of that is the following:<\/p>\n<div>\n<pre><code>module.exports = {\n\n\n    init: function(elements, UI) {\n        this.elements = elements\n        this.UI = UI\n        this.id = \"main-options\"\n        this.setInput()\n    },\n\n    moveToIDRequest: function(handler) {\n        return this.UI.loadScreen('id-requests', (err, ) => {\n            \n        })\n    },\n\n    createNewGame: function(handler) {\n\n        handler.createNewGame(this.UI.gamestate.APIKEY, (err, gameData) => {\n              this.UI.gamestate.gameID = gameData._id\n              handler.joinGame(this.UI.gamestate, (err) => {\n                return this.UI.loadScreen('main-ui', {\n                    flashmessage: \"You've joined game \" + this.UI.gamestate.gameID + \" successfully\"\n                },  (err, ) => {\n                    \n                })\n              })\n            \n          })\n    },\n\n    setInput: function() {\n        \n        let handler = require(this.elements[\"input\"].meta.handlerPath)\n        let input = this.elements[\"input\"].obj\n        let usernameRequest = this.elements['username-request'].obj\n        let usernameRequestMeta = this.elements['username-request'].meta\n        let question = usernameRequestMeta.params.content.trim()\n\n\n        usernameRequest.setValue(question)\n\n        this.UI.renderScreen()\n\n         let validOptions =  {\n             1: this.moveToIDRequest.bind(this),\n             2: this.createNewGame.bind(this)\n         }\n\n        usernameRequest.on('submit', (username) => {\n\n            logger.info(\"Username:\" +username)\n            logger.info(\"Playername: \" + username.replace(question, ''))\n            this.UI.gamestate.playername = username.replace(question, '')\n\n            input.focus()\n\n\n\n            input.on('submit', (data) => {\n                let command = input.getValue()\n                  if(!validOptions[+command]) {\n                      this.UI.setUpAlert(\"Invalid option: \" + command)\n                      return this.UI.renderScreen()\n                  }\n                  return validOptions[+command](handler)\n            })\n\n\n        })\n\n        \n        \n\n        return input\n    }\n}\n<\/code><\/pre>\n<\/div>\n<p>I know that\u2019s a lot of code, but just focus on the <code>init<\/code> method. The last thing it does is to call the <code>setInput<\/code> method which takes care of adding the right events to the right input boxes.<\/p>\n<p>Therefore, with these lines:<\/p>\n<div>\n<pre><code>let handler = require(this.elements[\"input\"].meta.handlerPath)\nlet input = this.elements[\"input\"].obj\nlet usernameRequest = this.elements['username-request'].obj\nlet usernameRequestMeta = this.elements['username-request'].meta\nlet question = usernameRequestMeta.params.content.trim()\n<\/code><\/pre>\n<\/div>\n<p>We\u2019re accessing the Blessed objects and getting their references, so that we can later set up the <code>submit<\/code> events. So after we submit the username, we\u2019re switching focus to the second input box (literally with <code>input.focus() <\/code>).<\/p>\n<p>Depending on what option we choose from the menu, we\u2019re calling either of the methods:<\/p>\n<ul>\n<li><code>createNewGame<\/code>: creates a new game by interacting with its associated handler;<\/li>\n<li><code>moveToIDRequest<\/code>: renders the next screen in charge of requesting the game ID to join.<\/li>\n<\/ul>\n<h4>Communication With The Game Engine<\/h4>\n<p>Last but certainly not least (and following the above example), if you hit 2, you\u2019ll notice that the method <code>createNewGame<\/code> uses the handler\u2019s methods <code>createNewGame<\/code> and then <code>joinGame<\/code> (joining the game right after creating it).<\/p>\n<p>Both these methods are meant to simplify the interaction with the Game Engine\u2019s API. Here is the code for this screen\u2019s handler:<\/p>\n<div>\n<pre><code>const request = require(\"request\"),\n    config = require(\"config\"),\n    apiClient = require(\".\/apiClient\")\n\nlet API = config.get(\"api\")\nmodule.exports = {\n\n    joinGame: function(apikey, gameId, cb) {\n        apiClient.joinGame(apikey, gameId, cb)\n    },\n\n    createNewGame: function(apikey, cb) {\n        request.post(API.url + API.endpoints.games + \"?apikey=\" + apikey, { \/\/creating game\n            body: {\n                cartridgeid: config.get(\"app.game.cartdrigename\")\n            },\n            json: true\n        }, (err, resp, body) => {\n            cb(null, body)    \n        })\n        \n    }\n}\n<\/code><\/pre>\n<\/div>\n<p>There you see two different ways to handle this behavior. The first method actually uses the <code>apiClient<\/code> class, which again, wraps the interactions with the GameEngine into yet another layer of abstraction.<\/p>\n<p>The second method though performs the action directly by sending a POST request to the correct URL with the right payload. Nothing fancy is done afterwards; we\u2019re just sending the body of the response back to the UI logic.<\/p>\n<p><strong>Note<\/strong>: <em>If you\u2019re interested in the full version of the source code for this client, you can check it out <a href=\"https:\/\/github.com\/deleteman\/tardis-project-client\">here<\/a>.<\/em><\/p>\n<h3>Final Words<\/h3>\n<p>This is it for the text-based client for our text adventure. I covered:<\/p>\n<ul>\n<li>How to structure a client application;<\/li>\n<li>How I used Blessed as the core tech for creating the presentation layer;<\/li>\n<li>How to structure the interaction with the back-end services from a complex client;<\/li>\n<li>And hopefully, with the full repository available.<\/li>\n<\/ul>\n<p>And while the UI might not look exactly like the original version did, it does fulfill its purpose. Hopefully, this article gave you an idea of how to architect such an endeavor and you were inclined to try it for yourself in the future. Blessed is definitely a very powerful tool, but you\u2019ll have to have patience with it while learning how to use it and how to navigate through their docs.<\/p>\n<p>In the next and final part, I\u2019ll cover how I added the chat server both on the back-end as well as for this text client.<\/p>\n<p><em>See you on the next one!<\/em><\/p>\n<div>\n  <img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/logo-red-22.png?w=900\" alt=\"Smashing Editorial\"><span>(dm, yk, il)<\/span>\n<\/div>\n<\/article>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00 I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as&#8230;<a class=\"moretag\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/\"> Read the full article&#8230;<\/a><\/p>\n","protected":false},"author":4,"featured_media":72343,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[371],"tags":[],"class_list":["post-72342","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-user-experience"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.0.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Guest Contribution\"\/>\n\t<meta name=\"keywords\" content=\"user experience\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies\" \/>\n\t\t<meta property=\"og:description\" content=\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T11:20:04+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T11:20:04+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#article\",\"name\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies\",\"headline\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)\",\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/10\\\/ui-wireframe.png?fit=400%2C283&ssl=1\",\"width\":400,\"height\":283},\"datePublished\":\"2019-10-31T04:20:04-07:00\",\"dateModified\":\"2019-10-31T04:20:04-07:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#webpage\"},\"articleSection\":\"User Experience\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"position\":2,\"name\":\"User Experience\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#listItem\",\"name\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#listItem\",\"position\":3,\"name\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/\",\"name\":\"Guest Contribution\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Guest Contribution\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#webpage\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/\",\"name\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies\",\"description\":\"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/10\\\/ui-wireframe.png?fit=400%2C283&ssl=1\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#mainImage\",\"width\":400,\"height\":283},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/10\\\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\\\/#mainImage\"},\"datePublished\":\"2019-10-31T04:20:04-07:00\",\"dateModified\":\"2019-10-31T04:20:04-07:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies","description":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics","canonical_url":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/","robots":"max-image-preview:large","keywords":"user experience","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#article","name":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies","headline":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)","author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/ui-wireframe.png?fit=400%2C283&ssl=1","width":400,"height":283},"datePublished":"2019-10-31T04:20:04-07:00","dateModified":"2019-10-31T04:20:04-07:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#webpage"},"isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#webpage"},"articleSection":"User Experience"},{"@type":"BreadcrumbList","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","position":1,"name":"Home","item":"https:\/\/www.taterboy.com\/blog","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","position":2,"name":"User Experience","item":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#listItem","name":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#listItem","position":3,"name":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)","previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}}]},{"@type":"Organization","@id":"https:\/\/www.taterboy.com\/blog\/#organization","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","url":"https:\/\/www.taterboy.com\/blog\/"},{"@type":"Person","@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author","url":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/","name":"Guest Contribution","image":{"@type":"ImageObject","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g","width":96,"height":96,"caption":"Guest Contribution"}},{"@type":"WebPage","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#webpage","url":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/","name":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies","description":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#breadcrumblist"},"author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"creator":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/ui-wireframe.png?fit=400%2C283&ssl=1","@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#mainImage","width":400,"height":283},"primaryImageOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/#mainImage"},"datePublished":"2019-10-31T04:20:04-07:00","dateModified":"2019-10-31T04:20:04-07:00"},{"@type":"WebSite","@id":"https:\/\/www.taterboy.com\/blog\/#website","url":"https:\/\/www.taterboy.com\/blog\/","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","og:type":"article","og:title":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies","og:description":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics","og:url":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/","article:published_time":"2019-10-31T11:20:04+00:00","article:modified_time":"2019-10-31T11:20:04+00:00","twitter:card":"summary","twitter:title":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) | Design for Immersive Technologies","twitter:description":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3) Fernando Doglio 2019-10-30T13:00:59+02:002019-10-31T11:05:57+00:00I first showed you how to define a project such as this one, and gave you the basics of the architecture as well as the mechanics"},"aioseo_meta_data":{"post_id":"72342","title":null,"description":null,"keywords":null,"keyphrases":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_custom_image_width":null,"og_custom_image_height":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"location":null,"local_seo":null,"created":"2021-02-07 17:03:21","updated":"2026-09-02 09:37:07","focus_keyword":null,"additional_keywords":null,"truseo_locale":null,"primary_term":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"twitter_image_url":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"Article","isEnabled":true},"graphs":[]},"limit_modified_date":false,"ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/\" title=\"User Experience\">User Experience<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tWriting A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.taterboy.com\/blog"},{"label":"User Experience","link":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/"},{"label":"Writing A Multiplayer Text Adventure Engine In Node.js: Creating The Terminal Client (Part 3)","link":"https:\/\/www.taterboy.com\/blog\/2019\/10\/writing-a-multiplayer-text-adventure-engine-in-node-js-creating-the-terminal-client-part-3\/"}],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8wzr5-iOO","jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/10\/ui-wireframe.png?fit=400%2C283&ssl=1","_links":{"self":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/72342","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/comments?post=72342"}],"version-history":[{"count":0,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/72342\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media\/72343"}],"wp:attachment":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media?parent=72342"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/categories?post=72342"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/tags?post=72342"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}