Building a Text-Based Adventure in Vue.js

Building a Text-Based Adventure in Vue.js

This post is more than 2 years old.

Note - I found a bug with the room description that was fixed in a later build. Sorry about that! Happy Valentines Day! Today I'm showing my love for Vue.js by building something totally impractical and fun - a text-based adventure in Vue.js. As a child of 80s, I grew up playing text-based games from Infocom. In fact, to this day I still say that some of the most interesting games ever created were done by Infocom. My favorite? "A Mind Forever Voyaging"

Cover art

Heck, the first time I added RAM to a machine was just to support playing "Wishbringer", another Infocom classic. I graduated from these games into MUDs while at college and had fun not only playing them but coding them as well. (See this nearly five year old post about the code I'm most proud of.) I thought it might be fun to take a stab at building a simple text-based game in Vue.

Now - to be clear, a text parser is not a simple task. Infocom games were notorious for their complex parsers and their ability to take input and map it to a proper action in the game. I'm not going to pretend to have the coding chops to do that. I did think it would be interesting to try a few basic commands, like movement, and then see if I could build up from there. With that in mind, I'm happy to share my initial version.

Before I get into the code, note that you can find the complete code base here:

Alright, so what did I build? For my initial version, I decided that I would only support basic navigation among a dataset of rooms. So given that a room has exits to the west and east, I'd support the user typing commands to move in those directions. If you moved to the west, I'd let the user know about the new room and then they would be able to move in whatever directions that particular room supported. I began by designing my game data. Right now this is a JSON file, but in the next version, I'm going to support the abilty to use individual files for rooms and use a Node script to handle converting that data into JSON. That will let me write more freely and not worry about escaping crap that JSON complains about. Anyway, here is the current version:

		"description":"This is a rather boring room, but despite that, you feel the pull of a new adventure!",
		"description":"This is a rather dramatic room, almost presidential you would say.",
		"description":"You've entered Ray's office. You are surrounded by a mess of Star Wars toys and other distractions. No wonder Ray never seems to get anything done.",

Each room has a unique ID. This is used to allow one room to 'target' another for movement. And in theory, you could imagine items (magic potions?) that transport a user. Each room currently has 2 properties - a simple description and an array of exits. Now let's take a look at the front end.

<!DOCTYPE html>
		<meta charset="utf-8">
		<meta name="description" content="">
		<meta name="viewport" content="width=device-width">
		<link rel="stylesheet" href="app.css">

		<div id="app" v-cloak>
			<div v-if="loading">
				Please stand by - loading your adventure...
			</div><div v-else>
				<div id="roomDesc" v-if="room">

					<p v-if="room.exits.length > 1">
						You see exits to the {{ room.exits | exitDesc}}.
					</p><p v-else>
						You see an exit to the {{ room.exits | exitDesc }}.
				<div id="cli">
					<input v-model="input" @keydown.enter="cli" ref="input">


		<script src=""></script>
		<script src="app.js"></script>

The game's view layer is split into two states - one to use while stuff is loading (you could imagine the room data becoming quite large) and one to display the main "game" UI. Right now that supports two elements - a room description with exit data dynamically generated and then a "CLI" that's really just an input field. I applied all my CSS powers to generate this:

Game UI

Alright, now let's tackle the code. First, here is a filter I wrote to handle displaying exits. It simply handles converting "x,y" to "X and Y", or "x,y,z" to "X, Y, and Z". I could have done that in the view layer, but I also needed to support converting "n" to "North."

// mapping of short dir to long
const dirMapping = {

Vue.filter('exitDesc', function (exits) {
	let result = '';

	if(exits.length > 1) {
		for(let i=0;i<exits.length;i++) {
			result += dirMapping[exits[i].dir];
			if(i < exits.length-2) result += ', ';
			if(i == exits.length-2) result += ' and ';
	} else {
		result = dirMapping[exits[0].dir];
	return result;

By the way, dirMapping is external to the filter as it is used someplace else as well. Ok, now for the core logic.

const app = new Vue({
	data() {
		return {
	mounted() {
		console.log('Loading room data...');
		.then(res => res.json())
		.then(res => {
			this.rooms = res; = this.rooms[this.initialRoom];
			this.roomDesc =;
			this.loading = false;
			//nextTick required because line above changes the DOM
			this.$nextTick(() => {
	methods: {
		cli() {
			console.log('Running cli on '+this.input);

			// first see if valid input, for now, it must be a dir
			if(!this.validInput(this.input)) {
				alert('Sorry, but I don\'t recognize: '+this.input);
				this.input = '';
			// Ok, currently this is just handles moving, nothng else
			// so this is where I'd add a parser, imagine it is there
			// and after running, it determines our action is "movement"
			let action = 'movement';
			// arg would be the argument for the action, so like "go west", arg=west. 
			// for now, it's just the cli
			let arg = this.input;

			switch(action) {
				case 'movement':{

			this.input = '';
		doMovement(d) {
			console.log('Move '+d);
			// first, change North to n
			let mappedDir = '';
			for(let dir in dirMapping) {
				if(dir === d.toLowerCase()) mappedDir = d;
				if(dirMapping[dir].toLowerCase() === d.toLowerCase()) mappedDir = dir;
			// see if valid direction
			for(let i=0;i<;i++) {
				if([i].dir === mappedDir) { = this.rooms[[i].room];
			// if we get here, boo
			alert(dirMapping[d] + ' is not a valid direction!');
		validInput(i) {
			// v1 is stupid dumb
			let valid = ['w','e','s','n','west','east','south','north'];
			return valid.includes(i.toLowerCase());

Alright, so that's a bit of code there, let me break it down bit by bit. The data block handles storing things like my current position and other flags.

Next I use mounted to load the initial data. I previously had created but ran into an issue when I was trying to automatically focus the input field. First - refs can't be used in created, the DOM isn't rendered yet, and secondly, I had to use $nextTick() because this.loading = false; changes the DOM and actually makes that input visible. This one little part took me maybe twenty minutes, but I'm really glad I ran into it as I learned something new.

The cli method handles input and as the comments say, it is pretty simplistic at the moment. Right now it has no parser and just assumes everything is a movement. validInput is the beginning of the abstraction to handle verifying input, but obviously later I'll need some code to handle taking in input and mapping it to a proper action. As I said, this is just a beginning.

The only supported action now is movement, and you can see that in play in doMovement. First this converts your input to a shorthand value (ie, "north" to "n"), then verifies that it is valid for the room. If it is, I simply move you.

For errors I'm using alerts, but I really want to do something different. Like maybe have a div that is an active response to your input. It could handle both showing errors as well as responding to good commands ("You move west."), but I wasn't sure how to handle that visually. Anyone have an idea?

So that's it. I've got some notes about what I want to do next. If you want to "play", visit the demo here:

Header photo by Jeremy Bishop on Unsplash

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA

Archived Comments

Comment 1 by Robert Zehnder posted on 2/14/2018 at 8:22 PM

I was a "Hitchhikers Guide to the Galaxy" man myself. It will be interesting to see where this goes. ;)

Comment 2 (In reply to #1) by Raymond Camden posted on 2/14/2018 at 8:46 PM

I'm going to work on "lookables" next, so you can go into a room and "l at X" and see a description (if one exists).

Comment 3 by Raymond Camden posted on 2/14/2018 at 10:31 PM

Hey folks - there is a bug where the room description doesn't update. I'm fixing that in the next release.

Comment 4 (In reply to #1) by Gary Stenzel posted on 2/17/2018 at 5:20 PM

I have many hours logged on that one. LOL

Comment 5 by Gary Stenzel posted on 2/17/2018 at 5:29 PM

Re: Your request for ideas about responses...
I immediately saw in my mind a large black "Plus Sign" with arrows at the ends. North is up, south is down, etc. After the user types his command, a purple "wave" goes from the center of the plus sign to the arrow, maybe 3 times in the appropriate direction. I thought that would be pretty cool.
Then I remembered... "TEXT adventure". LOL
Well, "Oregon Train" was a text adventure with pictures. Haha

Comment 6 (In reply to #5) by Raymond Camden posted on 2/18/2018 at 2:11 PM

Us Old School Gamers don't need fancy UI things like that - or automaps. ;) I drew maps on graph paper for Bard Tale back in the day.

Comment 7 (In reply to #6) by Gary Stenzel posted on 2/18/2018 at 3:31 PM

I AM an old school gamer. Haha. Started on a Sinclair, then a VIC-20, Commodore 64, etc...

Comment 8 by Brook Monroe posted on 2/18/2018 at 5:47 PM

Node? I think you misspelled "Electron." :)

But seriously, there's so much potential here that I can't avoid thinking about all those text-based game plots I thought of in the early 90s. Hmm. Yeah.

Comment 9 (In reply to #8) by Raymond Camden posted on 2/18/2018 at 6:40 PM

I like Electron, but every time I think about using it, I realize I don't need it. I mean the only real benefit would be file system access and I wouldn't need it for this. The web has so much power now that things like Electron seem less useful now.

Comment 10 by Ken Courville posted on 2/19/2018 at 1:52 PM

Interesting start. You may find the piglet library helpful for the text parsing and mapping.

Comment 11 (In reply to #10) by Raymond Camden posted on 2/19/2018 at 3:09 PM

Pretty cool - but wrong language. :)

Comment 12 (In reply to #11) by Ken Courville posted on 2/19/2018 at 3:20 PM

Sorry.. guess I should see if things still exist before posting.

Comment 13 (In reply to #12) by Raymond Camden posted on 2/19/2018 at 3:21 PM

Oh it exists - it's just not JavaScript. :>

Comment 14 (In reply to #13) by Ken Courville posted on 2/19/2018 at 11:49 PM

Here’s what I was thinking about:

Comment 15 (In reply to #14) by Raymond Camden posted on 2/20/2018 at 12:34 AM

Ah thank you!

Comment 16 by Jaxcoder posted on 2/20/2018 at 1:16 PM

I am just now starting out with
Vue and this is a perfect project to fork and play with. I am very excited to see where it goes.

Comment 17 (In reply to #16) by Raymond Camden posted on 2/20/2018 at 2:04 PM

Cool. I'm hoping to get items (just visual items) done this week.

Comment 18 (In reply to #17) by Jaxcoder posted on 2/21/2018 at 4:31 PM

Very good. I am playing around with it myself. Been a long time since I have even played text adventure. I want to use something like this via a bot for chat gamification.