7
\$\begingroup\$

A game I've created to implement everything I've learned and to further build upon. I just recently learned classes so I'm probably using them wrongly. Every bit of criticism is appreciated, I don't want to create bad habits.
Edit: My game draws up a field / map of symbols and characters which represents the playing field. It's turn-based and the goal of the game is to move the player ('P') to the exit on the other side of the playing field while avoid monsters ('M') who randomly walks around.

#include <iostream>
#include <time.h>
#include <vector>
#include <string>

class fieldinfo
{
    public:
        static int fieldLength;
        static int fieldWidth;
        static int fieldExitAmount;
        static std::vector<int> xPositionFieldExit;
        static std::vector<int> yPositionFieldExit;
};

class playerinfo
{
    public:
        static int xPosition;
        static int yPosition;
};

class monsterinfo
{
    public:
        static std::vector<int> xPosition;
        static std::vector<int> yPosition;
        static int monsterAmountDesired;
        static int monsterAmountCurrent;
};

int fieldinfo::fieldLength = 10;    // Default fieldsize.
int fieldinfo::fieldWidth = 10;     // Default fieldsize.
int fieldinfo::fieldExitAmount;
std::vector<int> fieldinfo::xPositionFieldExit;
std::vector<int> fieldinfo::yPositionFieldExit;

int playerinfo::xPosition;
int playerinfo::yPosition;

int monsterinfo::monsterAmountDesired = 0;
int monsterinfo::monsterAmountCurrent = 0;
std::vector<int> monsterinfo::xPosition;
std::vector<int> monsterinfo::yPosition;

void GameRules( );
void SetFieldSize( );
void SetFieldExits( );
void SetPlayerPosition( );
void SetMonsterAmount( );
void RandomizeMonsterPositions( );
bool CheckForSpawnNearPlayer( int xPos, int yPos );
bool CheckForFieldExit( int xPos, int yPos );
bool CheckForFieldWall( int xPos, int yPos );
bool CheckForPlayerPosition( int xPos, int yPos );
bool CheckForMonsterPosition( int xPos, int yPos );
void CheckForWinCondition( );
void CheckForLoseCondition( );
void DrawField( );
void TurnOptions( );
void TurnChoice( );
void RandomMonsterMovement( );
void PreventAutoExit( );
bool CheckUnvaildIntegerInput( std::string input );


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

    GameRules( );
    SetFieldSize( );
    SetFieldExits( );
    SetPlayerPosition( );
    SetMonsterAmount( );
    RandomizeMonsterPositions( );

    while( true )
    {
        DrawField( );
        CheckForWinCondition( );
        CheckForLoseCondition( );
        TurnOptions( );
        TurnChoice( );
        RandomMonsterMovement( );
    }

    return 0;
}

void GameRules( )
{
    std::cout << "Win  condition: Enter the exit.\n";
    std::cout << "Lose condition: Enter the same space as a monster.\n\n";
}

void SetFieldSize( )
{
    char userChoice = 'n';

    std::cout << "Would you like to determine the playing field, [y/n]: ";
    std::cin >> userChoice;

    if( userChoice == 'y' )
    {
        while( true )   // Defensive programming.
        {
            std::string inputLength;
            std::string inputWidth;

            std::cout << std::endl;
            std::cout << "Enter the playing field length: ";
            std::cin >> inputLength;
            std::cout << "Enter the playing field width: ";
            std::cin >> inputWidth;

            if( CheckUnvaildIntegerInput( inputLength ) == true ||
                CheckUnvaildIntegerInput( inputWidth ) == true )
            {
                fieldinfo::fieldLength = atoi( inputLength.c_str( ) );  // Convert string to integer.
                fieldinfo::fieldWidth = atoi( inputWidth.c_str( ) );    // Convert string to integer.
                break;
            }
            else
            {
                std::cout << "\nInvaild input, try again.\n";
            }
        }
    }
}

void SetFieldExits( )
{
    fieldinfo::fieldExitAmount = 1;
    fieldinfo::xPositionFieldExit.push_back( 1 );
    fieldinfo::yPositionFieldExit.push_back( 0 );
}

