Add base game

This commit is contained in:
ethanglide 2024-07-04 16:57:11 -04:00
parent 85e4a03a90
commit 716ae24600
16 changed files with 520 additions and 11 deletions

12
.gitignore vendored
View File

@ -1,11 +1 @@
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
build

6
CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.0)
project(CppConsoleGame VERSION 1.0.0)
add_subdirectory(conio)
add_subdirectory(random-utils)
add_subdirectory(cpp-console-game)

11
conio/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
add_library(
conio
conio.hpp
conio.cpp
)
target_include_directories(
conio
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

30
conio/conio.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "conio.hpp"
#include <termios.h>
#include <unistd.h>
#include <iostream>
/**
* Get a character from the console without needing to press enter
*/
char conio::getch()
{
char c;
struct termios oldattr, newattr;
// Get the terminal settings
tcgetattr(STDIN_FILENO, &oldattr);
newattr = oldattr;
// Disable canonical mode and echo
newattr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
// Get the character, will not wait for enter because of the new settings
std::cin.get(c);
// Reset the terminal settings back to normal behaviour
tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
return c;
}

9
conio/conio.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef CONIO_HPP
#define CONIO_HPP
namespace conio
{
char getch();
}
#endif // CONIO_HPP

View File

@ -0,0 +1,55 @@
#include "Board.hpp"
#include <iostream>
namespace ConsoleGame
{
Board::Board(int width, int height, std::string fill) : fill(fill)
{
cells.resize(height);
for (int i = 0; i < height; i++)
{
cells[i].resize(width, fill);
}
}
void Board::draw()
{
std::cout << "\033[H"; // Move cursor to the top left corner
std::cout << "\033[2J"; // Clear the screen
for (auto row : cells)
{
for (auto cell : row)
{
std::cout << cell;
}
std::cout << std::endl;
}
}
void Board::setCell(int x, int y, std::string value)
{
cells[y][x] = value;
}
std::string Board::getCell(int x, int y)
{
return cells[y][x];
}
int Board::getWidth()
{
return cells[0].size();
}
int Board::getHeight()
{
return cells.size();
}
std::string Board::getFill()
{
return fill;
}
}

View File

@ -0,0 +1,32 @@
#ifndef BOARD_HPP
#define BOARD_HPP
#include <vector>
#include <string>
namespace ConsoleGame
{
class Board
{
public:
/**
* Construct a new Board object
* @param width The width of the board
* @param height The height of the board
* @param fill The string to fill the board with
*/
Board(int width, int height, std::string fill = "#");
void draw();
void setCell(int x, int y, std::string value);
std::string getCell(int x, int y);
int getWidth();
int getHeight();
std::string getFill();
private:
std::vector<std::vector<std::string>> cells;
std::string fill;
};
}
#endif // BOARD_HPP

View File

@ -0,0 +1,40 @@
add_library(
Board
Board.hpp
Board.cpp
)
add_library(
GameState
GameState.hpp
GameState.cpp
)
add_library(
Player
Player.hpp
Player.cpp
)
add_executable(
cpp-console-game
main.cpp
)
target_link_libraries(
cpp-console-game PRIVATE
GameState
conio
)
target_link_libraries(
GameState PRIVATE
Board
Player
RandomUtils
)
target_link_libraries(
Player PRIVATE
RandomUtils
)

View File

@ -0,0 +1,91 @@
#include "GameState.hpp"
#include <RandomUtils.hpp>
int idCounter = 0;
namespace ConsoleGame
{
GameState::GameState(int width, int height, std::string fill)
: board(width, height, fill),
running(true),
players() {}
int GameState::addPlayer()
{
std::lock_guard<std::mutex> lock(mutex);
// Add a player to the game at a random position
int x, y;
do
{
x = RandomUtils::rand(0, board.getWidth() - 1);
y = RandomUtils::rand(0, board.getHeight() - 1);
} while (board.getCell(x, y) != board.getFill());
// Increment the id counter and add the player to the map
int id = idCounter++;
players[id] = Player({x, y});
board.setCell(x, y, players[id].getFill());
return id;
}
void GameState::removePlayer(int id)
{
std::lock_guard<std::mutex> lock(mutex);
// Replace the player's position with a regular board cell
auto pos = players[id].getPosition();
board.setCell(pos.first, pos.second, board.getFill());
players.erase(id);
}
void GameState::movePlayer(int id, std::pair<int, int> newPos)
{
std::lock_guard<std::mutex> lock(mutex);
// Check if the new position is out of bounds
if (newPos.first < 0 || newPos.first >= board.getWidth() || newPos.second < 0 || newPos.second >= board.getHeight())
{
return;
}
// Replace the player's old position with a regular board cell
auto oldPos = players[id].getPosition();
board.setCell(oldPos.first, oldPos.second, board.getFill());
// Update the player's position and set the new position on the board
players[id].setPosition(newPos);
board.setCell(newPos.first, newPos.second, players[id].getFill());
}
std::pair<int, int> GameState::getPlayerPosition(int id)
{
std::lock_guard<std::mutex> lock(mutex);
return players[id].getPosition();
}
void GameState::draw()
{
std::lock_guard<std::mutex> lock(mutex);
board.draw();
}
bool GameState::isRunning()
{
std::lock_guard<std::mutex> lock(mutex);
return running;
}
void GameState::stop()
{
std::lock_guard<std::mutex> lock(mutex);
running = false;
}
}

View File

@ -0,0 +1,68 @@
#ifndef GAME_STATE_HPP
#define GAME_STATE_HPP
#include "Board.hpp"
#include "Player.hpp"
#include <unordered_map>
#include <string>
#include <mutex>
namespace ConsoleGame
{
class GameState
{
public:
GameState(int width, int heigh, std::string fill = "#");
/**
* Add a player to the game
* @return New player id
*/
int addPlayer();
/**
* Remove a player from the game
* @param id Player id
*/
void removePlayer(int id);
/**
* Move a player to a new position
* @param id Player id
* @param newPos New position of the player
*/
void movePlayer(int id, std::pair<int, int> newPos);
/**
* Get the position of a player
* @param id Player id
* @return Player position
*/
std::pair<int, int> getPlayerPosition(int id);
/**
* Draw the game board
*/
void draw();
/**
* Check if the game is running
* @return True if the game is running, false otherwise
*/
bool isRunning();
/**
* Stop the game
*/
void stop();
private:
Board board;
std::unordered_map<int, Player> players;
bool running = true;
std::mutex mutex;
};
}
#endif // GAME_STATE_HPP

View File

@ -0,0 +1,41 @@
#include "Player.hpp"
#include <string>
#include <RandomUtils.hpp>
namespace ConsoleGame
{
int colors[] = {
31, // Red
32, // Green
33, // Yellow
36, // Cyan
};
Player::Player() : pos({0, 0})
{
fill = "\033[32m@\033[0m";
}
Player::Player(std::pair<int, int> pos) : pos(pos)
{
// Set the fill color to a random color
int color = colors[RandomUtils::rand(0, sizeof(colors) / sizeof(colors[0]) - 1)];
fill = "\033[" + std::to_string(color) + "m@\033[0m";
}
std::pair<int, int> Player::getPosition()
{
return pos;
}
std::string Player::getFill()
{
return fill;
}
void Player::setPosition(std::pair<int, int> newPos)
{
pos = newPos;
}
}

View File

@ -0,0 +1,25 @@
#ifndef PLAYER_HPP
#define PLAYER_HPP
#include <string>
namespace ConsoleGame
{
class Player
{
public:
Player(); // TODO - Get rid of this constructor while keeping the compiler happy
Player(std::pair<int, int> pos);
std::pair<int, int> getPosition();
std::string getFill();
void setPosition(std::pair<int, int> newPos);
private:
std::pair<int, int> pos;
std::string fill;
};
}
#endif // PLAYER_HPP

60
cpp-console-game/main.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "GameState.hpp"
#include <unistd.h>
#include <thread>
#include <conio.hpp>
ConsoleGame::GameState gameState(20, 10);
void inputLoop(int playerId)
{
while (gameState.isRunning())
{
char c = conio::getch();
auto position = gameState.getPlayerPosition(playerId);
switch (c)
{
case 'w':
gameState.movePlayer(playerId, {position.first, position.second - 1});
break;
case 'a':
gameState.movePlayer(playerId, {position.first - 1, position.second});
break;
case 's':
gameState.movePlayer(playerId, {position.first, position.second + 1});
break;
case 'd':
gameState.movePlayer(playerId, {position.first + 1, position.second});
break;
case 'q':
gameState.stop();
break;
case 'p':
gameState.addPlayer();
break;
}
}
}
void renderLoop()
{
while (gameState.isRunning())
{
gameState.draw();
usleep(100000); // Sleep for 100ms
}
}
int main()
{
int playerId = gameState.addPlayer();
std::thread inputThread(inputLoop, playerId);
std::thread renderThread(renderLoop);
inputThread.join();
renderThread.join();
return 0;
}

View File

@ -0,0 +1,11 @@
add_library(
RandomUtils
RandomUtils.hpp
RandomUtils.cpp
)
target_include_directories(
RandomUtils
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@ -0,0 +1,26 @@
#include "RandomUtils.hpp"
#include <time.h>
namespace RandomUtils
{
bool seeded = false;
void seed()
{
if (seeded)
{
return;
}
srand(time(NULL));
seeded = true;
}
int rand(int min, int max)
{
seed();
return min + std::rand() % (max - min + 1);
}
}

View File

@ -0,0 +1,14 @@
#ifndef RANDOM_HPP
#define RANDOM_HPP
#include <cstdlib>
namespace RandomUtils
{
/**
* Generates a random number between min and max (inclusive).
*/
int rand(int min = 0, int max = RAND_MAX);
}
#endif // RANDOM_HPP