Text adventure game - text input processing
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
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);
javascript design-patterns parsing typescript adventure-game
add a comment |Â
up vote
4
down vote
favorite
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);
javascript design-patterns parsing typescript adventure-game
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
add a comment |Â
up vote
4
down vote
favorite
up vote
4
down vote
favorite
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);
javascript design-patterns parsing typescript adventure-game
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);
javascript design-patterns parsing typescript adventure-game
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
add a comment |Â
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
add a comment |Â
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
orgo north
? - It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about
look at north door
oropen 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.
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
add a comment |Â
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
orgo north
? - It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about
look at north door
oropen 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.
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
add a comment |Â
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
orgo north
? - It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about
look at north door
oropen 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.
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
add a comment |Â
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
orgo north
? - It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about
look at north door
oropen 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.
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
orgo north
? - It's difficult to ensure that all possible (meaningful) interactions are accounted for. What about
look at north door
oropen 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.
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
add a comment |Â
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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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