Jump to content
  • Advertisement
Sign in to follow this  
cw

simple game example

This topic is 4117 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, here's a simple game I coded up for someone I'm helping. Maybe some of you can find it useful:
// A simple text-based game of hangman

// This code is public domain. You can do what you want with it.

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>


class Game {
	const std::string& word;
	std::string letters_picked;
	std::string remaining_letters;
	int chances_left;
	bool won_;

public:

	Game(const std::string& word, int chances):
		word(word), chances_left(chances), won_(false) {

		for(int i=0; i<word.length(); i++) {
			char letter = word;

			// Don't put duplicates in
			if(remaining_letters.find(letter, 0) == std::string::npos) {
				remaining_letters.push_back(letter);
			}
		}
	}

	// Print out the word with correct guesses filled in and everything else as blanks
	void print_word() {
		for(int i=0; i<word.length(); i++) {
			char letter = word;

			if(letters_picked.find(letter, 0) != std::string::npos) {
				std::cout << letter << " ";
			} else {
				std::cout << "_ ";
			}
		}
	}


	// Print each letter the user has picked, separated by spaces
	void print_letters_picked() {
		for(int i=0; i<letters_picked.length(); i++) {
			std::cout << letters_picked << " ";
		}
	}


	// Update the game with the given input
	void try_letter(char letter) {
		letters_picked.push_back(letter);

		// If the letter is correct, remove that letter from the remaining_letters
		// and check to see if they won.
		int pos = word.find(letter, 0);
		if(pos != std::string::npos) {
			remaining_letters.erase(0, pos);

			if(remaining_letters.length() == 0) {
				won_ = true;
			}
		} else {
			chances_left--;
		}
	}

	bool done() {
		return chances_left == 0 || won_;
	}

	bool won() {
		return won_;
	}

	int get_chances() {
		return chances_left;
	}
};


const char* word_list[] = {
	"pickles",
	"soup",
	"Curtis",
	"duck",
	"salad",
	"onions",
	"building",
	"tower",
	"tomato",
	"plate",
	"planet",
	"universe",
	"multiverse",
	"hyperbola",
	"displacement",
	"differential",
	"hypertension",
	"at",
	"the",
	"those",
	"people",
	"eat",
	"severed",
	"heads"
};

const int word_list_length = sizeof(word_list) / sizeof(word_list[0]);


// Print out the given word with spaces between each letter
void print_word(const std::string& word) {
	for(int i=0; i<word.length(); i++) {
		std::cout << word << " ";
	}
}


// Return whether we should start a new game or not
bool ask_continue() {
	while(true) {
		std::cout << std::endl << "Continue playing (y/n)? ";

		char response;
		std::cin >> response;

		if(response == 'y' || response == 'Y') {
			return true;
		} else if(response == 'n' || response == 'N') {
			return false;
		}
	}
}


// Play a single round of a game of hang man
void play(Game& game) {
	std::system("cls");

	game.print_word();

	std::cout << "     ";
	game.print_letters_picked();

	std::cout << std::endl << std::endl << "Body parts left: " << game.get_chances();

	std::cout << std::endl << std::endl << "Letter: ";
	char letter;
	std::cin >> letter;

	game.try_letter(letter);
}


int main() {
	std::srand( std::time(0) );

	bool continue_playing = false;
	
	do {
		// Pick a random word to use
		std::string word = word_list[ rand() % word_list_length ];

		Game game(word, 6);

		while(!game.done()) {
			play(game);
		}

		std::system("cls");

		print_word(word);

		std::cout 
			<< std::endl << std::endl
			<< (game.won() ? "Congratulations! You won." : "Congratulations! You lost.")
			<< std::endl;

		continue_playing = ask_continue();

	} while(continue_playing);

	return 0;
}




edit: STLified version courtesy of Zahlman: (Thanks!)
// A simple text-based game of hangman

// This code is public domain. You can do what you want with it.

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <iterator>

// Print out the given word with spaces between each letter
void print_word(std::ostream& where, const std::string& word) {
	std::copy(word.begin(), word.end(),
	          std::ostream_iterator<char>(where, " "));
}

class Game {
	const std::string word;
	std::string progress;
	std::string picked;
	int chances;

	bool won() const {
		return progress == word;
	}

	public:
	Game(const std::string& word, int chances):
	word(word), progress(word.length(), '_'),
	picked(), chances(chances) {}

	friend std::ostream& operator<<(std::ostream& os, const Game& g) {
		if (g.done()) {
			print_word(os, g.word);
			os << "\n\n"
			   << (g.won() ? "Congratulations! You won." :
			                 "Congratulations! You lost.")
			   << "\n";
		} else {
			print_word(os, g.progress);
			os << "     ";
			print_word(os, g.picked);
			os << "\n\nBody parts left: " << g.chances << "\n";
		}
		return os;
	}

