////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2018 Leo Tenenbaum
// This file is part of GraphColoring.
//
// GraphColoring is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GraphColoring is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GraphColoring. If not, see .
////////////////////////////////////////////////////////////////////////////////
#include "level.hpp"
#include
#include "graphcoloring.hpp"
#include "graphs/vertex.hpp"
#include "graphs/edge.hpp"
#include "levels/graphloader.hpp"
namespace graphcoloring {
std::vector Level::colors;
pugi::xml_node Level::GetLevelNode(
const pugi::xml_document& document, std::string category_id,
std::string level_id)
{
std::stringstream xpath;
xpath << "/category-listing/category[@id='" << category_id << "']"
<< "/level[@id='" + level_id + "']";
pugi::xpath_node xnode = document.select_node(xpath.str().c_str());
pugi::xml_node node = xnode.node();
return node;
}
Level::Level(gui::Window* window_,
std::string category_id_, std::string level_id_)
: window(window_), category_id(category_id_), level_id(level_id_),
graph(window, viewport_position),
path(window, graph, rule_loader, color_loader),
point_calculator(value_loader, rule_loader, color_loader, path)
{
window->SetRenderCallback([this] (gui::Window*){ Render(); });
window->SetKeyupCallback([this] (gui::Window*){ ResetViewport(); },
GDK_KEY_o);
window->SetKeyupCallback([this] (gui::Window*) {
if (window->IsControlDown())
Reset();
}, GDK_KEY_r);
window->SetKeyupCallback([this] (gui::Window*) {
if (window->IsControlDown())
Load(SLOT_BEST);
}, GDK_KEY_b);
for (int digit = 0; digit <= 9; digit++)
{
window->SetKeyupCallback([this,digit](gui::Window*) {
if (window->IsControlDown())
Load(digit);
else
Save(digit);
}, GDK_KEY_0 + digit);
}
LoadLevelDocument();
}
Level::~Level()
{
Save();
}
std::pair Level::NextLevel() const
{
pugi::xml_document document;
document.load_file(LevelSelect::LEVEL_LISTING_PATH);
pugi::xml_node this_level_node =
GetLevelNode(document, category_id, level_id);
pugi::xml_node next_level_node = this_level_node.next_sibling();
if (next_level_node)
{
std::string next_level_id = next_level_node.attribute("id").value();
return std::make_pair(category_id, next_level_id);
}
else
{
pugi::xml_node category_node = this_level_node.parent();
pugi::xml_node next_category_node = category_node.next_sibling();
if (next_category_node)
{
std::string next_category_id =
next_category_node.attribute("id").value();
next_level_node = next_category_node.first_child();
std::string next_level_id =
next_level_node.attribute("id").value();
return std::make_pair(next_category_id, next_level_id);
}
else
{
return std::make_pair("", ""); // No next level.
}
}
}
void Level::LoadLevelDocument()
{
pugi::xml_document document;
std::string filename
= "assets/levels/" + category_id + "/" + level_id + ".xml";
document.load_file(filename.c_str());
pugi::xml_node level_node = document.child("level");
title = level_node.attribute("title").value();
description = level_node.attribute("description").value();
objective = level_node.attribute("objective").value();
color_loader.LoadDocument(document);
global_loader.LoadDocument(document);
graph.can_add_new_vertices = !global_loader.IsVertexProtected(PROTECT_ADD);
graph.can_add_new_edges = !global_loader.IsEdgeProtected(PROTECT_ADD);
GraphLoader graph_loader(color_loader, global_loader);
graph_loader.LoadDocument(document, graph);
value_loader.LoadGraph(graph_loader);
value_loader.LoadColors(color_loader);
value_loader.LoadDocument(document);
rule_loader.LoadDocument(document, color_loader);
path.LoadFromDocument(document);
Load(); // Check for save file
ResetViewport();
}
std::string Level::GetFile()
{
return "assets/levels/" + category_id + "/" + level_id + ".xml";
}
void Level::Reset()
{
pugi::xml_document document;
std::string filename = GetFile();
document.load_file(filename.c_str());
graph.Clear();
GraphLoader graph_loader(color_loader, global_loader);
graph_loader.LoadDocument(document, graph);
}
int Level::GetPoints(bool check_if_invalid) const
{
return point_calculator.Points(graph, check_if_invalid);
}
void Level::GetBestPoints()
{
if (has_loaded_best)
return;
pugi::xml_document document;
if (!document.load_file(SaveFilename(SLOT_BEST).c_str()))
{
has_loaded_best = false;
return;
}
pugi::xml_node graph_node = document.child("graph");
has_loaded_best = true;
best_points = graph_node.attribute("points").as_int();
}
void Level::Render()
{
MoveViewport();
window->SetDrawColor(GraphColoring::BACKGROUND_COLOR);
window->Clear();
graph.Render(path.PathEdgeSet(), path.PathVertexSet(), path.LastVertex());
window->SetDrawColor(TEXT_COLOR);
int y = 50;
window->SetTextSize(TITLE_SIZE);
window->DrawText(title, gui::Position(window->GetWidth()/2, y),
gui::Alignment::CENTER, gui::Alignment::TOP);
y += TITLE_SIZE + 10;
window->SetTextSize(DESCRIPTION_SIZE);
std::string description_text = description;
size_t semicolon_index;
do { // Read description lines
semicolon_index = description_text.find(';');
std::string line = description_text.substr(0, semicolon_index);
window->DrawText(line, gui::Position(window->GetWidth()/2, y),
gui::Alignment::CENTER, gui::Alignment::TOP);
description_text.erase(0, semicolon_index+1);
y += DESCRIPTION_SIZE + 10;
} while (semicolon_index != std::string::npos);
window->SetTextSize(OBJECTIVE_SIZE);
window->DrawText(objective, gui::Position(window->GetWidth()/2, y),
gui::Alignment::CENTER, gui::Alignment::TOP);
y += OBJECTIVE_SIZE + 10;
RenderPoints(y);
RenderRules();
}
void Level::RenderPoints(int y)
{
if (window->IsKeyDown(GDK_KEY_p) && !window->IsControlDown())
{
color_loader.RenderColorPoints(window);
return;
}
bool is_valid = rule_loader.IsValid(graph);
int points = GetPoints();
int objective = value_loader.ObjectivePoints(graph);
GetBestPoints();
if (!has_loaded_best || points > best_points)
Save(SLOT_BEST);
if (points >= objective && is_valid)
{
window->SetTextSize(PRESS_ESC_SIZE);
window->SetDrawColor(SUCCESS_COLOR);
window->DrawText("Press Control-N to go to the next level.",
gui::Position(window->GetWidth()/2, y),
gui::Alignment::CENTER, gui::Alignment::TOP);
y += PRESS_ESC_SIZE + 10;
}
if (!is_valid)
window->SetDrawColor(INVALID_COLOR);
std::stringstream points_text;
int invalid_points = GetPoints(false);
points_text << "Points: " << invalid_points << "/" << objective;
window->SetTextSize(POINTS_SIZE);
window->DrawText(points_text.str(), gui::Position(window->GetWidth()/2, y),
gui::Alignment::CENTER, gui::Alignment::TOP);
}
void Level::RenderRules()
{
if (!window->IsKeyDown(GDK_KEY_r) || window->IsControlDown()) return;
rule_loader.RenderRules(window);
}
void Level::MoveViewport()
{
if (window->IsKeyDown(GDK_KEY_Up))
viewport_position.y -= VIEW_MOVE_SPEED;
if (window->IsKeyDown(GDK_KEY_Down))
viewport_position.y += VIEW_MOVE_SPEED;
if (window->IsKeyDown(GDK_KEY_Left))
viewport_position.x -= VIEW_MOVE_SPEED;
if (window->IsKeyDown(GDK_KEY_Right))
viewport_position.x += VIEW_MOVE_SPEED;
}
void Level::ResetViewport()
{
viewport_position.SetPos(0, 0);
}
std::string Level::SaveFilename(int slot) const
{
std::string suffix;
if (slot == SLOT_RECENT)
suffix = "";
else if (slot == SLOT_BEST)
suffix = "_best";
else
suffix = std::string("_") + std::to_string(slot);
return "saves/" + category_id + "/" + level_id + suffix + ".xml";
}
void Level::Save(int slot)
{
pugi::xml_document document;
pugi::xml_node graph_node = document.append_child("graph");
graph_node.append_attribute("points") = GetPoints();
GraphLoader graph_loader(color_loader, global_loader);
graph_loader.WriteGraph(graph, graph_node);
document.save_file(SaveFilename(slot).c_str());
if (slot == SLOT_BEST)
{
has_loaded_best = false;
UpdateLevelList();
}
}
int Level::Load(int slot)
{
pugi::xml_document document;
if (document.load_file(SaveFilename(slot).c_str()))
{
graph.Clear();
GraphLoader graph_loader(color_loader, global_loader);
graph_loader.LoadDocument(document, graph);
return 1;
}
else
{
return 0;
}
}
void Level::UpdateLevelList()
{
pugi::xml_document category_listing;
category_listing.load_file(LevelSelect::LEVEL_LISTING_PATH);
int points = GetPoints();
int objective = value_loader.ObjectivePoints(graph);
pugi::xml_node node = GetLevelNode(category_listing, category_id, level_id);
if (node.attribute("points").empty())
node.append_attribute("points") = points;
else
node.attribute("points") = points;
if (points >= objective)
{
if (node.attribute("completed").empty())
node.append_attribute("completed") = "t";
else
node.attribute("completed") = "t";
}
category_listing.save_file(LevelSelect::LEVEL_LISTING_PATH);
}
} // namespace graphcoloring