224 lines
7.7 KiB
C++
224 lines
7.7 KiB
C++
#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);
|
|
}
|
|
}
|
|
}
|