diff --git a/board_widget.cpp b/board_widget.cpp deleted file mode 100644 index 64631d8..0000000 --- a/board_widget.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include -#include - -#include -#include - -#include -#include -#include - -#include "board_widget.hpp" -#include "game.hpp" - -using namespace bwidgets; -using namespace game; -using namespace game::data; - -ui::BoardWidget::BoardWidget(Widget* p) : Widget(p) -{ - _click_area = &_widget_area; - click_handler = [this](const SDL_MouseButtonEvent& p) -> void { - if (board != nullptr) { - auto* coord = new SDL_Point(this->board_coord_from_input({p.x, p.y})); - SDL_Event play_ev; - SDL_zero(play_ev); - play_ev.type = this->event_type; - play_ev.user.code = (Sint32)BoardWidget::event_code::PLAY; - play_ev.user.data1 = this; - play_ev.user.data2 = coord; - SDL_PushEvent(&play_ev); - } - }; -} - -ui::BoardWidget::~BoardWidget() -{ - discard(_texture_p1, _texture_p2); -} - -auto ui::BoardWidget::board_coord_from_input(const SDL_Point& p) const -> SDL_Point -{ - if (board == nullptr) return {-1, -1}; - - return {(p.x - _viewport.x - _widget_area.x) / (_widget_area.w / board->width), - (p.y - _viewport.y - _widget_area.y) / (_widget_area.h / board->height)}; -} - -auto ui::BoardWidget::size() const noexcept -> Size -{ - Size min_board_size; - // NOLINTNEXTLINE(readability-magic-numbers) - if (board != nullptr) min_board_size = {board->width * 64, board->height * 64}; - // NOLINTNEXTLINE(readability-magic-numbers) - else min_board_size = {64, 64}; - - auto shortest {min_board_size.w < min_board_size.h ? min_board_size.w - : min_board_size.h}; - int border {shortest / _border_ratio}; - - return {min_board_size.w + 2 * border, min_board_size.h + 2 * border}; -} - -void ui::BoardWidget::_draw_atoms(const SDL_Rect& area, int atoms, Texture* texture) -{ - const auto radius = area.w < area.h ? area.w / 8 : area.h / 8; - const auto diameter = radius * 2; - - const SDL_Point center {area.w / 2 - radius, area.h / 2 - radius}; - const SDL_Point diagonal_radius { - (int)std::sqrt(radius * radius / 2), - (int)std::sqrt(radius * radius / 2), - }; - const int disabled = -1; - // clang-format off - std::array render_origins; - render_origins[0] = { - atoms == 1 || atoms == 3 - ? center.x - : atoms == 2 || atoms == 4 - ? center.x + diagonal_radius.x - : disabled, - atoms == 1 || atoms == 3 - ? center.y - : atoms == 2 || atoms == 4 - ? center.y + diagonal_radius.y - : disabled - }; - render_origins[1] = { - atoms >= 2 && atoms <= 4 - ? center.x - diagonal_radius.x - : disabled, - atoms >= 2 && atoms <= 4 - ? center.y - diagonal_radius.y - : disabled, - }; - render_origins[2] = { - atoms == 3 - ? center.x + diagonal_radius.x - : atoms == 4 - ? center.x + diagonal_radius.x - : disabled, - atoms == 3 - ? center.y + diagonal_radius.y - : atoms == 4 - ? center.y - diagonal_radius.y - : disabled - }; - render_origins[3] = { - atoms == 4 - ? center.x - diagonal_radius.x - : disabled, - atoms == 4 - ? center.y + diagonal_radius.y - : disabled - }; - // clang-format on - for (auto o : render_origins) { - const int size {o.x == disabled ? 0 : diameter}; - _renderer->copy(texture, nullptr, {area.x + o.x, area.y + o.y, size, size}); - } -} - -void ui::BoardWidget::_handle_geometry_change(const SDL_Rect& vp) noexcept -{ - _widget_area = {0, 0, vp.w, vp.h}; - auto smallest_size {_widget_area.w < _widget_area.h ? _widget_area.w - : _widget_area.h}; - _border_area = {_widget_area.x, _widget_area.y, smallest_size / _border_ratio, - smallest_size / _border_ratio}; - _widget_area = {_border_area.x + _border_area.w, _border_area.y + _border_area.w, - _widget_area.w - 2 * (_border_area.w), - _widget_area.h - 2 * (_border_area.w)}; - - discard(_texture_p1, _texture_p2); -} - -void ui::BoardWidget::_handle_renderer_change(Renderer* /*unused*/) -{ - discard(_texture_p1, _texture_p2); -} - -void ui::BoardWidget::_handle_rendering() -{ - if ((_texture_p1 == nullptr) || (_texture_p2 == nullptr)) _handle_texture_update(); - _renderer->draw_color(color_bg)->fill_rect(_widget_area); - - _render_empty_board(); - _render_square_content(); -} - -void ui::BoardWidget::_handle_texture_update() -{ - SDL_assert_release((_texture_p1 == nullptr) && (_texture_p2 == nullptr)); // NOLINT - - if (board == nullptr) return; - - auto circle_size = _widget_area.w > _widget_area.h - ? _widget_area.h / board->height / 4 - : _widget_area.w / board->width / 4; - - // NOLINTNEXTLINE(readability-magic-numbers) - auto aa = circle_size > 20 ? 3 : circle_size > 10 ? 2 : circle_size > 4 ? 1 : 0; - // NOLINTNEXTLINE(readability-magic-numbers) - _texture_p1 = filled_circle({75, 125, 255, 255}, circle_size, _renderer, aa); - // NOLINTNEXTLINE(readability-magic-numbers) - _texture_p2 = filled_circle({255, 100, 100, 255}, circle_size, _renderer, aa); -} - -void ui::BoardWidget::_render_empty_board() -{ - _renderer->draw_color(color_outline); - - if (board != nullptr) { - for (int col = 0; col <= board->width; col++) { - int x = col * (_widget_area.w - 1) / board->width; - _renderer->draw_line({_widget_area.x + x, _widget_area.y}, - {_widget_area.x + x, _widget_area.y + _widget_area.h}); - } - - for (int row = 0; row <= board->height; row++) { - int y = row * (_widget_area.h - 1) / board->height; - _renderer->draw_line({_widget_area.x, _widget_area.y + y}, - {_widget_area.x + _widget_area.w, _widget_area.y + y}); - } - } - - for (int xy = 0; xy < _border_area.w; xy++) { - const float color_factor = 1 - (float)xy / (float)_border_area.w / 3; - _renderer - ->draw_color({(uint8_t)((float)color_outline.r * color_factor), - (uint8_t)((float)color_outline.g * color_factor), - (uint8_t)((float)color_outline.b * color_factor), - color_outline.a}) - ->draw_rect({_border_area.x + xy, _border_area.y + xy, - _widget_area.w + 2 * (_widget_area.x - _border_area.x - xy) + 1, - _widget_area.h + 2 * (_widget_area.y - _border_area.y - xy) + 1}); - } -} - -void ui::BoardWidget::_render_square_content() -{ - if (board == nullptr) return; - - for (int y = 0; y < board->height; y++) { - for (int x = 0; x < board->width; x++) { - const SDL_Rect sqr_area { - _widget_area.x + x * _widget_area.w / board->width + 1, - _widget_area.y + y * _widget_area.h / board->height + 1, - _widget_area.w / board->width - 2, _widget_area.h / board->height - 2}; - - const auto& sqr = Board::square(*board, {x, y}); - const auto capacity = logic::square_capacity(*board, {x, y}); - if (sqr.value > capacity) { - _renderer->draw_color(color_hl1)->fill_rect(sqr_area); - } - else if (sqr.value == capacity) { - _renderer->draw_color(color_hl2)->fill_rect(sqr_area); - } - _draw_atoms(sqr_area, sqr.value, - sqr.owner == game::data::Player::P1 ? _texture_p1 : _texture_p2); - } - } -} diff --git a/board_widget.hpp b/board_widget.hpp deleted file mode 100644 index f382c47..0000000 --- a/board_widget.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef BOARD_WIDGET_HPP -#define BOARD_WIDGET_HPP - -#include -#include - -#include -#include -#include - -extern "C" { - auto SDL_RegisterEvents(int) -> Uint32; -} - -namespace game::data -{ - struct Board; -} - -namespace game::ui -{ - class BoardWidget : public bwidgets::Widget, - public bwidgets::MouseHandler, - public bwidgets::TextureHandler - - { - protected: - static const int _border_ratio {30}; - - SDL_Rect _border_area; - bwidgets::Texture* _texture_p1 {nullptr}; - bwidgets::Texture* _texture_p2 {nullptr}; - - virtual void _draw_atoms(const SDL_Rect&, int, bwidgets::Texture*); - virtual void _render_empty_board(); - virtual void _render_square_content(); - - void _handle_focus_change(bool) override {} - void _handle_geometry_change(const SDL_Rect&) noexcept override; - void _handle_renderer_change(bwidgets::Renderer*) override; - void _handle_rendering() override; - void _handle_texture_update() override; - - public: - enum struct event_code : Sint32 - { - PLAY - }; - const Uint32 event_type {SDL_RegisterEvents(1)}; - - const data::Board* board {nullptr}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_bg {50, 50, 50, SDL_ALPHA_OPAQUE}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_hl1 {255, 0, 0, SDL_ALPHA_OPAQUE / 16}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_hl2 {255, 255, 0, SDL_ALPHA_OPAQUE / 16}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_outline {175, 175, 175, SDL_ALPHA_OPAQUE}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_p1 {255, 0, 0, SDL_ALPHA_OPAQUE}; - // NOLINTNEXTLINE(readability-magic-numbers) - SDL_Color color_p2 {0, 0, 255, SDL_ALPHA_OPAQUE}; - - BoardWidget(Widget* parent = nullptr); - ~BoardWidget() override; - - [[nodiscard]] auto board_coord_from_input(const SDL_Point&) const -> SDL_Point; - [[nodiscard]] auto size() const noexcept -> bwidgets::Size override; - }; -} -#endif diff --git a/data.cpp b/data.cpp deleted file mode 100644 index cab8562..0000000 --- a/data.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -#include "data.hpp" - -using namespace game::data; - -Board::Board(int w, int h) : width(w), height(h) -{ - squares.resize((std::vector::size_type)w * h, {}); -} diff --git a/data.hpp b/data.hpp deleted file mode 100644 index 581633b..0000000 --- a/data.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef DATA_HPP -#define DATA_HPP - -#include - -namespace game::data -{ - enum struct Player - { - NONE, - P1, - P2 - }; - - struct Square - { - Player owner = Player::NONE; - int value = 0; - - static inline void reset(Square& s) - { - s.owner = Player::NONE; - s.value = 0; - } - }; - - struct Coordinate - { - int x; - int y; - }; - - struct Board - { - std::vector squares; - int width; - int height; - - Board(int w, int h); - - [[nodiscard]] inline auto is_corner(const Coordinate& c) const -> bool - { - return (c.x == 0 && c.y == 0) || (c.x == 0 && c.y == height - 1) - || (c.x == width - 1 && c.y == 0) - || (c.x == width - 1 && c.y == height - 1); - } - [[nodiscard]] inline auto is_side(const Coordinate& c) const -> bool - { - return c.x == 0 || c.x == width - 1 || c.y == 0 || c.y == height - 1; - } - static inline auto square(const Board& b, const Coordinate& c) -> const Square& - { - return square(const_cast(b), c); - } - static inline auto square(Board& b, const Coordinate c) -> Square& - { - return b.squares.at(c.y * b.width + c.x); - } - }; - - using Boards = std::vector; -} - -#endif diff --git a/fading_caption.cpp b/fading_caption.cpp deleted file mode 100644 index 8103d60..0000000 --- a/fading_caption.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include "fading_caption.hpp" - -using namespace game; - -auto ui::FadingCaption::show() -> ui::FadingCaption* -{ - _start_time = SDL_GetTicks(); - _shown = true; - - return this; -} - -void ui::FadingCaption::_handle_rendering() -{ - if (!_shown) return; - if (_text_texture == nullptr) _handle_texture_update(); - - if (SDL_GetTicks() - _start_time < duration) { - uint8_t alpha = SDL_ALPHA_OPAQUE * visibility(); - _text_texture->blend_mode(SDL_BLENDMODE_BLEND); - _text_texture->alpha_mode(alpha); - Caption::_handle_rendering(); - } - else _shown = false; -} diff --git a/fading_caption.hpp b/fading_caption.hpp deleted file mode 100644 index e0bf8a8..0000000 --- a/fading_caption.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef FADING_CAPTION_HPP -#define FADING_CAPTION_HPP - -#include - -#include - -namespace game::ui -{ - class FadingCaption final : public bwidgets::Caption - { - bool _shown {}; - uint32_t _start_time; - void _handle_rendering() override; - - public: - uint32_t duration = 2500U; // NOLINT(readability-magic-numbers) - - auto show() -> FadingCaption*; - - [[nodiscard]] inline auto visibility() const noexcept -> float - { - if (!_shown) return 0; - auto progress = (float)std::clamp(SDL_GetTicks() - _start_time, 0U, duration) - / (float)duration; - return 1 - progress * progress * progress * progress; - } - }; -} - -#endif diff --git a/game.cpp b/game.cpp deleted file mode 100644 index 0f82eb4..0000000 --- a/game.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "game.hpp" - -using namespace game; -using namespace game::data; - -auto game::logic::play(const Board& b, const Coordinate& c, const Player p) -> Boards -{ - auto new_state(b); - auto& square = Board::square(new_state, c); - Boards states; - - if (square.owner != Player::NONE && p != square.owner) throw error::invalid_move(); - - square.owner = p; - square.value++; - states = spread(new_state); - - return states; -} - -auto game::logic::spread(const data::Board& b) noexcept -> Boards -{ - Boards states; - Board current = b; - int overflows; - do { - overflows = 0; - auto new_state = current; - for (auto y = 0; y < current.height; y++) { - for (auto x = 0; x < current.width; x++) { - auto sqr = Board::square(current, {x, y}); - if (sqr.value > square_capacity(current, {x, y})) { - overflows++; - Square::reset(Board::square(new_state, {x, y})); - states.push_back(new_state); - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - for (data::Coordinate neighboors[4] { - { x, y + 1}, - {x + 1, y}, - { x, y - 1}, - {x - 1, y} - }; - const data::Coordinate& c : neighboors) - { - if (c.x >= 0 && c.y >= 0 && c.x < current.width - && c.y < current.height) { - auto& adj_sqr = Board::square(new_state, c); - adj_sqr.value++; - adj_sqr.owner = sqr.owner; - } - } - } - } - } - states.push_back(new_state); - current = new_state; - } while (overflows > 0 && winner(current) == Player::NONE); - - return states; -} -auto game::logic::winner(const Board& b) noexcept -> Player -{ - auto winner {Player::NONE}; - - for (const auto& s : b.squares) { - if (winner == Player::NONE) { - if (s.owner != Player::NONE) { - winner = s.owner; - } - } - else if (s.owner != Player::NONE && winner != s.owner) { - return Player::NONE; - } - } - - return winner; -} diff --git a/game.hpp b/game.hpp deleted file mode 100644 index 9fedc49..0000000 --- a/game.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef GAME_HPP -#define GAME_HPP - -#include - -#include "data.hpp" - -namespace game::error -{ - struct invalid_move : std::exception - {}; -} - -namespace game::logic -{ - auto play(const data::Board&, const data::Coordinate&, data::Player) -> data::Boards; - auto spread(const data::Board&) noexcept -> data::Boards; - auto winner(const data::Board&) noexcept -> data::Player; - - static inline auto square_capacity(const data::Board& b, - const data::Coordinate& c) noexcept -> int - { - if (b.is_corner(c)) return 1; - if (b.is_side(c)) return 2; - return 3; - } -} - -#endif diff --git a/game_screen.cpp b/game_screen.cpp deleted file mode 100644 index b80ecd4..0000000 --- a/game_screen.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include - -#include - -#include "game_screen.hpp" - -using namespace bwidgets; -using namespace game; -using namespace game::data; - -// TODO: Why don't I use a ptr to current caption? -static inline auto cur_caption(ui::GameScreen* scr) -> auto& -{ - switch (scr->cur_player) { - case Player::P1: - return scr->caption_p1; - case Player::P2: - return scr->caption_p2; - default: - return scr->caption_none; - } -} - -ui::GameScreen::GameScreen() - : _menu(this), board(this), caption_none(this), caption_p1(this), caption_p2(this) -{ - caption_p1.text("Player 1")->font_color_bg(board.color_outline); - caption_p2.text("Player 2")->font_color_bg(board.color_outline); - caption_notification.render_mode(Font::RenderMode::BLENDED)->alignment = - Caption::Alignment::CENTER; -} - -ui::GameScreen::~GameScreen() -{ - discard(_font_big, _font_medium); -} - -auto ui::GameScreen::font(const std::string& fname) -> ui::GameScreen* -{ - _font_file = Font::find(fname); - return this; -} - -auto ui::GameScreen::handle_event(const SDL_Event& e) -> ui::GameScreen* -{ - if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_ESCAPE) - _menu.shown = !_menu.shown; - if (!_menu.shown) board.handle_event(e); - else _menu.handle_event(e); - - return this; -} - -auto ui::GameScreen::size() const noexcept -> Size -{ - return {board.size().w, - board.size().h + cur_caption(const_cast(this)).size().h}; -} - -void ui::GameScreen::_handle_geometry_change(const SDL_Rect& vp) noexcept -{ - SDL_assert_release(!_font_file.empty()); // NOLINT - _widget_area = {0, 0, vp.w, vp.h}; - - auto old_vp_smallest = _viewport.w < _viewport.h ? _viewport.w : _viewport.h; - auto new_vp_smallest = vp.w < vp.h ? vp.w : vp.h; - // NOLINTNEXTLINE(readability-magic-numbers) - if (old_vp_smallest / (12) != new_vp_smallest / (12)) - // NOLINTNEXTLINE(readability-magic-numbers) - _load_font(new_vp_smallest / 24); - - int heighest_caption = 0; - for (auto step = 0; step < 2; step++) { - for (auto* c : {&caption_none, &caption_p1, &caption_p2}) { - auto size = c->size(); - if (step == 0) { - if (size.h > heighest_caption) heighest_caption = size.h; - } - else { - c->viewport( - {vp.x + (vp.w - size.w) / 2, vp.y, size.w, heighest_caption}); - } - } - } - - board.viewport({vp.x, vp.y + heighest_caption, vp.w, vp.h - heighest_caption}); - - _menu.viewport(vp); - caption_notification.viewport( - {vp.x, vp.y + center_line(vp.h, caption_notification.size().h), vp.w, - caption_notification.size().h}); -} - -void ui::GameScreen::_handle_renderer_change(Renderer* r) -{ - for (auto* c : - {(Widget*)&board, (Widget*)&caption_none, (Widget*)&caption_p1, - (Widget*)&caption_p2, (Widget*)&caption_notification, (Widget*)&_menu}) - c->renderer(r); -} - -void ui::GameScreen::_handle_rendering() -{ - _renderer->draw_color(board.color_outline)->fill_rect(_widget_area); - - cur_caption(this).render(); - board.render(); - _renderer->viewport(nullptr); - auto notif_visibility = caption_notification.visibility(); - if (notif_visibility > 0) { - // NOLINTNEXTLINE(readability-magic-numbers) - _renderer - // NOLINTNEXTLINE(readability-magic-numbers) - ->draw_color({200, 200, 200, (uint8_t)(200 * notif_visibility)}) - // NOLINTNEXTLINE(readability-magic-numbers) - ->fill_rect({_viewport.x, caption_notification.viewport().y - _viewport.h / 16, - _viewport.w, - // NOLINTNEXTLINE(readability-magic-numbers) - caption_notification.viewport().h + _viewport.h / 8}); - caption_notification.render(); - } - _menu.render(); -} - -void ui::GameScreen::_load_font(int size) -{ - discard(_font_big, _font_medium); - _font_big = new Font(_font_file, size * 2); - _font_medium = new Font(_font_file, size); - for (auto* c : {&caption_none, &caption_p1, &caption_p2}) c->font(_font_medium); - _menu.font(_font_medium); - caption_notification.font(_font_big); -} diff --git a/game_screen.hpp b/game_screen.hpp deleted file mode 100644 index 1696ea1..0000000 --- a/game_screen.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef GAME_SCREEN_HPP -#define GAME_SCREEN_HPP - -#include "board_widget.hpp" -#include "data.hpp" -#include "fading_caption.hpp" -#include "menu.hpp" - -#include - -struct SDL_Renderer; - -namespace game::ui -{ - class GameScreen final : public bwidgets::Widget - { - bwidgets::Font* _font_big {nullptr}; - std::string _font_file; - bwidgets::Font* _font_medium {nullptr}; - Menu _menu; - - void _load_font(int); - - void _handle_geometry_change(const SDL_Rect&) noexcept override; - void _handle_renderer_change(bwidgets::Renderer*) override; - void _handle_rendering() override; - - public: - BoardWidget board; - bwidgets::Caption caption_none; - FadingCaption caption_notification; - bwidgets::Caption caption_p1; - bwidgets::Caption caption_p2; - data::Player cur_player {data::Player::P1}; - - GameScreen(); - ~GameScreen() override; - - auto font(const std::string&) -> GameScreen*; - - auto handle_event(const SDL_Event&) -> GameScreen* override; - [[nodiscard]] auto size() const noexcept -> bwidgets::Size override; - - [[nodiscard]] inline auto settings_event() const - { - return _menu.settings_event; - } - }; -} - -#endif diff --git a/main.cpp b/main.cpp deleted file mode 100644 index f9c7c80..0000000 --- a/main.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include - -#include - -#include - -#include "game.hpp" -#include "game_screen.hpp" -#include "settings.hpp" - -using namespace bwidgets; -using namespace game; - -auto main() -> int -{ - const int width = 854; - const int height = 480; - - SDL_Init(SDL_INIT_VIDEO); - SDL_Window* win = - SDL_CreateWindow("ChainReact", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); - auto* rend = new Renderer(win, -1, SDL_RENDERER_ACCELERATED); - rend->blend_mode(SDL_BLENDMODE_BLEND); - - SDL_Rect viewport {0, 0, width, height}; - - TTF_Init(); - - data::Board board(4, 3); - data::Board transition_board(board); - - auto* game_screen = new ui::GameScreen(); - game_screen->board.board = &board; - game_screen->font("Monospace"); - game_screen->renderer(rend); - game_screen->viewport(viewport); - - bool quit = false; - auto player = data::Player::P1; - auto round = 1; - game::data::Boards transitions; - auto transitions_timer = SDL_GetTicks(); - size_t transitions_iterator = 0; - while (!quit) { - SDL_Event ev; - while (SDL_PollEvent(&ev) != 0) { - switch (ev.type) { - case SDL_QUIT: { - quit = true; - break; - } - case SDL_WINDOWEVENT: { - if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { - auto s = rend->output_size(); - viewport.w = s.w; - viewport.h = s.h; - - game_screen->viewport(viewport); - } - break; - } - } - - if (ev.type == game_screen->board.event_type) { - switch ((ui::BoardWidget::event_code)ev.user.code) { - case ui::BoardWidget::event_code::PLAY: { - auto* const coord = (SDL_Point*)ev.user.data2; - const auto can_play = - game_screen->caption_notification.visibility() == 0 - && (round <= 2 - || (transitions.empty() - && game::logic::winner(board) - == game::data::Player::NONE)); - if (!can_play) { - delete coord; - break; - } - - try { - transitions = - game::logic::play(board, {coord->x, coord->y}, player); - game_screen->cur_player = player = player == data::Player::P1 - ? data::Player::P2 - : data::Player::P1; - round++; - } catch (const game::error::invalid_move&) { - game_screen->caption_notification.show()->text( - "Invalid move!"); - } - delete coord; - break; - } - } - } - else if (ev.type == game_screen->settings_event()) { - auto* s = (Settings*)ev.user.data1; - board = data::Board(s->board_size.w, s->board_size.h); - game_screen->board.board = &board; - round = 0; - delete s; - } - - game_screen->handle_event(ev); - } - - rend->draw_color({0, 0, 0, SDL_ALPHA_OPAQUE})->clear(); - - if (transitions_iterator < transitions.size()) { - // NOLINTNEXTLINE(readability-magic-numbers) - if (auto t = SDL_GetTicks(); t - transitions_timer > 400) { - game_screen->board.board = &transitions[transitions_iterator]; - transitions_iterator++; - transitions_timer = t; - } - } - else if (transitions_iterator > 0 && transitions_iterator == transitions.size()) - { - board = transitions.back(); - game_screen->board.board = &board; - transitions_iterator = 0; - transitions.clear(); - if (auto winner = game::logic::winner(board); - round > 2 && winner != game::data::Player::NONE) - { - if (winner == game::data::Player::P1) - game_screen->caption_notification.show()->text("Player 1 has won!"); - else game_screen->caption_notification.show()->text("Player 2 has won!"); - } - } - game_screen->render(); - - rend->present(); - } - - delete game_screen; - delete rend; - TTF_Quit(); - SDL_DestroyWindow(win); - SDL_Quit(); - return 0; -} diff --git a/menu.cpp b/menu.cpp deleted file mode 100644 index b1c934f..0000000 --- a/menu.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -#include "menu.hpp" -#include "settings.hpp" - -using namespace bwidgets; -using namespace game; - -ui::Menu::Menu(Widget* parent) - : Widget(parent), _menu(AlignedLayout::Alignment::VERTICAL) -{ - auto* caption = new Caption(); - auto* layout = new AlignedLayout(AlignedLayout::Alignment::HORIZONTAL); - - auto* input_col {new NumericInput()}; - // NOLINTNEXTLINE(readability-magic-numbers) - input_col->value_range(3, 10)->input_min_width = 2; - // NOLINTNEXTLINE(readability-magic-numbers) - caption->font_color_fg({200, 200, 200, 255}); - caption->render_mode(Font::RenderMode::BLENDED)->text("Columns: "); - layout->add_widget(caption)->add_widget(input_col); - _menu.add_widget(layout); - - caption = new Caption(); - auto* input_row = new NumericInput(); - layout = new AlignedLayout(AlignedLayout::Alignment::HORIZONTAL); - // NOLINTNEXTLINE(readability-magic-numbers) - input_row->value_range(3, 10)->input_min_width = 2; - // NOLINTNEXTLINE(readability-magic-numbers) - caption->font_color_fg({200, 200, 200, 255}); - caption->render_mode(Font::RenderMode::BLENDED)->text("Rows: "); - layout->add_widget(caption)->add_widget(input_row); - _menu.add_widget(layout); - - auto* btn_ok = new Button(); - btn_ok->text("Ok"); - btn_ok->click_handler = [this, input_col, input_row](const SDL_MouseButtonEvent&) { - _push_settings(new Settings({ - {input_col->value, input_row->value} - })); - this->shown = false; - }; - _menu.add_widget(btn_ok); -} - -auto ui::Menu::handle_event(const SDL_Event& ev) -> ui::Menu* -{ - _menu.handle_event(ev); - return this; -} - -auto ui::Menu::size() const noexcept -> Size -{ - return _menu.size(); -} - -void ui::Menu::_handle_font_change(Font* f) -{ - auto apply_font = [f](Widget* w) { - if (auto* handler = dynamic_cast(w); handler) handler->font(f); - }; - _menu.for_widgets([apply_font](Widget* w) { - if (auto* layout = dynamic_cast(w); layout) - layout->for_widgets(apply_font); - else if (auto* handler = dynamic_cast(w); handler) apply_font(w); - }); -} - -void ui::Menu::_handle_geometry_change(const SDL_Rect& vp) noexcept -{ - auto size = _menu.size(); - _widget_area = {center_line(vp.w, size.w), center_line(vp.h, size.h), size.w, - size.h}; - - _menu.viewport(rect_offset(_widget_area, _viewport)); -} - -void ui::Menu::_handle_renderer_change(Renderer* r) -{ - _menu.renderer(r); -} - -void ui::Menu::_handle_rendering() -{ - if (shown) { - // NOLINTNEXTLINE(readability-magic-numbers) - _renderer->draw_color({0, 0, 0, 185})->fill_rect(nullptr); - _menu.render(); - } -} - -void ui::Menu::_push_settings(Settings* s) const -{ - SDL_Event ev; - SDL_zero(ev); - ev.type = settings_event; - ev.user.data1 = s; - SDL_PushEvent(&ev); -} diff --git a/menu.hpp b/menu.hpp deleted file mode 100644 index fdbed59..0000000 --- a/menu.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef MENU_HPP -#define MENU_HPP - -#include - -#include -#include - -namespace game -{ - struct Settings; -} - -namespace game::ui -{ - class Menu final : public bwidgets::Widget, - public bwidgets::FontHandler - { - bwidgets::AlignedLayout _menu; - - void _push_settings(Settings*) const; - - void _handle_font_change(bwidgets::Font*) override; - void _handle_font_color_change(const bwidgets::Color&, - const bwidgets::Color&) override - {} - void _handle_geometry_change(const SDL_Rect&) noexcept override; - void _handle_renderer_change(bwidgets::Renderer*) override; - void _handle_rendering() override; - - public: - const uint32_t settings_event {SDL_RegisterEvents(1)}; - bool shown {}; - - Menu(Widget* parent = nullptr); - - auto handle_event(const SDL_Event&) -> Menu* override; - [[nodiscard]] auto size() const noexcept -> bwidgets::Size override; - }; -} - -#endif diff --git a/meson.build b/meson.build index 5808dfd..7f7342a 100644 --- a/meson.build +++ b/meson.build @@ -12,18 +12,21 @@ project('chainreact', 'cpp', add_project_arguments('-pedantic', language: 'cpp') +compiler = meson.get_compiler('cpp').get_id() +if compiler == 'clang++' + add_project_arguments('-fcoroutines-ts', language: 'cpp') +elif compiler == 'g++' + add_project_arguments('-fcoroutines', language: 'cpp') +endif + executable('chainreact', - 'board_widget.cpp', - 'data.cpp', - 'fading_caption.cpp', - 'game.cpp', - 'game_screen.cpp', - 'main.cpp', - 'menu.cpp', + 'src/core/board2d.cpp', + 'src/tui/tui.cpp', + 'src/tui/main.cpp', dependencies : [ dependency('basic_widgets', static : true, - ) + ), ], install : true, win_subsystem : 'windows') diff --git a/settings.hpp b/settings.hpp deleted file mode 100644 index abf6cfa..0000000 --- a/settings.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef SETTINGS_HPP -#define SETTINGS_HPP - -#include - -#include - -namespace game -{ - struct Settings - { - bwidgets::Size board_size {4, 3}; - }; -} - -#endif diff --git a/src/core/a.out b/src/core/a.out new file mode 100755 index 0000000..7ac7b3c Binary files /dev/null and b/src/core/a.out differ diff --git a/src/core/board2d.cpp b/src/core/board2d.cpp new file mode 100644 index 0000000..40367c5 --- /dev/null +++ b/src/core/board2d.cpp @@ -0,0 +1,151 @@ +#include +#include + +#include "board2d.hpp" + +using namespace core; + +Board2D::Board2D(const size_type w, const size_type h) +{ + init(w, h); +} + +void Board2D::add_unit(const Coord c) +{ + std::cerr << "add_unit " << c.x << "," << c.y << std::endl; + auto& space = at(c); + if (!(space.capacity >= space.value)) throw InvalidMove {"Space is overloaded"}; + + space.value++; +} + +void Board2D::capture(const Coord c, const Player p) noexcept +{ + std::cerr << static_cast(p) << " captures " << c.x << "," << c.y << std::endl; + at(c).owner = p; +} + +void Board2D::init(const size_type w, const size_type h) +{ + _width = w; + _height = h; + + _spaces.reserve(w * h); // NOLINT + for (size_type y = 0; y < h; y++) { + for (size_type x = 0; x < w; x++) { + _spaces.push_back(Coord(x, y)); + if (x == 0 || x == w - 1) { + if (y == 0 || y == h - 1) { + _spaces.back().capacity = 1; + } + else { + _spaces.back().capacity = 2; + } + } + else { + if (y == 0 || y == h - 1) { + _spaces.back().capacity = 2; + } + else { + _spaces.back().capacity = 3; + } + } + } + } +} + +auto Board2D::move(const Coord c, const Player p) -> Generator +{ + const auto& space = at(c); + if (space.owner == Player::NONE) capture(c, p); + else if (space.owner != p) throw InvalidMove {"Player is not the Space owner"}; + add_unit(c); + co_yield *this; + + std::vector overloaded; + if (is_overloaded(c)) { + overloaded.push_back(c); + do { + const auto src = overloaded.back(); + const auto targets = neighbours(src); + overloaded.pop_back(); + + auto spreader = spread(src, targets, p); + while (spreader) co_yield spreader(); + + for (const auto t : targets) { + if (is_overloaded(t)) { + std::cerr << "overloaded: " << t.x << "," << t.y << std::endl; + overloaded.push_back(t); + }; + } + } while (!overloaded.empty()); + } +} + +auto Board2D::neighbours(const Coord c) const noexcept -> std::vector +{ + std::vector nghbrs; + + if (c.x > 0) nghbrs.push_back({c.x - 1, c.y}); + if (c.x < _width - 1) nghbrs.push_back({c.x + 1, c.y}); + if (c.y > 0) nghbrs.push_back({c.x, c.y - 1}); + if (c.y < _height - 1) nghbrs.push_back({c.x, c.y + 1}); + + return nghbrs; +} + +auto Board2D::is_overloaded(Coord c) const noexcept -> bool +{ + const auto& s = at(c); + return s.value > s.capacity; +} + +auto Board2D::size() const noexcept -> std::array +{ + return {_width, _height}; +} + +auto Board2D::spread(const Coord src, const std::vector& targets, Player p) + -> Generator +{ + auto& s = at(src); + std::cerr << "spread " << src.x << "," << src.y << " " << s.value << "/" + << s.capacity << std::endl; + { + const auto count = targets.size(); + if (s.value != static_cast(count)) { + throw std::logic_error("cannot spread: unit count and target " + "spaces count differ"); + } + } + + s.reset(); + co_yield *this; + + for (const auto& c : targets) { + capture(c, p); + add_unit(c); + co_yield *this; + } +} + +auto Board2D::operator()(const Coord c) const noexcept -> const Board2D::Space& +{ + return _spaces.at(coord2idx(c)); +} + +auto Board2D::at(const Coord c) noexcept -> Space& +{ + return _spaces.at(coord2idx(c)); +} + +auto Board2D::at(const Coord c) const noexcept -> const Space& +{ + return _spaces.at(coord2idx(c)); +} + +auto Board2D::coord2idx(const Coord c) const noexcept -> size_type +{ + return c.y * _width + c.x; +} diff --git a/src/core/board2d.hpp b/src/core/board2d.hpp new file mode 100644 index 0000000..ac157f2 --- /dev/null +++ b/src/core/board2d.hpp @@ -0,0 +1,74 @@ +#ifndef CORE_BOARD2D_HPP +#define CORE_BOARD2D_HPP + +#include +#include +#include + +#include "generator.hpp" +#include "player.hpp" + +namespace core +{ + class Board2D + { + public: + struct Space; + using Spaces = std::vector; + using size_type = Spaces::size_type; + + struct Coord + { + size_type x; + size_type y; + + Coord(size_t x_, size_t y_) : x {x_}, y {y_} {} + }; + + struct Space + { + int capacity {}; + Coord coord; + Player owner {Player::NONE}; + int value {0}; + + Space(Coord c) : coord {c} {} + + inline void reset() noexcept + { + owner = Player::NONE; + value = 0; + } + }; + + struct InvalidMove : public std::logic_error + { + using std::logic_error::logic_error; + }; + + Board2D() = default; + Board2D(size_type, size_type); + + void add_unit(Coord); + void capture(Coord, Player) noexcept; + void init(size_type, size_type); + auto move(Coord, Player) -> Generator; + [[nodiscard]] auto neighbours(Coord) const noexcept -> std::vector; + [[nodiscard]] auto operator()(Coord) const noexcept -> const Space&; + [[nodiscard]] auto size() const noexcept -> std::array; + auto spread(Coord, const std::vector&, Player) -> Generator; + + [[nodiscard]] auto is_overloaded(Coord) const noexcept -> bool; + + private: + size_type _height; + Spaces _spaces; + size_type _width; + + [[nodiscard]] auto at(Coord) noexcept -> Space&; + [[nodiscard]] auto at(Coord) const noexcept -> const Space&; + [[nodiscard]] auto coord2idx(Coord) const noexcept -> size_type; + }; +} + +#endif diff --git a/src/core/generator.hpp b/src/core/generator.hpp new file mode 100644 index 0000000..6afd5fa --- /dev/null +++ b/src/core/generator.hpp @@ -0,0 +1,66 @@ +// https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html#generic-generator-example +#include +#include +#ifdef __clang__ +#include +namespace stdco = std::experimental; +#else +#include +namespace stdco = std; +#endif + +template +struct Generator +{ + struct promise_type; + using handle_type = stdco::coroutine_handle; + + struct promise_type + { + T value_; + std::exception_ptr exception_; + + Generator get_return_object() + { + return Generator(handle_type::from_promise(*this)); + } + stdco::suspend_always initial_suspend() { return {}; } + stdco::suspend_always final_suspend() noexcept { return {}; } + void unhandled_exception() { exception_ = std::current_exception(); } + template From> // C++20 concept + stdco::suspend_always yield_value(From&& from) + { + value_ = std::forward(from); + return {}; + } + void return_void() {} + }; + + handle_type h_; + + Generator(handle_type h) : h_(h) {} + ~Generator() { h_.destroy(); } + explicit operator bool() + { + fill(); + return !h_.done(); + } + T operator()() + { + fill(); + full_ = false; + return std::move(h_.promise().value_); + } + +private: + bool full_ = false; + + void fill() + { + if (!full_) { + h_(); + if (h_.promise().exception_) std::rethrow_exception(h_.promise().exception_); + full_ = true; + } + } +}; diff --git a/src/core/generator.hpp.gch b/src/core/generator.hpp.gch new file mode 100644 index 0000000..a50992a Binary files /dev/null and b/src/core/generator.hpp.gch differ diff --git a/src/core/player.hpp b/src/core/player.hpp new file mode 100644 index 0000000..531d118 --- /dev/null +++ b/src/core/player.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_PLAYER_HPP +#define CORE_PLAYER_HPP + +namespace core +{ + enum struct Player + { + NONE, + P1, + P2 + }; +} + +#endif diff --git a/src/tui/main.cpp b/src/tui/main.cpp new file mode 100644 index 0000000..50901ee --- /dev/null +++ b/src/tui/main.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "tui.hpp" + +using namespace core; +using namespace std; +using namespace tui; + +int main() +{ + Board2D b {3, 3}; + Player p {Player::P1}; + do { + try { + while (true) { + cout << "Player " << static_cast(p) << endl; + const auto opt = move_ask(); + const auto coord = opt.value(); + play_move(&b, coord, p, [](auto state) { board_print(state); }); + p = p == Player::P1 ? Player::P2 : Player::P1; + } + break; + } catch (const Board2D::InvalidMove& e) { + cerr << "Invalid move!" << std::endl; + } + } while (true); + + return EXIT_SUCCESS; +} diff --git a/src/tui/tui.cpp b/src/tui/tui.cpp new file mode 100644 index 0000000..d504c56 --- /dev/null +++ b/src/tui/tui.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#include "tui.hpp" + +#include + +using namespace core; +using namespace std; +using namespace tui; + +void tui::board_print(const Board2D& b) +{ + auto [w, h] = b.size(); + for (Board2D::size_type y = 0; y < h; y++) { + for (Board2D::size_type x = 0; x < w; x++) { + const auto& space = b({x, y}); + cout << "|" << static_cast(space.owner) << ":" << space.value; + } + cout << endl; + } + + for (Board2D::size_type x = 0; x < w; x++) cout << "-"; + cout << endl; +} + +auto tui::move_ask() -> optional +{ + const auto success_code = std::errc(); + std::from_chars_result result; + Board2D::size_type x, y; + string x_input, y_input; + + cout << "Play move:" << endl << "\tx: "; + cin >> x_input; + result = std::from_chars(x_input.c_str(), x_input.c_str() + x_input.size(), x); + if (result.ec != success_code) goto failure; + + cout << "\ty: "; + cin >> y_input; + result = std::from_chars(y_input.c_str(), y_input.c_str() + y_input.size(), y); + if (result.ec != success_code) goto failure; + + return make_optional(Board2D::Coord {x, y}); + +failure: + return nullopt; +} diff --git a/src/tui/tui.hpp b/src/tui/tui.hpp new file mode 100644 index 0000000..01d33d3 --- /dev/null +++ b/src/tui/tui.hpp @@ -0,0 +1,26 @@ +#include +#include + +#include "../core/board2d.hpp" +#include "../core/player.hpp" + +namespace tui +{ + using namespace core; + + template + concept PlayCB = std::is_convertible_v>; + + void board_print(const Board2D&); + auto move_ask() -> std::optional; + + template + void play_move(Board2D* b, Board2D::Coord c, Player p, Cb cb) + { + auto move_stepper = b->move(c, p); + while (move_stepper) { + auto new_state = move_stepper(); + cb(new_state); + } + } +} diff --git a/subprojects/basic_widgets.wrap b/subprojects/basic_widgets.wrap index b19cec7..5cf633a 100644 --- a/subprojects/basic_widgets.wrap +++ b/subprojects/basic_widgets.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/soratobukuroneko/sdl2_basic_widgets.git -revision = prototype01 +revision = 0.9.0 [provide] basic_widgets = libbasic_widgets_dep