void SetPlayerPosition( )
{
    playerinfo::xPosition = fieldinfo::fieldWidth - 2;
    playerinfo::yPosition = fieldinfo::fieldLength - 2; // Bottom right.
}

void SetMonsterAmount( )
{
    while( true )   // Defensive programming.
    {
        std::string input;

        std::cout << "\nEnter amount of monsters: ";
        std::cin >> input;

        if( CheckUnvaildIntegerInput( input ) == true )
        {
            monsterinfo::monsterAmountDesired = atoi( input.c_str( ) ); // Convert string to integer. 

            if( monsterinfo::monsterAmountDesired > ( fieldinfo::fieldLength - 2 ) * ( fieldinfo::fieldWidth - 2 ) - 9 )
            {
                std::cout << "\nToo many monsters, try again.\n";
                continue;
            }

            break;
        }
        else
        {
            std::cout << "\nInvaild input, try again.\n";
        }
    }
}

void RandomizeMonsterPositions( ) 
{
    int xPosTemp, yPosTemp;

    for( int i = 0; i < monsterinfo::monsterAmountDesired; i++ )
    {
        while( true )
        {
            xPosTemp = 1 + ( rand( ) % ( fieldinfo::fieldWidth - 2 ) );     // Between 1 and 8 if default fieldWidth value (10).
            yPosTemp = 1 + ( rand( ) % ( fieldinfo::fieldLength - 2 ) );    // Between 1 and 8 if default fieldLength value (10).

            if( CheckForMonsterPosition( xPosTemp, yPosTemp ) == false &&
                CheckForSpawnNearPlayer( xPosTemp, yPosTemp ) == false &&
                CheckForPlayerPosition( xPosTemp, yPosTemp ) == false &&
                CheckForFieldWall( xPosTemp, yPosTemp ) == false )
            {
                break;
            }
        }

        if( monsterinfo::monsterAmountCurrent < monsterinfo::monsterAmountDesired )
        {
            monsterinfo::monsterAmountCurrent++;    // To avoid trying to access unvalid memory of monsterpositions before its allocated.
        }

        monsterinfo::xPosition.push_back( xPosTemp );
        monsterinfo::yPosition.push_back( yPosTemp );
    }
}

bool CheckForSpawnNearPlayer( int xPos, int yPos )
{
    int protectRange = 3;

    for( int yProtect = 0; yProtect < protectRange; yProtect++ )
    {
        for( int xProtect = 0; xProtect < protectRange; xProtect++ )
        {
            if( playerinfo::yPosition - yProtect == yPos &&
                playerinfo::xPosition - xProtect == xPos )
            {
                return true;
            }
        }
    }

    return false;
}

bool CheckForFieldExit( int xPos, int yPos )
{
    for( int i = 0; i < fieldinfo::fieldExitAmount; i++ )
    {
        if( xPos == fieldinfo::xPositionFieldExit[i] &&
            yPos == fieldinfo::yPositionFieldExit[i] )
        {
            return true;
        }
    }

    return false;
}

bool CheckForFieldWall( int xPos, int yPos )
{
    if( xPos == 0 || xPos == fieldinfo::fieldWidth - 1 ||
        yPos == 0 || yPos == fieldinfo::fieldLength - 1 )
    {
        return true;
    }

    return false;
}

bool CheckForPlayerPosition( int xPos, int yPos )
{
    if( xPos == playerinfo::xPosition &&
        yPos == playerinfo::yPosition )
    {
        return true;
    }

    return false;
}

bool CheckForMonsterPosition( int xPos, int yPos )
{
    for( int i = 0; i < monsterinfo::monsterAmountCurrent; i++ )
    {
        if( xPos == monsterinfo::xPosition[i] &&
            yPos == monsterinfo::yPosition[i] )
        {
            return true;
        }
    }

    return false;
}

void CheckForWinCondition( )
{
    if( CheckForFieldExit( playerinfo::xPosition, playerinfo::yPosition ) == true )
    {
        std::cout << "\nYou win!";
        PreventAutoExit( );
        exit( 0 );
    }
}

void CheckForLoseCondition( )
{
    if( CheckForMonsterPosition( playerinfo::xPosition, playerinfo::yPosition ) == true )
    {
        std::cout << "\nYou lose!";
        PreventAutoExit( );
        exit( 0 );
    }
}

