GameState, is this a good practice?

Started by
11 comments, last by nunobarao 9 years, 4 months ago
Hi, I stepped back and I'm trying to implement OOP in the guessing number game, I'm trying to do a menu, and at end of level user return to menu or go to the next level or replay level, i do not have levelProgress yet and do not know if reseting variables like i´m doing is the best way of doing things.

This is what i got so far:

#include"MainGame.h"

int main(int argc, char** argv)
{
    MainGame mainGame;
    mainGame.run();

    return 0;
}

MainGame.cpp:


#include"MainGame.h"
#include<iostream>
#include<ctime>
#include <random>
#include<string>

MainGame::MainGame() :
    _playerInput(0),
    _secretNumber(0),
    _playerTries(5),
    _isRunning(true),
    _gameState(GameState::MENU)
{
    
}

MainGame::~MainGame()
{

}

void MainGame::run()
{
    //Initialize game state machine
    stateMachine();
}

void MainGame::stateMachine()
{
    while (_gameState != GameState::EXIT)
    {
        updateState();
    }
    //Debug purposes
    std::cout << "end of stateMachine loop\n";
    system("pause");
    
}

void MainGame::updateState()
{
    switch (_gameState){
    case GameState::PLAY:
        gameRun();
        break;
    case GameState::MENU:

        mainMenuDisplay();

        processInput();

        mainMenuDecision();

        break;
    case GameState::EXIT:
        
        break;
    }
}

void MainGame::gameRun()
{
    clearScreen();

    initLevel();

    generateSecretNumber(1, 10);

    gameLoop();
}

void MainGame::mainMenuDisplay()
{    
    displayText("***MainMenu***\n1->Play\n2->Exit\n");
}

void MainGame::mainMenuDecision()
{
    if (_playerInput == 1)
    {
        _gameState = GameState::PLAY;
    }

    if (_playerInput == 2)
    {
        _gameState = GameState::EXIT;
    }
}

void MainGame::initLevel()
{
    _levels.push_back(new Level("Levels/level1.txt"));
    _currentLevel = 0;
    
}

void MainGame::generateSecretNumber(int min, int max)
{    
    // Seed the random number generator
    srand(static_cast<unsigned int>(time(NULL)));

    //secretNumber is a number between min and max i.e(1,10)
    _secretNumber = (rand() % max) + min;
}

void MainGame::gameLoop()
{    
    // Main loop
    while (_isRunning)
    {
        updatePlayerTries();

        processInput();

        clearScreen();
        
        checkVictory();

        giveHint();
    }
    
    //Reset all variables
    _isRunning = true;
    _playerTries = 5;
    _gameState = GameState::MENU;

    //Debug purposes
    std::cout << "End of gameloop loop\n";
    system("pause");
    clearScreen();
}

void MainGame::checkVictory()
{
    // Check if player wins, lose, low, high guess
    if (_playerTries == 0 && _playerInput != _secretNumber){
        displayText("***Game Over ***\n");
        _isRunning = false;
    }
    
    if (_playerInput == _secretNumber) {
        // Print victory message
        displayText("*** You win! ***\n");
        _isRunning = false;
    }
}

void MainGame::giveHint()
{    
    // Giving a hint if guess is low
    if (_playerInput < _secretNumber && _isRunning == true){
        displayText("*** Your guess was to low! ***\n");
    }

    // Giving a hint if guess is high
    if (_playerInput > _secretNumber && _isRunning == true){
        displayText("*** Your guess was to high! ***\n");
    }
}

void MainGame::processInput()
{
    //Get player input guess
retry:
    if (_gameState == GameState::PLAY)
    {
        displayText("Make your guess: ");
    }
    std::cin >> _playerInput;

    // we need to ignore anything non-number so that not cause any bug or failure
    if (std::cin.fail()){
        
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        
        //print warning to player to input a number
        displayText("you need to input a number\n");

        //If player input is non numeric we goto retry;
        goto retry;
    }

}

void MainGame::updatePlayerTries(){

    if (_playerTries != 0){
        //Display tries remaining
        displayText("Tries remaining: " + std::to_string(_playerTries) + "\n");
        _playerTries--;
    }
    
}

void MainGame::displayText(std::string messageDisplay)
{    
    std::cout << messageDisplay;
}

void MainGame::clearScreen()
{
    system("CLS");
}

MainGame.h:


#pragma once
#include<vector>
#include"Level.h"

enum class GameState
{
	PLAY,
	MENU,
	EXIT
};

class MainGame
{
public:
	MainGame();
	~MainGame();

