#include "logger.h"
#include <chrono>
#include <fstream>
#include <mutex>
namespace {
class Logger {
public:
Logger() : logfile_("debug.log") {}
void LogLine(const std::string& text) {
std::lock_guard guard(file_mutex_);
logfile_ << "[" << std::chrono::system_clock::now() << "] " << text
<< std::endl;
logfile_.flush();
}
private:
std::ofstream logfile_;
std::mutex file_mutex_;
};
} // namespace
void TrackerLog(const std::string& text) {
static .highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */#include "tracker_state.h"
#include <list>
#include <map>
#include <mutex>
#include <set>
#include <sstream>
#include <tuple>
#include "ap_state.h"
#include "game_data.h"
namespace {
struct TrackerState {
std::map<int, bool> reachability;
std::mutex reachability_mutex;
};
enum Decision { kYes, kNo, kMaybe };
TrackerState& GetState() {
static TrackerState* instance = new TrackerState();
return *instance;
}
Decision IsDoorReachable_Helper(int door_id,
const std::set<int>& reachable_rooms,
const std::set<int>& solveable_panels) {
const Door& door_obj = GD_GetDoor(door_id);
if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
if (!reachable_rooms.count(door_obj.room)) {
return kMaybe;
}
for (int panel_id : door_obj.panels) {
if (!solveable_panels.count(panel_id)) {
return kMaybe;
}
}
return kYes;
} else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
!door_obj.group_name.empty()) {
return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
} else {
bool has_item = AP_HasItem(door_obj.ap_item_id);
if (!has_item) {
for (const ProgressiveRequirement& prog_req : door_obj.progressives) {
if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) {
has_item = true;
break;
}
}
}
return has_item ? kYes : kNo;
}
}
Decision IsPanelReachable_Helper(int panel_id,
const std::set<int>& reachable_rooms,
const std::set<int>& solveable_panels) {
const Panel& panel_obj = GD_GetPanel(panel_id);
if (!reachable_rooms.count(panel_obj.room)) {
return kMaybe;
}
if (panel_obj.name == "THE MASTER") {
int achievements_accessible = 0;
for (int achieve_id : GD_GetAchievementPanels()) {
if (solveable_panels.count(achieve_id)) {
achievements_accessible++;
if (achievements_accessible >= AP_GetMasteryRequirement()) {
break;
}
}
}
return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes
: kMaybe;
}
if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
AP_GetLevel2Requirement() > 1) {
int counting_panels_accessible = 0;
for (int solved_panel_id : solveable_panels) {
const Panel& solved_panel = GD_GetPanel(solved_panel_id);
if (!solved_panel.non_counting) {
counting_panels_accessible++;
}
}
return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1)
? kYes
: kMaybe;
}
for (int room_id : panel_obj.required_rooms) {
if (!reachable_rooms.count(room_id)) {
return kMaybe;
}
}
for (int door_id : panel_obj.required_doors) {
Decision door_reachable =
IsDoorReachable_Helper(door_id, reachable_rooms, solveable_panels);
if (door_reachable == kNo) {
const Door& door_obj = GD_GetDoor(door_id);
return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
? kMaybe
: kNo;
} else if (door_reachable == kMaybe) {
return kMaybe;
}
}
for (int panel_id : panel_obj.required_panels) {
if (!solveable_panels.count(panel_id)) {
return kMaybe;
}
}
if (AP_IsColorShuffle()) {
for (LingoColor color : panel_obj.colors) {
if (!AP_HasItem(GD_GetItemIdForColor(color))) {
return kNo;
}
}
}
return kYes;
}
} // namespace
void RecalculateReachability() {
std::set<int> reachable_rooms;
std::set<int> solveable_panels;
std::list<int> panel_boundary;
std::list<Exit> flood_boundary;
flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")});
if (AP_HasEarlyColorHallways()) {
flood_boundary.push_back(
{.destination_room = GD_GetRoomByName("Outside The Undeterred")});
}
bool reachable_changed = true;
while (reachable_changed) {
reachable_changed = false;
std::list<int> new_panel_boundary;
for (int panel_id : panel_boundary) {
if (solveable_panels.count(panel_id)) {
continue;
}
Decision panel_reachable =
IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels);
if (panel_reachable == kYes) {
solveable_panels.insert(panel_id);
reachable_changed = true;
} else if (panel_reachable == kMaybe) {
new_panel_boundary.push_back(panel_id);
}
}
std::list<Exit> new_boundary;
for (const Exit& room_exit : flood_boundary) {
if (reachable_rooms.count(room_exit.destination_room)) {
continue;
}
bool valid_transition = false;
if (room_exit.door.has_value()) {
Decision door_reachable = IsDoorReachable_Helper(
*room_exit.door, reachable_rooms, solveable_panels);
if (door_reachable == kYes) {
valid_transition = true;
} else if (door_reachable == kMaybe) {
new_boundary.push_back(room_exit);
}
} else {
valid_transition = true;
}
if (valid_transition) {
reachable_rooms.insert(room_exit.destination_room);
reachable_changed = true;
const Room& room_obj = GD_GetRoom(room_exit.destination_room);
for (const Exit& out_edge : room_obj.exits) {
if (!out_edge.painting || !AP_IsPaintingShuffle()) {
new_boundary.push_back(out_edge);
}
}
if (AP_IsPaintingShuffle()) {
for (const PaintingExit& out_edge : room_obj.paintings) {
if (AP_GetPaintingMapping().count(out_edge.id)) {
Exit painting_exit;
painting_exit.destination_room = GD_GetRoomForPainting(
AP_GetPaintingMapping().at(out_edge.id));
painting_exit.door = out_edge.door;
new_boundary.push_back(painting_exit);
}
}
}
for (int panel_id : room_obj.panels) {
new_panel_boundary.push_back(panel_id);
}
}
}
flood_boundary = new_boundary;
panel_boundary = new_panel_boundary;
}
std::map<int, bool> new_reachability;
for (const MapArea& map_area : GD_GetMapAreas()) {
for (size_t section_id = 0; section_id < map_area.locations.size();
section_id++) {
const Location& location_section = map_area.locations.at(section_id);
bool reachable = reachable_rooms.count(location_section.room);
if (reachable) {
for (int panel_id : location_section.panels) {
reachable &= (solveable_panels.count(panel_id) == 1);
}
}
new_reachability[location_section.ap_location_id] = reachable;
}
}
{
std::lock_guard reachability_guard(GetState().reachability_mutex);
std::swap(GetState().reachability, new_reachability);
}
}
bool IsLocationReachable(int location_id) {
std::lock_guard reachability_guard(GetState().reachability_mutex);
if (GetState().reachability.count(location_id)) {
return GetState().reachability.at(location_id);
} else {
return false;
}
}