Sign in to follow this  
PureSnowX

[C#][Console Based] Semi-Advance Text Adventure

Recommended Posts

[font="Trebuchet MS"]First of I'd like to say hello to everyone! ( Uh..Hello! )
I'm new to the forums so i do not know if this is the right place so please excuse me.

I have been working on a console based Adventure game in the style of god old Zork I.
I have been googling forever trying to find some good tutorials on how to write a more advanced text Adventure while having sofisticated programming(OOP) ( i.e avoiding hardcoding locations, items and so forth ).

I'v coded a DataParser class that reads the main Game.dat file that is going to contain all game information, from locations, items to game rules and so forth.
I'd like to get some feedback about my way of parsing data aswell as to help me with my newly found problem.


My main problem is how to solve the "Game Rules", and how to handle them? Like if you pour water on the ground in the Ashforest you die or if you feed the Troll the Garlic he moves out of the way ect ect.
So far this has been solved in EVERY tutorial I'v read ( Well they are for beginners aren't they? ) by hardcoding the ruleset with a big nasty If-else if.

And since i couldn't figure it out myself i thought i could get some help by more experienced users.

I'll post the DataParser Class:
[code]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Adventure
{
public class DataParser
{
private string[] data;
public List<Room> rooms = new List<Room>();

public void InitalizeGame(string filePath)
{
ReadDataFile(filePath);
ParseData();
// Free Memmory
data = null;
}


private void ReadDataFile(string filePath)
{
/* Load Data File into Memmory */
data = File.ReadAllLines(filePath);
}

private void ParseData()
{
/* Loops through each line of the data */
for (int i = 0; i < data.Length; i++)
{
/* We use a switch to use the right action on the right keyword */
switch (data[i])
{
case "@area":/* The keyword for an area */
CreateArea(i);
break;

default: /* Ignore null-lines and //-comments */
break;
}
}
}
private void CreateArea(int index)
{
rooms.Add(new Room());

int listIndex = (rooms.Count - 1);
/* Start reading the file from the keyword+1 */
index++;
string[] lineparser;

for (int i = index; i < data.Length; i++)
{
/* Split it by the #. 0 argument is the subword and 1 is the information */
lineparser = data[i].Split('#');
lineparser[0].Trim();
switch (lineparser[0])
{
case "name":
rooms[listIndex].SetNameOfArea(lineparser[1]);
break;

case "description":
rooms[listIndex].SetDescriptionOfArea(lineparser[1]);
break;

case "item":
lineparser = lineparser[1].Split(',');
rooms[listIndex].AddItem(lineparser[0], lineparser[1], lineparser[2], lineparser[3]);
break;

case "direction":
lineparser = lineparser[1].Split(',');
rooms[listIndex].AddDirection
(
lineparser[0],
lineparser[1],
lineparser[2],
lineparser[3],
lineparser[4],
lineparser[5]
);
break;

default:
return;
}

}


}

}
}

[/code]