void DrawField( )
{
    std::cout << std::endl;

    for( int yDraw = 0; yDraw < fieldinfo::fieldLength; yDraw++ )
    {
        for( int xDraw = 0; xDraw < fieldinfo::fieldWidth; xDraw++ )
        {
            if( CheckForFieldExit( xDraw, yDraw ) == true )
            {
                std::cout << "=";
            }
            else if( CheckForFieldWall( xDraw, yDraw ) == true )
            {
                std::cout << "#";
            }
            else if( CheckForMonsterPosition( xDraw, yDraw ) == true )
            {
                std::cout << "M";
            }
            else if( CheckForPlayerPosition( xDraw, yDraw ) == true )
            {
                std::cout << "P";
            }
            else
            {
                std::cout << "-";
            }
        }

        std::cout << std::endl;
    }
}

void TurnOptions( )
{
    std::cout << "\n";
    std::cout << "[1] Go up.\n";
    std::cout << "[2] Go down.\n";
    std::cout << "[3] Go left.\n";
    std::cout << "[4] Go Right.\n";
    std::cout << "[5] Exit game.\n";
    std::cout << "[N/A] Do nothing.\n";
}

void TurnChoice( )
{
    char userChoice;
    int yPosTemp, xPosTemp;

    std::cout << "\nYour choice: ";
    std::cin >> userChoice;
    switch( userChoice )
    {
        case '1':   // Move up.
        {
            yPosTemp = playerinfo::yPosition - 1; 

            if( CheckForFieldExit( playerinfo::xPosition, yPosTemp ) == true )  // Exits and Walls occupy the same positions.
            {
                playerinfo::yPosition = yPosTemp;
            }
            else if( CheckForFieldWall( playerinfo::xPosition, yPosTemp ) == false )
            {
                playerinfo::yPosition = yPosTemp;
            }

            break;
        }

        case '2':   // Move down.
        {
            yPosTemp = playerinfo::yPosition + 1;

            if( CheckForFieldExit( playerinfo::xPosition, yPosTemp ) == true )
            {
                playerinfo::yPosition = yPosTemp;
            }
            else if( CheckForFieldWall( playerinfo::xPosition, yPosTemp ) == false )
            {
                playerinfo::yPosition = yPosTemp;
            }

            break;
        }

        case '3':   // Move left.
        {
            xPosTemp = playerinfo::xPosition - 1;

            if( CheckForFieldExit( xPosTemp, playerinfo::yPosition ) == true )
            {
                playerinfo::yPosition = xPosTemp;
            }
            else if( CheckForFieldWall( xPosTemp, playerinfo::yPosition ) == false )
            {
                playerinfo::xPosition = xPosTemp;
            }

            break;
        }

        case '4':   // Move right.
        {
            xPosTemp = playerinfo::xPosition + 1;

            if( CheckForFieldExit( xPosTemp, playerinfo::yPosition ) == true )
            {
                playerinfo::yPosition = xPosTemp;
            }
            else if( CheckForFieldWall( xPosTemp, playerinfo::yPosition ) == false )
            {
                playerinfo::xPosition = xPosTemp;
            }

            break;
        }

        case '5':   // Exit game.
        {
            exit( 0 );
        }

        default:
        {
            break;
        }
    }
}

