noobish attempt at text-based adventure...

Started by
16 comments, last by StaticEngine 19 years, 3 months ago
I've been coding this text-based game in C++ for the past hour and a half. So far, it goes like this: -There are four rooms... ******4 ******| *3--2--1 -Rooms 3 and 4 can be inspected -You can exit only from room 1 The code:
#include <iostream>
using namespace std;

#define PRINT(x)	cout << (x) << endl;

int main()
{
	short choice=0;
	short number=1;
	short room3insp=0;
	short room4insp=0;
	char text[]="Welcome!";
	char one[]="Room one.  1 for west, 2 for north, 3 to exit.";
	char two[]="Room two.  1 for west, 2 for east.";
	char three[]="Room three.  1 for east, 2 to inspect.";
	char four[]="Room four.  1 for south, 2 to inspect.";
	char exit[]="Exiting...";
	char inspected[]="This room has been inspected.";
	char uninspect[]="This room is uninspected.";
	char invalid[]="Invalid choice.  Try again.";


	PRINT(text);
	while(number>0 || number<6)
	{
		if(number==1)
		{
			while(choice>0 || choice<4)
			{
				PRINT(one);
				cin>>choice;
				if(choice==1)
				{
					number=2;
					break;
				}
				if(choice==2)
				{
					number=4;
					break;
				}
				if(choice==3)
				{
					number=5;
					break;
				}
				else PRINT(invalid);
			}
		}
		if(number==2)
		{
			while(choice>0 || choice<3)
			{
				PRINT(two);
				cin>>choice;
				if(choice==1)
				{
					number=3;
					break;
				}
				if(choice==2)
				{
					number=1;
					break;
				}
				else PRINT(invalid);
			}
		}
		if(number==3)
		{
			while(choice>0 || choice<3)
			{
				PRINT(three);
				if(room3insp==0)
					PRINT(uninspect);
				if(room3insp!=0)
					PRINT(inspected);
				cin>>choice;
				if(choice==1)
				{
					number=2;
					break;
				}
				if(choice==2)
				{
					room3insp=1;
					break;
				}
				else PRINT(invalid);
			}
		}
		if(number==4)
		{
			while(choice>0 || choice<3)
			{
				PRINT(four);
				if(room4insp==0)
					PRINT(uninspect);
				if(room4insp!=0)
					PRINT(inspected);
				cin>>choice;
				if(choice==1)
				{
					number=1;
					break;
				}
				if(choice==2)
				{
					room4insp=1;
					break;
				}
				else PRINT(invalid);
			}
		}
		if(number==5)
		{
			PRINT(exit);
			break;
		}

	}
	return 0;
}


I plan to add a feature to tell how many moves have been made between rooms and a simple puzzle next. I tried using switch statements and functions, but I forgot some syntax (thanks in part to a hard semester of Motorola 68000 Assembly class). My questions for now are: 1. What can I do to compact the code? 2. When entering either +32768 or higher, or -32768 or lower, the loop goes on endlessly. Why does this happen? 3. Seeing as that I may experiment more with C# soon, would this code be easier to implement there than in C++?
Advertisement
A quick question in return : are you purposefully planning in C, or are you happy to use C++?

Although I've just seen the headers you're using, and the 'using' statement - seems to indicate C++.

OK - couple of comments:

You're using char arrays a lot - have a look into std::string from the STL library - it may help to avoid possible problems in the future.

Quote:
1. What can I do to compact the code?

Well, I'd say you need to compartmentalise your code at some point - either using functions or classes. A simple class to add might be a room class. A quick glance suggests that a room has the following properties:

A description.
A list of possible options, each with an integer key for input.
Which room each input key would take you to.
A bool flag for whether the room can be inspected.

Building this into a class would allow you to learn about classes (doh) and possibly std::map or std::vector (thinking of implementations off the top of my head).

This should allow easier addition of rooms to your program (including when you get into loading them from a file), and help remove that monster if/else clause you have. Have a think about how to do it, and if you have more questions then come back with some implementation questions.


Quote:
When entering either +32768 or higher, or -32768 or lower, the loop goes on endlessly. Why does this happen?