	void run();

	

private:

	//State Machine
	void stateMachine();

	//Update State
	void updateState();

	//Game run
	void gameRun();

	//MainMenu
	void mainMenuDisplay();

	//Main Menu Decision
	void mainMenuDecision();

	//Initialize the Level
	void initLevel();

	void generateSecretNumber(int min, int max);

	//Main gameLoop for our program
	void gameLoop();

	//Checks the victory condition
	void checkVictory();

	//We give hint to player
	void giveHint();

	//Handles input processing
	void processInput();

	//We check for player tries remaining
	void updatePlayerTries();

	//We display text
	void displayText(std::string messageDisplay);

	//Clean screen
	void clearScreen();

	int _playerInput;

	int _secretNumber;

	int _playerTries;

	int _currentLevel;

	bool _isRunning;

	//Vector of all levels
	std::vector<Level*> _levels; 

	GameState _gameState;
};

Level.cpp:


#include"Level.h"
#include<string>
#include<fstream>
#include<iostream>

Level::Level(const std::string& fileName)
{
    std::ifstream file;
    file.open(fileName);

    // Error checking
    if (file.fail()) {
        std::cout << "Failed to open " + fileName + "\n";
    }

    std::string tmp;

    // Read the level data
    while (std::getline(file, tmp)) {
        _levelData.emplace_back(tmp);
    }

    for (int i = 0; i < _levelData.size(); i++){
        std::cout << _levelData[i] << "\n";
    }
}

Level::~Level()
{

}

Level.h:


#pragma once
#include<string>
#include<vector>

class Level
{
public:
	Level(const std::string& fileName);
	~Level();

private:
	std::vector<std::string> _levelData;
};

if anyone provide me with some help i appreciate it.

Thank you

Advertisement
Hi nunobarao. Good attempt.

There is a great heuristic to Object Oriented design called The single responsibility principle. Your current MainGame class has multiple responsibilities: handling the menu, running the game logic, managing levels. It isn't clear to me what the "level" is in this game, the concept appears not to be used.

Some people like resetting variables, others do not. Consider that both std::ifstream and std::string allow you to reset the object's state to being "fresh". However, with such a model comes complexity. I prefer to write classes to be used once, rather than re-used, unless that proves necessary.

Note that you will struggle to come up with an Object Oriented design for such a small program. It is always going to be a little forced and over-engineered.

Note that not everything needs to be in an object. For example, seeding the random number generator should be done once at program startup. You can actually get less randomness to your results by reseeding, though it probably won't affect this game.

For a start attempt at least you didn't made a global game class — which it's good, but you can improve the readability and capacity of solving your problems with little change, opening doors to decide where to put game object instances.

You may want to build your general structure based on this article if you want to easy debug the game and remove all these game state switchs.

Note that the author mentioned (in that example) that the game state machine has only one state at time, and if you want one game state on the top of the other — like a pop-up or something — you'll need a stack of states, that can be implemented as a vector of states and just the last state gets updated, rendered, etc.

I'd say the L. Spiro Engine might be a little much for this. I'd recommend focusing on splitting your class up, not rewriting it to follow such a model (yet!).

I'll enter in contact with the community developers to see if they can try to implement one thing that may be good for the web-site: when I clicked without any intention in my Google® Chrome browser tab close button I lost all my reply information. I think maybe should be better to implement a functionality like a pop-up-before-close because it is the thirdy time I did that — my hand shakes a little bit sometimes (my country's temperature, etc.). Would be better to when I click in a button something like that should appear: "Would you like to leave the reply? You'll lost all your work.". Yes? No? etc. cool.png

Returning to the topic...

I'd say the L. Spiro Engine might be a little much for this. I'd recommend focusing on splitting your class up, not rewriting it to follow such a model (yet!).

Yeah, you're right. I tried to reply him to ease the things but I've lost all the reply.

Hi, I stepped back and I'm trying to implement OOP in the guessing number game, I'm trying to do a menu, and at end of level user return to menu or go to the next level or replay level, i do not have levelProgress yet and do not know if reseting variables like i´m doing is the best way of doing things.

Your game should have your level progress in this case. Any information that is persistent across all game states should be part of the game in your case, not only for organization purposes, but it is really the only way to do it — unless you're trying to pass everything from state to state.

while (_isRunning)
{
updatePlayerTries();

processInput();

clearScreen();

checkVictory();

giveHint();
}

You can refine your game loop and game logic by using the following sequence:


While ( QuitCondition ) {
     CheckPlayerInput();
     Update();
     ClearText();
     DrawText();
}