Here is a class file that contains Item.cs, Direction.cs and Room.cs
NOTE: Fieldvars is public due to testing, i will make them private.
[code]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Adventure
{

public class Direction
{
public string directionName;
public string directionDescription;
public bool isHidden = false;
public bool isLocked = false;
public string itemRequired;
public string travelLocation;

}
public class Item
{
public string itemName;
public string itemDesc;

public bool isHidden;
public string isFound;
public string located;
}



public class Room
{
private string roomName;
private string roomDesc;
protected const string _newline = "\r\n";
private Direction[] directions = new Direction[8];
public Item[] items = new Item[4];

public void AddItem(string name, string description, string hidden, string location )
{
for (int i = 0; i < items.Length; i++)
{
if (items[i] == null)
{
items[i] = new Item();
items[i].itemName = name;
items[i].itemDesc = description;
items[i].isHidden = bool.Parse(hidden);
items[i].located = location;
return;
}
}
Console.WriteLine("Couldn't add item to room: {0}", roomName);
}
public void AddDirection(string name, string pathdes, string destinationArea, string hidden, string locked, string requires )
{
for (int i = 0; i < directions.Length; i++)
{
if (directions[i] == null)
{
directions[i] = new Direction();
directions[i].directionName = name;
directions[i].directionDescription = pathdes;
directions[i].travelLocation = destinationArea;
directions[i].isHidden = bool.Parse(hidden);
directions[i].isLocked = bool.Parse(locked);
directions[i].itemRequired = requires;
return;
}
}
Console.WriteLine("Couldn't add direction to room: {0}", roomName);
}
public void SetNameOfArea(string name)
{
this.roomName = name;
}
public void SetDescriptionOfArea(string description)
{
this.roomDesc = description;
}

public string Name
{
get { return roomName; }
}
public string Description
{
get { return roomDesc; }
}

public void GetDirectionsInArea()
{
for (int i = 0; i < directions.Length; i++)
{
if (directions[i] == null || directions[i].isHidden)
return;
else
Console.Write(directions[i].directionDescription);
}
}
public void GetItemsInArea()
{
for(int i = 0; i < items.Length; i++)
{
if(items[i] == null || items[i].isHidden)
return;
else
Console.WriteLine(items[i].located);
}
}

public void DisplayArea()
{
Console.WriteLine(roomName);
Console.Write([/font]roomDesc[font="Trebuchet MS"]);
}
}
}

[/code]

And last, the Game.dat file:
[code]
// Item:
// Item Name, Item Examine Description, Hidden, Location

// Direction:
// Direction Name, Path Description, Travel Location, Is Hidden, Is Locked, Item Required to Unlock
//============== AREA SETUP ==============\\

@area
name#Deep Dungeon
description#You stand infront of a big dark cave. From the inside you can hear small dripping sounds of water falling from the ceiling. Around you is a thick forest. You hear a bird sing from within the forest.
direction#North,Infront of you their is a long narrow tunnel that plunges deep into the dark cave,,false,true,torch
direction#South,To the south their is a small path leading back to the forest,Gloomy Forest,false,false,none
item#Rusty Key, A small rusty key that seam really old,true,On the ground their is a small key,none


@area
name#Gloomy Forest
description#You stand in the middle of the groovy forest of Ashevale. Trees is surrounding you.
direction#North,a road leading deeper inside the forest,Deeper Dungeon,false,false,none
direction#West,,Forest of the Damned,false,false,none
item#Torch, An old torch. It still looks like it's usuable.,false,On the ground a bit away from the clearing their is a Torch,none

@area
name#Wakky Forest
description#You stand in the middle of the groovy forest of Ashevale. Trees is surrounding you.
direction#North,a road leading deeper inside the forest,Deeper Dungeon,false,false,none
direction#West,,Forest of the Damned,false,false,none
item#Torch, An old torch. It still looks like it's usuable.,false,ground,none
[/code]
[/font]

Share this post


Link to post
Share on other sites
[quote]So far this has been solved in EVERY tutorial I'v read ( Well they are for beginners aren't they? ) by hardcoding the ruleset with a big nasty If-else if.[/quote]

The tutorial you are reading are wrong.

I've been dealing with a similar problem myself, albeit my game is graphica/roguelikish. My particular solution, which I am by NO MEANS stating is optimal or necessarily even good, just better than endless if statements, was to embed Lua as a scripting language.

How my system works:

1. There are a number of verb functions, defined in Lua scripts. These get invoked when the player presses some keyboard key or picks something from a window.

2. Each verb function takes a number of arguments representing the nouns involved in the verb: One for intransitive verbs, two for transitive verbs, and three for ditransitive verbs.