Sorry - haven't had time to answer this, and my wife is just dishing up some chilli - will have a look after tea and see if anyone else has answered it.

Quote:
Seeing as that I may experiment more with C# soon, would this code be easier to implement there than in C++?

Given where I suspect you are in your development (based upon this question), I would say stick with one language for the moment and learn the basics of programming. One language means that you have less to think about, and when you've learnt one, transfer to another is a lot easier.

HTH,
Jim.
When entering either +32768 or higher, or -32768 or lower, the loop goes on endlessly. Why does this happen?

Overflow.

signed integers have 15 bits (16th is the sign bit, that makes it a + or a - number).

2^15 = 32768

When you try to shove a bigger number in there, it can't hold it. You end up with overflow. It basically only holds the last 16 bits of the number in that memory location,

If choise > 0 || choise < 4
you should have an && here. the or lets you get away with any number (negitive numbers are less then 4, and a couple of trillion is greater then 0)

Now because the number is not 1,2,3,4, and 5, it just keeps looping. The value doesn't change, so it loops forever.

From,
Nice coder
Click here to patch the mozilla IDN exploit, or click Here then type in Network.enableidn and set its value to false. Restart the browser for the patches to work.
You should check out Inform, by Graham Nelson, and read up on the Z-Machine, as developed by Infocom. The former is an Object Oriented language that compiles to bytecode for the latter, an interpreter that runs text adventures on any platform.

It's great stuff, and you can learn a lot about development in general by working in Inform.
Quote:Original post by obi-wan shinobi
I plan to add a feature to tell how many moves have been made between rooms and a simple puzzle next. I tried using switch statements and functions, but I forgot some syntax (thanks in part to a hard semester of Motorola 68000 Assembly class).


Judging from what I see, you should really look in to steering your education towards higher-level programming. I don't doubt you have a solid grasp of how the machine works, but you won't learn about data structures and algorithms that way (because it takes too long to hand-code them in assembly even if you do know what you're doing).


Quote:
My questions for now are:
1. What can I do to compact the code?


Develop some data structures in order to exploit the parallel behaviour. In C++ you get access to this wonderful thing called a "class", which allows you to associate code behaviour with a piece of data. Conceptually you so far have one real abstraction, "room". A room has a description, and some number of links to other rooms. Also, it may be inspected or uninspected. (I assume you really want all the rooms to be inspectable. If not, you could add extra state to represent that.)

Room text can be represented easily with arrays of characters, as you are well aware. But this is really a hack; characters are really numeric types, your "strings" are null-terminated (some operations are actually inefficient because you don't have direct access to a length count; plus you can't have 0 in the middle of your data), and they're prone to all kinds of coding errors that ultimately result in segmentation faults. Enter std::string.

Links can be represented easily by pointers - a familiar concept for you I'm sure. We don't have the same number of links for each room, but there are simple ways around that. The bare-bones way would be to just have a pointer to the list of pointers, and treat it like a C-style string. Of course, after a semester of 68k assembly you should appreciate the value of not doing things the hard way :) Thus we introduce your friend and mine, std::vector. We get:

class Room {  private:  string name;  bool inspected;  vector<Room *> neighbours;}  


OK, so that's what the room data looks like. Notice that vectors are templated; for container types like vectors, we can read the angle brackets as "of" - so this is a "vector of pointers to Room".

Now we need to specify some behaviour for Rooms. There are at least four things we need to be able to do:

- Create a new Room. It will be uninspected, and a name will be provided by the client code. It will start out with no links...

- Add a link to an existing Room.

- Tell the main game loop what its description is, and whether the room has been inspected.

- Handle input from the user and determine the user's current (new) Room.

So now we go back to the class definition, and add appropriate signatures for those methods... I'll wait for your questions before I try to explain them further ;)

class Room {  private:  std::string name;  bool inspected;  std::vector<Room *> neighbours;  public:  Room(std::string name) : name(name), inspected(false), neighbours(std::vector<Room *>>()) {}  void linkTo(const Room& other);  std::string description();  Room* processInput(int usersChoice);}  