	void guess(char letter) {
		bool found = false;
		picked.push_back(letter);
		// This can be done with standard library algorithms, but
		// requires more setup work than it's really worth, and won't
		// really generate any good reusable components.
		int len = progress.length();
		for (int i = 0; i < len; ++i) {
			if (word == letter) {
				found = true;
				progress = letter;
			}
		}
		if (!found) { --chances; }
	}

	bool done() const {
		return chances == 0 || won();
	}
};

// TODO: Read words in from a file.
const char* word_list[] = {
	"pickles",
	"soup",
	"Curtis",
	"duck",
	"salad",
	"onions",
	"building",
	"tower",
	"tomato",
	"plate",
	"planet",
	"universe",
	"multiverse",
	"hyperbola",
	"displacement",
	"differential",
	"hypertension",
	"at",
	"the",
	"those",
	"people",
	"eat",
	"severed",
	"heads"
};

const int word_list_length = sizeof(word_list) / sizeof(word_list[0]);

char get_letter() {
	std::string response;
	std::getline(std::cin, response);
	return (response + '\n')[0];
}

// Return whether we should start a new game or not
bool ask_continue() {
	while (true) {
		std::cout << "Continue playing (y/n)? ";
		switch (get_letter()) {
			case 'y': case 'Y': return true;
			case 'n': case 'N': return false;
		}
	}
}

int main() {
	std::srand(std::time(0));

	do {
		Game game(word_list[std::rand() % word_list_length], 6);

		while (true) {
			std::system("cls");
			std::cout << game;
			if (game.done()) { break; }
			std::cout	<< "\nLetter: ";
			game.guess(get_letter());
		}
	} while (ask_continue());
}

Very important: This example was meant to show the basic structure of a game. It uses a very minimal subset of the STL so as not to obfuscate this purpose. Do not code like this! Here's what I left out for this reason:
  • std::set should be used instead of a std::string for letters_picked and remaining_letters in Game.
  • All of the routines that use cout should accept a std::ostream as a parameter instead.
  • Iterators should always be used instead of indexes for STL containers.
  • The for loops to print out elements should be replaced with std::copy and ostream_iterators. Outputting the blanks for unknown letters should be done using std::replace--it might be handy to just do this in try_letter (Normally it makes more sense to just keep track of the incomplete word using an array with "holes" in it, but C++ makes it rather hard to do it the proper way).
(If someone wants to create a "proper" version with the above changes implemented, I'll gladly update this post and place it under the simplified version.) Some notes on the program design:
  • I intentionally used a crippled form of MVC for the Game class. The model and view were fused together for simplicity; I left the controller (play) out because doing so didn't add much complexity.
  • That being said, all of the print member functions should not normally be in the main class, unless removing them would add a large amount of complexity and if the flexibility gains aren't needed.
  • Each function was designed so that each statement would be at the same level of abstraction. Originally I had play embedded in main, which is an example of what not to do. (The Game constructor currently violates this, which can be remedied by using a std::set) If you want to know more about this, I recommend you buy Code Complete, an excellent book on programming style and code design.
Finally, I'd like to add that, unless you have a very good reason to C++, you should consider learning how to program using a different language. If you're already pretty far along in C++, I still suggest you only write a few games in C++, just to get a hang of the language, then switch to something more powerful (and simpler), such as python. If you know a decent amount of C++ already, I recommend Dive Into Python. Otherwise, take a look at How to Think Like a Computer Scientist. As always, google is your friend :) Hope this helps, Curtis edits: - Ugh, I hate 8 space tabs. - Linkified MVC. [Edited by - cw on June 11, 2007 11:54:04 AM]

Share this post


Link to post
Share on other sites
Advertisement
Very nice! This appears to be a wonderful learning source.
Now if you want to write one in C#, I wouldn't mind that either! :)

Share this post


Link to post
Share on other sites
Thanks for the code and the explenation, but i have a question. I just compiled it and found out that when i enter 'v' as a letter i always win :/ i've never used stl so i'm inexperienced with that. :)

Share this post


Link to post
Share on other sites
Thanks for the feedback, guys :)

Quote:
Original post by Oguz286
Thanks for the code and the explenation, but i have a question. I just compiled it and found out that when i enter 'v' as a letter i always win :/ i've never used stl so i'm inexperienced with that. :)

I don't know what to say, it works for me:




I'm compiling it with Visual C++ 2003, so it should work as long as you're not using VC++6. Are you're sure you actually won? It does say "Congratulations! You lost." when you lose.

Share this post


Link to post
Share on other sites
Quote:
Original post by cw
I'm compiling it with Visual C++ 2003, so it should work as long as you're not using VC++6. Are you're sure you actually won? It does say "Congratulations! You lost." when you lose.