3. The verb function calls a function in the Lua script associated with each of the nouns which is used to generate a function closure or nil, with nil meaning the noun cannot do the action in question. As arguments, the action generator takes the string representing the verb, the particular semantic role of the noun (Agent, Direct Object, Indirect object), and pointers to the nouns (Lua sets any missing arguments to nil, so as long as the noun pointers are last in the argument list and in the specific order mentioned above, you won't have problems).

4. Based on a number of different factors, the action generation functions either return nil or an appropriate function closure representing what they should do when that verb is invokved.

5. The verb function gets the returns from the action generation functions, makes sure none of them are nil, and then runs them all if they were non-nil.

6. Also you could have a level-wide action generator function that could be called, which could filter or block all actions or something.
This is still going to involve a goodish number of if-statements, in terms of figuring out which action gets generated, because those are sort of inevitable in an interactive game, however it's not going to involve nearly as many. Also depending on how you make your object model you could really generalize a lot of things.




Also for the record, writing the parser is going to be the hardest part of writing a text adventure, apparently.

Share this post


Link to post
Share on other sites
[quote name='MeshGearFox' timestamp='1302446370' post='4796689']
The tutorial you are reading are wrong.
[/quote]
Haha, well It's hard to find good tutorials.

[quote name='MeshGearFox' timestamp='1302446370' post='4796689']
I've been dealing with a similar problem myself, albeit my game is graphica/roguelikish. My particular solution, which I am by NO MEANS stating is optimal or necessarily even good, just better than endless if statements, was to embed Lua as a scripting language.
[/quote]
I have been thinking of implementing Lua in my project. Though i have been avoiding it because i would like to avoid it.

Maybe a Trigger-class that holds a string FunctionTrigger with Methods dealing with Adding Items to Locations, Adding Paths to Locations.
Then i could invoke the right method my calling something like: typeof(Trigger).GetMethod(FunctionTrigger).Invoke(this, parameters);

Though in the end, Lua is probably a better choice.

[quote name='MeshGearFox' timestamp='1302446370' post='4796689']
Also for the record, writing the parser is going to be the hardest part of writing a text adventure, apparently.
[/quote]
Well writing the DataFile parser wasn't that hard though a good nice Command Parser will be a bit harder :/



I would like to get more input on this! :D

Share this post


Link to post
Share on other sites
[quote name='Moonkis' timestamp='1302451655' post='4796717']
[quote name='MeshGearFox' timestamp='1302446370' post='4796689']
The tutorial you are reading are wrong.
[/quote]
Haha, well It's hard to find good tutorials.
[/quote]

The problem with finding tutorials on making text adventures is that the IF community is very much against people making their own IF engines because it is too hard. Personally I think being too hard is actually a really GOOD reason to try doing something, but whatever.




[quote]I have been thinking of implementing Lua in my project. Though i have been avoiding it because i would like to avoid it.[/quote]

I just noticed you were using C#. C# should have enough first-order function capabilities that you could stick to pure C# I guess.




[quote]Well writing the DataFile parser wasn't that hard though a good nice Command Parser will be a bit harder :/[/quote]

DataFile parsing is easy because you have a specific format. Command parsers are NOT easy because English is not a regular language, commands are not going to have a standard format. The hard part is figuring out the part of speech stuff has. It's actually not that hard, honestly, but it's more of a linguistics issue than a CS issue per se and if you don't know what a syntax tree is you might want to look that up.

Share this post


Link to post
Share on other sites
Hello I am also working on a text-based game in C#. I am learning everything as I go, building it all from the ground up. I don't feel like painting by numbers, since I already know this won't make me 5 bucks much less a million I figure I might as well learn some useful things for the time invested.

The approach I took to game data files was to use an MDB file (Jet database). I can access it very quickly with C# natively, perform actual SQL select statements against it, open and easily edit it during development using Microsoft Access, and later on down the line if I need at least some type of security (if you want to call it that) I can look into using a newer ACCDB database, also accessible via C#, that supports many more features. But the point is, it's quick to access on the fly, quick to get data out of, quick to get data into, and very accessible, and working with it is well documented.

As for the language parser... I haven't started on that yet! I'm working on world navigation and object manipulation right now...

Anyhow, food for thought. Have fun with your project!

Edit: sorry for the wall of text- the editor doesn't seem to want to save any line feeds for some reason.

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