From a4460f6d9453bbd7e584937686449cef3e19f052 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Mon, 20 Aug 2018 20:34:57 -0400 Subject: Initial commit --- src/graphcoloring/graphcoloring.cpp | 146 + src/graphcoloring/graphcoloring.hpp | 65 + src/graphcoloring/graphs/colormenu.cpp | 120 + src/graphcoloring/graphs/colormenu.hpp | 58 + src/graphcoloring/graphs/edge.cpp | 191 + src/graphcoloring/graphs/edge.hpp | 71 + src/graphcoloring/graphs/graph.cpp | 419 + src/graphcoloring/graphs/graph.hpp | 88 + src/graphcoloring/graphs/vertex.cpp | 204 + src/graphcoloring/graphs/vertex.hpp | 76 + src/graphcoloring/level.cpp | 350 + src/graphcoloring/level.hpp | 91 + src/graphcoloring/levels/colorloader.cpp | 142 + src/graphcoloring/levels/colorloader.hpp | 54 + src/graphcoloring/levels/globalloader.cpp | 45 + src/graphcoloring/levels/globalloader.hpp | 40 + src/graphcoloring/levels/graphloader.cpp | 178 + src/graphcoloring/levels/graphloader.hpp | 57 + src/graphcoloring/levels/paths/path.cpp | 207 + src/graphcoloring/levels/paths/path.hpp | 70 + src/graphcoloring/levels/pointcalculator.cpp | 48 + src/graphcoloring/levels/pointcalculator.hpp | 45 + src/graphcoloring/levels/rules/boundrule.cpp | 135 + src/graphcoloring/levels/rules/boundrule.hpp | 55 + src/graphcoloring/levels/rules/edgerule.cpp | 82 + src/graphcoloring/levels/rules/edgerule.hpp | 51 + src/graphcoloring/levels/rules/rule.hpp | 37 + src/graphcoloring/levels/rules/ruleloader.cpp | 92 + src/graphcoloring/levels/rules/ruleloader.hpp | 53 + src/graphcoloring/levels/rules/rules.cpp | 72 + src/graphcoloring/levels/rules/rules.hpp | 42 + src/graphcoloring/levels/value.cpp | 304 + src/graphcoloring/levels/value.hpp | 80 + src/graphcoloring/levels/valueloader.cpp | 96 + src/graphcoloring/levels/valueloader.hpp | 46 + src/graphcoloring/levelselect.cpp | 167 + src/graphcoloring/levelselect.hpp | 64 + src/graphcoloring/mainmenu.cpp | 72 + src/graphcoloring/mainmenu.hpp | 47 + src/gui/button.cpp | 191 + src/gui/button.hpp | 83 + src/gui/colors.cpp | 101 + src/gui/colors.hpp | 55 + src/gui/gui.hpp | 28 + src/gui/menu.cpp | 74 + src/gui/menu.hpp | 53 + src/gui/position.cpp | 107 + src/gui/position.hpp | 73 + src/gui/window.hpp | 181 + src/gui/window_events.cpp | 112 + src/gui/window_events_keyboard.cpp | 107 + src/gui/window_events_keyboard.hpp | 32 + src/gui/window_events_mouse.cpp | 166 + src/gui/window_events_mouse.hpp | 37 + src/gui/window_main.cpp | 102 + src/gui/window_rendering.cpp | 282 + src/main.cpp | 39 + src/pugi/pugiconfig.hpp | 74 + src/pugi/pugixml.cpp | 12796 ++++++++++++++++++++++++ src/pugi/pugixml.hpp | 1461 +++ src/utils/errors.cpp | 33 + src/utils/errors.hpp | 34 + src/utils/filesystem.cpp | 72 + src/utils/filesystem.hpp | 34 + src/utils/geometry.cpp | 78 + src/utils/geometry.hpp | 34 + src/utils/windows.hpp | 27 + 67 files changed, 20726 insertions(+) create mode 100644 src/graphcoloring/graphcoloring.cpp create mode 100644 src/graphcoloring/graphcoloring.hpp create mode 100644 src/graphcoloring/graphs/colormenu.cpp create mode 100644 src/graphcoloring/graphs/colormenu.hpp create mode 100644 src/graphcoloring/graphs/edge.cpp create mode 100644 src/graphcoloring/graphs/edge.hpp create mode 100644 src/graphcoloring/graphs/graph.cpp create mode 100644 src/graphcoloring/graphs/graph.hpp create mode 100644 src/graphcoloring/graphs/vertex.cpp create mode 100644 src/graphcoloring/graphs/vertex.hpp create mode 100644 src/graphcoloring/level.cpp create mode 100644 src/graphcoloring/level.hpp create mode 100644 src/graphcoloring/levels/colorloader.cpp create mode 100644 src/graphcoloring/levels/colorloader.hpp create mode 100644 src/graphcoloring/levels/globalloader.cpp create mode 100644 src/graphcoloring/levels/globalloader.hpp create mode 100644 src/graphcoloring/levels/graphloader.cpp create mode 100644 src/graphcoloring/levels/graphloader.hpp create mode 100644 src/graphcoloring/levels/paths/path.cpp create mode 100644 src/graphcoloring/levels/paths/path.hpp create mode 100644 src/graphcoloring/levels/pointcalculator.cpp create mode 100644 src/graphcoloring/levels/pointcalculator.hpp create mode 100644 src/graphcoloring/levels/rules/boundrule.cpp create mode 100644 src/graphcoloring/levels/rules/boundrule.hpp create mode 100644 src/graphcoloring/levels/rules/edgerule.cpp create mode 100644 src/graphcoloring/levels/rules/edgerule.hpp create mode 100644 src/graphcoloring/levels/rules/rule.hpp create mode 100644 src/graphcoloring/levels/rules/ruleloader.cpp create mode 100644 src/graphcoloring/levels/rules/ruleloader.hpp create mode 100644 src/graphcoloring/levels/rules/rules.cpp create mode 100644 src/graphcoloring/levels/rules/rules.hpp create mode 100644 src/graphcoloring/levels/value.cpp create mode 100644 src/graphcoloring/levels/value.hpp create mode 100644 src/graphcoloring/levels/valueloader.cpp create mode 100644 src/graphcoloring/levels/valueloader.hpp create mode 100644 src/graphcoloring/levelselect.cpp create mode 100644 src/graphcoloring/levelselect.hpp create mode 100644 src/graphcoloring/mainmenu.cpp create mode 100644 src/graphcoloring/mainmenu.hpp create mode 100644 src/gui/button.cpp create mode 100644 src/gui/button.hpp create mode 100644 src/gui/colors.cpp create mode 100644 src/gui/colors.hpp create mode 100644 src/gui/gui.hpp create mode 100644 src/gui/menu.cpp create mode 100644 src/gui/menu.hpp create mode 100644 src/gui/position.cpp create mode 100644 src/gui/position.hpp create mode 100644 src/gui/window.hpp create mode 100644 src/gui/window_events.cpp create mode 100644 src/gui/window_events_keyboard.cpp create mode 100644 src/gui/window_events_keyboard.hpp create mode 100644 src/gui/window_events_mouse.cpp create mode 100644 src/gui/window_events_mouse.hpp create mode 100644 src/gui/window_main.cpp create mode 100644 src/gui/window_rendering.cpp create mode 100644 src/main.cpp create mode 100644 src/pugi/pugiconfig.hpp create mode 100644 src/pugi/pugixml.cpp create mode 100644 src/pugi/pugixml.hpp create mode 100644 src/utils/errors.cpp create mode 100644 src/utils/errors.hpp create mode 100644 src/utils/filesystem.cpp create mode 100644 src/utils/filesystem.hpp create mode 100644 src/utils/geometry.cpp create mode 100644 src/utils/geometry.hpp create mode 100644 src/utils/windows.hpp (limited to 'src') diff --git a/src/graphcoloring/graphcoloring.cpp b/src/graphcoloring/graphcoloring.cpp new file mode 100644 index 0000000..246bfd0 --- /dev/null +++ b/src/graphcoloring/graphcoloring.cpp @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "graphcoloring.hpp" + +#include "utils/windows.hpp" +#include "utils/filesystem.hpp" + +#include + +namespace graphcoloring { + +namespace filesystem = utils::filesystem; + +const std::string GraphColoring::TITLE = "Graph Coloring"; + +GraphColoring::GraphColoring() + : state(State::MAIN_MENU) +{ + window = new gui::Window("Graph Coloring", 800, 600); + CheckSaves(); + window->OnActivate([this] (){ SwitchToMainMenu(); }); +} + +GraphColoring::~GraphColoring() +{ + DeleteAll(); + delete window; +} + +void GraphColoring::CheckSaves() +{ + + if (filesystem::directory_exists("saves")) + { + // Check for version mismatch + pugi::xml_document saves_document; + saves_document.load_file(LevelSelect::LEVEL_LISTING_PATH); + pugi::xml_document assets_document; + assets_document.load_file("assets/levels/level-list.xml"); + std::string saves_version( + saves_document.child("category-listing") + .attribute("version").value()); + std::string assets_version( + assets_document.child("category-listing") + .attribute("version").value()); + + if (saves_version != assets_version) + { + // Copy new level list. + filesystem::remove_file(LevelSelect::LEVEL_LISTING_PATH); + filesystem::copy_file( + "assets/levels/level-list.xml", + LevelSelect::LEVEL_LISTING_PATH); + } + return; + } + + // Create saves directory + filesystem::create_directory("saves"); + filesystem::copy_file( + "assets/levels/level-list.xml", LevelSelect::LEVEL_LISTING_PATH); + + pugi::xml_document document; + document.load_file(LevelSelect::LEVEL_LISTING_PATH); + for (pugi::xml_node category + : document.child("category-listing").children("category")) + { + std::stringstream filename; + filename << "saves/" << category.attribute("id").value(); + filesystem::create_directory(filename.str()); + } +} + +void GraphColoring::DeleteAll() +{ + mainMenu = nullptr; + levelSelect = nullptr; + level = nullptr; + window->RemoveAllCallbacks(); +} + +void GraphColoring::SwitchToMainMenu() +{ + DeleteAll(); + mainMenu = std::make_unique(window, + [this] (){ SwitchToLevelSelect(); }); + + window->SetKeyupCallback([this] (gui::Window*){ // Press Escape to quit + window->Quit(); + }, GDK_KEY_Escape); +} + +void GraphColoring::SwitchToLevelSelect() +{ + DeleteAll(); + auto callback = [this](std::string category_id,std::string level_id) { + SwitchToLevel(category_id, level_id); + }; + levelSelect = std::make_unique(window, callback); + window->SetKeyupCallback([this] (gui::Window*){ // Press Escape to go back to menu + SwitchToMainMenu(); + }, GDK_KEY_Escape); +} + +void GraphColoring::SwitchToLevel(std::string category_id, std::string level_id) +{ + DeleteAll(); + level = std::make_unique(window, category_id, level_id); + window->SetKeyupCallback([this] (gui::Window*) { // Press Escape to go back to level selection + level->Save(); + SwitchToLevelSelect(); + }, GDK_KEY_Escape); + window->SetKeyupCallback([this] (gui::Window*) { // Press Control-N to go to the next level + if (!window->IsControlDown()) return; + level->Save(); + std::pair next_level = level->NextLevel(); + if (next_level.first.empty()) // No next level + SwitchToLevelSelect(); + else + SwitchToLevel(next_level.first, next_level.second); + }, GDK_KEY_n); +} + +void GraphColoring::Start() +{ + window->Mainloop(); +} + + +} // namespace graphcoloring diff --git a/src/graphcoloring/graphcoloring.hpp b/src/graphcoloring/graphcoloring.hpp new file mode 100644 index 0000000..05801dd --- /dev/null +++ b/src/graphcoloring/graphcoloring.hpp @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GRAPHCOLORING_GRAPHCOLORING_H_ +#define GRAPHCOLORING_GRAPHCOLORING_H_ + +#include "gui/window.hpp" +#include "mainmenu.hpp" +#include "levelselect.hpp" +#include "level.hpp" + +namespace graphcoloring { + +class GraphColoring +{ +public: + enum class State + { + MAIN_MENU, + LEVEL_SELECT + }; + + GraphColoring(); + virtual ~GraphColoring(); + void Start(); + static constexpr gui::Color BACKGROUND_COLOR = 0x111111FF; + + static const std::string TITLE; +private: + void CheckSaves(); // Makes saves directory if it does not exist. + void DeleteAll(); + void SwitchToMainMenu(); + void SwitchToLevelSelect(); + void SwitchToLevel(std::string category_id, std::string level_id); + State state; + gui::Window* window; + std::unique_ptr mainMenu; + std::unique_ptr levelSelect; + std::unique_ptr level; +}; + +constexpr char PROTECT_ADD = 'a'; +constexpr char PROTECT_COLOR = 'c'; +constexpr char PROTECT_DELETE = 'd'; +constexpr char PROTECT_EDGE = 'e'; // Prevent user from adding edge to/from vertex + +} // namespace graphcoloring + +#endif /* GRAPHCOLORING_GRAPHCOLORING_H_ */ diff --git a/src/graphcoloring/graphs/colormenu.cpp b/src/graphcoloring/graphs/colormenu.cpp new file mode 100644 index 0000000..e466de2 --- /dev/null +++ b/src/graphcoloring/graphs/colormenu.cpp @@ -0,0 +1,120 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "colormenu.hpp" + +#include "../level.hpp" +#include "utils/geometry.hpp" + +namespace graphcoloring { + +int ColorMenu::number_of_color_menus = 0; + +ColorMenu::ColorMenu(gui::Window* window_, int x_, int y_, + const gui::Position& viewport_position_, + std::function color_click_callback_) + : window(window_), x(x_), y(y_), viewport_position(viewport_position_), + color_click_callback(color_click_callback_) +{ + number_of_color_menus++; + if (number_of_color_menus == 1) + { + MakeButtons(); + callback_id = window->SetMousedownCallback([this](gui::Window*,int x,int y){ + if (!IsInsideMenu(x,y) && close_callback) + close_callback(); + + }); + } + else + { + callback_id = -1; + } +} + +ColorMenu::~ColorMenu() +{ + if (callback_id != -1) + window->RemoveMousedownCallback(callback_id); + number_of_color_menus--; +} + +void ColorMenu::SetCloseCallback(std::function close_callback_) +{ + close_callback = close_callback_; +} + +int ColorMenu::GetWidth() const +{ + int number_of_colors = Level::colors.size(); + return SPACING + (SPACING + 2 * CIRCLE_RADIUS) * number_of_colors; +} + +int ColorMenu::GetHeight() const +{ + return 2 * (CIRCLE_RADIUS + SPACING); +} + +void ColorMenu::MakeButtons() +{ + int xpos = x + SPACING; + for (gui::Color color : Level::colors) + { + gui::Position pos(xpos, y + SPACING, 0, 0, nullptr, + &negative_viewport_position); + gui::Size size(CIRCLE_RADIUS); + std::unique_ptr button( + new gui::Button(window, "", pos, size, + color, gui::Alignment::LEFT, gui::Alignment::TOP, + gui::Button::Shape::CIRCLE)); + button->SetCommand([this,color] (){ + if (!color_click_callback) return; + color_click_callback(color); + }, GDK_BUTTON_PRIMARY); + buttons.push_back(std::move(button)); + xpos += SPACING + 2 * CIRCLE_RADIUS; + } +} + +void ColorMenu::Render() +{ + if (callback_id == -1) // Failed to create ColorMenu + { + close_callback(); + return; + } + + int view_x = viewport_position.x; + int view_y = viewport_position.y; + negative_viewport_position.SetPos(-view_x, -view_y); + + window->SetDrawColor(BACKGROUND_COLOR); + window->DrawRectangle(x-view_x, y-view_y, GetWidth(), GetHeight(), true); + for (std::unique_ptr& button : buttons) + button->Render(); +} + +bool ColorMenu::IsInsideMenu(int mx, int my) const +{ + return utils::geometry::InRectangle(mx, my, + x-viewport_position.X(), y-viewport_position.Y(), + GetWidth(), GetHeight()); +} + +} // namespace graphcoloring + diff --git a/src/graphcoloring/graphs/colormenu.hpp b/src/graphcoloring/graphs/colormenu.hpp new file mode 100644 index 0000000..3062654 --- /dev/null +++ b/src/graphcoloring/graphs/colormenu.hpp @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GRAPHS_COLORMENU_H_ +#define GRAPHCOLORING_GRAPHS_COLORMENU_H_ + +#include "gui/window.hpp" +#include "gui/button.hpp" + +#include + +namespace graphcoloring { + +class ColorMenu { +public: + ColorMenu(gui::Window* window, int x, int y, + const gui::Position& viewport_position, + std::function color_click_callback); + virtual ~ColorMenu(); + void Render(); + void SetCloseCallback(std::function close_callback); + int GetWidth() const; + int GetHeight() const; + bool IsInsideMenu(int x, int y) const; // Is this mouse position inside the menu? +private: + void MakeButtons(); + static constexpr int CIRCLE_RADIUS = 20; + static constexpr int SPACING = 10; + static constexpr gui::Color BACKGROUND_COLOR = 0x222222FF; + static int number_of_color_menus; // Number of color menus open. + int callback_id; + gui::Window* const window; + const int x, y; + const gui::Position& viewport_position; + gui::Position negative_viewport_position; + std::vector> buttons; + std::function color_click_callback; + std::function close_callback; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_GRAPHS_COLORMENU_H_ diff --git a/src/graphcoloring/graphs/edge.cpp b/src/graphcoloring/graphs/edge.cpp new file mode 100644 index 0000000..91adc1d --- /dev/null +++ b/src/graphcoloring/graphs/edge.cpp @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "edge.hpp" + +#define _USE_MATH_DEFINES +#include +#include + +#include "utils/geometry.hpp" +#include "utils/errors.hpp" + +namespace graphcoloring { + +int Edge::id_counter = 0; + +void Edge::ResetID() +{ + id_counter = 0; +} + + +Edge::Edge(gui::Window* window_, Vertex& from_, Vertex& to_, gui::Color color_, + const gui::Position& viewport_position_, bool directed_) + : id(id_counter++), from(from_), to(to_), window(window_), + viewport_position(viewport_position_) +{ + color = color_; + directed = directed_; + color_menu = nullptr; + mousedown_callback_id = + window->SetMousedownCallback([this] (gui::Window*, int x, int y) { + MouseCallback(x, y); + }); + x_keyup_callback_id = + window->SetKeyupCallback([this] (gui::Window*) { + if (hovering && !is_delete_protected && delete_callback + && !is_locked) + delete_callback(); + }, GDK_KEY_x); +} + +Edge::~Edge() +{ + window->RemoveMousedownCallback(mousedown_callback_id); + window->RemoveKeyupCallback(x_keyup_callback_id, GDK_KEY_x); +} + +int Edge::OtherEndpoint(int vertex_id) const +{ + if (from.id == vertex_id) return to.id; + if (to.id == vertex_id) return from.id; + utils::errors::Die("No other endpoint."); + return -1; +} + +void Edge::Lock() +{ + is_locked = true; +} + +void Edge::Unlock() +{ + is_locked = false; +} + +bool Edge::IsHovering() const +{ + return hovering; +} + +bool Edge::IsAdjacentTo(const Edge& edge) const +{ + return edge.HasEndpoint(from.id) || edge.HasEndpoint(to.id); +} + +void Edge::SetDeleteCallback(std::function delete_callback_) +{ + delete_callback = delete_callback_; +} + +gui::Color Edge::Color() const +{ + return color; +} + +bool Edge::ChangeColor(gui::Color new_color) +{ + if (is_color_protected) return false; + color = new_color; + return true; +} + +bool Edge::HasEndpoints(int id1, int id2) const +{ + if (from.id == id1 && to.id == id2) + return true; + return !directed && from.id == id2 && to.id == id1; +} + +bool Edge::HasEndpoint(int v_id) const +{ + return from.id == v_id || to.id == v_id; +} + +void Edge::Render(bool is_in_path) +{ + int dist = utils::geometry::PointToLineSegmentDistance( + window->GetMouseX(), window->GetMouseY(), + from.RenderX(), from.RenderY(), to.RenderX(), to.RenderY(), + EDGE_CLICK_TOLERANCE); + hovering = dist < EDGE_CLICK_TOLERANCE && dist != -1; + int from_x = from.RenderX(); + int from_y = from.RenderY(); + int to_x = to.RenderX(); + int to_y = to.RenderY(); + + double theta1 = utils::geometry::LineAngle(from_x, from_y, to_x, to_y); + double theta2 = utils::geometry::LineAngle(to_x, to_y, from_x, from_y); + + int x1 = from_x + Vertex::VERTEX_RADIUS * std::cos(theta1); + int y1 = from_y + Vertex::VERTEX_RADIUS * std::sin(theta1); + + int x2 = to_x + Vertex::VERTEX_RADIUS * std::cos(theta2); + int y2 = to_y + Vertex::VERTEX_RADIUS * std::sin(theta2); + + window->SetDrawColor(is_in_path ? gui::colors::WHITE : color); + window->DrawLine(x1, y1, x2, y2); + + if (directed) + { + double theta3 = theta2 - M_PI / 4; + double theta4 = theta2 + M_PI / 4; + + int x3 = x2 + ARROW_SIZE * std::cos(theta3); + int y3 = y2 + ARROW_SIZE * std::sin(theta3); + + int x4 = x2 + ARROW_SIZE * std::cos(theta4); + int y4 = y2 + ARROW_SIZE * std::sin(theta4); + + window->DrawLine(x2, y2, x3, y3); + window->DrawLine(x2, y2, x4, y4); + } + +} + +void Edge::RenderColorMenu() +{ + if (color_menu != nullptr) + color_menu->Render(); +} + + +void Edge::MouseCallback(int mouse_x, int mouse_y) +{ + if (!hovering || color_menu != nullptr || is_color_protected + || is_locked) + return; + + std::function callback = [this] (gui::Color color) { + assert(ChangeColor(color)); + color_menu = nullptr; + }; + + color_menu = std::make_unique(window, + mouse_x+viewport_position.X(), mouse_y+viewport_position.Y(), + viewport_position, callback); + + std::function close_callback = [this] () { + color_menu = nullptr; + }; + color_menu->SetCloseCallback(close_callback); +} + + +} // namespace graphcoloring diff --git a/src/graphcoloring/graphs/edge.hpp b/src/graphcoloring/graphs/edge.hpp new file mode 100644 index 0000000..d5350b8 --- /dev/null +++ b/src/graphcoloring/graphs/edge.hpp @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GRAPHS_EDGE_H_ +#define GRAPHCOLORING_GRAPHS_EDGE_H_ + +#include "vertex.hpp" + +#include "gui/window.hpp" + +namespace graphcoloring { + +class Edge { +public: + static void ResetID(); + Edge(gui::Window* window, Vertex& from, Vertex& to, + gui::Color color, const gui::Position& viewport_position, + bool directed = false); + virtual ~Edge(); + int OtherEndpoint(int vertex_id) const; + void Lock(); + void Unlock(); + bool IsHovering() const; + bool IsAdjacentTo(const Edge& edge) const; + void SetDeleteCallback(std::function delete_callback); + bool ChangeColor(gui::Color new_color); // Returns false if edge is color protected. + gui::Color Color() const; + bool HasEndpoint(int id) const; // Does this edge have this endpoint? + bool HasEndpoints(int id1, int id2) const; // Does this edge have these endpoints? + void Render(bool is_in_path = false); + void RenderColorMenu(); // Color menu rendering is handled separately + const int id; + Vertex& from; + Vertex& to; + bool is_color_protected = false; + bool is_delete_protected = false; +private: + void MouseCallback(int mouse_x, int mouse_y); + static constexpr int EDGE_CLICK_TOLERANCE = 10; + static constexpr int ARROW_SIZE = 10; + static int id_counter; + gui::Window* const window; + gui::Color color; + std::unique_ptr color_menu; + const gui::Position& viewport_position; + bool directed; + bool hovering = false; + bool is_locked = false; + int mousedown_callback_id; + int x_keyup_callback_id; + std::function delete_callback; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_GRAPHS_EDGE_H_ diff --git a/src/graphcoloring/graphs/graph.cpp b/src/graphcoloring/graphs/graph.cpp new file mode 100644 index 0000000..7785a8f --- /dev/null +++ b/src/graphcoloring/graphs/graph.cpp @@ -0,0 +1,419 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "graph.hpp" + +#include +#include +#include + +#include "../level.hpp" +#include "utils/errors.hpp" + +namespace graphcoloring { + +void Graph::ResetIDs() +{ + Vertex::ResetID(); + Edge::ResetID(); +} + +Graph::Graph(gui::Window* window_, const gui::Position& viewport_position_, + bool directed_) + : window(window_), viewport_position(viewport_position_), + edge_vertex(-1) +{ + directed = directed_; + can_add_new_vertices = true; + v_keyup_callback = + window->SetKeyupCallback([this] (gui::Window*) { + if (can_add_new_vertices && !is_locked) + AddVertex(window->GetMouseX()+viewport_position.X(), + window->GetMouseY()+viewport_position.Y()); + }, GDK_KEY_v); + e_keyup_callback = + window->SetKeyupCallback([this] (gui::Window*){EPressed();}, GDK_KEY_e); +} + +Graph::~Graph() +{ + Clear(); + + window->RemoveKeyupCallback(v_keyup_callback, GDK_KEY_v); + window->RemoveKeyupCallback(e_keyup_callback, GDK_KEY_e); +} + +int Graph::V() const +{ + return vertices.size(); +} + +int Graph::E() const +{ + return edges.size(); +} + +void Graph::AddOrigin(int id) +{ + origins.push_back(id); +} + +void Graph::Clear() +{ + for (Vertex* v : vertices) + delete v; + for (Edge* e : edges) + delete e; + vertices.clear(); + edges.clear(); + ResetIDs(); +} + +void Graph::Lock() +{ + is_locked = true; + for (Vertex* v : vertices) + v->Lock(); + for (Edge* e : edges) + e->Lock(); +} + +void Graph::Unlock() +{ + is_locked = false; + for (Vertex* v : vertices) + v->Unlock(); + for (Edge* e : edges) + e->Unlock(); +} + +void Graph::EPressed() +{ + if (!can_add_new_edges || is_locked) return; + if (edge_vertex != -1 && !HasVertexWithID(edge_vertex)) // Check if vertex was deleted. + edge_vertex = -1; + int hover_id = GetHoveringVertex(); + if (edge_vertex == -1) + { + edge_vertex = hover_id; + } + else if (hover_id != -1) + { + if (edge_vertex == hover_id) return; // Disallow self-loops + if (HasEdge(edge_vertex, hover_id)) return; // Disallow multiple edges between same vertices + if (GetVertexByID(hover_id).is_edge_protected) return; + AddEdge(edge_vertex, hover_id); + edge_vertex = -1; // Reset edge_vertex + } + else // User pressed e with no second vertex + { + edge_vertex = -1; // Cancel + } +} + +bool Graph::HasVertexWithID(int id) const +{ + for (const Vertex* v : vertices) + if (v->id == id) + return true; + return false; +} + +Vertex& Graph::GetVertexByID(int id) +{ + for (Vertex* v : vertices) + if (v->id == id) + return *v; + std::stringstream s; + s << "Failed to find vertex with id: " << id; + utils::errors::Die(s.str()); + return *vertices[-1]; // So the compiler doesn't worry... +} + +const Vertex& Graph::GetVertexByIDConst(int id) const +{ + for (const Vertex* v : vertices) + if (v->id == id) + return *v; + std::stringstream s; + s << "Failed to find vertex with id: " << id; + utils::errors::Die(s.str()); + return *vertices[-1]; // So the compiler doesn't worry... +} + + +Edge& Graph::GetEdgeByEndpoints(int from, int to) +{ + for (Edge* e : edges) + if (e->from.id == from && e->to.id == to) + return *e; + + std::stringstream s; + s << "Failed to find edge with end-points: " << from << ", " << to; + utils::errors::Die(s.str()); + return *edges[-1]; // So the compiler doesn't worry... +} + +const Edge& Graph::GetEdgeByEndpointsConst(int from, int to) const +{ + for (const Edge* e : edges) + if (e->HasEndpoints(from, to)) + return *e; + + std::stringstream s; + s << "Failed to find edge with end-points: " << from << ", " << to; + utils::errors::Die(s.str()); + return *edges[-1]; // So the compiler doesn't worry... +} + +bool Graph::HasEdgeWithID(int id) const +{ + for (const Edge* e : edges) + if (e->id == id) + return true; + return false; +} + +Edge& Graph::GetEdgeByID(int id) +{ + for (Edge* e : edges) + if (e->id == id) + return *e; + + std::stringstream s; + s << "Failed to find edge with id: " << id; + utils::errors::Die(s.str()); + return *edges[-1]; +} + + +const Edge& Graph::GetEdgeByIDConst(int id) const +{ + for (const Edge* e : edges) + if (e->id == id) + return *e; + std::stringstream s; + s << "Failed to find edge with id: " << id; + utils::errors::Die(s.str()); + return *edges[-1]; +} + +int Graph::GetHoveringVertex() const +{ + for (const Vertex* v : vertices) + if (v->IsHovering()) + return v->id; + return -1; +} + +int Graph::GetHoveringEdge() const +{ + for (const Edge* e : edges) + if (e->IsHovering()) + return e->id; + return -1; +} + +int Graph::AddVertex(Vertex* v) +{ + vertices.push_back(v); + std::function f = [this, v] () { + RemoveVertex(v->id); + }; + v->SetDeleteCallback(f); + DFS(); + return v->id; +} + +int Graph::AddVertex(Vertex& v) +{ + return AddVertex(&v); +} + +int Graph::AddVertex(int x, int y) +{ + Vertex* v = new Vertex(window, Level::colors[0], x, y, viewport_position); + return AddVertex(v); +} + +int Graph::AddEdge(Edge* e) +{ + edges.push_back(e); + e->SetDeleteCallback([this, e] () { + RemoveEdge(e->from.id, e->to.id); + }); + DFS(); + return e->id; +} + +int Graph::AddEdge(Edge& e) +{ + return AddEdge(&e); +} + +int Graph::AddEdge(int id1, int id2) +{ + if (!HasVertexWithID(id1)|| !HasVertexWithID(id2)) + utils::errors::Die("Trying to create edge with non-existent vertex."); + Vertex& v1 = GetVertexByID(id1); + Vertex& v2 = GetVertexByID(id2); + Edge* e = new Edge(window, v1, v2, Level::colors[0], viewport_position, + directed); + return AddEdge(e); +} + +bool Graph::HasEdge(int id1, int id2) const +{ + for (const Edge* e : edges) + if (e->HasEndpoints(id1, id2)) + return true; + return false; +} + +void Graph::RemoveVertex(int id) +{ + for (int i = 0; i < E(); i++) + { + if (edges[i]->from.id == id || edges[i]->to.id == id) + { + delete edges[i]; + edges.erase(edges.begin() + i); + i--; + } + } + + for (int i = 0; i < V(); i++) + { + if (vertices[i]->id == id) + { + delete vertices[i]; + vertices.erase(vertices.begin()+i); + break; + } + } + DFS(); +} + +void Graph::RemoveEdge(int id1, int id2) +{ + for (int i = 0; i < E(); i++) + { + if (edges[i]->HasEndpoints(id1, id2)) + { + delete edges[i]; + edges.erase(edges.begin() + i); + break; + + } + } + DFS(); +} + +int Graph::Degree(int id) const +{ + int degree = 0; + for (int i = 0; i < E(); i++) + { + if (edges[i]->from.id == id || edges[i]->to.id == id) + degree++; + } + return degree; +} + +void Graph::DFS() +{ + std::stack vertices; + for (int v : origins) + vertices.push(v); + connected.clear(); + while (!vertices.empty()) + { + int v = vertices.top(); + vertices.pop(); + if (connected.count(v)) + continue; + connected.insert(v); + for (Edge* e : edges) + { + if (e->from.id == v && !connected.count(e->to.id)) + vertices.push(e->to.id); + if (e->to.id == v && !connected.count(e->from.id)) + vertices.push(e->from.id); + + } + } +} + +bool Graph::IsConnected(int id) const +{ + return connected.count(id) > 0; +} + +bool Graph::IsConnected() const +{ + for (const Vertex* v : vertices) + if (!IsConnected(v->id)) + return false; + return true; +} + +void Graph::Render(const std::set& edges_in_path, + const std::set& vertices_in_path, int last_vertex) +{ + // Display counter + window->SetTextSize(COUNTER_TEXT_SIZE); + window->SetDrawColor(0xCCCCCCFF); + std::stringstream counter_text; + counter_text << "Vertices: " << V() << ", Edges: " << E(); + window->DrawText(counter_text.str(), 10, COUNTER_TEXT_SIZE+10); + + if (edge_vertex != -1) // Draw line from edge vertex to mouse. + { + if (!HasVertexWithID(edge_vertex)) + { + edge_vertex = -1; + } + else + { + Vertex& v = GetVertexByID(edge_vertex); + window->SetDrawColor(0x888888FF); + window->DrawLine(v.RenderX(), v.RenderY(), + window->GetMouseX(), window->GetMouseY()); + } + } + + for (Vertex* v : vertices) + v->Render(Degree(v->id), IsConnected(v->id) > 0, + vertices_in_path.count(v->id) > 0, last_vertex == v->id); + for (Edge* e : edges) + e->Render(edges_in_path.count(e->id) > 0); + + for (Vertex* v : vertices) + v->RenderColorMenu(); + for (Edge* e : edges) + e->RenderColorMenu(); +} + +void Graph::Render() +{ + std::set edges_in_path; + std::set vertices_in_path; + Render(edges_in_path, vertices_in_path, -1); +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/graphs/graph.hpp b/src/graphcoloring/graphs/graph.hpp new file mode 100644 index 0000000..d7223c4 --- /dev/null +++ b/src/graphcoloring/graphs/graph.hpp @@ -0,0 +1,88 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GRAPHS_GRAPH_H_ +#define GRAPHCOLORING_GRAPHS_GRAPH_H_ + +#include + +#include "vertex.hpp" +#include "edge.hpp" + +namespace graphcoloring { + +class Graph { +public: + Graph(gui::Window* window, const gui::Position& viewport_position, + bool directed = false); + virtual ~Graph(); + int V() const; + int E() const; + void AddOrigin(int id); + void Clear(); // Clear all vertices and edges + void Lock(); // Disallow changes to the graph + void Unlock(); // Allow changes to the graph + bool HasVertexWithID(int id) const; + Vertex& GetVertexByID(int id); + const Vertex& GetVertexByIDConst(int id) const; + Edge& GetEdgeByEndpoints(int from, int to); + const Edge& GetEdgeByEndpointsConst(int from, int to) const; + bool HasEdgeWithID(int id) const; + Edge& GetEdgeByID(int id); + const Edge& GetEdgeByIDConst(int id) const; + int GetHoveringVertex() const; // Returns ID of vertex which mouse is hovering over, -1 if no such vertex. + int GetHoveringEdge() const; + int AddVertex(Vertex& v); + int AddVertex(int x, int y); // Create vertex with some default parameters. + int AddEdge(Edge& e); + int AddEdge(int v1, int v2); // Create edge with some default parameters. + bool HasEdge(int id1, int id2) const; // Is there an edge between id1 and id2? + void RemoveVertex(int id); + void RemoveEdge(int id1, int id2); + int Degree(int id) const; + bool IsConnected(int id) const; + bool IsConnected() const; + void Render(const std::set& edges_in_path, + const std::set& vertices_in_path, int last_vertex); + void Render(); + bool can_add_new_vertices; // Can the user add more vertices? + bool can_add_new_edges; + std::vector vertices; + std::vector edges; +private: + static void ResetIDs(); // Reset edge and vertex IDs. + int AddVertex(Vertex* v); + int AddEdge(Edge* e); + void EPressed(); // e key was pressed + void DFS(); // Run a DFS on the graph to see which vertices are connected + static constexpr int COUNTER_TEXT_SIZE = 24; + gui::Window* const window; + const gui::Position& viewport_position; + bool directed; + std::vector origins; + std::set connected; + bool is_locked = false; + + int edge_vertex; // First vertex in edge; -1 if not making edge. + int v_keyup_callback; + int e_keyup_callback; +}; + +} // namespace graphcoloring + +#endif // SRC_GRAPHCOLORING_GRAPHS_GRAPH_H_ diff --git a/src/graphcoloring/graphs/vertex.cpp b/src/graphcoloring/graphs/vertex.cpp new file mode 100644 index 0000000..b4d3afe --- /dev/null +++ b/src/graphcoloring/graphs/vertex.cpp @@ -0,0 +1,204 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "vertex.hpp" + +#include +#include + +#include "utils/geometry.hpp" +#include "gui/button.hpp" +#include "../graphcoloring.hpp" + +namespace graphcoloring { + +int Vertex::id_counter = 0; +int Vertex::moving_vertex = -1; + +void Vertex::ResetID() +{ + id_counter = 0; +} + +Vertex::Vertex(gui::Window* window_, gui::Color color_, int x_, int y_, + const gui::Position& viewport_position_) + : x(x_), y(y_), id(id_counter++), + window(window_), + color(color_), + viewport_position(viewport_position_), + color_menu(nullptr) +{ + SetClickCallback(); + SetMoveCallbacks(); + SetDeleteKeyCallback(); +} + +Vertex::~Vertex() +{ + if (moving_vertex == id) moving_vertex = -1; + window->RemoveMousedownCallback(mousedown_callback_id); + window->RemoveKeydownCallback(m_keydown_callback_id, GDK_KEY_m); + window->RemoveKeyupCallback(m_keyup_callback_id, GDK_KEY_m); + window->RemoveKeyupCallback(x_keyup_callback_id, GDK_KEY_x); +} + +void Vertex::Lock() +{ + is_locked = true; + moving_vertex = -1; +} + +void Vertex::Unlock() +{ + is_locked = false; +} + +void Vertex::SetClickCallback() +{ + mousedown_callback_id = + window->SetMousedownCallback([this] (gui::Window*, int x, int y) { + MouseCallback(x, y); + }, GDK_BUTTON_PRIMARY); + +} + +void Vertex::SetMoveCallbacks() +{ + m_keydown_callback_id = + window->SetKeydownCallback([this] (gui::Window*) { + if (moving_vertex == -1 && hovering) + { + moving_vertex = id; + } + }, GDK_KEY_m); + m_keyup_callback_id = + window->SetKeyupCallback([this] (gui::Window*) { + moving_vertex = -1; + }, GDK_KEY_m); +} + +void Vertex::SetDeleteKeyCallback() +{ + x_keyup_callback_id = + window->SetKeyupCallback([this] (gui::Window*) { + if (hovering && !is_delete_protected && !is_locked) + { + if (delete_callback) + { + delete_callback(); + } + } + }, GDK_KEY_x); +} + +void Vertex::SetDeleteCallback(std::function delete_callback_) +{ + delete_callback = delete_callback_; +} + +bool Vertex::operator==(const Vertex other) const +{ + return id == other.id; +} + +gui::Color Vertex::Color() const +{ + return color; +} + +bool Vertex::ChangeColor(gui::Color new_color) +{ + if (is_color_protected) return false; + color = new_color; + return true; +} + +int Vertex::RenderX() const +{ + return x - viewport_position.X(); +} + +int Vertex::RenderY() const +{ + return y - viewport_position.Y(); +} + + +void Vertex::Render(int degree, bool filled, bool is_in_path, + bool is_last_vertex) +{ + int rx = RenderX(), ry = RenderY(); // Coordinates of circle + + CheckIfMoving(); + + window->SetDrawColor(is_in_path ? gui::colors::WHITE : color); + if (is_last_vertex) + window->SetDrawColor(0x888888FF); + window->DrawCircle(rx, ry, VERTEX_RADIUS, filled); + window->SetTextSize(VERTEX_RADIUS*gui::Button::CIRCLE_TEXT_SIZE_FACTOR); + + if (filled) + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + + hovering = utils::geometry::InCircle( + window->GetMouseX(), window->GetMouseY(), rx, ry, VERTEX_RADIUS); + if (hovering) + { + window->DrawText(std::to_string(degree), gui::Position(rx,ry), + gui::Alignment::CENTER, gui::Alignment::CENTER); + } +} + +void Vertex::CheckIfMoving() +{ + if (moving_vertex == id) + { + x = window->GetMouseX() + viewport_position.X(); + y = window->GetMouseY() + viewport_position.Y(); + } +} + +void Vertex::RenderColorMenu() +{ + if (color_menu != nullptr) + color_menu->Render(); +} + + +void Vertex::MouseCallback(int mouse_x, int mouse_y) +{ + if (is_color_protected || is_locked) return; + if (hovering && color_menu == nullptr && !is_color_protected) + { + std::function callback = [this] (gui::Color color) { + assert(ChangeColor(color)); + color_menu = nullptr; + }; + + color_menu = std::make_unique(window, + mouse_x+viewport_position.X(), mouse_y+viewport_position.Y(), + viewport_position, callback); + + std::function close_callback = [this] () { + color_menu = nullptr; + }; + color_menu->SetCloseCallback(close_callback); + } +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/graphs/vertex.hpp b/src/graphcoloring/graphs/vertex.hpp new file mode 100644 index 0000000..f5dea49 --- /dev/null +++ b/src/graphcoloring/graphs/vertex.hpp @@ -0,0 +1,76 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GRAPHS_VERTEX_H_ +#define GRAPHCOLORING_GRAPHS_VERTEX_H_ + +#include "gui/colors.hpp" +#include "gui/window.hpp" +#include "colormenu.hpp" + +namespace graphcoloring { + +class Vertex { +public: + static void ResetID(); + Vertex(gui::Window* window, gui::Color color, int x, int y, + const gui::Position& viewport_position); + virtual ~Vertex(); + void Lock(); + void Unlock(); + void SetDeleteCallback(std::function delete_callback); + bool operator==(const Vertex other) const; + gui::Color Color() const; + bool ChangeColor(gui::Color new_color); + int RenderX() const; // x-position when rendered + int RenderY() const; + bool IsHovering() const { return hovering; }; // Is the mouse hovering over this vertex? + void Render(int degree, bool filled = false, bool is_in_path = false, + bool is_last_vertex = false); + void RenderColorMenu(); + static constexpr int VERTEX_RADIUS = 40; + int x; + int y; + const int id; + bool is_color_protected = false; + bool is_edge_protected = false; + bool is_delete_protected = false; + static int moving_vertex; // id of vertex that is being moved, or -1 if no vertex is being moved. +private: + void SetClickCallback(); + void SetMoveCallbacks(); + void SetDeleteKeyCallback(); + void MouseCallback(int mouse_x, int mouse_y); + void CheckIfMoving(); + static int id_counter; + std::function delete_callback; + int mousedown_callback_id; + int m_keydown_callback_id; + int m_keyup_callback_id; + int x_keyup_callback_id; + gui::Window* const window; + gui::Color color; + bool hovering = false; + bool is_locked = false; + const gui::Position& viewport_position; + std::unique_ptr color_menu; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_GRAPHS_VERTEX_H_ diff --git a/src/graphcoloring/level.cpp b/src/graphcoloring/level.cpp new file mode 100644 index 0000000..793e0cd --- /dev/null +++ b/src/graphcoloring/level.cpp @@ -0,0 +1,350 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "level.hpp" + +#include + +#include "graphcoloring.hpp" +#include "graphs/vertex.hpp" +#include "graphs/edge.hpp" +#include "levels/graphloader.hpp" + +namespace graphcoloring { + +std::vector Level::colors; + +pugi::xml_node Level::GetLevelNode( + const pugi::xml_document& document, std::string category_id, + std::string level_id) +{ + std::stringstream xpath; + xpath << "/category-listing/category[@id='" << category_id << "']" + << "/level[@id='" + level_id + "']"; + + pugi::xpath_node xnode = document.select_node(xpath.str().c_str()); + pugi::xml_node node = xnode.node(); + return node; +} + +Level::Level(gui::Window* window_, + std::string category_id_, std::string level_id_) + : window(window_), category_id(category_id_), level_id(level_id_), + graph(window, viewport_position), + path(window, graph, rule_loader, color_loader), + point_calculator(value_loader, rule_loader, color_loader, path) +{ + + window->SetRenderCallback([this] (gui::Window*){ Render(); }); + window->SetKeyupCallback([this] (gui::Window*){ ResetViewport(); }, + GDK_KEY_o); + window->SetKeyupCallback([this] (gui::Window*) { + if (window->IsControlDown()) + Reset(); + }, GDK_KEY_r); + window->SetKeyupCallback([this] (gui::Window*) { + if (window->IsControlDown()) + Load(SLOT_BEST); + }, GDK_KEY_b); + + for (int digit = 0; digit <= 9; digit++) + { + window->SetKeyupCallback([this,digit](gui::Window*) { + if (window->IsControlDown()) + Load(digit); + else + Save(digit); + }, GDK_KEY_0 + digit); + } + LoadLevelDocument(); +} + +Level::~Level() +{ + Save(); +} + +std::pair Level::NextLevel() const +{ + pugi::xml_document document; + document.load_file(LevelSelect::LEVEL_LISTING_PATH); + pugi::xml_node this_level_node = + GetLevelNode(document, category_id, level_id); + pugi::xml_node next_level_node = this_level_node.next_sibling(); + if (next_level_node) + { + std::string next_level_id = next_level_node.attribute("id").value(); + return std::make_pair(category_id, next_level_id); + } + else + { + pugi::xml_node category_node = this_level_node.parent(); + pugi::xml_node next_category_node = category_node.next_sibling(); + if (next_category_node) + { + std::string next_category_id = + next_category_node.attribute("id").value(); + next_level_node = next_category_node.first_child(); + + std::string next_level_id = + next_level_node.attribute("id").value(); + return std::make_pair(next_category_id, next_level_id); + } + else + { + return std::make_pair("", ""); // No next level. + } + } +} + +void Level::LoadLevelDocument() +{ + pugi::xml_document document; + std::string filename + = "assets/levels/" + category_id + "/" + level_id + ".xml"; + document.load_file(filename.c_str()); + + pugi::xml_node level_node = document.child("level"); + title = level_node.attribute("title").value(); + description = level_node.attribute("description").value(); + objective = level_node.attribute("objective").value(); + + color_loader.LoadDocument(document); + global_loader.LoadDocument(document); + + graph.can_add_new_vertices = !global_loader.IsVertexProtected(PROTECT_ADD); + graph.can_add_new_edges = !global_loader.IsEdgeProtected(PROTECT_ADD); + GraphLoader graph_loader(color_loader, global_loader); + graph_loader.LoadDocument(document, graph); + value_loader.LoadGraph(graph_loader); + value_loader.LoadColors(color_loader); + value_loader.LoadDocument(document); + rule_loader.LoadDocument(document, color_loader); + path.LoadFromDocument(document); + + Load(); // Check for save file + + ResetViewport(); +} + +std::string Level::GetFile() +{ + return "assets/levels/" + category_id + "/" + level_id + ".xml"; +} + +void Level::Reset() +{ + pugi::xml_document document; + std::string filename = GetFile(); + document.load_file(filename.c_str()); + graph.Clear(); + GraphLoader graph_loader(color_loader, global_loader); + graph_loader.LoadDocument(document, graph); +} + +int Level::GetPoints(bool check_if_invalid) const +{ + return point_calculator.Points(graph, check_if_invalid); +} + +void Level::GetBestPoints() +{ + if (has_loaded_best) + return; + pugi::xml_document document; + if (!document.load_file(SaveFilename(SLOT_BEST).c_str())) + { + has_loaded_best = false; + return; + } + pugi::xml_node graph_node = document.child("graph"); + has_loaded_best = true; + best_points = graph_node.attribute("points").as_int(); +} + +void Level::Render() +{ + MoveViewport(); + + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + window->Clear(); + graph.Render(path.PathEdgeSet(), path.PathVertexSet(), path.LastVertex()); + + window->SetDrawColor(TEXT_COLOR); + + int y = 50; + window->SetTextSize(TITLE_SIZE); + window->DrawText(title, gui::Position(window->GetWidth()/2, y), + gui::Alignment::CENTER, gui::Alignment::TOP); + + y += TITLE_SIZE + 10; + window->SetTextSize(DESCRIPTION_SIZE); + std::string description_text = description; + size_t semicolon_index; + do { // Read description lines + semicolon_index = description_text.find(';'); + std::string line = description_text.substr(0, semicolon_index); + window->DrawText(line, gui::Position(window->GetWidth()/2, y), + gui::Alignment::CENTER, gui::Alignment::TOP); + description_text.erase(0, semicolon_index+1); + y += DESCRIPTION_SIZE + 10; + } while (semicolon_index != std::string::npos); + + window->SetTextSize(OBJECTIVE_SIZE); + window->DrawText(objective, gui::Position(window->GetWidth()/2, y), + gui::Alignment::CENTER, gui::Alignment::TOP); + + y += OBJECTIVE_SIZE + 10; + RenderPoints(y); + RenderRules(); +} + +void Level::RenderPoints(int y) +{ + if (window->IsKeyDown(GDK_KEY_p) && !window->IsControlDown()) + { + color_loader.RenderColorPoints(window); + return; + } + + bool is_valid = rule_loader.IsValid(graph); + int points = GetPoints(); + int objective = value_loader.ObjectivePoints(graph); + + GetBestPoints(); + if (!has_loaded_best || points > best_points) + Save(SLOT_BEST); + + if (points >= objective && is_valid) + { + window->SetTextSize(PRESS_ESC_SIZE); + window->SetDrawColor(SUCCESS_COLOR); + window->DrawText("Press Control-N to go to the next level.", + gui::Position(window->GetWidth()/2, y), + gui::Alignment::CENTER, gui::Alignment::TOP); + y += PRESS_ESC_SIZE + 10; + } + if (!is_valid) + window->SetDrawColor(INVALID_COLOR); + + std::stringstream points_text; + int invalid_points = GetPoints(false); + points_text << "Points: " << invalid_points << "/" << objective; + window->SetTextSize(POINTS_SIZE); + window->DrawText(points_text.str(), gui::Position(window->GetWidth()/2, y), + gui::Alignment::CENTER, gui::Alignment::TOP); +} + +void Level::RenderRules() +{ + if (!window->IsKeyDown(GDK_KEY_r) || window->IsControlDown()) return; + rule_loader.RenderRules(window); +} + +void Level::MoveViewport() +{ + if (window->IsKeyDown(GDK_KEY_Up)) + viewport_position.y -= VIEW_MOVE_SPEED; + if (window->IsKeyDown(GDK_KEY_Down)) + viewport_position.y += VIEW_MOVE_SPEED; + + if (window->IsKeyDown(GDK_KEY_Left)) + viewport_position.x -= VIEW_MOVE_SPEED; + if (window->IsKeyDown(GDK_KEY_Right)) + viewport_position.x += VIEW_MOVE_SPEED; + +} + +void Level::ResetViewport() +{ + viewport_position.SetPos(0, 0); +} + +std::string Level::SaveFilename(int slot) const +{ + + std::string suffix; + if (slot == SLOT_RECENT) + suffix = ""; + else if (slot == SLOT_BEST) + suffix = "_best"; + else + suffix = std::string("_") + std::to_string(slot); + + return "saves/" + category_id + "/" + level_id + suffix + ".xml"; +} + +void Level::Save(int slot) +{ + pugi::xml_document document; + pugi::xml_node graph_node = document.append_child("graph"); + graph_node.append_attribute("points") = GetPoints(); + GraphLoader graph_loader(color_loader, global_loader); + graph_loader.WriteGraph(graph, graph_node); + document.save_file(SaveFilename(slot).c_str()); + + if (slot == SLOT_BEST) + { + has_loaded_best = false; + UpdateLevelList(); + } +} + +int Level::Load(int slot) +{ + pugi::xml_document document; + if (document.load_file(SaveFilename(slot).c_str())) + { + graph.Clear(); + GraphLoader graph_loader(color_loader, global_loader); + graph_loader.LoadDocument(document, graph); + return 1; + } + else + { + return 0; + } +} + +void Level::UpdateLevelList() +{ + pugi::xml_document category_listing; + category_listing.load_file(LevelSelect::LEVEL_LISTING_PATH); + + int points = GetPoints(); + int objective = value_loader.ObjectivePoints(graph); + + pugi::xml_node node = GetLevelNode(category_listing, category_id, level_id); + + if (node.attribute("points").empty()) + node.append_attribute("points") = points; + else + node.attribute("points") = points; + + if (points >= objective) + { + + if (node.attribute("completed").empty()) + node.append_attribute("completed") = "t"; + else + node.attribute("completed") = "t"; + } + category_listing.save_file(LevelSelect::LEVEL_LISTING_PATH); +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/level.hpp b/src/graphcoloring/level.hpp new file mode 100644 index 0000000..2d396fb --- /dev/null +++ b/src/graphcoloring/level.hpp @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GRAPHCOLORING_LEVEL_H_ +#define GRAPHCOLORING_LEVEL_H_ + +#include +#include + +#include "gui/window.hpp" +#include "graphs/graph.hpp" +#include "levels/globalloader.hpp" +#include "levels/valueloader.hpp" +#include "levels/rules/ruleloader.hpp" +#include "levels/pointcalculator.hpp" + +namespace graphcoloring { + +class Level { +public: + static pugi::xml_node GetLevelNode( + const pugi::xml_document& document, std::string category_id, + std::string level_id); + Level(gui::Window* window, std::string category_id, std::string level_id); + virtual ~Level(); + std::pair NextLevel() const; // Returns <"", ""> on success. + void Render(); + void Save(int slot = SLOT_RECENT); + int Load(int slot = SLOT_RECENT); // Returns 1 on success, 0 on failure + static std::vector colors; +private: + void LoadLevelDocument(); + std::string GetFile(); + void Reset(); + void MoveViewport(); + void ResetViewport(); + int GetPoints(bool check_if_invalid = true) const; + void GetBestPoints(); + void RenderPoints(int y); + void RenderRules(); + std::string SaveFilename(int slot = SLOT_RECENT) const; + void UpdateLevelList(); + static constexpr int VIEW_MOVE_SPEED = 5; + static constexpr int TITLE_SIZE = 48; + static constexpr int DESCRIPTION_SIZE = 18; + static constexpr int OBJECTIVE_SIZE = 32; + static constexpr int POINTS_SIZE = 36; + static constexpr int PRESS_ESC_SIZE = 18; + static constexpr gui::Color TEXT_COLOR = 0xDDDDDDFF; + static constexpr gui::Color SUCCESS_COLOR = gui::colors::GREEN; + static constexpr gui::Color INVALID_COLOR = gui::colors::RED; + static constexpr int SLOT_RECENT = -2; + static constexpr int SLOT_BEST = -1; + + gui::Window* const window; + int best_points = 0; + bool has_loaded_best = false; + gui::Position viewport_position; + std::string category_id; + std::string level_id; + std::string title; + std::string description; + std::string objective; + Graph graph; + ColorLoader color_loader; + GlobalLoader global_loader; + ValueLoader value_loader; + RuleLoader rule_loader; + Path path; + PointCalculator point_calculator; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVEL_H_ diff --git a/src/graphcoloring/levels/colorloader.cpp b/src/graphcoloring/levels/colorloader.cpp new file mode 100644 index 0000000..a580333 --- /dev/null +++ b/src/graphcoloring/levels/colorloader.cpp @@ -0,0 +1,142 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "colorloader.hpp" + +#include "utils/errors.hpp" +#include "../level.hpp" +#include "../graphcoloring.hpp" + +namespace graphcoloring { + +ColorLoader::ColorLoader() +{ +} + +void ColorLoader::LoadDocument(const pugi::xml_document& document) +{ + Level::colors.clear(); + pugi::xml_node colors_node = document.child("colors"); + for (pugi::xml_node color_node : colors_node.children("color")) + { + std::string name = color_node.attribute("name").value(); + gui::Color color = + gui::colors::FromAttribute(color_node.attribute("color")); + int vertex_points = color_node.attribute("vertex-points").as_int(0); + int edge_points = color_node.attribute("edge-points").as_int(0); + color_names[name] = color; + vertex_color_points[color] = vertex_points; + edge_color_points[color] = edge_points; + Level::colors.push_back(color); + } +} + +gui::Color ColorLoader::GetColorByName(std::string name) const +{ + if (color_names.count(name) == 0) + utils::errors::Die("Could not find color: " + name); + return color_names.at(name); +} + +gui::Color ColorLoader::GetColorFromAttribute(pugi::xml_attribute attr) const +{ + if (attr.empty()) + return Level::colors[0]; + else + return GetColorByName(attr.value()); +} + +std::string ColorLoader::GetColorName(gui::Color color) const +{ + for (std::pair color_name : color_names) + if (color_name.second == color) + return color_name.first; + utils::errors::Die("Could not find color."); + return ""; +} + +int ColorLoader::GetVertexPoints(gui::Color color) const +{ + if (vertex_color_points.count(color) == 0) + utils::errors::Die("Could not find color."); + return vertex_color_points.at(color); +} + +int ColorLoader::GetEdgePoints(gui::Color color) const +{ + if (edge_color_points.count(color) == 0) + utils::errors::Die("Could not find color."); + return edge_color_points.at(color); +} + +void ColorLoader::RenderColorPoints(gui::Window* window) const +{ + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + window->Clear(); + int x = 10, y = 10; + const int w = COLOR_POINTS_COLUMN_WIDTH, h = Vertex::VERTEX_RADIUS * 2; + for (gui::Color color : Level::colors) + { + if (edge_color_points.at(color) == 0) continue; + RenderEdgeColorPoints(window, color, x, y); + y += h + 10; + if (y > window->GetHeight()-h-10) + { + y = 10; + x += w + 10; + } + } + for (gui::Color color : Level::colors) + { + if (vertex_color_points.at(color) == 0) continue; + RenderVertexColorPoints(window, color, x, y); + y += h + 10; + if (y > window->GetHeight()-h-10) + { + y = 10; + x += w + 10; + } + } + +} + +void ColorLoader::RenderEdgeColorPoints(gui::Window* window, gui::Color color, + int x, int y) const +{ + const int w = COLOR_POINTS_COLUMN_WIDTH, h = Vertex::VERTEX_RADIUS*2; + window->SetDrawColor(rules::RenderColor(color)); + window->DrawLine(x, y+h/2, x+w/4, y+h/2); + window->SetTextSize(48); + window->DrawText(std::to_string(GetEdgePoints(color)), + gui::Position(x+w, y), gui::Alignment::RIGHT, gui::Alignment::TOP); +} + +void ColorLoader::RenderVertexColorPoints(gui::Window* window, gui::Color color, + int x, int y) const +{ + const int w = COLOR_POINTS_COLUMN_WIDTH, r = Vertex::VERTEX_RADIUS; + window->SetDrawColor(rules::RenderColor(color)); + window->DrawCircle(x+r, y+r, r, false); + window->SetTextSize(48); + window->DrawText(std::to_string(GetVertexPoints(color)), + gui::Position(x+w, y+r), gui::Alignment::RIGHT, gui::Alignment::CENTER); +} + + + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/colorloader.hpp b/src/graphcoloring/levels/colorloader.hpp new file mode 100644 index 0000000..c5f52cc --- /dev/null +++ b/src/graphcoloring/levels/colorloader.hpp @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_COLORLOADER_H_ +#define GRAPHCOLORING_LEVELS_COLORLOADER_H_ + +#include +#include + +#include "gui/window.hpp" +#include "pugi/pugixml.hpp" + +namespace graphcoloring { + +class ColorLoader { +public: + ColorLoader(); + virtual ~ColorLoader(){} + void LoadDocument(const pugi::xml_document& document); + gui::Color GetColorByName(std::string name) const; + gui::Color GetColorFromAttribute(pugi::xml_attribute attr) const; + std::string GetColorName(gui::Color color) const; + int GetVertexPoints(gui::Color color) const; + int GetEdgePoints(gui::Color color) const; + void RenderColorPoints(gui::Window* window) const; + std::map color_names; + std::map vertex_color_points; + std::map edge_color_points; +private: + static constexpr int COLOR_POINTS_COLUMN_WIDTH = 200; + void RenderVertexColorPoints(gui::Window* window, gui::Color color, + int x, int y) const; + void RenderEdgeColorPoints(gui::Window* window, gui::Color color, + int x, int y) const; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_COLORLOADER_H_ diff --git a/src/graphcoloring/levels/globalloader.cpp b/src/graphcoloring/levels/globalloader.cpp new file mode 100644 index 0000000..05b8cda --- /dev/null +++ b/src/graphcoloring/levels/globalloader.cpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "globalloader.hpp" + +namespace graphcoloring { + +GlobalLoader::GlobalLoader() +{ +} + +void GlobalLoader::LoadDocument(const pugi::xml_document& document) +{ + vertex_protections = + document.child("global-vertex-protections").attribute("protect").value(); + edge_protections = + document.child("global-edge-protections").attribute("protect").value(); +} + +bool GlobalLoader::IsVertexProtected(char protection) const +{ + return vertex_protections.find(protection) != std::string::npos; +} + +bool GlobalLoader::IsEdgeProtected(char protection) const +{ + return edge_protections.find(protection) != std::string::npos; +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/globalloader.hpp b/src/graphcoloring/levels/globalloader.hpp new file mode 100644 index 0000000..7a0b739 --- /dev/null +++ b/src/graphcoloring/levels/globalloader.hpp @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_GLOBALLOADER_H_ +#define GRAPHCOLORING_LEVELS_GLOBALLOADER_H_ + +#include "pugi/pugixml.hpp" + +namespace graphcoloring { + +class GlobalLoader { +public: + GlobalLoader(); + virtual ~GlobalLoader() {} + void LoadDocument(const pugi::xml_document& document); + bool IsVertexProtected(char protection) const; + bool IsEdgeProtected(char protection) const; +private: + std::string vertex_protections; + std::string edge_protections; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_GLOBALLOADER_H_ diff --git a/src/graphcoloring/levels/graphloader.cpp b/src/graphcoloring/levels/graphloader.cpp new file mode 100644 index 0000000..9b8b4b4 --- /dev/null +++ b/src/graphcoloring/levels/graphloader.cpp @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "graphloader.hpp" + +#include + +#include "utils/errors.hpp" +#include "../level.hpp" +#include "../graphcoloring.hpp" + +namespace graphcoloring { + +GraphLoader::GraphLoader(const ColorLoader& color_loader_, + const GlobalLoader& global_loader_) + : color_loader(color_loader_), global_loader(global_loader_) +{} + +void GraphLoader::LoadDocument(const pugi::xml_document& document, Graph& graph) +{ + pugi::xml_node graph_node = document.child("graph"); + for (pugi::xml_node vertex_node : graph_node.children("vertex")) + ReadVertex(vertex_node, graph); + for (pugi::xml_node edge_node : graph_node.children("edge")) + ReadEdge(edge_node, graph); +} + +bool GraphLoader::IsVertexProtected(std::string protections, char protection) + const +{ + bool is_protected = protections.find(protection) != std::string::npos; + if (global_loader.IsVertexProtected(protection)) + is_protected = true; + return is_protected; +} +bool GraphLoader::IsEdgeProtected(std::string protections, char protection) + const +{ + bool is_protected = protections.find(protection) != std::string::npos; + if (global_loader.IsEdgeProtected(protection)) + is_protected = true; + return is_protected; +} + +void GraphLoader::ReadVertex(pugi::xml_node vertex_node, Graph& graph) +{ + int x = vertex_node.attribute("x").as_int(); + int y = vertex_node.attribute("y").as_int(); + + int v_id = graph.AddVertex(x, y); + Vertex& v = graph.GetVertexByID(v_id); + + if (!vertex_node.attribute("origin").empty()) + { + v.is_delete_protected = true; + graph.AddOrigin(v_id); + } + + + + if (!vertex_node.attribute("id").empty()) + { + vertex_ids[vertex_node.attribute("id").value()] = v.id; + } + + gui::Color color + = color_loader.GetColorFromAttribute(vertex_node.attribute("color")); + v.ChangeColor(color); + // NOTE: This next line does handle the case where there is no protect + // specified; pugixml will default to "". + std::string protections = vertex_node.attribute("protect").value(); + v.is_color_protected = IsVertexProtected(protections, PROTECT_COLOR); + v.is_delete_protected = IsVertexProtected(protections, PROTECT_DELETE); + v.is_edge_protected = IsVertexProtected(protections, PROTECT_EDGE); +} + +void GraphLoader::ReadEdge(pugi::xml_node edge_node, Graph& graph) +{ + std::string v1_name = edge_node.attribute("v1").value(); + std::string v2_name = edge_node.attribute("v2").value(); + + int v1 = GetVertexByName(v1_name); + int v2 = GetVertexByName(v2_name); + if (v1 == -1) + utils::errors::Die("There is no vertex named " + v1_name); + if (v2 == -1) + utils::errors::Die("There is no vertex named " + v2_name); + + int e_id = graph.AddEdge(v1, v2); + Edge& e = graph.GetEdgeByID(e_id); + if (!edge_node.attribute("id").empty()) + { + edge_ids[edge_node.attribute("id").value()] = e.id; + } + gui::Color color + = color_loader.GetColorFromAttribute(edge_node.attribute("color")); + e.ChangeColor(color); + + std::string protections = edge_node.attribute("protect").value(); + e.is_color_protected = IsEdgeProtected(protections, PROTECT_COLOR); + e.is_delete_protected = IsEdgeProtected(protections, PROTECT_DELETE); +} + +void GraphLoader::WriteVertex(const Vertex& vertex, pugi::xml_node& node) const +{ + node.set_name("vertex"); + node.append_attribute("x") = vertex.x; + node.append_attribute("y") = vertex.y; + node.append_attribute("id") = vertex.id; + node.append_attribute("color") = color_loader.GetColorName(vertex.Color()) + .c_str(); + std::stringstream protections; + if (vertex.is_color_protected) protections << PROTECT_COLOR; + if (vertex.is_delete_protected) protections << PROTECT_DELETE; + if (vertex.is_edge_protected) protections << PROTECT_EDGE; + node.append_attribute("protect") = protections.str().c_str(); +} + +void GraphLoader::WriteEdge(const Edge& edge, pugi::xml_node& node) const +{ + node.set_name("edge"); + node.append_attribute("v1") = edge.from.id; + node.append_attribute("v2") = edge.to.id; + node.append_attribute("id") = edge.id; + node.append_attribute("color") = color_loader.GetColorName(edge.Color()) + .c_str(); + std::stringstream protections; + if (edge.is_color_protected) protections << PROTECT_COLOR; + if (edge.is_delete_protected) protections << PROTECT_DELETE; + node.append_attribute("protect") = protections.str().c_str(); +} + +void GraphLoader::WriteGraph(const Graph& graph, pugi::xml_node& node) const +{ + for (const Vertex* v : graph.vertices) + { + pugi::xml_node vertex_node = node.append_child("vertex"); + WriteVertex(*v, vertex_node); + } + for (const Edge* e : graph.edges) + { + pugi::xml_node edge_node = node.append_child("edge"); + WriteEdge(*e, edge_node); + } +} + +int GraphLoader::GetVertexByName(const std::string& name) const +{ + if (vertex_ids.count(name)) + return vertex_ids.at(name); + else + return -1; +} + +int GraphLoader::GetEdgeByName(const std::string& name) const +{ + if (edge_ids.count(name)) + return edge_ids.at(name); + else + return -1; +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/graphloader.hpp b/src/graphcoloring/levels/graphloader.hpp new file mode 100644 index 0000000..b17f7e8 --- /dev/null +++ b/src/graphcoloring/levels/graphloader.hpp @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_GRAPHLOADER_H_ +#define GRAPHCOLORING_LEVELS_GRAPHLOADER_H_ + +#include + +#include "../graphs/graph.hpp" +#include "pugi/pugixml.hpp" +#include "colorloader.hpp" +#include "globalloader.hpp" + +namespace graphcoloring { + +class GraphLoader { +public: + GraphLoader(const ColorLoader& color_loader, + const GlobalLoader& global_loader); + virtual ~GraphLoader() {} + // Load document into graph + void LoadDocument(const pugi::xml_document& document, Graph& graph); + // Returns id of vertex, or -1 if there is no vertex with that name. + int GetVertexByName(const std::string& name) const; + int GetEdgeByName(const std::string& name) const; + void WriteGraph(const Graph& graph, pugi::xml_node& node) const; + std::map vertex_ids; + std::map edge_ids; +private: + bool IsVertexProtected(std::string protections, char protection) const; + bool IsEdgeProtected(std::string protections, char protection) const; + void ReadVertex(pugi::xml_node vertex_node, Graph& graph); + void ReadEdge(pugi::xml_node edge_node, Graph& graph); + void WriteVertex(const Vertex& vertex, pugi::xml_node& node) const; + void WriteEdge(const Edge& edge, pugi::xml_node& node) const; + const ColorLoader& color_loader; + const GlobalLoader& global_loader; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_GRAPHLOADER_H_ diff --git a/src/graphcoloring/levels/paths/path.cpp b/src/graphcoloring/levels/paths/path.cpp new file mode 100644 index 0000000..6528b18 --- /dev/null +++ b/src/graphcoloring/levels/paths/path.cpp @@ -0,0 +1,207 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "path.hpp" + +#include +#include + +namespace graphcoloring { + +constexpr gui::Color Path::ANY_COLOR; + +Path::Path(gui::Window* window_, + Graph& graph_, const RuleLoader& rule_loader_, + const ColorLoader& color_loader_) + : window(window_), graph(graph_), rule_loader(rule_loader_), + color_loader(color_loader_) +{ + window->SetMouseupCallback([this] (gui::Window*,int,int) { + RightClick(); + }, GDK_BUTTON_SECONDARY); + window->SetKeyupCallback([this] (gui::Window*) { + ResetPath(); + }, GDK_KEY_q); +} + +void Path::LoadFromNode(pugi::xml_node node) +{ + std::string name = node.name(); + + if (name == "cycle") + type = Type::CYCLE; + else + type = Type::PATH; + + points_starting_value = node.attribute("points").as_int(0); + + for (pugi::xml_node point_node : node.children()) + { + std::string name = point_node.name(); + if (name != "edge") continue; + std::string color_str = point_node.attribute("color").value(); + gui::Color color; + if (color_str == "any") + color = ANY_COLOR; + else + color = + color_loader.GetColorFromAttribute( + point_node.attribute("color")); + color_operations[color] = LoadOperation(point_node); + } +} + +void Path::LoadFromDocument(const pugi::xml_document& document) +{ + for (pugi::xml_node node : document.children()) + { + std::string name = node.name(); + if (name != "cycle" && name != "path") continue; + LoadFromNode(node); + break; + } +} + +Path::operation_t Path::LoadOperation(pugi::xml_node node) +{ + int val = node.attribute("val").as_int(0); + std::map operations = { + {"+", [val] (int a) { return a + val; }}, + {"-", [val] (int a) { return a - val; }}, + {"*", [val] (int a) { return a * val; }}, + {"/", [val] (int a) { return a / val; }}, + {"%", [val] (int a) { return a % val; }} + }; + std::string op = node.attribute("op").value(); + return operations[op]; +} + +void Path::RightClick() +{ + if (!IsPath()) return; + if (!rule_loader.IsValid(graph)) return; + int vertex_id = graph.GetHoveringVertex(); + if (vertex_id == -1) return; + graph.Lock(); + if (last_vertex == -1) + { + last_vertex = vertex_id; + if (first_vertex == -1) + first_vertex = vertex_id; + return; + } + if (last_vertex == vertex_id) // Remove end of path + { + if (path.size() == 0) + { + ResetPath(); + return; + } + int edge_id = path.at(path.size()-1); + path.pop_back(); + last_vertex = + graph.GetEdgeByIDConst(edge_id).OtherEndpoint(last_vertex); + return; + } + if (!graph.HasEdge(last_vertex, vertex_id)) + { + return; + } + int edge_id = graph.GetEdgeByEndpointsConst(last_vertex, vertex_id).id; + if (std::find(path.begin(), path.end(), edge_id) != path.end()) // Duplicate edge + return; + path.push_back(edge_id); + last_vertex = vertex_id; +} + +void Path::ResetPath() +{ + if (!IsPath()) return; + graph.Unlock(); + path = std::vector(); + is_making_path = false; + first_vertex = -1; + last_vertex = -1; +} + +std::vector Path::GetPath() const +{ + return path; +} + +std::set Path::PathEdgeSet() const +{ + return std::set(path.begin(), path.end()); + +} + +std::set Path::PathVertexSet() const +{ + std::set vertices; + if (last_vertex != -1) + vertices.insert(last_vertex); + for (int e : path) + { + const Edge& edge = graph.GetEdgeByIDConst(e); + vertices.insert(edge.from.id); + vertices.insert(edge.to.id); + } + return vertices; +} + +int Path::LastVertex() const +{ + return last_vertex; +} + +bool Path::IsMakingPath() const +{ + return is_making_path; +} + +bool Path::IsPath() const +{ + return type != Type::NO_PATH; +} + +int Path::Points() const +{ + if (!IsPath()) return 0; + if (type == Type::CYCLE) + { + if (path.size() < 2) + return 0; + if (first_vertex != last_vertex) + return 0; + } + int points = points_starting_value; + for (int e : path) + { + const Edge& edge = graph.GetEdgeByIDConst(e); + if (color_operations.count(edge.Color()) + && color_operations.at(edge.Color())) + points = color_operations.at(edge.Color())(points); + + if (color_operations.count(ANY_COLOR) + && color_operations.at(ANY_COLOR)) + points = color_operations.at(ANY_COLOR)(points); + } + return points; +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/paths/path.hpp b/src/graphcoloring/levels/paths/path.hpp new file mode 100644 index 0000000..294b1a2 --- /dev/null +++ b/src/graphcoloring/levels/paths/path.hpp @@ -0,0 +1,70 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_PATHS_PATH_H_ +#define GRAPHCOLORING_LEVELS_PATHS_PATH_H_ + +#include "gui/window.hpp" +#include "graphcoloring/levels/rules/ruleloader.hpp" + +namespace graphcoloring { + +class Path { +public: + Path(gui::Window* window, + Graph& graph, const RuleLoader& rule_loader, + const ColorLoader& color_loader); + virtual ~Path() {} + void LoadFromDocument(const pugi::xml_document& document); + void ResetPath(); + bool IsPath() const; // Is there a path in the document? + std::vector GetPath() const; + std::set PathEdgeSet() const; + std::set PathVertexSet() const; + int LastVertex() const; // -1 if no last vertex + bool IsMakingPath() const; + int Points() const; +private: + enum class Type + { + NO_PATH, // There is no path in the document. + CYCLE, + PATH + }; + typedef std::function operation_t; + static constexpr gui::Color ANY_COLOR = 0; + void LoadFromNode(pugi::xml_node node); + void RightClick(); + operation_t LoadOperation(pugi::xml_node node); + gui::Window* window; + Graph& graph; + const RuleLoader& rule_loader; + const ColorLoader& color_loader; + Type type = Type::NO_PATH; + bool is_making_path = false; + int first_vertex = -1; + int last_vertex = -1; + std::vector path; + int points_starting_value = 0; + std::map color_operations; + +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_PATHS_PATH_H_ diff --git a/src/graphcoloring/levels/pointcalculator.cpp b/src/graphcoloring/levels/pointcalculator.cpp new file mode 100644 index 0000000..09f761e --- /dev/null +++ b/src/graphcoloring/levels/pointcalculator.cpp @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "pointcalculator.hpp" + +namespace graphcoloring { + +PointCalculator::PointCalculator(const ValueLoader& value_loader_, + const RuleLoader& rule_loader_, const ColorLoader& color_loader_, + const Path& path_) + : value_loader(value_loader_), rule_loader(rule_loader_), + color_loader(color_loader_), path(path_) +{} + +int PointCalculator::Points(const Graph& graph, bool check_if_invalid) const +{ + if (check_if_invalid) + { + bool is_valid = rule_loader.IsValid(graph); + if (!is_valid) return 0; + } + int points = value_loader.Points(graph); + for (const Vertex* v : graph.vertices) + points += color_loader.GetVertexPoints(v->Color()); + for (const Edge* e : graph.edges) + points += color_loader.GetEdgePoints(e->Color()); + + points += path.Points(); + + return points; +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/pointcalculator.hpp b/src/graphcoloring/levels/pointcalculator.hpp new file mode 100644 index 0000000..5baee6c --- /dev/null +++ b/src/graphcoloring/levels/pointcalculator.hpp @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_POINTCALCULATOR_H_ +#define GRAPHCOLORING_LEVELS_POINTCALCULATOR_H_ + +#include "valueloader.hpp" +#include "colorloader.hpp" +#include "rules/ruleloader.hpp" +#include "paths/path.hpp" + +namespace graphcoloring { + +class PointCalculator { +public: + PointCalculator(const ValueLoader& value_loader, + const RuleLoader& rule_loader, const ColorLoader& color_loader, + const Path& path); + int Points(const Graph& graph, bool check_if_invalid = true) const; + virtual ~PointCalculator() {} +private: + const ValueLoader& value_loader; + const RuleLoader& rule_loader; + const ColorLoader& color_loader; + const Path& path; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_POINTCALCULATOR_H_ diff --git a/src/graphcoloring/levels/rules/boundrule.cpp b/src/graphcoloring/levels/rules/boundrule.cpp new file mode 100644 index 0000000..0aef4af --- /dev/null +++ b/src/graphcoloring/levels/rules/boundrule.cpp @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "boundrule.hpp" + +#include "graphcoloring/level.hpp" +#include "utils/errors.hpp" + +namespace graphcoloring { +namespace rules { + +BoundRule::BoundRule(gui::Color color_, int bound_, bool bound_type_, + bool rule_type_) + : color(color_), bound(bound_), bound_type(bound_type_), + rule_type(rule_type_) +{} + + +BoundRule::BoundRule(pugi::xml_node node, const ColorLoader& color_loader) +{ + LoadFromNode(node, color_loader); +} + +void BoundRule::LoadFromNode(pugi::xml_node node, + const ColorLoader& color_loader) +{ + std::string name(node.name()); + if (name.substr(name.length()-7) == std::string("minimum")) + bound_type = MINIMUM; + else + bound_type = MAXIMUM; + + if (name.substr(0,name.length()-8) == std::string("vertex")) + rule_type = VERTEX_RULE; + else + rule_type = EDGE_RULE; + color = ColorFromAttribute(node.attribute("color"), color_loader); + bound = node.attribute(bound_type == MINIMUM ? "min" : "max").as_int(0); +} + +bool BoundRule::CheckAllCounts(const Graph& graph) const +{ + std::map counts; + for (gui::Color color : Level::colors) + counts[color] = 0; + if (rule_type == VERTEX_RULE) + { + for (const Vertex* v : graph.vertices) + { + if (counts.count(v->Color())) + counts[v->Color()]++; + } + } + else + { + for (const Edge* e : graph.edges) + { + if (counts.count(e->Color())) + counts[e->Color()]++; + else + counts[e->Color()] = 1; + } + } + for (std::pair count : counts) + { + if (bound_type == MINIMUM && count.second < bound) + return false; + if (bound_type == MAXIMUM && count.second > bound) + return false; + } + return true; +} + +bool BoundRule::ObeysRule(const Graph& graph) const +{ + if (color == SAME_COLOR) // For same, check if any color is out of bounds. + return CheckAllCounts(graph); + + int count = 0; + if (rule_type == VERTEX_RULE) + { + for (const Vertex* v : graph.vertices) + if (IsSameColor(v->Color(), color)) + count++; + } + else + { + for (const Edge* e : graph.edges) + if (IsSameColor(e->Color(), color)) + count++; + } + return bound_type == MINIMUM ? (count >= bound) : (count <= bound); +} + +int BoundRule::Render(gui::Window* window, int x, int y, int width) const +{ + int r = Vertex::VERTEX_RADIUS; + window->SetDrawColor(RenderColor(color)); + if (rule_type == VERTEX_RULE) + { + window->DrawCircle(x+r, y+r, r, false); + } + else + { + window->DrawLine(x, y+r, x+width/4, y+r); + } + window->SetTextSize(r*2); + window->DrawText(std::to_string(bound), gui::Position(x+width, y+r), + gui::Alignment::RIGHT, gui::Alignment::CENTER); + window->DrawText(bound_type == MAXIMUM ? ">" : "<", + gui::Position(x+width/2, y+r), + gui::Alignment::CENTER, gui::Alignment::CENTER); + window->SetDrawColor(0xFF0000FF); + window->DrawLine(x+3*width/8, y, x+5*width/8, y+2*r); + return r*2; +} + + +} // namespace rules +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/rules/boundrule.hpp b/src/graphcoloring/levels/rules/boundrule.hpp new file mode 100644 index 0000000..a1c5e09 --- /dev/null +++ b/src/graphcoloring/levels/rules/boundrule.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_RULES_BOUNDRULE_H_ +#define GRAPHCOLORING_LEVELS_RULES_BOUNDRULE_H_ + +#include "graphcoloring/graphs/graph.hpp" + +#include "rules.hpp" +#include "rule.hpp" + +namespace graphcoloring { +namespace rules { + +class BoundRule : public Rule { +public: + BoundRule(gui::Color color = ANY_COLOR, + int bound = 0, bool bound_type = MAXIMUM, + bool rule_type = VERTEX_RULE); + BoundRule(pugi::xml_node node, const ColorLoader& color_loader); + void LoadFromNode(pugi::xml_node node, const ColorLoader& color_loader); + virtual ~BoundRule() {} + bool ObeysRule(const Graph& graph) const; + int Render(gui::Window* window, int x, int y, int width) const; +private: + bool CheckAllCounts(const Graph& graph) const; + static constexpr bool EDGE_RULE = false; + static constexpr bool VERTEX_RULE = true; + static constexpr bool MINIMUM = false; + static constexpr bool MAXIMUM = true; + gui::Color color; + int bound; + bool bound_type; + bool rule_type; +}; + +} // namespace rules +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_RULES_BOUNDRULE_H_ diff --git a/src/graphcoloring/levels/rules/edgerule.cpp b/src/graphcoloring/levels/rules/edgerule.cpp new file mode 100644 index 0000000..e2a3a74 --- /dev/null +++ b/src/graphcoloring/levels/rules/edgerule.cpp @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "edgerule.hpp" + +namespace graphcoloring { +namespace rules { + +EdgeRule::EdgeRule(gui::Color vcolor1, gui::Color vcolor2, gui::Color ecolor) + : vertex_color1(vcolor1), vertex_color2(vcolor2), edge_color(ecolor) +{} + +EdgeRule::EdgeRule(pugi::xml_node node, const ColorLoader& color_loader) +{ + LoadFromNode(node, color_loader); +} + +void EdgeRule::LoadFromNode(pugi::xml_node node,const ColorLoader& color_loader) +{ + vertex_color1 = ColorFromAttribute(node.attribute("v1"), color_loader); + vertex_color2 = ColorFromAttribute(node.attribute("v2"), color_loader); + edge_color = ColorFromAttribute(node.attribute("edge"), color_loader); +} + +bool EdgeRule::ObeysRule(const Edge& edge) const +{ + ResetSameColor(); + if (!IsSameColor(edge.Color(), edge_color)) return true; + + gui::Color v1 = edge.from.Color(); + gui::Color v2 = edge.to.Color(); + bool obeys + = !((IsSameColor(v1,vertex_color1) && IsSameColor(v2,vertex_color2)) + || (IsSameColor(v2,vertex_color1) && IsSameColor(v1,vertex_color2))); + ResetSameColor(); + return obeys; +} + +bool EdgeRule::ObeysRule(const Graph& graph) const +{ + for (const Edge* e : graph.edges) + if (!ObeysRule(*e)) + return false; + return true; +} + +int EdgeRule::Render(gui::Window* window, int x, int y, int width) const +{ + int r = Vertex::VERTEX_RADIUS; + // First vertex + window->SetDrawColor(RenderColor(vertex_color1)); + window->DrawCircle(x+r, y+r, r, false); + // Edge + window->SetDrawColor(RenderColor(edge_color)); + window->DrawLine(x+2*r, y+r, x+width-2*r, y+r); + // Second vertex + window->SetDrawColor(RenderColor(vertex_color2)); + window->DrawCircle(x+width-r, y+r, r, false); + // Red line + window->SetDrawColor(0xFF0000FF); + window->DrawLine(x+width/2-r/2, y+r/2, x+width/2+r/2, y+3*r/2); + return r*2; + +} + +} // namespace rules +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/rules/edgerule.hpp b/src/graphcoloring/levels/rules/edgerule.hpp new file mode 100644 index 0000000..187baf7 --- /dev/null +++ b/src/graphcoloring/levels/rules/edgerule.hpp @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GRAPHCOLORING_LEVELS_RULES_EDGERULE_H_ +#define GRAPHCOLORING_LEVELS_RULES_EDGERULE_H_ + +#include "graphcoloring/graphs/graph.hpp" +#include "rules.hpp" +#include "rule.hpp" + +namespace graphcoloring { +namespace rules { + +class EdgeRule : public Rule { +public: + EdgeRule(gui::Color vertex_color1 = ANY_COLOR, + gui::Color vertex_color2 = ANY_COLOR, + gui::Color edge_color = ANY_COLOR); + EdgeRule(pugi::xml_node node, const ColorLoader& color_loader); + void LoadFromNode(pugi::xml_node node, const ColorLoader& color_loader); + bool ObeysRule(const Graph& graph) const; + int Render(gui::Window* window, int x, int y, int width) const; + virtual ~EdgeRule() {} +private: + bool ObeysRule(const Edge& edge) const; + gui::Color vertex_color1; + gui::Color vertex_color2; + gui::Color edge_color; + +}; + +} // namespace rules +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_RULES_EDGERULE_H_ diff --git a/src/graphcoloring/levels/rules/rule.hpp b/src/graphcoloring/levels/rules/rule.hpp new file mode 100644 index 0000000..5856fc0 --- /dev/null +++ b/src/graphcoloring/levels/rules/rule.hpp @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_RULES_RULE_H_ +#define GRAPHCOLORING_LEVELS_RULES_RULE_H_ + +#include "graphcoloring/graphs/graph.hpp" + +namespace graphcoloring { +namespace rules { + +class Rule { +public: + virtual ~Rule() {} + virtual bool ObeysRule(const Graph& graph) const = 0; + virtual int Render(gui::Window* window, int x, int y, int width) const = 0; // Returns height +}; + +} // namespace rules +} // namespace graphcoloring + +#endif /* GRAPHCOLORING_LEVELS_RULES_RULE_H_ */ diff --git a/src/graphcoloring/levels/rules/ruleloader.cpp b/src/graphcoloring/levels/rules/ruleloader.cpp new file mode 100644 index 0000000..e741594 --- /dev/null +++ b/src/graphcoloring/levels/rules/ruleloader.cpp @@ -0,0 +1,92 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "ruleloader.hpp" + +#include "graphcoloring/graphcoloring.hpp" + +namespace graphcoloring { + +RuleLoader::RuleLoader() {} + +void RuleLoader::LoadDocument(const pugi::xml_document& document, + const ColorLoader& color_loader) +{ + for (pugi::xml_node rule_node : document.child("rules").children()) + { + std::string name = rule_node.name(); + if (name == "connected") + connected_rule = true; + // These node methods will check the name of the node. + LoadEdgeRule(rule_node, color_loader); + LoadMaximumRule(rule_node, color_loader); + + } +} + +void RuleLoader::LoadEdgeRule(pugi::xml_node node, + const ColorLoader& color_loader) +{ + std::string name = node.name(); + if (name != "edge-rule") return; + rules::EdgeRule rule(node, color_loader); + edge_rules.push_back(rule); + all_rules.push_back(std::make_unique(rule)); +} + +void RuleLoader::LoadMaximumRule(pugi::xml_node node, + const ColorLoader& color_loader) +{ + std::string name = node.name(); + if (name != "vertex-maximum" && name != "edge-maximum" + && name != "vertex-minimum" && name != "edge-minimum") + return; + rules::BoundRule rule(node, color_loader); + maximum_rules.push_back(rule); + all_rules.push_back(std::make_unique(rule)); +} + + +bool RuleLoader::IsValid(const Graph& graph) const +{ + for (auto& rule : all_rules) + if (!rule->ObeysRule(graph)) + return false; + if (connected_rule && !graph.IsConnected()) + return false; + return true; +} + +void RuleLoader::RenderRules(gui::Window* window) const +{ + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + window->Clear(); + int x = 10, y = 10; + for (auto& rule : all_rules) + { + if (y >= window->GetHeight()-2*Vertex::VERTEX_RADIUS-10) + { + y = 10; + x += 50 + RULE_COLUMN_WIDTH; + } + int height = rule->Render(window, x, y, RULE_COLUMN_WIDTH); + y += height + 10; + } +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/rules/ruleloader.hpp b/src/graphcoloring/levels/rules/ruleloader.hpp new file mode 100644 index 0000000..8089cb5 --- /dev/null +++ b/src/graphcoloring/levels/rules/ruleloader.hpp @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_RULELOADER_H_ +#define GRAPHCOLORING_LEVELS_RULELOADER_H_ + +#include "pugi/pugixml.hpp" + +#include "graphcoloring/levels/colorloader.hpp" +#include "graphcoloring/graphs/graph.hpp" +#include "boundrule.hpp" +#include "edgerule.hpp" + +namespace graphcoloring { + +class RuleLoader { +public: + static constexpr gui::Color ANY_COLOR = 0x00000000; + RuleLoader(); + virtual ~RuleLoader() {} + void LoadDocument(const pugi::xml_document& document, + const ColorLoader& color_loader); + bool IsValid(const Graph& graph) const; // O(Rules * Edges) + void RenderRules(gui::Window* window) const; +private: + void LoadEdgeRule(pugi::xml_node node, const ColorLoader& color_loader); + void LoadMaximumRule(pugi::xml_node node, const ColorLoader& color_loader); + static constexpr int RULE_COLUMN_WIDTH = 200; + std::vector edge_rules; + std::vector maximum_rules; + bool connected_rule = false; // true if the graph should be connected + std::vector> all_rules; + +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_RULELOADER_H_ diff --git a/src/graphcoloring/levels/rules/rules.cpp b/src/graphcoloring/levels/rules/rules.cpp new file mode 100644 index 0000000..2b9537f --- /dev/null +++ b/src/graphcoloring/levels/rules/rules.cpp @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "rules.hpp" + +namespace graphcoloring { +namespace rules { + +static gui::Color same_color = ANY_COLOR; + +void ResetSameColor() { same_color = ANY_COLOR; } + +bool IsSameColor(gui::Color color1, gui::Color color2) +{ + if (color1 == ANY_COLOR || color2 == ANY_COLOR) + return true; + + if (same_color == ANY_COLOR) + { + if (color1 == SAME_COLOR && color2 != SAME_COLOR) + { + same_color = color2; + return true; + } + if (color2 == SAME_COLOR && color1 != SAME_COLOR) + { + same_color = color1; + return true; + } + } + if (color1 == SAME_COLOR && color2 == same_color) + return true; + if (color2 == SAME_COLOR && color1 == same_color) + return true; + + return color1 == color2; +} + +gui::Color RenderColor(gui::Color color) +{ + if (color == ANY_COLOR) + return gui::colors::WHITE; + if (color == SAME_COLOR) + return 0x666666FF; + return color; +} + +gui::Color ColorFromAttribute(pugi::xml_attribute attr, + const ColorLoader& color_loader) +{ + if (attr.empty()) return ANY_COLOR; + if (std::string(attr.value()) == "same") return SAME_COLOR; + return attr.empty() ? ANY_COLOR : color_loader.GetColorFromAttribute(attr); +} + +} // namespace rules +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/rules/rules.hpp b/src/graphcoloring/levels/rules/rules.hpp new file mode 100644 index 0000000..2e6d83c --- /dev/null +++ b/src/graphcoloring/levels/rules/rules.hpp @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef GRAPHCOLORING_LEVELS_RULES_RULES_H_ +#define GRAPHCOLORING_LEVELS_RULES_RULES_H_ + +#include "pugi/pugixml.hpp" +#include "../colorloader.hpp" + +namespace graphcoloring { +namespace rules { + +constexpr gui::Color ANY_COLOR = 0; +constexpr gui::Color SAME_COLOR = 1; // Refers to patterns like same-red-same, which can mean blue-red-blue, red-red-red, etc. +extern void ResetSameColor(); // Forget about "same" color +extern bool IsSameColor(gui::Color color1, gui::Color color2); +extern gui::Color RenderColor(gui::Color color); // Turns ANY_COLOR into white. +extern gui::Color ColorFromAttribute(pugi::xml_attribute attr, + const ColorLoader& color_loader); // ANY_COLOR if attribute is empty. + +} // namespace rules +} // namespace graphcoloring + + + +#endif /* SRC_GRAPHCOLORING_LEVELS_RULES_RULES_HPP_ */ diff --git a/src/graphcoloring/levels/value.cpp b/src/graphcoloring/levels/value.cpp new file mode 100644 index 0000000..f84d019 --- /dev/null +++ b/src/graphcoloring/levels/value.cpp @@ -0,0 +1,304 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "value.hpp" + +#include +#include + +#include "utils/errors.hpp" + +namespace graphcoloring { + +std::map Value::operation_table; + +Value::Value() : type(Type::NULL_TYPE) {} + +Value::Value(int val_) +{ + val = val_; + type = Type::VALUE; +} + +Value::Value(std::vector list_) +{ + list = list_; + type = Type::LIST; +} + +Value::Value(const std::string& string) +{ + FromString(string); +} + +void Value::FromString(const std::string& string) +{ + const std::map type_map = + { + {"", Type::NULL_TYPE}, + {"V", Type::NUMBER_OF_VERTICES}, + {"E", Type::NUMBER_OF_EDGES}, + {"vertices", Type::VERTICES}, + {"edges", Type::EDGES} + }; + if (type_map.count(string)) + { + type = type_map.at(string); + return; + } + try + { + val = std::stoi(string); + type = Type::VALUE; + } + catch (std::invalid_argument&) + { + // NOTE: Does not check if variable exists! This means that errors + // will only be caught when the variable is evaluated, but it allows + // out-of-order declaration. + type = Type::VARIABLE; + variable_name = string; + } +} + + +Value::Value(pugi::xml_node node) +{ + std::string name = node.name(); + if (name == "op") + { + std::string v1 = node.attribute("val1").value(); + std::string v2 = node.attribute("val2").value(); + std::string op = node.attribute("op").value(); + type = Type::OPERATION; + val1 = std::shared_ptr(new Value(v1)); + val2 = std::shared_ptr(new Value(v2)); + ReadOperation(op); + } + else if (name == "var") + { + FromString(node.attribute("val").value()); + } + else if (name == "map" || name == "fold") + { + if (name == "map") + type = Type::MAP; + else + type = Type::FOLD; + std::string op = node.attribute("op").value(); + std::string v = node.attribute("val").value(); + std::string start = node.attribute("start").value(); + if (v == "") + utils::errors::Die("No argument passed to " + name + "."); + ReadOperation(op); + val1 = std::shared_ptr(new Value(v)); + fold_start = std::shared_ptr(new Value(start)); + } + else if (name == "zip") + { + type = Type::ZIP; + std::string op = node.attribute("op").value(); + std::string v1 = node.attribute("val1").value(); + std::string v2 = node.attribute("val2").value(); + if (v1 == "" || v2 == "") + utils::errors::Die("Empty argument passed to zip."); + ReadOperation(op); + val1 = std::shared_ptr(new Value(v1)); + val2 = std::shared_ptr(new Value(v2)); + } + else + { + utils::errors::Die("Unknown tag: " + name); + } +} + +Value::operation_t Value::SimpleOperation(std::function op) +{ + return [op](const Graph&, int a, int b) { return op(a, b); }; +} + +Value::operation_t Value::SimpleBoolOperation(std::function op) +{ + return [op](const Graph&, int a, int b) { return op(a, b) ? 1 : 0; }; +} + +Value::operation_t Value::SimpleLogicOperation(std::functionop) +{ + return [op](const Graph&, int a, int b) { + return op(a != 0, b != 0) ? 1 : 0; + }; +} + +void Value::InitializeOperationTable() +{ + operation_table = { + {"min", SimpleOperation([](int a, int b)->int{return std::min(a,b);})}, + {"max", SimpleOperation([](int a, int b)->int{return std::max(a,b);})}, + {"+", SimpleOperation(std::plus())}, + {"-", SimpleOperation(std::minus())}, + {"*", SimpleOperation(std::multiplies())}, + {"/", SimpleOperation(std::divides())}, + {"%", SimpleOperation(std::modulus())}, + {"=", SimpleBoolOperation(std::equal_to())}, + {"!=", SimpleBoolOperation(std::not_equal_to())}, + {"<", SimpleBoolOperation(std::less())}, + {">", SimpleBoolOperation(std::greater())}, + {"<=", SimpleBoolOperation(std::less_equal())}, + {">=", SimpleBoolOperation(std::greater_equal())}, + {"and", SimpleLogicOperation(std::logical_and())}, + {"or", SimpleLogicOperation(std::logical_or())}, + {"not", SimpleLogicOperation([](bool a, bool b)->bool{ + return !a; // Ignore second argument. + })}, + {"v1", [](const Graph& graph, int e, int)->int { + return graph.GetEdgeByIDConst(e).from.id; + }}, + {"v2", [](const Graph& graph, int e, int)->int { + return graph.GetEdgeByIDConst(e).to.id; + }}, + {"connected", [](const Graph& graph, int v1, int v2)->int { + if (!graph.HasVertexWithID(v1) || !graph.HasVertexWithID(v2)) + return 0; + return graph.HasEdge(v1, v2) ? 1 : 0; + }}, + {"degree", [](const Graph& graph, int v, int)->int { + return graph.Degree(v); + }}, + {"vertex-color", [](const Graph& graph, int v_id, int)->int { + const Vertex& v = graph.GetVertexByIDConst(v_id); + return (int)v.Color(); + }}, + {"edge-color", [](const Graph& graph, int e_id, int)->int { + const Edge& e = graph.GetEdgeByIDConst(e_id); + return (int)e.Color(); + }} + }; +} + +void Value::ReadOperation(std::string op) +{ + if (operation_table.size() == 0) + InitializeOperationTable(); + if (operation_table.count(op)) + { + operation = operation_table[op]; + } + else + { + utils::errors::Die("Invalid operation: " + op); + } +} + +std::vector Value::EvalListOperation(const Graph& graph, + std::function lookup_variable) const +{ + switch (type) + { + case Type::VARIABLE: + return lookup_variable(variable_name) + .EvalListOperation(graph, lookup_variable); + case Type::VERTICES: + { + std::vector list; + for (const Vertex* v : graph.vertices) + { + list.push_back(v->id); + } + return list; + } + case Type::EDGES: + { + std::vector list; + for (const Edge* e : graph.edges) + { + list.push_back(e->id); + } + return list; + } + case Type::LIST: + { + std::vector out; + for (Value v : list) + out.push_back(v.Eval(graph, lookup_variable)); + return out; + } + case Type::MAP: + { + std::vector in = val1->EvalListOperation(graph, lookup_variable); + std::vector out; + for (int v : in) + out.push_back(operation(graph, v, 0)); + return out; + } + case Type::ZIP: + { + std::vector in1= val1->EvalListOperation(graph, lookup_variable); + std::vector in2= val2->EvalListOperation(graph, lookup_variable); + std::vector out; + for (int i = 0; i < (int)std::min(in1.size(),in2.size()); i++) + out.push_back(operation(graph, in1[i], in2[i])); + return out; + } + default: break; + } + std::stringstream s; + s << "Invalid list operation: " << (int)type << "."; + utils::errors::Die(s.str()); + return std::vector(); +} + +int Value::Eval(const Graph& graph, + std::function lookup_variable) const +{ + switch (type) + { + case Type::NULL_TYPE: + return 0; + case Type::VALUE: + return val; + case Type::VARIABLE: + return lookup_variable(variable_name).Eval(graph, lookup_variable); + case Type::NUMBER_OF_VERTICES: + return graph.V(); + case Type::NUMBER_OF_EDGES: + return graph.E(); + case Type::OPERATION: + assert(operation); + return operation(graph, + val1->Eval(graph, lookup_variable), + val2->Eval(graph, lookup_variable)); + case Type::FOLD: + { + assert(operation); + int x = fold_start->Eval(graph, lookup_variable); + for (const Value& v : val1->EvalListOperation(graph, lookup_variable)) + { + x = operation(graph, x, v.Eval(graph, lookup_variable)); + } + return x; + } + + default: break; + } + std::stringstream s; + s << "Invalid operation: " << (int)type << "."; + utils::errors::Die(s.str()); + return -1; +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/value.hpp b/src/graphcoloring/levels/value.hpp new file mode 100644 index 0000000..789540e --- /dev/null +++ b/src/graphcoloring/levels/value.hpp @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_VALUE_H_ +#define GRAPHCOLORING_LEVELS_VALUE_H_ + + +#include "pugi/pugixml.hpp" +#include "../graphs/graph.hpp" + +namespace graphcoloring { + +class Value { +public: + typedef std::function operation_t; + Value(); + Value(std::vector list); + Value(int val); + Value(const std::string& string); + Value(pugi::xml_node node); + virtual ~Value() {} + int Eval(const Graph& graph, + std::function lookup_variable) const; + static void AddNodeToVariables(pugi::xml_node node); +private: + void FromString(const std::string& string); + void ReadOperation(std::string op); + static operation_t SimpleOperation(std::function op); // For operations which don't look at the graph. + // Bool operations return bools. + static operation_t SimpleBoolOperation(std::function op); + // Logical operations take bools and return bools. + static operation_t SimpleLogicOperation(std::function op); + static void InitializeOperationTable(); + std::vector EvalListOperation(const Graph& graph, // Handles anything that returns a list. + std::function lookup_variable) const; + operation_t operation; + static std::map operation_table; + std::shared_ptr fold_start; + std::shared_ptr val1; + std::shared_ptr val2; + std::string variable_name; + int val = 0; + std::vector list; + enum class Type + { + NULL_TYPE, + VALUE, // This value represents a constant number + LIST, + VARIABLE, + NUMBER_OF_VERTICES, + NUMBER_OF_EDGES, + VERTICES, + EDGES, + OPERATION, + LIST_OPERATION, + MAP, + ZIP, + FOLD + }; + Type type; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_VALUE_H_ diff --git a/src/graphcoloring/levels/valueloader.cpp b/src/graphcoloring/levels/valueloader.cpp new file mode 100644 index 0000000..527ce99 --- /dev/null +++ b/src/graphcoloring/levels/valueloader.cpp @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "valueloader.hpp" + +#include "../level.hpp" +#include "utils/errors.hpp" + +namespace graphcoloring { + +ValueLoader::ValueLoader() : objective_points(-1) {} + +void ValueLoader::AddNode(pugi::xml_node node) +{ + variables[node.attribute("id").value()] = Value(node); +} + +void ValueLoader::LoadGraph(const GraphLoader& graph_loader) +{ + for (const std::pair& v_id : graph_loader.vertex_ids) + variables[v_id.first] = v_id.second; + for (const std::pair& e_id : graph_loader.edge_ids) + variables[e_id.first] = e_id.second; +} + + +void ValueLoader::LoadColors(const ColorLoader& color_loader) +{ + for (std::pair color : color_loader.color_names) + { + variables[color.first] = (int)color.second; + } +} + +void ValueLoader::LoadDocument(const pugi::xml_document& document) +{ + for (pugi::xml_node node : document.child("values").children()) + { + std::string name = node.name(); + if (name == "comment") continue; + if (name == "include") + { + pugi::xml_document doc; + std::string filename = node.attribute("file").value(); + std::string path = "assets/levels/" + filename + ".xml"; + doc.load_file(path.c_str()); + LoadDocument(doc); + } + else + { + AddNode(node); + } + } +} + + +int ValueLoader::VariableValue(const Graph& graph, + const std::string& name) const +{ + if (variables.count(name) == 0) + utils::errors::Die("Variable not found: " + name); + std::function lookup_variable + = [this] (std::string name)->Value { + if (variables.count(name) == 0) + utils::errors::Die("Variable not found: " + name); + return variables.at(name); + }; + return variables.at(name).Eval(graph, lookup_variable); +} + +int ValueLoader::Points(const Graph& graph) const +{ + return VariableValue(graph, "points"); +} + +int ValueLoader::ObjectivePoints(const Graph& graph) const +{ + return VariableValue(graph, "objective"); +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levels/valueloader.hpp b/src/graphcoloring/levels/valueloader.hpp new file mode 100644 index 0000000..0035864 --- /dev/null +++ b/src/graphcoloring/levels/valueloader.hpp @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELS_VALUELOADER_H_ +#define GRAPHCOLORING_LEVELS_VALUELOADER_H_ + +#include "value.hpp" + +#include "graphloader.hpp" + +namespace graphcoloring { + +class ValueLoader { +public: + ValueLoader(); + virtual ~ValueLoader() {} + void LoadDocument(const pugi::xml_document& document); + void LoadGraph(const GraphLoader& graph_loader); // Loads edge & vertex IDs. + void LoadColors(const ColorLoader& color_loader); // Loads color names. + int VariableValue(const Graph& graph, const std::string& name) const; + int Points(const Graph& graph) const; + int ObjectivePoints(const Graph& graph) const; +private: + void AddNode(pugi::xml_node node); + std::map variables; + int objective_points; +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELS_VALUELOADER_H_ diff --git a/src/graphcoloring/levelselect.cpp b/src/graphcoloring/levelselect.cpp new file mode 100644 index 0000000..02107e8 --- /dev/null +++ b/src/graphcoloring/levelselect.cpp @@ -0,0 +1,167 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "levelselect.hpp" + +#include "utils/errors.hpp" +#include "graphcoloring.hpp" + +namespace graphcoloring { + +const char* const LevelSelect::LEVEL_LISTING_PATH = "saves/level-list.xml"; + +LevelSelect::LevelSelect(gui::Window* window_, + level_click_callback_t level_click_callback_) + : window(window_), level_click_callback(level_click_callback_) +{ + ReadCategories(); + window->SetRenderCallback([this](gui::Window*){Render();}); + window->SetScrollCallback([this](gui::Window*, GdkScrollDirection d) { + if (d == GDK_SCROLL_UP) + Scroll(-1); + else if (d == GDK_SCROLL_DOWN) + Scroll(+1); + }); + MakeLevelButtons(); +} + +void LevelSelect::Scroll(int direction) +{ + int circle_radius = + gui::Size(0,0,LEVEL_CIRCLE_RADIUS,0,&min_dimension_both).X(); + lowest_y = 0; + for (auto& button : buttons) + if (button->GetY() > lowest_y) + lowest_y = button->GetY(); + if (direction == 1 + && lowest_y < window->GetHeight()-10-2*circle_radius) + return; + scroll_position.y -= direction * 10; + if (scroll_position.y > 0) + scroll_position.y = 0; +} + +pugi::xml_object_range + LevelSelect::GetCategories() +{ + return category_listing.child("category-listing").children("category"); +} + +void LevelSelect::ButtonClicked(std::string category_id, std::string level_id) +{ + level_click_callback(category_id, level_id); +} + +void LevelSelect::MakeLevelButtons() +{ + double y = LEVEL_CIRCLE_PADDING; + total_points = 0; + for (pugi::xml_node category : GetCategories()) + { + std::string category_id(category.attribute("id").value()); + gui::Color color + = gui::colors::FromAttribute(category.attribute("color")); + + categories_y_positions[category_id] = y; // Keep track of y position to write text to + y += TEXT_SIZE + LEVEL_CIRCLE_PADDING; // Move past text + double x_spacing = // This formula determines the amount of space between each button + (1-LEVELS_PER_ROW*2*LEVEL_CIRCLE_RADIUS) / (LEVELS_PER_ROW+1) + + 2*LEVEL_CIRCLE_RADIUS; + double x = x_spacing - 2*LEVEL_CIRCLE_RADIUS; + + int i = 1; // Index of level + for (pugi::xml_node level : category.children("level")) + { + std::string level_id(level.attribute("id").value()); + total_points += level.attribute("points").as_int(0); + + if (x > 1.01-x_spacing) // Move down + { + x = x_spacing - 2*LEVEL_CIRCLE_RADIUS; + y += 2 * LEVEL_CIRCLE_RADIUS + LEVEL_CIRCLE_PADDING; + } + bool completed = !level.attribute("completed").empty(); + auto button = std::make_unique(window, + std::to_string(i), + gui::Position(0,0,x,y,&min_dimension_y,&scroll_position), + gui::Size(0,0,LEVEL_CIRCLE_RADIUS,0,&min_dimension_both), + completed ? gui::colors::Shade(color, COMPLETED_SHADE) : color, + gui::Alignment::LEFT, + gui::Alignment::TOP, gui::Button::Shape::CIRCLE); + + button->SetCommand([category_id, level_id, this]() { + ButtonClicked(category_id, level_id); + }); + + buttons.push_back(std::move(button)); + x += x_spacing; + + i++; + } + y += 2 * LEVEL_CIRCLE_RADIUS + LEVEL_CIRCLE_PADDING; + } +} + +void LevelSelect::ReadCategories() +{ + if (!category_listing.load_file(LEVEL_LISTING_PATH)) + utils::errors::Die("Failed to load category listing file."); +} + +void LevelSelect::Render() +{ + // Update coordinates + int m = std::min(window->GetWidth(), window->GetHeight()); + min_dimension_both.SetPos(m,m); + min_dimension_y.SetPos(window->GetWidth(),m); + + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + window->Clear(); + + // Draw Buttons + for (std::unique_ptr& button : buttons) + button->Render(); + + RenderCategoryLabels(); + window->SetDrawColor(0xDDDDDDFF); + window->DrawText(std::string("Points: ") + std::to_string(total_points), + gui::Position(window->GetWidth()-10, 10), + gui::Alignment::RIGHT, gui::Alignment::TOP); + + +} + +void LevelSelect::RenderCategoryLabels() +{ + window->SetTextSize(TEXT_SIZE*min_dimension_both.X()); + for (pugi::xml_node category : GetCategories()) + { + std::string id(category.attribute("id").value()); + std::string category_name = category.attribute("name").value(); + gui::Color color + = gui::colors::FromAttribute(category.attribute("color")); + window->SetDrawColor(color); + double y_pos = categories_y_positions[id]; + window->DrawText(category_name, + gui::Position(0,0, LEVEL_CIRCLE_PADDING,y_pos,&min_dimension_both, + &scroll_position), + gui::Alignment::LEFT, gui::Alignment::TOP); + } +} + +} // namespace graphcoloring diff --git a/src/graphcoloring/levelselect.hpp b/src/graphcoloring/levelselect.hpp new file mode 100644 index 0000000..d0dc482 --- /dev/null +++ b/src/graphcoloring/levelselect.hpp @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_LEVELSELECT_H_ +#define GRAPHCOLORING_LEVELSELECT_H_ + +#include + +#include "gui/gui.hpp" +#include "pugi/pugixml.hpp" + +namespace graphcoloring { + +class LevelSelect +{ +public: + typedef std::function level_click_callback_t; + LevelSelect(gui::Window* window, + level_click_callback_t level_click_callback); + virtual ~LevelSelect() {} + static const char* const LEVEL_LISTING_PATH; +private: + void Scroll(int direction); // -1 for up, +1 for down + void ReadCategories(); + pugi::xml_object_range GetCategories(); + void ButtonClicked(std::string category_id, std::string level_id); + void MakeLevelButtons(); + void Render(); + void RenderCategoryLabels(); + static constexpr double LEVEL_CIRCLE_RADIUS = 0.06; // As a percentage of min(width, height) + static constexpr double LEVEL_CIRCLE_PADDING = 0.03; + static constexpr double TEXT_SIZE = 0.06; + static constexpr double COMPLETED_SHADE = 0.7; // How much to shade completed levels + static constexpr int LEVELS_PER_ROW = 7; + gui::Window* const window; + pugi::xml_document category_listing; + int total_points; + std::map categories_y_positions; + level_click_callback_t level_click_callback; + std::vector> buttons; + gui::Size min_dimension_both; // min_dimension_both.x,y = min(window width, window height) + gui::Size min_dimension_y; // min_dimension_y.x = window width, .y = min(window width, window height) + gui::Position scroll_position; + int lowest_y; // Lowest y position of bottom of level circle. Used for calculating maximum scroll down. +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_LEVELSELECT_H_ diff --git a/src/graphcoloring/mainmenu.cpp b/src/graphcoloring/mainmenu.cpp new file mode 100644 index 0000000..3aed9c0 --- /dev/null +++ b/src/graphcoloring/mainmenu.cpp @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "mainmenu.hpp" + +#include + +#include "gui/gui.hpp" +#include "graphcoloring.hpp" + +namespace graphcoloring { + +MainMenu::MainMenu(gui::Window* win, gui::Button::callback_t onstart) + : window(win), + title_size(0, 0, TITLE_SIZE/1.5, TITLE_SIZE, win->GetSizePtr()) +{ + gui::Button::callback_t quit = [this](){ + window->Quit(); + }; + + gui::Size button_size = gui::Position(0,0,BUTTON_WIDTH,BUTTON_HEIGHT, + win->GetSizePtr()); + + std::shared_ptr quitButton(new gui::Button(window, "Quit", + gui::Position(), button_size, 0xFF0066FF)); + quitButton->SetCommand(quit); + + std::shared_ptr startButton(new gui::Button(window, "Start", + gui::Position(), button_size, 0x00FF66FF)); + startButton->SetCommand(onstart); + + std::vector> buttons = { + startButton, quitButton + }; + + int title_height = title_size.Y(); + gui::Position menuPosition = window->GetPosition(0, title_height); + gui::Size menuSize = window->GetPosition(0, -title_height, 1, 1); + + menu = std::make_unique(window, buttons, menuPosition, menuSize); + + window->SetRenderCallback([this](gui::Window*){Render();}); +} + +void MainMenu::Render() +{ + window->SetDrawColor(GraphColoring::BACKGROUND_COLOR); + window->Clear(); + window->SetDrawColor(TITLE_COLOR); + window->SetTextSize(std::min(title_size.X(), title_size.Y())); + window->DrawText(GraphColoring::TITLE, window->GetPosition(0,10,0.5,0), + gui::Alignment::CENTER, gui::Alignment::TOP); + menu->Render(); +} + + +} // namespace graphcoloring diff --git a/src/graphcoloring/mainmenu.hpp b/src/graphcoloring/mainmenu.hpp new file mode 100644 index 0000000..7df74bd --- /dev/null +++ b/src/graphcoloring/mainmenu.hpp @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_MAINMENU_H_ +#define GRAPHCOLORING_MAINMENU_H_ + +#include "gui/gui.hpp" + +#include + +namespace graphcoloring { + +class MainMenu { +public: + MainMenu(gui::Window* window, gui::Button::callback_t onstart); + MainMenu(); + virtual ~MainMenu() {} +private: + void Render(); + gui::Window* const window; + std::unique_ptr menu; + gui::Size title_size; + static constexpr double BUTTON_WIDTH = 0.42; // 42% of window width + static constexpr double BUTTON_HEIGHT = 0.18; + static constexpr double TITLE_SIZE = 0.15; + static constexpr gui::Color TITLE_COLOR = 0xDDDDDDFF; + +}; + +} // namespace graphcoloring + +#endif // GRAPHCOLORING_MAINMENU_H_ diff --git a/src/gui/button.cpp b/src/gui/button.cpp new file mode 100644 index 0000000..daa224f --- /dev/null +++ b/src/gui/button.cpp @@ -0,0 +1,191 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "button.hpp" + +#include + +#include "utils/geometry.hpp" + +namespace gui { + +const std::vector Button::MOUSE_BUTTONS = { + GDK_BUTTON_PRIMARY, GDK_BUTTON_MIDDLE, GDK_BUTTON_SECONDARY}; + +Button::Button(Window* window_, const std::string& text_, + Position position_, Size size_, Color color_, + Position::Alignment horizontal_align_, Position::Alignment vertical_align_, + Shape shape_) + : window(window_), + text(text_), + position(position_), + size(size_), + color(color_), + horizontal_align(horizontal_align_), + vertical_align(vertical_align_), + shape(shape_), + hovering(false) +{ + // Given a mouse button, this lambda gives the appropriate + // mouse_callback_t (which will call CheckClick with the button). + std::function callback = + [this](guint button)->Window::mouse_callback_t { + return [this,button](Window*,int,int) { + CheckClick(button); + }; + }; + + + + for (guint button : MOUSE_BUTTONS) + { + // Initialize callbacks. + callbacks[button] = std::vector(); + int callback_id = window->SetMouseupCallback(callback(button), button); + mouseup_callback_ids.push_back(callback_id); + } +} + +Button::~Button() +{ + int i; + for (i = 0; i < (int)MOUSE_BUTTONS.size(); i++) + window->RemoveMouseupCallback(mouseup_callback_ids[i],MOUSE_BUTTONS[i]); +} + + + +void Button::CheckHovering() +{ + int mx = window->GetMouseX(), my = window->GetMouseY(); + hovering = shape == Shape::RECTANGLE + ? utils::geometry::InRectangle(mx, my, GetX(), GetY(), + GetWidth(), GetHeight()) + : utils::geometry::InCircle(mx, my, + GetX()+GetRadius(), GetY()+GetRadius(), GetRadius()); + if (hovering) + for (callback_t callback : hover_callbacks) + callback(); +} + +void Button::CheckClick(guint button) +{ + CheckHovering(); + if (hovering) + { + for (callback_t callback : callbacks[button]) + { + callback(); + } + } +} + +void Button::Render() +{ + CheckHovering(); + window->SetLineWidth(BORDER_WIDTH); + window->SetDrawColor(colors::Shade(color, 0.3)); + if (shape == Shape::CIRCLE) + window->SetTextSize(GetRadius()*CIRCLE_TEXT_SIZE_FACTOR); + else + window->SetTextSize(GetHeight()*TEXT_SIZE_FACTOR); + + if (hovering) + { + switch (shape) + { + case Shape::RECTANGLE: + window->DrawRectangle(GetX()+BORDER_WIDTH/2, GetY()+BORDER_WIDTH/2, + GetWidth()-BORDER_WIDTH, GetHeight()-BORDER_WIDTH, true); + break; + case Shape::CIRCLE: + window->DrawCircle(GetX()+GetRadius(), GetY()+GetRadius(), + GetRadius()-BORDER_WIDTH); + break; + } + } + + window->SetDrawColor(color); + switch (shape) + { + case Shape::RECTANGLE: + window->DrawRectangle(GetX(), GetY(), GetWidth(), GetHeight(), false); + break; + case Shape::CIRCLE: + window->DrawCircle(GetX()+GetRadius(), GetY()+GetRadius(), GetRadius(), + false); + break; + } + + int text_x = 0, text_y = 0; + switch (shape) + { + case Shape::RECTANGLE: + { + Position text_pos(GetX(), GetY(), 0.5, 0.5, &size); + text_x = text_pos.X(); + text_y = text_pos.Y(); + } + break; + case Shape::CIRCLE: + text_x = GetX() + GetRadius(); + text_y = GetY() + GetRadius(); + break; + } + + Position text_pos(text_x, text_y); + window->DrawText(text, text_pos, Alignment::CENTER, Alignment::CENTER); +} + +void Button::SetCommand(callback_t callback, guint button) +{ + callbacks[button].push_back(callback); +} + +void Button::SetHoverCallback(callback_t callback) +{ + hover_callbacks.push_back(callback); +} + +int Button::GetWidth() const { return size.X(); } +int Button::GetHeight() const { return size.Y(); } +int Button::GetRadius() const { return size.X(); } + +int Button::GetX() const +{ + return position.AlignedX(horizontal_align, GetWidth()); +} + +int Button::GetY() const +{ + return position.AlignedY(vertical_align, GetHeight()); +} + +void Button::SetPosition(Position position_) +{ + position = position_; +} + +void Button::SetAlignment(Alignment horizontal_align_,Alignment vertical_align_) +{ + horizontal_align = horizontal_align_; + vertical_align = vertical_align_; +} + + +} // namespace gui diff --git a/src/gui/button.hpp b/src/gui/button.hpp new file mode 100644 index 0000000..29a071f --- /dev/null +++ b/src/gui/button.hpp @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GUI_BUTTON_H_ +#define GRAPHCOLORING_GUI_BUTTON_H_ + + +#include +#include +#include +#include + +#include "colors.hpp" +#include "position.hpp" +#include "window.hpp" + +namespace gui { + +class Button +{ +public: + typedef std::function callback_t; + typedef std::map> callback_map_t; + enum class Shape + { + RECTANGLE, + CIRCLE // NOTE: If you select this, size.X() will be used for the radius of the circle. + }; + Button(Window* window, const std::string& text, Position pos, Size size, + Color color, + Position::Alignment horizontal_align = Position::Alignment::LEFT, + Position::Alignment vertical_align = Position::Alignment::TOP, + Shape shape = Shape::RECTANGLE); + virtual ~Button(); + void SetCommand(callback_t callback, guint button = GDK_BUTTON_PRIMARY); + void SetHoverCallback(callback_t callback); + void Render(); + int GetX() const; + int GetY() const; + int GetWidth() const; + int GetHeight() const; + int GetRadius() const; + void SetPosition(Position position); + void SetAlignment(Alignment horizontal_align, Alignment vertical_align); + static constexpr double CIRCLE_TEXT_SIZE_FACTOR = 0.9; // Allows for ~2 characters +private: + void CheckHovering(); + void CheckClick(guint button); + static constexpr int BORDER_WIDTH = 3; + static constexpr double TEXT_SIZE_FACTOR = 0.7; + static const std::vector MOUSE_BUTTONS; + Window* const window; + std::string text; + Position position; + Size size; + Color color; + Alignment horizontal_align, vertical_align; + Shape shape; + bool hovering; + std::vector mouseup_callback_ids; + callback_map_t callbacks; + std::vector hover_callbacks; +}; + +} // namespace gui + + +#endif // GRAPHCOLORING_GUI_BUTTON_H_ diff --git a/src/gui/colors.cpp b/src/gui/colors.cpp new file mode 100644 index 0000000..9ab75a8 --- /dev/null +++ b/src/gui/colors.cpp @@ -0,0 +1,101 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "colors.hpp" +#include "utils/errors.hpp" + +#include + + +#define SHADE_FACTOR 1.5 + +namespace gui { +namespace colors { + +void Unpack(Color color, uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) +{ + *r = (color >> 24) & 0xFF; + *g = (color >> 16) & 0xFF; + *b = (color >> 8) & 0xFF; + *a = (color >> 0) & 0xFF; +} + +Color Pack(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (r << 24) + (g << 16) + (b << 8) + (a << 0); +} + +static uint8_t shade_channel(uint8_t channel, double factor) +{ + if (channel * factor > 0xFF) + channel = 0xFF; + else + channel *= factor; + return channel; +} + +Color Shade(Color color, double factor) +{ + uint8_t r, g, b, a; + Unpack(color, &r, &g, &b, &a); + r = shade_channel(r, factor); + g = shade_channel(g, factor); + b = shade_channel(b, factor); + a = shade_channel(a, factor); + return Pack(r, g, b, a); +} + +Color Light(Color color) +{ + return Shade(color, SHADE_FACTOR); +} + +Color Dark(Color color) +{ + return Shade(color, 1/SHADE_FACTOR); +} + +Color FromString(const std::string& str) +{ + uint8_t r, g, b, a; + try + { + r = std::stoi(str.substr(1,2),nullptr,16); + g = std::stoi(str.substr(3,2),nullptr,16); + b = std::stoi(str.substr(5,2),nullptr,16); + if (str.length() >= 9) + a = std::stoi(str.substr(7,2),nullptr,16); + else + a = 255; + + return Pack(r, g, b, a); + } + catch (std::invalid_argument&) + { + utils::errors::Die("Invalid color string: " + str); + } + return 0; +} + +Color FromAttribute(pugi::xml_attribute attr) +{ + return FromString(std::string(attr.value())); +} + +} // namespace colors +} // namespace gui diff --git a/src/gui/colors.hpp b/src/gui/colors.hpp new file mode 100644 index 0000000..0ecefb0 --- /dev/null +++ b/src/gui/colors.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GUI_COLORS_H_ +#define GRAPHCOLORING_GUI_COLORS_H_ + +#include +#include + +#include "pugi/pugixml.hpp" + +namespace gui { + +typedef uint32_t Color; + +namespace colors { + +// Color constants +static constexpr Color WHITE = 0xFFFFFFFF; +static constexpr Color BLACK = 0x000000FF; +static constexpr Color RED = 0xFF0000FF; +static constexpr Color GREEN = 0x00FF00FF; +static constexpr Color BLUE = 0x0000FFFF; +static constexpr Color YELLOW = 0xFFFF00FF; +static constexpr Color CYAN = 0x00FFFFFF; +static constexpr Color MAGENTA = 0xFF00FFFF; + +extern void Unpack(Color color, + uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a); // 32-bit color => 8-bit channels +extern Color Pack(uint8_t r, uint8_t g, uint8_t b, uint8_t a); // 8-bit channels => 32-bit color +extern Color Shade(Color color, double factor); +extern Color Light(Color color); +extern Color Dark(Color color); +extern Color FromString(const std::string& str); // Reads #FF00AA or #AA33FFCC +extern Color FromAttribute(pugi::xml_attribute attr); + +} // namespace colors +} // namespace gui + +#endif // GRAPHCOLORING_GUI_COLORS_H_ diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp new file mode 100644 index 0000000..a9aea3e --- /dev/null +++ b/src/gui/gui.hpp @@ -0,0 +1,28 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GRAPHCOLORING_GUI_GUI_H_ +#define GRAPHCOLORING_GUI_GUI_H_ + +#include "button.hpp" +#include "colors.hpp" +#include "menu.hpp" +#include "position.hpp" +#include "window.hpp" + +#endif // GRAPHCOLORING_GUI_GUI_H_ diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp new file mode 100644 index 0000000..18bacb2 --- /dev/null +++ b/src/gui/menu.cpp @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Leo Tenenbaum +// This file is part of GraphColoring. +// +// GraphColoring is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// GraphColoring is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with GraphColoring. If not, see . +//////////////////////////////////////////////////////////////////////////////// + +#include "menu.hpp" + +#include + +namespace gui { + +Menu::Menu(Window* window, + const std::vector>& buttons_, + Position position_, Size size_) + : window(window), buttons(buttons_), position(position_), size(size_) +{ + int total_button_height = 0; + for (std::shared_ptr