and then for organization's sake, we put this into a header file, "room.h". In the corresponding implementation file "room.cpp", we implement those methods:

#include "Room.h"using namespace std;// The "constructor" was already implemented; there is no initialization we// have to do except just setting the field values.void Room::linkTo(const Room& other) {  // Add a Room* to the vector which points at the input room.  neighbours.push_back(&other);  // The "neighbours" here is that belonging to the 'current object'.  // Member function calls always have an object associated with them, like  // I was talking about. Actually, you've been using this all along...  // e.g. 'cout << "hi"' is really cout.operator<<("hi"), which is calling the  // iostream method operator<< on the input "hi" with associated object cout.  // Since neighbours is a vector, it will automatically resize itself to hold  // anything you might push_back() on the end.}std::string description() {  // Create a string from the room description and inspection state.  string result = name + "\n";  if (inspected) result += "This room has been inspected.";  else result += "This room is uninspected.";  return result;  // See how beautifully it reads? No messing around with strcat() here...  // Left as an exercise: Modify this so that it will look at the "neighbours"  // vector and add information about what directions are valid.  // You may find you want to associate a name with each "direction". Of course,  // you can't just add a "direction" string to each Room, because you could  // enter a room in several ways. The name needs to be associated with the  // link itself.  // Fortunately the standard library provides a cheap hack for handling simple  // pairings like this: look up std::pair. You would end up with a  // std::vector<std::pair<std::string, Room*> >. :)  // (That space is important - trust me.)}Room* processInput(int usersChoice) {  // Given an input choice, possibly adjust the value of 'inspected',  // and return the Room* which points to the user's new room.  // Left as an exercise ;) But here are some hints:  // 1) "this" is a pointer to the current Room (which you can use if the action  // doesn't result in the player moving).  // 2) You can access elements in the "neighbours" vector as if it were an  // array (except that arrays don't automatically resize).  // 3) You are strongly recommended to always use the same numeric value for  // "inspect", to simplify the logic. All Rooms should behave in the same  // fundamental way; that's part of the abstraction.  // 4) Try to do some arithmetic on the choice in order to get an array index.  // 5) Return NULL to indicate that the user has exited and is in "no room".}


Now we have a properly organized pair of source files to represent the Room class. You might think of them together as the "Room module". Now we can use it from the main program:

#include "Room.h"// We need that in order to let the main program know what Rooms are. Note we// *do not* include the Room.cpp! Roughly, .h files are for interface// description (except really simple things, and no actual variable storage)// and .cpp files are for implementation.#include <iostream>using namespace std;// We don't need to use the preprocessor for things like this any more in C++:template<typename T>inline void print(const T& x) {  cout << x << endl;}int getInput(const string& prompt) {  // Left as an exercise; see my answer to your other questions.}int main() {  // Create the rooms. We invoke that special "constructor" method, and  // initialize rooms with given names.  // The string literals are implicitly converted to std::strings. AFAIK...  // (If it doesn't work, it's easy to fix; just make use of the std::string  // constructor that accepts a char* :)  Room a("Room one."),       b("Room two."),       c("Room three."),       d("Room four.");  // Make the links between rooms.  a.linkTo(b); // 1->2  a.linkTo(d); // 1->4  // etc. for the other links. Looks nice and readable yes?  // And now we can add the rest of the variables we need...  Room* current = a; // the player starts in room 1.  // Oh, that's all we need. Heh. :)  // Now we do our main game loop.  print("Welcome!");  while(current) {     // We will indicate that the user has exited by returning a NULL pointer.    // Inform the user of current location, inspection status, and options    print(current->description()); // note the -> because we have a pointer    // Get input, and let the current Room update itself and tell us where    // the user ends up.    current = current->processInput(getInput("Your command?"));    // For example.     // Alternatively, you could make getInput into a Room method, or build it    // into processInput... however, it's usually best to keep IO stuff    // separated out from your objects as best you can.    // The problem is that the main game loop doesn't know how many exits are    // in the current room, so it can't validate the input very well. There are    // a few ways around that; I'll let you think about it. :)  }  print("Exiting...");}



