Sign in to follow this  
hdnine

[web] Need help with my PHP-based Text Adventure parser

Recommended Posts

Hey everyone!

I have been working about a month on a PHP-based text adventure engine which at this point can parse two words like TAKE SUITCASE or EXAMINE SOFA. All verbs and objects are located in arrays which are fetched from a database at the start of the game. The problem i have is that i can't seem to find a good way to parse text if the input is more than two words.

In PHP i use EXPLODE to seperate the words and then check each word against the different arrays that i have -- verbs, objects, prepositions, noise words and so on. These are marked with "v" if verb, "p" if preposition and so on to determine what kind of word the are right... So far i can write TAKE <object> and it will be picked up, or, EXAMINE <object> and a text explaining the object will be shown.

So here is the problem which i really would like to find a more elegant solution for -- i use IF / ELSE and SWITCH / CASE conditional statements to handle the parsing and these are quickly getting larger as i try to move in to a third word. My plan is for the text parser to handle at least 5-7 words not counting the noise words like "the". Here is some example code:

[code]// First word
if(isset($input[0]))
{
$word1 = checkWord(0,$input);
}

// ---------------

// WORD 1 - SYSTEM
// ---------------
if(isset($word1['type']) && $word1['type'] == 's')
{
// System commands can only be alone, any following words will be ignored
eval($word1['word']['action']);
}
// ------------------
// WORD 1 - DIRECTION
// ------------------
elseif(isset($word1['type']) && $word1['type'] == 'd')
{
// This will set the player to the destination she has chosen
$array = $word1['word']['dest'];
$_SESSION['player']->setPos($array);
}[/code]

So basically the only words that are allowed later on when parsing three or more words are the ones starting with a verb. So the code is similar to this:

[code]// -------------
// WORD 1 - VERB
// -------------
elseif(isset($word1['type']) && $word1['type']== 'v')
{
// The first word is...
switch($word1['word']['name'])
{
// BACK
case 'back':
// If the verb is "back" then go to the last position the player was at
eval($word1['word']['action']);
break; // back
// GO
case 'exit': case 'enter': case 'crawl': case 'go': case 'move': case 'run': case 'walk':
// --------------------
// WORD 2 - PREPOSITION
// --------------------
if(isset($word2['type']) && $word2['type'] == 'p')
{
// The second word is...
switch($word2['word']['name'])
{
case 'above': case 'over':
break;
case 'across':
break;
case 'after':
break;
case 'around':
break;
case 'behind':
break;
case 'below': case 'beneath': case 'under':
break;
case 'between':
break;
case 'beyond':
break;
case 'in': case 'inside': case 'into':
break;
case 'near':
break;
case 'on': case 'onto':
break;
case 'out': case 'outside':
break;
case 'through':
break;
case 'to': case 'towards':
break;
case 'until':
break;
}
}
// ------------------
// WORD 2 - DIRECTION
// ------------------
elseif(isset($word2['type']) && $word2['type'] == 'd')
{
// This is like saying "go north" or "go kitchen" instead if just "north" or "kitchen"
$array = $word2['word']['dest'];
$_SESSION['player']->setPos($array);
}
else
{
goto showError;
}
[/code]


I realized that at this stage there are a lot of repetitions to start off with and A LOT more code just to check every possible combination like VERB+OBJECT, VERB+PREPOSITION+OBJECT, VERB+OBJECT+PREP+OBJECT and so on. Please help me... there has to be a better way to handle this than thousands of lines of code to check all possible combos? I was thinking of having some sort of relational database table which would contain all verbs and which prepositions they can be followed by, not sure if this is a better solution.

Another problem is that some verbs only work with specific prepositions or objects, for example WALK ACROSS BRIDGE works fine, TAKE ACROSS BRIDGE doesn't make sense. LOOK (verb) UNDER (preposition) TABLE (noun/object) will work fine, WEAR UNDER TABLE doesn't. Hopefully you see the problem since the combinations seems to be endless. Two words are easy, three gets complicated and more than that seems impossible.

Thanks,
Krister Nielsen
Sweden

Share this post


Link to post
Share on other sites
This is an interesting problem, and without mapping out -all- the possibilities you like there is no clear cut way to go about doing this. Naturally the first thing I thought of though would be something like this. I'm certainly not proficient in PHP, so I don't know standards, but I have an idea of how to make it work without a very deep switch statement or a db (I think).

I'll be building off of purely what you've given in your example to help keep this (hopefully) reasonably short


Assuming the action comes as one thing from a single input split it into an array based on a space.
[code]
$verb = $word1; // simply to get a more readable name

$verbFile = "/verb/" . $verb . ".php";
if(file_exists( $verbFile ) { // have an individual object for each verb since they need to know how to act
require_once( $verbFile );
}else {
die( "Invalid Verb" ); //probably should fail more gracefully
}
[/code]

Take out the need for a switch statement, if there isn't a file for the verb, assume it's useless. Lets now define a verb, and an Action interface.


[code]
interface Action{
public bool Can( $word );
}
[/code]
All verbs must be able to tell what it 'Can' do. We'll pass in '$word2' into here to judge if it's capable.


[code]
abstract class Verb implements Action{
protected $action;
protected $objects;
protected $preposition;

public bool Can( $word ) {
foreach( $i in $action ) {
if( $word === $action ) {
return true;
}
}
foreach( $i in $object ) {
if( $word === $object ) {
return true;
}
}

foreach( $i in $preposition ) {
if( $word === $preposition ) {
return true;
}
}
}
}
[/code]


So each verb now has the knowledge of which actions it can perform within itself. There is no reason anything else needs this globally available (like the switch statement would do). How can we now implements each individual verb to handle accordingly though..

In your switch statement all the words like 'exit', 'enter', 'crawl', etc can all perform simply actions, so we should have a class which each of these words inherit from also. For the sake of time I'll call it 'Movement' (even though 'exit' techincally fills this description, and doesn't have the same actions available so think of a better name :) )

[code]
class Movement extends Verb {
protected $action;
protected $objects;
protected $preposition = array('above', 'across', 'after', 'near'); //all the other things in the switch statement

public void Can( $word ) {
if( parent::__Can( $word ) ) { // Any thing all verbs can do should be checked first (although I don't think there is anything..)
return true;
}
//now loop through each '$action', '$objects', '$preposition' like 'Verb' does to make sure this specific Verb cannot do something an simple verb can do
}
}
[/code]

Now each verb that can do all the things in the preposition should be inherited from 'Movement', and now they can also have their own special cases. Lets say for example only 'Crawl' can do something like "CRAWL IN HOLE" (makes splitting the string more complicated, but makes for an easier example).

[code]
class Crawl extends Movement {
protected $action = array( 'in hole';
protected $objects;
protected $prepositions;

public bool Can( $word ) {
if( parent::__Can( $word ) ) {
return true;
}
// loop through possibilities for crawl specifically like the past 2 examples
}
}
[/code]

Anyway, I hope that gives a basic idea of how to go about that. Simply make generic classes, and slowly build up into more specific verbs which have their own specific cases. Now they code to check if something is possible can be represented as:

[code]
$verbObj = new $verb();
if( $verbObj->Can( $word2 ) ) {
// Probably should add a method 'Do()' into the Action interface so you can now do
$verbObj->Do( $word2, $allWords ); //$allWords represents any word after the verb, and $word2 in the query
}else{
die( "Invalid action for " . $verb . "." ); // fail more gracefully
}
[/code]

Now you have a verb passing on responsibility to individual verb objects which can now know exactly what they are capable of without you having to build up a huge switch statement. Granted now you have to do something similar in order for '$verb->Do( $word2, $allWords );' in order for the proceeding words to now be able to take action based on that.

I typed type all of this up purely in the forum so the chances of it being 100% correct are about 0%, but hopefully it gives you an idea of how to approach this. Also hope it didn't turn into a bunch of rambling.

[b]Edit: [/b]In some way you eventually have to check individual cases, but an OOP approach helps break it down into much more manageable pieces. For example if you decided that 'exit', 'walk', 'run', etc all should be able to do something new instead of wandering through the switch statement you can now just add the new action into "Movement", because you know all of those are movements. Or if you decide a certain movement needs it's own special case you can easily add it to it's own class also. Overall this is a more manageable approach, but doesn't eliminate the need to check if a verb can do a specific thing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this