I create a game in the terminal the premise(for now) is walking around a field collecting points marked as $, and avoiding mines marked as !, the player is marked as @ and normal terrain marked as #.
the playing field consists of a bunch of boards, all the boards are the same size and the field is those boards connected in rectangular shape.
the game shows only the boards the player visited, so if you walk in an L shape across the board you will see only the left column and the bottom row.
when a player collects a $ it will reset the visible boards so the player wont know where the next $ spawned.
when the player steps on a mine he takes damage and when it reaches 0, the game end showing the amount of points he collected, for now I made the player to lose from 1 mine because I haven't implemented mine removal without which it will cause the player take damage as long a he stands on the mine.
here's the code:
Point class
serves storing location of different entities.
stores a board number to know which, it is needed because every board uses coordinates of its own for example a 2*2 board will have (0,0),(1,0),(0,1),(1,1) and the next board will have those same coordinates.
header
#pragma once
class Point
{
private:
int boardNum;
int x;
int y;
public:
Point();
Point(int boardNum, int x, int y);
~Point();
bool operator == (const Point& point);
int GetBoardNum();
int GetX();
int GetY();
void SetBoardNum(int _boardNum);
void SetX(int _x);
void SetY(int _y);
};
cpp
#include "Point.h"
Point::Point()
{
boardNum = 0;
x = 0;
y = 0;
}
Point::Point(int boardNum, int x, int y) : boardNum(boardNum), x(x), y(y)
{
}
Point::~Point()
{
}
bool Point::operator == (const Point& point)
{
return boardNum == point.boardNum && x == point.x && y == point.y;
}
int Point::GetBoardNum()
{
return boardNum;
}
int Point::GetX()
{
return x;
}
int Point::GetY()
{
return y;
}
void Point::SetBoardNum(int _boardNum)
{
boardNum = _boardNum;
}
void Point::SetX(int _x)
{
x = _x;
}
void Point::SetY(int _y)
{
y = _y;
}
Player
the player class stores a location as a Point, health which is currently not really in use and the Move method which changes the location based on the direction.
header
#pragma once
#include "Includes.h"
#include "Point.h"
class Player
{
private:
Point currentLocation;
int health;
public:
Player();
Player(int health);
~Player();
int GetX();
int GetY();
void SetX(int x);
void SetY(int y);
int GetCurrentBoard();
int GetHealth();
Point GetCurrentLocation();
void TakeDamage(int damageAmount);
void SetCurrentBoard(int boardNum);
void Move(int direction);
void ResetPlayer(int _health);
};
cpp
#include "Player.h"
Player::Player() : currentLocation(0, 1, 1)
{
health = 3;
}
Player::Player(int health) : health(health), currentLocation(0, 1, 1)
{
}
Player::~Player()
{
}
int Player::GetX()
{
return currentLocation.GetX();
}
int Player::GetY()
{
return currentLocation.GetY();
}
void Player::SetX(int x)
{
currentLocation.SetX(x);
}
void Player::SetY(int y)
{
currentLocation.SetY(y);
}
int Player::GetCurrentBoard()
{
return currentLocation.GetBoardNum();
}
Point Player::GetCurrentLocation()
{
return currentLocation;
}
void Player::SetCurrentBoard(int field)
{
currentLocation.SetBoardNum(field);
}
int Player::GetHealth()
{
return health;
}
void Player::TakeDamage(int damageAmount)
{
health -= damageAmount;
}
void Player::Move(int direction) //changes the location based on the direction
{
switch (direction)
{
case KEY_UP:
currentLocation.SetY(currentLocation.GetY() - 1);
break;
case KEY_DOWN:
currentLocation.SetY(currentLocation.GetY() + 1);
break;
case KEY_RIGHT:
currentLocation.SetX(currentLocation.GetX() + 1);
break;
case KEY_LEFT:
currentLocation.SetX(currentLocation.GetX() - 1);
break;
default:
break;
}
}
void Player::ResetPlayer(int _health)
{
currentLocation.SetBoardNum(0);
currentLocation.SetX(1);
currentLocation.SetY(1);
health = _health;
}
GameField
the GameField class represents the boards and is manipulating the board for example prints only the boards that the player visited, generating new mines and $ and a bit more.
I believe the comments in my code explain each method pretty good so I wont be doing it here but if you have any questions I will answer.
I also added a random number generator outside of the class.
header
#pragma once
#include "Player.h"
#include <vector>
#include <set>
#include <algorithm>
#include <iostream>
#include <random>
#include <tuple>
class GameField
{
private:
int boardLength; // board and field dimansions
int boardWidth;
int fieldLength;
int fieldWidth;
std::vector<std::vector<char>> board; // a single board
std::vector<std::vector<std::vector<char>>> boardList; // list
std::vector<std::vector<std::vector<char>>> initialBoardList; // a representaion of the field but without the player @ absrating the point he stands on
Point collectibleLocation; // the board and coordinate for a $ to collect
std::vector<Point> mines; // the board and coordinate for a mine to appear
std::set<int> visibleBoards; // board numbers that are visible to the player
Player& player;
public:
GameField(int boardWidth, int boardLength, int fieldLength, int fieldWidth, Player& player);
~GameField();
void UpdateBoard(); //updates the board based on changes made
void PrintBoard();
std::vector<std::vector<char>> GetBoard();
std::vector<std::vector<std::vector<char>>> GetBoardList();
std::vector<std::vector<std::vector<char>>> GetInitialBoardList();
void SetPreviousBoard(int boardNum)
{
boardList.at(boardNum) = initialBoardList.at(boardNum);
}
// getters
int GetBoardLength();
int GetBoardWidth();
int GetFieldLength();
int GetFieldWidth();
Player GetPlayer();
Point GetCollectiobleLocation();
std::vector<Point> GetMines();
void AddVisibleBoard(int board); // adds a new visible board the player just visited
void GenNewPoint(); // generates a new mine and a new $
void ResetVisibleBoards();
};
int GenRandomNumber(int start, int end);
cpp
#include "GameField.h"
int GenRandomNumber(int start, int end)
{
std::random_device rd; // obtain a random number from hardware
std::mt19937 gen(rd()); // seed the generator
std::uniform_int_distribution<> distr(start, end); // define the range
return distr(gen);
}
GameField::GameField(int boardWidth, int boardLength, int fieldLength, int fieldWidth, Player& player) :
player(player), boardLength(boardLength), boardWidth(boardWidth), fieldLength(fieldLength), fieldWidth(fieldWidth)
{
for (int i = 0; i < boardLength; i++)
{
std::vector<char> line;
for (int j = 0; j < boardWidth; j++)
{
line.push_back('#');
}
board.push_back(line);
}
for (int i = 0; i < fieldLength; i++)
{
for (int j = 0; j < fieldWidth; j++)
{
boardList.push_back(board);
}
}
initialBoardList = boardList;
GenNewPoint();
boardList.at(collectibleLocation.GetBoardNum()).at(collectibleLocation.GetX()).at(collectibleLocation.GetY()) = '$'; // puts the $ on the random locationg provided bu GenNewPoint()
visibleBoards.insert({0}); // the first visible board is 0 thats where the player starts
boardList.at(0).at(player.GetY()).at(player.GetX()) = '@'; // places an indicator for the player
}
GameField::~GameField()
{
}
void GameField::GenNewPoint() // generates a new $ point and a mine at a random location on the board
{
initialBoardList.at(collectibleLocation.GetBoardNum()).at(collectibleLocation.GetX()).at(collectibleLocation.GetY()) = '#'; // erases the previous $ point that the player collected
collectibleLocation.SetBoardNum(GenRandomNumber(0, fieldLength * fieldWidth - 1)); // generating random board number to win
collectibleLocation.SetX(GenRandomNumber(0, boardLength - 1)); // generating x and y for the wining position
collectibleLocation.SetY(GenRandomNumber(0, boardWidth - 1));
initialBoardList.at(collectibleLocation.GetBoardNum()).at(collectibleLocation.GetX()).at(collectibleLocation.GetY()) = '$'; //places the $ on the field
Point* mineToAdd;
do
mineToAdd = new Point(GenRandomNumber(0, fieldLength * fieldWidth - 1), GenRandomNumber(0, boardLength - 1), GenRandomNumber(0, boardWidth - 1));
while (*mineToAdd == collectibleLocation || std::count(mines.begin(), mines.end(), *mineToAdd) || player.GetCurrentLocation() == *mineToAdd); // generates a mine until it wont collide with $, another mine or player position
mines.push_back(*mineToAdd); // adds the mine to the vector so with every point collected more and more mines are generated
initialBoardList.at(mineToAdd->GetBoardNum()).at(mineToAdd->GetY()).at(mineToAdd->GetX()) = '!'; // adds the indicator for the mine
}
void GameField::ResetVisibleBoards()
{
visibleBoards.clear(); //resets visible board memory
visibleBoards.insert(player.GetCurrentBoard()); // adds the current player board back to memory
}
void GameField::AddVisibleBoard(int board)
{
visibleBoards.insert({ board }); // adds a new visible board a player can see happens when a player visits a new board
}
void GameField::UpdateBoard()
{
int currentBoard = player.GetCurrentBoard();
for (int i = 0; i < boardList.at(currentBoard).size(); i++)
{
for (int j = 0; j < boardList.at(currentBoard).at(i).size(); j++)
{
boardList.at(currentBoard).at(i).at(j) = initialBoardList.at(currentBoard).at(i).at(j); // reset the current field to original without the player
}
}
boardList.at(currentBoard).at(player.GetY()).at(player.GetX()) = '@'; // place the player again at the new position
}
int GameField::GetBoardLength()
{
return boardLength;
}
int GameField::GetBoardWidth()
{
return boardWidth;
}
void GameField::PrintBoard()
{
UpdateBoard(); // updates the board if any changes were made
for (int i = 0; i < boardList.size(); i += fieldWidth) // runs on the field and prints the ones that the player visited and space on the one that the player did not
{
for (int j = 0; j < boardList.at(0).size(); j++)
{
for (int k = i; k < i + fieldWidth; k++)
{
for (int l = 0; l < boardList.at(l).at(j).size(); l++)
{
if (!std::binary_search(visibleBoards.begin(), visibleBoards.end(), k))
std::cout << ' ';
else
std::cout << boardList.at(k).at(j).at(l);
}
}
std::cout << '\n';
}
}
}
//Getters
int GameField::GetFieldLength()
{
return fieldLength;
}
int GameField::GetFieldWidth()
{
return fieldWidth;
}
std::vector<Point> GameField::GetMines()
{
return mines;
}
Point GameField::GetCollectiobleLocation()
{
return collectibleLocation;
}
std::vector<std::vector<char>> GameField::GetBoard()
{
return board;
}
std::vector<std::vector<std::vector<char>>> GameField::GetBoardList()
{
return boardList;
}
std::vector<std::vector<std::vector<char>>> GameField::GetInitialBoardList()
{
return initialBoardList;
}
Player GameField::GetPlayer()
{
return player;
}
Rogue
that's the name of the game (I stole it from that one old game that runs in the terminal).
here is the main function, and two functions one for clearing the screen as System("cls") was too slow and the terminal flickered although it still is but less, and the second one to remove the mouse writing flickering thingy, I have no idea how either of those two work I just copied them from the internet.
I also added 2 functions to check if player moved from one board to another and check if the move the player is legal basically checking the boundaries of the field.
here I create an instance of a player, field and points the player collected.
an endless while loop (or until the player steps on a mine).
first I check if the player is on a mine, the if the player lost, then if a player picked a $ if he did the I generate new mine and $ and reset all the visible boards to the player, and at last I get the key the player pressed, I check if the move is legal, inside I check if the player did not switch boards if he didn't he moves in the direction, if he did the MoveBoard function moves him to a new board.
MoveBoard functiong takes in fieldm player and the direction, checks the width and length of a board, remembers the previous board number (it is current but it will become previous if the player moves boards) and a boolean value that checks if the player did moved boards, based on the direction it checks if the player at the edge of that board in that direction if so it moves the player boards, in the end it returns if the player did moved boards.
CheckIfMovementLegal if the player trying to move outside the game field, the hard part for me here was knowing what edge the player is because im storing the boards in a single dimention vector.
cpp
#include <stdlib.h>
#include <iostream>
#include <conio.h>
#include<windows.h>
#include "GameField.h"
#include "Includes.h"
void cls();
bool CheckIfMovementLegal(GameField& field, Player& player, int direction);
bool MoveBoard(GameField& field, Player& player, int direction);
void ShowConsoleCursor(bool showFlag);
int main()
{
ShowConsoleCursor(false);
Player player;
int playerHealth = player.GetHealth();
GameField field(5, 5, 3, 3, player);
field.PrintBoard();
int points = 0;
while (true)
{
std::vector<Point> mines = field.GetMines();
if (std::count(mines.begin(), mines.end(), player.GetCurrentLocation())) // checks if a player stepped on a mine
player.TakeDamage(3);
if (player.GetHealth() <= 0)
{
cls();
std::cout << "You finished with " << points << " points. GG!" << '\n';
std::cout << "type 'r' to restart or 'q' to quite" << '\n';
std::string userInput;
std::cin >> userInput;
if (userInput == "r")
{
cls();
player.ResetPlayer(playerHealth);
field.GenNewPoint();
field.ResetVisibleBoards();
field.PrintBoard();
continue;
}
else
break;
}
if (field.GetCollectiobleLocation() == player.GetCurrentLocation()) // check if a player picked a $
{
field.GenNewPoint(); // generate new $ and mine
field.ResetVisibleBoards(); // remove memory of visible boards
cls();
field.PrintBoard();
points++;
}
int direction = _getch();
if (CheckIfMovementLegal(field, player, direction)) //checks if movement is legal then if need to be board switch and then moves player in direction
{
if (!MoveBoard(field, player, direction))
player.Move(direction);
cls();
field.PrintBoard();
}
}
return 0;
}
bool MoveBoard(GameField& field, Player& player, int direction)
{
int width = field.GetBoardList().at(player.GetCurrentBoard()).size();
int length = field.GetBoardList().at(player.GetCurrentBoard()).at(0).size();
int previousBoardNum = player.GetCurrentBoard();
bool isMovedToNewBoard = false;
switch (direction) // checks if player on the edge of the current boards if so then move to the next board in that direction
{
case KEY_UP:
if (player.GetY() == 0)
{
isMovedToNewBoard = true;
field.SetPreviousBoard(previousBoardNum);
int newBoardNum = player.GetCurrentBoard() - field.GetFieldLength();
field.AddVisibleBoard(newBoardNum);
player.SetCurrentBoard(newBoardNum);
player.SetY(width - 1);
}
break;
case KEY_DOWN:
if (player.GetY() == width - 1)
{
isMovedToNewBoard = true;
field.SetPreviousBoard(previousBoardNum);
int newBoardNum = player.GetCurrentBoard() + field.GetFieldLength();
field.AddVisibleBoard(newBoardNum);
player.SetCurrentBoard(newBoardNum);
player.SetY(0);
}
break;
case KEY_RIGHT:
if (player.GetX() == length - 1)
{
isMovedToNewBoard = true;
field.SetPreviousBoard(previousBoardNum);
int newBoardNum = player.GetCurrentBoard() + 1;
field.AddVisibleBoard(newBoardNum);
player.SetCurrentBoard(newBoardNum);
player.SetX(0);
}
break;
case KEY_LEFT:
if (player.GetX() == 0)
{
isMovedToNewBoard = true;
field.SetPreviousBoard(previousBoardNum);
int newBoardNum = player.GetCurrentBoard() - 1;
field.AddVisibleBoard(newBoardNum);
player.SetCurrentBoard(newBoardNum);
player.SetX(length - 1);
}
break;
default:
break;
}
return isMovedToNewBoard;
}
bool CheckIfMovementLegal(GameField& field, Player& player, int direction)
{
int fieldLength = field.GetFieldLength();
int fieldWidth = field.GetFieldWidth();
int currentBoard = player.GetCurrentBoard();
int boardWidth = field.GetBoard().size();
int boardLength = field.GetBoard().at(0).size();
bool flag = true;
switch (direction) // checks if player on the edge of the field if so check if player on the edge of current board (boards are set in a single linear vector)
{
case KEY_UP:
for (int i = 0; i < fieldLength; i++)
{
if (i == currentBoard)
{
flag = false;
break;
}
}
if (flag)
return true;
if (player.GetY() > 0)
return true;
break;
case KEY_DOWN:
for (int i = (fieldLength * fieldWidth) - 1; i > (fieldLength * fieldWidth) - fieldLength - 1; i--)
{
if (i == currentBoard)
{
flag = false;
break;
}
}
if (flag)
return true;
if (player.GetY() < boardWidth - 1)
return true;
break;
case KEY_LEFT:
for (int i = 0; i < fieldLength * fieldWidth; i += fieldLength)
{
if (i == currentBoard)
{
flag = false;
break;
}
}
if (flag)
return true;
if (player.GetX() > 0)
return true;
break;
case KEY_RIGHT:
for (int i = fieldLength - 1; i < fieldLength * fieldWidth; i += fieldLength)
{
if (i == currentBoard)
{
flag = false;
break;
}
}
if (flag)
return true;
if (player.GetX() < boardLength - 1)
return true;
break;
default:
break;
}
return false;
}
void ShowConsoleCursor(bool showFlag)
{
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(out, &cursorInfo);
cursorInfo.bVisible = showFlag; // set the cursor visibility
SetConsoleCursorInfo(out, &cursorInfo);
}
void cls()
{
// Get the Win32 handle representing standard output.
// This generally only has to be done once, so we make it static.
static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
COORD topLeft = { 0, 0 };
// std::cout uses a buffer to batch writes to the underlying console.
// We need to flush that to the console because we're circumventing
// std::cout entirely; after we clear the console, we don't want
// stale buffered text to randomly be written out.
std::cout.flush();
// Figure out the current width and height of the console window
if (!GetConsoleScreenBufferInfo(hOut, &csbi)) {
// TODO: Handle failure!
abort();
}
DWORD length = csbi.dwSize.X * csbi.dwSize.Y;
DWORD written;
// Flood-fill the console with spaces to clear it
FillConsoleOutputCharacter(hOut, TEXT(' '), length, topLeft, &written);
// Reset the attributes of every character to the default.
// This clears all background colour formatting, if any.
FillConsoleOutputAttribute(hOut, csbi.wAttributes, length, topLeft, &written);
// Move the cursor back to the top left for the next sequence of writes
SetConsoleCursorPosition(hOut, topLeft);
}
oh and the includes.h is basically the directions values I only use arrow keys for the game and I need those numbers inside the Player class and the Rogue.cpp.
Includes
header
#pragma once
#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_LEFT 75
#define KEY_RIGHT 77
hope to see where can I improve the readability, and the structure itself I removed a bunch of useless code before posting but maybe there is still more, also Ill be happy to here ideas that I can add to the game itself, Thanks.