Quote:
2. When entering either +32768 or higher, or -32768 or lower, the loop goes on endlessly. Why does this happen?


The previous post citing overflow was on the right track, but missed the point.

When you attempt to read a number from cin with the >> operator, the beginning of the stream has to have some decimal digits, representing a number that will fit within that data type. If the input isn't valid (either the number is too big, or it's not actually a number - go on, try your program with input "hi"), then:
- A "failed" flag is set within the cin object; this must be reset before you can read any more.
- The invalid input is not consumed; it remains there at the beginning of the stream.

Thus, to recover from this (in a command-line environment), you need to:
- "reset" the fail flag with cin.clear().
- Ignore the current line of input (so that the user will have to input a new line of input): cin.ignore(numeric_limits<streamsize>::max(),'\n');

The .ignore() method requests a maximum number of characters to ignore (here we set it as big as possible. You could also set it in terms of the currently available amount of data: 'cin.rdbuf->in_avail()' - I probably got that a bit wrong though), and the character to ignore up until (a new line).

And then you can loop to try again.

See more here.


Quote:
3. Seeing as that I may experiment more with C# soon, would this code be easier to implement there than in C++?


Eventually, probably. At this point, not really. There isn't enough complexity yet (beyond what you impose on yourself). C++ is high enough level that you can think in terms of data structures and algorithms, although you may still find yourself burdened by having to specify lots of lower-level details as well.
Lisp.

Really, if you want a nice heigh level language, use lisp.

