diff --git a/client/Makefile b/client/Makefile index 3944d32..aa846c0 100644 --- a/client/Makefile +++ b/client/Makefile @@ -4,7 +4,7 @@ NAME = nibbler_client CC = c++ CFLAGS = -O2 -Wall -Wextra -std=c++17 -pthread FSANITIZE = -fsanitize=thread -SOURCES_M := src/main.cpp src/Client.cpp src/Drawer.cpp +SOURCES_M := src/main.cpp src/Client.cpp src/Drawer.cpp src/EventManager.cpp OBJECTS := $(SOURCES_M:.cpp=.o) diff --git a/client/assets.cfg b/client/assets.cfg new file mode 100644 index 0000000..fbd47f0 --- /dev/null +++ b/client/assets.cfg @@ -0,0 +1,4 @@ +assets/body.png +assets/head.png +assets/food.png + diff --git a/client/includes/nibbler.hpp b/client/includes/nibbler.hpp index dbf522f..8fac711 100644 --- a/client/includes/nibbler.hpp +++ b/client/includes/nibbler.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #define TILE_SIZE 0.05f #define SCALE 20.0f @@ -47,17 +48,58 @@ enum actions KEY_3 }; +struct rgb +{ + float r; + float g; + float b; +}; + +// Created to accommodate sf::Vector2i from SFML +// Graphics-agnostic 2D integer vector for window size and mouse position +struct Vec2i { + int x; + int y; +}; + enum type { - KEY, - MOUSE, - EXIT, - EMPTY + // Old types (removed, kept for reference): + // KEY, // Replaced by KEY_PRESSED/KEY_RELEASED + // MOUSE, // Replaced by MOUSE_BUTTON_PRESSED/MOUSE_BUTTON_RELEASED + // EXIT, // Replaced by CLOSED + + // Match EventManager's EventType enum + CLOSED = 0, // Window closed (was EXIT) + RESIZED = 1, // Window resized + FOCUS_LOST = 2, // Window lost focus + FOCUS_GAINED = 3, // Window gained focus + TEXT_ENTERED = 4, // Text input + KEY_PRESSED = 5, // Key pressed (was KEY) + KEY_RELEASED = 6, // Key released + MOUSE_WHEEL_SCROLLED = 7, // Mouse wheel scrolled + MOUSE_BUTTON_PRESSED = 8, // Mouse button pressed (was MOUSE) + MOUSE_BUTTON_RELEASED = 9, // Mouse button released + MOUSE_MOVED = 10, // Mouse moved + MOUSE_MOVED_RAW = 11, // Mouse moved (raw) + MOUSE_ENTERED = 12, // Mouse entered window + MOUSE_LEFT = 13, // Mouse left window + EMPTY = 99 // No event (used by graphics libraries for polling) }; typedef struct s_event { type type; - int a; - int b; + union { + int keyCode; // When type == KEY_PRESSED/KEY_RELEASED: key code (UP, DOWN, LEFT, RIGHT, M, N, KEY_1, KEY_2, KEY_3) + struct { + int x, y; // When type == MOUSE_*: mouse coordinates + int button; // When type == MOUSE_BUTTON_*: button code (0=left, 1=right, 2=middle) + } mouse; + struct { + int width, height; // When type == RESIZED: new window dimensions + } window; + int wheelDelta; // When type == MOUSE_WHEEL_SCROLLED: scroll delta + std::uint32_t unicode; // When type == TEXT_ENTERED: unicode character + }; } t_event; typedef void *(*initFunc)(int, int, void *); diff --git a/client/keys.cfg b/client/keys.cfg new file mode 100644 index 0000000..703812e --- /dev/null +++ b/client/keys.cfg @@ -0,0 +1,18 @@ +Window_close 0:0 +Fullscreen_toggle 5:89 +Move 21:0 20:38 +Intro_Continue 5:57 +Mouse_Left 9:0 +Key_Escape 5:36 +Key_P 5:15 +Key_Up 5:73 +Key_Down 5:74 +Key_Left 5:71 +Key_Right 5:72 +Key_W 5:22 +Key_A 5:0 +Key_S 5:18 +Key_D 5:3 +Key_M 5:12 +Key_N 5:13 + diff --git a/client/src/BaseState.cpp b/client/src/BaseState.cpp new file mode 100644 index 0000000..7beb5d5 --- /dev/null +++ b/client/src/BaseState.cpp @@ -0,0 +1,11 @@ +#include "BaseState.hpp" + +// BaseState implementation is empty - all methods are pure virtual +// or have inline implementations in the header + + + + + + + diff --git a/client/src/BaseState.hpp b/client/src/BaseState.hpp new file mode 100644 index 0000000..1c0fd92 --- /dev/null +++ b/client/src/BaseState.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +class StateManager; + +class BaseState { + friend class StateManager; +public: + BaseState(StateManager* l_stateManager) + : m_stateMgr(l_stateManager), m_transparent(false), + m_transcendent(false) {} + virtual ~BaseState() {} + + virtual void OnCreate() = 0; + virtual void OnDestroy() = 0; + virtual void Activate() = 0; + virtual void Deactivate() = 0; + virtual void Update(const sf::Time& l_time) = 0; + virtual void Draw() = 0; + + void SetTransparent(const bool& l_transparent) { + m_transparent = l_transparent; + } + bool IsTransparent() const { return m_transparent; } + + void SetTranscendent(const bool& l_transcendence) { + m_transcendent = l_transcendence; + } + bool IsTranscendent() const { return m_transcendent; } + + StateManager* GetStateManager() { return m_stateMgr; } + +protected: + StateManager* m_stateMgr; + bool m_transparent; + bool m_transcendent; +}; + diff --git a/client/src/Drawer.cpp b/client/src/Drawer.cpp index 376d978..7ce9e3e 100644 --- a/client/src/Drawer.cpp +++ b/client/src/Drawer.cpp @@ -1,4 +1,5 @@ #include "Drawer.hpp" +#include "EventManager.hpp" #define WIDTH 1000 #define HEIGHT 1000 @@ -11,6 +12,29 @@ multiplayerButton{400, 300, 200, 60, "Multiplayer", Button::MULTIPLAYER}, singlePlayerButton{400, 400, 200, 60, "Single-player", Button::SINGLE_PLAYER} { tilePx = std::max(1, std::min(WIDTH / screenSize, HEIGHT / screenSize)); + + // Initialize EventManager + eventManager = new EventManager(); + + // Register movement callbacks (arrow keys and WASD) - only active in Game state + eventManager->AddCallback(StateType::Game, "Key_Up", &Drawer::MoveUp, this); + eventManager->AddCallback(StateType::Game, "Key_Down", &Drawer::MoveDown, this); + eventManager->AddCallback(StateType::Game, "Key_Left", &Drawer::MoveLeft, this); + eventManager->AddCallback(StateType::Game, "Key_Right", &Drawer::MoveRight, this); + eventManager->AddCallback(StateType::Game, "Key_W", &Drawer::MoveUp, this); + eventManager->AddCallback(StateType::Game, "Key_A", &Drawer::MoveLeft, this); + eventManager->AddCallback(StateType::Game, "Key_S", &Drawer::MoveDown, this); + eventManager->AddCallback(StateType::Game, "Key_D", &Drawer::MoveRight, this); + + // Register zoom callbacks + eventManager->AddCallback(StateType::Game, "Key_M", &Drawer::ZoomIn, this); + eventManager->AddCallback(StateType::Game, "Key_N", &Drawer::ZoomOut, this); + + // Register mouse callback - only active in Menu state (not during gameplay) + eventManager->AddCallback(StateType::MainMenu, "Mouse_Left", &Drawer::OnMouseClick, this); + + // Set initial state to Menu (since we start with the menu) + eventManager->SetCurrentState(StateType::MainMenu); assets = (char **)malloc(sizeof(char *) * 4); assets[0] = strdup("assets/body.png"); @@ -21,6 +45,7 @@ singlePlayerButton{400, 400, 200, 60, "Single-player", Button::SINGLE_PLAYER} Drawer::~Drawer() { + delete eventManager; this->closeDynamicLib(); for (int i = 0; assets[i]; ++i) @@ -95,19 +120,27 @@ void Drawer::start() this->beginFrame(this->window); t_event event = this->checkEvents(this->window); - switch (event.type) - { - case EXIT: - gameRunning = false; - break; - case KEY: - onKeyPress(event.a); - break; - case MOUSE: - onMouseUp(event.a, event.b); - break; - default: - break; + + // Debug: log all events + if (event.type != EMPTY) { + std::cout << "[Drawer] Event received: type=" << static_cast(event.type); + if (event.type == MOUSE_BUTTON_PRESSED || event.type == MOUSE_BUTTON_RELEASED) { + std::cout << " mouse button=" << event.mouse.button << " at (" << event.mouse.x << ", " << event.mouse.y << ")"; + } else if (event.type == KEY_PRESSED || event.type == KEY_RELEASED) { + std::cout << " keyCode=" << event.keyCode; + } + std::cout << std::endl; + } + + // Handle CLOSED event directly + if (event.type == CLOSED) { + gameRunning = false; + } + + // Pass event to EventManager (only if not EMPTY) + if (event.type != EMPTY) { + eventManager->HandleEvent(event); + eventManager->Update(); } if (this->gameMode == GAME) @@ -250,44 +283,117 @@ void Drawer::onMouseUp(float x, float y) std::cout << " - Starting local server"; std::cout << std::endl; - this->client->setIsDead(false); - this->client->setStopFlag(false); + this->client->setIsDead(false); + this->client->setStopFlag(false); this->clientThread = std::thread(&Client::start, this->client, serverIP, isSinglePlayer); this->gameMode = GAME; + eventManager->SetCurrentState(StateType::Game); } } -void Drawer::onKeyPress(int key) +// Old onKeyPress - kept for reference, replaced by EventManager callbacks +// void Drawer::onKeyPress(int key) +// { +// actions action = (actions)key; +// +// switch (action) +// { +// case UP: +// case DOWN: +// case RIGHT: +// case LEFT: +// this->client->sendDirection(action); +// break; +// case M: +// this->screenSize = this->screenSize * 1.10 + 0.5; +// this->tilePx = std::max(1, std::min(WIDTH / screenSize, HEIGHT / screenSize)); +// break; +// case N: +// this->screenSize = this->screenSize / 1.10; +// this->tilePx = std::max(1, std::min(WIDTH / screenSize, HEIGHT / screenSize)); +// break; +// case KEY_1: +// this->switchLibPath = "../libs/lib1/lib1"; +// this->gameRunning = false; +// break; +// case KEY_2: +// this->switchLibPath = "../libs/lib2/lib2"; +// this->gameRunning = false; +// break; +// case KEY_3: +// this->switchLibPath = "../libs/lib4/lib3"; +// this->gameRunning = false; +// break; +// } +// } + +// EventManager callbacks +void Drawer::MoveUp(EventDetails* l_details) { - actions action = (actions)key; + (void)l_details; // Unused, but required by callback signature + this->client->sendDirection(UP); +} - switch (action) +void Drawer::MoveDown(EventDetails* l_details) { - case UP: - case DOWN: - case RIGHT: - case LEFT: - this->client->sendDirection(action); - break; - case M: + (void)l_details; + this->client->sendDirection(DOWN); +} + +void Drawer::MoveLeft(EventDetails* l_details) +{ + (void)l_details; + this->client->sendDirection(LEFT); +} + +void Drawer::MoveRight(EventDetails* l_details) +{ + (void)l_details; + this->client->sendDirection(RIGHT); +} + +void Drawer::ZoomIn(EventDetails* l_details) +{ + (void)l_details; this->screenSize = this->screenSize * 1.10 + 0.5; this->tilePx = std::max(1, std::min(WIDTH / screenSize, HEIGHT / screenSize)); - break; - case N: +} + +void Drawer::ZoomOut(EventDetails* l_details) +{ + (void)l_details; this->screenSize = this->screenSize / 1.10; this->tilePx = std::max(1, std::min(WIDTH / screenSize, HEIGHT / screenSize)); - break; - case KEY_1: +} + +void Drawer::SwitchLib1(EventDetails* l_details) +{ + (void)l_details; this->switchLibPath = "../libs/lib1/lib1"; this->gameRunning = false; - break; - case KEY_2: +} + +void Drawer::SwitchLib2(EventDetails* l_details) +{ + (void)l_details; this->switchLibPath = "../libs/lib2/lib2"; this->gameRunning = false; - break; - case KEY_3: +} + +void Drawer::SwitchLib3(EventDetails* l_details) +{ + (void)l_details; this->switchLibPath = "../libs/lib4/lib3"; this->gameRunning = false; - break; +} + +void Drawer::OnMouseClick(EventDetails* l_details) +{ + // Only process mouse clicks when in menu mode + if (this->gameMode != MENU) { + return; } + std::cout << "[Drawer::OnMouseClick] Mouse clicked at (" << l_details->m_mouse.x << ", " << l_details->m_mouse.y << ")" << std::endl; + // Extract mouse coordinates from EventDetails + onMouseUp(l_details->m_mouse.x, l_details->m_mouse.y); } diff --git a/client/src/Drawer.hpp b/client/src/Drawer.hpp index 3fef439..df3275e 100644 --- a/client/src/Drawer.hpp +++ b/client/src/Drawer.hpp @@ -3,6 +3,7 @@ #include "../includes/nibbler.hpp" #include "Client.hpp" +#include "EventManager.hpp" enum mode { @@ -33,6 +34,7 @@ class Drawer private: Client *client; + EventManager *eventManager; void *dynamicLibrary = nullptr; void *window = nullptr; int screenSize; @@ -64,11 +66,26 @@ class Drawer void startDynamicLib(); void closeDynamicLib(); void onMouseUp(float x, float y); - void onKeyPress(int action); + // Old onKeyPress - kept for reference, replaced by EventManager callbacks + // void onKeyPress(int action); + + // EventManager callbacks + void MoveUp(EventDetails* l_details); + void MoveDown(EventDetails* l_details); + void MoveLeft(EventDetails* l_details); + void MoveRight(EventDetails* l_details); + void ZoomIn(EventDetails* l_details); + void ZoomOut(EventDetails* l_details); + void SwitchLib1(EventDetails* l_details); + void SwitchLib2(EventDetails* l_details); + void SwitchLib3(EventDetails* l_details); + void OnMouseClick(EventDetails* l_details); + void loadDynamicLibrary(const std::string &lib); void drawBorder(int x, int y, int px, int py, int tilePx); void drawGameField(); void drawMenu(); + void loadAssetPaths(const std::string &configFile = "assets.cfg"); }; #endif diff --git a/client/src/EventManager.cpp b/client/src/EventManager.cpp new file mode 100644 index 0000000..85810c8 --- /dev/null +++ b/client/src/EventManager.cpp @@ -0,0 +1,264 @@ +#include "EventManager.hpp" +#include "StateManager.hpp" // For StateType enum +#include +#include +#include + +EventManager::EventManager() : m_hasFocus(true), m_currentState(StateType::Global) { + LoadBindings(); +} + +EventManager::~EventManager() { + for (auto& itr : m_bindings) { + delete itr.second; + itr.second = nullptr; + } +} + +bool EventManager::AddBinding(Binding* l_binding) { + if (m_bindings.find(l_binding->m_name) != m_bindings.end()) { + return false; + } + return m_bindings.emplace(l_binding->m_name, l_binding).second; +} + +bool EventManager::RemoveBinding(std::string l_name) { + auto itr = m_bindings.find(l_name); + if (itr == m_bindings.end()) { + return false; + } + delete itr->second; + m_bindings.erase(itr); + return true; +} + +void EventManager::SetFocus(const bool& l_focus) { + m_hasFocus = l_focus; +} + +void EventManager::SetCurrentState(StateType l_state) { + m_currentState = l_state; +} + +bool EventManager::RemoveCallback(StateType l_state, const std::string& l_name) { + auto itr = m_callbacks.find(l_state); + if (itr == m_callbacks.end()) { return false; } + auto itr2 = itr->second.find(l_name); + if (itr2 == itr->second.end()) { return false; } + itr->second.erase(l_name); + return true; +} + +/// @brief Handles incoming SFML events and matches them to registered bindings. +/// +/// This method processes SFML events by iterating through all registered bindings +/// and their associated events. When a match is found: +/// - For keyboard events (KeyDown/KeyUp): Checks if the key code matches the binding's event code +/// - For mouse button events (MButtonDown/MButtonUp): Checks if the button matches and stores mouse position +/// - For MouseWheel events: Stores the wheel delta value +/// - For WindowResized events: Stores the new window dimensions (width, height) +/// - For TextEntered events: Stores the unicode character entered +/// +/// When a match is found, the binding's event count is incremented and relevant +/// event details are stored in the binding's EventDetails structure. +/// +/// @param l_event Reference to the SFML event to process +void EventManager::HandleEvent(t_event& l_event) { + // Map t_event.type to EventType enum + // t_event.type values match EventType enum values + EventType eventType = static_cast(l_event.type); + + // Check if event type is valid + if (l_event.type < CLOSED || l_event.type > MOUSE_LEFT) { + return; // Unknown event type + } + + // Debug: log mouse events (key events logged only on match) + if (eventType == EventType::MouseButtonPressed || eventType == EventType::MouseButtonReleased) { + std::cout << "[EventManager::HandleEvent] Mouse event type=" << static_cast(eventType) + << " button=" << l_event.mouse.button + << " at (" << l_event.mouse.x << ", " << l_event.mouse.y << ")" << std::endl; + } + + for (auto& b_itr : m_bindings) { + Binding* bind = b_itr.second; + for (auto& e_itr : bind->m_events) { + if (e_itr.first != eventType) { + continue; + } + if (eventType == EventType::KeyPressed || + eventType == EventType::KeyReleased) { + if (e_itr.second.m_code == l_event.keyCode) { + std::cout << "[EventManager::HandleEvent] Match! Key code " << l_event.keyCode << " matches binding '" << bind->m_name << "'" << std::endl; + bind->m_details.m_keyCode = l_event.keyCode; + ++(bind->c); + break; + } + } else if (eventType == EventType::MouseButtonPressed || + eventType == EventType::MouseButtonReleased) { + std::cout << "[EventManager] Checking binding '" << bind->m_name + << "' for mouse button " << l_event.mouse.button + << " (binding expects " << e_itr.second.m_code << ")" << std::endl; + if (e_itr.second.m_code == l_event.mouse.button) { + std::cout << "[EventManager] Match! Setting mouse coords to (" + << l_event.mouse.x << ", " << l_event.mouse.y << ")" << std::endl; + bind->m_details.m_mouse.x = l_event.mouse.x; + bind->m_details.m_mouse.y = l_event.mouse.y; + if (bind->m_details.m_keyCode != -1) { + bind->m_details.m_keyCode = l_event.mouse.button; + } + ++(bind->c); + break; + } + } else { + // No need for additional checking. + if (eventType == EventType::MouseWheelScrolled) { + bind->m_details.m_mouseWheelDelta = l_event.wheelDelta; + } else if (eventType == EventType::Resized) { + bind->m_details.m_size.x = l_event.window.width; + bind->m_details.m_size.y = l_event.window.height; + } else if (eventType == EventType::TextEntered) { + bind->m_details.m_textEntered = l_event.unicode; + } + ++(bind->c); + } + } + } +} + +void EventManager::Update() { + if (!m_hasFocus) { + return; + } + for (auto& b_itr : m_bindings) { + Binding* bind = b_itr.second; + for (auto& e_itr : bind->m_events) { + switch (e_itr.first) { + case(EventType::Keyboard): + // TODO: Continuous keyboard polling - needs graphics-agnostic implementation + // if (sf::Keyboard::isKeyPressed( + // sf::Keyboard::Key(e_itr.second.m_code))) { + // std::cout << "Keyboard key " << e_itr.second.m_code << " is currently pressed (continuous check)" << std::endl; + // if (bind->m_details.m_keyCode != -1) { + // bind->m_details.m_keyCode = e_itr.second.m_code; + // } + // ++(bind->c); + // } + break; + case(EventType::Mouse): + // TODO: Continuous mouse polling - needs graphics-agnostic implementation + // if (sf::Mouse::isButtonPressed( + // sf::Mouse::Button(e_itr.second.m_code))) { + // // std::cout << "Mouse button " << e_itr.second.m_code << " is currently pressed (continuous check)" << std::endl; + // if (bind->m_details.m_keyCode != -1) { + // bind->m_details.m_keyCode = e_itr.second.m_code; + // } + // ++(bind->c); + // } + break; + case(EventType::Joystick): + // Up for expansion. + break; + default: + // Other event types are handled in HandleEvent, not here + break; + } + } + if (static_cast(bind->m_events.size()) == bind->c) { + std::cout << "[EventManager::Update] Binding '" << bind->m_name << "' triggered! All " << bind->c << " events matched." << std::endl; + std::cout << "[EventManager::Update] Current state: " << static_cast(m_currentState) << std::endl; + + // Check callbacks for current state + auto stateCallbacks = m_callbacks.find(m_currentState); + if (stateCallbacks != m_callbacks.end()) { + std::cout << "[EventManager::Update] Found callbacks for current state, looking for '" << bind->m_name << "'" << std::endl; + auto callItr = stateCallbacks->second.find(bind->m_name); + if (callItr != stateCallbacks->second.end()) { + std::cout << "[EventManager::Update] Calling callback for '" << bind->m_name << "'" << std::endl; + callItr->second(&bind->m_details); + } else { + std::cout << "[EventManager::Update] No callback found for '" << bind->m_name << "' in current state" << std::endl; + } + } else { + std::cout << "[EventManager::Update] No callbacks registered for current state" << std::endl; + } + + // Check global callbacks (StateType::Global - always active regardless of state) + auto otherCallbacks = m_callbacks.find(StateType::Global); + if (otherCallbacks != m_callbacks.end()) { + std::cout << "[EventManager::Update] Checking global callbacks for '" << bind->m_name << "'" << std::endl; + auto callItr = otherCallbacks->second.find(bind->m_name); + if (callItr != otherCallbacks->second.end()) { + std::cout << "[EventManager::Update] Calling global callback for '" << bind->m_name << "'" << std::endl; + callItr->second(&bind->m_details); + } + } + } + bind->c = 0; + bind->m_details.Clear(); + } +} + +void EventManager::LoadBindings() { + std::string delimiter = ":"; + std::ifstream bindings; + bindings.open("keys.cfg"); + if (!bindings.is_open()) { + std::cout << "! Failed loading keys.cfg." << std::endl; + return; + } + std::cout << "[EventManager] Loading bindings from keys.cfg..." << std::endl; + + std::string line; + while (std::getline(bindings, line)) { + if (line.empty()) { + continue; + } + std::stringstream keystream(line); + std::string callbackName; + keystream >> callbackName; + if (callbackName.empty()) { + continue; + } + Binding* bind = new Binding(callbackName); + + while (!keystream.eof()) { + std::string keyval; + keystream >> keyval; + if (keyval.empty()) { + break; + } + int start = 0; + std::string::size_type end = keyval.find(delimiter); + if (end == std::string::npos) { + delete bind; + bind = nullptr; + break; + } + try { + EventType type = EventType( + stoi(keyval.substr(start, end - start))); + std::string codeStr = keyval.substr(end + delimiter.length()); + std::string::size_type nextDelim = codeStr.find(delimiter); + int code = stoi(nextDelim == std::string::npos ? codeStr : codeStr.substr(0, nextDelim)); + EventInfo eventInfo; + eventInfo.m_code = code; + bind->BindEvent(type, eventInfo); + } catch (...) { + delete bind; + bind = nullptr; + break; + } + } + + if (bind && !AddBinding(bind)) { + std::cout << "[EventManager] Failed to add binding: " << bind->m_name << std::endl; + delete bind; + } else if (bind) { + std::cout << "[EventManager] Loaded binding: " << bind->m_name << " with " << bind->m_events.size() << " event(s)" << std::endl; + } + } + bindings.close(); + std::cout << "[EventManager] Loaded " << m_bindings.size() << " bindings total" << std::endl; +} + diff --git a/client/src/EventManager.hpp b/client/src/EventManager.hpp new file mode 100644 index 0000000..ce59033 --- /dev/null +++ b/client/src/EventManager.hpp @@ -0,0 +1,132 @@ +#ifndef EVENTMANAGER_HPP +#define EVENTMANAGER_HPP + +#include +#include +#include +#include +#include +#include "../includes/nibbler.hpp" // For Vec2i + +// Forward declaration - StateType is defined in StateManager.hpp +enum class StateType; + +// SFML 3.x uses variant-based events, so we use numeric values for EventType +// These correspond to the variant index positions in sf::Event +enum class EventType { + Closed = 0, + Resized = 1, + FocusLost = 2, + FocusGained = 3, + TextEntered = 4, + KeyPressed = 5, + KeyReleased = 6, + MouseWheelScrolled = 7, + MouseButtonPressed = 8, + MouseButtonReleased = 9, + MouseMoved = 10, + MouseMovedRaw = 11, + MouseEntered = 12, + MouseLeft = 13, + // Custom categories (after all SFML events) + Keyboard = 20, + Mouse = 21, + Joystick = 22 +}; + +struct EventInfo { + EventInfo() { m_code = 0; } + EventInfo(int l_event) { m_code = l_event; } + + union { + int m_code; + }; +}; + +using Events = std::vector>; + +struct EventDetails { + EventDetails(const std::string& l_bindName) + : m_name(l_bindName) { + Clear(); + } + + std::string m_name; + // NOTE: m_size naming is vague - doesn't indicate what size or where it comes from. + // Better names would be: windowWidth/windowHeight, or resizedWidth/resizedHeight. + // Functionally, it's only used for sf::Event::Resized, so context limits confusion. + Vec2i m_size; + std::uint32_t m_textEntered; + Vec2i m_mouse; + int m_mouseWheelDelta; + int m_keyCode; // Single key code. + + void Clear() { + m_size = Vec2i{0, 0}; + m_textEntered = 0; + m_mouse = Vec2i{0, 0}; + m_mouseWheelDelta = 0; + m_keyCode = -1; + } +}; + +struct Binding { + Binding(const std::string& l_name) + : m_name(l_name), c(0), m_details(l_name) {} + + // NOTE: Consider adding a method that takes an array/vector of event pairs + // to bind multiple events at once, rather than calling BindEvent multiple times. + // Alternatively, consider an EventInfo constructor that takes an array of events. + void BindEvent(EventType l_type, EventInfo l_info = EventInfo()) { + m_events.emplace_back(l_type, l_info); + } + + Events m_events; + std::string m_name; + int c; // Count of events that are "happening". + EventDetails m_details; +}; + +using Bindings = std::unordered_map; +// State-aware callback types +using CallbackContainer = std::unordered_map>; +using Callbacks = std::unordered_map; + +class EventManager { +public: + EventManager(); + ~EventManager(); + + bool AddBinding(Binding* l_binding); + bool RemoveBinding(std::string l_name); + + void SetFocus(const bool& l_focus); + void SetCurrentState(StateType l_state); + + // Needs to be defined in the header! + template + bool AddCallback(StateType l_state, const std::string& l_name, + void(T::*l_func)(EventDetails*), T* l_instance) { + auto itr = m_callbacks.emplace(l_state, CallbackContainer()).first; + auto temp = std::bind(l_func, l_instance, std::placeholders::_1); + return itr->second.emplace(l_name, temp).second; + } + + bool RemoveCallback(StateType l_state, const std::string& l_name); + + void HandleEvent(t_event& l_event); + void Update(); + +private: + void LoadBindings(); + + Bindings m_bindings; + Callbacks m_callbacks; + bool m_hasFocus; + StateType m_currentState; +}; + +// SFML types used in this file: +// - sf::Event: Event type from SFML, used as base values for EventType enum + +#endif \ No newline at end of file diff --git a/client/src/StateGame.cpp b/client/src/StateGame.cpp new file mode 100644 index 0000000..2d160a2 --- /dev/null +++ b/client/src/StateGame.cpp @@ -0,0 +1,213 @@ +#include "StateGame.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include +#include + +StateGame::StateGame(StateManager* l_stateManager) + : BaseState(l_stateManager) + , m_world(sf::Vector2u(800, 600)) + , m_snake(m_world.GetBlockSize()) + , m_textbox() // Must be in initializer list to match declaration order + , m_isDying(false) + , m_deathAnimationTime(0.0f) { + // World is initialized with default size (800, 600) + // Window size will be updated in OnCreate() if needed +} + +StateGame::~StateGame() { +} + +void StateGame::OnCreate() { + // Seed random number generator for apple placement + srand(time(nullptr)); + + // Get window size (World is already initialized in constructor with default size) + // Note: World constructor takes window size, so it's set during member initialization + // If window size differs from 800x600, we'd need to recreate World here + // For now, assuming window is 800x600 as per Game constructor + + // Reset snake to starting position + m_snake.Reset(); + + // Setup textbox + m_textbox.Setup(5, 14, 350, sf::Vector2f(225, 0)); + m_textbox.Add("Snake game started!"); + + // Register input callbacks + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + // Arrow keys + evMgr->AddCallback(StateType::Game, "Key_Up", + &StateGame::MoveUp, this); + evMgr->AddCallback(StateType::Game, "Key_Down", + &StateGame::MoveDown, this); + evMgr->AddCallback(StateType::Game, "Key_Left", + &StateGame::MoveLeft, this); + evMgr->AddCallback(StateType::Game, "Key_Right", + &StateGame::MoveRight, this); + // WASD keys (same callbacks as arrow keys) + evMgr->AddCallback(StateType::Game, "Key_W", + &StateGame::MoveUp, this); + evMgr->AddCallback(StateType::Game, "Key_S", + &StateGame::MoveDown, this); + evMgr->AddCallback(StateType::Game, "Key_A", + &StateGame::MoveLeft, this); + evMgr->AddCallback(StateType::Game, "Key_D", + &StateGame::MoveRight, this); + // Other controls + evMgr->AddCallback(StateType::Game, "Key_P", + &StateGame::Pause, this); + evMgr->AddCallback(StateType::Game, "Key_Escape", + &StateGame::MainMenu, this); +} + +void StateGame::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + // Arrow keys + evMgr->RemoveCallback(StateType::Game, "Key_Up"); + evMgr->RemoveCallback(StateType::Game, "Key_Down"); + evMgr->RemoveCallback(StateType::Game, "Key_Left"); + evMgr->RemoveCallback(StateType::Game, "Key_Right"); + // WASD keys + evMgr->RemoveCallback(StateType::Game, "Key_W"); + evMgr->RemoveCallback(StateType::Game, "Key_S"); + evMgr->RemoveCallback(StateType::Game, "Key_A"); + evMgr->RemoveCallback(StateType::Game, "Key_D"); + // Other controls + evMgr->RemoveCallback(StateType::Game, "Key_P"); + evMgr->RemoveCallback(StateType::Game, "Key_Escape"); +} + +void StateGame::Activate() { +} + +void StateGame::Deactivate() { +} + +void StateGame::Update(const sf::Time& l_time) { + // Update death animation timer + if (m_isDying) { + m_deathAnimationTime -= l_time.asSeconds(); + if (m_deathAnimationTime <= 0.0f) { + // Animation finished, switch to game over + std::cout << "Death animation finished. Switching to GameOver state..." << std::endl; + m_stateMgr->SwitchTo(StateType::GameOver); + } + } + + // Accumulate elapsed time for snake movement timing + static float elapsed = 0.0f; + elapsed += l_time.asSeconds(); + + // Snake moves based on speed (default speed is 15, meaning 15 moves per second) + // So we need to move every 1/15 seconds = 0.0667 seconds + float moveInterval = 1.0f / static_cast(m_snake.GetSpeed()); + + if (elapsed >= moveInterval) { + elapsed -= moveInterval; + + // Check if snake would hit wall before moving + if (!m_snake.HasLost()) { + sf::Vector2i currentPos = m_snake.GetPosition(); + Direction currentDir = m_snake.GetDirection(); + + if (m_world.WouldHitWall(currentPos, currentDir)) { + std::cout << "Preventing move: Snake would hit wall at (" + << currentPos.x << ", " << currentPos.y + << ") moving " << static_cast(currentDir) << std::endl; + m_snake.Lose(); // Stop the snake + m_isDying = true; // Start death animation + m_deathAnimationTime = 2.0f; // 2 seconds of blinking + } else { + // Update snake (movement and collision detection) + m_snake.Tick(); + + // Update world (check for apple eaten, wall collision) + m_world.Update(m_snake, m_textbox); + } + } else { + // Snake has lost, don't move + // But still update world for apple checks (though snake won't move) + } + + // Debug: Check snake state + static int moveCount = 0; + std::cout << "Move " << moveCount << ": Snake position: (" + << m_snake.GetPosition().x << ", " << m_snake.GetPosition().y + << "), Lost: " << m_snake.HasLost() + << ", Body size: " << m_snake.GetPosition().x << std::endl; + moveCount++; + + // Check for game over conditions (this shouldn't happen here since we check before moving) + // But keeping it as a safety check + if (m_snake.HasLost() && !m_isDying) { + std::cout << "Game Over detected! Starting death animation..." << std::endl; + m_isDying = true; + m_deathAnimationTime = 2.0f; + } + } +} + +void StateGame::Draw() { + sf::RenderWindow* window = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow(); + + // Draw world (walls and apple) + m_world.Render(*window); + + // Draw snake with blinking effect if dying + if (m_isDying) { + // Blink effect: invert colors every 0.1 seconds (10 times per second) + // Use modulo to create blinking effect + int blinkPhase = static_cast(m_deathAnimationTime * 10.0f) % 2; + bool invertColors = (blinkPhase == 1); + m_snake.Render(*window, invertColors); + } else { + m_snake.Render(*window); + } + + // Draw textbox + m_textbox.Render(*window); +} + +void StateGame::MoveUp(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + std::cout << "MoveUp called! Current direction: " << static_cast(m_snake.GetPhysicalDirection()) + << ", Body size: " << m_snake.GetPosition().x << "," << m_snake.GetPosition().y << std::endl; + if (m_snake.GetPhysicalDirection() != Direction::Down) { + m_snake.SetDirection(Direction::Up); + } +} + +void StateGame::MoveDown(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + if (m_snake.GetPhysicalDirection() != Direction::Up) { + m_snake.SetDirection(Direction::Down); + } +} + +void StateGame::MoveLeft(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + if (m_snake.GetPhysicalDirection() != Direction::Right) { + m_snake.SetDirection(Direction::Left); + } +} + +void StateGame::MoveRight(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + if (m_snake.GetPhysicalDirection() != Direction::Left) { + m_snake.SetDirection(Direction::Right); + } +} + +void StateGame::Pause(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::Paused); +} + +void StateGame::MainMenu(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::MainMenu); +} diff --git a/client/src/StateGame.hpp b/client/src/StateGame.hpp new file mode 100644 index 0000000..7b704a3 --- /dev/null +++ b/client/src/StateGame.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include "World.hpp" +#include "Snake.hpp" +#include "TextBox.hpp" +#include + +class StateGame : public BaseState { +public: + StateGame(StateManager* l_stateManager); + ~StateGame(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + // Input callbacks + void MoveUp(EventDetails* l_details); + void MoveDown(EventDetails* l_details); + void MoveLeft(EventDetails* l_details); + void MoveRight(EventDetails* l_details); + void Pause(EventDetails* l_details); + void MainMenu(EventDetails* l_details); + +private: + World m_world; + Snake m_snake; + Textbox m_textbox; + bool m_isDying; // Whether snake is in death animation + float m_deathAnimationTime; // Time remaining for death animation +}; + +// SFML types used in this file: +// - sf::Time: Time duration type, used in Update() method parameter +// - sf::RenderWindow: Window object used for rendering graphics, accessed through shared context + diff --git a/client/src/StateGameOver.cpp b/client/src/StateGameOver.cpp new file mode 100644 index 0000000..308f862 --- /dev/null +++ b/client/src/StateGameOver.cpp @@ -0,0 +1,97 @@ +#include "StateGameOver.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include + +StateGameOver::StateGameOver(StateManager* l_stateManager) + : BaseState(l_stateManager), m_text(m_font), m_instructionText(m_font) { +} + +StateGameOver::~StateGameOver() { +} + +void StateGameOver::OnCreate() { + SetTransparent(true); // Set our transparency flag. + if (!m_font.openFromFile("assets/arial/ARIAL.TTF")) { + std::cerr << "Error: Failed to load font from assets/arial/ARIAL.TTF" << std::endl; + } + m_text.setFont(m_font); + m_text.setString(sf::String("GAME OVER")); + m_text.setCharacterSize(18); + m_text.setStyle(sf::Text::Bold); + sf::Vector2u windowSize = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow()->getSize(); + sf::FloatRect textRect = m_text.getLocalBounds(); + m_text.setOrigin(sf::Vector2f(textRect.position.x + textRect.size.x / 2.0f, + textRect.position.y + textRect.size.y / 2.0f)); + m_text.setPosition(sf::Vector2f(windowSize.x / 2.0f, windowSize.y / 2.0f - 20.0f)); + + // Add instruction text below + sf::Text instructionText(m_font); + instructionText.setString(sf::String("Press any key to return to menu")); + instructionText.setCharacterSize(12); + sf::FloatRect instructionRect = instructionText.getLocalBounds(); + instructionText.setOrigin(sf::Vector2f(instructionRect.position.x + instructionRect.size.x / 2.0f, + instructionRect.position.y + instructionRect.size.y / 2.0f)); + instructionText.setPosition(sf::Vector2f(windowSize.x / 2.0f, windowSize.y / 2.0f + 20.0f)); + + m_rect.setSize(sf::Vector2f(windowSize)); + m_rect.setPosition(sf::Vector2f(0, 0)); + m_rect.setFillColor(sf::Color(0, 0, 0, 150)); + + // Register callback for any key press - register common keys + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + // Register multiple common keys to handle "any key" behavior + evMgr->AddCallback(StateType::GameOver, "Intro_Continue", // Spacebar + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_Escape", + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_P", + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_Up", + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_Down", + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_Left", + &StateGameOver::ReturnToMenu, this); + evMgr->AddCallback(StateType::GameOver, "Key_Right", + &StateGameOver::ReturnToMenu, this); +} + +void StateGameOver::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->RemoveCallback(StateType::GameOver, "Intro_Continue"); + evMgr->RemoveCallback(StateType::GameOver, "Key_Escape"); + evMgr->RemoveCallback(StateType::GameOver, "Key_P"); + evMgr->RemoveCallback(StateType::GameOver, "Key_Up"); + evMgr->RemoveCallback(StateType::GameOver, "Key_Down"); + evMgr->RemoveCallback(StateType::GameOver, "Key_Left"); + evMgr->RemoveCallback(StateType::GameOver, "Key_Right"); +} + +void StateGameOver::Activate() { +} + +void StateGameOver::Deactivate() { +} + +void StateGameOver::Update(const sf::Time& l_time) { + (void)l_time; // Suppress unused parameter warning +} + +void StateGameOver::Draw() { + sf::RenderWindow* wind = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow(); + wind->draw(m_rect); + wind->draw(m_text); + wind->draw(m_instructionText); +} + +void StateGameOver::ReturnToMenu(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::MainMenu); + m_stateMgr->Remove(StateType::GameOver); +} + diff --git a/client/src/StateGameOver.hpp b/client/src/StateGameOver.hpp new file mode 100644 index 0000000..d3fa071 --- /dev/null +++ b/client/src/StateGameOver.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include + +class StateGameOver : public BaseState { +public: + StateGameOver(StateManager* l_stateManager); + ~StateGameOver(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + void ReturnToMenu(EventDetails* l_details); + +private: + sf::Font m_font; + sf::Text m_text; + sf::Text m_instructionText; + sf::RectangleShape m_rect; +}; + diff --git a/client/src/StateIntro.cpp b/client/src/StateIntro.cpp new file mode 100644 index 0000000..cca12b6 --- /dev/null +++ b/client/src/StateIntro.cpp @@ -0,0 +1,153 @@ +#include "StateIntro.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include +#include // For timing verification (using static variable, not member) + +StateIntro::StateIntro(StateManager* l_stateManager) + : BaseState(l_stateManager), m_introSprite(m_introTexture), m_text(m_font), + m_timePassed(0.0f) /*, m_startTime() */ { // COMMENTED OUT: m_startTime initialization +} + +StateIntro::~StateIntro() { +} + +void StateIntro::OnCreate() { + // Initialize time tracking + m_timePassed = 0.0f; + + // Get window size for positioning + sf::Vector2u windowSize = m_stateMgr->GetContext()-> + m_wind->GetRenderWindow()->getSize(); + + // Load and setup intro sprite + if (!m_introTexture.loadFromFile("assets/intro.png")) { + std::cerr << "Error: Failed to load texture from assets/intro.png" << std::endl; + } else { + std::cout << "Intro texture loaded successfully. Size: " + << m_introTexture.getSize().x << "x" << m_introTexture.getSize().y << std::endl; + // Set texture with resetRect=true to update sprite bounds after loading + m_introSprite.setTexture(m_introTexture, true); + sf::Vector2u textureSize = m_introTexture.getSize(); + m_introSprite.setOrigin(sf::Vector2f(textureSize.x / 2.0f, + textureSize.y / 2.0f)); + m_introSprite.setPosition(sf::Vector2f(windowSize.x / 2.0f, 0)); + std::cout << "Intro sprite positioned at (" << windowSize.x / 2.0f << ", 0)" << std::endl; + std::cout << "Texture size: " << textureSize.x << "x" << textureSize.y << std::endl; + // Verify sprite bounds + sf::FloatRect bounds = m_introSprite.getGlobalBounds(); + std::cout << "Sprite global bounds: (" << bounds.position.x << ", " << bounds.position.y + << ") size: " << bounds.size.x << "x" << bounds.size.y << std::endl; + } + + // Load and setup text + if (!m_font.openFromFile("assets/arial/ARIAL.TTF")) { + std::cerr << "Error: Failed to load font from assets/arial/ARIAL.TTF" << std::endl; + } + m_text.setFont(m_font); + m_text.setString({ "Press SPACE to continue" }); + m_text.setCharacterSize(15); + sf::FloatRect textRect = m_text.getLocalBounds(); + m_text.setOrigin(sf::Vector2f(textRect.position.x + textRect.size.x / 2.0f, + textRect.position.y + textRect.size.y / 2.0f)); + m_text.setPosition(sf::Vector2f(windowSize.x / 2.0f, windowSize.y / 2.0f)); + + // Bind Spacebar key to Continue method + // Get event manager through shared context and register callback with state type + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->AddCallback(StateType::Intro, "Intro_Continue", + &StateIntro::Continue, this); +} + +void StateIntro::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->RemoveCallback(StateType::Intro, "Intro_Continue"); +} + +void StateIntro::Activate() { +} + +void StateIntro::Deactivate() { +} + +void StateIntro::Update(const sf::Time& l_time) { + m_timePassed += l_time.asSeconds(); + + // COMMENTED OUT: Timing verification with chrono member variable - caused AddressSanitizer heap buffer overflow + // Verify timing with real clock (for debugging) + // auto now = std::chrono::steady_clock::now(); + // auto realElapsed = std::chrono::duration_cast(now - m_startTime).count() / 1000.0f; + // + // // Log comparison every second + // static float lastLogTime = 0.0f; + // if (m_timePassed - lastLogTime >= 1.0f) { + // std::cout << "SFML time: " << m_timePassed << "s, Real time: " << realElapsed + // << "s, Delta: " << l_time.asSeconds() << "s" << std::endl; + // lastLogTime = m_timePassed; + // } + + // Simple timing verification using static variable (doesn't change class layout) + static bool firstCall = true; + static std::chrono::steady_clock::time_point startTime; + if (firstCall) { + startTime = std::chrono::steady_clock::now(); + firstCall = false; + } + + // Log comparison every second + static float lastLogTime = 0.0f; + if (m_timePassed - lastLogTime >= 1.0f) { + auto now = std::chrono::steady_clock::now(); + auto realElapsed = std::chrono::duration_cast(now - startTime).count() / 1000.0f; + std::cout << "SFML time: " << m_timePassed << "s, Real time: " << realElapsed + << "s, Delta: " << l_time.asSeconds() << "s" << std::endl; + lastLogTime = m_timePassed; + } + + if (m_timePassed < 5.0f) { // Less than five seconds - sprite animates down + sf::Vector2f currentPos = m_introSprite.getPosition(); + m_introSprite.setPosition(sf::Vector2f(currentPos.x, + currentPos.y + (48 * l_time.asSeconds()))); + } + // After 5 seconds, sprite stops moving and text becomes available +} + +void StateIntro::Draw() { + sf::RenderWindow* window = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow(); + window->draw(m_introSprite); + if (m_timePassed >= 5.0f) { + window->draw(m_text); + } +} + +void StateIntro::Continue(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + if (m_timePassed >= 5.0f) { + m_stateMgr->SwitchTo(StateType::MainMenu); + m_stateMgr->Remove(StateType::Intro); + } +} + +// SFML types and functions used in this file: +// - sf::Texture::loadFromFile(): Loads texture from file +// - sf::Texture::getSize(): Gets texture dimensions +// - sf::Sprite::setTexture(): Sets the texture for the sprite +// - sf::Sprite::setOrigin(): Sets the origin point of the sprite +// - sf::Sprite::setPosition(): Sets the position of the sprite +// - sf::Sprite::getPosition(): Gets the current position of the sprite +// - sf::Font::openFromFile(): Loads font from file +// - sf::Text::setFont(): Sets the font for the text +// - sf::Text::setString(): Sets the text string +// - sf::Text::setCharacterSize(): Sets the character size +// - sf::Text::getLocalBounds(): Gets the local bounding rectangle +// - sf::Text::setOrigin(): Sets the origin point of the text +// - sf::Text::setPosition(): Sets the position of the text +// - sf::Time::asSeconds(): Converts time to seconds (float) +// - sf::Vector2u: 2D vector with unsigned integer components +// - sf::FloatRect: Rectangle with float components +// - sf::RenderWindow::draw(): Draws a drawable object +// - sf::RenderWindow::getSize(): Gets the window size + diff --git a/client/src/StateIntro.hpp b/client/src/StateIntro.hpp new file mode 100644 index 0000000..6581c2d --- /dev/null +++ b/client/src/StateIntro.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include +// #include // COMMENTED OUT: Caused AddressSanitizer heap buffer overflow + +class StateIntro : public BaseState { +public: + StateIntro(StateManager* l_stateManager); + ~StateIntro(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + void Continue(EventDetails* l_details); + +private: + sf::Texture m_introTexture; + sf::Sprite m_introSprite; + sf::Font m_font; + sf::Text m_text; + float m_timePassed; + // std::chrono::steady_clock::time_point m_startTime; // COMMENTED OUT: For timing verification - caused AddressSanitizer heap buffer overflow +}; + +// SFML types used in this file: +// - sf::Texture: Image texture, used for m_introTexture +// - sf::Sprite: Drawable sprite, used for m_introSprite +// - sf::Font: Font object used for text rendering (m_font) +// - sf::Text: Text object used for displaying messages (m_text) +// - sf::Time: Time duration type, used in Update() method parameter +// - sf::Vector2u: 2D vector with unsigned integer components, used for window size +// - sf::FloatRect: Rectangle with float components, used for text bounds calculation +// - sf::RenderWindow: Window object used for rendering graphics, accessed through shared context + + diff --git a/client/src/StateMainMenu.cpp b/client/src/StateMainMenu.cpp new file mode 100644 index 0000000..d6e7e89 --- /dev/null +++ b/client/src/StateMainMenu.cpp @@ -0,0 +1,129 @@ +#include "StateMainMenu.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include + +StateMainMenu::StateMainMenu(StateManager* l_stateManager) + : BaseState(l_stateManager), m_text(m_font), m_labels{m_font, m_font, m_font, m_font} { +} + +StateMainMenu::~StateMainMenu() { +} + +void StateMainMenu::OnCreate() { + if (!m_font.openFromFile("assets/arial/ARIAL.TTF")) { + std::cerr << "Error: Failed to load font from assets/arial/ARIAL.TTF" << std::endl; + } + m_text.setFont(m_font); + m_text.setString(sf::String("MAIN MENU:")); + m_text.setCharacterSize(18); + sf::FloatRect textRect = m_text.getLocalBounds(); + m_text.setOrigin(sf::Vector2f(textRect.position.x + textRect.size.x / 2.0f, + textRect.position.y + textRect.size.y / 2.0f)); + m_text.setPosition(sf::Vector2f(400, 100)); + + m_buttonSize = sf::Vector2f(300.0f, 32.0f); + m_buttonPos = sf::Vector2f(400, 200); + m_buttonPadding = 4; // 4px. + + std::string str[4]; + str[0] = "PLAY"; + str[1] = "MUSHROOM"; + str[2] = "CREDITS"; + str[3] = "EXIT"; + + for (int i = 0; i < 4; ++i) { + sf::Vector2f buttonPosition(m_buttonPos.x, m_buttonPos.y + + (i * (m_buttonSize.y + m_buttonPadding))); + m_rects[i].setSize(m_buttonSize); + m_rects[i].setFillColor(sf::Color::Red); + m_rects[i].setOrigin(sf::Vector2f(m_buttonSize.x / 2.0f, + m_buttonSize.y / 2.0f)); + m_rects[i].setPosition(buttonPosition); + m_labels[i].setFont(m_font); + m_labels[i].setString(sf::String(str[i])); + m_labels[i].setCharacterSize(12); + sf::FloatRect rect = m_labels[i].getLocalBounds(); + m_labels[i].setOrigin(sf::Vector2f(rect.position.x + rect.size.x / 2.0f, + rect.position.y + rect.size.y / 2.0f)); + m_labels[i].setPosition(buttonPosition); + } + + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->AddCallback(StateType::MainMenu, "Mouse_Left", + &StateMainMenu::MouseClick, this); +} + +void StateMainMenu::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->RemoveCallback(StateType::MainMenu, "Mouse_Left"); +} + +void StateMainMenu::Activate() { + if (m_stateMgr->HasState(StateType::Game) + && m_labels[0].getString() == "PLAY") + { + m_labels[0].setString(sf::String("RESUME")); + sf::FloatRect rect = m_labels[0].getLocalBounds(); + m_labels[0].setOrigin(sf::Vector2f(rect.position.x + rect.size.x / 2.0f, + rect.position.y + rect.size.y / 2.0f)); + } +} + +void StateMainMenu::Deactivate() { +} + +void StateMainMenu::Update(const sf::Time& l_time) { + (void)l_time; // Suppress unused parameter warning +} + +void StateMainMenu::Draw() { + sf::RenderWindow* window = m_stateMgr->GetContext()-> + m_wind->GetRenderWindow(); + window->draw(m_text); + for (int i = 0; i < 4; ++i) { + window->draw(m_rects[i]); + window->draw(m_labels[i]); + } +} + +void StateMainMenu::MouseClick(EventDetails* l_details) { + sf::Vector2i mousePos = l_details->m_mouse; + std::cout << "MouseClick called! Mouse position: (" << mousePos.x << ", " << mousePos.y << ")" << std::endl; + + float halfX = m_buttonSize.x / 2.0f; + float halfY = m_buttonSize.y / 2.0f; + for (int i = 0; i < 4; ++i) { + sf::Vector2f buttonPos = m_rects[i].getPosition(); + float left = buttonPos.x - halfX; + float right = buttonPos.x + halfX; + float top = buttonPos.y - halfY; + float bottom = buttonPos.y + halfY; + + std::cout << "Button " << i << " bounds: left=" << left << ", right=" << right + << ", top=" << top << ", bottom=" << bottom << std::endl; + + if (mousePos.x >= left && mousePos.x <= right && + mousePos.y >= top && mousePos.y <= bottom) + { + std::cout << "Button " << i << " clicked!" << std::endl; + if (i == 0) { + // PLAY button - switch to actual snake game + std::cout << "Switching to Game state (snake game)" << std::endl; + m_stateMgr->SwitchTo(StateType::Game); + } else if (i == 1) { + // MUSHROOM button - switch to bouncing mushroom demo + std::cout << "Switching to Mushroom state (demo)" << std::endl; + m_stateMgr->SwitchTo(StateType::Mushroom); + } else if (i == 2) { + // Credits state. + } else if (i == 3) { + std::cout << "Closing window" << std::endl; + m_stateMgr->GetContext()->m_wind->Close(); + } + } + } +} + diff --git a/client/src/StateMainMenu.hpp b/client/src/StateMainMenu.hpp new file mode 100644 index 0000000..31ce3f1 --- /dev/null +++ b/client/src/StateMainMenu.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include + +class StateMainMenu : public BaseState { +public: + StateMainMenu(StateManager* l_stateManager); + ~StateMainMenu(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + void MouseClick(EventDetails* l_details); + +private: + sf::Font m_font; + sf::Text m_text; + sf::Vector2f m_buttonSize; + sf::Vector2f m_buttonPos; + unsigned int m_buttonPadding; + sf::RectangleShape m_rects[4]; + sf::Text m_labels[4]; +}; + +// SFML types used in this file: +// - sf::Font: Font object used for text rendering (m_font) +// - sf::Text: Text object used for displaying menu title and button labels (m_text, m_labels) +// - sf::Vector2f: 2D vector with float components, used for button size and position +// - sf::RectangleShape: Drawable rectangle shape, used for button backgrounds (m_rects) +// - sf::Time: Time duration type, used in Update() method parameter +// - sf::RenderWindow: Window object used for rendering graphics, accessed through shared context + + + + + + diff --git a/client/src/StateManager.cpp b/client/src/StateManager.cpp new file mode 100644 index 0000000..d1b9311 --- /dev/null +++ b/client/src/StateManager.cpp @@ -0,0 +1,141 @@ +#include "StateManager.hpp" +#include "Window.hpp" +#include "StateIntro.hpp" +#include "StateMainMenu.hpp" +#include "StateGame.hpp" +#include "StateMushroom.hpp" +#include "StatePaused.hpp" +#include "StateGameOver.hpp" +#include + +// NOTE: RegisterState template implementation is in the header file (StateManager.hpp) +// because template methods must be defined in headers for proper instantiation. +// The template includes a static_assert to ensure type safety at compile time. + +StateManager::StateManager(SharedContext* l_shared) +: m_shared(l_shared) +{ + // State registration + RegisterState(StateType::Intro); + RegisterState(StateType::MainMenu); + RegisterState(StateType::Game); + RegisterState(StateType::Mushroom); + RegisterState(StateType::Paused); + RegisterState(StateType::GameOver); +} + +StateManager::~StateManager() { + for (auto& itr : m_states) { + itr.second->OnDestroy(); + delete itr.second; + } +} + +void StateManager::Update(const sf::Time& l_time) { + if (m_states.empty()) { return; } + + if (m_states.back().second->IsTranscendent() && m_states.size() > 1) { + auto itr = m_states.end(); + while (itr != m_states.begin()) { + if (itr != m_states.end()) { + if (!itr->second->IsTranscendent()) { + break; + } + } + --itr; + } + for (; itr != m_states.end(); ++itr) { + itr->second->Update(l_time); + } + } else { + m_states.back().second->Update(l_time); + } +} + +void StateManager::Draw() { + if (m_states.empty()) { return; } + + if (m_states.back().second->IsTransparent() && m_states.size() > 1) { + auto itr = m_states.end(); + while (itr != m_states.begin()) { + if (itr != m_states.end()) { + if (!itr->second->IsTransparent()) { + break; + } + } + --itr; + } + for (; itr != m_states.end(); ++itr) { + itr->second->Draw(); + } + } else { + m_states.back().second->Draw(); + } +} + +SharedContext* StateManager::GetContext() { + return m_shared; +} + +bool StateManager::HasState(const StateType& l_type) { + for (auto itr = m_states.begin(); itr != m_states.end(); ++itr) { + if (itr->first == l_type) { + auto removed = std::find(m_toRemove.begin(), m_toRemove.end(), l_type); + if (removed == m_toRemove.end()) { return true; } + return false; + } + } + return false; +} + +void StateManager::Remove(const StateType& l_type) { + m_toRemove.push_back(l_type); +} + +void StateManager::ProcessRequests() { + while (m_toRemove.begin() != m_toRemove.end()) { + RemoveState(*m_toRemove.begin()); + m_toRemove.erase(m_toRemove.begin()); + } +} + +void StateManager::SwitchTo(const StateType& l_type) { + m_shared->m_eventManager->SetCurrentState(l_type); + + for (auto itr = m_states.begin(); itr != m_states.end(); ++itr) { + if (itr->first == l_type) { + m_states.back().second->Deactivate(); + StateType tmp_type = itr->first; + BaseState* tmp_state = itr->second; + m_states.erase(itr); + m_states.emplace_back(tmp_type, tmp_state); + tmp_state->Activate(); + return; + } + } + + // State with l_type wasn't found. + if (!m_states.empty()) { m_states.back().second->Deactivate(); } + CreateState(l_type); + m_states.back().second->Activate(); +} + +void StateManager::CreateState(const StateType& l_type) { + auto newState = m_stateFactory.find(l_type); + if (newState == m_stateFactory.end()) { return; } + BaseState* state = newState->second(); + m_states.emplace_back(l_type, state); + state->OnCreate(); +} + +void StateManager::RemoveState(const StateType& l_type) { + for (auto itr = m_states.begin(); itr != m_states.end(); ++itr) { + if (itr->first == l_type) { + itr->second->OnDestroy(); + delete itr->second; + m_states.erase(itr); + return; + } + } +} + diff --git a/client/src/StateManager.hpp b/client/src/StateManager.hpp new file mode 100644 index 0000000..8347333 --- /dev/null +++ b/client/src/StateManager.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "BaseState.hpp" +#include +#include +#include +#include +#include +#include + +// Forward declarations +class Window; +class EventManager; + +enum class StateType { + Global = 0, + Intro, + MainMenu, + Game, + Mushroom, + Paused, + GameOver, + Credits +}; + +/// @brief Shared context structure providing access to common resources for all states. +/// +/// This structure holds pointers to resources that are shared across all game states. +/// Instead of each state maintaining its own copies of Window and EventManager, they +/// all access the same instances through this shared context. +/// +/// The SharedContext is: +/// - Created once in the Game class +/// - Passed to the StateManager constructor +/// - Accessed by states through m_stateMgr->GetContext() +/// +/// @note This structure can be extended later to include additional shared resources +/// such as player data, resource managers, sound managers, or networking components. +/// +/// @example +/// @code +/// // In any state, access shared resources: +/// void StateIntro::OnCreate() { +/// sf::Vector2u windowSize = m_stateMgr->GetContext()-> +/// m_wind->GetRenderWindow()->getSize(); +/// +/// EventManager* evMgr = m_stateMgr->GetContext()->m_eventManager; +/// evMgr->AddCallback(StateType::Intro, "Intro_Continue", +/// &StateIntro::Continue, this); +/// } +/// @endcode +struct SharedContext { + /// @brief Default constructor initializes all pointers to nullptr. + SharedContext() : m_wind(nullptr), m_eventManager(nullptr) {} + + /// @brief Pointer to the main window instance (shared across all states). + Window* m_wind; + + /// @brief Pointer to the event manager instance (shared across all states). + EventManager* m_eventManager; +}; + +// Type definitions +using StateContainer = std::vector>; +using TypeContainer = std::vector; +using StateFactory = std::unordered_map>; + +class StateManager { +public: + StateManager(SharedContext* l_shared); + ~StateManager(); + + void Update(const sf::Time& l_time); + void Draw(); + void ProcessRequests(); + + SharedContext* GetContext(); + bool HasState(const StateType& l_type); + void SwitchTo(const StateType& l_type); + void Remove(const StateType& l_type); + + template + void RegisterState(const StateType& l_type); + +private: + void CreateState(const StateType& l_type); + void RemoveState(const StateType& l_type); + + SharedContext* m_shared; + StateContainer m_states; + TypeContainer m_toRemove; + StateFactory m_stateFactory; +}; + +// Template implementation +// NOTE: Template methods must be in the header file for proper instantiation. +// Type safety: static_assert ensures T derives from BaseState at compile time. +template +void StateManager::RegisterState(const StateType& l_type) { + static_assert(std::is_base_of_v, + "T must derive from BaseState"); + m_stateFactory[l_type] = [this]() -> BaseState* { + return new T(this); + }; +} + diff --git a/client/src/StateMushroom.cpp b/client/src/StateMushroom.cpp new file mode 100644 index 0000000..17e0a34 --- /dev/null +++ b/client/src/StateMushroom.cpp @@ -0,0 +1,76 @@ +#include "StateMushroom.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include + +StateMushroom::StateMushroom(StateManager* l_stateManager) + : BaseState(l_stateManager), m_sprite(m_texture) { +} + +StateMushroom::~StateMushroom() { +} + +void StateMushroom::OnCreate() { + if (!m_texture.loadFromFile("assets/Mushroom.png")) { + std::cerr << "Error: Failed to load texture from assets/Mushroom.png" << std::endl; + } + m_sprite.setTexture(m_texture, true); + m_sprite.setPosition(sf::Vector2f(0, 0)); + m_increment = sf::Vector2f(400.0f, 400.0f); + + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->AddCallback(StateType::Mushroom, "Key_Escape", + &StateMushroom::MainMenu, this); + evMgr->AddCallback(StateType::Mushroom, "Key_P", + &StateMushroom::Pause, this); +} + +void StateMushroom::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->RemoveCallback(StateType::Mushroom, "Key_Escape"); + evMgr->RemoveCallback(StateType::Mushroom, "Key_P"); +} + +void StateMushroom::Activate() { +} + +void StateMushroom::Deactivate() { +} + +void StateMushroom::Update(const sf::Time& l_time) { + sf::Vector2u l_windSize = m_stateMgr->GetContext()-> + m_wind->GetWindowSize(); + sf::Vector2u l_textSize = m_texture.getSize(); + + if ((m_sprite.getPosition().x > l_windSize.x - l_textSize.x && m_increment.x > 0) || + (m_sprite.getPosition().x < 0 && m_increment.x < 0)) + { + m_increment.x = -m_increment.x; + } + if ((m_sprite.getPosition().y > l_windSize.y - l_textSize.y && m_increment.y > 0) || + (m_sprite.getPosition().y < 0 && m_increment.y < 0)) + { + m_increment.y = -m_increment.y; + } + m_sprite.setPosition(sf::Vector2f( + m_sprite.getPosition().x + (m_increment.x * l_time.asSeconds()), + m_sprite.getPosition().y + (m_increment.y * l_time.asSeconds()))); +} + +void StateMushroom::Draw() { + m_stateMgr->GetContext()->m_wind-> + GetRenderWindow()->draw(m_sprite); +} + +void StateMushroom::MainMenu(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::MainMenu); +} + +void StateMushroom::Pause(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::Paused); +} + diff --git a/client/src/StateMushroom.hpp b/client/src/StateMushroom.hpp new file mode 100644 index 0000000..c856eff --- /dev/null +++ b/client/src/StateMushroom.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include + +class StateMushroom : public BaseState { +public: + StateMushroom(StateManager* l_stateManager); + ~StateMushroom(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + void MainMenu(EventDetails* l_details); + void Pause(EventDetails* l_details); + +private: + sf::Texture m_texture; + sf::Sprite m_sprite; + sf::Vector2f m_increment; +}; + +// SFML types used in this file: +// - sf::Texture: Image texture, used for m_texture +// - sf::Sprite: Drawable sprite, used for m_sprite +// - sf::Vector2f: 2D vector with float components, used for m_increment (velocity) +// - sf::Time: Time duration type, used in Update() method parameter +// - sf::RenderWindow: Window object used for rendering graphics, accessed through shared context + diff --git a/client/src/StatePaused.cpp b/client/src/StatePaused.cpp new file mode 100644 index 0000000..87abab1 --- /dev/null +++ b/client/src/StatePaused.cpp @@ -0,0 +1,64 @@ +#include "StatePaused.hpp" +#include "StateManager.hpp" +#include "Window.hpp" +#include + +StatePaused::StatePaused(StateManager* l_stateManager) + : BaseState(l_stateManager), m_text(m_font) { +} + +StatePaused::~StatePaused() { +} + +void StatePaused::OnCreate() { + SetTransparent(true); // Set our transparency flag. + if (!m_font.openFromFile("assets/arial/ARIAL.TTF")) { + std::cerr << "Error: Failed to load font from assets/arial/ARIAL.TTF" << std::endl; + } + m_text.setFont(m_font); + m_text.setString(sf::String("PAUSED")); + m_text.setCharacterSize(14); + m_text.setStyle(sf::Text::Bold); + sf::Vector2u windowSize = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow()->getSize(); + sf::FloatRect textRect = m_text.getLocalBounds(); + m_text.setOrigin(sf::Vector2f(textRect.position.x + textRect.size.x / 2.0f, + textRect.position.y + textRect.size.y / 2.0f)); + m_text.setPosition(sf::Vector2f(windowSize.x / 2.0f, windowSize.y / 2.0f)); + m_rect.setSize(sf::Vector2f(windowSize)); + m_rect.setPosition(sf::Vector2f(0, 0)); + m_rect.setFillColor(sf::Color(0, 0, 0, 150)); + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->AddCallback(StateType::Paused, "Key_P", + &StatePaused::Unpause, this); +} + +void StatePaused::OnDestroy() { + EventManager* evMgr = m_stateMgr-> + GetContext()->m_eventManager; + evMgr->RemoveCallback(StateType::Paused, "Key_P"); +} + +void StatePaused::Activate() { +} + +void StatePaused::Deactivate() { +} + +void StatePaused::Update(const sf::Time& l_time) { + (void)l_time; // Suppress unused parameter warning +} + +void StatePaused::Draw() { + sf::RenderWindow* wind = m_stateMgr-> + GetContext()->m_wind->GetRenderWindow(); + wind->draw(m_rect); + wind->draw(m_text); +} + +void StatePaused::Unpause(EventDetails* l_details) { + (void)l_details; // Suppress unused parameter warning + m_stateMgr->SwitchTo(StateType::Mushroom); +} + diff --git a/client/src/StatePaused.hpp b/client/src/StatePaused.hpp new file mode 100644 index 0000000..ea509ff --- /dev/null +++ b/client/src/StatePaused.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "BaseState.hpp" +#include "EventManager.hpp" +#include + +class StatePaused : public BaseState { +public: + StatePaused(StateManager* l_stateManager); + ~StatePaused(); + + void OnCreate() override; + void OnDestroy() override; + void Activate() override; + void Deactivate() override; + void Update(const sf::Time& l_time) override; + void Draw() override; + + void Unpause(EventDetails* l_details); + +private: + sf::Font m_font; + sf::Text m_text; + sf::RectangleShape m_rect; +}; + +// SFML types used in this file: +// - sf::Font: Font object used for text rendering (m_font) +// - sf::Text: Text object used for displaying "PAUSED" message (m_text) +// - sf::RectangleShape: Drawable rectangle shape, used for semi-transparent backdrop (m_rect) +// - sf::Time: Time duration type, used in Update() method parameter +// - sf::RenderWindow: Window object used for rendering graphics, accessed through shared context + diff --git a/libs/lib1/Graphics.cpp b/libs/lib1/Graphics.cpp index 3ae30d2..a4df7ed 100644 --- a/libs/lib1/Graphics.cpp +++ b/libs/lib1/Graphics.cpp @@ -105,35 +105,47 @@ void Graphics::endFrame() t_event Graphics::checkEvents() { t_event event; - event.type = KEY; + event.type = KEY_PRESSED; - if (WindowShouldClose()) - event.type = EXIT; + if (WindowShouldClose()) + event.type = CLOSED; else if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { Vector2 mp = GetMousePosition(); - event.type = MOUSE; - event.a = (int)mp.x; - event.b = (int)mp.y; + event.type = MOUSE_BUTTON_RELEASED; // Changed to RELEASED to match keys.cfg (9:0) + event.mouse.x = (int)mp.x; + event.mouse.y = (int)mp.y; + event.mouse.button = 0; // Left button = 0 (matches keys.cfg) } - else if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) - event.a = UP; - else if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) - event.a = DOWN; - else if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_LEFT)) - event.a = LEFT; - else if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) - event.a = RIGHT; + // Map raylib key codes to SFML key codes (to match keys.cfg) + // Raylib: KEY_W=87, KEY_A=65, KEY_S=83, KEY_D=68, KEY_UP=265, KEY_DOWN=264, KEY_LEFT=263, KEY_RIGHT=262 + // SFML: W=22, A=0, S=18, D=3, Up=73, Down=74, Left=71, Right=72 + else if (IsKeyPressed(KEY_W)) + event.keyCode = 22; // SFML W + else if (IsKeyPressed(KEY_UP)) + event.keyCode = 73; // SFML Up + else if (IsKeyPressed(KEY_S)) + event.keyCode = 18; // SFML S + else if (IsKeyPressed(KEY_DOWN)) + event.keyCode = 74; // SFML Down + else if (IsKeyPressed(KEY_A)) + event.keyCode = 0; // SFML A + else if (IsKeyPressed(KEY_LEFT)) + event.keyCode = 71; // SFML Left + else if (IsKeyPressed(KEY_D)) + event.keyCode = 3; // SFML D + else if (IsKeyPressed(KEY_RIGHT)) + event.keyCode = 72; // SFML Right else if (IsKeyPressed(KEY_M)) - event.a = M; + event.keyCode = 12; // SFML M (approximate) else if (IsKeyPressed(KEY_N)) - event.a = N; + event.keyCode = 13; // SFML N (approximate) else if (IsKeyPressed(KEY_ONE)) - event.a = KEY_1; + event.keyCode = 27; // SFML Num1 (approximate) else if (IsKeyPressed(KEY_TWO)) - event.a = KEY_2; + event.keyCode = 28; // SFML Num2 (approximate) else if (IsKeyPressed(KEY_THREE)) - event.a = KEY_3; + event.keyCode = 29; // SFML Num3 (approximate) else event.type = EMPTY; diff --git a/libs/lib2/Graphics.cpp b/libs/lib2/Graphics.cpp index 5b76c4b..73a33eb 100644 --- a/libs/lib2/Graphics.cpp +++ b/libs/lib2/Graphics.cpp @@ -111,7 +111,7 @@ t_event Graphics::checkEvents() { if (event->is()) { - e.type = EXIT; + e.type = CLOSED; return e; } else if (const auto *keyPressed = event->getIf()) @@ -126,13 +126,14 @@ t_event Graphics::checkEvents() t_event Graphics::onMouseUp(const sf::Mouse::Button button, const sf::Vector2i position) { t_event event; - event.type = MOUSE; + event.type = MOUSE_BUTTON_RELEASED; // Changed to RELEASED to match keys.cfg (9:0) switch (button) { case sf::Mouse::Button::Left: - event.a = position.x; - event.b = position.y; + event.mouse.x = position.x; + event.mouse.y = position.y; + event.mouse.button = 0; // Left button = 0 (matches keys.cfg) break; default: event.type = EMPTY; @@ -145,45 +146,24 @@ t_event Graphics::onMouseUp(const sf::Mouse::Button button, const sf::Vector2i p t_event Graphics::onKeyPress(sf::Keyboard::Key code) { t_event event; - event.type = KEY; - - switch (code) - { - case sf::Keyboard::Key::W: - case sf::Keyboard::Key::Up: - event.a = UP; - break; - case sf::Keyboard::Key::S: - case sf::Keyboard::Key::Down: - event.a = DOWN; - break; - case sf::Keyboard::Key::A: - case sf::Keyboard::Key::Left: - event.a = LEFT; - break; - case sf::Keyboard::Key::D: - case sf::Keyboard::Key::Right: - event.a = RIGHT; - break; - case sf::Keyboard::Key::M: - event.a = M; - break; - case sf::Keyboard::Key::N: - event.a = N; - break; - case sf::Keyboard::Key::Num1: - event.a = KEY_1; - break; - case sf::Keyboard::Key::Num2: - event.a = KEY_2; - break; - case sf::Keyboard::Key::Num3: - event.a = KEY_3; - break; - default: - event.type = EMPTY; - break; - } - + event.type = KEY_PRESSED; + + // Return the actual SFML key code (not the actions enum) + // This matches what EventManager expects from keys.cfg + int keyCode = static_cast(code); + event.keyCode = keyCode; + + // Debug: log key press with name + std::cout << "[lib2 SFML] Key pressed: code=" << keyCode; + if (code == sf::Keyboard::Key::Up) std::cout << " (Up/73)"; + else if (code == sf::Keyboard::Key::Down) std::cout << " (Down/74)"; + else if (code == sf::Keyboard::Key::Left) std::cout << " (Left/71)"; + else if (code == sf::Keyboard::Key::Right) std::cout << " (Right/72)"; + else if (code == sf::Keyboard::Key::W) std::cout << " (W/22)"; + else if (code == sf::Keyboard::Key::A) std::cout << " (A/0)"; + else if (code == sf::Keyboard::Key::S) std::cout << " (S/18)"; + else if (code == sf::Keyboard::Key::D) std::cout << " (D/3)"; + std::cout << std::endl; + return event; } diff --git a/libs/lib4/Graphics.cpp b/libs/lib4/Graphics.cpp index 9650f63..bfcbcd4 100644 --- a/libs/lib4/Graphics.cpp +++ b/libs/lib4/Graphics.cpp @@ -182,9 +182,9 @@ t_event Graphics::checkEvents() { switch (event.type) { - case SDL_EVENT_QUIT: - e.type = EXIT; - return e; + case SDL_EVENT_QUIT: + e.type = CLOSED; + return e; case SDL_EVENT_KEY_DOWN: return onKeyPress(event.key); @@ -204,9 +204,10 @@ t_event Graphics::onMouseUp(const SDL_MouseButtonEvent &buttonEvent) if (buttonEvent.button == SDL_BUTTON_LEFT) { - event.a = buttonEvent.x; - event.b = buttonEvent.y; - event.type = MOUSE; + event.mouse.x = buttonEvent.x; + event.mouse.y = buttonEvent.y; + event.mouse.button = 0; // Left button = 0 (matches keys.cfg) + event.type = MOUSE_BUTTON_RELEASED; // Changed to RELEASED to match keys.cfg (9:0) } return event; @@ -215,42 +216,53 @@ t_event Graphics::onMouseUp(const SDL_MouseButtonEvent &buttonEvent) t_event Graphics::onKeyPress(const SDL_KeyboardEvent &keyEvent) { t_event event; - event.type = KEY; + event.type = KEY_PRESSED; SDL_Keycode code = keyEvent.key; + // Map SDL key codes to SFML key codes (to match keys.cfg) + // SDL: SDLK_W=119, SDLK_A=97, SDLK_S=115, SDLK_D=100, SDLK_UP=1073741906, SDLK_DOWN=1073741905, SDLK_LEFT=1073741904, SDLK_RIGHT=1073741903 + // SFML: W=22, A=0, S=18, D=3, Up=73, Down=74, Left=71, Right=72 switch (code) { case SDLK_W: + event.keyCode = 22; // SFML W + break; case SDLK_UP: - event.a = UP; + event.keyCode = 73; // SFML Up break; case SDLK_S: + event.keyCode = 18; // SFML S + break; case SDLK_DOWN: - event.a = DOWN; + event.keyCode = 74; // SFML Down break; case SDLK_A: + event.keyCode = 0; // SFML A + break; case SDLK_LEFT: - event.a = LEFT; + event.keyCode = 71; // SFML Left break; case SDLK_D: + event.keyCode = 3; // SFML D + break; case SDLK_RIGHT: - event.a = RIGHT; + event.keyCode = 72; // SFML Right break; case SDLK_M: - event.a = M; + event.keyCode = 12; // SFML M (approximate) break; case SDLK_N: - event.a = N; + event.keyCode = 13; // SFML N (approximate) break; case SDLK_1: - event.a = KEY_1; + event.keyCode = 27; // SFML Num1 (approximate) break; case SDLK_2: - event.a = KEY_2; + event.keyCode = 28; // SFML Num2 (approximate) break; case SDLK_3: - event.a = KEY_3; + event.keyCode = 29; // SFML Num3 (approximate) break; default: event.type = EMPTY;