diff --git a/.gitignore b/.gitignore index 1c2e22e..408deef 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ ia/.cache *.out zappy_server zappy_gui -zappy_ia +zappy_ai + +__pycache__/ vcpkg_installed/ vcpkg-bootstrap.log diff --git a/Makefile b/Makefile index 9a5d113..15ddcda 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,11 @@ BUILD_TYPE ?= Release include utils.mk -all: zappy_server zappy_gui +all: zappy_server zappy_gui zappy_ai + +zappy_ai: + @ python3 ia/compile.py + @ $(LOG_TIME) "$(C_BLUE) OK $(C_GREEN) ai built $(C_RESET)" zappy_server: check_vcpkg @ cmake -S server -B server/build -G Ninja -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_TOOLCHAIN_FILE=$(VCPKG_ROOT)/scripts/buildsystems/vcpkg.cmake @@ -51,10 +55,10 @@ clean: @ $(LOG_TIME) "$(C_YELLOW) RM $(C_PURPLE) server/build gui/build $(C_RESET)" fclean: clean - @ rm -f zappy_server zappy_gui - @ $(LOG_TIME) "$(C_YELLOW) RM $(C_PURPLE) zappy_server zappy_gui $(C_RESET)" + @ rm -f zappy_server zappy_gui zappy_ai + @ $(LOG_TIME) "$(C_YELLOW) RM $(C_PURPLE) zappy_server zappy_gui zappy_ai $(C_RESET)" re: fclean all -.PHONY: all zappy_server zappy_gui check_vcpkg debug hooks format clean fclean re \ +.PHONY: all zappy_server zappy_gui zappy_ai check_vcpkg debug hooks format clean fclean re \ tests_unit_server tests_func_server tests_run diff --git a/ai/bot.py b/ai/bot.py new file mode 100644 index 0000000..1aec2d4 --- /dev/null +++ b/ai/bot.py @@ -0,0 +1,335 @@ +import asyncio +import random +import stats +from network import Network +from const import R, S, D + +_pending = 0 + + +def _spawn(t, h, p, n=1): + global _pending + _pending += n + for _ in range(n): + asyncio.create_task(_run_bot(t, h, p)) + + +async def _run_bot(t, h, p): + global _pending + bot = Bot(t, h, p) + connected = False + try: + net = Network(h, p) + bot.n = net + await net.c() + + await asyncio.wait_for(net.q.get(), timeout=10) + await net.s(t) + resp = await asyncio.wait_for(net.q.get(), timeout=10) + + if resp == "ko" or resp == "dead": + return + + await asyncio.wait_for(net.q.get(), timeout=10) + + _pending -= 1 + connected = True + stats.add_bot() + bot.log("Connected") + + await bot.life() + + except asyncio.TimeoutError: + pass + except Exception: + pass + finally: + if not connected and _pending > 0: + _pending -= 1 + if connected: + stats.rm_bot(bot.l) + try: + if bot.n and bot.n.w and not bot.n.w.is_closing(): + bot.n.w.close() + except Exception: + pass + + +class Bot: + def __init__(self, t, h, p): + self.t = t + self.h = h + self.p = p + self.n = None + self.l = 1 + self.i = {k: 0 for k in R} + self.s = 0 + self.id = str(random.randint(0, 999999)) + self.k = sum(ord(c) for c in t) % 999 + self.ld = -1 + self.lid = None + self.forked = False + self.wt = 0 + + def log(self, msg, is_lvl=False): + m = f"[Bot {self.id} | Lvl {self.l}] {msg}" + if stats.D: + print(m, flush=True) + if is_lvl: + stats.log_evt(m) + + async def cmd(self, c): + if not self.n.w or self.n.w.is_closing(): + return "dead" + await self.n.s(c) + while True: + r = await self.n.q.get() + if r == "dead": + return "dead" + if r == "Elevation underway": + if c == "Incantation": + return r + continue + if r.startswith("Current level: "): + nl = int(r[15:]) + if nl > self.l: + old = self.l + stats.lvl_up(old, nl) + self.l = nl + self.log(f"Level up! Now level {self.l}", True) + self.s = 0 + for k in R[1:]: + self.i[k] = 0 + continue + return r + + async def look(self): + r = await self.cmd("Look") + if r == "dead": + return None + r = r.strip("[]").split(",") + return [x.strip().split() for x in r] + + async def inventory(self): + r = await self.cmd("Inventory") + if r == "dead": + return + r = r.strip("[]").split(",") + for x in r: + p = x.strip().split() + if len(p) == 2 and p[0] in self.i: + try: + self.i[p[0]] = int(p[1]) + except ValueError: + pass + + def has_stones(self): + if self.l >= 8: + return False + req = S[self.l - 1] + for idx, item in enumerate(R[1:]): + if self.i.get(item, 0) < req[idx + 1]: + return False + return True + + async def handle_events(self): + while not self.n.e.empty(): + ev, data = await self.n.e.get() + if ev == "j": + if self.s in (1, 2, 3): + self.s = 0 + self.ld = -1 + self.lid = None + self.log("Ejected") + elif ev == "m": + try: + ds, txt = data.split(",", 1) + di = int(ds.strip()) + txt = txt.strip() + if txt.startswith(f"{self.k}_R_"): + parts = txt.split("_") + if len(parts) == 4 and parts[1] == "R": + msg_level = int(parts[2]) + lid = parts[3] + if msg_level == self.l: + if self.s == 0 and self.i.get("food", 0) > 10: + self.ld = di + self.lid = lid + self.s = 2 + self.wt = 0 + self.log(f"Following leader {lid} lvl {self.l} dir {di}") + elif self.s == 2 and self.lid == lid: + self.ld = di + elif self.s == 1 and lid < self.id: + self.s = 2 + self.lid = lid + self.ld = di + self.wt = 0 + self.log(f"Yielding to better leader {lid}") + except Exception: + pass + + async def life(self): + while True: + if not self.n.w or self.n.w.is_closing(): + break + await self.handle_events() + await self.inventory() + if not self.n.w or self.n.w.is_closing(): + break + if self.i.get("food", 0) <= 0: + break + if self.s == 0: + await self.collect() + elif self.s == 1: + await self.lead() + elif self.s == 2: + await self.follow() + elif self.s == 3: + await self.wait_incantation() + + async def collect(self): + global _pending + + if not self.forked and self.i.get("food", 0) > 20: + total = stats.S["t"] + _pending + if total < 35: + cn_str = await self.cmd("Connect_nbr") + if cn_str != "dead" and cn_str.isdigit(): + cn = int(cn_str) + available = cn - _pending + if available > 0: + needed = min(available, 35 - stats.S["t"] - _pending) + if needed > 0: + self.log(f"Forking to spawn {needed} bot(s)") + r = await self.cmd("Fork") + if r == "ok": + self.forked = True + _spawn(self.t, self.h, self.p, 1) + + if self.has_stones() and self.i.get("food", 0) > 25: + self.log(f"Have stones for level {self.l + 1}, becoming leader") + self.s = 1 + return + + v = await self.look() + if v is None: + return + cell = v[0] + + if "food" in cell: + await self.cmd("Take food") + return + + if self.l < 8: + req = S[self.l - 1] + for idx, item in enumerate(R[1:]): + if self.i.get(item, 0) < req[idx + 1] and item in cell: + await self.cmd(f"Take {item}") + return + + r = random.random() + if r < 0.15: + await self.cmd("Left") + elif r < 0.30: + await self.cmd("Right") + await self.cmd("Forward") + + async def lead(self): + food = self.i.get("food", 0) + if food < 6: + self.log("Low food, back to collect") + self.s = 0 + return + + req = S[self.l - 1] + needed_players = req[0] + + await self.cmd(f"Broadcast {self.k}_R_{self.l}_{self.id}") + + v = await self.look() + if v is None: + return + cell = v[0] + + if "food" in cell: + await self.cmd("Take food") + return + + player_count = cell.count("player") + + if player_count >= needed_players: + self.log(f"Starting incantation ({player_count}/{needed_players} players)") + + for item in R[1:]: + for _ in range(cell.count(item)): + r = await self.cmd(f"Take {item}") + if r == "dead": + return + + for idx, item in enumerate(R[1:]): + for _ in range(req[idx + 1]): + r = await self.cmd(f"Set {item}") + if r == "dead": + return + + r = await self.cmd("Incantation") + if r == "Elevation underway": + res = await self.n.q.get() + if res != "dead" and res.startswith("Current level: "): + nl = int(res[15:]) + if nl > self.l: + old = self.l + stats.lvl_up(old, nl) + self.l = nl + self.log(f"Level up! Now level {self.l}", True) + for k in R[1:]: + self.i[k] = 0 + self.s = 0 + self.forked = False + + async def follow(self): + food = self.i.get("food", 0) + if food < 5: + self.log("Low food, back to collect") + self.s = 0 + self.ld = -1 + self.lid = None + return + + if self.ld == 0: + self.log("On leader tile, waiting for incantation") + self.s = 3 + self.wt = 0 + return + + if self.ld == -1: + self.wt += 1 + if self.wt > 8: + self.s = 0 + self.lid = None + return + + direction = self.ld + self.ld = -1 + self.wt = 0 + + if direction in D: + for mv in D[direction]: + r = await self.cmd(mv) + if r == "dead": + return + + async def wait_incantation(self): + self.wt += 1 + food = self.i.get("food", 0) + + v = await self.look() + if v is not None and "food" in v[0]: + await self.cmd("Take food") + + if self.wt > 150 or food < 3: + self.log("Incantation wait timeout, back to collect") + self.s = 0 + self.lid = None + self.ld = -1 diff --git a/ai/compile.py b/ai/compile.py new file mode 100644 index 0000000..510420c --- /dev/null +++ b/ai/compile.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +import os +import zipapp +from stat import S_IEXEC + +s = os.path.dirname(os.path.abspath(__file__)) +t = os.path.join(s, os.pardir, "zappy_ai") + +zipapp.create_archive( + s, + target=t, + interpreter="/usr/bin/env python3", + main="zappy_ai:main", +) +os.chmod(t, S_IEXEC | os.stat(t).st_mode) diff --git a/ai/const.py b/ai/const.py new file mode 100644 index 0000000..7f84427 --- /dev/null +++ b/ai/const.py @@ -0,0 +1,20 @@ +R = ["food", "linemate", "deraumere", "sibur", "mendiane", "phiras", "thystame"] +S = [ + [1, 1, 0, 0, 0, 0, 0], + [2, 1, 1, 1, 0, 0, 0], + [2, 2, 0, 1, 0, 2, 0], + [4, 1, 1, 2, 0, 1, 0], + [4, 1, 2, 1, 3, 0, 0], + [6, 1, 2, 3, 0, 1, 0], + [6, 2, 2, 2, 2, 2, 1] +] +D = { + 1: ["Forward"], + 2: ["Forward", "Left", "Forward"], + 3: ["Left", "Forward"], + 4: ["Left", "Left", "Forward"], + 5: ["Left", "Left", "Forward"], + 6: ["Right", "Right", "Forward"], + 7: ["Right", "Forward"], + 8: ["Forward", "Right", "Forward"] +} diff --git a/ai/network.py b/ai/network.py new file mode 100644 index 0000000..e3c8ea4 --- /dev/null +++ b/ai/network.py @@ -0,0 +1,41 @@ +import asyncio +import sys + + +class Network: + def __init__(self, h, p): + self.h = h + self.p = p + self.r = None + self.w = None + self.q = asyncio.Queue() + self.e = asyncio.Queue() + + async def c(self): + self.r, self.w = await asyncio.open_connection(self.h, self.p) + asyncio.create_task(self.l()) + + async def l(self): + try: + while True: + d = await self.r.readline() + if not d: + break + x = d.decode().strip() + if x == "dead": + break + if x.startswith("message "): + await self.e.put(("m", x[8:])) + elif x.startswith("eject: "): + await self.e.put(("j", x[7:])) + else: + await self.q.put(x) + except Exception: + pass + if self.w: + self.w.close() + await self.q.put("dead") + + async def s(self, c): + self.w.write((c + "\n").encode()) + await self.w.drain() diff --git a/ai/stats.py b/ai/stats.py new file mode 100644 index 0000000..3d07345 --- /dev/null +++ b/ai/stats.py @@ -0,0 +1,23 @@ +S = { + "t": 0, + "l": {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} +} +L = [] +D = False + +def add_bot(): + S["t"] += 1 + S["l"][1] += 1 + +def rm_bot(l): + S["t"] -= 1 + S["l"][l] -= 1 + +def lvl_up(o, n): + S["l"][o] -= 1 + S["l"][n] += 1 + +def log_evt(m): + L.append(m) + if len(L) > 10: + L.pop(0) diff --git a/ai/zappy_ai.py b/ai/zappy_ai.py new file mode 100644 index 0000000..dcea959 --- /dev/null +++ b/ai/zappy_ai.py @@ -0,0 +1,63 @@ +import sys +import asyncio +import logging +import stats +from bot import _spawn + +logging.getLogger("asyncio").setLevel(logging.CRITICAL) + + +async def tui(): + sys.stdout.write("\033[2J") + while True: + sys.stdout.write("\033[H") + sys.stdout.write(f"\033[1;37mBots alive: {stats.S['t']}\033[0m\n\n") + for l in range(1, 9): + c = stats.S['l'][l] + b = "\u2588" * min(c, 40) + sys.stdout.write(f"\033[1;32mLevel {l}\033[0m : [\033[1;34m{b:<40}\033[0m] {c}\n") + sys.stdout.write("\n\033[1;33m--- Events ---\033[0m\n") + for entry in stats.L: + sys.stdout.write(f"{entry:<80}\n") + sys.stdout.write("\033[J") + sys.stdout.flush() + await asyncio.sleep(0.5) + + +async def start_swarm(n, h, p): + _spawn(n, h, p, 1) + if not stats.D: + asyncio.create_task(tui()) + try: + while True: + await asyncio.sleep(3600) + except asyncio.CancelledError: + pass + + +def main(): + a = sys.argv[1:] + p = -1 + n = "" + h = "127.0.0.1" + for i in range(len(a)): + if a[i] == "-p" and i + 1 < len(a): + p = int(a[i + 1]) + elif a[i] == "-n" and i + 1 < len(a): + n = a[i + 1] + elif a[i] == "-h" and i + 1 < len(a): + h = a[i + 1] + elif a[i] == "--debug": + stats.D = True + + if p < 0 or not n: + sys.exit(84) + + try: + asyncio.run(start_swarm(n, h, p)) + except KeyboardInterrupt: + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/gui/src/Buttons/Button.cpp b/gui/src/Buttons/Button.cpp index bf98576..919b41e 100644 --- a/gui/src/Buttons/Button.cpp +++ b/gui/src/Buttons/Button.cpp @@ -1,50 +1,48 @@ #include "Buttons/Button.hpp" #include -namespace Zappy -{ -Button::Button(Texture &texture, float x, float y, float width, float height, std::function function, WindowSize &ws) : _function(function), _width(width), _height(height), _x(x), _y(y), _hovered(false), _ws(ws) -{ - _sprite = std::make_unique(texture); - _sprite->setPosition(x, y); - _sprite->scale = Zappy::Math::vec3(width, height, 1.0f); - - +namespace Zappy { +Button::Button(Texture &texture, float x, float y, float width, float height, + std::function function, WindowSize &ws) + : _function(function), _width(width), _height(height), _x(x), _y(y), + _hovered(false), _ws(ws) { + _sprite = std::make_unique(texture); + _sprite->setPosition(x, y); + _sprite->scale = Zappy::Math::vec3(width, height, 1.0f); } -void Button::draw(Shader &shader) -{ - if (_sprite) { - Zappy::Math::mat4 orthoProjection = Zappy::Math::ortho(0.0f, _ws.width, _ws.height, 0.0f, -1.0f, 1.0f); - Zappy::Math::mat4 view; - _sprite->draw(shader, view, orthoProjection); - } +void Button::draw(Shader &shader) { + if (_sprite) { + Zappy::Math::mat4 orthoProjection = + Zappy::Math::ortho(0.0f, _ws.width, _ws.height, 0.0f, -1.0f, 1.0f); + Zappy::Math::mat4 view; + _sprite->draw(shader, view, orthoProjection); + } } -void Button::setPosition(float x, float y) -{ - _x = x; - _y = y; - if (_sprite) { - _sprite->setPosition(x, y); - } +void Button::setPosition(float x, float y) { + _x = x; + _y = y; + if (_sprite) { + _sprite->setPosition(x, y); + } } -void Button::update(const std::vector &events) -{ - for (const auto &event : events) - { - if (event.type == EventType::MouseMoved){ - if (event.mouseX >= _x - _width / 2 && event.mouseX <= _x + _width / 2 && event.mouseY >= _y - _height / 2 && event.mouseY <= _y + _height / 2){ - _hovered = true; - } else { - _hovered = false; - } - } - if (event.type == EventType::MousePressed && event.button == 1) { - if (_hovered) { - _function(); - } - } +void Button::update(const std::vector &events) { + for (const auto &event : events) { + if (event.type == EventType::MouseMoved) { + if (event.mouseX >= _x - _width / 2 && event.mouseX <= _x + _width / 2 && + event.mouseY >= _y - _height / 2 && + event.mouseY <= _y + _height / 2) { + _hovered = true; + } else { + _hovered = false; + } + } + if (event.type == EventType::MousePressed && event.button == 1) { + if (_hovered) { + _function(); + } } + } } -}// namespace Zappy +} // namespace Zappy diff --git a/gui/src/Buttons/Button.hpp b/gui/src/Buttons/Button.hpp index 0cdbf2c..3594167 100644 --- a/gui/src/Buttons/Button.hpp +++ b/gui/src/Buttons/Button.hpp @@ -1,27 +1,28 @@ #pragma once #include "Buttons/IButton.hpp" -#include "Sprite/Sprite.hpp" #include "IScene/IScene.hpp" +#include "Sprite/Sprite.hpp" #include #include -namespace Zappy -{ - class Button : public IButton { - private: - std::unique_ptr _sprite; - std::function _function; - float _width; - float _height; - float _x; - float _y; - bool _hovered; - WindowSize &_ws; - public: - Button(Texture& texture, float x, float y, float width, float height, std::function function, WindowSize &ws); - void draw(Shader &shader); - void setPosition(float x, float y); - void update(const std::vector &events); - }; +namespace Zappy { +class Button : public IButton { +private: + std::unique_ptr _sprite; + std::function _function; + float _width; + float _height; + float _x; + float _y; + bool _hovered; + WindowSize &_ws; + +public: + Button(Texture &texture, float x, float y, float width, float height, + std::function function, WindowSize &ws); + void draw(Shader &shader); + void setPosition(float x, float y); + void update(const std::vector &events); +}; -} // namespace Z +} // namespace Zappy diff --git a/gui/src/Buttons/IButton.hpp b/gui/src/Buttons/IButton.hpp index f0698ad..fb7762b 100644 --- a/gui/src/Buttons/IButton.hpp +++ b/gui/src/Buttons/IButton.hpp @@ -3,14 +3,13 @@ #include "Event.hpp" #include -namespace Zappy -{ - class IButton { - private: - public: - virtual ~IButton() = default; - virtual void draw(Shader &shader) = 0; - virtual void setPosition(float x, float y) = 0; - virtual void update(const std::vector &events) = 0; - }; -} +namespace Zappy { +class IButton { +private: +public: + virtual ~IButton() = default; + virtual void draw(Shader &shader) = 0; + virtual void setPosition(float x, float y) = 0; + virtual void update(const std::vector &events) = 0; +}; +} // namespace Zappy diff --git a/gui/src/Core/Core.cpp b/gui/src/Core/Core.cpp index 1e28e94..a6b268b 100644 --- a/gui/src/Core/Core.cpp +++ b/gui/src/Core/Core.cpp @@ -8,7 +8,6 @@ #include #include - namespace Zappy { Core::Core() : _isRunning(true) {} @@ -35,7 +34,8 @@ void Core::init(const std::string &ip, int port) { if (!isConnected) LOG_WARN("Failed to connect to the server."); - _networkManager = std::make_unique(*_networkClient, ip, port); + _networkManager = + std::make_unique(*_networkClient, ip, port); _sceneManager.changeScene(std::make_unique( _sceneManager.getTextureManager(), _sceneManager.getAudioManager())); } diff --git a/gui/src/IScene/IScene.hpp b/gui/src/IScene/IScene.hpp index 07cb99e..74573d5 100644 --- a/gui/src/IScene/IScene.hpp +++ b/gui/src/IScene/IScene.hpp @@ -9,8 +9,7 @@ #define WIDTH 1920.0f #define HEIGHT 1080.0f -struct WindowSize -{ +struct WindowSize { unsigned int width = 1; unsigned int height = 1; }; diff --git a/gui/src/Network/NetworkManager.cpp b/gui/src/Network/NetworkManager.cpp index 4e51bb4..bc62df7 100644 --- a/gui/src/Network/NetworkManager.cpp +++ b/gui/src/Network/NetworkManager.cpp @@ -6,8 +6,8 @@ namespace Zappy { -NetworkManager::NetworkManager(INetworkClient &client, std::string ip, int port) : _netClient(client), _ip(ip), _port(port) -{ +NetworkManager::NetworkManager(INetworkClient &client, std::string ip, int port) + : _netClient(client), _ip(ip), _port(port) { initCommandHandlers(); } @@ -40,15 +40,12 @@ void NetworkManager::initCommandHandlers() { _commandHandlers["sbp"] = [this](const auto &args) { handleSbp(args); }; } - -bool NetworkManager::connectToServer() -{ +bool NetworkManager::connectToServer() { std::cout << "IP:" << _ip << " Port:" << _port << std::endl; return _netClient.connectToServer(_ip, _port); } -bool NetworkManager::connectToServer(const std::string &host, int port) -{ +bool NetworkManager::connectToServer(const std::string &host, int port) { return _netClient.connectToServer(host, port); } void NetworkManager::update() { @@ -291,15 +288,11 @@ void NetworkManager::handleSbp(const std::vector &args) { _eventQueue.push_back({NetworkEventType::SERVER_ERROR, args}); } -void NetworkManager::sendSst(int time) -{ +void NetworkManager::sendSst(int time) { std::string format = "sst " + std::to_string(time) + "\n"; this->sendCommand(format); } -void NetworkManager::sendSgt() -{ - this->sendCommand(std::string("sgt\n")); -} +void NetworkManager::sendSgt() { this->sendCommand(std::string("sgt\n")); } } // namespace Zappy \ No newline at end of file diff --git a/gui/src/Network/NetworkManager.hpp b/gui/src/Network/NetworkManager.hpp index 27c4103..0498d05 100644 --- a/gui/src/Network/NetworkManager.hpp +++ b/gui/src/Network/NetworkManager.hpp @@ -26,8 +26,6 @@ class NetworkManager { std::vector consumeEvents(); private: - int _socket; - bool _isConnected; std::string _buffer; GameState _gameState; diff --git a/gui/src/Render/Render.hpp b/gui/src/Render/Render.hpp index 7d7f4f0..1de76db 100644 --- a/gui/src/Render/Render.hpp +++ b/gui/src/Render/Render.hpp @@ -158,23 +158,23 @@ class Renderer { glDepthFunc(GL_LESS); } - void - render(const Camera &camera, InstancedGrid &floor, - const std::vector> &players, - const std::vector> &resources = {}, - const WindowSize &windowSize = {}){ - - -if (windowSize.width != _width || windowSize.height != _height) { - _width = windowSize.width; - _height = windowSize.height; - - glBindTexture(GL_TEXTURE_2D, sceneColorTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - glBindTexture(GL_TEXTURE_2D, sceneDepthTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); -} + void render(const Camera &camera, InstancedGrid &floor, + const std::vector> &players, + const std::vector> &resources = {}, + const WindowSize &windowSize = {}) { + + if (windowSize.width != _width || windowSize.height != _height) { + _width = windowSize.width; + _height = windowSize.height; + + glBindTexture(GL_TEXTURE_2D, sceneColorTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, + GL_UNSIGNED_BYTE, NULL); + + glBindTexture(GL_TEXTURE_2D, sceneDepthTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, + GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + } glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); @@ -189,10 +189,9 @@ if (windowSize.width != _width || windowSize.height != _height) { Zappy::Math::mat4 lightSpaceMatrix = lightProjection * lightView; Zappy::Math::mat4 projection = Zappy::Math::perspective( - Zappy::Math::radians(45.0f), - static_cast(_width) / static_cast(_height), - 0.1f, - 1000.0f); + Zappy::Math::radians(45.0f), + static_cast(_width) / static_cast(_height), 0.1f, + 1000.0f); Zappy::Math::mat4 view = camera.getViewMatrix(); Zappy::Math::mat4 viewProj = projection * view; diff --git a/gui/src/Scene/Game.cpp b/gui/src/Scene/Game.cpp index 7ac31d9..a8a5dcc 100644 --- a/gui/src/Scene/Game.cpp +++ b/gui/src/Scene/Game.cpp @@ -6,60 +6,65 @@ void Zappy::GameScene::onEnter() { _texManager.get("gui/assets/cute.png"); _texManager.get("gui/assets/egg.png"); - for (int i = 0; i < 7; i++) - _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); - - _textShader = std::make_unique("gui/src/Core/Shader/text.vert", "gui/src/Core/Shader/text.frag"); - Font &feedFont = _fontManager.get("gui/assets/fonts/mainTitle.otf", 24.0f, 512); - - for (int i = 0; i < 5; i++) { - auto t = std::make_unique(feedFont, "", 20.0f, 30.0f + i * 30.0f); - t->color = Zappy::Math::vec3(0.3f, 1.0f, 0.3f); - _broadcastTexts.push_back(std::move(t)); + for (int i = 0; i < 7; i++) + _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); + + _textShader = std::make_unique("gui/src/Core/Shader/text.vert", + "gui/src/Core/Shader/text.frag"); + Font &feedFont = + _fontManager.get("gui/assets/fonts/mainTitle.otf", 24.0f, 512); + + for (int i = 0; i < 5; i++) { + auto t = std::make_unique(feedFont, "", 20.0f, 30.0f + i * 30.0f); + t->color = Zappy::Math::vec3(0.3f, 1.0f, 0.3f); + _broadcastTexts.push_back(std::move(t)); } - Font &goFont = _fontManager.get("gui/assets/fonts/mainTitle.otf", 64.0f, 1024); - _gameOverText = std::make_unique(goFont, "", 0.0f, _windowSize.height / 2.0f); + Font &goFont = + _fontManager.get("gui/assets/fonts/mainTitle.otf", 64.0f, 1024); + _gameOverText = + std::make_unique(goFont, "", 0.0f, _windowSize.height / 2.0f); _gameOverText->color = Zappy::Math::vec3(1.0f, 0.8f, 0.0f); -_texManager.get("gui/assets/incantation.png"); -_incantations.clear(); + _texManager.get("gui/assets/incantation.png"); + _incantations.clear(); } +void Zappy::GameScene::updateTileResources3D(int x, int z, + const Zappy::Tile &tileData, + float offX, float offZ) { + int mapWidth = static_cast(offX * 2.0f); + int tileIndex = (z * mapWidth) + x; - void Zappy::GameScene::updateTileResources3D(int x, int z, const Zappy::Tile& tileData, float offX, float offZ) { - int mapWidth = static_cast(offX * 2.0f); - int tileIndex = (z * mapWidth) + x; + float baseX = (x - offX) * 2.0f; + float baseZ = (z - offZ) * 2.0f; - float baseX = (x - offX) * 2.0f; - float baseZ = (z - offZ) * 2.0f; + for (int i = 0; i < 7; i++) { + int count = tileData.resources[i]; - for (int i = 0; i < 7; i++) { - int count = tileData.resources[i]; + if (count > 0 && !_tileVisuals[tileIndex].resourceSprites[i]) { + Texture &resTex = + _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); + auto spr = std::make_unique(resTex); - if (count > 0 && !_tileVisuals[tileIndex].resourceSprites[i]) { - Texture &resTex = _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); - auto spr = std::make_unique(resTex); + spr->position = Zappy::Math::vec3(baseX + RESOURCE_OFFSETS[i][0], 0.1f, + baseZ + RESOURCE_OFFSETS[i][1]); + spr->scale = Zappy::Math::vec3(0.4f, 0.4f, 0.4f); + spr->rotation = Zappy::Math::vec3(90.0f, 0.0f, 0.0f); + spr->isBillboard = true; - spr->position = Zappy::Math::vec3(baseX + RESOURCE_OFFSETS[i][0], 0.1f, baseZ + RESOURCE_OFFSETS[i][1]); - spr->scale = Zappy::Math::vec3(0.4f, 0.4f, 0.4f); - spr->rotation = Zappy::Math::vec3(90.0f, 0.0f, 0.0f); - spr->isBillboard = true; - - _tileVisuals[tileIndex].resourceSprites[i] = std::move(spr); - } + _tileVisuals[tileIndex].resourceSprites[i] = std::move(spr); + } - else if (count == 0 && _tileVisuals[tileIndex].resourceSprites[i]) { - _tileVisuals[tileIndex].resourceSprites[i].reset(); - } - } + else if (count == 0 && _tileVisuals[tileIndex].resourceSprites[i]) { + _tileVisuals[tileIndex].resourceSprites[i].reset(); + } } +} - Zappy::SceneState Zappy::GameScene::update(const std::vector &events, - const Zappy::GameState &gameState, - const std::vector &netEvents, - float deltaTime) -{ +Zappy::SceneState Zappy::GameScene::update( + const std::vector &events, const Zappy::GameState &gameState, + const std::vector &netEvents, float deltaTime) { _camera.update(events); static float globalCrystalTime = 0.0f; globalCrystalTime += deltaTime; @@ -75,10 +80,12 @@ _incantations.clear(); bool isPPressed = false; for (const auto &event : events) { - if (event.type == Zappy::EventType::KeyPressed && event.keyCode == Zappy::Key::Space) { + if (event.type == Zappy::EventType::KeyPressed && + event.keyCode == Zappy::Key::Space) { isSpacePressed = true; } - if (event.type == Zappy::EventType::KeyPressed && event.keyCode == Zappy::Key::P) { + if (event.type == Zappy::EventType::KeyPressed && + event.keyCode == Zappy::Key::P) { isPPressed = true; } if (event.type == Zappy::EventType::MouseWheelMove && _playerInventory) { @@ -87,47 +94,54 @@ _incantations.clear(); for (const auto &[id, _] : gameState.players) { playersIds.push_back(id); } - if (event.wheelDelta > 0) + if (event.wheelDelta > 0) _currentPlayerIndex++; else if (event.wheelDelta < 0) _currentPlayerIndex--; - if (_currentPlayerIndex >= (int)playersIds.size()) + if (_currentPlayerIndex >= (int)playersIds.size()) _currentPlayerIndex = 0; - else if (_currentPlayerIndex < 0) + else if (_currentPlayerIndex < 0) _currentPlayerIndex = playersIds.size() - 1; _playerInventory->setTargetPlayer(playersIds[_currentPlayerIndex]); } } - if (event.type == Zappy::EventType::MousePressed && event.button == 1 && !_quickMenu && !_playerInventory) { + if (event.type == Zappy::EventType::MousePressed && event.button == 1 && + !_quickMenu && !_playerInventory) { float x_ndc = (2.0f * event.mouseX) / _windowSize.width - 1.0f; float y_ndc = 1.0f - (2.0f * event.mouseY) / _windowSize.height; - Zappy::Math::mat4 proj = Zappy::Math::perspective(Zappy::Math::radians(45.0f), (float)_windowSize.width/(float)_windowSize.height, 0.1f, 1000.0f); + Zappy::Math::mat4 proj = Zappy::Math::perspective( + Zappy::Math::radians(45.0f), + (float)_windowSize.width / (float)_windowSize.height, 0.1f, + 1000.0f); float eyeX = x_ndc / proj.m[0]; float eyeY = y_ndc / proj.m[5]; float eyeZ = -1.0f; Zappy::Math::mat4 view = _camera.getViewMatrix(); Zappy::Math::vec3 rayDir( - eyeX * view.m[0] + eyeY * view.m[1] + eyeZ * view.m[2], - eyeX * view.m[4] + eyeY * view.m[5] + eyeZ * view.m[6], - eyeX * view.m[8] + eyeY * view.m[9] + eyeZ * view.m[10] - ); + eyeX * view.m[0] + eyeY * view.m[1] + eyeZ * view.m[2], + eyeX * view.m[4] + eyeY * view.m[5] + eyeZ * view.m[6], + eyeX * view.m[8] + eyeY * view.m[9] + eyeZ * view.m[10]); rayDir = Zappy::Math::normalize(rayDir); Zappy::Math::vec3 camPos( - -(view.m[12]*view.m[0] + view.m[13]*view.m[1] + view.m[14]*view.m[2]), - -(view.m[12]*view.m[4] + view.m[13]*view.m[5] + view.m[14]*view.m[6]), - -(view.m[12]*view.m[8] + view.m[13]*view.m[9] + view.m[14]*view.m[10]) - ); + -(view.m[12] * view.m[0] + view.m[13] * view.m[1] + + view.m[14] * view.m[2]), + -(view.m[12] * view.m[4] + view.m[13] * view.m[5] + + view.m[14] * view.m[6]), + -(view.m[12] * view.m[8] + view.m[13] * view.m[9] + + view.m[14] * view.m[10])); if (rayDir.y < 0.0f) { float t = -camPos.y / rayDir.y; float hitX = camPos.x + rayDir.x * t; float hitZ = camPos.z + rayDir.z * t; int mapX = std::round((hitX / 2.0f) + offsetX); - int mapZ = std::round((hitZ/ 2.0f) + offsetZ); - if (mapX >= 0 && mapX < gameState.map.width && mapZ >= 0 && mapZ < gameState.map.height) { + int mapZ = std::round((hitZ / 2.0f) + offsetZ); + if (mapX >= 0 && mapX < gameState.map.width && mapZ >= 0 && + mapZ < gameState.map.height) { int index = mapZ * gameState.map.width + mapX; _currentTileIndex = index; if (!_tileInventory) { - _tileInventory = std::make_unique(_texManager, _networkManager, _fontManager); + _tileInventory = std::make_unique( + _texManager, _networkManager, _fontManager); _tileInventory->onEnter(); } _tileInventory->setTargetTile(gameState.grid[index]); @@ -141,39 +155,42 @@ _incantations.clear(); } } if (isPPressed && !_wasPPressed) { - if (_playerInventory) { - _playerInventory->onExit(); - _playerInventory.reset(); - } else { - _playerInventory = std::make_unique(_texManager, _networkManager, _fontManager); - _playerInventory->onEnter(); - if (!gameState.players.empty()) { - _currentPlayerIndex = 0; - _playerInventory->setTargetPlayer(gameState.players.begin()->first); - } + if (_playerInventory) { + _playerInventory->onExit(); + _playerInventory.reset(); + } else { + _playerInventory = std::make_unique( + _texManager, _networkManager, _fontManager); + _playerInventory->onEnter(); + if (!gameState.players.empty()) { + _currentPlayerIndex = 0; + _playerInventory->setTargetPlayer(gameState.players.begin()->first); } + } } if (isSpacePressed && !_wasSpacePressed) { - if (_quickMenu) { - _quickMenu->onExit(); - _quickMenu.reset(); - } else { - _quickMenu = std::make_unique(_texManager, _networkManager, _windowSize); - _quickMenu->onEnter(); - } + if (_quickMenu) { + _quickMenu->onExit(); + _quickMenu.reset(); + } else { + _quickMenu = std::make_unique(_texManager, _networkManager, + _windowSize); + _quickMenu->onEnter(); + } } _wasPPressed = isPPressed; _wasSpacePressed = isSpacePressed; if (_quickMenu) { - SceneState quickMenuState = _quickMenu->update(events, gameState, netEvents, deltaTime); - if (quickMenuState != SceneState::NONE) - return quickMenuState; + SceneState quickMenuState = + _quickMenu->update(events, gameState, netEvents, deltaTime); + if (quickMenuState != SceneState::NONE) + return quickMenuState; } if (_tileInventory) { - _tileInventory->update(events, gameState, netEvents, deltaTime); + _tileInventory->update(events, gameState, netEvents, deltaTime); } if (_playerInventory) { - _playerInventory->update(events, gameState, netEvents, deltaTime); + _playerInventory->update(events, gameState, netEvents, deltaTime); } if (!_tileInventory && !_quickMenu && !_playerInventory) _camera.update(events); @@ -194,7 +211,8 @@ _incantations.clear(); auto crystalSprite = std::make_unique(crystalTex); crystalSprite->scale = Zappy::Math::vec3(0.3f, 0.3f, 0.3f); crystalSprite->isBillboard = true; - crystalSprite->colorTint = getTeamColor(gameState.players.at(id).team); + crystalSprite->colorTint = + getTeamColor(gameState.players.at(id).team); _playerCrystalMap[id] = crystalSprite.get(); _crystals.push_back(std::move(crystalSprite)); @@ -220,10 +238,10 @@ _incantations.clear(); } case Zappy::NetworkEventType::EGG_LAID: { if (netEvent.arguments.size() >= 5) { - int eggId = cleanId(netEvent.arguments[1]); - int x = std::stoi(netEvent.arguments[3]); - int y = std::stoi(netEvent.arguments[4]); - spawnEgg3D(eggId, x, y, offsetX, offsetZ); + int eggId = cleanId(netEvent.arguments[1]); + int x = std::stoi(netEvent.arguments[3]); + int y = std::stoi(netEvent.arguments[4]); + spawnEgg3D(eggId, x, y, offsetX, offsetZ); } break; } @@ -231,74 +249,82 @@ _incantations.clear(); case Zappy::NetworkEventType::EGG_HATCHED: case Zappy::NetworkEventType::EGG_DIED: { if (netEvent.arguments.size() >= 2) { - int eggId = cleanId(netEvent.arguments[1]); - removeEgg3D(eggId); + int eggId = cleanId(netEvent.arguments[1]); + removeEgg3D(eggId); } break; } case Zappy::NetworkEventType::BROADCAST: { if (netEvent.arguments.size() >= 3) { - std::string sender = netEvent.arguments[1]; - std::string fullMsg = ""; - for (size_t i = 2; i < netEvent.arguments.size(); ++i) { - fullMsg += netEvent.arguments[i] + (i == netEvent.arguments.size() - 1 ? "" : " "); - } - addBroadcastLog(sender, fullMsg); + std::string sender = netEvent.arguments[1]; + std::string fullMsg = ""; + for (size_t i = 2; i < netEvent.arguments.size(); ++i) { + fullMsg += netEvent.arguments[i] + + (i == netEvent.arguments.size() - 1 ? "" : " "); + } + addBroadcastLog(sender, fullMsg); } break; } case Zappy::NetworkEventType::RESOURCE_COLLECTED: case Zappy::NetworkEventType::RESOURCE_DROPPED: { if (netEvent.arguments.size() >= 2) { - _playerAnims[cleanId(netEvent.arguments[1])] = {"jump", 0.0f}; + _playerAnims[cleanId(netEvent.arguments[1])] = {"jump", 0.0f}; } break; } case Zappy::NetworkEventType::PLAYER_EXPULSED: { if (netEvent.arguments.size() >= 2) { - _playerAnims[cleanId(netEvent.arguments[1])] = {"shake", 0.0f}; + _playerAnims[cleanId(netEvent.arguments[1])] = {"shake", 0.0f}; } break; } case Zappy::NetworkEventType::EGG_LAYING: { if (netEvent.arguments.size() >= 2) { - _playerAnims[cleanId(netEvent.arguments[1])] = {"squeeze", 0.0f}; + _playerAnims[cleanId(netEvent.arguments[1])] = {"squeeze", 0.0f}; } break; } case Zappy::NetworkEventType::GAME_OVER: { if (netEvent.arguments.size() >= 2) { - _isGameOver = true; - _gameOverText->setString("VICTORY FOR TEAM " + netEvent.arguments[1]); - _gameOverText->setPosition((_windowSize.width / 2.0f) - (_gameOverText->getWidth() / 2.0f), _windowSize.height / 2.0f); + _isGameOver = true; + _gameOverText->setString("VICTORY FOR TEAM " + netEvent.arguments[1]); + _gameOverText->setPosition((_windowSize.width / 2.0f) - + (_gameOverText->getWidth() / 2.0f), + _windowSize.height / 2.0f); } break; } case Zappy::NetworkEventType::SERVER_MESSAGE: { if (netEvent.arguments.size() >= 2) { - addBroadcastLog("SERVER", netEvent.arguments[1]); + addBroadcastLog("SERVER", netEvent.arguments[1]); } break; } case Zappy::NetworkEventType::INCANTATION_START: { if (netEvent.arguments.size() >= 4) { - int x = std::stoi(netEvent.arguments[1]); - int y = std::stoi(netEvent.arguments[2]); - Texture &magicTex = _texManager.get("gui/assets/incantation.png"); - auto magicSprite = std::make_unique(magicTex); - magicSprite->isBillboard = true; - _incantations.push_back({x, y, 0.0f, std::move(magicSprite)}); - addBroadcastLog("SERVER", "Incantation en (" + std::to_string(x) + ", " + std::to_string(y) + ")"); + int x = std::stoi(netEvent.arguments[1]); + int y = std::stoi(netEvent.arguments[2]); + Texture &magicTex = _texManager.get("gui/assets/incantation.png"); + auto magicSprite = std::make_unique(magicTex); + magicSprite->isBillboard = true; + _incantations.push_back({x, y, 0.0f, std::move(magicSprite)}); + addBroadcastLog("SERVER", "Incantation en (" + std::to_string(x) + + ", " + std::to_string(y) + ")"); } break; } case Zappy::NetworkEventType::INCANTATION_END: { if (netEvent.arguments.size() >= 4) { - int x = std::stoi(netEvent.arguments[1]); - int y = std::stoi(netEvent.arguments[2]); - _incantations.erase(std::remove_if(_incantations.begin(), _incantations.end(), - [x, y](const ActiveIncantation& inc) { return inc.x == x && inc.y == y; }), _incantations.end()); + int x = std::stoi(netEvent.arguments[1]); + int y = std::stoi(netEvent.arguments[2]); + _incantations.erase( + std::remove_if(_incantations.begin(), _incantations.end(), + [x, y](const ActiveIncantation &inc) { + return inc.x == x && inc.y == y; + }), + _incantations.end()); } break; } @@ -315,91 +341,88 @@ _incantations.clear(); float scaleX = 1.0f, scaleY = 1.0f; if (_playerAnims.contains(id)) { - _playerAnims[id].timer += deltaTime; - float t = _playerAnims[id].timer; - - if (_playerAnims[id].type == "jump") { - if (t < 0.3f) { - baseHeight += std::sin(t / 0.3f * 3.14159f) * 0.5f; - } else { - _playerAnims.erase(id); - } - } - else if (_playerAnims[id].type == "shake") { - if (t < 0.3f) { - animOffsetX = std::sin(t * 50.0f) * 0.2f; - } else { - _playerAnims.erase(id); - } + _playerAnims[id].timer += deltaTime; + float t = _playerAnims[id].timer; + + if (_playerAnims[id].type == "jump") { + if (t < 0.3f) { + baseHeight += std::sin(t / 0.3f * 3.14159f) * 0.5f; + } else { + _playerAnims.erase(id); + } + } else if (_playerAnims[id].type == "shake") { + if (t < 0.3f) { + animOffsetX = std::sin(t * 50.0f) * 0.2f; + } else { + _playerAnims.erase(id); } - else if (_playerAnims[id].type == "squeeze") { - if (t < 1.0f) { - float squeeze = std::sin(t * 10.0f) * 0.2f; - scaleX = 1.0f + squeeze; - scaleY = 1.0f - squeeze; - } else { - _playerAnims.erase(id); - } + } else if (_playerAnims[id].type == "squeeze") { + if (t < 1.0f) { + float squeeze = std::sin(t * 10.0f) * 0.2f; + scaleX = 1.0f + squeeze; + scaleY = 1.0f - squeeze; + } else { + _playerAnims.erase(id); } + } } - + _playerMap[id]->scale = Zappy::Math::vec3(scaleX, scaleY, 1.0f); - Zappy::Math::vec3 targetPos( - (p.x - offsetX) * 2.0f + animOffsetX, - baseHeight, - (p.y - offsetZ) * 2.0f - 1.0f - ); + Zappy::Math::vec3 targetPos((p.x - offsetX) * 2.0f + animOffsetX, + baseHeight, (p.y - offsetZ) * 2.0f - 1.0f); float dx = targetPos.x - _playerMap[id]->position.x; float dz = targetPos.z - _playerMap[id]->position.z; if (std::abs(dx) > 3.0f || std::abs(dz) > 3.0f) { - _playerMap[id]->position = targetPos; + _playerMap[id]->position = targetPos; } else { - float lerpSpeed = 10.0f * deltaTime; - if (lerpSpeed > 1.0f) - lerpSpeed = 1.0f; + float lerpSpeed = 10.0f * deltaTime; + if (lerpSpeed > 1.0f) + lerpSpeed = 1.0f; - _playerMap[id]->position = Zappy::Math::transi(_playerMap[id]->position, targetPos, lerpSpeed); + _playerMap[id]->position = Zappy::Math::transi( + _playerMap[id]->position, targetPos, lerpSpeed); } if (_playerCrystalMap.contains(id)) { - float hoverY = 1.0f + std::sin(globalCrystalTime * 3.0f + id) * 0.15f; - _playerCrystalMap[id]->position = _playerMap[id]->position; - _playerCrystalMap[id]->position.y += hoverY; + float hoverY = 1.0f + std::sin(globalCrystalTime * 3.0f + id) * 0.15f; + _playerCrystalMap[id]->position = _playerMap[id]->position; + _playerCrystalMap[id]->position.y += hoverY; } } } for (auto &inc : _incantations) { inc.timer += deltaTime; - inc.sprite->position = Zappy::Math::vec3((inc.x - offsetX) * 2.0f, 0.5f, (inc.y - offsetZ) * 2.0f - 1.0f); + inc.sprite->position = Zappy::Math::vec3((inc.x - offsetX) * 2.0f, 0.5f, + (inc.y - offsetZ) * 2.0f - 1.0f); float pulse = 1.5f + std::sin(inc.timer * 6.0f) * 0.3f; inc.sprite->scale = Zappy::Math::vec3(pulse, pulse, pulse); } } - for (auto it = _broadcastLogs.begin(); it != _broadcastLogs.end(); ) { - it->timer += deltaTime; - if (it->timer >= 5.0f) { - it = _broadcastLogs.erase(it); - } else { - it++; - } + for (auto it = _broadcastLogs.begin(); it != _broadcastLogs.end();) { + it->timer += deltaTime; + if (it->timer >= 5.0f) { + it = _broadcastLogs.erase(it); + } else { + it++; + } } - for (auto it = _dyingEntities.begin(); it != _dyingEntities.end(); ) { - it->timer += deltaTime; + for (auto it = _dyingEntities.begin(); it != _dyingEntities.end();) { + it->timer += deltaTime; - float progress = std::min(it->timer / 1.0f, 1.0f); - it->sprite->rotation.x = Zappy::Math::transi(0.0f, -1.5708f, progress); - it->sprite->position.y = Zappy::Math::transi(0.0f, -0.5f, progress); + float progress = std::min(it->timer / 1.0f, 1.0f); + it->sprite->rotation.x = Zappy::Math::transi(0.0f, -1.5708f, progress); + it->sprite->position.y = Zappy::Math::transi(0.0f, -0.5f, progress); - if (it->timer >= 1.0f) { - it = _dyingEntities.erase(it); - } else { - it++; - } + if (it->timer >= 1.0f) { + it = _dyingEntities.erase(it); + } else { + it++; + } } return SceneState::NONE; @@ -419,85 +442,87 @@ void Zappy::GameScene::draw(Shader &shader, WindowSize &windowSize) { } for (auto &eggSprite : _eggs) { - resourcesToDraw.push_back(*eggSprite); + resourcesToDraw.push_back(*eggSprite); } for (auto &dying : _dyingEntities) { - resourcesToDraw.push_back(*dying.sprite); + resourcesToDraw.push_back(*dying.sprite); } for (auto &inc : _incantations) { - resourcesToDraw.push_back(*inc.sprite); + resourcesToDraw.push_back(*inc.sprite); } for (auto &c : _crystals) { - resourcesToDraw.push_back(*c); + resourcesToDraw.push_back(*c); } _renderer->render(_camera, *_floor, _players, resourcesToDraw, windowSize); } if (_quickMenu) { - glDisable(GL_DEPTH_TEST); - _quickMenu->draw(shader, windowSize); - glEnable(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); + _quickMenu->draw(shader, windowSize); + glEnable(GL_DEPTH_TEST); } if (_tileInventory) { - glDisable(GL_DEPTH_TEST); - _tileInventory->draw(shader, windowSize); - glEnable(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); + _tileInventory->draw(shader, windowSize); + glEnable(GL_DEPTH_TEST); } if (_playerInventory) { - glDisable(GL_DEPTH_TEST); - _playerInventory->draw(shader, windowSize); - glEnable(GL_DEPTH_TEST); + glDisable(GL_DEPTH_TEST); + _playerInventory->draw(shader, windowSize); + glEnable(GL_DEPTH_TEST); } if (!_broadcastLogs.empty()) { + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (!_broadcastLogs.empty()) { glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if (!_broadcastLogs.empty()) { - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - Zappy::Math::mat4 orthoProj = Zappy::Math::ortho(0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); - - for (size_t i = 0; i < _broadcastLogs.size() && i < _broadcastTexts.size(); i++) { - _broadcastTexts[i]->setString(_broadcastLogs[i].text); - - float t = _broadcastLogs[i].timer; - float alpha = 1.0f; - - if (t < 0.5f) { - alpha = t / 0.5f; - } else if (t > 4.0f) { - alpha = (5.0f - t) / 1.0f; - } + Zappy::Math::mat4 orthoProj = Zappy::Math::ortho( + 0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); - _broadcastTexts[i]->alpha = alpha; - _broadcastTexts[i]->draw(*_textShader, orthoProj); + for (size_t i = 0; + i < _broadcastLogs.size() && i < _broadcastTexts.size(); i++) { + _broadcastTexts[i]->setString(_broadcastLogs[i].text); + + float t = _broadcastLogs[i].timer; + float alpha = 1.0f; + + if (t < 0.5f) { + alpha = t / 0.5f; + } else if (t > 4.0f) { + alpha = (5.0f - t) / 1.0f; + } + + _broadcastTexts[i]->alpha = alpha; + _broadcastTexts[i]->draw(*_textShader, orthoProj); } } - if (_isGameOver && _gameOverText) { - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Zappy::Math::mat4 orthoProj = Zappy::Math::ortho(0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); + Zappy::Math::mat4 orthoProj = Zappy::Math::ortho( + 0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); - static float goTimer = 0.0f; - goTimer += 0.016f; - _gameOverText->alpha = 0.5f + std::sin(goTimer * 5.0f) * 0.5f; + static float goTimer = 0.0f; + goTimer += 0.016f; + _gameOverText->alpha = 0.5f + std::sin(goTimer * 5.0f) * 0.5f; - _gameOverText->draw(*_textShader, orthoProj); + _gameOverText->draw(*_textShader, orthoProj); - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); } -} + } } void Zappy::GameScene::onExit() { @@ -517,7 +542,6 @@ void Zappy::GameScene::onExit() { _quickMenu->onExit(); if (_tileInventory) _tileInventory->onExit(); - } void Zappy::GameScene::buildMap(const Zappy::GameState &gameState) { @@ -563,12 +587,13 @@ void Zappy::GameScene::spawnPlayer3D(int id, const Zappy::Player &p, _players.push_back(std::move(playerSprite)); } - void Zappy::GameScene::removePlayer3D(int id) { if (_playerMap.contains(id)) { Sprite *targetSprite = _playerMap[id]; auto it = std::find_if(_players.begin(), _players.end(), - [targetSprite](const std::unique_ptr &s) { return s.get() == targetSprite; }); + [targetSprite](const std::unique_ptr &s) { + return s.get() == targetSprite; + }); if (it != _players.end()) { (*it)->isBillboard = false; @@ -578,44 +603,53 @@ void Zappy::GameScene::removePlayer3D(int id) { _playerMap.erase(id); } if (_playerCrystalMap.contains(id)) { - Sprite *cTarget = _playerCrystalMap[id]; - auto cIt = std::find_if(_crystals.begin(), _crystals.end(), - [cTarget](const std::unique_ptr &s) { return s.get() == cTarget; }); - if (cIt != _crystals.end()) { - (*cIt)->isBillboard = false; - _dyingEntities.push_back({std::move(*cIt), 0.0f}); - _crystals.erase(cIt); - } - _playerCrystalMap.erase(id); + Sprite *cTarget = _playerCrystalMap[id]; + auto cIt = std::find_if(_crystals.begin(), _crystals.end(), + [cTarget](const std::unique_ptr &s) { + return s.get() == cTarget; + }); + if (cIt != _crystals.end()) { + (*cIt)->isBillboard = false; + _dyingEntities.push_back({std::move(*cIt), 0.0f}); + _crystals.erase(cIt); + } + _playerCrystalMap.erase(id); } } int Zappy::GameScene::cleanId(const std::string &idStr) { - if (idStr.empty()) return 0; + if (idStr.empty()) + return 0; return (idStr[0] == '#') ? std::stoi(idStr.substr(1)) : std::stoi(idStr); } -void Zappy::GameScene::spawnEgg3D(int eggId, int x, int y, float offX, float offZ) { - if (_eggMap.contains(eggId)) return; +void Zappy::GameScene::spawnEgg3D(int eggId, int x, int y, float offX, + float offZ) { + if (_eggMap.contains(eggId)) + return; Texture &eggTex = _texManager.get("gui/assets/egg.png"); auto eggSprite = std::make_unique(eggTex); - eggSprite->position = Zappy::Math::vec3((x - offX) * 2.0f, 0.1f, (y - offZ) * 2.0f - 0.5f); + eggSprite->position = + Zappy::Math::vec3((x - offX) * 2.0f, 0.1f, (y - offZ) * 2.0f - 0.5f); eggSprite->scale = Zappy::Math::vec3(0.5f, 0.5f, 0.5f); eggSprite->isBillboard = true; _eggMap[eggId] = eggSprite.get(); _eggs.push_back(std::move(eggSprite)); - - LOG_INFO("GUI: Egg spawned with ID #" + std::to_string(eggId) + " at (" + std::to_string(x) + ", " + std::to_string(y) + ")"); + + LOG_INFO("GUI: Egg spawned with ID #" + std::to_string(eggId) + " at (" + + std::to_string(x) + ", " + std::to_string(y) + ")"); } void Zappy::GameScene::removeEgg3D(int eggId) { if (_eggMap.contains(eggId)) { Sprite *target = _eggMap[eggId]; auto it = std::find_if(_eggs.begin(), _eggs.end(), - [target](const std::unique_ptr &s) { return s.get() == target; }); + [target](const std::unique_ptr &s) { + return s.get() == target; + }); if (it != _eggs.end()) { (*it)->isBillboard = false; @@ -626,23 +660,24 @@ void Zappy::GameScene::removeEgg3D(int eggId) { } } -void Zappy::GameScene::addBroadcastLog(const std::string &sender, const std::string &message) { +void Zappy::GameScene::addBroadcastLog(const std::string &sender, + const std::string &message) { std::string entry = "id: " + sender + " broadcasted: " + message; - + LOG_INFO(entry); _broadcastLogs.insert(_broadcastLogs.begin(), {entry, 0.0f}); - + if (_broadcastLogs.size() > 5) { - _broadcastLogs.pop_back(); + _broadcastLogs.pop_back(); } } -Zappy::Math::vec3 Zappy::GameScene::getTeamColor(const std::string& teamName) { - if (!_teamColors.contains(teamName)) { - float r = (rand() % 155 + 100) / 255.0f; - float g = (rand() % 155 + 100) / 255.0f; - float b = (rand() % 155 + 100) / 255.0f; - _teamColors[teamName] = Zappy::Math::vec3(r, g, b); - } - return _teamColors[teamName]; +Zappy::Math::vec3 Zappy::GameScene::getTeamColor(const std::string &teamName) { + if (!_teamColors.contains(teamName)) { + float r = (rand() % 155 + 100) / 255.0f; + float g = (rand() % 155 + 100) / 255.0f; + float b = (rand() % 155 + 100) / 255.0f; + _teamColors[teamName] = Zappy::Math::vec3(r, g, b); + } + return _teamColors[teamName]; } \ No newline at end of file diff --git a/gui/src/Scene/Game.hpp b/gui/src/Scene/Game.hpp index 5e184da..d9f5d53 100644 --- a/gui/src/Scene/Game.hpp +++ b/gui/src/Scene/Game.hpp @@ -1,30 +1,30 @@ #pragma once +#include "Font/FontManager.hpp" #include "IScene/IScene.hpp" #include "Logger.hpp" -#include "Sprite/InstancedGrid.hpp" -#include "Sprite/Sprite.hpp" -#include "Scene/QuickMenu.hpp" -#include "Scene/TileInventory.hpp" -#include "Scene/PlayerInventory.hpp" -#include "Texture/TextureManager.hpp" -#include "Utils/math.hpp" #include "Network/NetworkManager.hpp" #include "Render/Camera.hpp" #include "Render/Render.hpp" +#include "Scene/PlayerInventory.hpp" +#include "Scene/QuickMenu.hpp" +#include "Scene/TileInventory.hpp" +#include "Sprite/InstancedGrid.hpp" +#include "Sprite/Sprite.hpp" #include "Text/Text.hpp" -#include "Font/FontManager.hpp" +#include "Texture/TextureManager.hpp" +#include "Utils/math.hpp" #include +#include #include #include #include -#include namespace Zappy { struct DyingEntity { - std::unique_ptr sprite; - float timer; + std::unique_ptr sprite; + float timer; }; struct BroadcastMsg { @@ -44,7 +44,7 @@ class GameScene : public IScene { std::map _playerMap; std::unique_ptr _quickMenu = nullptr; bool _wasSpacePressed = false; - + std::unique_ptr _tileInventory; std::unique_ptr _playerInventory; bool _wasPPressed = false; @@ -63,8 +63,8 @@ class GameScene : public IScene { std::vector _dyingEntities; struct PlayerAnim { - std::string type; - float timer; + std::string type; + float timer; }; std::map _playerAnims; @@ -72,22 +72,23 @@ class GameScene : public IScene { std::unique_ptr _gameOverText; struct ActiveIncantation { - int x; - int y; - float timer; - std::unique_ptr sprite; + int x; + int y; + float timer; + std::unique_ptr sprite; }; std::vector _incantations; std::vector> _crystals; std::map _playerCrystalMap; std::map _teamColors; - Zappy::Math::vec3 getTeamColor(const std::string& teamName); + Zappy::Math::vec3 getTeamColor(const std::string &teamName); WindowSize _windowSize; public: - GameScene(TextureManager &tm, Zappy::NetworkManager &nm) : _texManager(tm), _isMapBuilt(false), _networkManager(nm) {} + GameScene(TextureManager &tm, Zappy::NetworkManager &nm) + : _texManager(tm), _isMapBuilt(false), _networkManager(nm) {} void onEnter() override; diff --git a/gui/src/Scene/PlayerInventory.hpp b/gui/src/Scene/PlayerInventory.hpp index c19f7a2..d8f1a8f 100644 --- a/gui/src/Scene/PlayerInventory.hpp +++ b/gui/src/Scene/PlayerInventory.hpp @@ -1,151 +1,162 @@ #pragma once +#include "Buttons/Button.hpp" +#include "Font/FontManager.hpp" #include "IScene/IScene.hpp" -#include "Texture/TextureManager.hpp" -#include "Sprite/Sprite.hpp" #include "Logger.hpp" -#include "Buttons/Button.hpp" #include "Network/NetworkManager.hpp" #include "Render/Render.hpp" +#include "Sprite/Sprite.hpp" #include "Text/Text.hpp" -#include "Font/FontManager.hpp" -#include +#include "Texture/TextureManager.hpp" +#include "Utils/math.hpp" +#include #include +#include #include +#include #include #include -#include -#include "Utils/math.hpp" -#include namespace Zappy { - class playerInventory : public IScene { - private: - TextureManager &_texManager; - std::unique_ptr _playerInventorySprite; - Zappy::NetworkManager &_networkManager; - FontManager &_fontManager; - std::vector> _buttons; - std::unique_ptr _uiShader; - std::unique_ptr _textShader; - std::vector> _resourcesIcons; - std::vector> _resourcesTexts; - std::unique_ptr _Team; - std::unique_ptr _Player; - std::unique_ptr _Lvl; - int _targetPlayerId = -1; - int _currentLvl = -1; - std::array _currentQuantity = {-1, -1, -1, -1, -1, -1, -1}; - std::optional> _target; - public: - playerInventory(TextureManager &tm, Zappy::NetworkManager &nm, FontManager &fm) : _texManager(tm), _networkManager(nm), _fontManager(fm) {} - void onEnter() override { - Texture& playerInventoryTex = _texManager.get("gui/assets/tileInventory.png"); - Font& font = _fontManager.get("gui/assets/fonts/mainTitle.otf", 48.0f); - Font& infoFont = _fontManager.get("gui/assets/fonts/mainTitle.otf", 32.0f); - _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", "gui/src/Core/Shader/ui.frag"); - _textShader = std::make_unique("gui/src/Core/Shader/text.vert", "gui/src/Core/Shader/text.frag"); - _playerInventorySprite = std::make_unique(playerInventoryTex); - _playerInventorySprite->isBillboard = false; - _playerInventorySprite->setPosition(1720.0f, 140.0f); - _playerInventorySprite->setScale(Zappy::Math::vec3(400.0f, 800.0f, 1.0f)); - _playerInventorySprite->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - float fixedX = 1720.0f - 40.0f; - float startY = 320.0f; - float spacing = 80.0f; - _Player = std::make_unique(infoFont, "...", 1720 - 100.0f, 200.0f); - _Player->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - _Team = std::make_unique(infoFont, "...", 1720 - 100.0f, 250.0f); - _Team->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - _Lvl = std::make_unique(infoFont, "...", 1720 - 100.0f, 300.0f); - _Lvl->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - for (int i = 0; i < 7; i++) { - Texture &resTex = _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); - auto spr = std::make_unique(resTex); - spr->isBillboard = false; - spr->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - spr->setPosition(fixedX, startY + (i * spacing)); - spr->setScale(Zappy::Math::vec3(50.0f, 50.0f, 1.0f)); - _resourcesIcons.push_back(std::move(spr)); - auto txt = std::make_unique(font, "0", fixedX + 60.0f, startY + (i * spacing) + 40.0f); - txt->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - _resourcesTexts.push_back(std::move(txt)); - } - } - void setTargetPlayer(int id) - { - _targetPlayerId = id; - _networkManager.sendCommand("pin " + std::to_string(id) + "\n"); - _networkManager.sendCommand("plv " + std::to_string(id) + "\n"); - _currentLvl = -1; - _currentQuantity.fill(-1); - } - SceneState update(const std::vector &events, const Zappy::GameState &gameState, const std::vector &netEvents, float deltaTime) override - { - if (_targetPlayerId == -1 || !gameState.players.contains(_targetPlayerId)) { - _targetPlayerId = -1; - return SceneState::NONE; - } - const Zappy::Player& player = gameState.players.at(_targetPlayerId); +class playerInventory : public IScene { +private: + TextureManager &_texManager; + std::unique_ptr _playerInventorySprite; + Zappy::NetworkManager &_networkManager; + FontManager &_fontManager; + std::vector> _buttons; + std::unique_ptr _uiShader; + std::unique_ptr _textShader; + std::vector> _resourcesIcons; + std::vector> _resourcesTexts; + std::unique_ptr _Team; + std::unique_ptr _Player; + std::unique_ptr _Lvl; + int _targetPlayerId = -1; + int _currentLvl = -1; + std::array _currentQuantity = {-1, -1, -1, -1, -1, -1, -1}; + std::optional> _target; + +public: + playerInventory(TextureManager &tm, Zappy::NetworkManager &nm, + FontManager &fm) + : _texManager(tm), _networkManager(nm), _fontManager(fm) {} + void onEnter() override { + Texture &playerInventoryTex = + _texManager.get("gui/assets/tileInventory.png"); + Font &font = _fontManager.get("gui/assets/fonts/mainTitle.otf", 48.0f); + Font &infoFont = _fontManager.get("gui/assets/fonts/mainTitle.otf", 32.0f); + _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", + "gui/src/Core/Shader/ui.frag"); + _textShader = std::make_unique("gui/src/Core/Shader/text.vert", + "gui/src/Core/Shader/text.frag"); + _playerInventorySprite = std::make_unique(playerInventoryTex); + _playerInventorySprite->isBillboard = false; + _playerInventorySprite->setPosition(1720.0f, 140.0f); + _playerInventorySprite->setScale(Zappy::Math::vec3(400.0f, 800.0f, 1.0f)); + _playerInventorySprite->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + float fixedX = 1720.0f - 40.0f; + float startY = 320.0f; + float spacing = 80.0f; + _Player = std::make_unique(infoFont, "...", 1720 - 100.0f, 200.0f); + _Player->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + _Team = std::make_unique(infoFont, "...", 1720 - 100.0f, 250.0f); + _Team->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + _Lvl = std::make_unique(infoFont, "...", 1720 - 100.0f, 300.0f); + _Lvl->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + for (int i = 0; i < 7; i++) { + Texture &resTex = + _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); + auto spr = std::make_unique(resTex); + spr->isBillboard = false; + spr->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + spr->setPosition(fixedX, startY + (i * spacing)); + spr->setScale(Zappy::Math::vec3(50.0f, 50.0f, 1.0f)); + _resourcesIcons.push_back(std::move(spr)); + auto txt = std::make_unique(font, "0", fixedX + 60.0f, + startY + (i * spacing) + 40.0f); + txt->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + _resourcesTexts.push_back(std::move(txt)); + } + } + void setTargetPlayer(int id) { + _targetPlayerId = id; + _networkManager.sendCommand("pin " + std::to_string(id) + "\n"); + _networkManager.sendCommand("plv " + std::to_string(id) + "\n"); + _currentLvl = -1; + _currentQuantity.fill(-1); + } + SceneState update(const std::vector &events, + const Zappy::GameState &gameState, + const std::vector &netEvents, + float deltaTime) override { + if (_targetPlayerId == -1 || !gameState.players.contains(_targetPlayerId)) { + _targetPlayerId = -1; + return SceneState::NONE; + } + const Zappy::Player &player = gameState.players.at(_targetPlayerId); - if (_currentLvl != player.level) { - _currentLvl = player.level; - std::string playerString = "Player : " + std::to_string(player.id) + "\n"; - _Player->setString(playerString); - std::string playerTeam = "Team : " + player.team + "\n"; - _Team->setString(playerTeam); - std::string playerLvl = "Level : " + std::to_string(player.level) + "\n"; - _Lvl->setString(playerLvl); - } - for (int i = 0; i < 7; ++i) { - int amount = player.inventory[i]; - if (_currentQuantity[i] != amount) { - _currentQuantity[i] = amount; - if ((size_t) i < _resourcesTexts.size() && _resourcesTexts[i]) { - _resourcesTexts[i]->setString(std::to_string(amount)); - } - } - } - return SceneState::NONE; - } - void draw(Shader &shader, WindowSize &windowSize) override { - if (_targetPlayerId == -1 || !_playerInventorySprite || !_uiShader || !_textShader) - return; - glDisable(GL_DEPTH_TEST); - _uiShader->bind(); - Zappy::Math::mat4 orthoProjection = Zappy::Math::ortho(0.0f, WIDTH, HEIGHT, 0.0f, -1.0f, 1.0f); - Zappy::Math::mat4 view; - _playerInventorySprite->draw(*_uiShader, view, orthoProjection); - for (auto &sprite : _resourcesIcons) { - sprite->draw(*_uiShader, view, orthoProjection); - } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if (_Player) - _Player->draw(*_textShader, orthoProjection); - if (_Team) - _Team->draw(*_textShader, orthoProjection); - if (_Lvl) - _Lvl->draw(*_textShader, orthoProjection); - for (auto &txt : _resourcesTexts) { - txt->draw(*_textShader, orthoProjection); - } - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - void onExit() override { - _playerInventorySprite.reset(); - _uiShader.reset(); - _Player.reset(); - _Team.reset(); - _Lvl.reset(); - _textShader.reset(); - _resourcesTexts.clear(); - _resourcesIcons.clear(); - _target = std::nullopt; - _targetPlayerId = -1; - _currentLvl = -1; - _currentQuantity.fill(-1); - } - }; -} \ No newline at end of file + if (_currentLvl != player.level) { + _currentLvl = player.level; + std::string playerString = "Player : " + std::to_string(player.id) + "\n"; + _Player->setString(playerString); + std::string playerTeam = "Team : " + player.team + "\n"; + _Team->setString(playerTeam); + std::string playerLvl = "Level : " + std::to_string(player.level) + "\n"; + _Lvl->setString(playerLvl); + } + for (int i = 0; i < 7; ++i) { + int amount = player.inventory[i]; + if (_currentQuantity[i] != amount) { + _currentQuantity[i] = amount; + if ((size_t)i < _resourcesTexts.size() && _resourcesTexts[i]) { + _resourcesTexts[i]->setString(std::to_string(amount)); + } + } + } + return SceneState::NONE; + } + void draw(Shader &shader, WindowSize &windowSize) override { + if (_targetPlayerId == -1 || !_playerInventorySprite || !_uiShader || + !_textShader) + return; + glDisable(GL_DEPTH_TEST); + _uiShader->bind(); + Zappy::Math::mat4 orthoProjection = + Zappy::Math::ortho(0.0f, WIDTH, HEIGHT, 0.0f, -1.0f, 1.0f); + Zappy::Math::mat4 view; + _playerInventorySprite->draw(*_uiShader, view, orthoProjection); + for (auto &sprite : _resourcesIcons) { + sprite->draw(*_uiShader, view, orthoProjection); + } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (_Player) + _Player->draw(*_textShader, orthoProjection); + if (_Team) + _Team->draw(*_textShader, orthoProjection); + if (_Lvl) + _Lvl->draw(*_textShader, orthoProjection); + for (auto &txt : _resourcesTexts) { + txt->draw(*_textShader, orthoProjection); + } + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + } + void onExit() override { + _playerInventorySprite.reset(); + _uiShader.reset(); + _Player.reset(); + _Team.reset(); + _Lvl.reset(); + _textShader.reset(); + _resourcesTexts.clear(); + _resourcesIcons.clear(); + _target = std::nullopt; + _targetPlayerId = -1; + _currentLvl = -1; + _currentQuantity.fill(-1); + } +}; +} // namespace Zappy \ No newline at end of file diff --git a/gui/src/Scene/QuickMenu.hpp b/gui/src/Scene/QuickMenu.hpp index 3aa0e30..0554914 100644 --- a/gui/src/Scene/QuickMenu.hpp +++ b/gui/src/Scene/QuickMenu.hpp @@ -1,93 +1,109 @@ #pragma once +#include "Buttons/Button.hpp" #include "IScene/IScene.hpp" -#include "Texture/TextureManager.hpp" -#include "Sprite/Sprite.hpp" #include "Logger.hpp" -#include "Buttons/Button.hpp" #include "Network/NetworkManager.hpp" #include "Render/Render.hpp" -#include -#include -#include +#include "Sprite/Sprite.hpp" +#include "Texture/TextureManager.hpp" #include "Utils/math.hpp" #include +#include +#include +#include namespace Zappy { - class quickMenu : public IScene { - private: - TextureManager &_texManager; - std::unique_ptr _backgroundSprite; - Zappy::NetworkManager &_networkManager; - std::unique_ptr _SpeedButton; - std::unique_ptr _IncreaseButton; - std::unique_ptr _DecreaseButton; - std::unique_ptr _uiShader; - int _speed; - WindowSize &_windowSize; - public: - quickMenu(TextureManager &tm, Zappy::NetworkManager &nm, WindowSize &ws) : _texManager(tm), _networkManager(nm), _windowSize(ws) { - _speed = 0; - } - void onEnter() override { - Texture& menuTex = _texManager.get("gui/assets/quickMenu.png"); - Texture& speedButtonTex = _texManager.get("gui/assets/speedButton.png"); - Texture& decreaseButtonTex = _texManager.get("gui/assets/minus.png"); - Texture& increaseButtonTex = _texManager.get("gui/assets/plus.png"); - _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", "gui/src/Core/Shader/ui.frag"); - _backgroundSprite = std::make_unique(menuTex); - - _networkManager.sendCommand("sgt\n"); - auto helper = []() { - std::cout << "You can decrease / increase the number of ticks per seconds." << std::endl; - }; - auto decrease = [this]() { - int newSpeed = std::max(1, this->_speed - 5); - this->_networkManager.sendCommand("sst " + std::to_string(newSpeed) + "\n"); - std::cout << "Decrease requested: " << newSpeed << std::endl; - }; - auto increase = [this]() { - int newSpeed = this->_speed + 5; - this->_networkManager.sendCommand("sst " + std::to_string(newSpeed) + "\n"); - std::cout << "Increase requested: " << newSpeed << std::endl; - }; - _SpeedButton = std::make_unique(speedButtonTex, (_windowSize.width / 2.0f) - 40.0f, (_windowSize.height / 2.0f) - 40.0f, 80.0f, 80.0f, helper, _windowSize); - _IncreaseButton = std::make_unique(increaseButtonTex, 1050.0f, 465.0f, 50.0f, 50.0f, increase, _windowSize); - _DecreaseButton = std::make_unique(decreaseButtonTex, 850.0f, 465.0f, 50.0f, 50.0f, decrease, _windowSize); - _backgroundSprite->scale = Zappy::Math::vec3(415.0f, 415.0f, 1.0f); - } - SceneState update(const std::vector &events, const Zappy::GameState &gameState, const std::vector &netEvents, float deltaTime) override - { - float centerX = _windowSize.width / 2.0f; - float centerY = _windowSize.height / 2.0f; - - _backgroundSprite->setPosition(_windowSize.width / 2.0f, (_windowSize.height - 415.0f) / 2.0f); - _speed = gameState.map.timeUnit; - _SpeedButton->setPosition(centerX - 20.0f, centerY - 40.0f); - _IncreaseButton->setPosition(centerX + 80.0f, centerY - 35.0f); - _DecreaseButton->setPosition(centerX - 95.0f, centerY - 35.0f); - _SpeedButton->update(events); - _DecreaseButton->update(events); - _IncreaseButton->update(events); - return SceneState::NONE; - } - void draw(Shader &shader, WindowSize &windowSize) override { - if (!_backgroundSprite || !_uiShader) - return; - glDisable(GL_DEPTH_TEST); - _uiShader->bind(); - Zappy::Math::mat4 orthoProjection = Zappy::Math::ortho(0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); - Zappy::Math::mat4 view; - _backgroundSprite->draw(*_uiShader, view, orthoProjection); - _SpeedButton->draw(*_uiShader); - _IncreaseButton->draw(*_uiShader); - _DecreaseButton->draw(*_uiShader); - glEnable(GL_DEPTH_TEST); - } - void onExit() override { - _SpeedButton.reset(); - _backgroundSprite.reset(); - _uiShader.reset(); - } +class quickMenu : public IScene { +private: + TextureManager &_texManager; + std::unique_ptr _backgroundSprite; + Zappy::NetworkManager &_networkManager; + std::unique_ptr _SpeedButton; + std::unique_ptr _IncreaseButton; + std::unique_ptr _DecreaseButton; + std::unique_ptr _uiShader; + int _speed; + WindowSize &_windowSize; + +public: + quickMenu(TextureManager &tm, Zappy::NetworkManager &nm, WindowSize &ws) + : _texManager(tm), _networkManager(nm), _windowSize(ws) { + _speed = 0; + } + void onEnter() override { + Texture &menuTex = _texManager.get("gui/assets/quickMenu.png"); + Texture &speedButtonTex = _texManager.get("gui/assets/speedButton.png"); + Texture &decreaseButtonTex = _texManager.get("gui/assets/minus.png"); + Texture &increaseButtonTex = _texManager.get("gui/assets/plus.png"); + _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", + "gui/src/Core/Shader/ui.frag"); + _backgroundSprite = std::make_unique(menuTex); + + _networkManager.sendCommand("sgt\n"); + auto helper = []() { + std::cout + << "You can decrease / increase the number of ticks per seconds." + << std::endl; + }; + auto decrease = [this]() { + int newSpeed = std::max(1, this->_speed - 5); + this->_networkManager.sendCommand("sst " + std::to_string(newSpeed) + + "\n"); + std::cout << "Decrease requested: " << newSpeed << std::endl; }; -} + auto increase = [this]() { + int newSpeed = this->_speed + 5; + this->_networkManager.sendCommand("sst " + std::to_string(newSpeed) + + "\n"); + std::cout << "Increase requested: " << newSpeed << std::endl; + }; + _SpeedButton = std::make_unique( + speedButtonTex, (_windowSize.width / 2.0f) - 40.0f, + (_windowSize.height / 2.0f) - 40.0f, 80.0f, 80.0f, helper, _windowSize); + _IncreaseButton = + std::make_unique(increaseButtonTex, 1050.0f, 465.0f, + 50.0f, 50.0f, increase, _windowSize); + _DecreaseButton = std::make_unique( + decreaseButtonTex, 850.0f, 465.0f, 50.0f, 50.0f, decrease, _windowSize); + _backgroundSprite->scale = Zappy::Math::vec3(415.0f, 415.0f, 1.0f); + } + SceneState update(const std::vector &events, + const Zappy::GameState &gameState, + const std::vector &netEvents, + float deltaTime) override { + float centerX = _windowSize.width / 2.0f; + float centerY = _windowSize.height / 2.0f; + + _backgroundSprite->setPosition(_windowSize.width / 2.0f, + (_windowSize.height - 415.0f) / 2.0f); + _speed = gameState.map.timeUnit; + _SpeedButton->setPosition(centerX - 20.0f, centerY - 40.0f); + _IncreaseButton->setPosition(centerX + 80.0f, centerY - 35.0f); + _DecreaseButton->setPosition(centerX - 95.0f, centerY - 35.0f); + _SpeedButton->update(events); + _DecreaseButton->update(events); + _IncreaseButton->update(events); + return SceneState::NONE; + } + void draw(Shader &shader, WindowSize &windowSize) override { + if (!_backgroundSprite || !_uiShader) + return; + glDisable(GL_DEPTH_TEST); + _uiShader->bind(); + Zappy::Math::mat4 orthoProjection = Zappy::Math::ortho( + 0.0f, _windowSize.width, _windowSize.height, 0.0f, -1.0f, 1.0f); + Zappy::Math::mat4 view; + _backgroundSprite->draw(*_uiShader, view, orthoProjection); + _SpeedButton->draw(*_uiShader); + _IncreaseButton->draw(*_uiShader); + _DecreaseButton->draw(*_uiShader); + glEnable(GL_DEPTH_TEST); + } + void onExit() override { + _SpeedButton.reset(); + _backgroundSprite.reset(); + _uiShader.reset(); + } +}; +} // namespace Zappy diff --git a/gui/src/Scene/TileInventory.hpp b/gui/src/Scene/TileInventory.hpp index dbdd8f9..f446ee4 100644 --- a/gui/src/Scene/TileInventory.hpp +++ b/gui/src/Scene/TileInventory.hpp @@ -1,112 +1,119 @@ #pragma once +#include "Buttons/Button.hpp" +#include "Font/FontManager.hpp" #include "IScene/IScene.hpp" -#include "Texture/TextureManager.hpp" -#include "Sprite/Sprite.hpp" #include "Logger.hpp" -#include "Buttons/Button.hpp" #include "Network/NetworkManager.hpp" #include "Render/Render.hpp" +#include "Sprite/Sprite.hpp" #include "Text/Text.hpp" -#include "Font/FontManager.hpp" -#include +#include "Texture/TextureManager.hpp" +#include "Utils/math.hpp" +#include #include +#include #include +#include #include #include -#include -#include "Utils/math.hpp" -#include namespace Zappy { - class tileInventory : public IScene { - private: - TextureManager &_texManager; - std::unique_ptr _tileInventorySprite; - Zappy::NetworkManager &_networkManager; - FontManager &_fontManager; - std::vector> _buttons; - std::unique_ptr _uiShader; - std::unique_ptr _textShader; - std::vector> _resourcesIcons; - std::vector> _resourcesTexts; - std::array _currentQuantity = {-1, -1, -1, -1, -1, -1, -1}; - std::optional> _target; - public: - tileInventory(TextureManager &tm, Zappy::NetworkManager &nm, FontManager &fm) : _texManager(tm), _networkManager(nm), _fontManager(fm) {} - void onEnter() override { - Texture& tileInventoryTex = _texManager.get("gui/assets/tileInventory.png"); - Font& font = _fontManager.get("gui/assets/fonts/mainTitle.otf", 48.0f); - _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", "gui/src/Core/Shader/ui.frag"); - _textShader = std::make_unique("gui/src/Core/Shader/text.vert", "gui/src/Core/Shader/text.frag"); - _tileInventorySprite = std::make_unique(tileInventoryTex); - _tileInventorySprite->isBillboard = false; - _tileInventorySprite->setPosition(200.0f, 140.0f); - _tileInventorySprite->setScale(Zappy::Math::vec3(400.0f, 800.0f, 1.0f)); - _tileInventorySprite->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - float fixedX = 160.0f; - float startY = 180.0f; - float spacing = 110.0f; - for (int i = 0; i < 7; i++) { - Texture &resTex = _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); - auto spr = std::make_unique(resTex); - spr->isBillboard = false; - spr->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - spr->setPosition(fixedX, startY + (i * spacing)); - spr->setScale(Zappy::Math::vec3(50.0f, 50.0f, 1.0f)); - _resourcesIcons.push_back(std::move(spr)); - auto txt = std::make_unique(font, "0", fixedX + 60.0f, startY + (i * spacing) + 40.0f); - txt->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); - _resourcesTexts.push_back(std::move(txt)); - } - } - void setTargetTile(const Zappy::Tile& tile) - { - _target = tile; - } - SceneState update(const std::vector &events, const Zappy::GameState &gameState, const std::vector &netEvents, float deltaTime) override - { - if (!_target) - return SceneState::NONE; - const Zappy::Tile& currentTile = _target->get(); +class tileInventory : public IScene { +private: + TextureManager &_texManager; + std::unique_ptr _tileInventorySprite; + FontManager &_fontManager; + std::vector> _buttons; + std::unique_ptr _uiShader; + std::unique_ptr _textShader; + std::vector> _resourcesIcons; + std::vector> _resourcesTexts; + std::array _currentQuantity = {-1, -1, -1, -1, -1, -1, -1}; + std::optional> _target; + +public: + tileInventory(TextureManager &tm, Zappy::NetworkManager &nm, FontManager &fm) + : _texManager(tm), _fontManager(fm) { + (void)nm; + } + void onEnter() override { + Texture &tileInventoryTex = _texManager.get("gui/assets/tileInventory.png"); + Font &font = _fontManager.get("gui/assets/fonts/mainTitle.otf", 48.0f); + _uiShader = std::make_unique("gui/src/Core/Shader/ui.vert", + "gui/src/Core/Shader/ui.frag"); + _textShader = std::make_unique("gui/src/Core/Shader/text.vert", + "gui/src/Core/Shader/text.frag"); + _tileInventorySprite = std::make_unique(tileInventoryTex); + _tileInventorySprite->isBillboard = false; + _tileInventorySprite->setPosition(200.0f, 140.0f); + _tileInventorySprite->setScale(Zappy::Math::vec3(400.0f, 800.0f, 1.0f)); + _tileInventorySprite->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + float fixedX = 160.0f; + float startY = 180.0f; + float spacing = 110.0f; + for (int i = 0; i < 7; i++) { + Texture &resTex = + _texManager.get("gui/assets/resource_" + std::to_string(i) + ".png"); + auto spr = std::make_unique(resTex); + spr->isBillboard = false; + spr->rotation = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + spr->setPosition(fixedX, startY + (i * spacing)); + spr->setScale(Zappy::Math::vec3(50.0f, 50.0f, 1.0f)); + _resourcesIcons.push_back(std::move(spr)); + auto txt = std::make_unique(font, "0", fixedX + 60.0f, + startY + (i * spacing) + 40.0f); + txt->color = Zappy::Math::vec3(0.0f, 0.0f, 0.0f); + _resourcesTexts.push_back(std::move(txt)); + } + } + void setTargetTile(const Zappy::Tile &tile) { _target = tile; } + SceneState update(const std::vector &events, + const Zappy::GameState &gameState, + const std::vector &netEvents, + float deltaTime) override { + if (!_target) + return SceneState::NONE; + const Zappy::Tile ¤tTile = _target->get(); - for (int i = 0; i < 7; ++i) { - int amount = currentTile.resources[i]; - if (_currentQuantity[i] != amount) { - _currentQuantity[i] = amount; - if ((size_t)i < _resourcesTexts.size() && _resourcesTexts[i]) - _resourcesTexts[i]->setString(std::to_string(amount)); - } - } - return SceneState::NONE; - } - void draw(Shader &shader, WindowSize &windowSize) override { - if (!_tileInventorySprite || !_uiShader) - return; - glDisable(GL_DEPTH_TEST); - _uiShader->bind(); - Zappy::Math::mat4 orthoProjection = Zappy::Math::ortho(0.0f, WIDTH, HEIGHT, 0.0f, -1.0f, 1.0f); - Zappy::Math::mat4 view; - _tileInventorySprite->draw(*_uiShader, view, orthoProjection); - for (auto &sprite : _resourcesIcons) { - sprite->draw(*_uiShader, view, orthoProjection); - } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - for (auto &txt : _resourcesTexts) { - txt->draw(*_textShader, orthoProjection); - } - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - void onExit() override { - _tileInventorySprite.reset(); - _uiShader.reset(); - _textShader.reset(); - _resourcesTexts.clear(); - _resourcesIcons.clear(); - _target = std::nullopt; - _currentQuantity.fill(-1); - } - }; -} \ No newline at end of file + for (int i = 0; i < 7; ++i) { + int amount = currentTile.resources[i]; + if (_currentQuantity[i] != amount) { + _currentQuantity[i] = amount; + if ((size_t)i < _resourcesTexts.size() && _resourcesTexts[i]) + _resourcesTexts[i]->setString(std::to_string(amount)); + } + } + return SceneState::NONE; + } + void draw(Shader &shader, WindowSize &windowSize) override { + if (!_tileInventorySprite || !_uiShader) + return; + glDisable(GL_DEPTH_TEST); + _uiShader->bind(); + Zappy::Math::mat4 orthoProjection = + Zappy::Math::ortho(0.0f, WIDTH, HEIGHT, 0.0f, -1.0f, 1.0f); + Zappy::Math::mat4 view; + _tileInventorySprite->draw(*_uiShader, view, orthoProjection); + for (auto &sprite : _resourcesIcons) { + sprite->draw(*_uiShader, view, orthoProjection); + } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + for (auto &txt : _resourcesTexts) { + txt->draw(*_textShader, orthoProjection); + } + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + } + void onExit() override { + _tileInventorySprite.reset(); + _uiShader.reset(); + _textShader.reset(); + _resourcesTexts.clear(); + _resourcesIcons.clear(); + _target = std::nullopt; + _currentQuantity.fill(-1); + } +}; +} // namespace Zappy \ No newline at end of file diff --git a/gui/src/Sprite/Sprite.cpp b/gui/src/Sprite/Sprite.cpp index d520bb8..8b27740 100644 --- a/gui/src/Sprite/Sprite.cpp +++ b/gui/src/Sprite/Sprite.cpp @@ -5,8 +5,8 @@ namespace Zappy { Sprite::Sprite(Texture &texture) - : _VAO(0), _VBO(0), _EBO(0), _texture(texture), rotation(0.0f, 0.0f, 0.0f), position(0.0f, 0.0f, 0.0f), - scale(1.0f, 1.0f, 1.0f), isBillboard(false) { + : _VAO(0), _VBO(0), _EBO(0), _texture(texture), rotation(0.0f, 0.0f, 0.0f), + position(0.0f, 0.0f, 0.0f), scale(1.0f, 1.0f, 1.0f), isBillboard(false) { float vertices[] = {0.5f, 1.0f, 1.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, -0.5f, 1.0f, 0.0f, 1.0f}; unsigned int indices[] = {0, 1, 3, 1, 2, 3}; @@ -34,15 +34,15 @@ Sprite::Sprite(Texture &texture) glBindVertexArray(0); } - -Sprite::Sprite(Texture &texture, Zappy::Math::vec2 uvScale, Zappy::Math::vec2 uvOffset) - : _VAO(0), _VBO(0), _EBO(0), _texture(texture), rotation(0,0,0), position(0.0f, 0.0f, 0.0f), - scale(1.0f, 1.0f, 1.0f), isBillboard(false), +Sprite::Sprite(Texture &texture, Zappy::Math::vec2 uvScale, + Zappy::Math::vec2 uvOffset) + : _VAO(0), _VBO(0), _EBO(0), _texture(texture), rotation(0, 0, 0), + position(0.0f, 0.0f, 0.0f), scale(1.0f, 1.0f, 1.0f), isBillboard(false), _uvOffset(uvOffset), _uvScale(uvScale) { float vertices[] = {0.5f, 1.0f, 1.0f, 1.0f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.0f, 0.0f, 0.0f, -0.5f, 1.0f, 0.0f, 1.0f}; unsigned int indices[] = {0, 1, 3, 1, 2, 3}; - + glGenVertexArrays(1, &_VAO); glGenBuffers(1, &_VBO); glGenBuffers(1, &_EBO); @@ -136,8 +136,7 @@ void Sprite::setPosition(float x, float y) { position.y = y; } -void Sprite::setScale(Zappy::Math::vec3 newScale) -{ +void Sprite::setScale(Zappy::Math::vec3 newScale) { scale.x = newScale.x; scale.y = newScale.y; scale.z = newScale.z; diff --git a/gui/src/Texture/Texture.cpp b/gui/src/Texture/Texture.cpp index e38de65..22018a2 100644 --- a/gui/src/Texture/Texture.cpp +++ b/gui/src/Texture/Texture.cpp @@ -25,7 +25,7 @@ Texture::Texture(const std::string &filepath) stbi_set_flip_vertically_on_load(true); unsigned char *data = - stbi_load(filepath.c_str(), &_width, &_height, &_channels, 4); + stbi_load(filepath.c_str(), &_width, &_height, &_channels, 4); if (!data) { LOG_ERROR("Error while loading the texture" + filepath); diff --git a/gui/src/Window/WindowLinux.cpp b/gui/src/Window/WindowLinux.cpp index 07680bf..6a0cd22 100644 --- a/gui/src/Window/WindowLinux.cpp +++ b/gui/src/Window/WindowLinux.cpp @@ -194,9 +194,9 @@ const std::vector &Window::pollEvents() { if (xev.xbutton.button == 4) { event.type = EventType::MouseWheelMove; event.wheelDelta = 1; - } else if ( xev.xbutton.button == 5) { + } else if (xev.xbutton.button == 5) { event.type = EventType::MouseWheelMove; - event.wheelDelta -=1; + event.wheelDelta -= 1; } else { event.type = EventType::MousePressed; event.mouseX = xev.xbutton.x; @@ -226,10 +226,10 @@ const std::vector &Window::pollEvents() { } void Window::getSize(unsigned int &width, unsigned int &height) const { - XWindowAttributes gwa; - XGetWindowAttributes((Display*)_display, (::Window)_windowHandle, &gwa); - width = gwa.width; - height = gwa.height; + XWindowAttributes gwa; + XGetWindowAttributes((Display *)_display, (::Window)_windowHandle, &gwa); + width = gwa.width; + height = gwa.height; } } // namespace Zappy diff --git a/gui/src/Window/WindowWindow.cpp b/gui/src/Window/WindowWindow.cpp index 61a6e6f..5f1ad5c 100644 --- a/gui/src/Window/WindowWindow.cpp +++ b/gui/src/Window/WindowWindow.cpp @@ -214,10 +214,10 @@ void Window::swapBuffers() { } void Window::getSize(unsigned int &width, unsigned int &height) const { - RECT rect; - GetClientRect((HWND)_windowHandle, &rect); - width = rect.right - rect.left; - height = rect.bottom - rect.top; + RECT rect; + GetClientRect((HWND)_windowHandle, &rect); + width = rect.right - rect.left; + height = rect.bottom - rect.top; } } // namespace Zappy diff --git a/gui/src/main.cpp b/gui/src/main.cpp index 549923c..9297b33 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -11,24 +11,21 @@ int main(int ac, char **av) { std::string ip; std::string machine; - if (ac == 5){ + if (ac == 5) { if (std::string(av[1]) == "-p") machine = av[2]; if (std::string(av[3]) == "-h") ip = av[4]; } else { - throw std::runtime_error("USAGE: ./zappy_gui -p port -h machine"); + throw std::runtime_error("USAGE: ./zappy_gui -p port -h machine"); } - try - { + try { core.init(ip, std::stoi(machine)); - } - catch(const std::exception& e) - { + } catch (const std::exception &e) { throw std::runtime_error("USAGE: ./zappy_gui -p port -h machine"); } - + core.run(); } catch (const std::exception &e) { diff --git a/ia/Dockerfile b/ia/Dockerfile deleted file mode 100644 index 67b7828..0000000 --- a/ia/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM python:3.13.14-trixie -WORKDIR /app -COPY . . -ENTRYPOINT [ "python3.13", "zappy_ai.py" ] - -# HOW TO USE # -# To build the image, run this : -# docker build --tag client . -# To run it, use it like so : -# docker run --net=host client:latest -h -p -n diff --git a/ia/anthill/anthill.py b/ia/anthill/anthill.py deleted file mode 100644 index 34a207d..0000000 --- a/ia/anthill/anthill.py +++ /dev/null @@ -1,661 +0,0 @@ -from network.network import * -from communication.communication import * -from anthill.const import * -from player.player import * -import base64 - - -def sortWeaknest(weak): - return weak[1] - - - -class deathExeption(BaseException): - pass - - - -class ia: - def __init__(self, team: str, connection: network, tick:int, key:int, role: int, state = Spawn, id: int = 0): - self.tick = tick - self.overview = "" - self.inv = "" - self.broadcast = [] - self.all = "" - self.alive = True - self.state = state - self.level = 0 - self.player = Player(connection) - self.team = team - self.role = role - self.key = key - self.prev = 0 - self.id = id - self.nextid = 1 - self.follow = "" - self.waiting = "" - self.savedBroadcast = [] - self.sabotage = [] - self.weakest = [] - self.needed = 0 - self.nbReady = 0 - self.connectNbr = 0 - self.waitIncanting = "" - self.strId = chr((int(self.id * 0.01) + A)) + chr((int(self.id * 0.1) % 100 + A)) + chr((self.id % 10 + A)) - - # - # Fork - # - - def startNewIa(self, role: int, state: int = Collect): - """ - Start a new ai on an empty egg. - """ - self.player.connection.taskGroup.create_task( - runIa( - port= self.player.connection.port, - teamName= self.team, - machine= self.player.connection.machine, - tg= self.player.connection.taskGroup, - tick= self.tick, - key= self.key, - role= role, - state= state, - id= self.nextid - ) - ) - self.nextid += 1 - - - - async def collectRessource(self): - """ - Collect all ressources depending on the current state. - """ - if self.state == Collect: - for i in range(0, 7): - if ressources[i] in self.overview[0]: - await self.Take(ressources[i]) - else: - await self.Take("food") - - - - - - # - # Is useful function - # - - - - - - def upTick(self, val: int): - """ - Update the curent tick of the ai by avoiding an overflow. - """ - self.tick = self.tick + val if maxInt - self.tick > val else val - maxInt - self.tick - - - - async def readUntil(self): - """ - Read all data received until a data is not equal to a broadcast, and return this data. - """ - msg = "" - needToContinue = True - result = "" - while needToContinue: - msg = await self.player.read() - for s in msg.split("\n")[:-1]: - if s == "dead": - raise(deathExeption("You're dead")) - elif "message" not in s: - needToContinue = False - result = s - else: - self.broadcast.append(s) - return result - - - - async def wait(self, wait: int): - """ - Make the ai wait. - """ - for i in range(0, wait): - await self.Look() - - - - - - # - # Call the command to the server and clean the response - # - - - - - - - async def Forward(self): - """ - Make the ai move forward. - """ - await self.player.Forward() - self.upTick(7) - await self.readUntil() - - async def Right(self): - """ - Make the ai turn to its right. - """ - await self.player.Right() - self.upTick(7) - await self.readUntil() - - async def Left(self): - """ - Make the ai turn to its left. - """ - await self.player.Left() - self.upTick(7) - await self.readUntil() - - async def Look(self): - """ - Get the information of all elements in front of the ai. - """ - await self.player.Look() - self.upTick(7) - self.overview = (await self.readUntil()).split(",") - - - - async def Inv(self): - """ - Get the information of the current inventory of the ai. - """ - await self.player.Inventory() - self.upTick(7) - self.inv = (await self.readUntil()).split(",") - self.inv[-1] = self.inv[-1][:-1] - for i, elem in enumerate(self.inv): - self.inv[i] = int(elem.split(" ")[-1]) - - - - async def Broadcast(self, msg: str): - """ - Send a message to everyone. - """ - msg, self.key = crypt(self.role, msg, self.key) - await self.player.Broadcast(msg) - self.upTick(7) - await self.readUntil() - - - - async def Connect_nbr(self): - """ - Send a message to everyone. - """ - await self.player.Connect_nbr() - return int(await self.readUntil()) - - - - async def Fork(self, role: int): - """ - Create a new slot for another ai. - """ - await self.readQueen() - if self.connectNbr: - self.startNewIa(role, Survivor) - else: - await self.player.Fork() - self.upTick(42) - if await self.readUntil() == "ok": - self.startNewIa(role, Survivor) - - - - async def Eject(self): - """ - Push all ai on the same cell. - """ - await self.player.Eject() - self.upTick(7) - await self.readUntil() - - - - async def Take(self, obj: str): - """ - Try to take an object. - """ - await self.player.Take(obj) - self.upTick(7) - await self.readUntil() - - - - async def Set(self, obj: str): - """ - Try removing an item from its inventory. - """ - await self.player.Set(obj) - self.upTick(7) - await self.readUntil() - - - - async def Incantation(self): - """ - Try to process an elevation. - """ - await self.player.Incantation() - self.upTick(300) - res = await self.readUntil() - if res == "Elevation underway": - await self.readUntil() - self.level += 1 - - - - - - # - # Define each state comportement - # - - - - - - async def Spawn(self): - - """ - Is the first behavior of the ai when she spawn - """ - await self.Look() - if "food" in self.overview[0]: - await self.Take("food") - elif "food" in self.overview[2]: - await self.Forward() - await self.Take("food") - self.connectNbr = await self.Connect_nbr() - self.state = Survivor - - - - async def Collect(self): - """ - Is the behavior of an ai when she's in collect state. - """ - await self.Look() - if "food" in self.overview[0]: - await self.collectRessource() - elif "food" in self.overview[2] and not "player" in self.overview[2]: - await self.Forward() - await self.collectRessource() - if "food" in self.overview[1] and not "player" in self.overview[1]: - await self.Left() - await self.Forward() - await self.collectRessource() - elif "food" in self.overview[3] and not "player" in self.overview[3]: - await self.Right() - await self.Forward() - await self.collectRessource() - elif "food" in self.overview[1] and not "player" in self.overview[1]: - await self.Forward() - await self.Left() - await self.Forward() - await self.collectRessource() - elif "food" in self.overview[3] and not "player" in self.overview[3]: - await self.Forward() - await self.Right() - await self.Forward() - await self.collectRessource() - else: - await self.Forward() - - - - async def Call(self): - """ - Is the behavior of an ai when she want to level up. - """ - self.needed = int(steps[self.level][0]) - 1 - self.weakest = [] - - for i in range(1, 7): - if steps[self.level][i] > self.inv[i]: - self.state = Collect - if self.role != Queen: - await self.Broadcast("END OF THE INCANTATION" + self.strId) - return - if self.needed >= 1: - await self.Broadcast("WHO IS ALIVE ?" + chr(self.level + A) + self.strId) - await self.wait(10) - - for i in range(30): - self.broadcast += self.player.readNoWait().split("\n") - - self.nbReady = 0 - prevKey = self.key - for broad in self.broadcast: - self.savedBroadcast.append(broad) - msg, new_key, _ = decrypt(broad[11:], self.key) - if "NOT ME MY QUEEN" in msg: - prevKey = self.key - self.key = new_key - elif "I CAN'T MY QUEEN" in msg: - prevKey = self.key - self.key = new_key - self.weakest.append([msg[-4:-1], msg[-1:]]) - elif "ME MY QUEEN" in msg: - prevKey = self.key - self.key = new_key - self.nbReady += 1 - else: - msg, new_key, _ = decrypt(broad[11:], prevKey) - if "NOT ME MY QUEEN" in msg: - pass - elif "ME MY QUEEN" in msg: - self.nbReady += 1 - elif "I CAN'T MY QUEEN" in msg: - self.weakest.append([msg[-4:-1], msg[-1:]]) - if self.nbReady < self.needed: - self.state = Collect - if len(self.weakest): - self.weakest.sort(key=sortWeaknest) - await self.Broadcast("IMPROVE YOURSELF" + self.weakest[-1:][0][0]) - self.state = WaitingIncanting - self.waitIncanting = self.weakest[-1:][0][0] - else: - self.state = Survivor - return - - nbHere = 0 - prevKey = self.key - while nbHere < self.needed: - await self.Broadcast("WHERE ARE YOU NOW ?" + self.strId) - await self.wait(8) - for i in range(30): - self.broadcast += self.player.readNoWait().split("\n") - self.savedBroadcast += self.broadcast - for broad in self.broadcast: - msg, new_key, _ = decrypt(broad[11:], self.key) - if "HERE MY QUEEN" in msg: - prevKey = self.key - self.key = new_key - if broad[8:9] == "0": - nbHere += 1 - else: - msg, new_key, _ = decrypt(broad[11:], prevKey) - if "HERE MY QUEEN" in msg: - if broad[8:9] == "0": - nbHere += 1 - self.broadcast = [] - - await self.Broadcast("START OF THE INCANTATION" + self.strId) - for i in range(1, 7): - for _ in range(steps[self.level][i]): - await self.Set(ressources[i]) - await self.Incantation() - await self.Broadcast("END OF THE INCANTATION" + self.strId) - self.state = Collect - self.needed = 0 - - - - - - # - # point towards the right behavior - # - - - - - - async def readWorker(self): - """ - Make the workers read all message and processes them. - """ - self.broadcast += self.player.readNoWait().split("\n") - state = [] - keep = "" - for broad in self.broadcast: - msg, new_key, _ = decrypt(broad[11:], self.key) - if msg[-3:] == self.strId: - pass - if "WHO IS ALIVE" in msg: - self.key = new_key - self.follow = msg[-3:] - await self.Inv() - for i in range(1, 7): - if steps[self.level][i] > self.inv[i]: - state.append(Hungry) - if self.inv[FOOD] > 15: - self.state = Collect - else: - self.state = Survivor - if Hungry not in state: - if ord(msg[14:15]) == self.level + A: - if self.inv[FOOD] >= 20: - state.append(Join) - else: - state.append(Hungry) - elif ord(msg[14:15]) < self.level + A: - pass - else: - state.append(Weak) - elif "WHERE ARE YOU NOW" in msg: - self.key = new_key - if self.state == Join and msg[-3:] == self.follow: - keep = broad[8:9] - state.append(Joining) - elif "IMPROVE YOURSELF" in msg: - self.key = new_key - keep = msg[-3:] - state.append(Improving) - elif "ME MY QUEEN" in msg or "HERE MY QUEEN" in msg or "NOT ME MY QUEEN" in msg or "I CAN'T MY QUEEN" in msg: - self.key = new_key - elif "END OF THE INCANTATION" in msg: - self.key = new_key - if self.state == WaitingIncanting and msg[-3:] == self.waitIncanting: - if len(self.weakest): - self.weakest.pop() - self.needed -= 1 - state.append(Improving) - keep = self.strId - if self.follow == msg[-3:] or self.waiting == msg[-3:]: - self.follow = "" - self.state = Survivor - elif "START OF THE INCANTATION" in msg: - self.key = new_key - if self.state != Here and self.state != WaitingIncanting: - self.follow = "" - self.state = ForcedSurvivor - self.waiting = msg[-3:] - elif "ME MY QUEEN" in msg : - self.key = new_key - elif broad != "": - self.sabotage.append(broad[11:]) - self.broadcast = [] - for s in state: - if s == Join : - self.state = Join - await self.Broadcast("ME MY QUEEN ") - elif s == Weak: - await self.Broadcast("I CAN'T MY QUEEN" + self.strId + chr(self.level + 1)) - self.follow = "" - elif s == Hungry: - await self.Broadcast("NOT ME MY QUEEN ") - self.follow = "" - elif s == Improving: - if keep == self.strId: - if self.needed and len(self.weakest): - await self.Broadcast("IMPROVE YOURSELF" + self.weakest[-1:][0][0]) - self.waitIncanting = self.weakest[-1:][0][0] - else: - await self.Call() - elif s == Joining: - await self.Broadcast("HERE MY QUEEN" + self.follow) - dir = keep - if dir == "1" or dir == "2" or dir == "8": - await self.Forward() - elif dir == "3" or dir == "4": - await self.Left() - await self.Forward() - elif dir == "7" or dir == "6": - await self.Right() - await self.Forward() - elif dir == "5": - await self.Left() - await self.Left() - await self.Forward() - elif dir == "0": - self.state = Here - - - - async def readQueen(self): - """ - Make the queen read all message and processes them. - """ - self.broadcast += self.player.readNoWait().split("\n") - for broad in self.broadcast: - msg, new_key, _ = decrypt(broad[11:], self.key) - if msg[-3:] == self.strId: - pass - if ("WHO IS ALIVE" in msg or "WHERE ARE YOU NOW" in msg or "IMPROVE YOURSELF" in msg or "ME MY QUEEN" in msg or "NOT ME MY QUEEN" in msg or "I CAN'T MY QUEEN" in msg or "HERE MY QUEEN" in msg or "START OF THE INCANTATION" in msg) and msg[-3:] != self.strId: - self.key = new_key - elif "END OF THE INCANTATION" in msg: - self.key = new_key - if msg[-3:] == self.waitIncanting: - self.weakest.pop() - self.needed -= 1 - if not self.needed: - await self.Call() - elif len(self.weakest): - await self.Broadcast("IMPROVE YOURSELF" + self.weakest[-1:][0][0]) - self.waitIncanting = self.weakest[-1:][0][0] - else: - self.state = Survivor - self.broadcast = [] - - - - async def takeDecision(self): - """ - Point towards the right behavior - """ - if self.role == Workers: - if self.state == Collect or self.state == Survivor or self.state == ForcedSurvivor or self.state == WaitingIncanting: - await self.Collect() - if self.tick - self.prev >= 700 and self.state != ForcedSurvivor and self.state != WaitingIncanting: - await self.Inv() - self.prev = self.tick - if self.inv[FOOD] > 20: - self.state = Collect - elif self.inv[FOOD] <= 20: - self.state == Survivor - elif self.state == Improving: - await self.Call() - elif self.state == Join: - await self.Inv() - elif self.state == Here: - result = await self.readUntil() - if result == "Elevation underway": - await self.readUntil() - self.level += 1 - elif "Current level:" in result: - self.level += 1 - self.state = Survivor - await self.readWorker() - elif self.role == Queen: - if self.state == Spawn: - await self.Spawn() - elif self.state == Collect or self.state == Survivor or self.state == WaitingIncanting: - await self.Collect() - if self.state != WaitingIncanting and self.tick - self.prev >= 700: - await self.Inv() - self.prev = self.tick - if self.inv[FOOD] > 20 and self.inv[PHIRAS] < 6: - self.state = Collect - elif self.inv[FOOD] <= 20 or self.inv[PHIRAS] >= 6: - self.state == Survivor - if self.inv[FOOD] > 20: - await self.Fork(Workers) - if self.inv[FOOD] > 30 and self.level < 7: - self.state = Call - await self.readQueen() - elif self.state == Call: - await self.Call() - self.prev = self.tick - - - - async def isAlive(self): - """ - Return if the ai is alive - """ - self.all += self.player.readNoWait() - for msg in self.broadcast: - if msg == "dead": - self.alive = False - break - splited = self.all.split("\n") - for msg in splited: - if msg == "dead": - self.alive = False - break - self.broadcast += [base64.b64decode(broad.encode()).decode("ascii") for broad in splited] - return self.alive - - - - async def landing(self): - """ - Is the first step to connect the ai to the server - """ - message = await self.player.read() - if "WELCOME" not in message: - raise Exception("No welcome message") - await self.player.send(self.team + "\n") - message = await self.player.read() - if "ko" in message: - raise Exception("Couldn't join") - message = message.split("\n") - if len(message) < 2 or message[1] == "": - await self.readUntil() - - - - - - # - # Is the main of the ia - # - - - - - -async def runIa(port: int, teamName: str, machine: str, tg: asyncio.TaskGroup, tick: int = 0, key: int = 0, role: int = Queen, state: int = Spawn, id: int = 0): - try: - connection = await connect(port, machine, tg) - myIa = ia(teamName, connection, tick, key, role, state, id) - await myIa.landing() - while await myIa.isAlive(): - await myIa.takeDecision() - except deathExeption as e: - pass - except Exception as e: - print(e) - return \ No newline at end of file diff --git a/ia/anthill/const.py b/ia/anthill/const.py deleted file mode 100644 index 40ecc8b..0000000 --- a/ia/anthill/const.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys - -maxInt = sys.maxsize -A = ord('A') -# -FOOD = 0 -LIMEMATE = 1 -DERAUMERE = 2 -SIBUR = 3 -MENDIANE = 4 -PHIRAS = 5 -THYSTAME = 6 - - -# only Queen states -Spawn = 0 -Hungry = 2 -Call = 3 - -#only Workers/Guards states -Feed = 4 -Join = 5 -Joining = 6 -Improving = 7 -ForcedSurvivor = 9 -Here = 10 -Sabotage = 11 -Weak = 12 - -#all States -Collect = 1 -Survivor = 8 -WaitingIncanting = 13 - -steps = [ - [1, 1, 0, 0, 0, 0, 0], - [2, 1, 1, 1, 0, 0, 0], - [2, 2, 0, 1, 0, 2, 0], - [4, 1, 1, 2, 0, 1, 0], - [4, 1, 2, 1, 3, 0, 0], - [6, 1, 2, 3, 0, 1, 0], - [6, 2, 2, 2, 2, 2, 1], -] - -ressources = ["food", "linemate", "deraumere", "sibur", "mendiane", "phiras", "thystame"] - -nearest = [[[0, 1]], #0 - [[2, 2], [1, 4], [3, 4]], #1 - [[6, 3], [5, 5], [7, 5]], #2 - [[12, 4]], #3 - [[20, 5]], #4 - ] diff --git a/ia/communication/communication.py b/ia/communication/communication.py deleted file mode 100644 index a51e257..0000000 --- a/ia/communication/communication.py +++ /dev/null @@ -1,61 +0,0 @@ -import sys -import base64 - -max_int = sys.maxsize -Queen = 1 -Workers = 2 -Guard = 3 - -def crypt(role: int, msg: str, key: int): - - # get the len of the message - size = len(msg) - - # get the role of the sender - tag = '' - if role == Queen: - tag = 'Q' - elif role == Workers: - tag = 'W' - else: - tag = 'G' - - # get the crypted message - crypted_msg = chr(size) + tag - crypted_msg += ''.join(chr(ord(a) ^ key) for a in msg) - crypted_msg = base64.b64encode(crypted_msg.encode("utf-8")).decode("utf-8") - - # update the key - key = key + size if max_int - size > key else size - (max_int - key) - - return crypted_msg, key - -def decrypt(msg: str, key: int): - - # get the size of the message - try: - msg = base64.b64decode(msg.encode("utf-8")).decode("utf-8") - size = ord(msg[0]) - except: - return "", key, 0 - - # verify the size of the message - if len(msg) - 2 != size: - return "", key, 0 - - # get the role of the sender - role = 0 - if msg[1] == 'Q': - role = Queen - elif msg[1] == 'W': - role = Workers - else: - role = Guard - - # get the decrypted message - decrypted_msg = ''.join(chr(ord(a) ^ key) for a in msg[2:]) - - # update the key - key = key + size if max_int - size > key else size - (max_int - key) - - return decrypted_msg, key, role diff --git a/ia/compile.py b/ia/compile.py deleted file mode 100755 index 59b5fe9..0000000 --- a/ia/compile.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python3 -import py_compile -import os -from stat import S_IEXEC - -file_path = py_compile.compile("ia/zappy_ai.py", "zappy_ai", "../") -os.chmod(file_path, S_IEXEC | os.stat(file_path).st_mode) \ No newline at end of file diff --git a/ia/network/network.py b/ia/network/network.py deleted file mode 100644 index dc2bd2e..0000000 --- a/ia/network/network.py +++ /dev/null @@ -1,244 +0,0 @@ -import logging -import asyncio - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - -# Queue function that allows reading from stream to be nonblocking. -# It stops once it reaches EOF. -# It also shuts down the queue to notify processes that depend on it. -async def readStream(reader: asyncio.StreamReader, queue: asyncio.Queue): - logger.info("Reader started.") - while True: - # wait for data - data = await reader.read(4096) - logger.debug("Recieved data : <%s>", data) - # if eof is reached - if not data: - break - # push read data to the queue - await queue.put(data.decode("utf-8")) - - logger.info("Reader stopped.") - - # Once loop is broken, shut down the queue - queue.shutdown(immediate=True) - -# Writer function that takes input from the queue and writes it to the stream. -# If it recieves empty input, it shuts down the queue and stops. -async def writeStream(writer: asyncio.StreamWriter, queue: asyncio.Queue): - logger.info("Writer started.") - while True: - # Wait until data is available in the queue - data: str = await queue.get() - - # If data is empty (simulate eof), shut down the queue and stop - if not data: - break - - # Write data to stream - writer.write(data.encode("utf-8")) - - # Wait for the buffer to flush (confirm that the write was succesful) - await writer.drain() - - logger.debug("Sent data : <%s>", data) - - logger.info("Writer stopped.") - - # Once loop is broken, shut down the queue - queue.shutdown(immediate=True) - -class network: - def __init__(self, port: int, machine: str, tg: asyncio.TaskGroup): - self.machine = machine - self.port = port - self.reader = None - self.writer = None - self.iQueue = asyncio.Queue() - self.oQueue = asyncio.Queue() - self.taskGroup = tg - self.readerTask = None - self.writerTask = None - self.up = False - - async def connect(self): - # Open connection and create stream objects. Can except in case connection fails. - self.reader, self.writer = await asyncio.open_connection(self.machine, self.port) - - logger.info("Connection opened.") - - # Set connection indicator - self.up = True - - # Instantiate reader task - self.readerTask = asyncio.create_task( - readStream( - self.reader, - self.iQueue - ) - ) - - # Instantiate writer task - self.writerTask = asyncio.create_task( - writeStream( - self.writer, - self.oQueue - ) - ) - - async def disconnect(self): - # Close connection - self.writer.close() - - logger.info("Connection closed.") - - # Cancel reader and writer task if they are still running - for task in (self.readerTask, self.writerTask): - task.cancel() - # This must be wrapped in a try block as it raises an exception if it cancelled (even if is its intended behavior) - try: - await task - except asyncio.CancelledError: - logger.info(task.get_name()," stopped.") - pass - - # Shutdown both input and output queues - self.iQueue.shutdown(immediate=True) - self.oQueue.shutdown(immediate=True) - - # Set connection indicator to false - self.up = False - - def sendNoWait(self, msg: str): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot send : connection is not open.") - return - try: - # Add element to queue - self.oQueue.put_nowait(msg) - - # This should not happen as queues are initialised without a limit. - except asyncio.QueueFull: - logger.error("Cannot send : queue is full.") - pass - # In case of something happening on the writer task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot send : connection is shutdown.") - self.up = False - - async def send(self, msg: str): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot send : connection is not open.") - return - try: - # Add element to queue - await self.oQueue.put(msg) - - # In case of something happening on the writer task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot send : connection is shutdown.") - self.up = False - - async def sendTimeout(self, msg:str, timeout: float): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot send : connection is not open.") - return - - # Try to read from queue - try: - # Add element to queue with set timeout - return await asyncio.wait_for(self.oQueue.put(msg), timeout=timeout) - - # If no room is available for a new element, nothing is done. - except asyncio.TimeoutError: - logger.error("Cannot send : timed out.") - pass - # In case of something happening on the writer task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot send : connection is shutdown.") - self.up = False - - def readNoWait(self): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot read : connection is not open.") - return - - # Try to read from queue - try: - # Try to get one element from queue - return self.iQueue.get_nowait() - - # In case no elements are present in the queue. - # It is done that way to ensure that if the stream is empty, the QueueShutdown state can be detected. - except asyncio.QueueEmpty: - logger.error("Cannot read : queue is empty.") - pass - - # In case of something happening on the reader task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot read : connection is shutdown.") - self.up = False - - # If no elements are present in the queue (or queue is shutdown), fallback to empty str - return "" - - async def read(self): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot read : connection is not open.") - return - - # Try to read from queue - try: - # Try to get one element from queue - return await self.iQueue.get() - - # In case of something happening on the reader task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot read : connection is shutdown.") - self.up = False - - async def readTimeout(self, timeout: float): - # Do nothing in case connection is closed - if not self.up: - logger.error("Cannot read : connection is not open.") - return - - # Try to read from queue - try: - # Try to get one element from queue with set timeout - return await asyncio.wait_for(self.iQueue.get(), timeout=timeout) - - # If no element comes up in the queue during the timeout, the function returns an empty element. - except asyncio.TimeoutError: - logger.error("Cannot read : timed out.") - return "" - # In case of something happening on the reader task, queue is shut down. - # This sets the connection indicator to false, and will prevent further I/O operations. - except asyncio.QueueShutDown: - logger.error("Cannot read : connection is shutdown.") - self.up = False - -async def connect(port: int, machine: str, tg: asyncio.TaskGroup): - # Instantiate object - connection = network(port, machine, tg) - # Wrap the connection in a try block as it can except - try: - # Wait for connection - await connection.connect() - except Exception: - # In case anything happens, the client cannot be launched (dependant on connection), and it raises an error - raise(Exception("Connection to the server failed.")) - - # Return connection object after it is initialised - return connection \ No newline at end of file diff --git a/ia/player/player.py b/ia/player/player.py deleted file mode 100644 index 0ff393e..0000000 --- a/ia/player/player.py +++ /dev/null @@ -1,50 +0,0 @@ -from network.network import * - -class Player: - def __init__(self, connection: network): - self.connection = connection - - async def Forward(self): - await self.connection.send("Forward\n") - - async def Right(self): - await self.connection.send("Right\n") - - async def Left(self): - await self.connection.send("Left\n") - - async def Look(self): - await self.connection.send("Look\n") - - async def Inventory(self): - await self.connection.send("Inventory\n") - - async def Broadcast(self, msg: bytes): - await self.connection.send("Broadcast " + str(msg) + "\n") - - async def Connect_nbr(self): - await self.connection.send("Connect_nbr\n") - - async def Fork(self): - await self.connection.send("Fork\n") - - async def Eject(self): - await self.connection.send("Eject\n") - - async def Take(self, obj: str): - await self.connection.send("Take " + obj + "\n") - - async def Set(self, obj: str): - await self.connection.send("Set " + obj + "\n") - - async def Incantation(self): - await self.connection.send("Incantation\n") - - async def send(self, msg: str): - await self.connection.send(msg) - - async def read(self): - return await self.connection.read() - - def readNoWait(self): - return self.connection.readNoWait() \ No newline at end of file diff --git a/ia/simple_ai/ai.py b/ia/simple_ai/ai.py deleted file mode 100644 index 71efd11..0000000 --- a/ia/simple_ai/ai.py +++ /dev/null @@ -1,139 +0,0 @@ -from network.network import * -import sys - -max_int = sys.maxsize - -class deathExeption(BaseException): - def __init__(self, args, kwargs): - self.args = args - self.kwargs = kwargs - -class ia: - def __init__(self, connection: network): - self.connection = connection - self.tick = 0 - self.overview = "" - self.inv = "" - self.broadcast = [] - self.all = "" - self.alive = True - - def up_tick(self, val: int): - self.tick = self.tick + val if max_int - self.tick > val else val - max_int - self.tick - - async def Look(self): - await self.connection.send("Look\n") - self.up_tick(7) - server_response = await self.connection.read() - splited = server_response.split("\n") - self.overview = "" - while self.overview == "": - for i in range(len(splited)): - if splited[i] == "dead": - raise(deathExeption("You're dead")) - if splited[i][:1] == '[': - self.overview = splited[i] - else: - self.all += splited[i] + "\n" - if self.overview != "": - break - server_response = await self.connection.read() - splited = server_response.split("\n") - - async def Inv(self): - await self.connection.send("Inventory\n") - self.up_tick(7) - server_response = await self.connection.read() - splited = server_response.split("\n") - self.inv = "" - while self.inv == "": - for i in range(len(splited)): - if splited[i] == "dead": - raise(deathExeption("You're dead")) - if splited[i][:1] == "[": - self.inv = splited[i] - else: - self.all += splited[i] + "\n" - if self.inv != "": - break - server_response = await self.connection.read() - splited = server_response.split("\n") - - async def Broadcast(self): - self.all += await self.connection.read() - self.broadcast += self.all.split("\n") - self.all = "" - - async def get_info(self): - await self.Inv() - await self.Look() - - async def take_decision(self): - cell_content = self.overview.split(",") - if "food" in cell_content[0]: - await self.connection.send("Take food\n") - if "food" in cell_content[2]: - await self.connection.send("Forward\n") - await self.connection.send("Take food\n") - if "food" in cell_content[1]: - await self.connection.send("Left\n") - await self.connection.send("Forward\n") - await self.connection.send("Take food\n") - elif "food" in cell_content[3]: - await self.connection.send("Right\n") - await self.connection.send("Forward\n") - await self.connection.send("Take food\n") - elif "food" in cell_content[1]: - await self.connection.send("Forward\n") - await self.connection.send("Left\n") - await self.connection.send("Forward\n") - await self.connection.send("Take food\n") - elif "food" in cell_content[3]: - await self.connection.send("Forward\n") - await self.connection.send("Right\n") - await self.connection.send("Forward\n") - await self.connection.send("Take food\n") - else: - await self.connection.send("Forward\n") - server_response = await self.connection.read() - splited = server_response.split("\n") - for i in range(len(splited)): - if splited[i] != "ok": - self.all += splited[i] + "\n" - - async def is_alive(self): - for msg in self.broadcast: - if msg == "dead": - self.alive = False - break - splited = self.all.split("\n") - for msg in splited: - if msg == "dead": - self.alive = False - break - return self.alive - - async def landing(self): - message = await self.connection.read() - if "WELCOME" not in message: - raise Exception("No welcome message") - await self.connection.send(self.connection.team + "\n") - message = await self.connection.read() - if "ko" in message: - raise Exception("Couldn't join") - await self.connection.send("Fork\n") - -async def run_ia(connection: network): - my_ia = ia(connection) - try: - await my_ia.landing() - while await my_ia.is_alive(): - await my_ia.get_info() - await my_ia.take_decision() - except deathExeption as e: - pass - except Exception as e: - print(e) - finally: - await connection.disconnect() - return \ No newline at end of file diff --git a/ia/tests.py b/ia/tests.py deleted file mode 100755 index d929a2d..0000000 --- a/ia/tests.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -import unittest -from unittest.mock import AsyncMock, Mock, patch -from communication.communication import * -import network.network as net -import asyncio - -class TestCommunication(unittest.TestCase): - - def test_crypt(self): - crypted_msg, key = crypt(Queen, "hello world", 1) - self.assertEqual(crypted_msg, "C1FpZG1tbiF2bnNtZQ==") - self.assertEqual(key, 12) - - def test_decrypt(self): - decrypted_msg, key, role = decrypt("DVFpZG1tbiF2bnNtZSFg", 1) - self.assertEqual(decrypted_msg, "hello world a") - self.assertEqual(key, 14) - self.assertEqual(role, Queen) - decrypted_msg, key, role = decrypt("C1dpZG1tbiF2bnNtZQ==", 1) - self.assertEqual(role, Workers) - decrypted_msg, key, role = decrypt("C0dpZG1tbiF2bnNtZQ==", 1) - self.assertEqual(role, Guard) - - def test_sensible_case(self): - crypted_msg, key = crypt(Queen, "hello world", 27) - self.assertEqual(crypted_msg, "C1Fzfnd3dDtsdGl3fw==") - self.assertEqual(key, 38) - decrypted_msg, key, role = decrypt(crypted_msg, 27) - self.assertEqual(decrypted_msg, "hello world") - self.assertEqual(key, 38) - self.assertEqual(role, Queen) - -class TestNetwork(unittest.IsolatedAsyncioTestCase): - - async def test_send_puts_message_in_queue(self): - conn = net.network(8000, "localhost", None) - conn.up = True - - await conn.send("hello") - - msg = await conn.oQueue.get() - - self.assertEqual(msg, "hello") - - async def test_read_returns_message(self): - conn = net.network(8000, "localhost", None) - conn.up = True - - await conn.iQueue.put("hello") - - result = await conn.read() - - self.assertEqual(result, "hello") - - @patch("network.network.asyncio.open_connection") - async def test_connect(self, mock_open_connection): - reader = AsyncMock() - reader.read.side_effect = [b""] - - writer = Mock() - writer.drain = AsyncMock() - - mock_open_connection.return_value = (reader, writer) - - conn = net.network(8000, "localhost", None) - conn.iQueue.shutdown = Mock() - - await conn.connect() - - self.assertTrue(conn.up) - self.assertIs(conn.reader, reader) - self.assertIs(conn.writer, writer) - - @patch("network.network.asyncio.open_connection") - async def test_connect_failure(self, mock_open_connection): - mock_open_connection.side_effect = OSError() - - conn = net.network(8000, "localhost", None) - - with self.assertRaises(OSError): - await conn.connect() - - async def test_reads_until_eof(self): - reader = AsyncMock() - - reader.read.side_effect = [ - b"hello", - b"world", - b"" - ] - - queue = asyncio.Queue() - - queue.shutdown = Mock() - - await net.readStream(reader, queue) - - self.assertEqual(await queue.get(), "hello") - self.assertEqual(await queue.get(), "world") - queue.shutdown.assert_called_once_with(immediate=True) - - async def test_writes_data(self): - writer = Mock() - writer.drain = AsyncMock() - - queue = asyncio.Queue() - queue.shutdown = Mock() - - await queue.put("hello") - await queue.put("") # stop signal - - await net.writeStream(writer, queue) - - writer.write.assert_called_once_with( - b"hello" - ) - - writer.drain.assert_awaited_once() - queue.shutdown.assert_called_once_with(immediate=True) - - async def test_disconnect(self): - conn = net.network(8000, "localhost", None) - - conn.up = True - - conn.writer = Mock() - - conn.readerTask = asyncio.create_task( - asyncio.sleep(100) - ) - - conn.writerTask = asyncio.create_task( - asyncio.sleep(100) - ) - - conn.iQueue.shutdown = Mock() - conn.oQueue.shutdown = Mock() - - await conn.disconnect() - - conn.writer.close.assert_called_once() - - self.assertFalse(conn.up) - self.assertTrue(conn.readerTask.cancelled()) - self.assertTrue(conn.writerTask.cancelled()) - conn.iQueue.shutdown.assert_called_once_with(immediate=True) - conn.oQueue.shutdown.assert_called_once_with(immediate=True) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/ia/zappy_ai.py b/ia/zappy_ai.py deleted file mode 100755 index f41c9cd..0000000 --- a/ia/zappy_ai.py +++ /dev/null @@ -1,62 +0,0 @@ -import sys -from network.network import * -from anthill.anthill import * -arg = sys.argv[1:] - -def help(): - print("USAGE: ./zappy_ai -p port -n name -h machine") - print("-p port:\t\tport number") - print("-n name:\t\tname of the team") - print("-h machine:\t\tname of the machine; localhost by default") - -def getArgument(arg): - port = -1 - name = "" - machine = "localhost" - machineChanged = False - for i in range(0, len(arg), 2): - if arg[i] == "-p": - if port >= 0: - raise(Exception("Multiple definitions of port")) - port = int(arg[i + 1]) - elif arg[i] == "-n": - if name != "": - raise(Exception("Multiple definitions of team name")) - name = arg[i + 1] - elif arg[i] == "-h": - if machineChanged: - raise(Exception("Multiple definitions of machine name")) - machine = arg[i + 1] - machineChanged = True - else: - raise(Exception(f"Unknown flag: {arg[i]}")) - if port < 0: - raise(Exception("Missing port")) - if name == "": - raise(Exception("Missing team name")) - return port, name, machine - -async def main(): - if len(arg) < 4: - help() - try: - if arg[0] == "-h" or arg[0] == "--help": - return 0 - except: - return 84 - port = 0 - name = "" - machine = "localhost" - try: - port, name, machine = getArgument(arg) - async with asyncio.TaskGroup() as tg: - tg.create_task(runIa(port, name, machine, tg)) - except IndexError: - print("Incomplete argument") - except Exception as e: - print(e) - return 84 - return 0 - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/server/src/Game/GameCommands.cpp b/server/src/Game/GameCommands.cpp index 241eca1..a3bb0e6 100644 --- a/server/src/Game/GameCommands.cpp +++ b/server/src/Game/GameCommands.cpp @@ -4,6 +4,7 @@ #include "Game/Tile.hpp" #include "GameLogic.hpp" #include "Server.hpp" +#include "Logger.hpp" void game::GameLogic::broadcastPpo(Player &player) { player.getClient()->getServer().get().broadcastToGui( @@ -47,7 +48,7 @@ void game::GameLogic::playerInventory(Player &player) { void game::GameLogic::playerBroadcast(Player &player, const std::string &text) { for (const auto &team : _teams) { for (const auto &other : team->getPlayers()) { - int dir = getDir(player, *other, _mapX, _mapY); + int dir = getDir(*other, player, _mapX, _mapY); other->getClient()->sendMessage("message " + std::to_string(dir) + ", " + text + "\n"); } @@ -350,6 +351,7 @@ void game::GameLogic::playerIncantationEnd(Player &player) { "Current level: " + std::to_string(el->getLevel()) + "\n"); server.broadcastToGui("plv #" + std::to_string(el->getId()) + " " + std::to_string(el->getLevel()) + "\n"); + LOG_INFO(std::format("Player [{}] leveled up to {}", el->getId(), el->getLevel())); } server.broadcastToGui("pie " + std::to_string(x) + " " + std::to_string(y) + diff --git a/server/src/Game/GameLogic.cpp b/server/src/Game/GameLogic.cpp index 038f816..2c93b1b 100644 --- a/server/src/Game/GameLogic.cpp +++ b/server/src/Game/GameLogic.cpp @@ -159,6 +159,7 @@ bool game::GameLogic::checkWinCond() { void game::GameLogic::newPlayer(Client &client, const std::string &teamname) { if (teamname == GUI_TEAM) { client.setType(ClientType::GUI); + sendGuiWelcome(client); return; } @@ -215,6 +216,44 @@ void game::GameLogic::newPlayer(Client &client, const std::string &teamname) { "Cannot create player, the team [{}] doesn't exist", teamname)); } +void game::GameLogic::sendGuiWelcome(Client &client) { + client.sendMessage("msz " + std::to_string(_mapX) + " " + + std::to_string(_mapY) + "\n"); + client.sendMessage("sgt " + std::to_string(_freq) + "\n"); + + for (int y = 0; y < _mapY; y++) + for (int x = 0; x < _mapX; x++) + client.sendMessage(formatBct(x, y)); + + for (const auto &team : _teams) + client.sendMessage("tna " + team->getName() + "\n"); + + for (const auto &team : _teams) { + for (const auto &player : team->getPlayers()) { + client.sendMessage("pnw #" + std::to_string(player->getId()) + " " + + std::to_string(player->getX()) + " " + + std::to_string(player->getY()) + " " + + std::to_string(player->getOrientation()) + " " + + std::to_string(player->getLevel()) + " " + + team->getName() + "\n"); + client.sendMessage("plv #" + std::to_string(player->getId()) + " " + + std::to_string(player->getLevel()) + "\n"); + std::string pinMsg = "pin #" + std::to_string(player->getId()) + " " + + std::to_string(player->getX()) + " " + + std::to_string(player->getY()); + for (int i = 0; i < RESOURCE_COUNT; i++) + pinMsg += " " + std::to_string(player->getRessource(i)); + pinMsg += "\n"; + client.sendMessage(pinMsg); + } + for (const auto &egg : team->getEggs()) + client.sendMessage("enw #" + std::to_string(egg->getId()) + " #" + + std::to_string(egg->getPlayerId()) + " " + + std::to_string(egg->getX()) + " " + + std::to_string(egg->getY()) + "\n"); + } +} + //--------------------Utils functions----------------------- void game::GameLogic::Debug() { return; } diff --git a/server/src/Game/GameLogic.hpp b/server/src/Game/GameLogic.hpp index 9f1b8d8..5eb61c6 100644 --- a/server/src/Game/GameLogic.hpp +++ b/server/src/Game/GameLogic.hpp @@ -65,6 +65,7 @@ class GameLogic { // utils void newPlayer(Client &client, const std::string &teamname); + void sendGuiWelcome(Client &client); int getDir(game::Player &player, game::Player &other, int width, int heigth); int getIndexByName(std::string &toTake); std::shared_ptr getPlayerById(int id) const; diff --git a/server/src/Game/Player.hpp b/server/src/Game/Player.hpp index 2d954e7..0fab1ba 100644 --- a/server/src/Game/Player.hpp +++ b/server/src/Game/Player.hpp @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include "Common.hpp" diff --git a/server/src/Game/Team.hpp b/server/src/Game/Team.hpp index d2a3a68..0690a6d 100644 --- a/server/src/Game/Team.hpp +++ b/server/src/Game/Team.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include #include -#include #include "Egg.hpp" #include "Player.hpp"