[Refactor] One GameState to rule them all!

Introduces a central GameState class to manage the client's lifecycle,
network connection and UI components. The Terminal has been demoted to a
pure UI widget and main has been simplified to a basic entrypoint as
they should be!
This commit is contained in:
Ritchie Cunningham 2025-09-27 18:07:01 +01:00
parent 59783d2408
commit 00e78cc2ba
7 changed files with 145 additions and 77 deletions

81
client/src/game_state.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <cstdio>
#include <memory>
#include <sstream>
#include "game_state.h"
#include "gfx/shape_renderer.h"
#include "gfx/txt_renderer.h"
#include "terminal.h"
#include "ui/desktop.h"
#include "ui/ui_window.h"
GameState::GameState(void) = default;
GameState::~GameState(void) = default;
void GameState::init(void) {
/* Create and connect the network client. */
_network = std::make_unique<ClientNetwork>();
if(_network->connect("127.0.0.1", 1337)) {
/* TODO: handle connection success message. */
} else {
/* TODO: handle connection failure. */
}
/* Create the desktop. */
_desktop = std::make_unique<Desktop>();
/*
* Create initial terminal window.
*/
auto term = std::make_unique<Terminal>(_network.get()); /* Pass network connection. */
auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
term_window->set_content(std::move(term));
_desktop->add_window(std::move(term_window));
}
void GameState::handle_event(SDL_Event* event) {
if(_desktop) {
_desktop->handle_event(event);
}
}
void GameState::update(void) {
std::string server_msg;
while(_network->poll_message(server_msg)) {
UIWindow* focused_window = _desktop->get_focused_window();
if(!focused_window) continue;
Terminal* terminal = focused_window->get_content();
if(!terminal) continue;
/* Server sends "output\nprompt", split them. */
size_t last_newline = server_msg.find_last_of("\n");
if(last_newline != std::string::npos) {
std::string prompt = server_msg.substr(last_newline+1);
terminal->set_prompt(prompt);
std::string output = server_msg.substr(0, last_newline);
if(!output.empty()) {
/* split the multiline output and push each line to history. */
std::stringstream ss(output);
std::string line;
while(std::getline(ss, line, '\n')) {
terminal->add_history(line);
}
}
} else {
terminal->add_history(server_msg);
}
}
if(_desktop) {
_desktop->update();
}
}
void GameState::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
if(_desktop) {
_desktop->render(shape_renderer, txt_renderer, screen_height, show_cursor);
}
}

25
client/src/game_state.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <memory>
class ClientNetwork;
class Desktop;
class ShapeRenderer;
class TextRenderer;
union SDL_Event;
class GameState {
public:
GameState(void);
~GameState(void);
void init(void);
void handle_event(SDL_Event* event);
void update(void);
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor);
private:
std::unique_ptr<ClientNetwork> _network;
std::unique_ptr<Desktop> _desktop;
};

View File