void RandomMonsterMovement( )
{
    int yPosTemp, xPosTemp;
    int randomTemp;

    for( int i = 0; i < monsterinfo::monsterAmountCurrent; i++ )
    {
        bool RetryLoop = true;

        do
        {
            randomTemp = 1 + ( rand( ) % 8 );

            switch( randomTemp )
            {
                case 1:
                {
                    yPosTemp = monsterinfo::yPosition[i] - 1;

                    if( CheckForMonsterPosition( monsterinfo::xPosition[i], yPosTemp ) == false &&
                        CheckForFieldWall      ( monsterinfo::xPosition[i], yPosTemp ) == false )
                    {
                        monsterinfo::yPosition[i] -= 1; // Monster goes up.
                        RetryLoop = false;
                    }

                    break;
                }

                case 2:
                {
                    yPosTemp = monsterinfo::yPosition[i] + 1;

                    if( CheckForMonsterPosition( monsterinfo::xPosition[i], yPosTemp ) == false &&
                        CheckForFieldWall      ( monsterinfo::xPosition[i], yPosTemp ) == false )
                    {
                        monsterinfo::yPosition[i] += 1; // Monster goes down.
                        RetryLoop = false;
                    }

                    break;
                }

                case 3:
                {
                    xPosTemp = monsterinfo::xPosition[i] - 1;

                    if( CheckForMonsterPosition( xPosTemp, monsterinfo::yPosition[i] ) == false &&
                        CheckForFieldWall      ( xPosTemp, monsterinfo::yPosition[i] ) == false )
                    {
                        monsterinfo::xPosition[i] -= 1; // Monster goes left.
                        RetryLoop = false;
                    }

                    break;
                }

                case 4:
                {
                    xPosTemp = monsterinfo::xPosition[i] + 1;

                    if( CheckForMonsterPosition( xPosTemp, monsterinfo::yPosition[i] ) == false &&
                        CheckForFieldWall      ( xPosTemp, monsterinfo::yPosition[i] ) == false )
                    {
                        monsterinfo::xPosition[i] += 1; // Monster goes right.
                        RetryLoop = false;
                    }

                    break;
                }

                case 5:
                case 6:
                case 7:
                case 8:
                {
                    for( int k = 0; k < monsterinfo::monsterAmountCurrent; k++ )    // Modified CheckForMonsterPosition, incase
                    {                                                               // another monster has moved to its position.
                        if( k == i )    // If it checks it's own position.
                        {
                            continue;
                        }
                        else if( monsterinfo::xPosition[i] == monsterinfo::xPosition[k] &&
                                 monsterinfo::yPosition[i] == monsterinfo::yPosition[k] )
                        {
                            break;
                        }
                        else if( k == monsterinfo::monsterAmountCurrent - 1 )
                        {
                            RetryLoop = false;  // Monster stands still.
                        }
                    }

                    break;
                }

                default:
                {
                    break;
                }
            }
        }
        while( RetryLoop );
    }
}

void PreventAutoExit( )
{
    std::cout << std::endl;
    std::cin.get( );
    std::cin.get( );
}

bool CheckUnvaildIntegerInput( std::string input )
{
    if( input.size( ) > 9 ) // Manual max int input: 999 999 999, roughly half of the max size for signed int.
    {
        return false;
    }
    for( unsigned int i = 0; i < input.size( ); i++ )   // Unsigned int to supress compile warning.
    {
        if( isdigit( input[i] ) == false )  // Check if all of inputed characters are digits, including minus.
        {
            return false;
        }
    }

    return true;
}
\$\endgroup\$
5
  • \$\begingroup\$ You assigned probably involved a use of the class static members. For the real program however the classes fieldinfo or playerinfo with all static members are unnecessary. Global variables would do just fine. \$\endgroup\$
    – cha
    Mar 30, 2015 at 22:37
  • \$\begingroup\$ Welcome to Code Review. This is a pretty good first post. I've posted an answer with some pointers, but my advice, if your primary goal is to really learn classes well, try to implement a RationalNumber class. It's a great way to actually learn what classes are and how they're supposed to be used. \$\endgroup\$
    – nhgrif
    Mar 30, 2015 at 23:47
  • \$\begingroup\$ I am sorry, but I am downvoting because I find that you don't provide much explanation about what your game is about, compared to how much code you provide. I don't want you to create a bad habit of this, so I hope you will provide more explanation and context of your code in the future. \$\endgroup\$ Mar 31, 2015 at 0:02
  • \$\begingroup\$ My only comment (because nhgrif covered the class thing) would be that some of your functions don't have meaningful names, such as GameRules(), TurnOptions(), and TurnChoice(). Function names should almost always contain, and usually start with, a verb. I also disagree with putting spaces inside parentheses, but that's a personal style decision. For example, I'd rather do if (userChoice == 'y') rather than if( userChoice == 'y' ). To me, the line if( monsterinfo::monsterAmountDesired > ( fieldinfo::fieldLength - 2 ) * ( fieldinfo::fieldWidth - 2 ) - 9 ) becomes harder to read. \$\endgroup\$ Mar 31, 2015 at 1:25
  • \$\begingroup\$ @Sohcahtoa82 I highly recommend you post an answer. There is a lot to cover in this code. I didn't even scroll but maybe 1/4th of the way through the posted code before I posted my answer. There's plenty of other topics to make full answers out of, including the stuff you've mentioned in this comment. \$\endgroup\$
    – nhgrif
    Mar 31, 2015 at 1:31

