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;
}
GameRules()
,TurnOptions()
, andTurnChoice()
. 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 doif (userChoice == 'y')
rather thanif( userChoice == 'y' )
. To me, the lineif( monsterinfo::monsterAmountDesired > ( fieldinfo::fieldLength - 2 ) * ( fieldinfo::fieldWidth - 2 ) - 9 )
becomes harder to read. \$\endgroup\$