@ -10,10 +10,9 @@
#include <chrono>
#include "../../server/src/network_manager.h"
#include "terminal.h"
#include "gfx/shape_renderer.h"
#include "ui/desktop.h"
#include "ui/ui_window.h"
#include "gfx/txt_renderer.h"
#include "game_state.h"
void run_server(void) {
try {
@ -104,16 +103,8 @@ int main(int argc, char** argv) {
/* Init shape renderer. */
ShapeRenderer* shape_renderer_instance = new ShapeRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
/* Create terminal. */
auto term = std::make_unique<Terminal>();
/* Create UI window and pass it a terminal. */
auto test_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
test_window->set_content(std::move(term));
/* Create desktop and add the window. */
auto desktop = std::make_unique<Desktop>();
desktop->add_window(std::move(test_window));
auto game_state = std::make_unique<GameState>();
game_state->init();
/* timer for cursor blink. */
Uint32 last_blink_time = 0;
@ -125,10 +116,10 @@ int main(int argc, char** argv) {
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type == SDL_EVENT_QUIT) { running = false; }
desktop.get()->handle_event(&event);
game_state->handle_event(&event);
}
desktop.get()->update();
game_state->update();
Uint32 current_time = SDL_GetTicks();
if(current_time - last_blink_time > 500) { /* Every 500ms. */
@ -140,14 +131,14 @@ int main(int argc, char** argv) {
glClearColor(0.1f, 0.1f, 0.1, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
desktop.get()->render(shape_renderer_instance, txt_render_instance, SCREEN_HEIGHT, show_cursor);
game_state->render(shape_renderer_instance, txt_render_instance, SCREEN_HEIGHT, show_cursor);
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
SDL_GL_SwapWindow(window);
}
/* Cleanup. */
desktop.reset();
game_state.reset();
delete shape_renderer_instance;
delete txt_render_instance;
SDL_GL_DestroyContext(context);

View File

@ -1,54 +1,26 @@
#include <cstdio>
#include <memory>
#include <SDL3/SDL.h>
#include <GL/glew.h>
#include <SDL3/SDL_events.h>
#include <sstream>
#include "terminal.h"
#include "client_network.h"
#include "gfx/txt_renderer.h"
Terminal::Terminal(void) {
Terminal::Terminal(ClientNetwork* network) : _network(network) {
/* Placeholder welcome message to history. */
_history.push_back("Welcome to Bettola");
_history.push_back("Connecting to server...");
_network = std::make_unique<ClientNetwork>();
_should_close = false;
_scroll_offset = 0;
_prompt = "";
/* TODO: Move network to main.cpp, or better yet, a dedicatated game state file */
if(_network->connect("127.0.0.1", 1337)) {
_history.push_back("Connection successful.");
} else {
_history.push_back("Connection failed. Please restart.");
_prompt = "error>";
}
}
Terminal::~Terminal(void) {}
void Terminal::update(void) {
std::string server_msg;
while(_network->poll_message(server_msg)) {
if(server_msg == "__CLOSE_CONNECTION__") {
_history.push_back("Connection closed by server.");
_should_close = true;
return;
}
/* Server will send "output\nprompt". Split them. */
size_t last_newline = server_msg.find_last_of('\n');
if(last_newline != std::string::npos) {
_prompt = server_msg.substr(last_newline+1);
std::string output = server_msg.substr(0, last_newline);
if(!output.empty()) {
/* Split multiline output and push each line to history. */
std::stringstream ss(output);
std::string line;
while(std::getline(ss, line, '\n')) {
/* Replace tabs. */
}
void Terminal::add_history(const std::string& line) {
std::string line_with_spaces;
for(char ch : line) {
if(ch == '\t') {
@ -58,16 +30,15 @@ void Terminal::update(void) {
}
}
_history.push_back(line_with_spaces);
if(line == "__CLOSE_CONNECTION__") {
_history.back() = "Connection closed by server.";
_should_close = true;
}
}
} else {
/*
* If there's no newline, it might be the initial welcome message,
* or some other non-standard message, just it to history.
*/
_history.push_back(server_msg);
}
}
}
void Terminal::set_prompt(const std::string& prompt) {
_prompt = prompt;
}
bool Terminal::close(void) { return _should_close; }

View File

@ -1,5 +1,4 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <SDL3/SDL.h>
@ -9,13 +8,15 @@
class Terminal {
public:
Terminal(void);
Terminal(ClientNetwork* network);
~Terminal(void);
void update(void);
void handle_input(SDL_Event* event);
void render(TextRenderer* renderer, int x, int y, int width, int height, bool show_cursor);
void scroll(int amount, int content_height);
void add_history(const std::string& line);
void set_prompt(const std::string& prompt);
bool close(void);
private:
@ -26,5 +27,5 @@ private:
std::vector<std::string> _history;
int _scroll_offset;
std::string _prompt;
std::unique_ptr<ClientNetwork> _network;
ClientNetwork* _network;
};

View File

@ -55,14 +55,7 @@ void Desktop::handle_event(SDL_Event* event) {
}
void Desktop::update(void) {
/* Poll all windows for network updates. */
for(auto& window : _windows) {
Terminal* term = window->get_content();
if(term) {
term->update();
}
}
/* Remove closed windows. */
_windows.erase(std::remove_if(_windows.begin(), _windows.end(),
[](const std::unique_ptr<UIWindow>& window) {
Terminal* term = window->get_content();
@ -71,6 +64,10 @@ void Desktop::update(void) {
_windows.end());
}
UIWindow* Desktop::get_focused_window(void) {
return _focused_window;
}
void Desktop::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
int screen_height, bool show_cursor) {
for(const auto& win : _windows) {

View File

@ -19,6 +19,8 @@ public:
void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
bool show_cursor);
UIWindow* get_focused_window(void);
private:
std::vector<std::unique_ptr<UIWindow>> _windows;
UIWindow* _focused_window;