Text adventure game - text input processing

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite
2












As a fun project with my wife, I decided to make a small text adventure game à la Zork. Instead of using available engines, I decided to roll my own. It won't be that hard since there are no graphics.



The piece of code I submit for review is a class representing a room in the game. The processInput method takes in the words typed by the player and determines the appropriate response.



I am concerned with the heavy use of nested conditionals and am wondering if there is a design pattern, or perhaps a different data structure, that could make the code cleaner.






import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);










share|improve this question















  • 2




    I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
    – Jonathan
    Jan 17 at 12:48











  • @Jonathan, can you describe what you identify as the command code that could be separated?
    – neoflash
    Jan 17 at 15:39
















up vote
4
down vote

favorite
2












As a fun project with my wife, I decided to make a small text adventure game à la Zork. Instead of using available engines, I decided to roll my own. It won't be that hard since there are no graphics.



The piece of code I submit for review is a class representing a room in the game. The processInput method takes in the words typed by the player and determines the appropriate response.



I am concerned with the heavy use of nested conditionals and am wondering if there is a design pattern, or perhaps a different data structure, that could make the code cleaner.






import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);










share|improve this question















  • 2




    I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
    – Jonathan
    Jan 17 at 12:48











  • @Jonathan, can you describe what you identify as the command code that could be separated?
    – neoflash
    Jan 17 at 15:39












up vote
4
down vote

favorite
2









up vote
4
down vote

favorite
2






2





As a fun project with my wife, I decided to make a small text adventure game à la Zork. Instead of using available engines, I decided to roll my own. It won't be that hard since there are no graphics.



The piece of code I submit for review is a class representing a room in the game. The processInput method takes in the words typed by the player and determines the appropriate response.



I am concerned with the heavy use of nested conditionals and am wondering if there is a design pattern, or perhaps a different data structure, that could make the code cleaner.






import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);










share|improve this question











As a fun project with my wife, I decided to make a small text adventure game à la Zork. Instead of using available engines, I decided to roll my own. It won't be that hard since there are no graphics.



The piece of code I submit for review is a class representing a room in the game. The processInput method takes in the words typed by the player and determines the appropriate response.



I am concerned with the heavy use of nested conditionals and am wondering if there is a design pattern, or perhaps a different data structure, that could make the code cleaner.






import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);









import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);






import Room, Output from "../room";
import GameState from "../game-controller";

export class CenterRoom extends Room
roomName = 'centerRoom';

roomState =
openedBox: false,
keyTaken: false,
northDoorUnlocked: false


processInput(terms: string, gameState: GameState): Output
let output: Output =
text: '',
moveTo: '',
inventory:
add: ,
remove:



// LOOK
if (terms.includes('look'))
if (terms.includes('around'))
if (this.roomState.northDoorUnlocked)
output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.n' +
'The door on the North wall is opened and leads to another room steeped in darkness.';
return output;

output.text = 'You are standing in an empty square room. Each of the four walls has a door.n' +
'You can see a small box on the floor in the middle of the room.';
return output;

if (terms.includes('box'))
if (this.roomState.openedBox && !this.roomState.keyTaken)
output.text = 'It is a plain wooden box with a lid. Inside, you see a small metal key.';
else
output.text = 'It is a plain wooden box with a lid.'
return output;

output.text = `Sorry, you can't look at that.`;
return output;


// OPEN
if (terms.includes('open'))
if (terms.includes('box'))
this.roomState.openedBox = true;
output.text = 'You open the box. Inside, you see a small metal key.'
return output;

output.text = `Sorry, you can't open that.`;
return output;


// TAKE
if (terms.includes('take'))
if (terms.includes('key') && this.roomState.openedBox)
if (this.roomState.keyTaken)
output.text = `You have already taken the key`;
else
this.roomState.keyTaken = true;
output.text = 'You take the key.';
output.inventory.add = ['a small metal key']

return output;

output.text = `Sorry, you can't take that.`
return output;


// UNLOCK
if (terms.includes('unlock'))
if (terms.includes('door') && this.roomState.keyTaken)
if (terms.includes('north'))
if (this.roomState.northDoorUnlocked)
output.text = 'You have already unlocked the North door';
else
output.text = 'You unlock and open the north door.n' +
'The room on the other side is steeped in darkness.'
this.roomState.northDoorUnlocked = true;
return output;

output.text = `Please identify the door you wish to unlock?`
return output;
else
output.text = `Sorry, you can't unlock that.`
return output;



// WALK
if (terms.includes('walk'))
if (terms.includes('north') && this.roomState.northDoorUnlocked)
output.moveTo = 'northRoom';
return output;



return super.processInput(terms, gameState);









share|improve this question










share|improve this question




share|improve this question