Nope i was wrong about which letter it is. It really weird. If i get a wrong letter the numbers of limbs decreases by 1 (which is normal beheaviour) but when i get a good letter i win (and yes it says "congratulations, you won!"). I don't know what's causing it. By the way i'm compiling with VS2005 but it shouldn't make any difference.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oguz286
Quote:
Original post by cw
I'm compiling it with Visual C++ 2003, so it should work as long as you're not using VC++6. Are you're sure you actually won? It does say "Congratulations! You lost." when you lose.


Nope i was wrong about which letter it is. It really weird. If i get a wrong letter the numbers of limbs decreases by 1 (which is normal beheaviour) but when i get a good letter i win (and yes it says "congratulations, you won!"). I don't know what's causing it. By the way i'm compiling with VS2005 but it shouldn't make any difference.

I see. Maybe it got messed up when you copied and pasted it?

Share this post


Link to post
Share on other sites
Quote:
Original post by cw
I see. Maybe it got messed up when you copied and pasted it?


I copy pasted it again but to no avail. But sometimes it works as it's supposed to but sometimes i win when i still have a bunch of letters to go and i just guess one of them. Could it have something to do with memory? Because it doesn't misbehave everytime, but at random (or so it seems).

EDIT: It happened again with the word 'the'. First i entered 'e' and it appeared at the right place, then i entered 'h' and it showed 'the' and 'congratulations! you won.' without me entering 'the'. Same thing for 'hypertension', i just entered 'n' and i won immediatly. I'll see if i can find out why it doesn't always work.

Share this post


Link to post
Share on other sites
I also reproduced the problem. I believe the error is here:


remaining_letters.erase(0, pos);


This removes all letters from remaining_letters up until the found one (not including the found one) - not that letter itself.

Also, I don't think you're really allowed to hold the 'word' member by reference like that; the underlying referred-to word is a temporary (constructed from the char* selected out of the word list) and I don't believe its lifetime is guaranteed to be extended as needed.

This version tracks the user's progress by building a separate string containing underscores for unguessed letters and actual letters for guessed ones. We know we're done when this matches the original word. This actually simplifies the logic quite a bit. I also reuse the global print_word() functionality within the class (this is a good thing). Also, I have simplified the interface to the class: since all the "print" functions are called as a unit and there's no need to call them separately, they get joined together so that the caller only makes one call. (In general, you want to simplify your code - but while simplification of implementations often involves making more functions, simplifying the interface almost always involves making fewer - at least, fewer *public* ones).

I made a few other changes; feel free to review. I haven't done everything that might be done, and I may well have done things that not everyone would agree with ;)


// A simple text-based game of hangman

// This code is public domain. You can do what you want with it.

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <iterator>

// Print out the given word with spaces between each letter
void print_word(std::ostream& where, const std::string& word) {
std::copy(word.begin(), word.end(),
std::ostream_iterator<char>(where, " "));
}

class Game {
const std::string word;
std::string progress;
std::string picked;
int chances;

bool won() const {
return progress == word;
}

public:
Game(const std::string& word, int chances):
word(word), progress(word.length(), '_'),
picked(), chances(chances) {}

friend std::ostream& operator<<(std::ostream& os, const Game& g) {
if (g.done()) {
print_word(os, g.word);
os << "\n\n"
<< (g.won() ? "Congratulations! You won." :
"Congratulations! You lost.")
<< "\n";
} else {
print_word(os, g.progress);
os << " ";
print_word(os, g.picked);
os << "\n\nBody parts left: " << g.chances << "\n";
}
return os;
}

void guess(char letter) {
bool found = false;
picked.push_back(letter);
// This can be done with standard library algorithms, but
// requires more setup work than it's really worth, and won't
// really generate any good reusable components.
int len = progress.length();
for (int i = 0; i < len; ++i) {
if (word == letter) {
found = true;
progress = letter;
}
}
if (!found) { --chances; }
}

bool done() const {
return chances == 0 || won();
}
};

// TODO: Read words in from a file.
const char* word_list[] = {
"pickles",
"soup",
"Curtis",
"duck",
"salad",
"onions",
"building",
"tower",
"tomato",
"plate",
"planet",
"universe",
"multiverse",
"hyperbola",
"displacement",
"differential",
"hypertension",
"at",
"the",
"those",
"people",
"eat",
"severed",
"heads"
};

const int word_list_length = sizeof(word_list) / sizeof(word_list[0]);

char get_letter() {
std::string response;
std::getline(std::cin, response);
return (response + '\n')[0];
}

// Return whether we should start a new game or not
bool ask_continue() {
while (true) {
std::cout << "Continue playing (y/n)? ";
switch (get_letter()) {
case 'y': case 'Y': return true;
case 'n': case 'N': return false;
}
}
}

int main() {
std::srand(std::time(0));

do {
Game game(word_list[std::rand() % word_list_length], 6);

while (true) {
std::system("cls");
std::cout << game;
if (game.done()) { break; }
std::cout << "\nLetter: ";
game.guess(get_letter());
}
} while (ask_continue());
}

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!