From 3f5499cebc06356ed99159be3fb9676292cf7b8b Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 24 Jul 2024 10:44:13 +0200 Subject: turns: rename domain to core --- CMakeLists.txt | 2 +- app/CMakeLists.txt | 2 +- app/include/turns/app/widgets/participant_row.hpp | 4 +- app/include/turns/app/widgets/turn_order_view.hpp | 8 +- .../turns/app/windows/participant_editor.hpp | 12 +- app/include/turns/app/windows/tracker.hpp | 4 +- app/src/widgets/participant_row.cpp | 10 +- app/src/widgets/turn_order_view.cpp | 4 +- app/src/windows/participant_editor.cpp | 10 +- app/src/windows/tracker.cpp | 16 +- app/tests/widgets/participant_row.cpp | 8 +- app/tests/windows/participant_editor.cpp | 6 +- core/CMakeLists.txt | 57 ++++++ core/include/turns/core/disposition.hpp | 27 +++ core/include/turns/core/participant.hpp | 58 ++++++ core/include/turns/core/turn_order.hpp | 85 ++++++++ core/src/disposition.cpp | 25 +++ core/src/participant.cpp | 35 ++++ core/src/turn_order.cpp | 225 +++++++++++++++++++++ core/tests/disposition.cpp | 32 +++ core/tests/participant.cpp | 113 +++++++++++ core/tests/register_types.cpp | 13 ++ core/tests/turn_order.cpp | 225 +++++++++++++++++++++ core/tests/turn_order_bugs.cpp | 42 ++++ domain/CMakeLists.txt | 55 ----- domain/include/turns/domain/disposition.hpp | 27 --- domain/include/turns/domain/participant.hpp | 58 ------ domain/include/turns/domain/turn_order.hpp | 85 -------- domain/src/disposition.cpp | 25 --- domain/src/participant.cpp | 35 ---- domain/src/turn_order.cpp | 225 --------------------- domain/tests/disposition.cpp | 32 --- domain/tests/participant.cpp | 113 ----------- domain/tests/register_types.cpp | 13 -- domain/tests/turn_order.cpp | 225 --------------------- domain/tests/turn_order_bugs.cpp | 42 ---- 36 files changed, 980 insertions(+), 978 deletions(-) create mode 100644 core/CMakeLists.txt create mode 100644 core/include/turns/core/disposition.hpp create mode 100644 core/include/turns/core/participant.hpp create mode 100644 core/include/turns/core/turn_order.hpp create mode 100644 core/src/disposition.cpp create mode 100644 core/src/participant.cpp create mode 100644 core/src/turn_order.cpp create mode 100644 core/tests/disposition.cpp create mode 100644 core/tests/participant.cpp create mode 100644 core/tests/register_types.cpp create mode 100644 core/tests/turn_order.cpp create mode 100644 core/tests/turn_order_bugs.cpp delete mode 100644 domain/CMakeLists.txt delete mode 100644 domain/include/turns/domain/disposition.hpp delete mode 100644 domain/include/turns/domain/participant.hpp delete mode 100644 domain/include/turns/domain/turn_order.hpp delete mode 100644 domain/src/disposition.cpp delete mode 100644 domain/src/participant.cpp delete mode 100644 domain/src/turn_order.cpp delete mode 100644 domain/tests/disposition.cpp delete mode 100644 domain/tests/participant.cpp delete mode 100644 domain/tests/register_types.cpp delete mode 100644 domain/tests/turn_order.cpp delete mode 100644 domain/tests/turn_order_bugs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ccf083..088f9d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ include("Catch") # Targets add_subdirectory("app") -add_subdirectory("domain") +add_subdirectory("core") add_subdirectory("lang") add_subdirectory("res") add_subdirectory("test_support") diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0514383..22cf9bf 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -30,7 +30,7 @@ target_link_libraries("app" PUBLIC "PkgConfig::adwaita" "PkgConfig::gtkmm" - "turns::domain" + "turns::core" "turns::lang" "$<$:-Wl,--whole-archive>" diff --git a/app/include/turns/app/widgets/participant_row.hpp b/app/include/turns/app/widgets/participant_row.hpp index b1942b1..d5c896a 100644 --- a/app/include/turns/app/widgets/participant_row.hpp +++ b/app/include/turns/app/widgets/participant_row.hpp @@ -2,7 +2,7 @@ #define TURNS_APP_WIDGETS_PARTICIPANT_ROW_HPP #include "turns/app/widgets/template_widget.hpp" -#include "turns/domain/participant.hpp" +#include "turns/core/participant.hpp" #include @@ -27,7 +27,7 @@ namespace turns::app::widgets "toggle_defeated", }; - participant_row(Glib::RefPtr participant); + participant_row(Glib::RefPtr participant); auto property_delete_enabled() -> Glib::PropertyProxy; auto property_edit_enabled() -> Glib::PropertyProxy; diff --git a/app/include/turns/app/widgets/turn_order_view.hpp b/app/include/turns/app/widgets/turn_order_view.hpp index 15524a9..cb996f8 100644 --- a/app/include/turns/app/widgets/turn_order_view.hpp +++ b/app/include/turns/app/widgets/turn_order_view.hpp @@ -2,9 +2,9 @@ #define TURNS_APP_WIDGETS_TURN_ORDER_VIEW_HPP #include "turns/app/widgets/template_widget.hpp" -#include "turns/domain/disposition.hpp" -#include "turns/domain/participant.hpp" -#include "turns/domain/turn_order.hpp" +#include "turns/core/disposition.hpp" +#include "turns/core/participant.hpp" +#include "turns/core/turn_order.hpp" #include @@ -18,7 +18,7 @@ namespace turns::app::widgets { struct turn_order_view : template_widget { - using model_type = domain::turn_order; + using model_type = core::turn_order; auto constexpr inline static children = std::array{ "view", diff --git a/app/include/turns/app/windows/participant_editor.hpp b/app/include/turns/app/windows/participant_editor.hpp index 23d0569..1ca3e43 100644 --- a/app/include/turns/app/windows/participant_editor.hpp +++ b/app/include/turns/app/windows/participant_editor.hpp @@ -1,7 +1,7 @@ #ifndef TURNS_APP_WINDOWS_PARTICIPANT_EDITOR_HPP #define TURNS_APP_WINDOWS_PARTICIPANT_EDITOR_HPP -#include "turns/domain/participant.hpp" +#include "turns/core/participant.hpp" #include @@ -21,11 +21,11 @@ namespace turns::app::windows struct participant_editor : Gtk::Widget { - using signal_finished_type = sigc::signal().name().get_value()), - decltype(std::declval().priority().get_value()), - decltype(std::declval().disposition().get_value()))>; + using signal_finished_type = sigc::signal().name().get_value()), + decltype(std::declval().priority().get_value()), + decltype(std::declval().disposition().get_value()))>; - participant_editor(BaseObjectType * base, Glib::RefPtr const builder, Glib::RefPtr obj = {}); + participant_editor(BaseObjectType * base, Glib::RefPtr const builder, Glib::RefPtr obj = {}); auto present(Gtk::Widget * parent) -> void; @@ -45,7 +45,7 @@ namespace turns::app::windows Glib::RefPtr m_disposition_factory; Glib::RefPtr m_disposition_model; - Glib::RefPtr m_participant; + Glib::RefPtr m_participant; signal_finished_type m_signal_finished{}; }; diff --git a/app/include/turns/app/windows/tracker.hpp b/app/include/turns/app/windows/tracker.hpp index e1349f8..b9dea52 100644 --- a/app/include/turns/app/windows/tracker.hpp +++ b/app/include/turns/app/windows/tracker.hpp @@ -2,7 +2,7 @@ #define TURNS_APP_WINDOWS_TRACKER_HPP #include "turns/app/widgets/turn_order_view.hpp" -#include "turns/domain/turn_order.hpp" +#include "turns/core/turn_order.hpp" #include #include @@ -36,7 +36,7 @@ namespace turns::app::windows Gtk::Stack * m_stack; Gtk::Button * m_start; AdwWindowTitle * m_title; - Glib::RefPtr m_turn_order; + Glib::RefPtr m_turn_order; widgets::turn_order_view * m_turn_order_view; Glib::PropertyProxy m_subtitle; }; diff --git a/app/src/widgets/participant_row.cpp b/app/src/widgets/participant_row.cpp index 87cc217..6a274d5 100644 --- a/app/src/widgets/participant_row.cpp +++ b/app/src/widgets/participant_row.cpp @@ -18,15 +18,15 @@ namespace turns::app::widgets auto constexpr static TYPE_NAME = "participant_row"; auto constexpr static TEMPLATE = "/ch/arknet/Turns/widgets/participant_row.ui"; - auto css_class_for(domain::disposition value) -> Glib::ustring + auto css_class_for(core::disposition value) -> Glib::ustring { switch (value) { - case domain::disposition::friendly: + case core::disposition::friendly: return "disposition-friendly"; - case domain::disposition::hostile: + case core::disposition::hostile: return "disposition-hostile"; - case domain::disposition::secret: + case core::disposition::secret: return "disposition-secret"; default: return ""; @@ -34,7 +34,7 @@ namespace turns::app::widgets } } // namespace - participant_row::participant_row(Glib::RefPtr participant) + participant_row::participant_row(Glib::RefPtr participant) : Glib::ObjectBase(TYPE_NAME) , template_widget{TEMPLATE} , m_delete{get_widget("delete")} diff --git a/app/src/widgets/turn_order_view.cpp b/app/src/widgets/turn_order_view.cpp index 67e0afa..172b6a2 100644 --- a/app/src/widgets/turn_order_view.cpp +++ b/app/src/widgets/turn_order_view.cpp @@ -1,7 +1,7 @@ #include "turns/app/widgets/turn_order_view.hpp" #include "turns/app/widgets/participant_row.hpp" -#include "turns/domain/participant.hpp" +#include "turns/core/participant.hpp" #include "turns/lang/messages.hpp" #include @@ -32,7 +32,7 @@ namespace turns::app::widgets auto turn_order_view::handle_create_row(Glib::RefPtr const item) -> Gtk::Widget * { - auto participant = std::dynamic_pointer_cast(item); + auto participant = std::dynamic_pointer_cast(item); auto row = Gtk::make_managed(participant); Glib::Binding::bind_property(m_model->is_running(), diff --git a/app/src/windows/participant_editor.cpp b/app/src/windows/participant_editor.cpp index 0b35c72..b31ad77 100644 --- a/app/src/windows/participant_editor.cpp +++ b/app/src/windows/participant_editor.cpp @@ -1,6 +1,6 @@ #include "turns/app/windows/participant_editor.hpp" -#include "turns/domain/disposition.hpp" +#include "turns/core/disposition.hpp" #include "turns/lang/messages.hpp" #include @@ -14,7 +14,7 @@ namespace turns::app::windows { - participant_editor::participant_editor(BaseObjectType * base, Glib::RefPtr const builder, Glib::RefPtr obj) + participant_editor::participant_editor(BaseObjectType * base, Glib::RefPtr const builder, Glib::RefPtr obj) : Gtk::Widget{base} , m_adw{ADW_DIALOG(gobj())} , m_disposition{ADW_COMBO_ROW(builder->get_widget("disposition")->gobj())} @@ -29,9 +29,9 @@ namespace turns::app::windows adw_dialog_set_title(m_adw, _(obj ? lang::edit_participant : lang::add_participant)); m_finish->signal_clicked().connect(sigc::mem_fun(*this, &participant_editor::handle_finish_clicked)); - for (auto n : std::views::iota(std::uint8_t{}, static_cast(domain::disposition::END))) + for (auto n : std::views::iota(std::uint8_t{}, static_cast(core::disposition::END))) { - m_disposition_model->append(presentation_name_for(domain::disposition{n})); + m_disposition_model->append(presentation_name_for(core::disposition{n})); } m_disposition_factory->signal_bind().connect(sigc::mem_fun(*this, &participant_editor::handle_item_bind)); @@ -62,7 +62,7 @@ namespace turns::app::windows { auto name = gtk_editable_get_text(GTK_EDITABLE(m_name)); auto priority = adw_spin_row_get_value(m_priority); - auto disposition = static_cast(adw_combo_row_get_selected(m_disposition)); + auto disposition = static_cast(adw_combo_row_get_selected(m_disposition)); if (m_participant) { diff --git a/app/src/windows/tracker.cpp b/app/src/windows/tracker.cpp index 11f4642..7c4d2cd 100644 --- a/app/src/windows/tracker.cpp +++ b/app/src/windows/tracker.cpp @@ -16,13 +16,13 @@ namespace turns::app::windows namespace { - auto editor_for(Glib::RefPtr participant) + auto editor_for(Glib::RefPtr participant) { auto builder = Gtk::Builder::create_from_resource("/ch/arknet/Turns/windows/participant_editor.ui"); return std::pair{builder, Gtk::Builder::get_widget_derived(builder, "participant_editor", participant)}; } - auto stop_dialog_callback(AdwAlertDialog * dialog, GAsyncResult * result, domain::turn_order * order) + auto stop_dialog_callback(AdwAlertDialog * dialog, GAsyncResult * result, core::turn_order * order) { auto response = adw_alert_dialog_choose_finish(dialog, result); if (response == Glib::ustring{"clear"}) @@ -42,7 +42,7 @@ namespace turns::app::windows , m_stack{builder->get_widget("stack")} , m_start{builder->get_widget("start")} , m_title(ADW_WINDOW_TITLE(builder->get_widget("title")->gobj())) - , m_turn_order{domain::turn_order::create()} + , m_turn_order{core::turn_order::create()} , m_turn_order_view{Gtk::make_managed(m_turn_order)} , m_subtitle{Glib::wrap(GTK_WIDGET(m_title)), "subtitle"} { @@ -84,7 +84,7 @@ namespace turns::app::windows { static_cast(param); auto index = Glib::VariantBase::cast_dynamic>(param); - auto participant = m_turn_order->get_typed_object(index.get()); + auto participant = m_turn_order->get_typed_object(index.get()); auto [lifeline, dialog] = editor_for(participant); dialog->present(this); } @@ -120,7 +120,7 @@ namespace turns::app::windows // win.clear // depends-on: turn_order:is_empty == false { - auto action = add_action("clear", sigc::mem_fun(*m_turn_order, &domain::turn_order::clear)); + auto action = add_action("clear", sigc::mem_fun(*m_turn_order, &core::turn_order::clear)); Glib::Binding::bind_property(m_turn_order->is_empty(), action->property_enabled(), @@ -130,7 +130,7 @@ namespace turns::app::windows // win.next // depends-on: turn_order:state == running { - auto action = add_action("next", sigc::mem_fun(*m_turn_order, &domain::turn_order::next)); + auto action = add_action("next", sigc::mem_fun(*m_turn_order, &core::turn_order::next)); Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } @@ -138,7 +138,7 @@ namespace turns::app::windows // win.previous // depends-on: turn_order:has_previous == true { - auto action = add_action("previous", sigc::mem_fun(*m_turn_order, &domain::turn_order::previous)); + auto action = add_action("previous", sigc::mem_fun(*m_turn_order, &core::turn_order::previous)); Glib::Binding::bind_property(m_turn_order->has_previous(), action->property_enabled(), Glib::Binding::Flags::SYNC_CREATE); } @@ -146,7 +146,7 @@ namespace turns::app::windows // win.start // depends-on: turn_order:is_empty == false { - auto action = add_action("start", sigc::mem_fun(*m_turn_order, &domain::turn_order::start)); + auto action = add_action("start", sigc::mem_fun(*m_turn_order, &core::turn_order::start)); Glib::Binding::bind_property(m_turn_order->is_running(), action->property_enabled(), diff --git a/app/tests/widgets/participant_row.cpp b/app/tests/widgets/participant_row.cpp index 04950ad..ebf1762 100644 --- a/app/tests/widgets/participant_row.cpp +++ b/app/tests/widgets/participant_row.cpp @@ -1,7 +1,7 @@ #include "turns/app/widgets/participant_row.hpp" -#include "turns/domain/disposition.hpp" -#include "turns/domain/participant.hpp" +#include "turns/core/disposition.hpp" +#include "turns/core/participant.hpp" #include "turns/lang/messages.hpp" #include @@ -19,12 +19,12 @@ namespace turns::app::widgets::tests { SECTION("can be created without a participant") { - REQUIRE(Gtk::make_managed(Glib::RefPtr{})); + REQUIRE(Gtk::make_managed(Glib::RefPtr{})); } SECTION("can be created with a participant") { - REQUIRE(Gtk::make_managed(domain::participant::create("Tazmyla Fireforge", 13, domain::disposition::secret))); + REQUIRE(Gtk::make_managed(core::participant::create("Tazmyla Fireforge", 13, core::disposition::secret))); } } diff --git a/app/tests/windows/participant_editor.cpp b/app/tests/windows/participant_editor.cpp index 9f73861..670e56f 100644 --- a/app/tests/windows/participant_editor.cpp +++ b/app/tests/windows/participant_editor.cpp @@ -1,7 +1,7 @@ #include "turns/app/windows/participant_editor.hpp" -#include "turns/domain/participant.hpp" -#include "turns/domain/disposition.hpp" +#include "turns/core/participant.hpp" +#include "turns/core/disposition.hpp" #include "turns/lang/messages.hpp" #include @@ -70,7 +70,7 @@ namespace turns::app::windows::tests auto locale = GENERATE("en_US.UTF-8", "de_CH.UTF-8"); setlocale(LC_ALL, locale); - auto participant = domain::participant::create("Qibi Babblebranch", 12, domain::disposition::neutral); + auto participant = core::participant::create("Qibi Babblebranch", 12, core::disposition::neutral); auto builder = Gtk::Builder::create_from_resource("/ch/arknet/Turns/windows/participant_editor.ui"); auto instance = Gtk::Builder::get_widget_derived(builder, "participant_editor", participant); auto window = Gtk::Window{}; diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..785421c --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,57 @@ +set(COMPONENT "core") + +# Library + +add_library("${COMPONENT}" + "src/disposition.cpp" + "src/participant.cpp" + "src/turn_order.cpp" +) + +add_library("turns::${COMPONENT}" ALIAS "${COMPONENT}") + + +target_compile_options("${COMPONENT}" PUBLIC + "$<$:-Wall>" + "$<$:-Wextra>" + "$<$:-Werror>" + "$<$:-pedantic-errors>" + PRIVATE + "$<$,$>:-fprofile-arcs>" + "$<$,$>:-ftest-coverage>" +) + +target_include_directories("${COMPONENT}" PUBLIC + "include" +) + +target_link_libraries("${COMPONENT}" PUBLIC + "$<$,$>:gcov>" + + "PkgConfig::giomm" + "PkgConfig::glibmm" +) + +target_link_options("${COMPONENT}" PRIVATE + "$<$,$>:--coverage>" +) + +# Tests + +add_executable("${COMPONENT}-tests" + "tests/register_types.cpp" + + "tests/disposition.cpp" + "tests/participant.cpp" + "tests/turn_order_bugs.cpp" + "tests/turn_order.cpp" +) + +target_link_libraries("${COMPONENT}-tests" + "Catch2::Catch2" + + "turns::core" + "turns::glib-test-main" +) + +catch_discover_tests("${COMPONENT}-tests") \ No newline at end of file diff --git a/core/include/turns/core/disposition.hpp b/core/include/turns/core/disposition.hpp new file mode 100644 index 0000000..291aaf5 --- /dev/null +++ b/core/include/turns/core/disposition.hpp @@ -0,0 +1,27 @@ +#ifndef TURNS_DOMAIN_DISPOSITION_HPP +#define TURNS_DOMAIN_DISPOSITION_HPP + +#include +#include + +#include + +namespace turns::core +{ + + enum struct disposition : std::uint8_t + { + neutral, + friendly, + hostile, + secret, + + ///! End marker + END + }; + + auto presentation_name_for(disposition value) -> Glib::ustring; + +} // namespace turns::core + +#endif \ No newline at end of file diff --git a/core/include/turns/core/participant.hpp b/core/include/turns/core/participant.hpp new file mode 100644 index 0000000..9b5dab4 --- /dev/null +++ b/core/include/turns/core/participant.hpp @@ -0,0 +1,58 @@ +#ifndef TURNS_DOMAIN_PARTICIPANT_HPP +#define TURNS_DOMAIN_PARTICIPANT_HPP + +#include "turns/core/disposition.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace turns::core +{ + struct participant : Glib::Object + { + auto static create(Glib::ustring name, float priority, disposition disposition) -> Glib::RefPtr; + + participant(); + participant(Glib::ustring name, float priority, disposition disposition); + + auto operator<=>(participant const & other) const noexcept -> std::partial_ordering; + + template + auto disposition(this Self && self) + { + return self.m_disposition.get_proxy(); + } + + template + auto is_active(this Self && self) + { + return self.m_is_active.get_proxy(); + } + + template + auto name(this Self && self) + { + return self.m_name.get_proxy(); + } + + template + auto priority(this Self && self) + { + return self.m_priority.get_proxy(); + } + + private: + Glib::Property m_disposition{*this, "disposition", core::disposition::neutral}; + Glib::Property m_is_active{*this, "active", false}; + Glib::Property m_name{*this, "name", ""}; + Glib::Property m_priority{*this, "priority", 0.0f}; + }; + +} // namespace turns::core + +#endif \ No newline at end of file diff --git a/core/include/turns/core/turn_order.hpp b/core/include/turns/core/turn_order.hpp new file mode 100644 index 0000000..43ee075 --- /dev/null +++ b/core/include/turns/core/turn_order.hpp @@ -0,0 +1,85 @@ +#ifndef TURNS_DOMAIN_TURN_ORDER_HPP +#define TURNS_DOMAIN_TURN_ORDER_HPP + +#include "turns/core/disposition.hpp" +#include "turns/core/participant.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace turns::core +{ + + struct turn_order : Gio::ListModel, + Glib::Object + { + using value_type = Glib::RefPtr; + using container_type = std::vector; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + + using active_participant_type = unsigned int; + using is_empty_type = bool; + using has_next_type = bool; + using has_previous_type = bool; + using is_running_type = bool; + using round_number_type = unsigned int; + + auto static constexpr invalid_participant_index = std::numeric_limits::max(); + auto static constexpr invalid_round_number = std::numeric_limits::max(); + + /** Life-time */ + turn_order(); + + auto static create() -> Glib::RefPtr; + + /** Properties */ + auto is_empty() const -> Glib::PropertyProxy_ReadOnly; + auto has_next() const -> Glib::PropertyProxy_ReadOnly; + auto has_previous() const -> Glib::PropertyProxy_ReadOnly; + auto is_running() const -> Glib::PropertyProxy_ReadOnly; + auto round_number() const -> Glib::PropertyProxy_ReadOnly; + + /** Element Modifications */ + auto add(Glib::ustring const & name, float priority, disposition disposition) -> void; + auto clear() -> void; + auto remove(unsigned index) -> void; + + /** Turn Modification */ + auto next() -> void; + auto previous() -> void; + auto start() -> void; + auto stop() -> void; + + private: + auto get_item_type_vfunc() -> GType override; + auto get_n_items_vfunc() -> unsigned override; + auto get_item_vfunc(unsigned position) -> void * override; + + /** Signal handlers */ + auto handle_priority_changed(value_type entry) -> void; + + /** Data management */ + auto find(value_type entry) const -> const_iterator; + auto insert(value_type entry) -> const_iterator; + + container_type m_data{}; + std::optional m_active{}; + + Glib::Property m_has_next{*this, "has-next", false}; + Glib::Property m_has_previous{*this, "has-previous", false}; + Glib::Property m_is_empty{*this, "is-empty", true}; + Glib::Property m_is_running{*this, "is-running", false}; + Glib::Property m_round_number{*this, "round-number", invalid_round_number}; + }; + +} // namespace turns::core + +#endif \ No newline at end of file diff --git a/core/src/disposition.cpp b/core/src/disposition.cpp new file mode 100644 index 0000000..4eec33e --- /dev/null +++ b/core/src/disposition.cpp @@ -0,0 +1,25 @@ +#include "turns/core/disposition.hpp" + +#include + +namespace turns::core +{ + + auto presentation_name_for(disposition value) -> Glib::ustring + { + switch (value) + { + case disposition::neutral: + return _("Neutral"); + case disposition::friendly: + return _("Friendly"); + case disposition::hostile: + return _("Hostile"); + case disposition::secret: + return _("Secret"); + default: + return _("Unknown disposition value"); + } + } + +} // namespace turns::core \ No newline at end of file diff --git a/core/src/participant.cpp b/core/src/participant.cpp new file mode 100644 index 0000000..45b02bd --- /dev/null +++ b/core/src/participant.cpp @@ -0,0 +1,35 @@ +#include "turns/core/participant.hpp" + +#include +#include + +#include +#include + +namespace turns::core +{ + auto participant::create(Glib::ustring name, float priority, core::disposition disposition) -> Glib::RefPtr + { + return Glib::make_refptr_for_instance(new participant{name, priority, disposition}); + } + + participant::participant() + : Glib::ObjectBase{typeid(participant)} + , Glib::Object{} + { + } + + participant::participant(Glib::ustring name, float priority, core::disposition disposition) + : participant() + { + m_name = name; + m_priority = priority; + m_disposition = disposition; + } + + auto participant::operator<=>(participant const & other) const noexcept -> std::partial_ordering + { + return m_priority <=> other.m_priority; + } + +} // namespace turns::core \ No newline at end of file diff --git a/core/src/turn_order.cpp b/core/src/turn_order.cpp new file mode 100644 index 0000000..ae3511e --- /dev/null +++ b/core/src/turn_order.cpp @@ -0,0 +1,225 @@ +#include "turns/core/turn_order.hpp" + +#include "turns/core/participant.hpp" + +#include +#include +#include +#include + +#include + +namespace turns::core +{ + + namespace + { + auto constexpr comparator = [](auto lhs, auto rhs) { + return *lhs > *rhs; + }; + + auto constexpr equal_comparator = [](auto lhs, auto rhs) { + return (lhs->get_name() == rhs->get_name()) && (lhs->get_priority() && rhs->get_priority()); + }; + } // namespace + + /** Construction */ + + turn_order::turn_order() + : Glib::ObjectBase{typeid(turn_order)} + , Gio::ListModel{} + { + } + + auto turn_order::create() -> Glib::RefPtr + { + return Glib::make_refptr_for_instance(new turn_order{}); + } + + /** Queries */ + + auto turn_order::is_empty() const -> Glib::PropertyProxy_ReadOnly + { + return m_is_empty.get_proxy(); + } + + auto turn_order::has_next() const -> Glib::PropertyProxy_ReadOnly + { + return m_has_next.get_proxy(); + } + + auto turn_order::has_previous() const -> Glib::PropertyProxy_ReadOnly + { + return m_has_previous.get_proxy(); + } + + auto turn_order::is_running() const -> Glib::PropertyProxy_ReadOnly + { + return m_is_running.get_proxy(); + } + + auto turn_order::round_number() const -> Glib::PropertyProxy_ReadOnly + { + return m_round_number.get_proxy(); + } + + /** Modifiers */ + + auto turn_order::add(Glib::ustring const & name, float priority, disposition disposition) -> void + { + auto entry = participant::create(name, priority, disposition); + entry->priority().signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &turn_order::handle_priority_changed), entry)); + auto position = std::distance(m_data.cbegin(), insert(entry)); + items_changed(position, 0, 1); + + if (get_n_items() == 1) + { + m_is_empty = false; + m_has_next = true; + } + } + + auto turn_order::clear() -> void + { + m_is_running = false; + m_is_empty = true; + m_has_next = false; + m_has_previous = false; + m_active.reset(); + m_round_number = invalid_round_number; + + auto old_size = get_n_items(); + m_data.clear(); + items_changed(0, old_size, 0); + } + + auto turn_order::next() -> void + { + auto old_active = *m_active; + m_active = m_active.transform([this](auto index) { return (index + 1) % get_n_items(); }); + + m_has_previous = true; + m_data[old_active]->is_active() = false; + m_data[*m_active]->is_active() = true; + + if (m_active == 0) + { + m_round_number = m_round_number + 1; + } + } + + auto turn_order::previous() -> void + { + if (!(m_has_previous && m_is_running)) + { + return; + } + + auto old_active = *m_active; + m_active = m_active.transform([this](auto index) { return index ? index - 1 : get_n_items() - 1; }); + + m_has_previous = m_round_number > 0 || m_active > 0; + m_data[old_active]->is_active() = false; + m_data[*m_active]->is_active() = true; + + if (m_active == 0 && m_round_number > 0) + { + m_round_number = m_round_number - 1; + } + } + + auto turn_order::remove(unsigned index) -> void + { + if (index >= get_n_items()) + { + return; + } + + auto position = m_data.begin() + index; + m_data.erase(position); + items_changed(index, 1, 0); + if (get_n_items() == 0) + { + m_is_empty = true; + m_is_running = false; + m_has_next = false; + } + } + + auto turn_order::start() -> void + { + if (!m_active) + { + m_active = 0; + m_data[*m_active]->is_active() = true; + } + if (m_round_number == invalid_round_number) + { + m_round_number = 0; + } + m_is_running = true; + } + + auto turn_order::stop() -> void + { + m_is_running = false; + } + + /** ListModel implementation */ + + auto turn_order::get_item_type_vfunc() -> GType + { + return participant::get_type(); + } + + auto turn_order::get_n_items_vfunc() -> unsigned + { + return m_data.size(); + } + + auto turn_order::get_item_vfunc(unsigned position) -> void * + { + if (position >= get_n_items()) + { + return nullptr; + } + auto item = m_data[position]; + item->reference(); + return item->gobj(); + } + + /** Signal handlers */ + + auto turn_order::handle_priority_changed(value_type entry) -> void + { + auto original_position = find(entry); + auto original_index = distance(m_data.cbegin(), original_position); + auto target_position = std::ranges::upper_bound(m_data, entry, comparator); + if (original_position == target_position) + { + return; + } + + m_data.erase(original_position); + auto inserted_position = insert(entry); + items_changed(0, get_n_items(), get_n_items()); + if (m_active == original_index) + { + m_active = distance(m_data.cbegin(), inserted_position); + m_has_previous = m_round_number > 0 || m_active > 0; + } + } + + /** Data management */ + + auto turn_order::find(value_type entry) const -> const_iterator + { + return std::ranges::find(m_data, entry); + } + + auto turn_order::insert(value_type entry) -> const_iterator + { + return m_data.insert(std::ranges::upper_bound(m_data, entry, comparator), entry); + } + +} // namespace turns::core \ No newline at end of file diff --git a/core/tests/disposition.cpp b/core/tests/disposition.cpp new file mode 100644 index 0000000..3a35741 --- /dev/null +++ b/core/tests/disposition.cpp @@ -0,0 +1,32 @@ +#include "turns/core/disposition.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace turns::core::tests +{ + + TEST_CASE("to_presentation_name returns the correct string for the current language", "[disposition]") + { + auto [value, name] = GENERATE(std::pair{disposition::neutral, Glib::ustring{_("Neutral")}}, + std::pair{disposition::friendly, Glib::ustring{_("Friendly")}}, + std::pair{disposition::hostile, Glib::ustring{_("Hostile")}}, + std::pair{disposition::secret, Glib::ustring{_("Secret")}}, + std::pair{static_cast(std::numeric_limits>::max()), + Glib::ustring{_("Unknown disposition value")}}); + + SECTION(std::format("the presentation name for '{}' is '{}'", static_cast>(value), name.c_str())) + { + REQUIRE(presentation_name_for(value) == name); + } + } + +} // namespace turns::core::tests \ No newline at end of file diff --git a/core/tests/participant.cpp b/core/tests/participant.cpp new file mode 100644 index 0000000..14fb1ae --- /dev/null +++ b/core/tests/participant.cpp @@ -0,0 +1,113 @@ +#include "turns/core/participant.hpp" +#include "turns/core/disposition.hpp" + +#include + +#include + +#include + +namespace turns::core::tests +{ + + TEST_CASE("A freshly constructed participant") + { + auto constexpr constructed_name = "Vana Thistletop"; + auto constexpr constructed_priority = 17; + auto constexpr constructed_disposition = disposition::friendly; + auto instance = participant{constructed_name, constructed_priority, constructed_disposition}; + + SECTION("can be created") + { + REQUIRE(participant::create(constructed_name, constructed_priority, constructed_disposition)); + } + + SECTION("allows access to its disposition") + { + SECTION("allowing to get it") + { + REQUIRE(instance.disposition() == constructed_disposition); + } + + SECTION("allowing to get it via a constant object") + { + auto const & cref = instance; + REQUIRE(cref.disposition() == constructed_disposition); + } + + SECTION("allowing to set it") + { + instance.disposition() = disposition::hostile; + REQUIRE(instance.disposition() == disposition::hostile); + } + } + + SECTION("allows access to its name") + { + SECTION("allowing to get it") + { + REQUIRE(instance.name() == constructed_name); + } + + SECTION("allowing to get it via a constant object") + { + auto const & cref = instance; + REQUIRE(cref.name() == constructed_name); + } + + SECTION("allowing to set it") + { + instance.name() = "replaced"; + REQUIRE(instance.name() == "replaced"); + } + } + + SECTION("allows access to its priority") + { + SECTION("allowing to get it") + { + REQUIRE(instance.priority() == constructed_priority); + } + + SECTION("allowing to get it via a constant object") + { + auto const & cref = instance; + REQUIRE(cref.priority() == constructed_priority); + } + + SECTION("allowing to set it") + { + instance.priority() = 4; + REQUIRE(instance.priority() == 4); + } + } + + SECTION("can be compared with another participant") + { + auto equivalent_instance = participant{"Equivalent", constructed_priority, constructed_disposition}; + auto lesser_instance = participant{"Lesser", constructed_priority - 1, constructed_disposition}; + auto greater_instance = participant{"Greater", constructed_priority + 1, constructed_disposition}; + + SECTION("yielding std::partial_ordering::equivalent for itself") + { + REQUIRE((instance <=> equivalent_instance) == std::partial_ordering::equivalent); + } + + SECTION("yielding std::partial_ordering::equivalent for an equivalent participant") + { + REQUIRE((instance <=> equivalent_instance) == std::partial_ordering::equivalent); + } + + SECTION("yielding std::partial_ordering::greater for a lesser participant") + { + REQUIRE((instance <=> lesser_instance) == std::partial_ordering::greater); + } + + SECTION("yielding std::partial_ordering::less for a greater participant") + { + REQUIRE((instance <=> greater_instance) == std::partial_ordering::less); + } + } + } + +} // namespace turns::core::tests \ No newline at end of file diff --git a/core/tests/register_types.cpp b/core/tests/register_types.cpp new file mode 100644 index 0000000..2ad0628 --- /dev/null +++ b/core/tests/register_types.cpp @@ -0,0 +1,13 @@ +#include "turns/core/participant.hpp" +#include "turns/core/turn_order.hpp" + +namespace turns::tests +{ + + auto register_types() -> void + { + static_cast(core::participant{}); + static_cast(core::turn_order{}); + } + +} // namespace turns::tests \ No newline at end of file diff --git a/core/tests/turn_order.cpp b/core/tests/turn_order.cpp new file mode 100644 index 0000000..fc779d7 --- /dev/null +++ b/core/tests/turn_order.cpp @@ -0,0 +1,225 @@ +#include "turns/core/turn_order.hpp" + +#include "turns/core/participant.hpp" + +#include + +#include + +namespace turns::core::tests +{ + SCENARIO("Queries on a fresh turn_order instance", "[turn_order]") + { + GIVEN("an empty turn_order") + { + auto instance = turn_order::create(); + + THEN("get_n_items() returns 0") + { + auto str = Gio::ListStore::create(); + REQUIRE(instance->get_n_items() == str->get_n_items()); + } + + THEN("get_type() returns participant::get_type()") + { + REQUIRE(instance->get_item_type() == participant::get_type()); + } + + THEN("get_typed_object(0) returns nullptr") + { + REQUIRE(instance->get_typed_object(0) == nullptr); + } + + THEN("has_next() returns false") + { + REQUIRE_FALSE(instance->has_next()); + } + + THEN("has_previous() returns false") + { + REQUIRE_FALSE(instance->has_previous()); + } + + THEN("is_empty() returns true") + { + REQUIRE(instance->is_empty()); + } + + THEN("is_running() returns false") + { + REQUIRE_FALSE(instance->is_running()); + } + + THEN("round_number() returns invalid_round_number") + { + REQUIRE(instance->round_number() == turn_order::invalid_round_number); + } + } + } + + SCENARIO("Adding participants") + { + auto instance = turn_order::create(); + + GIVEN("a participant has been added to a turn_order") + { + instance->add("Participant #0", 0, disposition::neutral); + + THEN("get_n_items() returns 1") + { + REQUIRE(instance->get_n_items() == 1); + } + + THEN("get_typed_object(0) returns a non-null pointer") + { + REQUIRE(instance->get_typed_object(0) != nullptr); + } + + THEN("has_next() returns true") + { + REQUIRE(instance->has_next()); + } + + THEN("has_previous() returns false") + { + REQUIRE_FALSE(instance->has_previous()); + } + + THEN("is_empty() returns false") + { + REQUIRE_FALSE(instance->is_empty()); + } + + THEN("is_running() returns false") + { + REQUIRE_FALSE(instance->is_running()); + } + + THEN("round_number() returns invalid_round_number") + { + REQUIRE(instance->round_number() == turn_order::invalid_round_number); + } + + WHEN("the turn_order is start()ed") + { + instance->start(); + + THEN("get_n_items() still returns 1") + { + REQUIRE(instance->get_n_items() == 1); + } + + THEN("get_typed_object(0) still returns a non-null pointer") + { + REQUIRE(instance->get_typed_object(0) != nullptr); + } + + THEN("has_next() still returns true") + { + REQUIRE(instance->has_next()); + } + + THEN("has_previous() still returns false") + { + REQUIRE_FALSE(instance->has_previous()); + } + + THEN("is_empty() still returns false") + { + REQUIRE_FALSE(instance->is_empty()); + } + + THEN("is_running() returns true") + { + REQUIRE(instance->is_running()); + } + + THEN("round_number() returns 0") + { + REQUIRE(instance->round_number() == 0); + } + + AND_WHEN("invoking previous()") + { + instance->previous(); + + THEN("get_n_items() still returns 1") + { + REQUIRE(instance->get_n_items() == 1); + } + + THEN("get_typed_object(0) still returns a non-null pointer") + { + REQUIRE(instance->get_typed_object(0) != nullptr); + } + + THEN("has_next() still returns true") + { + REQUIRE(instance->has_next()); + } + + THEN("has_previous() still returns false") + { + REQUIRE_FALSE(instance->has_previous()); + } + + THEN("is_empty() still returns false") + { + REQUIRE_FALSE(instance->is_empty()); + } + + THEN("is_running() returns true") + { + REQUIRE(instance->is_running()); + } + + THEN("round_number() returns 0") + { + REQUIRE(instance->round_number() == 0); + } + } + + AND_WHEN("invoking next()") + { + instance->next(); + + THEN("get_n_items() still returns 1") + { + REQUIRE(instance->get_n_items() == 1); + } + + THEN("get_typed_object(0) still returns a non-null pointer") + { + REQUIRE(instance->get_typed_object(0) != nullptr); + } + + THEN("has_next() still returns true") + { + REQUIRE(instance->has_next()); + } + + THEN("has_previous() returns true") + { + REQUIRE(instance->has_previous()); + } + + THEN("is_empty() still returns false") + { + REQUIRE_FALSE(instance->is_empty()); + } + + THEN("is_running() returns true") + { + REQUIRE(instance->is_running()); + } + + THEN("round_number() returns 1") + { + REQUIRE(instance->round_number() == 1); + } + } + } + } + } + +} // namespace turns::core::tests \ No newline at end of file diff --git a/core/tests/turn_order_bugs.cpp b/core/tests/turn_order_bugs.cpp new file mode 100644 index 0000000..0fa0720 --- /dev/null +++ b/core/tests/turn_order_bugs.cpp @@ -0,0 +1,42 @@ +#include "turns/core/participant.hpp" +#include "turns/core/turn_order.hpp" + +#include + +#include + +namespace turns::core::tests +{ + /** + * Bug description: + * + * After having stepped according to the step pattern below, tt was possible to step backward often enough to underflow the round number: + * - forward + * - backward + * - forward + */ + SCENARIO("Can step back infinitely", "[turn_order][bug]") + { + GIVEN("a non-empty turn_order") + { + auto instance = turn_order::create(); + + instance->add("A", 0, disposition::neutral); + + WHEN("it is started and then stepped forward, backward, forward") + { + instance->start(); + instance->next(); + instance->previous(); + instance->next(); + + THEN("it is not possible to step backwards more than once") + { + instance->previous(); + instance->previous(); + REQUIRE(instance->round_number() == 0); + } + } + } + } +} // namespace turns::core::tests \ No newline at end of file diff --git a/domain/CMakeLists.txt b/domain/CMakeLists.txt deleted file mode 100644 index 6c54e31..0000000 --- a/domain/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Library - -add_library("domain" - "src/disposition.cpp" - "src/participant.cpp" - "src/turn_order.cpp" -) - -add_library("turns::domain" ALIAS "domain") - - -target_compile_options("domain" PUBLIC - "$<$:-Wall>" - "$<$:-Wextra>" - "$<$:-Werror>" - "$<$:-pedantic-errors>" - PRIVATE - "$<$,$>:-fprofile-arcs>" - "$<$,$>:-ftest-coverage>" -) - -target_include_directories("domain" PUBLIC - "include" -) - -target_link_libraries("domain" PUBLIC - "$<$,$>:gcov>" - - "PkgConfig::giomm" - "PkgConfig::glibmm" -) - -target_link_options("domain" PRIVATE - "$<$,$>:--coverage>" -) - -# Tests - -add_executable("domain-tests" - "tests/register_types.cpp" - - "tests/disposition.cpp" - "tests/participant.cpp" - "tests/turn_order_bugs.cpp" - "tests/turn_order.cpp" -) - -target_link_libraries("domain-tests" - "Catch2::Catch2" - - "turns::domain" - "turns::glib-test-main" -) - -catch_discover_tests("domain-tests") \ No newline at end of file diff --git a/domain/include/turns/domain/disposition.hpp b/domain/include/turns/domain/disposition.hpp deleted file mode 100644 index d9b8b5b..0000000 --- a/domain/include/turns/domain/disposition.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef TURNS_DOMAIN_DISPOSITION_HPP -#define TURNS_DOMAIN_DISPOSITION_HPP - -#include -#include - -#include - -namespace turns::domain -{ - - enum struct disposition : std::uint8_t - { - neutral, - friendly, - hostile, - secret, - - ///! End marker - END - }; - - auto presentation_name_for(disposition value) -> Glib::ustring; - -} // namespace turns::domain - -#endif \ No newline at end of file diff --git a/domain/include/turns/domain/participant.hpp b/domain/include/turns/domain/participant.hpp deleted file mode 100644 index b51425d..0000000 --- a/domain/include/turns/domain/participant.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef TURNS_DOMAIN_PARTICIPANT_HPP -#define TURNS_DOMAIN_PARTICIPANT_HPP - -#include "turns/domain/disposition.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace turns::domain -{ - struct participant : Glib::Object - { - auto static create(Glib::ustring name, float priority, disposition disposition) -> Glib::RefPtr; - - participant(); - participant(Glib::ustring name, float priority, disposition disposition); - - auto operator<=>(participant const & other) const noexcept -> std::partial_ordering; - - template - auto disposition(this Self && self) - { - return self.m_disposition.get_proxy(); - } - - template - auto is_active(this Self && self) - { - return self.m_is_active.get_proxy(); - } - - template - auto name(this Self && self) - { - return self.m_name.get_proxy(); - } - - template - auto priority(this Self && self) - { - return self.m_priority.get_proxy(); - } - - private: - Glib::Property m_disposition{*this, "disposition", domain::disposition::neutral}; - Glib::Property m_is_active{*this, "active", false}; - Glib::Property m_name{*this, "name", ""}; - Glib::Property m_priority{*this, "priority", 0.0f}; - }; - -} // namespace turns::domain - -#endif \ No newline at end of file diff --git a/domain/include/turns/domain/turn_order.hpp b/domain/include/turns/domain/turn_order.hpp deleted file mode 100644 index ca44b62..0000000 --- a/domain/include/turns/domain/turn_order.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef TURNS_DOMAIN_TURN_ORDER_HPP -#define TURNS_DOMAIN_TURN_ORDER_HPP - -#include "turns/domain/disposition.hpp" -#include "turns/domain/participant.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace turns::domain -{ - - struct turn_order : Gio::ListModel, - Glib::Object - { - using value_type = Glib::RefPtr; - using container_type = std::vector; - using iterator = container_type::iterator; - using const_iterator = container_type::const_iterator; - - using active_participant_type = unsigned int; - using is_empty_type = bool; - using has_next_type = bool; - using has_previous_type = bool; - using is_running_type = bool; - using round_number_type = unsigned int; - - auto static constexpr invalid_participant_index = std::numeric_limits::max(); - auto static constexpr invalid_round_number = std::numeric_limits::max(); - - /** Life-time */ - turn_order(); - - auto static create() -> Glib::RefPtr; - - /** Properties */ - auto is_empty() const -> Glib::PropertyProxy_ReadOnly; - auto has_next() const -> Glib::PropertyProxy_ReadOnly; - auto has_previous() const -> Glib::PropertyProxy_ReadOnly; - auto is_running() const -> Glib::PropertyProxy_ReadOnly; - auto round_number() const -> Glib::PropertyProxy_ReadOnly; - - /** Element Modifications */ - auto add(Glib::ustring const & name, float priority, disposition disposition) -> void; - auto clear() -> void; - auto remove(unsigned index) -> void; - - /** Turn Modification */ - auto next() -> void; - auto previous() -> void; - auto start() -> void; - auto stop() -> void; - - private: - auto get_item_type_vfunc() -> GType override; - auto get_n_items_vfunc() -> unsigned override; - auto get_item_vfunc(unsigned position) -> void * override; - - /** Signal handlers */ - auto handle_priority_changed(value_type entry) -> void; - - /** Data management */ - auto find(value_type entry) const -> const_iterator; - auto insert(value_type entry) -> const_iterator; - - container_type m_data{}; - std::optional m_active{}; - - Glib::Property m_has_next{*this, "has-next", false}; - Glib::Property m_has_previous{*this, "has-previous", false}; - Glib::Property m_is_empty{*this, "is-empty", true}; - Glib::Property m_is_running{*this, "is-running", false}; - Glib::Property m_round_number{*this, "round-number", invalid_round_number}; - }; - -} // namespace turns::domain - -#endif \ No newline at end of file diff --git a/domain/src/disposition.cpp b/domain/src/disposition.cpp deleted file mode 100644 index 077a312..0000000 --- a/domain/src/disposition.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "turns/domain/disposition.hpp" - -#include - -namespace turns::domain -{ - - auto presentation_name_for(disposition value) -> Glib::ustring - { - switch (value) - { - case disposition::neutral: - return _("Neutral"); - case disposition::friendly: - return _("Friendly"); - case disposition::hostile: - return _("Hostile"); - case disposition::secret: - return _("Secret"); - default: - return _("Unknown disposition value"); - } - } - -} // namespace turns::domain \ No newline at end of file diff --git a/domain/src/participant.cpp b/domain/src/participant.cpp deleted file mode 100644 index 6f0efb1..0000000 --- a/domain/src/participant.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "turns/domain/participant.hpp" - -#include -#include - -#include -#include - -namespace turns::domain -{ - auto participant::create(Glib::ustring name, float priority, domain::disposition disposition) -> Glib::RefPtr - { - return Glib::make_refptr_for_instance(new participant{name, priority, disposition}); - } - - participant::participant() - : Glib::ObjectBase{typeid(participant)} - , Glib::Object{} - { - } - - participant::participant(Glib::ustring name, float priority, domain::disposition disposition) - : participant() - { - m_name = name; - m_priority = priority; - m_disposition = disposition; - } - - auto participant::operator<=>(participant const & other) const noexcept -> std::partial_ordering - { - return m_priority <=> other.m_priority; - } - -} // namespace turns::domain \ No newline at end of file diff --git a/domain/src/turn_order.cpp b/domain/src/turn_order.cpp deleted file mode 100644 index d3140b1..0000000 --- a/domain/src/turn_order.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "turns/domain/turn_order.hpp" - -#include "turns/domain/participant.hpp" - -#include -#include -#include -#include - -#include - -namespace turns::domain -{ - - namespace - { - auto constexpr comparator = [](auto lhs, auto rhs) { - return *lhs > *rhs; - }; - - auto constexpr equal_comparator = [](auto lhs, auto rhs) { - return (lhs->get_name() == rhs->get_name()) && (lhs->get_priority() && rhs->get_priority()); - }; - } // namespace - - /** Construction */ - - turn_order::turn_order() - : Glib::ObjectBase{typeid(turn_order)} - , Gio::ListModel{} - { - } - - auto turn_order::create() -> Glib::RefPtr - { - return Glib::make_refptr_for_instance(new turn_order{}); - } - - /** Queries */ - - auto turn_order::is_empty() const -> Glib::PropertyProxy_ReadOnly - { - return m_is_empty.get_proxy(); - } - - auto turn_order::has_next() const -> Glib::PropertyProxy_ReadOnly - { - return m_has_next.get_proxy(); - } - - auto turn_order::has_previous() const -> Glib::PropertyProxy_ReadOnly - { - return m_has_previous.get_proxy(); - } - - auto turn_order::is_running() const -> Glib::PropertyProxy_ReadOnly - { - return m_is_running.get_proxy(); - } - - auto turn_order::round_number() const -> Glib::PropertyProxy_ReadOnly - { - return m_round_number.get_proxy(); - } - - /** Modifiers */ - - auto turn_order::add(Glib::ustring const & name, float priority, disposition disposition) -> void - { - auto entry = participant::create(name, priority, disposition); - entry->priority().signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &turn_order::handle_priority_changed), entry)); - auto position = std::distance(m_data.cbegin(), insert(entry)); - items_changed(position, 0, 1); - - if (get_n_items() == 1) - { - m_is_empty = false; - m_has_next = true; - } - } - - auto turn_order::clear() -> void - { - m_is_running = false; - m_is_empty = true; - m_has_next = false; - m_has_previous = false; - m_active.reset(); - m_round_number = invalid_round_number; - - auto old_size = get_n_items(); - m_data.clear(); - items_changed(0, old_size, 0); - } - - auto turn_order::next() -> void - { - auto old_active = *m_active; - m_active = m_active.transform([this](auto index) { return (index + 1) % get_n_items(); }); - - m_has_previous = true; - m_data[old_active]->is_active() = false; - m_data[*m_active]->is_active() = true; - - if (m_active == 0) - { - m_round_number = m_round_number + 1; - } - } - - auto turn_order::previous() -> void - { - if (!(m_has_previous && m_is_running)) - { - return; - } - - auto old_active = *m_active; - m_active = m_active.transform([this](auto index) { return index ? index - 1 : get_n_items() - 1; }); - - m_has_previous = m_round_number > 0 || m_active > 0; - m_data[old_active]->is_active() = false; - m_data[*m_active]->is_active() = true; - - if (m_active == 0 && m_round_number > 0) - { - m_round_number = m_round_number - 1; - } - } - - auto turn_order::remove(unsigned index) -> void - { - if (index >= get_n_items()) - { - return; - } - - auto position = m_data.begin() + index; - m_data.erase(position); - items_changed(index, 1, 0); - if (get_n_items() == 0) - { - m_is_empty = true; - m_is_running = false; - m_has_next = false; - } - } - - auto turn_order::start() -> void - { - if (!m_active) - { - m_active = 0; - m_data[*m_active]->is_active() = true; - } - if (m_round_number == invalid_round_number) - { - m_round_number = 0; - } - m_is_running = true; - } - - auto turn_order::stop() -> void - { - m_is_running = false; - } - - /** ListModel implementation */ - - auto turn_order::get_item_type_vfunc() -> GType - { - return participant::get_type(); - } - - auto turn_order::get_n_items_vfunc() -> unsigned - { - return m_data.size(); - } - - auto turn_order::get_item_vfunc(unsigned position) -> void * - { - if (position >= get_n_items()) - { - return nullptr; - } - auto item = m_data[position]; - item->reference(); - return item->gobj(); - } - - /** Signal handlers */ - - auto turn_order::handle_priority_changed(value_type entry) -> void - { - auto original_position = find(entry); - auto original_index = distance(m_data.cbegin(), original_position); - auto target_position = std::ranges::upper_bound(m_data, entry, comparator); - if (original_position == target_position) - { - return; - } - - m_data.erase(original_position); - auto inserted_position = insert(entry); - items_changed(0, get_n_items(), get_n_items()); - if (m_active == original_index) - { - m_active = distance(m_data.cbegin(), inserted_position); - m_has_previous = m_round_number > 0 || m_active > 0; - } - } - - /** Data management */ - - auto turn_order::find(value_type entry) const -> const_iterator - { - return std::ranges::find(m_data, entry); - } - - auto turn_order::insert(value_type entry) -> const_iterator - { - return m_data.insert(std::ranges::upper_bound(m_data, entry, comparator), entry); - } - -} // namespace turns::domain \ No newline at end of file diff --git a/domain/tests/disposition.cpp b/domain/tests/disposition.cpp deleted file mode 100644 index 0d91867..0000000 --- a/domain/tests/disposition.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "turns/domain/disposition.hpp" - -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace turns::domain::tests -{ - - TEST_CASE("to_presentation_name returns the correct string for the current language", "[disposition]") - { - auto [value, name] = GENERATE(std::pair{disposition::neutral, Glib::ustring{_("Neutral")}}, - std::pair{disposition::friendly, Glib::ustring{_("Friendly")}}, - std::pair{disposition::hostile, Glib::ustring{_("Hostile")}}, - std::pair{disposition::secret, Glib::ustring{_("Secret")}}, - std::pair{static_cast(std::numeric_limits>::max()), - Glib::ustring{_("Unknown disposition value")}}); - - SECTION(std::format("the presentation name for '{}' is '{}'", static_cast>(value), name.c_str())) - { - REQUIRE(presentation_name_for(value) == name); - } - } - -} // namespace turns::domain::tests \ No newline at end of file diff --git a/domain/tests/participant.cpp b/domain/tests/participant.cpp deleted file mode 100644 index e4e185c..0000000 --- a/domain/tests/participant.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "turns/domain/participant.hpp" -#include "turns/domain/disposition.hpp" - -#include - -#include - -#include - -namespace turns::domain::tests -{ - - TEST_CASE("A freshly constructed participant") - { - auto constexpr constructed_name = "Vana Thistletop"; - auto constexpr constructed_priority = 17; - auto constexpr constructed_disposition = disposition::friendly; - auto instance = participant{constructed_name, constructed_priority, constructed_disposition}; - - SECTION("can be created") - { - REQUIRE(participant::create(constructed_name, constructed_priority, constructed_disposition)); - } - - SECTION("allows access to its disposition") - { - SECTION("allowing to get it") - { - REQUIRE(instance.disposition() == constructed_disposition); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.disposition() == constructed_disposition); - } - - SECTION("allowing to set it") - { - instance.disposition() = disposition::hostile; - REQUIRE(instance.disposition() == disposition::hostile); - } - } - - SECTION("allows access to its name") - { - SECTION("allowing to get it") - { - REQUIRE(instance.name() == constructed_name); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.name() == constructed_name); - } - - SECTION("allowing to set it") - { - instance.name() = "replaced"; - REQUIRE(instance.name() == "replaced"); - } - } - - SECTION("allows access to its priority") - { - SECTION("allowing to get it") - { - REQUIRE(instance.priority() == constructed_priority); - } - - SECTION("allowing to get it via a constant object") - { - auto const & cref = instance; - REQUIRE(cref.priority() == constructed_priority); - } - - SECTION("allowing to set it") - { -