asked Jan 17 at 5:46









neoflash

1837




1837







  • 2




    I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
    – Jonathan
    Jan 17 at 12:48











  • @Jonathan, can you describe what you identify as the command code that could be separated?
    – neoflash
    Jan 17 at 15:39












  • 2




    I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
    – Jonathan
    Jan 17 at 12:48











  • @Jonathan, can you describe what you identify as the command code that could be separated?
    – neoflash
    Jan 17 at 15:39







2




2




I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
– Jonathan
Jan 17 at 12:48





I agree with your assessment that the nested if statements are a problem. I would probably separate the command code from the room and just leave lists of "lookables" / "takeables" / "unlockables" / etc. with their accompanying texts and side-effects in the room class.
– Jonathan
Jan 17 at 12:48













@Jonathan, can you describe what you identify as the command code that could be separated?
– neoflash
Jan 17 at 15:39




@Jonathan, can you describe what you identify as the command code that could be separated?
– neoflash
Jan 17 at 15:39










1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted










Flexible, but not scalable



Having to write input handling for each room like this lets you do anything you want, but it doesn't seem very scalable:



  • Some text is duplicated several times. What if you need to fix a spelling error, or want to adjust some description? It's easy to fix it in one place and forget about another.

  • Likewise, verbs will likely be duplicated across multiple rooms, which increases the possibility of ending up with different verbs in different rooms, and that can make the game more difficult (and frustrating) to control.

  • What if you want to support aliases such as inspect box or go north?

  • It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about look at north door or open north door?

These problems will likely get worse for more complicated rooms.



Alternative: a data-driven approach



As Jonathan already mentioned, a data-driven approach, if properly designed, should be easier to manage and result in a more robust and consistent experience. For example, your room could (roughly) be described by the following data:



new Room(
name = 'center room',
description = 'empty square room. Each of the four walls has a door.'
items = [
new Item(
name = 'box',
description = 'plain wooden box with a lid',
canBePickedUp = false,
isContainer = true,
isClosed = true,
items = [
new Item(
name = 'metal key',
description = 'small metal key',
unlocks = 'north door'),
]),
],
exits = [
new Exit(
name = 'north door',
description = 'the door leads to another room steeped in darkness'
isLocked = true,
destination = 'dark room'),
]);


With this, all common verbs can be handled by a single function. Most verbs involve a few common steps, after all:



  • a name lookup: does the current room contain an item or exit with this name?

    • if none, display a failure message.

    • if multiple, let the player choose from a list of matching things.


  • some verb-specific checks:


    • take is only valid for items, and only those that are pick-uppable.


    • look displays the description of a thing, and possibly their status (open/closed) and what they contain (container items) or what they lead to (exits) if applicable.


    • unlock only works if the player holds an item that can unlock the specified exit.

    • and so on.


One benefit is that, as soon as you add an item to a room, all possible interactions with it will 'just work' - it can be looked at, taken, opened, and so on, without any additional code. This even works for items that are dropped by the player, regardless of where they originally came from. It's now also relatively easy to add other creatures that can interact with items and navigate the world.






share|improve this answer





















  • I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
    – neoflash
    Jan 18 at 23:36






  • 1




    It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
    – Pieter Witvoet
    Jan 19 at 0:16






  • 1




    I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
    – Jonathan
    Jan 20 at 14:48










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185280%2ftext-adventure-game-text-input-processing%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










Flexible, but not scalable



Having to write input handling for each room like this lets you do anything you want, but it doesn't seem very scalable:



  • Some text is duplicated several times. What if you need to fix a spelling error, or want to adjust some description? It's easy to fix it in one place and forget about another.

  • Likewise, verbs will likely be duplicated across multiple rooms, which increases the possibility of ending up with different verbs in different rooms, and that can make the game more difficult (and frustrating) to control.

  • What if you want to support aliases such as inspect box or go north?

  • It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about look at north door or open north door?

These problems will likely get worse for more complicated rooms.



Alternative: a data-driven approach



As Jonathan already mentioned, a data-driven approach, if properly designed, should be easier to manage and result in a more robust and consistent experience. For example, your room could (roughly) be described by the following data:



new Room(
name = 'center room',
description = 'empty square room. Each of the four walls has a door.'
items = [
new Item(
name = 'box',
description = 'plain wooden box with a lid',
canBePickedUp = false,
isContainer = true,
isClosed = true,
items = [
new Item(
name = 'metal key',
description = 'small metal key',
unlocks = 'north door'),
]),
],
exits = [
new Exit(
name = 'north door',
description = 'the door leads to another room steeped in darkness'
isLocked = true,
destination = 'dark room'),
]);