this way, when you have the persistent data inside the game class, and when it is time to draw something you can just grab it and print to the user that data; organization (for more boring that may look) it is essential. IMHO you're not wrong in dividing everything in small steps like CheckPlayerInput(), etc. but inserting such information in base functions like Update(), Draw(), etc. can be more readable.

#pragma once
#include
#include

class Level
{
public:
Level(const std::string& fileName);
~Level();

private:
std::vector _levelData;
};

Your level data it is holding a array of strings. Maybe would be probably better to insert some data into too, like the maximum tries of the player in the level, or the level name, etc. so when you load the level you can just keep a temporary string, write the level name to it, and when it is time to draw — in the DrawText() function — you draw the level's name, etc.

Dividing game data into structures (or classes) should be probably a good idea, like that:


struct NUMBER_GUESSING_GAME_DATA {
       BOOLEAN PlayerDied;
       BOOLEAN PlayerSkippedNumber;
       INTEGER PlayerScore;
       INTEGER PlayerLives;
       INTEGER CurrentLevel;
};

and pass the data to the game class instead of making a thousand of local variables, etc. Such refinement can be good for starters, and helps organize the code and give more space to new ideas, and put these ideas in the correct place.

Also, it is very normal to see a class having a function like Reset(), which resets the class to the default state that can be used anytime you want to reuse the class without having to re-constructing it in the memory. In your case, reseting the game could be probably setting the default game state, reseting the player score, etc. without having to re-run the game application, etc.

Hi nunobarao. Good attempt.

There is a great heuristic to Object Oriented design called The single responsibility principle. Your current MainGame class has multiple responsibilities: handling the menu, running the game logic, managing levels. It isn't clear to me what the "level" is in this game, the concept appears not to be used.

Some people like resetting variables, others do not. Consider that both std::ifstream and std::string allow you to reset the object's state to being "fresh". However, with such a model comes complexity. I prefer to write classes to be used once, rather than re-used, unless that proves necessary.

Note that you will struggle to come up with an Object Oriented design for such a small program. It is always going to be a little forced and over-engineered.

Note that not everything needs to be in an object. For example, seeding the random number generator should be done once at program startup. You can actually get less randomness to your results by reseeding, though it probably won't affect this game.

I know it's a little forced to do this little game oop, but it is to try to understand the concept of how things work and how this is a simple game hence the choice. thanks for the single responsibility principle speak, as far as I realized, each class should only contain things related to it, i saw an example: class book must only contain 'get book author, get book title, get current page, get nextpage etc. anything that does not belong to this class must be made a new class, am i correct?


Thank you

Essentially, yes, that is the idea. It depends on the program though. I don't have one of those fancy e-readers, but imagine one that allows for multiple user "profiles" (e.g. shared between a married couple). In such a program, the concept of a "current page" might be independent of the book. So you might have a book, a reader, and the "current page" might be a concept that exists between a particular reader and a particular book.

OO is about modelling for *your* program, so the right OO model for your program might look very different from a program that deals with similar objects. Amazon's idea of a book for their online store can very different from that of an e-reader.

Essentially, yes, that is the idea. It depends on the program though. I don't have one of those fancy e-readers, but imagine one that allows for multiple user "profiles" (e.g. shared between a married couple). In such a program, the concept of a "current page" might be independent of the book. So you might have a book, a reader, and the "current page" might be a concept that exists between a particular reader and a particular book.

OO is about modelling for *your* program, so the right OO model for your program might look very different from a program that deals with similar objects. Amazon's idea of a book for their online store can very different from that of an e-reader.

Yes i can get the point, but it leaves me with a question, this will make me to #include and mix classes just to get information that i need for example: while i create a object book in main, to access this object from a diferent class user to getcurrent page in this case how can i manage this situation?

maybe I'm exaggerating but i´m trying to get this rigth... single responsability principle in OOP :

Main.cpp:


#include<iostream>
#include<string>

#include"Room.h"
#include"Furniture.h"
#include"Shelf.h"
#include"Book.h"
#include"Printer.h"

void loadRoom();

int main()
{
	loadRoom();

	system("pause");
	return 0;
}

