Tutorial: Create a Text Adventure Game in NodeJS (Part 1)
NicolasBrondinBernard
Do you want to create a video game but don't have the time or the means to work on the graphics? I have the solution!

Article published on 04/09/2020, last updated on 09/08/2026
I was born in the 90s, and yet one of the video games that has stuck with me the most in my life remains a game released in the 80s: Zork I.
Zork is a text adventure game in which you play a character discovering a passage under a small house that will allow you to explore, among other things, underground areas full of surprises.
And yes, as you'll have guessed, there are no graphics whatsoever, everything happens via command line.
For the curious ones, here's what Zork looks like, and now you can even play it online at this address!

Even though having to type in order to perform actions may seem counter-intuitive, it also gives a sense of freedom and near-infinite possibilities that isn't limited to some fixed number of buttons to press!
That's actually why I mentioned this idea in my article "9 creative project ideas for junior web developers" since it offers a lot of freedom to build with, while the game engine remains a very interesting technical challenge, particularly when it comes to the syntax parser, which must be able to understand the sentences entered by the player!
So that's where I'll start to guide you through the creation of a text adventure game, in the first part of this tutorial, the entire code of which you can find on Github!
Part 1: The syntax parser
Let's be clear, the goal is of course not to create a parser capable of detecting the entirety of the French language, but rather it will have two objectives:
- Understand enough terms to link all the features of our future game
- Be permissive enough not to complicate things for the player
In short, our parser will need to be able to detect sentences like "take", "take letter", "take the letter" or even "go north", while rejecting sentences like "I would really like to go to the north of the hill" for the sake of simplicity!
Modeling the syntax
Taking the examples above, we're going to need to define the different types of words present in the sentence, as well as their possible order, which is what we'll call a "syntax tree".
Ours will therefore contain 4 different word types: a verb in the infinitive (go), a preposition (towards), an article (the) and a noun (north). But careful, as we saw before they're not all mandatory, which gives us the following possibilities:
- [VERB, PREPOSITION, ARTICLE, NOUN]
- [VERB, ARTICLE, NOUN]
- [VERB, NOUN]
- [VERB]
Now all that's left is to create our Parser class and start entering a collection of possibilities for each of the word types above.
class Parser {
constructor(){
this.infinitive_verbs = [
"prendre",
"ouvrir",
"regarder",
"lire",
"aller",
"poser"
]
this.articles = [
"le",
"la",
"au"
];
this.prepositions = [
"vers",
"sur",
"dans"
]
this.nouns = [
"boite",
"lettre",
"nord",
"sud"
];
}
}
module.exports = Parser;
The tokens
Once our different possibilities are stored (and easily extensible), we're going to need methods to transform the words of our sentence into "tokens".
"Tokenizing" a string of characters consists of splitting that string in a certain way (here we're going to separate the words at each space) in order to add data onto each of these words.
Here the goal will be to add the word type in order to be able to classify it, and for that we're going to need small functions that will simply go through all the possibilities to see if the given word matches one of them.
If it does, we create the corresponding token, otherwise we return the value null.
//File : Parser.js
INFINITIVE_VERB(word){
let result = this.infinitive_verbs.find((verb)=>{return verb === word.toLowerCase();});
return result ? {type: "verb", value: result} : null;
}
NOUN(word){
let result = this.nouns.find((noun)=>{return noun === word.toLowerCase();});
return result ? {type: "noun", value: result} : null;
}
ARTICLE(word){
let result = this.articles.find((article)=>{return article === word.toLowerCase();});
return result ? {type: "article", value: result} : null;
}
PREPOSITION(word){
let result = this.prepositions.find((preposition)=>{return preposition === word.toLowerCase();});
return result ? {type: "preposition", value: result} : null;
}
Parsing and traversing the tree
Here is the last (and not the least) part of our parser. Its goal is to traverse every branch of the tree, and to test every item of every branch to detect whether the sentence is compatible with one of its branches, in order to be able to transform each word into a token.
//Parser.js
//Take a sentence a return the same sentence tokenized or throw an error
parseText(str){
//Initialize the token array and transform the string into an array or words
let tokens = [];
let str_array = str.split(' ');
//Let's build the syntaxic tree, from the longest possibility to the shortest (mandatory)
let syntaxic_tree = [
[this.INFINITIVE_VERB.bind(this),this.PREPOSITION.bind(this), this.ARTICLE.bind(this), this.NOUN.bind(this)],
[this.INFINITIVE_VERB.bind(this),this.ARTICLE.bind(this), this.NOUN.bind(this)],
[this.INFINITIVE_VERB.bind(this), this.NOUN.bind(this)],
[this.INFINITIVE_VERB.bind(this)]
];
//Let's try every possible sentence form until one succeed
let success = syntaxic_tree.some(function(syntaxic_branch){
//Let's copy all the words to avoid reference issues
let local_str_array = [...str_array];
let local_tokens = [];
//For every branch item, let's check if the next word (of first) fits a token type
let valid_syntax = syntaxic_branch.every(function(syntaxic_token){
let token = local_str_array[0] ? syntaxic_token(local_str_array[0].toLowerCase()) : null;
if(token){
//If the word fits a token, then we push the token and remove the word from the sentence
local_tokens.push(token);
local_str_array.splice(0,1);
return true;
}
return false;
});
//The sentence if fully parsed only when all the token has been found for a branch and there are no word remaining in the sentence
if(valid_syntax && local_str_array.length === 0){
tokens = local_tokens;
return true;
}
return false;
});
if(!success){
//If no branch of the syntaxic tree was compatible, the parsing couldn't be done
throw new Error("ParsingError");
}
return tokens;
}
To make this clearer, for the sentence "Go to the north", it's the third branch of our tree that will be compatible, namely [INFINITIVE_VERB, ARTICLE, NOUN], and the sentence will come out in the following form:
[
{ type: 'verb', value: 'aller' },
{ type: 'article', value: 'au' },
{ type: 'noun', value: 'nord' }
]
Thanks to this information, it will now be easy for us to link each word of the sentence to an action inside the game!
The tests
In order to test different syntactic forms and see whether the parser works correctly, I created an index.js file that simply serves to run it with a few example sentences.
const Parser = require('./Parser.js');
let parser = new Parser();
const sentences = [
"regarder",
"ouvrir",
"ouvre",
"prendre",
"lire",
"lire lettre",
"ouvrir boite",
"regarde boite",
"regarder la boite",
"aller au nord",
"aller vers le sud",
"sud aller vers le",
];
sentences.forEach((sentence)=>{
try {
let tokens = parser.parseText(sentence);
console.log(sentence, tokens);
} catch(e){
console.error("Invalid syntax for sentence: ",sentence);
}
});
The results coming out of the parser are therefore:
regarder [ { type: 'verb', value: 'regarder' } ]
ouvrir [ { type: 'verb', value: 'ouvrir' } ]
Invalid syntax for sentence: ouvre
prendre [ { type: 'verb', value: 'prendre' } ]
lire [ { type: 'verb', value: 'lire' } ]
lire lettre [ { type: 'verb', value: 'lire' }, { type: 'noun', value: 'lettre' } ]
...
Don't forget that you can find all the code in the Github repository below!
Next up
Part 2 of this tutorial is available at this address: https://blog.nicolas.brondin-bernard.com/blog/tutorial-create-a-text-adventure-game-in-node-js-part-2/
Complete courses, exercises and certificates to really learn programming!
4.8 average rating
No comments yet