With this, all common verbs can be handled by a single function. Most verbs involve a few common steps, after all:



  • a name lookup: does the current room contain an item or exit with this name?

    • if none, display a failure message.

    • if multiple, let the player choose from a list of matching things.


  • some verb-specific checks:


    • take is only valid for items, and only those that are pick-uppable.


    • look displays the description of a thing, and possibly their status (open/closed) and what they contain (container items) or what they lead to (exits) if applicable.


    • unlock only works if the player holds an item that can unlock the specified exit.

    • and so on.


One benefit is that, as soon as you add an item to a room, all possible interactions with it will 'just work' - it can be looked at, taken, opened, and so on, without any additional code. This even works for items that are dropped by the player, regardless of where they originally came from. It's now also relatively easy to add other creatures that can interact with items and navigate the world.






share|improve this answer





















  • I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
    – neoflash
    Jan 18 at 23:36






  • 1




    It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
    – Pieter Witvoet
    Jan 19 at 0:16






  • 1




    I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
    – Jonathan
    Jan 20 at 14:48














up vote
2
down vote



accepted










Flexible, but not scalable



Having to write input handling for each room like this lets you do anything you want, but it doesn't seem very scalable:



  • Some text is duplicated several times. What if you need to fix a spelling error, or want to adjust some description? It's easy to fix it in one place and forget about another.

  • Likewise, verbs will likely be duplicated across multiple rooms, which increases the possibility of ending up with different verbs in different rooms, and that can make the game more difficult (and frustrating) to control.

  • What if you want to support aliases such as inspect box or go north?

  • It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about look at north door or open north door?

These problems will likely get worse for more complicated rooms.



Alternative: a data-driven approach



As Jonathan already mentioned, a data-driven approach, if properly designed, should be easier to manage and result in a more robust and consistent experience. For example, your room could (roughly) be described by the following data:



new Room(
name = 'center room',
description = 'empty square room. Each of the four walls has a door.'
items = [
new Item(
name = 'box',
description = 'plain wooden box with a lid',
canBePickedUp = false,
isContainer = true,
isClosed = true,
items = [
new Item(
name = 'metal key',
description = 'small metal key',
unlocks = 'north door'),
]),
],
exits = [
new Exit(
name = 'north door',
description = 'the door leads to another room steeped in darkness'
isLocked = true,
destination = 'dark room'),
]);


With this, all common verbs can be handled by a single function. Most verbs involve a few common steps, after all:



  • a name lookup: does the current room contain an item or exit with this name?

    • if none, display a failure message.

    • if multiple, let the player choose from a list of matching things.


  • some verb-specific checks:


    • take is only valid for items, and only those that are pick-uppable.


    • look displays the description of a thing, and possibly their status (open/closed) and what they contain (container items) or what they lead to (exits) if applicable.


    • unlock only works if the player holds an item that can unlock the specified exit.

    • and so on.


One benefit is that, as soon as you add an item to a room, all possible interactions with it will 'just work' - it can be looked at, taken, opened, and so on, without any additional code. This even works for items that are dropped by the player, regardless of where they originally came from. It's now also relatively easy to add other creatures that can interact with items and navigate the world.






share|improve this answer





















  • I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
    – neoflash
    Jan 18 at 23:36






  • 1




    It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
    – Pieter Witvoet
    Jan 19 at 0:16






  • 1




    I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
    – Jonathan
    Jan 20 at 14:48












up vote
2
down vote



accepted







up vote
2
down vote



accepted






Flexible, but not scalable



Having to write input handling for each room like this lets you do anything you want, but it doesn't seem very scalable:



  • Some text is duplicated several times. What if you need to fix a spelling error, or want to adjust some description? It's easy to fix it in one place and forget about another.

  • Likewise, verbs will likely be duplicated across multiple rooms, which increases the possibility of ending up with different verbs in different rooms, and that can make the game more difficult (and frustrating) to control.

  • What if you want to support aliases such as inspect box or go north?

  • It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about look at north door or open north door?

These problems will likely get worse for more complicated rooms.



Alternative: a data-driven approach



As Jonathan already mentioned, a data-driven approach, if properly designed, should be easier to manage and result in a more robust and consistent experience. For example, your room could (roughly) be described by the following data:



new Room(
name = 'center room',
description = 'empty square room. Each of the four walls has a door.'
items = [
new Item(
name = 'box',
description = 'plain wooden box with a lid',
canBePickedUp = false,
isContainer = true,
isClosed = true,
items = [
new Item(
name = 'metal key',
description = 'small metal key',
unlocks = 'north door'),
]),
],
exits = [
new Exit(
name = 'north door',
description = 'the door leads to another room steeped in darkness'
isLocked = true,
destination = 'dark room'),
]);