//Load Room
void loadRoom()
{
	//Create room object 
	Room library("Library", 2);

	//Lets say this Library have 2 cabinets and this cabinets store the information of cabinet name and number os shelfs, so we create 2 cabinets object
	Furniture cabinet1("Informatics", 3);
	Furniture cabinet2("Math", 3);

	//And for each cabinet we create those 3 shelfs objects that store information of shelf number and number of books?
	//Informatics cabinet shelfs
	Shelf informaticShelf1(1, 5);
	Shelf informaticShelf2(2, 3);
	Shelf informaticShelf3(3, 7);

	//Math cabinet shelfs
	Shelf mathShelf1(1, 3);
	Shelf mathShelf2(2, 8);
	Shelf mathShelf3(3, 2);

	//And for each shelf the number of books...?
	//just for simplicity sake
	//InformaticsShelf1
	Book iS1Book1("title","author");
	Book iS1Book2("title", "author");
	Book iS1Book3("title", "author");
	Book iS1Book4("title", "author");
	Book iS1Book5("title", "author");

	//InformaticsShelf2
	//...

	//MathShelf1
	Book mS1Book1("title", "author");
	Book mS1Book2("title", "author");
	Book mS1Book3("title", "author");

	//MathShelf2
	//...

	//I want to tell Printer to do this job... 
	//I can cout here but what if i want to cout in another function or class? what if i want to access those objects from another function or class?
	std::cout << "We are in the " << library.getName() << " , this room have " << library.getNumCabinets() << " cabinets.\n";
	std::cout << "One " << cabinet1.getName() << " cabinet, and one " << cabinet2.getName() << " cabinet.\n";
	std::cout << "Each cabinet as " << cabinet1.getNumShelfs() << " shelfs.\n";
	std::cout << cabinet1.getName() << " cabinet 1st shelf have " << informaticShelf1.getNumBooks() << " books.\n";
	std::cout << cabinet1.getName() << " cabinet 2nd shelf have " << informaticShelf2.getNumBooks() << " books.\n";
	std::cout << cabinet1.getName() << " cabinet 3rd shelf have " << informaticShelf3.getNumBooks() << " books.\n";
	std::cout << cabinet2.getName() << " cabinet 1st shelf have " << mathShelf1.getNumBooks() << " books.\n";
	std::cout << cabinet2.getName() << " cabinet 2nd shelf have " << mathShelf2.getNumBooks() << " books.\n";
	std::cout << cabinet2.getName() << " cabinet 3rd shelf have " << mathShelf3.getNumBooks() << " books.\n";
}

Room.h:


#pragma once
#include<string>

class Room
{
public:

	//Constuctor
	Room(std::string name, int numCabinets)
	{
		_name = name;
		_numCabinets = numCabinets;
	};

	//Destructor
	~Room(){};

	//Getters
	//Get name
	std::string getName(){return _name;}
	
	//Get numCabinets
	int getNumCabinets(){ return _numCabinets; }

	//Setters
	//Set name
	std::string setName(std::string name){ _name = name; }

	//Set numCabinets
	int setNumCabinets(int numCabinets){ _numCabinets = numCabinets; }

private:

	std::string _name;
	int _numCabinets;

};

Furniture.h:


#pragma once
#include<string>

class Furniture
{
public:
	
	//Constructor
	Furniture(std::string name, int numShelfs)
	{
		_name = name;
		_numShelfs = numShelfs;
	};

	//Destructor
	~Furniture(){};

	//Getters
	//Get name
	std::string getName(){ return _name; }
	
	//Get numShelfs
	int getNumShelfs(){ return _numShelfs; }

	//Setters
	//Set name
	std::string setName(std::string name){ _name = name; }
	
	//Set numShelfs
	int setNumShelfs(int numShelfs){ _numShelfs = numShelfs; }

private:

	std::string _name;
	int _numShelfs;

};

Shelfs.h:


#pragma once
#include<string>

class Shelf
{
public:

	//Constructor
	Shelf(int number, int numBooks)
	{
		_number = number;
		_numBooks = numBooks;
	};

	//Destructor
	~Shelf(){};

	//Getters
	//Get number
	int getNumber(){ return _number; }

	//Get numbooks
	int getNumBooks(){ return _numBooks; }

	//Setters
	//Set number
	int setNumber(int number){ _number = number; }

	//Set numbooks
	int setNumBooks(int numBooks){ _numBooks = numBooks; }

private:

	int _number;
	int _numBooks;

};

Book.h:


#pragma once
#include<string>

class Book
{
public:

	//Constructor
	Book(std::string title, std::string author)
	{
		_title = title;
		_author = author;
	};

	//Destructor
	~Book(){};

	//Getters
	//Get Title
	std::string getTitle(){ return _title; }

	//Get Author
	std::string getAuthor(){ return _author; }

private:

	std::string _title;
	std::string _author;

};

Thank you

Ok i think i got the answer, vectors... thank you .

This topic is closed to new replies.

Advertisement