From a6a83eee7d697c90719b10a233dde3715e096587 Mon Sep 17 00:00:00 2001 From: ethanglide Date: Tue, 9 Jul 2024 16:56:03 -0400 Subject: [PATCH] Add multiplayer The game now works! Host up a server and get some clients going and you can see multiple players moving around on the game board! --- cpp-console-game/Board.cpp | 10 ++-- cpp-console-game/Board.hpp | 2 +- cpp-console-game/CMakeLists.txt | 38 ++++++++++--- cpp-console-game/GameClient.cpp | 61 ++++++++++++++++++++ cpp-console-game/GameClient.hpp | 29 ++++++++++ cpp-console-game/GameServer.cpp | 79 ++++++++++++++++++++++++++ cpp-console-game/GameServer.hpp | 32 +++++++++++ cpp-console-game/GameState.cpp | 4 +- cpp-console-game/GameState.hpp | 4 +- cpp-console-game/main.cpp | 98 ++------------------------------- 10 files changed, 247 insertions(+), 110 deletions(-) create mode 100644 cpp-console-game/GameClient.cpp create mode 100644 cpp-console-game/GameClient.hpp create mode 100644 cpp-console-game/GameServer.cpp create mode 100644 cpp-console-game/GameServer.hpp diff --git a/cpp-console-game/Board.cpp b/cpp-console-game/Board.cpp index 3a1ff3e..f5dc8bb 100644 --- a/cpp-console-game/Board.cpp +++ b/cpp-console-game/Board.cpp @@ -13,18 +13,20 @@ namespace ConsoleGame } } - void Board::draw() + std::string Board::draw() { - std::cout << "\033[H"; // Move cursor to the top left corner + std::string board = "\033[H"; // Move cursor to the top left corner for (auto row : cells) { for (auto cell : row) { - std::cout << cell; + board += cell; } - std::cout << std::endl; + board += "|"; } + + return board; } void Board::setCell(int x, int y, std::string value) diff --git a/cpp-console-game/Board.hpp b/cpp-console-game/Board.hpp index 2156ffa..2cbbad1 100644 --- a/cpp-console-game/Board.hpp +++ b/cpp-console-game/Board.hpp @@ -16,7 +16,7 @@ namespace ConsoleGame * @param fill The string to fill the board with */ Board(int width, int height, std::string fill = "#"); - void draw(); + std::string draw(); void setCell(int x, int y, std::string value); std::string getCell(int x, int y); int getWidth(); diff --git a/cpp-console-game/CMakeLists.txt b/cpp-console-game/CMakeLists.txt index 42b8e15..9c8d092 100644 --- a/cpp-console-game/CMakeLists.txt +++ b/cpp-console-game/CMakeLists.txt @@ -16,18 +16,23 @@ add_library( Player.cpp ) +add_library( + GameServer + GameServer.hpp + GameServer.cpp + ) + +add_library( + GameClient + GameClient.hpp + GameClient.cpp + ) + add_executable( cpp-console-game main.cpp ) -target_link_libraries( - cpp-console-game PRIVATE - GameState - conio - eRPC - ) - target_link_libraries( GameState PRIVATE Board @@ -38,4 +43,23 @@ target_link_libraries( target_link_libraries( Player PRIVATE RandomUtils + ) + +target_link_libraries( + GameServer PRIVATE + GameState + eRPC + ) + +target_link_libraries( + GameClient PRIVATE + eRPC + conio + ) + +target_link_libraries( + cpp-console-game PRIVATE + GameServer + GameClient + eRPC ) \ No newline at end of file diff --git a/cpp-console-game/GameClient.cpp b/cpp-console-game/GameClient.cpp new file mode 100644 index 0000000..8372c46 --- /dev/null +++ b/cpp-console-game/GameClient.cpp @@ -0,0 +1,61 @@ +#include "GameClient.hpp" + +#include +#include +#include +#include + +namespace ConsoleGame +{ + GameClient::GameClient(std::string host, int port) : client(host, port) + { + playerId = addPlayer(); + + std::thread renderThread(&GameClient::renderLoop, this); + std::thread inputThread(&GameClient::inputLoop, this); + + renderThread.join(); + inputThread.join(); + } + + void GameClient::renderLoop() + { + std::cout << "\033[2J"; // Clear the screen + + while (running) + { + auto res = client.call("draw", {}); + + //replace all "|" with newline + std::replace(res.begin(), res.end(), '|', '\n'); + + std::cout << res << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + void GameClient::inputLoop() + { + while (running) + { + char input = conio::getch(); + client.call("handleInput", {std::to_string(playerId), std::string(1, input)}); + + if (input == 'q') + { + running = false; + } + } + } + + int GameClient::addPlayer() + { + auto res = client.call("addPlayer", {}); + return std::stoi(res); + } + + void GameClient::removePlayer() + { + client.call("removePlayer", {std::to_string(playerId)}); + } +} \ No newline at end of file diff --git a/cpp-console-game/GameClient.hpp b/cpp-console-game/GameClient.hpp new file mode 100644 index 0000000..924d23e --- /dev/null +++ b/cpp-console-game/GameClient.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_CLIENT_HPP +#define GAME_CLIENT_HPP + +#include + +namespace ConsoleGame +{ + class GameClient + { + public: + /** + * Create and start new game client. This is a blocking call. + */ + GameClient(std::string host, int port); + + private: + eRPC::Client client; + int playerId; + bool running = true; + + void renderLoop(); + void inputLoop(); + + int addPlayer(); + void removePlayer(); + }; +} + +#endif // GAME_CLIENT_HPP \ No newline at end of file diff --git a/cpp-console-game/GameServer.cpp b/cpp-console-game/GameServer.cpp new file mode 100644 index 0000000..63f82a0 --- /dev/null +++ b/cpp-console-game/GameServer.cpp @@ -0,0 +1,79 @@ +#include "GameServer.hpp" + +namespace ConsoleGame +{ + GameServer::GameServer(int port, int width, int height, std::string fill) : server(port), state(width, height, fill) + { + server.bindMethod("handleInput", std::bind(&GameServer::rpc_handleInput, this, std::placeholders::_1)); + server.bindMethod("draw", std::bind(&GameServer::rpc_draw, this, std::placeholders::_1)); + server.bindMethod("addPlayer", std::bind(&GameServer::rpc_addPlayer, this, std::placeholders::_1)); + server.bindMethod("removePlayer", std::bind(&GameServer::rpc_removePlayer, this, std::placeholders::_1)); + + server.start(); + } + + void GameServer::handleInput(int playerId, char input) + { + std::pair playerPos = state.getPlayerPosition(playerId); + + switch (input) + { + case 'w': + playerPos.second--; + break; + case 's': + playerPos.second++; + break; + case 'a': + playerPos.first--; + break; + case 'd': + playerPos.first++; + break; + case 'q': + state.removePlayer(playerId); + return; + default: + return; + } + + state.movePlayer(playerId, playerPos); + } + + std::pair GameServer::rpc_handleInput(std::vector args) + { + if (args.size() != 2) + { + return {false, "Invalid number of arguments"}; + } + + int playerId = std::stoi(args[0]); + char input = args[1][0]; + + handleInput(playerId, input); + + return {true, ""}; + } + + std::pair GameServer::rpc_draw(std::vector args) + { + return {true, state.draw()}; + } + + std::pair GameServer::rpc_addPlayer(std::vector args) + { + return {true, std::to_string(state.addPlayer())}; + } + + std::pair GameServer::rpc_removePlayer(std::vector args) + { + if (args.size() != 1) + { + return {false, "Invalid number of arguments"}; + } + + state.removePlayer(std::stoi(args[0])); + + return {true, ""}; + } +} \ No newline at end of file diff --git a/cpp-console-game/GameServer.hpp b/cpp-console-game/GameServer.hpp new file mode 100644 index 0000000..4f26549 --- /dev/null +++ b/cpp-console-game/GameServer.hpp @@ -0,0 +1,32 @@ +#ifndef GAME_SERVER_HPP +#define GAME_SERVER_HPP + +#include "GameState.hpp" +#include + +namespace ConsoleGame +{ + class GameServer + { + public: + /** + * Create and start new game server. This is a blocking call. + */ + GameServer(int port, int width, int height, std::string fill = "#"); + + private: + GameState state; + eRPC::Server server; + + void handleInput(int playerId, char input); + + // RPC method bindings + + std::pair rpc_handleInput(std::vector args); + std::pair rpc_draw(std::vector args); + std::pair rpc_addPlayer(std::vector args); + std::pair rpc_removePlayer(std::vector args); + }; +} + +#endif // GAME_SERVER_HPP \ No newline at end of file diff --git a/cpp-console-game/GameState.cpp b/cpp-console-game/GameState.cpp index 186c6d7..d1ab8ff 100644 --- a/cpp-console-game/GameState.cpp +++ b/cpp-console-game/GameState.cpp @@ -68,11 +68,11 @@ namespace ConsoleGame return players[id].getPosition(); } - void GameState::draw() + std::string GameState::draw() { std::lock_guard lock(mutex); - board.draw(); + return board.draw(); } bool GameState::isRunning() diff --git a/cpp-console-game/GameState.hpp b/cpp-console-game/GameState.hpp index 930c4fe..4048f41 100644 --- a/cpp-console-game/GameState.hpp +++ b/cpp-console-game/GameState.hpp @@ -13,7 +13,7 @@ namespace ConsoleGame class GameState { public: - GameState(int width, int heigh, std::string fill = "#"); + GameState(int width, int height, std::string fill = "#"); /** * Add a player to the game @@ -44,7 +44,7 @@ namespace ConsoleGame /** * Draw the game board */ - void draw(); + std::string draw(); /** * Check if the game is running diff --git a/cpp-console-game/main.cpp b/cpp-console-game/main.cpp index 2e10423..30978b2 100644 --- a/cpp-console-game/main.cpp +++ b/cpp-console-game/main.cpp @@ -1,80 +1,8 @@ -#include "GameState.hpp" +#include "GameServer.hpp" +#include "GameClient.hpp" -#include -#include -#include -#include -#include #include -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() -{ - std::cout << "\033[2J"; // Clear the screen - - while (gameState.isRunning()) - { - gameState.draw(); - usleep(100000); // Sleep for 100ms - } -} - -std::pair movePlayer(std::vector args) -{ - int playerId = std::stoi(args[0]); - int x = std::stoi(args[1]); - int y = std::stoi(args[2]); - - //gameState.movePlayer(playerId, {x, y}); - std::cout << "Player " << playerId << " moved to (" << x << ", " << y << ")" << std::endl; - - return {true, "Move Successful"}; -} - -std::pair echo(std::vector args) -{ - for (auto arg : args) - { - std::cout << arg << " "; - } - - std::cout << std::endl; - - return {true, ""}; -} - int main(int argc, char **argv) { if (argc < 4) @@ -89,21 +17,11 @@ int main(int argc, char **argv) if (mode == "server") { - eRPC::Server server(port); - server.bindMethod("movePlayer", movePlayer); - server.bindMethod("echo", echo); - server.start(); + ConsoleGame::GameServer server(port, 20, 10, "#"); } else if (mode == "client") { - eRPC::Client client(host, port); - - client.call("echo", {"Hello", "World"}); - client.call("movePlayer", {"1", "5", "5"}); - client.call("movePlayer", {"1", "6", "6"}); - auto ret = client.call("echo", {"Goodbye", "World"}); - - std::cout << "Result: " << ret << std::endl; + ConsoleGame::GameClient client(host, port); } else { @@ -111,13 +29,5 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - // int playerId = gameState.addPlayer(); - - // std::thread inputThread(inputLoop, playerId); - // std::thread renderThread(renderLoop); - - // inputThread.join(); - // renderThread.join(); - return EXIT_SUCCESS; } \ No newline at end of file