Although it might be scary, it won't bite. as long as you done code like your in the lisp occc, you won't get stuck understanding what it means. (once you've used it for a while, of cource).

From,
Nice coder
Click here to patch the mozilla IDN exploit, or click Here then type in Network.enableidn and set its value to false. Restart the browser for the patches to work.
Thanks for the comments so far. I did manage to add a small puzzle element and a move counter successfully into the old code. But I've also recoded it so that each room has its own function and a letter has to be entered to make a choice.

My main problem is that I took a little 5-month hiatus from regular C++ practice and it's coming back to haunt me. Even worse, I'm still clueless on pointers, advanced classes, inline functions, templates, vectors...so all I can work with for now are whatever preprocessor macros I make up, functions, basic classes, and global variables.

Zalhman: The assembly class was a requirement for my Computer Science major. So far, I've also had two courses in Java, and am expecting to deal with FORTRAN in my next class. Where can I find std library documentation? My VC++6 Standard doesn't seem to have it.

Nice Coder: Okay, I thought it would have to do with overflow, but I didn't know the exact explanation. I'll check lisp too.


This time around:
-There are five rooms
-#3 has a key
-#4 has a lock
-#5 is only accessible after the key is used on the lock
-you can end the program from either #1 or #5
******4
******|
*3--2--1
***|***
***5***

#include <iostream>using namespace std;#define PRINT(x)	cout << (x) << endl;//function prototypesint one();int two();int three();int four();int five();//globalsshort secondflag=0;short thirdflag=0;short fourthflag=0;unsigned short count=0;char invalid[]="Invalid choice.  Please re-enter.";char intro[]="Welcome to the test program!";char out[]="Now leaving test program.";char resting[]="Zzzzzzzz....zzzzzz.........";short number=1;char letter;int main(){	PRINT(intro);	while(number > 0 && number <8)	{		if(number==1)			number=one();		if(number==2)			number=two();		if(number==3)			number=three();		if(number==4)			number=four();		if(number==5)			number=five();		if(number==6)		{			PRINT(out);			break;		}		if(number==7)		{			PRINT(resting);			break;		}	}	cout << "Number of room changes: " << count << endl;		return 0;}int one(){	char first[]="\nThis is the first room.  You may exit from here.";	char opt1[]="Enter: N-north, W-west, L-exit.";	PRINT(first);	PRINT(opt1);	cin>>letter;	if(letter=='N' || letter=='n')	{		count++;		return 4;	}	else if(letter=='W' || letter=='w')	{		count++;		return 2;	}	else if(letter=='L' || letter=='l')	{		return 6;	}	else PRINT(invalid);	return 1;}int two(){	char second_a[]="\nThis is the second room.  Nothing unusual...";	char second_b[]="\nThis is the second room.  There's a new door...";	char opt1[]="Enter: W-west, E-east.";	char opt2[]="Enter: W-west, E-east, S-south.";	if(secondflag==0)	{		PRINT(second_a);		PRINT(opt1);	}	else	{			PRINT(second_b);		PRINT(opt2);	}	cin>>letter;	if(letter=='W' || letter=='w')	{		count++;		return 3;	}	else if(letter=='E' || letter=='e')	{		count++;		return 1;	}	else if(letter=='S' || letter=='s')	{		if(secondflag==0)		{			PRINT(invalid);			return 2;		}		else		{			count++;			return 5;		}	}	else PRINT(invalid);	return 2;}int three(){	char third_a[]="\nThis is the third room.  There's a key on a table.";	char third_b[]="\nThis is the third room.  You already took the key.";	char notice[]="Nothing else was found.";	char opt1[]="Enter: E-east, T-take key.";	char opt2[]="Enter: E-east, S-search.";	if(thirdflag==0)	{		PRINT(third_a);		PRINT(opt1);	}	else	{			PRINT(third_b);		PRINT(opt2);	}	cin>>letter;	if(letter=='E' || letter=='e')	{		count++;		return 2;	}	else if(letter=='T' || letter=='t')	{		if(thirdflag==0)		{			thirdflag=1;			return 3;		}		else		{			PRINT(invalid);			return 3;		}	}	else if(letter=='S' || letter=='s')	{		if(thirdflag==0)		{			PRINT(invalid);			return 3;		}		else		{			PRINT(notice);			return 3;		}	}	else PRINT(invalid);	return 3;}int four(){	char fourth_a[]="\nThis is the fourth room.  A keyhole is visible.";	char fourth_b[]="\nThis is the fourth room.  The key has been used.";	char notice_a[]="You need a key...";	char notice_b[]="The key seems to be stuck...";	char opt1[]="Enter: S-south, U-open lock.";	char opt2[]="Enter: S-south, R-retrieve key.";	if(fourthflag==0)	{		PRINT(fourth_a);		PRINT(opt1);	}	else	{		PRINT(fourth_b);		PRINT(opt2);	}	cin>>letter;	if(letter=='S' || letter=='s')	{		count++;		return 1;	}	else if(letter=='U' || letter=='u')	{		if(fourthflag==0)		{			if(thirdflag==0)			{				PRINT(notice_a);				return 4;			}			else			{				fourthflag=1;				secondflag=1;				return 4;			}		}		else		{			PRINT(invalid);			return 4;		}	}	else if(letter=='R' || letter=='r')	{		if(fourthflag==0)		{			PRINT(invalid);			return 4;		}		else		{			PRINT(notice_b);			return 4;		}	}	else PRINT(invalid);	return 4;}int five(){	char fifth[]="\nThis is the fifth room.  There is a bed to rest in.";	char opt1[]="Enter: N-north, B-take a rest.";	PRINT(fifth);	PRINT(opt1);	cin>>letter;	if(letter=='N' || letter=='n')	{		count++;		return 2;	}	else if(letter=='B' || letter=='b')		return 7;	else PRINT(invalid);	return 5;}
Seriously, go learn Inform: http://www.inform-fiction.org/

There's a manual, the Inform Designer's Manual, available as a softcover bound edition, or as a PDF, and it's excellent. I would recommend it to anyone interested in learning to program, as an introduction.

Inform is powerful enough that you'll be able to go from Inform to C++ when you're ready to make the leap. Plus, you'll have a much more satisfying experience because you'll be able to make an actual text adventure within a few days, without having to learn about the STL, or program your own parser, and so on, right from the start.
Stl

Seriously, lisp makes this so easy.
From,
Nice coder
Click here to patch the mozilla IDN exploit, or click Here then type in Network.enableidn and set its value to false. Restart the browser for the patches to work.
Good heavens, they still teach FORTRAN? o_O

This topic is closed to new replies.

Advertisement