2 Answers 2

10
\$\begingroup\$

I just recently learned classes so I'm probably using them wrongly.

Yes. For this review, I intend to focus on that aspect of your code--your usage of classes.

First of all, if every member of our class is static, we almost certainly don't need a class. If everything is static, essentially what you want is a namespace. In this case, however, using a class might be correct, but using static for everything definitely is not.

I say that using a class might be correct, because some might argue that in your case you actually want a struct. I've seen the argument made that in C++, passive objects that just carry data should be structs while class should be reserved for object which define method you might call on them. I'm not a C++ master, and I'm not sure how many people follow this convention. For the sake of this answer, I'm going to assume we're okay using a class here rather than a struct.

A class should represent a thing (not info about a thing, but the thing). We should capitalize the names of classes, and the info suffix is unnecessary. For example, we want:

class Player {
    // stuff
}

An instance of this class represents a "player" and the "info" about that player is held in any instance variables we stick on here.


Timeout. Pitstop. We're using a lot of "xPosition" and "yPosition". Let's just make a "position" struct real quick, shall we? We'll also make a "size" struct while we're at it. This will simplify some things moving forward.

struct Position {
    int x;
    int y;
};

struct Size {
    int length;
    int width;
};

Okay, now that that's done, let's take a look out how our Field class might look:

class Field {
public:
    std::vector<Position> fieldExits;
    Size fieldSize;
    int exitAmount;
}

And our Player class?

class Player {
public:
    Position position;
}

Now, you will notice, nothing is static, right? So how do we use these?

When you declare a variable as static, that means only one copy of that variable exists for the class. Instead, what we want are "instance variables" (without the static keyword). What this means is we're going to create instances of our classes. The position variable on the Player class will be unique to each "player" we create.

For instance:

Player player1 = Player();
player1.position = {0,1};

Player player2 = Player();
player2.position = {5,7};

Player player3 = Player();
player3.position = {3,4};

Player player4 = Player();
player4.position = {2,7};

What have we done here?

Well, we've created four different variables of type Player. Each of these Player instances keep track of a Position. They each have their own unique position, and we've set this position for all four players individually.

Before though, with static prepended to all of our variables, we essentially had global variables that could be accessed through out the entire program. Now, we don't have that. You can only access player1's position if you can access the player1 object. So we'll need to pass the player1 object around... this will require a pretty major restructuring of your program however... just as a warning. This answer has just shown you the basics of the proper usage of classes--there is still much more to learn.

\$\endgroup\$
1
  • \$\begingroup\$ Where would I declare my struct object and how would I go about passing it to my functions? Originally I tried to use non static members of my classes, but I didn't understand how to properly store my values and pass them around so instead I made them static. \$\endgroup\$ Mar 31, 2015 at 0:27
5
\$\begingroup\$

A lot of these comments are what I call "school comments"--the sort of thing you see all too often in classwork but which aren't of much value in the real world.

A comment should not be telling you what the code is doing, it should be telling you why it's doing it.

Consider:

fieldinfo::fieldLength = atoi( inputLength.c_str( ) );  // Convert string to integer.

Duh! My C is buried in rust and I've never written C++ (I do not like the lack of type safety in either language) but even I can instantly see what this is doing--the comment serves no purpose.

Next:

while( true )   // Defensive programming.

This one isn't a school comment but I sure don't understand it. "Defensive programming" isn't a reason, it's too broad. What are you defending against??

You do have some good comments, though:

switch( userChoice )
{
    case '1':   // Move up.

This is what you should be doing with comments--it's telling you what's going on, not what the code is doing.

And I have a bit of a nit to pick with this:

monsterinfo::yPosition[i] -= 1; // Monster goes up.

The comment is good but I would have put it here:

switch( randomTemp )
{
    case 1:
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.