With this, all common verbs can be handled by a single function. Most verbs involve a few common steps, after all:



  • a name lookup: does the current room contain an item or exit with this name?

    • if none, display a failure message.

    • if multiple, let the player choose from a list of matching things.


  • some verb-specific checks:


    • take is only valid for items, and only those that are pick-uppable.


    • look displays the description of a thing, and possibly their status (open/closed) and what they contain (container items) or what they lead to (exits) if applicable.


    • unlock only works if the player holds an item that can unlock the specified exit.

    • and so on.


One benefit is that, as soon as you add an item to a room, all possible interactions with it will 'just work' - it can be looked at, taken, opened, and so on, without any additional code. This even works for items that are dropped by the player, regardless of where they originally came from. It's now also relatively easy to add other creatures that can interact with items and navigate the world.






share|improve this answer













Flexible, but not scalable



Having to write input handling for each room like this lets you do anything you want, but it doesn't seem very scalable:



  • Some text is duplicated several times. What if you need to fix a spelling error, or want to adjust some description? It's easy to fix it in one place and forget about another.

  • Likewise, verbs will likely be duplicated across multiple rooms, which increases the possibility of ending up with different verbs in different rooms, and that can make the game more difficult (and frustrating) to control.

  • What if you want to support aliases such as inspect box or go north?

  • It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about look at north door or open north door?

These problems will likely get worse for more complicated rooms.



Alternative: a data-driven approach



As Jonathan already mentioned, a data-driven approach, if properly designed, should be easier to manage and result in a more robust and consistent experience. For example, your room could (roughly) be described by the following data:



new Room(
name = 'center room',
description = 'empty square room. Each of the four walls has a door.'
items = [
new Item(
name = 'box',
description = 'plain wooden box with a lid',
canBePickedUp = false,
isContainer = true,
isClosed = true,
items = [
new Item(
name = 'metal key',
description = 'small metal key',
unlocks = 'north door'),
]),
],
exits = [
new Exit(
name = 'north door',
description = 'the door leads to another room steeped in darkness'
isLocked = true,
destination = 'dark room'),
]);


With this, all common verbs can be handled by a single function. Most verbs involve a few common steps, after all:



  • a name lookup: does the current room contain an item or exit with this name?

    • if none, display a failure message.

    • if multiple, let the player choose from a list of matching things.


  • some verb-specific checks:


    • take is only valid for items, and only those that are pick-uppable.


    • look displays the description of a thing, and possibly their status (open/closed) and what they contain (container items) or what they lead to (exits) if applicable.


    • unlock only works if the player holds an item that can unlock the specified exit.

    • and so on.


One benefit is that, as soon as you add an item to a room, all possible interactions with it will 'just work' - it can be looked at, taken, opened, and so on, without any additional code. This even works for items that are dropped by the player, regardless of where they originally came from. It's now also relatively easy to add other creatures that can interact with items and navigate the world.







share|improve this answer













share|improve this answer



share|improve this answer











answered Jan 18 at 21:47









Pieter Witvoet

3,611721




3,611721











  • I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
    – neoflash
    Jan 18 at 23:36






  • 1




    It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
    – Pieter Witvoet
    Jan 19 at 0:16






  • 1




    I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
    – Jonathan
    Jan 20 at 14:48
















  • I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
    – neoflash
    Jan 18 at 23:36






  • 1




    It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
    – Pieter Witvoet
    Jan 19 at 0:16






  • 1




    I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
    – Jonathan
    Jan 20 at 14:48















I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
– neoflash
Jan 18 at 23:36




I love it. This approach also has the benefit of making scripting a room much easier... I could probably get my wife, a non programmer, to chip in with that work. One thing I'm wondering though, is how to deal with state dependent actions and descriptions. As an example, how would you handle a room's description being different depending on whether the player has a certain item in his inventory, or weather he has done a certain action previously in another room.
– neoflash
Jan 18 at 23:36




1




1




It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
– Pieter Witvoet
Jan 19 at 0:16




It depends. Maybe you can introduce a common mechanism, such as only being able to look around in dark rooms when there's a light-emitting item (either in the room or held by the player). Or introduce usable things that can affect other things (possibly in other rooms). You could also allow functions inside your data: description = (gameState) => ...return description... .
– Pieter Witvoet
Jan 19 at 0:16




1




1




I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
– Jonathan
Jan 20 at 14:48




I think that is a good suggestion. You can introduce a standardized way of describing room features, like exits, objects, NPCs and their states. You can get these from the entities in the room then
– Jonathan
Jan 20 at 14:48












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185280%2ftext-adventure-game-text-input-processing%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Chat program with C++ and SFML

Function to Return a JSON Like Objects Using VBA Collections and Arrays

Will my employers contract hold up in court?