summaryrefslogtreecommitdiff
path: root/src/graphcoloring
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2018-08-20 20:34:57 -0400
committerLeo Tenenbaum <pommicket@gmail.com>2018-08-20 20:34:57 -0400
commita4460f6d9453bbd7e584937686449cef3e19f052 (patch)
tree037c208f1e20302ed048c0952ef8e3418add9c86 /src/graphcoloring
Initial commit0.0.0
Diffstat (limited to 'src/graphcoloring')
-rw-r--r--src/graphcoloring/graphcoloring.cpp146
-rw-r--r--src/graphcoloring/graphcoloring.hpp65
-rw-r--r--src/graphcoloring/graphs/colormenu.cpp120
-rw-r--r--src/graphcoloring/graphs/colormenu.hpp58
-rw-r--r--src/graphcoloring/graphs/edge.cpp191
-rw-r--r--src/graphcoloring/graphs/edge.hpp71
-rw-r--r--src/graphcoloring/graphs/graph.cpp419
-rw-r--r--src/graphcoloring/graphs/graph.hpp88
-rw-r--r--src/graphcoloring/graphs/vertex.cpp204
-rw-r--r--src/graphcoloring/graphs/vertex.hpp76
-rw-r--r--src/graphcoloring/level.cpp350
-rw-r--r--src/graphcoloring/level.hpp91
-rw-r--r--src/graphcoloring/levels/colorloader.cpp142
-rw-r--r--src/graphcoloring/levels/colorloader.hpp54
-rw-r--r--src/graphcoloring/levels/globalloader.cpp45
-rw-r--r--src/graphcoloring/levels/globalloader.hpp40
-rw-r--r--src/graphcoloring/levels/graphloader.cpp178
-rw-r--r--src/graphcoloring/levels/graphloader.hpp57
-rw-r--r--src/graphcoloring/levels/paths/path.cpp207
-rw-r--r--src/graphcoloring/levels/paths/path.hpp70
-rw-r--r--src/graphcoloring/levels/pointcalculator.cpp48
-rw-r--r--src/graphcoloring/levels/pointcalculator.hpp45
-rw-r--r--src/graphcoloring/levels/rules/boundrule.cpp135
-rw-r--r--src/graphcoloring/levels/rules/boundrule.hpp55
-rw-r--r--src/graphcoloring/levels/rules/edgerule.cpp82
-rw-r--r--src/graphcoloring/levels/rules/edgerule.hpp51
-rw-r--r--src/graphcoloring/levels/rules/rule.hpp37
-rw-r--r--src/graphcoloring/levels/rules/ruleloader.cpp92
-rw-r--r--src/graphcoloring/levels/rules/ruleloader.hpp53
-rw-r--r--src/graphcoloring/levels/rules/rules.cpp72
-rw-r--r--src/graphcoloring/levels/rules/rules.hpp42
-rw-r--r--src/graphcoloring/levels/value.cpp304
-rw-r--r--src/graphcoloring/levels/value.hpp80
-rw-r--r--src/graphcoloring/levels/valueloader.cpp96
-rw-r--r--src/graphcoloring/levels/valueloader.hpp46
-rw-r--r--src/graphcoloring/levelselect.cpp167
-rw-r--r--src/graphcoloring/levelselect.hpp64
-rw-r--r--src/graphcoloring/mainmenu.cpp72
-rw-r--r--src/graphcoloring/mainmenu.hpp47
39 files changed, 4260 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "graphcoloring.hpp"
+
+#include "utils/windows.hpp"
+#include "utils/filesystem.hpp"
+
+#include <sstream>
+
+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<MainMenu>(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<LevelSelect>(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<Level>(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<std::string,std::string> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+
+#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> mainMenu;
+ std::unique_ptr<LevelSelect> levelSelect;
+ std::unique_ptr<Level> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<void(gui::Color)> 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<void()> 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<gui::Button> 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<gui::Button>& 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GRAPHS_COLORMENU_H_
+#define GRAPHCOLORING_GRAPHS_COLORMENU_H_
+
+#include "gui/window.hpp"
+#include "gui/button.hpp"
+
+#include <memory>
+
+namespace graphcoloring {
+
+class ColorMenu {
+public:
+ ColorMenu(gui::Window* window, int x, int y,
+ const gui::Position& viewport_position,
+ std::function<void(gui::Color)> color_click_callback);
+ virtual ~ColorMenu();
+ void Render();
+ void SetCloseCallback(std::function<void()> 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<std::unique_ptr<gui::Button>> buttons;
+ std::function<void(gui::Color)> color_click_callback;
+ std::function<void()> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "edge.hpp"
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <cassert>
+
+#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<void()> 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<void(gui::Color)> callback = [this] (gui::Color color) {
+ assert(ChangeColor(color));
+ color_menu = nullptr;
+ };
+
+ color_menu = std::make_unique<ColorMenu>(window,
+ mouse_x+viewport_position.X(), mouse_y+viewport_position.Y(),
+ viewport_position, callback);
+
+ std::function<void()> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<void()> 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<ColorMenu> 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<void()> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "graph.hpp"
+
+#include <sstream>
+#include <cassert>
+#include <stack>
+
+#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<void()> 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<int> 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<int>& edges_in_path,
+ const std::set<int>& 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<int> edges_in_path;
+ std::set<int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GRAPHS_GRAPH_H_
+#define GRAPHCOLORING_GRAPHS_GRAPH_H_
+
+#include <set>
+
+#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<int>& edges_in_path,
+ const std::set<int>& 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<Vertex*> vertices;
+ std::vector<Edge*> 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<int> origins;
+ std::set<int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "vertex.hpp"
+
+#include <cassert>
+#include <iostream>
+
+#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<void()> 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<void(gui::Color)> callback = [this] (gui::Color color) {
+ assert(ChangeColor(color));
+ color_menu = nullptr;
+ };
+
+ color_menu = std::make_unique<ColorMenu>(window,
+ mouse_x+viewport_position.X(), mouse_y+viewport_position.Y(),
+ viewport_position, callback);
+
+ std::function<void()> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<void()> 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<void()> 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<ColorMenu> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "level.hpp"
+
+#include <sstream>
+
+#include "graphcoloring.hpp"
+#include "graphs/vertex.hpp"
+#include "graphs/edge.hpp"
+#include "levels/graphloader.hpp"
+
+namespace graphcoloring {
+
+std::vector<gui::Color> 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<std::string,std::string> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+
+#ifndef GRAPHCOLORING_LEVEL_H_
+#define GRAPHCOLORING_LEVEL_H_
+
+#include <string>
+#include <memory>
+
+#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<std::string,std::string> 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<gui::Color> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<std::string, gui::Color> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_LEVELS_COLORLOADER_H_
+#define GRAPHCOLORING_LEVELS_COLORLOADER_H_
+
+#include <map>
+#include <string>
+
+#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<std::string, gui::Color> color_names;
+ std::map<gui::Color, int> vertex_color_points;
+ std::map<gui::Color, int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "graphloader.hpp"
+
+#include <sstream>
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_LEVELS_GRAPHLOADER_H_
+#define GRAPHCOLORING_LEVELS_GRAPHLOADER_H_
+
+#include <memory>
+
+#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<std::string, int> vertex_ids;
+ std::map<std::string, int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "path.hpp"
+
+#include <cassert>
+#include <algorithm>
+
+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<std::string, operation_t> 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<int>();
+ is_making_path = false;
+ first_vertex = -1;
+ last_vertex = -1;
+}
+
+std::vector<int> Path::GetPath() const
+{
+ return path;
+}
+
+std::set<int> Path::PathEdgeSet() const
+{
+ return std::set<int>(path.begin(), path.end());
+
+}
+
+std::set<int> Path::PathVertexSet() const
+{
+ std::set<int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<int> GetPath() const;
+ std::set<int> PathEdgeSet() const;
+ std::set<int> 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<int(int)> 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<int> path;
+ int points_starting_value = 0;
+ std::map<gui::Color, operation_t> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<gui::Color, int> 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<gui::Color, int> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<rules::EdgeRule>(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<rules::BoundRule>(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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<rules::EdgeRule> edge_rules;
+ std::vector<rules::BoundRule> maximum_rules;
+ bool connected_rule = false; // true if the graph should be connected
+ std::vector<std::unique_ptr<rules::Rule>> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+
+#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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "value.hpp"
+
+#include <algorithm>
+#include <sstream>
+
+#include "utils/errors.hpp"
+
+namespace graphcoloring {
+
+std::map<std::string, Value::operation_t> Value::operation_table;
+
+Value::Value() : type(Type::NULL_TYPE) {}
+
+Value::Value(int val_)
+{
+ val = val_;
+ type = Type::VALUE;
+}
+
+Value::Value(std::vector<Value> list_)
+{
+ list = list_;
+ type = Type::LIST;
+}
+
+Value::Value(const std::string& string)
+{
+ FromString(string);
+}
+
+void Value::FromString(const std::string& string)
+{
+ const std::map<std::string, Type> 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<Value>(new Value(v1));
+ val2 = std::shared_ptr<Value>(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<Value>(new Value(v));
+ fold_start = std::shared_ptr<Value>(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<Value>(new Value(v1));
+ val2 = std::shared_ptr<Value>(new Value(v2));
+ }
+ else
+ {
+ utils::errors::Die("Unknown tag: " + name);
+ }
+}
+
+Value::operation_t Value::SimpleOperation(std::function<int(int,int)> op)
+{
+ return [op](const Graph&, int a, int b) { return op(a, b); };
+}
+
+Value::operation_t Value::SimpleBoolOperation(std::function<bool(int,int)> op)
+{
+ return [op](const Graph&, int a, int b) { return op(a, b) ? 1 : 0; };
+}
+
+Value::operation_t Value::SimpleLogicOperation(std::function<bool(bool,bool)>op)
+{
+ 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<int>())},
+ {"-", SimpleOperation(std::minus<int>())},
+ {"*", SimpleOperation(std::multiplies<int>())},
+ {"/", SimpleOperation(std::divides<int>())},
+ {"%", SimpleOperation(std::modulus<int>())},
+ {"=", SimpleBoolOperation(std::equal_to<int>())},
+ {"!=", SimpleBoolOperation(std::not_equal_to<int>())},
+ {"<", SimpleBoolOperation(std::less<int>())},
+ {">", SimpleBoolOperation(std::greater<int>())},
+ {"<=", SimpleBoolOperation(std::less_equal<int>())},
+ {">=", SimpleBoolOperation(std::greater_equal<int>())},
+ {"and", SimpleLogicOperation(std::logical_and<bool>())},
+ {"or", SimpleLogicOperation(std::logical_or<bool>())},
+ {"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<int> Value::EvalListOperation(const Graph& graph,
+ std::function<Value(std::string)> lookup_variable) const
+{
+ switch (type)
+ {
+ case Type::VARIABLE:
+ return lookup_variable(variable_name)
+ .EvalListOperation(graph, lookup_variable);
+ case Type::VERTICES:
+ {
+ std::vector<int> list;
+ for (const Vertex* v : graph.vertices)
+ {
+ list.push_back(v->id);
+ }
+ return list;
+ }
+ case Type::EDGES:
+ {
+ std::vector<int> list;
+ for (const Edge* e : graph.edges)
+ {
+ list.push_back(e->id);
+ }
+ return list;
+ }
+ case Type::LIST:
+ {
+ std::vector<int> out;
+ for (Value v : list)
+ out.push_back(v.Eval(graph, lookup_variable));
+ return out;
+ }
+ case Type::MAP:
+ {
+ std::vector<int> in = val1->EvalListOperation(graph, lookup_variable);
+ std::vector<int> out;
+ for (int v : in)
+ out.push_back(operation(graph, v, 0));
+ return out;
+ }
+ case Type::ZIP:
+ {
+ std::vector<int> in1= val1->EvalListOperation(graph, lookup_variable);
+ std::vector<int> in2= val2->EvalListOperation(graph, lookup_variable);
+ std::vector<int> 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>();
+}
+
+int Value::Eval(const Graph& graph,
+ std::function<Value(std::string)> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<int(const Graph&,int,int)> operation_t;
+ Value();
+ Value(std::vector<Value> list);
+ Value(int val);
+ Value(const std::string& string);
+ Value(pugi::xml_node node);
+ virtual ~Value() {}
+ int Eval(const Graph& graph,
+ std::function<Value(std::string)> 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<int(int,int)> op); // For operations which don't look at the graph.
+ // Bool operations return bools.
+ static operation_t SimpleBoolOperation(std::function<bool(int,int)> op);
+ // Logical operations take bools and return bools.
+ static operation_t SimpleLogicOperation(std::function<bool(bool,bool)> op);
+ static void InitializeOperationTable();
+ std::vector<int> EvalListOperation(const Graph& graph, // Handles anything that returns a list.
+ std::function<Value(std::string)> lookup_variable) const;
+ operation_t operation;
+ static std::map<std::string, Value::operation_t> operation_table;
+ std::shared_ptr<Value> fold_start;
+ std::shared_ptr<Value> val1;
+ std::shared_ptr<Value> val2;
+ std::string variable_name;
+ int val = 0;
+ std::vector<Value> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<std::string, int>& v_id : graph_loader.vertex_ids)
+ variables[v_id.first] = v_id.second;
+ for (const std::pair<std::string, int>& e_id : graph_loader.edge_ids)
+ variables[e_id.first] = e_id.second;
+}
+
+
+void ValueLoader::LoadColors(const ColorLoader& color_loader)
+{
+ for (std::pair<std::string, gui::Color> 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<Value(std::string)> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<std::string, Value> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#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<pugi::xml_named_node_iterator>
+ 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<gui::Button>(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<gui::Button>& 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_LEVELSELECT_H_
+#define GRAPHCOLORING_LEVELSELECT_H_
+
+#include <memory>
+
+#include "gui/gui.hpp"
+#include "pugi/pugixml.hpp"
+
+namespace graphcoloring {
+
+class LevelSelect
+{
+public:
+ typedef std::function<void(std::string,std::string)> 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<pugi::xml_named_node_iterator> 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<std::string, double> categories_y_positions;
+ level_click_callback_t level_click_callback;
+ std::vector<std::unique_ptr<gui::Button>> 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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "mainmenu.hpp"
+
+#include <iostream>
+
+#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<gui::Button> quitButton(new gui::Button(window, "Quit",
+ gui::Position(), button_size, 0xFF0066FF));
+ quitButton->SetCommand(quit);
+
+ std::shared_ptr<gui::Button> startButton(new gui::Button(window, "Start",
+ gui::Position(), button_size, 0x00FF66FF));
+ startButton->SetCommand(onstart);
+
+ std::vector<std::shared_ptr<gui::Button>> 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<gui::Menu>(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 <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_MAINMENU_H_
+#define GRAPHCOLORING_MAINMENU_H_
+
+#include "gui/gui.hpp"
+
+#include <memory>
+
+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<gui::Menu> 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_