start rewrite
This commit is contained in:
parent
7c930df84c
commit
ee51631905
223
board_widget.cpp
223
board_widget.cpp
|
@ -1,223 +0,0 @@
|
|||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include <basic_widgets/core/draw.hpp>
|
||||
#include <basic_widgets/core/renderer.hpp>
|
||||
#include <basic_widgets/core/texture.hpp>
|
||||
|
||||
#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<SDL_Point, 4> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
#ifndef BOARD_WIDGET_HPP
|
||||
#define BOARD_WIDGET_HPP
|
||||
|
||||
#include <SDL2/SDL_rect.h>
|
||||
#include <SDL2/SDL_stdinc.h>
|
||||
|
||||
#include <basic_widgets/w/base/widget.hpp>
|
||||
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
||||
#include <basic_widgets/w/feat/texture_handler.hpp>
|
||||
|
||||
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
|
10
data.cpp
10
data.cpp
|
@ -1,10 +0,0 @@
|
|||
#include <utility>
|
||||
|
||||
#include "data.hpp"
|
||||
|
||||
using namespace game::data;
|
||||
|
||||
Board::Board(int w, int h) : width(w), height(h)
|
||||
{
|
||||
squares.resize((std::vector<Square>::size_type)w * h, {});
|
||||
}
|
64
data.hpp
64
data.hpp
|
@ -1,64 +0,0 @@
|
|||
#ifndef DATA_HPP
|
||||
#define DATA_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<Square> 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<Board&>(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<Board>;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,27 +0,0 @@
|
|||
#include <basic_widgets/core/texture.hpp>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
#ifndef FADING_CAPTION_HPP
|
||||
#define FADING_CAPTION_HPP
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <basic_widgets/w/caption.hpp>
|
||||
|
||||
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
|
77
game.cpp
77
game.cpp
|
@ -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;
|
||||
}
|
29
game.hpp
29
game.hpp
|
@ -1,29 +0,0 @@
|
|||
#ifndef GAME_HPP
|
||||
#define GAME_HPP
|
||||
|
||||
#include <exception>
|
||||
|
||||
#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
|
133
game_screen.cpp
133
game_screen.cpp
|
@ -1,133 +0,0 @@
|
|||
#include <SDL2/SDL_render.h>
|
||||
|
||||
#include <basic_widgets/core/math.hpp>
|
||||
|
||||
#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<GameScreen*>(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);
|
||||
}
|
|
@ -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 <unordered_map>
|
||||
|
||||
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
|
142
main.cpp
142
main.cpp
|
@ -1,142 +0,0 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
|
||||
#include <basic_widgets/core/font.hpp>
|
||||
|
||||
#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;
|
||||
}
|
101
menu.cpp
101
menu.cpp
|
@ -1,101 +0,0 @@
|
|||
#include <basic_widgets/core/math.hpp>
|
||||
#include <basic_widgets/w/caption.hpp>
|
||||
#include <basic_widgets/w/numeric_input.hpp>
|
||||
|
||||
#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<int>()};
|
||||
// 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<int>();
|
||||
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<FontHandler*>(w); handler) handler->font(f);
|
||||
};
|
||||
_menu.for_widgets([apply_font](Widget* w) {
|
||||
if (auto* layout = dynamic_cast<Layout*>(w); layout)
|
||||
layout->for_widgets(apply_font);
|
||||
else if (auto* handler = dynamic_cast<FontHandler*>(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);
|
||||
}
|
42
menu.hpp
42
menu.hpp
|
@ -1,42 +0,0 @@
|
|||
#ifndef MENU_HPP
|
||||
#define MENU_HPP
|
||||
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include <basic_widgets/w/aligned_layout.hpp>
|
||||
#include <basic_widgets/w/feat/font_handler.hpp>
|
||||
|
||||
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
|
19
meson.build
19
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')
|
||||
|
|
16
settings.hpp
16
settings.hpp
|
@ -1,16 +0,0 @@
|
|||
#ifndef SETTINGS_HPP
|
||||
#define SETTINGS_HPP
|
||||
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include <basic_widgets/core/type/size.hpp>
|
||||
|
||||
namespace game
|
||||
{
|
||||
struct Settings
|
||||
{
|
||||
bwidgets::Size board_size {4, 3};
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
Binary file not shown.
|
@ -0,0 +1,151 @@
|
|||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#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<int>(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<Board2D>
|
||||
{
|
||||
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<Coord> 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<Coord>
|
||||
{
|
||||
std::vector<Coord> 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<size_type, 2>
|
||||
{
|
||||
return {_width, _height};
|
||||
}
|
||||
|
||||
auto Board2D::spread(const Coord src, const std::vector<Coord>& targets, Player p)
|
||||
-> Generator<Board2D>
|
||||
{
|
||||
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<int>(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;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef CORE_BOARD2D_HPP
|
||||
#define CORE_BOARD2D_HPP
|
||||
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "generator.hpp"
|
||||
#include "player.hpp"
|
||||
|
||||
namespace core
|
||||
{
|
||||
class Board2D
|
||||
{
|
||||
public:
|
||||
struct Space;
|
||||
using Spaces = std::vector<Space>;
|
||||
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<Board2D>;
|
||||
[[nodiscard]] auto neighbours(Coord) const noexcept -> std::vector<Coord>;
|
||||
[[nodiscard]] auto operator()(Coord) const noexcept -> const Space&;
|
||||
[[nodiscard]] auto size() const noexcept -> std::array<size_type, 2>;
|
||||
auto spread(Coord, const std::vector<Coord>&, Player) -> Generator<Board2D>;
|
||||
|
||||
[[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
|
|
@ -0,0 +1,66 @@
|
|||
// https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html#generic-generator-example
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
#ifdef __clang__
|
||||
#include <experimental/coroutine>
|
||||
namespace stdco = std::experimental;
|
||||
#else
|
||||
#include <coroutine>
|
||||
namespace stdco = std;
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
struct Generator
|
||||
{
|
||||
struct promise_type;
|
||||
using handle_type = stdco::coroutine_handle<promise_type>;
|
||||
|
||||
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<std::convertible_to<T> From> // C++20 concept
|
||||
stdco::suspend_always yield_value(From&& from)
|
||||
{
|
||||
value_ = std::forward<From>(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;
|
||||
}
|
||||
}
|
||||
};
|
Binary file not shown.
|
@ -0,0 +1,14 @@
|
|||
#ifndef CORE_PLAYER_HPP
|
||||
#define CORE_PLAYER_HPP
|
||||
|
||||
namespace core
|
||||
{
|
||||
enum struct Player
|
||||
{
|
||||
NONE,
|
||||
P1,
|
||||
P2
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,30 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#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<int>(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;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#include <charconv>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "tui.hpp"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
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<int>(space.owner) << ":" << space.value;
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
for (Board2D::size_type x = 0; x < w; x++) cout << "-";
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
auto tui::move_ask() -> optional<Board2D::Coord>
|
||||
{
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "../core/board2d.hpp"
|
||||
#include "../core/player.hpp"
|
||||
|
||||
namespace tui
|
||||
{
|
||||
using namespace core;
|
||||
|
||||
template<typename Lambda>
|
||||
concept PlayCB = std::is_convertible_v<Lambda, std::function<void(const Board2D&)>>;
|
||||
|
||||
void board_print(const Board2D&);
|
||||
auto move_ask() -> std::optional<Board2D::Coord>;
|
||||
|
||||
template<PlayCB Cb>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue