From 51a4a4c7578a71837b78f2610e315bc953aca6ac Mon Sep 17 00:00:00 2001 From: Ruben Lapauw Date: Tue, 20 Nov 2018 17:18:13 +0100 Subject: [PATCH 1/4] Squashing zlkj, zlkjb, and ppj changes to a single commit. --- CMakeLists.txt | 4 +- src/pp.cpp | 11 +- src/pp.hpp | 2 + src/ppj.cpp | 294 +++++++++++++++++++++ src/ppj.hpp | 57 ++++ src/solvers.cpp | 6 + src/zlkj.cpp | 688 ++++++++++++++++++++++++++++++++++++++++++++++++ src/zlkj.hpp | 60 +++++ src/zlkjb.cpp | 596 +++++++++++++++++++++++++++++++++++++++++ src/zlkjb.hpp | 58 ++++ 10 files changed, 1773 insertions(+), 3 deletions(-) create mode 100644 src/ppj.cpp create mode 100644 src/ppj.hpp create mode 100644 src/zlkj.cpp create mode 100644 src/zlkj.hpp create mode 100644 src/zlkjb.cpp create mode 100644 src/zlkjb.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e9e8f3..8784f91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,9 @@ add_library(oink src/rtl.cpp src/ptl.cpp src/dtl.cpp + src/ppj.cpp + src/zlkj.cpp + src/zlkjb.cpp ) find_package(Boost COMPONENTS iostreams) target_link_libraries(oink pthread ${Boost_LIBRARIES}) @@ -111,4 +114,3 @@ install(TARGETS oink DESTINATION "${CMAKE_INSTALL_LIBDIR}") install(TARGETS solve DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES src/oink.hpp src/game.hpp src/uintqueue.hpp DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/oink.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") - diff --git a/src/pp.cpp b/src/pp.cpp index b5d375c..29eb5d0 100644 --- a/src/pp.cpp +++ b/src/pp.cpp @@ -32,7 +32,10 @@ PPSolver::PPSolver(Oink *oink, Game *game) : Solver(oink, game) PPSolver::~PPSolver() { } - +void PPSolver::unattracted(int) { +} +void PPSolver::endAttracting(int prio) { +} void PPSolver::attract(int prio, std::queue queue) { @@ -75,7 +78,10 @@ PPSolver::attract(int prio, std::queue queue) break; } } - if (can_escape) continue; + if (can_escape) { + unattracted(from); + continue; + } // if all outgoing edges are prio >= p, attract to a-maximal region rv.push_back(from); region[from] = prio; @@ -85,6 +91,7 @@ PPSolver::attract(int prio, std::queue queue) } } } + endAttracting(prio); } void diff --git a/src/pp.hpp b/src/pp.hpp index 098fea4..a4957cc 100644 --- a/src/pp.hpp +++ b/src/pp.hpp @@ -43,6 +43,8 @@ class PPSolver : public Solver //std::set> seen; virtual void attract(int prio, std::queue queue=std::queue()); + virtual void unattracted(int node); + virtual void endAttracting(int prio); virtual void promote(int from, int to); virtual void resetRegion(int priority); virtual bool setupRegion(int index, int priority, bool mustReset); diff --git a/src/ppj.cpp b/src/ppj.cpp new file mode 100644 index 0000000..eabe822 --- /dev/null +++ b/src/ppj.cpp @@ -0,0 +1,294 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "ppj.hpp" + +namespace pg { + + +int pm_cmp(int pl, int p_left, int p_right) { + p_left += pl; + p_right += pl; + return p_left == p_right ? 0 : + (p_right & 1) != (p_left & 1) ? (p_right & 1) - (p_left & 1) : + (p_left & 1) ^ (p_left < p_right) ? -1 : 1; +} + +PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game) +{ +} + +void PPJSolver::unattracted(int node) { + if (is_lost[node]) return; + if(trace >= 3) logger << "Node may be lost " << node << std::endl; + is_lost[node] = true; + lost.push_back(node); +} +escape PPJSolver::bestEscape(int node) { + int pl = owner[node]; + int r_node = region[node]; + //A higher priority than max_prio of opposite parity of pl; + int highest_escape = max_prio + 2 - ((1 + pl + max_prio) & 1); + int play = -1; + const int *_out = outs + outa[node]; + for (int to = *_out; to != -1; to = *++_out) { + int r_to = region[to]; + if (r_to == -2) continue; + //logger << node << " " << r_node << " " << to << " @" << r_to << std::endl; + if(pm_cmp(pl, highest_escape, r_to) < 0) { + highest_escape = r_to; + play = to; + } + } + int strategy = (highest_escape & 1) != pl ? -1 : play; + //strategy = (highest_escape < priority[node]) ? -1 : strategy; + escape out = escape{highest_escape, strategy, play}; + return out; +} +void PPJSolver::endAttracting(int prio) { + int pl = prio & 1; + std::vector killQueue; + // if (trace >= 3) logger << "Cleanup after attracting " << prio << std::endl; + for(auto it = lost.begin(); it != lost.end(); it++) { + int node = *it; + is_lost[node] = false; + if(region[node] == prio) continue; // has been attracted + if(owner[node] == pl) LOGIC_ERROR; + if(strategy[node] != -1 && (region[strategy[node]] & 1) != pl) { + // the node is attracted to an agreeing edge that was not flipped. + // the attraction stays valid. Thus ignore. + // if (trace >= 3) logger << "Not in justification " << node << std::endl; + continue; + } + escape highest_escape = bestEscape(node); + int r_escape = highest_escape.region; + if (((r_escape & 1) == pl) != (highest_escape.play == -1)) LOGIC_ERROR; + if (r_escape > prio) { + // At least one non-disabled edge should exist. + // At least one lower than prio should exist otherwise should have been attracted. + logger << "Node " << node << " should have been attracted to " << highest_escape.to << std::endl; + LOGIC_ERROR; + } else if (r_escape >= region[node]) { + if ((r_escape & 1) != pl) { + // if (trace >= 2) logger << "Node " << node << " with owner " << owner[node] << " may switch play to " << highest_escape.to << " ... rebuild" << std::endl; + killQueue.push_back(node); + } else if (r_escape == region[node]) { + // if (trace >= 3) logger << "Unmovable " << node << " to " << r_escape << std::endl; + } else { + // all nodes are disagreeing with owner, the node is forced. + // but it was not attracted to prio. Thus waiting. + // if(trace >= 2) logger << "Delayed force " << node << " to @" << r_escape << std::endl; + setWaiting(node, r_escape); + } + } else if (r_escape > priority[node]) { + // The region can flip from attracted to a lower forced node. + if((region[node] & 1) == pl) { + // The region cannot both drop and be attracted while edge-nodes increase in priority (of the same parity). + logger << "Drop halfway " << node << " to @" << r_escape << " is " << region[node] << " over " << priority[node] << std::endl; + LOGIC_ERROR; + } + // Kill dependents and reset waiting. + // TODO? don't recalculate highest_escape when rebuilding but only kill dependents of node? + // if (trace >= 2) logger << "Flip-halfway " << node << " to " << r_escape << std::endl; + killQueue.push_back(node); + } else { //if (r_escape <= priority[node]) <= region[node] + if (region[node] == priority[node]) { + if (trace >= 3) logger << "Don't drop heads " << node << " of " << priority[node] << " at " << region[node] << " to " << r_escape << std::endl; + } else { + // highest_escape < region[from] + // The region is dropping. Don't try to catch falling nodes. This leads to bad cycles. + // if (trace >= 2) logger << "Dropping attraction " << node << " to " << r_escape << std::endl; + killQueue.push_back(node); + } + } + } + lost.clear(); + for(uint idx = 0; idx < killQueue.size(); idx++) { + int node = killQueue[idx]; + // if (trace >= 3) logger << "Lost support " << node << std::endl; + strategy[node] = -1; + region[node] = priority[node]; + const int *_in = ins + ina[node]; + for (int from = *_in; from != -1; from = *++_in) { + if(disabled[from]) continue; + // if (trace >= 4) logger << "Lost? " << from << " by " << node << " " << region[from] << std::endl; + if(strategy[from] >= 0 && strategy[from] != node) continue; // not in justification graph; + if(region[from] == priority[from]) { + continue; // don't reset heads; don't reset twice. + } + if(region[from] < priority[from]) LOGIC_ERROR; + killQueue.push_back(from); + } + } + // cannot set a node waiting with a given strategy. + // recalculate full strategy again instead of fixed edge? + // The difference is: + // Knowing the play but not the origin. Iterate over all incoming nodes. + // Verify all outgoing nodes for a forced attraction as well. + // Knowing the origin but not the play. Iterate over all outgoing nodes. + for(auto node : killQueue) { + escape highest_escape = bestEscape(node); + int r_escape = highest_escape.region; + if (r_escape > prio) { + logger << "Node "<< node << " attracted/forced to " << highest_escape.to << " at " << r_escape << std::endl; + // At least one non-disabled edge should exist. + // At least one lower than prio should exist otherwise should have been attracted. + LOGIC_ERROR; + } + // if (trace >= 2) logger << "Best rebuilt support " << node << " at " << r_escape << std::endl; + setWaiting(node, r_escape); + } +} + +void PPJSolver::setWaiting(int node, int prio) { + waitingPriority = std::max(waitingPriority, prio); + if(prio > priority[node]) { + waiting[prio].push_back(node); + } +} + +bool +PPJSolver::setupRegion(int i, int p, bool mustReset) +{ + // NOTE: a node can be "disabled" and in a region (after dominion attraction) + if (!mustReset) LOGIC_ERROR; + // Remove head (to be added again) + // Remove lost nodes. + regions[p].erase(std::remove_if(regions[p].begin(), regions[p].end(), + [&](const int x){ + return priority[x] == p || region[x] != p; + }), + regions[p].end()); + std::queue q; + // Add all escapes to regions vector + for (int j=i; j>=0 && priority[j] == p; j--) { + if (disabled[j]) region[j] = -2; + else if (region[j] == p) { + if (strategy[j] >= 0 and (disabled[strategy[j]] or region[strategy[j]] != p)) { + // logger << "Plays outside " << p << " : " << j << " to " << strategy[j] << " at " << region[strategy[j]] << std::endl; + strategy[j] = -1; + } + // if (trace >= 2) logger << "\033[1;37mhead \033[36m" << j << " \033[37mat \033[36m" << p << std::endl; + regions[p].push_back(j); + q.push(j); + } + } + for(auto node : waiting[p]) { + if (disabled[node]) continue; + if (region[node] >= p) continue; //has been attracted + escape best_esc = bestEscape(node); + if(best_esc.region == p) { + region[node] = p; + strategy[node] = best_esc.play; + regions[p].push_back(node); + q.push(node); + } + } + waiting[p].clear(); + + attract(p, q); + return regions[p].size() > 0; +} + +void +PPJSolver::run() +{ + // obtain highest priority and allocate arrays + max_prio = priority[n_nodes-1]; + int dom_len = max_prio + 4; + regions = new std::vector[dom_len]; + region = new int[n_nodes]; + strategy = new int[n_nodes]; + inverse = new int[dom_len]; + waiting = new std::vector[dom_len]; + is_lost = bitset(n_nodes); + + // initialize arrays + for (int i=0; i= 0) { + // get current priority and skip all disabled/attracted nodes + int i = inverse[p]; + // PPJ: reset region is unused + if (setupRegion(i, p, true)) { + // region not empty, maybe promote + while (true) { + if (trace >= 2) reportRegion(p); + int res = getRegionStatus(i, p); + + if (res == -2) { + p--; + break; + } else if (res == -1) { + // found dominion + int dominion = max_prio-(max_prio&1)+2+(p&1); + promote(p, dominion); + p = max_prio; + break; + } else { + // found promotion, promote + promote(p, res); + p = res; + i = inverse[p]; + } + } + } else { + p--; + } + } + for(int i = 0; i= 0) != (owner[i] == pl)) { + logger << "Strategy of " << i << " is " << strategy[i] << " when owned by " << owner[i] << " and won by " << pl << std::endl; + LOGIC_ERROR; + } + oink->solve(i, pl, owner[i] == pl ? strategy[i] : -1); + } + oink->flush(); + delete[] regions; + delete[] region; + delete[] strategy; + delete[] inverse; + delete[] waiting; + + logger << "solved with " << promotions << " promotions." << std::endl; +} + +} diff --git a/src/ppj.hpp b/src/ppj.hpp new file mode 100644 index 0000000..854dcdc --- /dev/null +++ b/src/ppj.hpp @@ -0,0 +1,57 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PPJ_HPP +#define PPJ_HPP + +#include +#include "pp.hpp" +#include "oink.hpp" +#include "solver.hpp" + +namespace pg { + +struct escape { + int region; + int play; + int to; +}; + +class PPJSolver : public PPSolver +{ +public: +PPJSolver(Oink *oink, Game *game); +virtual void run(); + +protected: +// Todo: possibly a large memory consumption. +std::vector *waiting; +int waitingPriority; +std::vector lost; +bitset is_lost; + +virtual void unattracted(int node); +virtual void endAttracting(int prio); +virtual bool setupRegion(int index, int priority, bool mustReset); + +void setWaiting(int node, int priority); +escape bestEscape(int node); +}; + +} + +#endif diff --git a/src/solvers.cpp b/src/solvers.cpp index 09523d9..0d6518b 100644 --- a/src/solvers.cpp +++ b/src/solvers.cpp @@ -17,6 +17,9 @@ #include "solvers.hpp" #include "zlk.hpp" +#include "zlkj.hpp" +#include "zlkjb.hpp" +#include "ppj.hpp" #include "pp.hpp" #include "ppp.hpp" #include "rr.hpp" @@ -43,8 +46,11 @@ Solvers::Solvers() add("zlkq", "QPT Zielonka", 1, [] (Oink* oink, Game* game) { return new ZLKQSolver(oink, game); }); add("zlk", "parallel Zielonka", 1, [] (Oink* oink, Game* game) { return new ZLKSolver(oink, game); }); add("uzlk", "unoptimized Zielonka", 1, [] (Oink* oink, Game* game) { return new UnoptimizedZLKSolver(oink, game); }); + add("zlkj", "justified Zielonka", 0, [] (Oink* oink, Game* game) { return new ZLKJSolver(oink, game); }); + add("zlkjb", "justified Zielonka variant b", 0, [] (Oink* oink, Game* game) { return new ZLKJBSolver(oink, game); }); add("npp", "priority promotion NPP", 0, [] (Oink* oink, Game* game) { return new NPPSolver(oink, game); }); add("pp", "priority promotion PP", 0, [] (Oink* oink, Game* game) { return new PPSolver(oink, game); }); + add("ppj", "priority promotion PPJ", 0, [] (Oink* oink, Game* game) { return new PPJSolver(oink, game); }); add("ppp", "priority promotion PP+", 0, [] (Oink* oink, Game* game) { return new PPPSolver(oink, game); }); add("rr", "priority promotion RR", 0, [] (Oink* oink, Game* game) { return new RRSolver(oink, game); }); add("dp", "priority promotion PP+ with DP strategy", 0, [] (Oink* oink, Game* game) { return new DPSolver(oink, game); }); diff --git a/src/zlkj.cpp b/src/zlkj.cpp new file mode 100644 index 0000000..bf0bb01 --- /dev/null +++ b/src/zlkj.cpp @@ -0,0 +1,688 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "zlkj.hpp" +#include "lace.h" +#include "printf.hpp" + +namespace pg { + +static const int DIS = 0x80000000; // permanently disabled vertex +static const int BOT = 0x80000001; // bottom state for vertex + + +ZLKJSolver::ZLKJSolver(Oink *oink, Game *game) : Solver(oink, game), Q(game->n_nodes), D(game->n_nodes) +{ +} + +ZLKJSolver::~ZLKJSolver() +{ + delete[] inverse; +} + +int +ZLKJSolver::attractExt(int i, int r, std::vector *R) +{ + const int pr = priority[i]; + const int pl = pr & 1; + + /** + * Starting at , attract head nodes until "inversion" + */ + for (; i>=0; i--) { + if (region[i] == DIS or region[i] >= 0) continue; // cannot be attracted + + // uncomment the next line to attract until lower priority instead of until inversion + //if (priority[i] != pr) break; // until other priority + if ((priority[i]&1) != pl) break; // until parity inversion (Maks Verver optimization) + + region[i] = r; + winning[i] = pl; + strategy[i] = -2; // head nodes do not have a strategy yet! + Q.push(i); + R->push_back(i); + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + + if (owner[from] == pl) { + // owned by same parity + region[from] = r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + + // Invariant: strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].insert(from); + } + } else { + region[from] = count; + } + } + } + } + } + + return i; +} + +/** + * Find all nodes in S (and in the game with region >= r) that attract to the other player. + */ +std::pair ZLKJSolver::attractLosing(int i, int r, std::vector *S, int next_r) +{ + int count = 0; + const int pr = priority[i]; + const int pl = pr & 1; + int new_i = -1; + + // NOTE: this algorithm could be improved using an "out counter" + +#ifndef NDEBUG + for (int i : *S) if (winning[i] != pl) LOGIC_ERROR; +#endif + + /** + * First check all region nodes... + * In reality, we just want to check the "head nodes", because all other nodes are attracted + * to the head nodes and cannot be attracted to the opponent directly. + * But we do not record which nodes are head nodes. + * TODO! DONE! + */ + S->erase(std::remove_if(S->begin(), S->end(), [this](int i) { + return this->strategy[i] != -2; + }), S->end()); + for (int i : *S) { + // check if the node is attracted + if (owner[i] == pl) { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] != pl) continue; // not an escape + can_escape = true; + //just[from].insert(to); + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[i] = r; + winning[i] = 1-pl; + strategy[i] = -1; + Q.push(i); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == i) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(i); + } + } + } else { + // "winner" attraction + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == pl) continue; // not attracting + region[i] = r; + winning[i] = 1-pl; + strategy[i] = to; + Q.push(i); + const int *second = outs + outa[i]; + for (int other = *second; other != -1; other = *++second) { + if (other == to) continue; // no concurrent modification error + if (region[other] == DIS) continue; + just[other].erase(i); + } + break; + } + } + } + + /** + * Now attract anything in this region/subregions of to 1- + */ + + while (!Q.empty()) { + int cur = Q.pop(); + ++count; + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + // if (region[from] == -1) LOGIC_ERROR; + if (region[from] < r && region[from] >= 0) { + continue; // not in subgame, or disabled + } + if (winning[from] == 1-pl) { + continue; // already lost + } + + if (owner[from] != pl) { + // owned by other + region[from] = r; + winning[from] = 1-pl; + strategy[from] = cur; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by us + bool can_escape = false; + { + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + // if (region[to] == -1) LOGIC_ERROR; + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + //switch strategy + //just[strategy[from]].erase(from); + just[to].insert(from); + strategy[from] = to; + break; + } + } + if (can_escape) { + if(winning[from] != -1) { + winning[from] = -1; + D.push(from); + } + continue; + } + region[from] = r; + winning[from] = 1-pl; + strategy[from] = -1; + Q.push(from); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(from); + } + } + } + } + for(unsigned int idx = 0; idx < D.size(); idx++) { + int cur = D[idx]; + // destroy dependents on + if(winning[cur] != -1) { + continue; + } + region[cur] = BOT; + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int to = *it; + if (region[to] < r) { + continue; // not in subgame, or disabled + } + if (winning[to] != pl) { + continue; + } + region[to] = BOT; + winning[to] = -1; + D.push(to); + } + } + + for(unsigned int idx = 0; idx < D.size(); idx++) { + int cur = D[idx]; + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + if (owner[cur] == pl) { + // "winner" attraction + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == 1 - pl) continue; // not attracting + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = to; + Q.push(cur); + const int *second = outs + outa[cur]; + for (int other = *second; other != -1; other = *++second) { + if (other == to) continue; // no concurrent modification error + if (region[other] == DIS) continue; + just[other].erase(cur); + } + just[to].insert(cur); + break; + } + if(strategy[cur] == -3) { + const int *second = outs + outa[cur]; + for (int other = *second; other != -1; other = *++second) { + if (region[other] == DIS) continue; + just[other].insert(cur); + } + } + } else { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = -1; + Q.push(cur); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == i) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(cur); + } + } + } + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + + if (owner[from] == pl) { + // owned by same parity + region[from] = next_r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = next_r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].insert(from); + } + } else { + //TODO : Make count useful, now basic can_escape. + // Note reassign the count to region[from] lead to bugs. + } + } + } + } + } + int lost = 0; + while(!D.empty()) { + int cur = D.pop(); + + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + lost++; + if(new_i < cur) { + new_i = cur; + } + } + return std::pair(count, new_i); +} + +void +ZLKJSolver::run() +{ + iterations = 0; + + // allocate and initialize data structures + region = new int[n_nodes]; + winning = new int[n_nodes]; + strategy = new int[n_nodes]; + + std::vector history; + std::vector > levels; + + // get number of nodes and create and initialize inverse array + max_prio = -1; + for (int n=n_nodes-1; n>=0; n--) { + strategy[n] = -4; + winning[n] = -1; + region[n] = disabled[n] ? DIS : BOT; + if (disabled[n]) continue; + strategy[n] = -3; + const int pr = game->priority[n]; + if (max_prio == -1) { + max_prio = pr; + inverse = new int[max_prio+1]; + memset(inverse, -1, sizeof(int[max_prio+1])); + } + if (inverse[pr] == -1) inverse[pr] = n; + } + just.reserve(n_nodes); + for (int n=0; n incoming; + just.push_back(incoming); + if(disabled[n]) continue; + just[n].reserve(game->in[n].size()); + for(int from : game->in[n]) { + if (disabled[from]) continue; + just[n].insert(from); + } + } + if (max_prio == -1) LOGIC_ERROR; // unexpected empty game + + // pre-allocate some space (similar to NPP) + { + int space = max_prio / 20; + if (space >= 500) space = 500; + history.reserve(3*space); + levels.reserve(space); + } + + // start the loop at the last node (highest priority) and at depth 0 + int i = inverse[max_prio]; + int next_r = 0; + + // initialize first level (i, r=0, phase=0) + levels.push_back(std::vector()); + history.push_back(i); + history.push_back(next_r++); + history.push_back(0); + while (true) { + // obtain current frame + const int hsize = history.size(); + if (hsize == 0) break; // no frame on the stack + + std::vector *A = &(*levels.rbegin()); + const int i = history[hsize-3]; + const int r = history[hsize-2]; + const int phase = history[hsize-1]; + +#ifndef NDEBUG + if (i < 0) LOGIC_ERROR; // just a sanity check + if (region[i] == DIS) LOGIC_ERROR; // just a sanity check + if (h*3 != hsize or (int)levels.size() != h) LOGIC_ERROR; // just a sanity check +#endif + + + /** + * Get priority and player + */ + const int pr = priority[i]; + const int pl = pr&1; + +#ifndef NDEBUG + if (trace) fmt::printf(logger, "\n\033[1mDepth %d phase %d\033[m: node %d priority %d\n", h-0, phase, i, pr); +#endif + + if (phase == 0) { + /** + * We are in the first phase. + * Compute extended attractor (until inversion). Then recursive step. + */ + + // attract until inversion and add to A + int j = attractExt(i, r, A); + // j is now the next i (subgame), or -1 if the subgame is empty + if(trace) std::cout << "attracted " << A->size() << " nodes" << " for " << pl << std::endl; + + // count number of iterations + ++iterations; + + // update phase to 1 + history.back() = 1; + + if (j != -1) { + // go recursive (subgame not empty) + levels.push_back(std::vector()); + history.push_back(j); + history.push_back(next_r++); + history.push_back(0); + } + } else if (phase == 1) { + /** + * After first recursion step + */ + +#ifndef NDEBUG + if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); +#endif + + /** + * Compute part of region that attracts to the other player... + */ + + int count = -1; + int new_i = -1; + std::tie(count, new_i) = attractLosing(i, r, A, next_r++); + if(trace) std::cout << "Losing " << count << " " << new_i << std::endl; + if (trace) { + if (count > 0) fmt::printf(logger, "%d nodes are attracted to losing region\n", count); + else if (count == 0) fmt::printf(logger, "no nodes are attracted to losing region\n"); + else fmt::printf(logger, "no losing region\n"); + } + + if (count <= 0) { + if(new_i >= 0) { // count =< 0 => new_i >= 0 + LOGIC_ERROR; + } + /** + * Nothing attracted to opponent, add A to W0/W1, fix strategies, go up. + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != pl) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + just[to].erase(to); + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + } + just[strategy[v]].insert(v); + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack and go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } else { + if (new_i == -1) { + /** + * And pop the stack to go up + */ + history.back() = 2; // set current phase to 2 + /*levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back();*/ + } else { + /** + * And push the stack to go down + */ + history.back() = 2; // set current phase to 2 + levels.push_back(std::vector()); + history.push_back(new_i); + history.push_back(next_r++); + history.push_back(0); + } + } + } else if (phase == 2) { + /** + * After second recursion step + * Choose a strategy + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + just[to].erase(v); + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + } + just[strategy[v]].insert(v); + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack to go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } + } + + // done + for (int i=0; isolve(i, winning[i], strategy[i]); + } + + delete[] region; + delete[] winning; + delete[] strategy; + + logger << "solved with " << iterations << " iterations." << std::endl; +} + +} diff --git a/src/zlkj.hpp b/src/zlkj.hpp new file mode 100644 index 0000000..da4595d --- /dev/null +++ b/src/zlkj.hpp @@ -0,0 +1,60 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ZLKJ_HPP +#define ZLKJ_HPP + +#include +#include +#include +#include "solver.hpp" +#include "lace.h" +#include "uintqueue.hpp" + +namespace pg { + +class ZLKJSolver : public Solver +{ +public: +ZLKJSolver(Oink *oink, Game *game); +virtual ~ZLKJSolver(); + +virtual void run(); + +int iterations; + +protected: +int *inverse; +int max_prio; + +int *region; +int *winning; +int *strategy; +std::vector > just; + + +bool to_inversion = true; +bool only_recompute_when_attracted = true; + +uintqueue Q; +uintqueue D; + +int attractExt(int i, int r, std::vector *R); +std::pair attractLosing(int i, int r, std::vector *S, int next_r); +}; +} +#endif diff --git a/src/zlkjb.cpp b/src/zlkjb.cpp new file mode 100644 index 0000000..7151ab1 --- /dev/null +++ b/src/zlkjb.cpp @@ -0,0 +1,596 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "zlkjb.hpp" +#include "lace.h" +#include "printf.hpp" + +namespace pg { + +static const int DIS = 0x80000000; // permanently disabled vertex +static const int BOT = 0x80000001; // bottom state for vertex + + +ZLKJBSolver::ZLKJBSolver(Oink *oink, Game *game) : Solver(oink, game), Q(game->n_nodes), D(game->n_nodes) +{ +} + +ZLKJBSolver::~ZLKJBSolver() +{ + delete[] inverse; +} + +int +ZLKJBSolver::attractExt(int i, int r, std::vector *R) +{ + const int pr = priority[i]; + const int pl = pr & 1; + + /** + * Starting at , attract head nodes until "inversion" + */ + for (; i>=0; i--) { + if (region[i] == DIS or region[i] >= 0) continue; // cannot be attracted + + // uncomment the next line to attract until lower priority instead of until inversion + //if (priority[i] != pr) break; // until other priority + if ((priority[i]&1) != pl) break; // until parity inversion (Maks Verver optimization) + + region[i] = r; + winning[i] = pl; + strategy[i] = -2; // head nodes do not have a strategy yet! + Q.push(i); + R->push_back(i); + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + const int * _in = ins + ina[cur]; + for (int from = *_in; from != -1; from = *++_in) { + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + if (owner[from] == pl) { + // owned by same parity + region[from] = r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + } else { + if (strategy[from] > -1 && strategy[from] != cur) { + continue; + } + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + } else { + region[from] = count; + } + } + } + } + } + return i; +} + +/** + * Find all nodes in S (and in the game with region >= r) that attract to the other player. + */ + std::pair ZLKJBSolver::attractLosing(int i, int r, std::vector *S, int next_r) +{ + int count = 0; + const int pr = priority[i]; + const int pl = pr & 1; + int new_i = -1; + + // NOTE: this algorithm could be improved using an "out counter" + +#ifndef NDEBUG + for (int i : *S) if (winning[i] != pl) LOGIC_ERROR; +#endif + + /** + * First check all region nodes... + * In reality, we just want to check the "head nodes", because all other nodes are attracted + * to the head nodes and cannot be attracted to the opponent directly. + * But we do not record which nodes are head nodes. + * TODO! + */ + S->erase(std::remove_if(S->begin(), S->end(), [this](int i) {return this->strategy[i] != -2;}), S->end()); + for (int i : *S) { + if(strategy[i] != -2) continue; + // check if the node is attracted + if (owner[i] == pl) { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] != pl) continue; // not an escape + can_escape = true; + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[i] = r; + winning[i] = 1-pl; + strategy[i] = -1; + Q.push(i); + } + } else { + // "winner" attraction + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == pl) continue; // not attracting + region[i] = r; + winning[i] = 1-pl; + strategy[i] = to; + Q.push(i); + break; + } + } + } + + /** + * Now attract anything in this region/subregions of to 1- + */ + + while (!Q.empty()) { + int cur = Q.pop(); + ++count; + // attract to + const int *_in = ins + ina[cur]; + for (int from = *_in; from != -1; from = *++_in) { + // if (region[from] == -1) LOGIC_ERROR; + if (region[from] < r && region[from] >= 0){ + continue; // not in subgame, or disabled + } + if (winning[from] != pl) { + continue; // already lost + } + // not watched + if(strategy[from] > -1 && strategy[from] != cur) continue; + + if (owner[from] != pl) { + // owned by other + region[from] = r; + winning[from] = 1-pl; + strategy[from] = cur; + Q.push(from); + } else { + // owned by us + bool can_escape = false; + { + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + // if (region[to] == -1) LOGIC_ERROR; + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + //switch strategy + //just[strategy[from]].erase(from); + strategy[from] = to; + break; + } + } + if (can_escape) { + if(winning[from] == -1) { + LOGIC_ERROR; + } + winning[from] = -1; + D.push(from); + } else { + region[from] = r; + winning[from] = 1-pl; + strategy[from] = -1; + Q.push(from); + } + } + } + } + for(unsigned int idx = 0; idx < D.size(); idx++) { + int cur = D[idx]; + // destroy dependents on + if(winning[cur] != -1) { + continue; + } + region[cur] = BOT; + const int *_in = ins + ina[cur]; + for (int from = *_in; from != -1; from = *++_in) { + if (region[from] < r){ + continue; // not in subgame, or disabled + } + if (winning[from] != pl) { + continue; + } + region[from] = BOT; + winning[from] = -1; + D.push(from); + } + } + + for(unsigned int idx = 0; idx < D.size(); idx++) { + int cur = D[idx]; + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + if (owner[cur] == pl) { + // "winner" attraction + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == 1 - pl) continue; // not attracting + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = to; + Q.push(cur); + break; + } + } else { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = -1; + Q.push(cur); + } + } + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + const int *_in = ins + ina[cur]; + for (int from = *_in; from != -1; from = *++_in) { + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + + if (owner[from] == pl) { + // owned by same parity + region[from] = next_r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + } else { + // if(strategy[from] > -1 && strategy[from] != cur) continue; + // Double decrement is impossible since all nodes are attracted to heads before attractLosing starts. + // The only dependen nodes with bot region are freshly invalidated. + // All nodes mustr have BOT or region >= 0. Thus the negative counts are correct and will not be double decremented. + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = next_r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + } else { + //TODO : Make count useful, now basic can_escape + } + } + } + } + } + int lost = 0; + while(!D.empty()) { + int cur = D.pop(); + + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + lost ++ ; + if(new_i < cur) { + new_i = cur; + } + } + return std::pair(count, new_i); +} + +void +ZLKJBSolver::run() +{ + iterations = 0; + + // allocate and initialize data structures + region = new int[n_nodes]; + winning = new int[n_nodes]; + strategy = new int[n_nodes]; + + std::vector history; + std::vector> levels; + + // get number of nodes and create and initialize inverse array + max_prio = -1; + for (int n=n_nodes-1; n>=0; n--) { + strategy[n] = -4 ; + winning[n] = -1; + region[n] = disabled[n] ? DIS : BOT; + if (disabled[n]) continue; + strategy[n] = -3 ; + const int pr = game->priority[n]; + if (max_prio == -1) { + max_prio = pr; + inverse = new int[max_prio+1]; + memset(inverse, -1, sizeof(int[max_prio+1])); + } + if (inverse[pr] == -1) inverse[pr] = n; + } + if (max_prio == -1) LOGIC_ERROR; // unexpected empty game + + // pre-allocate some space (similar to NPP) + { + int space = max_prio / 20; + if (space >= 500) space = 500; + history.reserve(3*space); + levels.reserve(space); + } + + // start the loop at the last node (highest priority) and at depth 0 + int i = inverse[max_prio]; + int next_r = 0; + + // initialize first level (i, r=0, phase=0) + levels.push_back(std::vector()); + history.push_back(i); + history.push_back(next_r++); + history.push_back(0); + while (true) { + // obtain current frame + const int hsize = history.size(); + if (hsize == 0) break; // no frame on the stack + + std::vector *A = &(*levels.rbegin()); + const int i = history[hsize-3]; + const int r = history[hsize-2]; + const int phase = history[hsize-1]; + +#ifndef NDEBUG + if (i < 0) LOGIC_ERROR; // just a sanity check + if (region[i] == DIS) LOGIC_ERROR; // just a sanity check + if (h*3 != hsize or (int)levels.size() != h) LOGIC_ERROR; // just a sanity check +#endif + + + /** + * Get priority and player + */ + const int pr = priority[i]; + const int pl = pr&1; + +#ifndef NDEBUG + if (trace) fmt::printf(logger, "\n\033[1mDepth %d phase %d\033[m: node %d priority %d\n", h-0, phase, i, pr); +#endif + + if (phase == 0) { + /** + * We are in the first phase. + * Compute extended attractor (until inversion). Then recursive step. + */ + + // attract until inversion and add to A + int j = attractExt(i, r, A); + // j is now the next i (subgame), or -1 if the subgame is empty + if(trace) std::cout << "attracted " << A->size() << " nodes" << " for " << pl << std::endl; + + // count number of iterations + ++iterations; + + // update phase to 1 + history.back() = 1; + + if (j != -1) { + // go recursive (subgame not empty) + levels.push_back(std::vector()); + history.push_back(j); + history.push_back(next_r++); + history.push_back(0); + } + } else if (phase == 1) { + /** + * After first recursion step + */ + +#ifndef NDEBUG + if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); +#endif + + /** + * Compute part of region that attracts to the other player... + */ + + int count = -1; + int new_i = -1; + std::tie(count, new_i) = attractLosing(i, r, A, next_r++); + if(trace) std::cout << "Losing " << count << " " << new_i << std::endl; + if (trace) { + if (count > 0) fmt::printf(logger, "%d nodes are attracted to losing region\n", count); + else if (count == 0) fmt::printf(logger, "no nodes are attracted to losing region\n"); + else fmt::printf(logger, "no losing region\n"); + } + + if (count <= 0) { + if(new_i >= 0) { // count =< 0 => new_i >= 0 + LOGIC_ERROR; + } + /** + * Nothing attracted to opponent, add A to W0/W1, fix strategies, go up. + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != pl) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + break; + } + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack and go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } else { + if (new_i == -1) { + /** + * And pop the stack to go up + */ + history.back() = 2; // set current phase to 2 + /*levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back();*/ + } else { + /** + * And push the stack to go down + */ + history.back() = 2; // set current phase to 2 + levels.push_back(std::vector()); + history.push_back(new_i); + history.push_back(next_r++); + history.push_back(0); + } + } + } else if (phase == 2) { + /** + * After second recursion step + * Choose a strategy + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + break; + } + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack to go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } + } + + // done + for (int i=0; isolve(i, winning[i], strategy[i]); + } + + delete[] region; + delete[] winning; + delete[] strategy; + + logger << "solved with " << iterations << " iterations." << std::endl; +} + +} diff --git a/src/zlkjb.hpp b/src/zlkjb.hpp new file mode 100644 index 0000000..7a91432 --- /dev/null +++ b/src/zlkjb.hpp @@ -0,0 +1,58 @@ +/* + * Copyright 2017-2020 Tom van Dijk, Johannes Kepler University Linz + * Copyright 2019-2020 Ruben Lapauw, KU Leuven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ZLKJB_HPP +#define ZLKJB_HPP + +#include +#include +#include "solver.hpp" +#include "lace.h" +#include "uintqueue.hpp" + +namespace pg { + +class ZLKJBSolver : public Solver +{ +public: +ZLKJBSolver(Oink *oink, Game *game); +virtual ~ZLKJBSolver(); + +virtual void run(); + +int iterations; + +protected: +int *inverse; +int max_prio; + +int *region; +int *winning; +int *strategy; + + +bool to_inversion = true; +bool only_recompute_when_attracted = true; + +uintqueue Q; +uintqueue D; + +int attractExt(int i, int r, std::vector *R); +std::pair attractLosing(int i, int r, std::vector *S, int next_r); +}; +} +#endif From 8f4750e2599bb718397c8204f58f467340b0d811 Mon Sep 17 00:00:00 2001 From: Ruben Lapauw Date: Sat, 18 Jan 2020 10:54:07 -0600 Subject: [PATCH 2/4] Autoformatting --- src/ppj.cpp | 458 +++++++++---------- src/ppj.hpp | 28 +- src/zlkj.cpp | 1212 ++++++++++++++++++++++++------------------------- src/zlkj.hpp | 32 +- src/zlkjb.cpp | 222 ++++----- src/zlkjb.hpp | 30 +- 6 files changed, 992 insertions(+), 990 deletions(-) diff --git a/src/ppj.cpp b/src/ppj.cpp index eabe822..195d36d 100644 --- a/src/ppj.cpp +++ b/src/ppj.cpp @@ -28,11 +28,11 @@ namespace pg { int pm_cmp(int pl, int p_left, int p_right) { - p_left += pl; - p_right += pl; - return p_left == p_right ? 0 : - (p_right & 1) != (p_left & 1) ? (p_right & 1) - (p_left & 1) : - (p_left & 1) ^ (p_left < p_right) ? -1 : 1; + p_left += pl; + p_right += pl; + return p_left == p_right ? 0 : + (p_right & 1) != (p_left & 1) ? (p_right & 1) - (p_left & 1) : + (p_left & 1) ^ (p_left < p_right) ? -1 : 1; } PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game) @@ -40,255 +40,255 @@ PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game) } void PPJSolver::unattracted(int node) { - if (is_lost[node]) return; - if(trace >= 3) logger << "Node may be lost " << node << std::endl; - is_lost[node] = true; - lost.push_back(node); + if (is_lost[node]) return; + if(trace >= 3) logger << "Node may be lost " << node << std::endl; + is_lost[node] = true; + lost.push_back(node); } escape PPJSolver::bestEscape(int node) { - int pl = owner[node]; - int r_node = region[node]; - //A higher priority than max_prio of opposite parity of pl; - int highest_escape = max_prio + 2 - ((1 + pl + max_prio) & 1); - int play = -1; - const int *_out = outs + outa[node]; - for (int to = *_out; to != -1; to = *++_out) { - int r_to = region[to]; - if (r_to == -2) continue; - //logger << node << " " << r_node << " " << to << " @" << r_to << std::endl; - if(pm_cmp(pl, highest_escape, r_to) < 0) { - highest_escape = r_to; - play = to; - } - } - int strategy = (highest_escape & 1) != pl ? -1 : play; - //strategy = (highest_escape < priority[node]) ? -1 : strategy; - escape out = escape{highest_escape, strategy, play}; - return out; + int pl = owner[node]; + int r_node = region[node]; + //A higher priority than max_prio of opposite parity of pl; + int highest_escape = max_prio + 2 - ((1 + pl + max_prio) & 1); + int play = -1; + const int *_out = outs + outa[node]; + for (int to = *_out; to != -1; to = *++_out) { + int r_to = region[to]; + if (r_to == -2) continue; + //logger << node << " " << r_node << " " << to << " @" << r_to << std::endl; + if(pm_cmp(pl, highest_escape, r_to) < 0) { + highest_escape = r_to; + play = to; + } + } + int strategy = (highest_escape & 1) != pl ? -1 : play; + //strategy = (highest_escape < priority[node]) ? -1 : strategy; + escape out = escape{highest_escape, strategy, play}; + return out; } void PPJSolver::endAttracting(int prio) { - int pl = prio & 1; - std::vector killQueue; - // if (trace >= 3) logger << "Cleanup after attracting " << prio << std::endl; - for(auto it = lost.begin(); it != lost.end(); it++) { - int node = *it; - is_lost[node] = false; - if(region[node] == prio) continue; // has been attracted - if(owner[node] == pl) LOGIC_ERROR; - if(strategy[node] != -1 && (region[strategy[node]] & 1) != pl) { - // the node is attracted to an agreeing edge that was not flipped. - // the attraction stays valid. Thus ignore. - // if (trace >= 3) logger << "Not in justification " << node << std::endl; - continue; - } - escape highest_escape = bestEscape(node); - int r_escape = highest_escape.region; - if (((r_escape & 1) == pl) != (highest_escape.play == -1)) LOGIC_ERROR; - if (r_escape > prio) { - // At least one non-disabled edge should exist. - // At least one lower than prio should exist otherwise should have been attracted. - logger << "Node " << node << " should have been attracted to " << highest_escape.to << std::endl; - LOGIC_ERROR; - } else if (r_escape >= region[node]) { - if ((r_escape & 1) != pl) { - // if (trace >= 2) logger << "Node " << node << " with owner " << owner[node] << " may switch play to " << highest_escape.to << " ... rebuild" << std::endl; - killQueue.push_back(node); - } else if (r_escape == region[node]) { - // if (trace >= 3) logger << "Unmovable " << node << " to " << r_escape << std::endl; - } else { - // all nodes are disagreeing with owner, the node is forced. - // but it was not attracted to prio. Thus waiting. - // if(trace >= 2) logger << "Delayed force " << node << " to @" << r_escape << std::endl; - setWaiting(node, r_escape); - } - } else if (r_escape > priority[node]) { - // The region can flip from attracted to a lower forced node. - if((region[node] & 1) == pl) { - // The region cannot both drop and be attracted while edge-nodes increase in priority (of the same parity). - logger << "Drop halfway " << node << " to @" << r_escape << " is " << region[node] << " over " << priority[node] << std::endl; - LOGIC_ERROR; - } - // Kill dependents and reset waiting. - // TODO? don't recalculate highest_escape when rebuilding but only kill dependents of node? - // if (trace >= 2) logger << "Flip-halfway " << node << " to " << r_escape << std::endl; - killQueue.push_back(node); - } else { //if (r_escape <= priority[node]) <= region[node] - if (region[node] == priority[node]) { - if (trace >= 3) logger << "Don't drop heads " << node << " of " << priority[node] << " at " << region[node] << " to " << r_escape << std::endl; - } else { - // highest_escape < region[from] - // The region is dropping. Don't try to catch falling nodes. This leads to bad cycles. - // if (trace >= 2) logger << "Dropping attraction " << node << " to " << r_escape << std::endl; - killQueue.push_back(node); - } - } - } - lost.clear(); - for(uint idx = 0; idx < killQueue.size(); idx++) { - int node = killQueue[idx]; - // if (trace >= 3) logger << "Lost support " << node << std::endl; - strategy[node] = -1; - region[node] = priority[node]; - const int *_in = ins + ina[node]; - for (int from = *_in; from != -1; from = *++_in) { - if(disabled[from]) continue; - // if (trace >= 4) logger << "Lost? " << from << " by " << node << " " << region[from] << std::endl; - if(strategy[from] >= 0 && strategy[from] != node) continue; // not in justification graph; - if(region[from] == priority[from]) { - continue; // don't reset heads; don't reset twice. - } - if(region[from] < priority[from]) LOGIC_ERROR; - killQueue.push_back(from); - } - } - // cannot set a node waiting with a given strategy. - // recalculate full strategy again instead of fixed edge? - // The difference is: - // Knowing the play but not the origin. Iterate over all incoming nodes. - // Verify all outgoing nodes for a forced attraction as well. - // Knowing the origin but not the play. Iterate over all outgoing nodes. - for(auto node : killQueue) { - escape highest_escape = bestEscape(node); - int r_escape = highest_escape.region; - if (r_escape > prio) { - logger << "Node "<< node << " attracted/forced to " << highest_escape.to << " at " << r_escape << std::endl; - // At least one non-disabled edge should exist. - // At least one lower than prio should exist otherwise should have been attracted. - LOGIC_ERROR; - } - // if (trace >= 2) logger << "Best rebuilt support " << node << " at " << r_escape << std::endl; - setWaiting(node, r_escape); - } + int pl = prio & 1; + std::vector killQueue; + // if (trace >= 3) logger << "Cleanup after attracting " << prio << std::endl; + for(auto it = lost.begin(); it != lost.end(); it++) { + int node = *it; + is_lost[node] = false; + if(region[node] == prio) continue; // has been attracted + if(owner[node] == pl) LOGIC_ERROR; + if(strategy[node] != -1 && (region[strategy[node]] & 1) != pl) { + // the node is attracted to an agreeing edge that was not flipped. + // the attraction stays valid. Thus ignore. + // if (trace >= 3) logger << "Not in justification " << node << std::endl; + continue; + } + escape highest_escape = bestEscape(node); + int r_escape = highest_escape.region; + if (((r_escape & 1) == pl) != (highest_escape.play == -1)) LOGIC_ERROR; + if (r_escape > prio) { + // At least one non-disabled edge should exist. + // At least one lower than prio should exist otherwise should have been attracted. + logger << "Node " << node << " should have been attracted to " << highest_escape.to << std::endl; + LOGIC_ERROR; + } else if (r_escape >= region[node]) { + if ((r_escape & 1) != pl) { + // if (trace >= 2) logger << "Node " << node << " with owner " << owner[node] << " may switch play to " << highest_escape.to << " ... rebuild" << std::endl; + killQueue.push_back(node); + } else if (r_escape == region[node]) { + // if (trace >= 3) logger << "Unmovable " << node << " to " << r_escape << std::endl; + } else { + // all nodes are disagreeing with owner, the node is forced. + // but it was not attracted to prio. Thus waiting. + // if(trace >= 2) logger << "Delayed force " << node << " to @" << r_escape << std::endl; + setWaiting(node, r_escape); + } + } else if (r_escape > priority[node]) { + // The region can flip from attracted to a lower forced node. + if((region[node] & 1) == pl) { + // The region cannot both drop and be attracted while edge-nodes increase in priority (of the same parity). + logger << "Drop halfway " << node << " to @" << r_escape << " is " << region[node] << " over " << priority[node] << std::endl; + LOGIC_ERROR; + } + // Kill dependents and reset waiting. + // TODO? don't recalculate highest_escape when rebuilding but only kill dependents of node? + // if (trace >= 2) logger << "Flip-halfway " << node << " to " << r_escape << std::endl; + killQueue.push_back(node); + } else { //if (r_escape <= priority[node]) <= region[node] + if (region[node] == priority[node]) { + if (trace >= 3) logger << "Don't drop heads " << node << " of " << priority[node] << " at " << region[node] << " to " << r_escape << std::endl; + } else { + // highest_escape < region[from] + // The region is dropping. Don't try to catch falling nodes. This leads to bad cycles. + // if (trace >= 2) logger << "Dropping attraction " << node << " to " << r_escape << std::endl; + killQueue.push_back(node); + } + } + } + lost.clear(); + for(uint idx = 0; idx < killQueue.size(); idx++) { + int node = killQueue[idx]; + // if (trace >= 3) logger << "Lost support " << node << std::endl; + strategy[node] = -1; + region[node] = priority[node]; + const int *_in = ins + ina[node]; + for (int from = *_in; from != -1; from = *++_in) { + if(disabled[from]) continue; + // if (trace >= 4) logger << "Lost? " << from << " by " << node << " " << region[from] << std::endl; + if(strategy[from] >= 0 && strategy[from] != node) continue; // not in justification graph; + if(region[from] == priority[from]) { + continue; // don't reset heads; don't reset twice. + } + if(region[from] < priority[from]) LOGIC_ERROR; + killQueue.push_back(from); + } + } + // cannot set a node waiting with a given strategy. + // recalculate full strategy again instead of fixed edge? + // The difference is: + // Knowing the play but not the origin. Iterate over all incoming nodes. + // Verify all outgoing nodes for a forced attraction as well. + // Knowing the origin but not the play. Iterate over all outgoing nodes. + for(auto node : killQueue) { + escape highest_escape = bestEscape(node); + int r_escape = highest_escape.region; + if (r_escape > prio) { + logger << "Node "<< node << " attracted/forced to " << highest_escape.to << " at " << r_escape << std::endl; + // At least one non-disabled edge should exist. + // At least one lower than prio should exist otherwise should have been attracted. + LOGIC_ERROR; + } + // if (trace >= 2) logger << "Best rebuilt support " << node << " at " << r_escape << std::endl; + setWaiting(node, r_escape); + } } void PPJSolver::setWaiting(int node, int prio) { - waitingPriority = std::max(waitingPriority, prio); - if(prio > priority[node]) { - waiting[prio].push_back(node); - } + waitingPriority = std::max(waitingPriority, prio); + if(prio > priority[node]) { + waiting[prio].push_back(node); + } } bool PPJSolver::setupRegion(int i, int p, bool mustReset) { - // NOTE: a node can be "disabled" and in a region (after dominion attraction) - if (!mustReset) LOGIC_ERROR; - // Remove head (to be added again) - // Remove lost nodes. - regions[p].erase(std::remove_if(regions[p].begin(), regions[p].end(), - [&](const int x){ - return priority[x] == p || region[x] != p; - }), - regions[p].end()); - std::queue q; - // Add all escapes to regions vector - for (int j=i; j>=0 && priority[j] == p; j--) { - if (disabled[j]) region[j] = -2; - else if (region[j] == p) { - if (strategy[j] >= 0 and (disabled[strategy[j]] or region[strategy[j]] != p)) { - // logger << "Plays outside " << p << " : " << j << " to " << strategy[j] << " at " << region[strategy[j]] << std::endl; - strategy[j] = -1; - } - // if (trace >= 2) logger << "\033[1;37mhead \033[36m" << j << " \033[37mat \033[36m" << p << std::endl; - regions[p].push_back(j); - q.push(j); - } - } - for(auto node : waiting[p]) { - if (disabled[node]) continue; - if (region[node] >= p) continue; //has been attracted - escape best_esc = bestEscape(node); - if(best_esc.region == p) { - region[node] = p; - strategy[node] = best_esc.play; - regions[p].push_back(node); - q.push(node); - } - } - waiting[p].clear(); + // NOTE: a node can be "disabled" and in a region (after dominion attraction) + if (!mustReset) LOGIC_ERROR; + // Remove head (to be added again) + // Remove lost nodes. + regions[p].erase(std::remove_if(regions[p].begin(), regions[p].end(), + [&](const int x){ + return priority[x] == p || region[x] != p; + }), + regions[p].end()); + std::queue q; + // Add all escapes to regions vector + for (int j=i; j>=0 && priority[j] == p; j--) { + if (disabled[j]) region[j] = -2; + else if (region[j] == p) { + if (strategy[j] >= 0 and (disabled[strategy[j]] or region[strategy[j]] != p)) { + // logger << "Plays outside " << p << " : " << j << " to " << strategy[j] << " at " << region[strategy[j]] << std::endl; + strategy[j] = -1; + } + // if (trace >= 2) logger << "\033[1;37mhead \033[36m" << j << " \033[37mat \033[36m" << p << std::endl; + regions[p].push_back(j); + q.push(j); + } + } + for(auto node : waiting[p]) { + if (disabled[node]) continue; + if (region[node] >= p) continue; //has been attracted + escape best_esc = bestEscape(node); + if(best_esc.region == p) { + region[node] = p; + strategy[node] = best_esc.play; + regions[p].push_back(node); + q.push(node); + } + } + waiting[p].clear(); - attract(p, q); - return regions[p].size() > 0; + attract(p, q); + return regions[p].size() > 0; } void PPJSolver::run() { - // obtain highest priority and allocate arrays - max_prio = priority[n_nodes-1]; - int dom_len = max_prio + 4; - regions = new std::vector[dom_len]; - region = new int[n_nodes]; - strategy = new int[n_nodes]; - inverse = new int[dom_len]; - waiting = new std::vector[dom_len]; - is_lost = bitset(n_nodes); + // obtain highest priority and allocate arrays + max_prio = priority[n_nodes-1]; + int dom_len = max_prio + 4; + regions = new std::vector[dom_len]; + region = new int[n_nodes]; + strategy = new int[n_nodes]; + inverse = new int[dom_len]; + waiting = new std::vector[dom_len]; + is_lost = bitset(n_nodes); - // initialize arrays - for (int i=0; i= 0) { - // get current priority and skip all disabled/attracted nodes - int i = inverse[p]; - // PPJ: reset region is unused - if (setupRegion(i, p, true)) { - // region not empty, maybe promote - while (true) { - if (trace >= 2) reportRegion(p); - int res = getRegionStatus(i, p); + // start loop at the highest priority + int p = max_prio; + while (p >= 0) { + // get current priority and skip all disabled/attracted nodes + int i = inverse[p]; + // PPJ: reset region is unused + if (setupRegion(i, p, true)) { + // region not empty, maybe promote + while (true) { + if (trace >= 2) reportRegion(p); + int res = getRegionStatus(i, p); - if (res == -2) { - p--; - break; - } else if (res == -1) { - // found dominion - int dominion = max_prio-(max_prio&1)+2+(p&1); - promote(p, dominion); - p = max_prio; - break; - } else { - // found promotion, promote - promote(p, res); - p = res; - i = inverse[p]; - } - } - } else { - p--; - } - } - for(int i = 0; i= 0) != (owner[i] == pl)) { - logger << "Strategy of " << i << " is " << strategy[i] << " when owned by " << owner[i] << " and won by " << pl << std::endl; - LOGIC_ERROR; - } - oink->solve(i, pl, owner[i] == pl ? strategy[i] : -1); - } - oink->flush(); - delete[] regions; - delete[] region; - delete[] strategy; - delete[] inverse; - delete[] waiting; + if (res == -2) { + p--; + break; + } else if (res == -1) { + // found dominion + int dominion = max_prio-(max_prio&1)+2+(p&1); + promote(p, dominion); + p = max_prio; + break; + } else { + // found promotion, promote + promote(p, res); + p = res; + i = inverse[p]; + } + } + } else { + p--; + } + } + for(int i = 0; i= 0) != (owner[i] == pl)) { + logger << "Strategy of " << i << " is " << strategy[i] << " when owned by " << owner[i] << " and won by " << pl << std::endl; + LOGIC_ERROR; + } + oink->solve(i, pl, owner[i] == pl ? strategy[i] : -1); + } + oink->flush(); + delete[] regions; + delete[] region; + delete[] strategy; + delete[] inverse; + delete[] waiting; - logger << "solved with " << promotions << " promotions." << std::endl; + logger << "solved with " << promotions << " promotions." << std::endl; } } diff --git a/src/ppj.hpp b/src/ppj.hpp index 854dcdc..5525c6c 100644 --- a/src/ppj.hpp +++ b/src/ppj.hpp @@ -26,30 +26,30 @@ namespace pg { struct escape { - int region; - int play; - int to; + int region; + int play; + int to; }; class PPJSolver : public PPSolver { public: -PPJSolver(Oink *oink, Game *game); -virtual void run(); + PPJSolver(Oink *oink, Game *game); + virtual void run(); protected: // Todo: possibly a large memory consumption. -std::vector *waiting; -int waitingPriority; -std::vector lost; -bitset is_lost; + std::vector *waiting; + int waitingPriority; + std::vector lost; + bitset is_lost; -virtual void unattracted(int node); -virtual void endAttracting(int prio); -virtual bool setupRegion(int index, int priority, bool mustReset); + virtual void unattracted(int node); + virtual void endAttracting(int prio); + virtual bool setupRegion(int index, int priority, bool mustReset); -void setWaiting(int node, int priority); -escape bestEscape(int node); + void setWaiting(int node, int priority); + escape bestEscape(int node); }; } diff --git a/src/zlkj.cpp b/src/zlkj.cpp index bf0bb01..f378b6d 100644 --- a/src/zlkj.cpp +++ b/src/zlkj.cpp @@ -37,89 +37,89 @@ ZLKJSolver::ZLKJSolver(Oink *oink, Game *game) : Solver(oink, game), Q(game->n_n ZLKJSolver::~ZLKJSolver() { - delete[] inverse; + delete[] inverse; } int ZLKJSolver::attractExt(int i, int r, std::vector *R) { - const int pr = priority[i]; - const int pl = pr & 1; - - /** - * Starting at , attract head nodes until "inversion" - */ - for (; i>=0; i--) { - if (region[i] == DIS or region[i] >= 0) continue; // cannot be attracted - - // uncomment the next line to attract until lower priority instead of until inversion - //if (priority[i] != pr) break; // until other priority - if ((priority[i]&1) != pl) break; // until parity inversion (Maks Verver optimization) - - region[i] = r; - winning[i] = pl; - strategy[i] = -2; // head nodes do not have a strategy yet! - Q.push(i); - R->push_back(i); - while (!Q.empty()) { - int cur = Q.pop(); - - // attract to - auto end = just[cur].end(); - auto it = just[cur].begin(); - for (; it != end; it++) { - int from = *it; - if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted - - if (owner[from] == pl) { - // owned by same parity - region[from] = r; - winning[from] = pl; - strategy[from] = cur; - Q.push(from); - - // Invariant: strategy[from] = cur <=> {x: from in just[x]} = {cur}; - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; - if (region[to] == DIS) continue; - just[to].erase(from); - } - } else { - // owned by other parity - int count = region[from]; - if (count == BOT) { - // compute count (to negative) - count = 1; - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] == DIS) continue; - if (region[to] >= 0 and region[to] < r) continue; - count--; - } - } else { - count++; - } - if (count == 0) { - region[from] = r; - winning[from] = pl; - strategy[from] = -1; - Q.push(from); - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; - if (region[to] == DIS) continue; - just[to].insert(from); - } - } else { - region[from] = count; - } - } - } - } - } - - return i; + const int pr = priority[i]; + const int pl = pr & 1; + + /** + * Starting at , attract head nodes until "inversion" + */ + for (; i>=0; i--) { + if (region[i] == DIS or region[i] >= 0) continue; // cannot be attracted + + // uncomment the next line to attract until lower priority instead of until inversion + //if (priority[i] != pr) break; // until other priority + if ((priority[i]&1) != pl) break; // until parity inversion (Maks Verver optimization) + + region[i] = r; + winning[i] = pl; + strategy[i] = -2; // head nodes do not have a strategy yet! + Q.push(i); + R->push_back(i); + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + + if (owner[from] == pl) { + // owned by same parity + region[from] = r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + + // Invariant: strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].insert(from); + } + } else { + region[from] = count; + } + } + } + } + } + + return i; } /** @@ -127,562 +127,562 @@ ZLKJSolver::attractExt(int i, int r, std::vector *R) */ std::pair ZLKJSolver::attractLosing(int i, int r, std::vector *S, int next_r) { - int count = 0; - const int pr = priority[i]; - const int pl = pr & 1; - int new_i = -1; + int count = 0; + const int pr = priority[i]; + const int pl = pr & 1; + int new_i = -1; - // NOTE: this algorithm could be improved using an "out counter" + // NOTE: this algorithm could be improved using an "out counter" #ifndef NDEBUG - for (int i : *S) if (winning[i] != pl) LOGIC_ERROR; + for (int i : *S) if (winning[i] != pl) LOGIC_ERROR; #endif - /** - * First check all region nodes... - * In reality, we just want to check the "head nodes", because all other nodes are attracted - * to the head nodes and cannot be attracted to the opponent directly. - * But we do not record which nodes are head nodes. - * TODO! DONE! - */ - S->erase(std::remove_if(S->begin(), S->end(), [this](int i) { - return this->strategy[i] != -2; - }), S->end()); - for (int i : *S) { - // check if the node is attracted - if (owner[i] == pl) { - // "loser" attraction - bool can_escape = false; - const int *_out = outs + outa[i]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously - // if not previously attracted, then either DIS or winning[to] != pl; - if (winning[to] != pl) continue; // not an escape - can_escape = true; - //just[from].insert(to); - break; - } - if (!can_escape) { - // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); - region[i] = r; - winning[i] = 1-pl; - strategy[i] = -1; - Q.push(i); - // strategy[from] = cur <=> {x: from in just[x]} = {cur}; - const int *_out = outs + outa[i]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == i) continue; // no concurrent modification error - if (region[to] == DIS) continue; - just[to].insert(i); - } - } - } else { - // "winner" attraction - const int *_out = outs + outa[i]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] < r) continue; // not in subgame, or -1/-2 - if (winning[to] == pl) continue; // not attracting - region[i] = r; - winning[i] = 1-pl; - strategy[i] = to; - Q.push(i); - const int *second = outs + outa[i]; - for (int other = *second; other != -1; other = *++second) { - if (other == to) continue; // no concurrent modification error - if (region[other] == DIS) continue; - just[other].erase(i); - } - break; - } - } - } - - /** - * Now attract anything in this region/subregions of to 1- - */ - - while (!Q.empty()) { - int cur = Q.pop(); - ++count; - // attract to - auto end = just[cur].end(); - auto it = just[cur].begin(); - for (; it != end; it++) { - int from = *it; - // if (region[from] == -1) LOGIC_ERROR; - if (region[from] < r && region[from] >= 0) { - continue; // not in subgame, or disabled - } - if (winning[from] == 1-pl) { - continue; // already lost - } - - if (owner[from] != pl) { - // owned by other - region[from] = r; - winning[from] = 1-pl; - strategy[from] = cur; - Q.push(from); - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; // no concurrent modification error - if (region[to] == DIS) continue; - just[to].erase(from); - } - } else { - // owned by us - bool can_escape = false; - { - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - // if (region[to] == -1) LOGIC_ERROR; - if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled - if (winning[to] == 1 - pl) continue; // not an escape - can_escape = true; - //switch strategy - //just[strategy[from]].erase(from); - just[to].insert(from); - strategy[from] = to; - break; - } - } - if (can_escape) { - if(winning[from] != -1) { - winning[from] = -1; - D.push(from); - } - continue; - } - region[from] = r; - winning[from] = 1-pl; - strategy[from] = -1; - Q.push(from); - // strategy[from] = cur <=> {x: from in just[x]} = {cur}; - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; // no concurrent modification error - if (region[to] == DIS) continue; - just[to].insert(from); - } - } - } - } + /** + * First check all region nodes... + * In reality, we just want to check the "head nodes", because all other nodes are attracted + * to the head nodes and cannot be attracted to the opponent directly. + * But we do not record which nodes are head nodes. + * TODO! DONE! + */ + S->erase(std::remove_if(S->begin(), S->end(), [this](int i) { + return this->strategy[i] != -2; + }), S->end()); + for (int i : *S) { + // check if the node is attracted + if (owner[i] == pl) { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] != pl) continue; // not an escape + can_escape = true; + //just[from].insert(to); + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[i] = r; + winning[i] = 1-pl; + strategy[i] = -1; + Q.push(i); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == i) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(i); + } + } + } else { + // "winner" attraction + const int *_out = outs + outa[i]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == pl) continue; // not attracting + region[i] = r; + winning[i] = 1-pl; + strategy[i] = to; + Q.push(i); + const int *second = outs + outa[i]; + for (int other = *second; other != -1; other = *++second) { + if (other == to) continue; // no concurrent modification error + if (region[other] == DIS) continue; + just[other].erase(i); + } + break; + } + } + } + + /** + * Now attract anything in this region/subregions of to 1- + */ + + while (!Q.empty()) { + int cur = Q.pop(); + ++count; + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + // if (region[from] == -1) LOGIC_ERROR; + if (region[from] < r && region[from] >= 0) { + continue; // not in subgame, or disabled + } + if (winning[from] == 1-pl) { + continue; // already lost + } + + if (owner[from] != pl) { + // owned by other + region[from] = r; + winning[from] = 1-pl; + strategy[from] = cur; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by us + bool can_escape = false; + { + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + // if (region[to] == -1) LOGIC_ERROR; + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + //switch strategy + //just[strategy[from]].erase(from); + just[to].insert(from); + strategy[from] = to; + break; + } + } + if (can_escape) { + if(winning[from] != -1) { + winning[from] = -1; + D.push(from); + } + continue; + } + region[from] = r; + winning[from] = 1-pl; + strategy[from] = -1; + Q.push(from); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(from); + } + } + } + } for(unsigned int idx = 0; idx < D.size(); idx++) { - int cur = D[idx]; - // destroy dependents on - if(winning[cur] != -1) { - continue; - } - region[cur] = BOT; - auto end = just[cur].end(); - auto it = just[cur].begin(); - for (; it != end; it++) { - int to = *it; - if (region[to] < r) { - continue; // not in subgame, or disabled - } - if (winning[to] != pl) { - continue; - } - region[to] = BOT; - winning[to] = -1; - D.push(to); - } - } - - for(unsigned int idx = 0; idx < D.size(); idx++) { - int cur = D[idx]; - if(region[cur] != BOT) { - continue; - } - strategy[cur] = -3; - if (owner[cur] == pl) { - // "winner" attraction - const int *_out = outs + outa[cur]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] < r) continue; // not in subgame, or -1/-2 - if (winning[to] == 1 - pl) continue; // not attracting - region[cur] = next_r; - winning[cur] = pl; - strategy[cur] = to; - Q.push(cur); - const int *second = outs + outa[cur]; - for (int other = *second; other != -1; other = *++second) { - if (other == to) continue; // no concurrent modification error - if (region[other] == DIS) continue; - just[other].erase(cur); - } - just[to].insert(cur); - break; - } - if(strategy[cur] == -3) { - const int *second = outs + outa[cur]; - for (int other = *second; other != -1; other = *++second) { - if (region[other] == DIS) continue; - just[other].insert(cur); - } - } - } else { - // "loser" attraction - bool can_escape = false; - const int *_out = outs + outa[cur]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously - // if not previously attracted, then either DIS or winning[to] != pl; - if (winning[to] == 1 - pl) continue; // not an escape - can_escape = true; - break; - } - if (!can_escape) { - // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); - region[cur] = next_r; - winning[cur] = pl; - strategy[cur] = -1; - Q.push(cur); - // strategy[from] = cur <=> {x: from in just[x]} = {cur}; - const int *_out = outs + outa[cur]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == i) continue; // no concurrent modification error - if (region[to] == DIS) continue; - just[to].insert(cur); - } - } - } - while (!Q.empty()) { - int cur = Q.pop(); - - // attract to - auto end = just[cur].end(); - auto it = just[cur].begin(); - for (; it != end; it++) { - int from = *it; - if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted - - if (owner[from] == pl) { - // owned by same parity - region[from] = next_r; - winning[from] = pl; - strategy[from] = cur; - Q.push(from); - - // strategy[from] = cur <=> {x: from in just[x]} = {cur}; - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; - if (region[to] == DIS) continue; - just[to].erase(from); - } - } else { - // owned by other parity - int count = region[from]; - if (count == BOT) { - // compute count (to negative) - count = 1; - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] == DIS) continue; - if (region[to] >= 0 and region[to] < r) continue; - count--; - } - } else { - count++; - } - if (count == 0) { - region[from] = next_r; - winning[from] = pl; - strategy[from] = -1; - Q.push(from); - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - if (to == cur) continue; - if (region[to] == DIS) continue; - just[to].insert(from); - } - } else { - //TODO : Make count useful, now basic can_escape. - // Note reassign the count to region[from] lead to bugs. - } - } - } - } - } - int lost = 0; - while(!D.empty()) { - int cur = D.pop(); - - if(region[cur] != BOT) { - continue; - } - strategy[cur] = -3; - lost++; - if(new_i < cur) { - new_i = cur; - } - } - return std::pair(count, new_i); + int cur = D[idx]; + // destroy dependents on + if(winning[cur] != -1) { + continue; + } + region[cur] = BOT; + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int to = *it; + if (region[to] < r) { + continue; // not in subgame, or disabled + } + if (winning[to] != pl) { + continue; + } + region[to] = BOT; + winning[to] = -1; + D.push(to); + } + } + + for(unsigned int idx = 0; idx < D.size(); idx++) { + int cur = D[idx]; + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + if (owner[cur] == pl) { + // "winner" attraction + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == 1 - pl) continue; // not attracting + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = to; + Q.push(cur); + const int *second = outs + outa[cur]; + for (int other = *second; other != -1; other = *++second) { + if (other == to) continue; // no concurrent modification error + if (region[other] == DIS) continue; + just[other].erase(cur); + } + just[to].insert(cur); + break; + } + if(strategy[cur] == -3) { + const int *second = outs + outa[cur]; + for (int other = *second; other != -1; other = *++second) { + if (region[other] == DIS) continue; + just[other].insert(cur); + } + } + } else { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = -1; + Q.push(cur); + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == i) continue; // no concurrent modification error + if (region[to] == DIS) continue; + just[to].insert(cur); + } + } + } + while (!Q.empty()) { + int cur = Q.pop(); + + // attract to + auto end = just[cur].end(); + auto it = just[cur].begin(); + for (; it != end; it++) { + int from = *it; + if (from > i or region[from] == DIS or region[from] >= 0) continue; // cannot be attracted + + if (owner[from] == pl) { + // owned by same parity + region[from] = next_r; + winning[from] = pl; + strategy[from] = cur; + Q.push(from); + + // strategy[from] = cur <=> {x: from in just[x]} = {cur}; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].erase(from); + } + } else { + // owned by other parity + int count = region[from]; + if (count == BOT) { + // compute count (to negative) + count = 1; + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] == DIS) continue; + if (region[to] >= 0 and region[to] < r) continue; + count--; + } + } else { + count++; + } + if (count == 0) { + region[from] = next_r; + winning[from] = pl; + strategy[from] = -1; + Q.push(from); + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + if (to == cur) continue; + if (region[to] == DIS) continue; + just[to].insert(from); + } + } else { + //TODO : Make count useful, now basic can_escape. + // Note reassign the count to region[from] lead to bugs. + } + } + } + } + } + int lost = 0; + while(!D.empty()) { + int cur = D.pop(); + + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + lost++; + if(new_i < cur) { + new_i = cur; + } + } + return std::pair(count, new_i); } void ZLKJSolver::run() { - iterations = 0; - - // allocate and initialize data structures - region = new int[n_nodes]; - winning = new int[n_nodes]; - strategy = new int[n_nodes]; - - std::vector history; - std::vector > levels; - - // get number of nodes and create and initialize inverse array - max_prio = -1; - for (int n=n_nodes-1; n>=0; n--) { - strategy[n] = -4; - winning[n] = -1; - region[n] = disabled[n] ? DIS : BOT; - if (disabled[n]) continue; - strategy[n] = -3; - const int pr = game->priority[n]; - if (max_prio == -1) { - max_prio = pr; - inverse = new int[max_prio+1]; - memset(inverse, -1, sizeof(int[max_prio+1])); - } - if (inverse[pr] == -1) inverse[pr] = n; - } - just.reserve(n_nodes); - for (int n=0; n incoming; - just.push_back(incoming); - if(disabled[n]) continue; - just[n].reserve(game->in[n].size()); - for(int from : game->in[n]) { - if (disabled[from]) continue; - just[n].insert(from); - } - } - if (max_prio == -1) LOGIC_ERROR; // unexpected empty game - - // pre-allocate some space (similar to NPP) - { - int space = max_prio / 20; - if (space >= 500) space = 500; - history.reserve(3*space); - levels.reserve(space); - } - - // start the loop at the last node (highest priority) and at depth 0 - int i = inverse[max_prio]; - int next_r = 0; - - // initialize first level (i, r=0, phase=0) - levels.push_back(std::vector()); - history.push_back(i); - history.push_back(next_r++); - history.push_back(0); - while (true) { - // obtain current frame - const int hsize = history.size(); - if (hsize == 0) break; // no frame on the stack - - std::vector *A = &(*levels.rbegin()); - const int i = history[hsize-3]; - const int r = history[hsize-2]; - const int phase = history[hsize-1]; + iterations = 0; + + // allocate and initialize data structures + region = new int[n_nodes]; + winning = new int[n_nodes]; + strategy = new int[n_nodes]; + + std::vector history; + std::vector > levels; + + // get number of nodes and create and initialize inverse array + max_prio = -1; + for (int n=n_nodes-1; n>=0; n--) { + strategy[n] = -4; + winning[n] = -1; + region[n] = disabled[n] ? DIS : BOT; + if (disabled[n]) continue; + strategy[n] = -3; + const int pr = game->priority[n]; + if (max_prio == -1) { + max_prio = pr; + inverse = new int[max_prio+1]; + memset(inverse, -1, sizeof(int[max_prio+1])); + } + if (inverse[pr] == -1) inverse[pr] = n; + } + just.reserve(n_nodes); + for (int n=0; n incoming; + just.push_back(incoming); + if(disabled[n]) continue; + just[n].reserve(game->in[n].size()); + for(int from : game->in[n]) { + if (disabled[from]) continue; + just[n].insert(from); + } + } + if (max_prio == -1) LOGIC_ERROR; // unexpected empty game + + // pre-allocate some space (similar to NPP) + { + int space = max_prio / 20; + if (space >= 500) space = 500; + history.reserve(3*space); + levels.reserve(space); + } + + // start the loop at the last node (highest priority) and at depth 0 + int i = inverse[max_prio]; + int next_r = 0; + + // initialize first level (i, r=0, phase=0) + levels.push_back(std::vector()); + history.push_back(i); + history.push_back(next_r++); + history.push_back(0); + while (true) { + // obtain current frame + const int hsize = history.size(); + if (hsize == 0) break; // no frame on the stack + + std::vector *A = &(*levels.rbegin()); + const int i = history[hsize-3]; + const int r = history[hsize-2]; + const int phase = history[hsize-1]; #ifndef NDEBUG - if (i < 0) LOGIC_ERROR; // just a sanity check - if (region[i] == DIS) LOGIC_ERROR; // just a sanity check - if (h*3 != hsize or (int)levels.size() != h) LOGIC_ERROR; // just a sanity check + if (i < 0) LOGIC_ERROR; // just a sanity check + if (region[i] == DIS) LOGIC_ERROR; // just a sanity check + if (h*3 != hsize or (int)levels.size() != h) LOGIC_ERROR; // just a sanity check #endif - /** - * Get priority and player - */ - const int pr = priority[i]; - const int pl = pr&1; + /** + * Get priority and player + */ + const int pr = priority[i]; + const int pl = pr&1; #ifndef NDEBUG - if (trace) fmt::printf(logger, "\n\033[1mDepth %d phase %d\033[m: node %d priority %d\n", h-0, phase, i, pr); + if (trace) fmt::printf(logger, "\n\033[1mDepth %d phase %d\033[m: node %d priority %d\n", h-0, phase, i, pr); #endif - if (phase == 0) { - /** - * We are in the first phase. - * Compute extended attractor (until inversion). Then recursive step. - */ - - // attract until inversion and add to A - int j = attractExt(i, r, A); - // j is now the next i (subgame), or -1 if the subgame is empty - if(trace) std::cout << "attracted " << A->size() << " nodes" << " for " << pl << std::endl; - - // count number of iterations - ++iterations; - - // update phase to 1 - history.back() = 1; - - if (j != -1) { - // go recursive (subgame not empty) - levels.push_back(std::vector()); - history.push_back(j); - history.push_back(next_r++); - history.push_back(0); - } - } else if (phase == 1) { - /** - * After first recursion step - */ + if (phase == 0) { + /** + * We are in the first phase. + * Compute extended attractor (until inversion). Then recursive step. + */ + + // attract until inversion and add to A + int j = attractExt(i, r, A); + // j is now the next i (subgame), or -1 if the subgame is empty + if(trace) std::cout << "attracted " << A->size() << " nodes" << " for " << pl << std::endl; + + // count number of iterations + ++iterations; + + // update phase to 1 + history.back() = 1; + + if (j != -1) { + // go recursive (subgame not empty) + levels.push_back(std::vector()); + history.push_back(j); + history.push_back(next_r++); + history.push_back(0); + } + } else if (phase == 1) { + /** + * After first recursion step + */ #ifndef NDEBUG - if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); + if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); #endif - /** - * Compute part of region that attracts to the other player... - */ - - int count = -1; - int new_i = -1; - std::tie(count, new_i) = attractLosing(i, r, A, next_r++); - if(trace) std::cout << "Losing " << count << " " << new_i << std::endl; - if (trace) { - if (count > 0) fmt::printf(logger, "%d nodes are attracted to losing region\n", count); - else if (count == 0) fmt::printf(logger, "no nodes are attracted to losing region\n"); - else fmt::printf(logger, "no losing region\n"); - } - - if (count <= 0) { - if(new_i >= 0) { // count =< 0 => new_i >= 0 - LOGIC_ERROR; - } - /** - * Nothing attracted to opponent, add A to W0/W1, fix strategies, go up. - */ - for (int v : *A) { - /** - * For nodes that are won and controlled by , check if their strategy needs to be fixed. - */ - if (strategy[v] != -2) { - if(strategy[v] != -1 && winning[strategy[v]] != pl) { - LOGIC_ERROR; - } - continue; // good strategy - } - if (owner[v] != pl) { - strategy[v] = -1; - continue; // not controlled by - } - /** - * Strategy of vertex needs to be updated! - * We search for a successor of in the subgame won by - */ - //strategy[v] = -1; - const int *_out = outs + outa[v]; - for (int to = *_out; to != -1; to = *++_out) { - just[to].erase(to); - if (region[to] < r) continue; // not in subgame - if (winning[to] != pl) continue; // not winning - if (strategy[v] != -2) continue; - strategy[v] = to; - } - just[strategy[v]].insert(v); - if (strategy[v] < 0) LOGIC_ERROR; - } - - /** - * The strategy has been updated. Nodes are now in W0/W1. - * Finally, pop the stack and go up... - */ - levels.pop_back(); - history.pop_back(); - history.pop_back(); - history.pop_back(); - } else { - if (new_i == -1) { - /** - * And pop the stack to go up - */ - history.back() = 2; // set current phase to 2 - /*levels.pop_back(); - history.pop_back(); - history.pop_back(); - history.pop_back();*/ - } else { - /** - * And push the stack to go down - */ - history.back() = 2; // set current phase to 2 - levels.push_back(std::vector()); - history.push_back(new_i); - history.push_back(next_r++); - history.push_back(0); - } - } - } else if (phase == 2) { - /** - * After second recursion step - * Choose a strategy - */ - for (int v : *A) { - /** - * For nodes that are won and controlled by , check if their strategy needs to be fixed. - */ - if (strategy[v] != -2) { - if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { - LOGIC_ERROR; - } - continue; // good strategy - } - if (owner[v] != pl) { - strategy[v] = -1; - continue; // not controlled by - } - /** - * Strategy of vertex needs to be updated! - * We search for a successor of in the subgame won by - */ - //strategy[v] = -1; - const int *_out = outs + outa[v]; - for (int to = *_out; to != -1; to = *++_out) { - just[to].erase(v); - if (region[to] < r) continue; // not in subgame - if (winning[to] != pl) continue; // not winning - if (strategy[v] != -2) continue; - strategy[v] = to; - } - just[strategy[v]].insert(v); - if (strategy[v] < 0) LOGIC_ERROR; - } - - /** - * The strategy has been updated. Nodes are now in W0/W1. - * Finally, pop the stack to go up... - */ - levels.pop_back(); - history.pop_back(); - history.pop_back(); - history.pop_back(); - } - } - - // done - for (int i=0; i 0) fmt::printf(logger, "%d nodes are attracted to losing region\n", count); + else if (count == 0) fmt::printf(logger, "no nodes are attracted to losing region\n"); + else fmt::printf(logger, "no losing region\n"); + } + + if (count <= 0) { + if(new_i >= 0) { // count =< 0 => new_i >= 0 + LOGIC_ERROR; + } + /** + * Nothing attracted to opponent, add A to W0/W1, fix strategies, go up. + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != pl) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + just[to].erase(to); + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + } + just[strategy[v]].insert(v); + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack and go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } else { + if (new_i == -1) { + /** + * And pop the stack to go up + */ + history.back() = 2; // set current phase to 2 + /*levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back();*/ + } else { + /** + * And push the stack to go down + */ + history.back() = 2; // set current phase to 2 + levels.push_back(std::vector()); + history.push_back(new_i); + history.push_back(next_r++); + history.push_back(0); + } + } + } else if (phase == 2) { + /** + * After second recursion step + * Choose a strategy + */ + for (int v : *A) { + /** + * For nodes that are won and controlled by , check if their strategy needs to be fixed. + */ + if (strategy[v] != -2) { + if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { + LOGIC_ERROR; + } + continue; // good strategy + } + if (owner[v] != pl) { + strategy[v] = -1; + continue; // not controlled by + } + /** + * Strategy of vertex needs to be updated! + * We search for a successor of in the subgame won by + */ + //strategy[v] = -1; + const int *_out = outs + outa[v]; + for (int to = *_out; to != -1; to = *++_out) { + just[to].erase(v); + if (region[to] < r) continue; // not in subgame + if (winning[to] != pl) continue; // not winning + if (strategy[v] != -2) continue; + strategy[v] = to; + } + just[strategy[v]].insert(v); + if (strategy[v] < 0) LOGIC_ERROR; + } + + /** + * The strategy has been updated. Nodes are now in W0/W1. + * Finally, pop the stack to go up... + */ + levels.pop_back(); + history.pop_back(); + history.pop_back(); + history.pop_back(); + } + } + + // done + for (int i=0; isolve(i, winning[i], strategy[i]); - } + oink->solve(i, winning[i], strategy[i]); + } - delete[] region; - delete[] winning; - delete[] strategy; + delete[] region; + delete[] winning; + delete[] strategy; - logger << "solved with " << iterations << " iterations." << std::endl; + logger << "solved with " << iterations << " iterations." << std::endl; } } diff --git a/src/zlkj.hpp b/src/zlkj.hpp index da4595d..ad771b0 100644 --- a/src/zlkj.hpp +++ b/src/zlkj.hpp @@ -30,31 +30,31 @@ namespace pg { class ZLKJSolver : public Solver { public: -ZLKJSolver(Oink *oink, Game *game); -virtual ~ZLKJSolver(); + ZLKJSolver(Oink *oink, Game *game); + virtual ~ZLKJSolver(); -virtual void run(); + virtual void run(); -int iterations; + int iterations; protected: -int *inverse; -int max_prio; + int *inverse; + int max_prio; -int *region; -int *winning; -int *strategy; -std::vector > just; + int *region; + int *winning; + int *strategy; + std::vector > just; -bool to_inversion = true; -bool only_recompute_when_attracted = true; + bool to_inversion = true; + bool only_recompute_when_attracted = true; -uintqueue Q; -uintqueue D; + uintqueue Q; + uintqueue D; -int attractExt(int i, int r, std::vector *R); -std::pair attractLosing(int i, int r, std::vector *S, int next_r); + int attractExt(int i, int r, std::vector *R); + std::pair attractLosing(int i, int r, std::vector *S, int next_r); }; } #endif diff --git a/src/zlkjb.cpp b/src/zlkjb.cpp index 7151ab1..436eb45 100644 --- a/src/zlkjb.cpp +++ b/src/zlkjb.cpp @@ -75,9 +75,9 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) strategy[from] = cur; Q.push(from); } else { - if (strategy[from] > -1 && strategy[from] != cur) { - continue; - } + if (strategy[from] > -1 && strategy[from] != cur) { + continue; + } // owned by other parity int count = region[from]; if (count == BOT) { @@ -110,7 +110,7 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) /** * Find all nodes in S (and in the game with region >= r) that attract to the other player. */ - std::pair ZLKJBSolver::attractLosing(int i, int r, std::vector *S, int next_r) +std::pair ZLKJBSolver::attractLosing(int i, int r, std::vector *S, int next_r) { int count = 0; const int pr = priority[i]; @@ -130,7 +130,9 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) * But we do not record which nodes are head nodes. * TODO! */ - S->erase(std::remove_if(S->begin(), S->end(), [this](int i) {return this->strategy[i] != -2;}), S->end()); + S->erase(std::remove_if(S->begin(), S->end(), [this](int i) { + return this->strategy[i] != -2; + }), S->end()); for (int i : *S) { if(strategy[i] != -2) continue; // check if the node is attracted @@ -178,11 +180,11 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) const int *_in = ins + ina[cur]; for (int from = *_in; from != -1; from = *++_in) { // if (region[from] == -1) LOGIC_ERROR; - if (region[from] < r && region[from] >= 0){ - continue; // not in subgame, or disabled + if (region[from] < r && region[from] >= 0) { + continue; // not in subgame, or disabled } if (winning[from] != pl) { - continue; // already lost + continue; // already lost } // not watched if(strategy[from] > -1 && strategy[from] != cur) continue; @@ -197,89 +199,89 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) // owned by us bool can_escape = false; { - const int *_out = outs + outa[from]; - for (int to = *_out; to != -1; to = *++_out) { - // if (region[to] == -1) LOGIC_ERROR; - if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled - if (winning[to] == 1 - pl) continue; // not an escape - can_escape = true; - //switch strategy - //just[strategy[from]].erase(from); - strategy[from] = to; - break; - } + const int *_out = outs + outa[from]; + for (int to = *_out; to != -1; to = *++_out) { + // if (region[to] == -1) LOGIC_ERROR; + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or disabled + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + //switch strategy + //just[strategy[from]].erase(from); + strategy[from] = to; + break; + } } if (can_escape) { - if(winning[from] == -1) { - LOGIC_ERROR; - } - winning[from] = -1; - D.push(from); + if(winning[from] == -1) { + LOGIC_ERROR; + } + winning[from] = -1; + D.push(from); } else { - region[from] = r; - winning[from] = 1-pl; - strategy[from] = -1; - Q.push(from); + region[from] = r; + winning[from] = 1-pl; + strategy[from] = -1; + Q.push(from); } } } } for(unsigned int idx = 0; idx < D.size(); idx++) { - int cur = D[idx]; - // destroy dependents on - if(winning[cur] != -1) { - continue; - } - region[cur] = BOT; - const int *_in = ins + ina[cur]; - for (int from = *_in; from != -1; from = *++_in) { - if (region[from] < r){ - continue; // not in subgame, or disabled + int cur = D[idx]; + // destroy dependents on + if(winning[cur] != -1) { + continue; } - if (winning[from] != pl) { - continue; + region[cur] = BOT; + const int *_in = ins + ina[cur]; + for (int from = *_in; from != -1; from = *++_in) { + if (region[from] < r) { + continue; // not in subgame, or disabled + } + if (winning[from] != pl) { + continue; + } + region[from] = BOT; + winning[from] = -1; + D.push(from); } - region[from] = BOT; - winning[from] = -1; - D.push(from); - } } for(unsigned int idx = 0; idx < D.size(); idx++) { - int cur = D[idx]; - if(region[cur] != BOT) { - continue; - } - strategy[cur] = -3; - if (owner[cur] == pl) { - // "winner" attraction - const int *_out = outs + outa[cur]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] < r) continue; // not in subgame, or -1/-2 - if (winning[to] == 1 - pl) continue; // not attracting - region[cur] = next_r; - winning[cur] = pl; - strategy[cur] = to; - Q.push(cur); - break; + int cur = D[idx]; + if(region[cur] != BOT) { + continue; } - } else { - // "loser" attraction - bool can_escape = false; - const int *_out = outs + outa[cur]; - for (int to = *_out; to != -1; to = *++_out) { - if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously - // if not previously attracted, then either DIS or winning[to] != pl; - if (winning[to] == 1 - pl) continue; // not an escape - can_escape = true; - break; - } - if (!can_escape) { - // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); - region[cur] = next_r; - winning[cur] = pl; - strategy[cur] = -1; - Q.push(cur); + strategy[cur] = -3; + if (owner[cur] == pl) { + // "winner" attraction + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] < r) continue; // not in subgame, or -1/-2 + if (winning[to] == 1 - pl) continue; // not attracting + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = to; + Q.push(cur); + break; + } + } else { + // "loser" attraction + bool can_escape = false; + const int *_out = outs + outa[cur]; + for (int to = *_out; to != -1; to = *++_out) { + if (region[to] >= 0 and region[to] < r) continue; // not in subgame, or -1/-2, would otherwise be attracted to subgame previously + // if not previously attracted, then either DIS or winning[to] != pl; + if (winning[to] == 1 - pl) continue; // not an escape + can_escape = true; + break; + } + if (!can_escape) { + // if (trace) fmt::printf(logger, "forced %d (%d) to W_%d\n", i, priority[i], 1-pl); + region[cur] = next_r; + winning[cur] = pl; + strategy[cur] = -1; + Q.push(cur); } } while (!Q.empty()) { @@ -329,16 +331,16 @@ ZLKJBSolver::attractExt(int i, int r, std::vector *R) } int lost = 0; while(!D.empty()) { - int cur = D.pop(); - - if(region[cur] != BOT) { - continue; - } - strategy[cur] = -3; - lost ++ ; - if(new_i < cur) { - new_i = cur; - } + int cur = D.pop(); + + if(region[cur] != BOT) { + continue; + } + strategy[cur] = -3; + lost++; + if(new_i < cur) { + new_i = cur; + } } return std::pair(count, new_i); } @@ -354,16 +356,16 @@ ZLKJBSolver::run() strategy = new int[n_nodes]; std::vector history; - std::vector> levels; + std::vector > levels; // get number of nodes and create and initialize inverse array max_prio = -1; for (int n=n_nodes-1; n>=0; n--) { - strategy[n] = -4 ; + strategy[n] = -4; winning[n] = -1; region[n] = disabled[n] ? DIS : BOT; if (disabled[n]) continue; - strategy[n] = -3 ; + strategy[n] = -3; const int pr = game->priority[n]; if (max_prio == -1) { max_prio = pr; @@ -448,7 +450,7 @@ ZLKJBSolver::run() */ #ifndef NDEBUG - if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); + if (trace) fmt::printf(logger, "current level contains %zu nodes\n", A->size()); #endif /** @@ -466,9 +468,9 @@ ZLKJBSolver::run() } if (count <= 0) { - if(new_i >= 0) { // count =< 0 => new_i >= 0 - LOGIC_ERROR; - } + if(new_i >= 0) { // count =< 0 => new_i >= 0 + LOGIC_ERROR; + } /** * Nothing attracted to opponent, add A to W0/W1, fix strategies, go up. */ @@ -477,14 +479,14 @@ ZLKJBSolver::run() * For nodes that are won and controlled by , check if their strategy needs to be fixed. */ if (strategy[v] != -2) { - if(strategy[v] != -1 && winning[strategy[v]] != pl) { - LOGIC_ERROR; - } - continue; // good strategy + if(strategy[v] != -1 && winning[strategy[v]] != pl) { + LOGIC_ERROR; + } + continue; // good strategy } if (owner[v] != pl) { - strategy[v] = -1; - continue; // not controlled by + strategy[v] = -1; + continue; // not controlled by } /** * Strategy of vertex needs to be updated! @@ -515,11 +517,11 @@ ZLKJBSolver::run() /** * And pop the stack to go up */ - history.back() = 2; // set current phase to 2 + history.back() = 2; // set current phase to 2 /*levels.pop_back(); - history.pop_back(); - history.pop_back(); - history.pop_back();*/ + history.pop_back(); + history.pop_back(); + history.pop_back();*/ } else { /** * And push the stack to go down @@ -541,14 +543,14 @@ ZLKJBSolver::run() * For nodes that are won and controlled by , check if their strategy needs to be fixed. */ if (strategy[v] != -2) { - if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { - LOGIC_ERROR; - } - continue; // good strategy + if(strategy[v] != -1 && winning[strategy[v]] != winning[v]) { + LOGIC_ERROR; + } + continue; // good strategy } if (owner[v] != pl) { - strategy[v] = -1; - continue; // not controlled by + strategy[v] = -1; + continue; // not controlled by } /** * Strategy of vertex needs to be updated! diff --git a/src/zlkjb.hpp b/src/zlkjb.hpp index 7a91432..777c0ea 100644 --- a/src/zlkjb.hpp +++ b/src/zlkjb.hpp @@ -29,30 +29,30 @@ namespace pg { class ZLKJBSolver : public Solver { public: -ZLKJBSolver(Oink *oink, Game *game); -virtual ~ZLKJBSolver(); + ZLKJBSolver(Oink *oink, Game *game); + virtual ~ZLKJBSolver(); -virtual void run(); + virtual void run(); -int iterations; + int iterations; protected: -int *inverse; -int max_prio; + int *inverse; + int max_prio; -int *region; -int *winning; -int *strategy; + int *region; + int *winning; + int *strategy; -bool to_inversion = true; -bool only_recompute_when_attracted = true; + bool to_inversion = true; + bool only_recompute_when_attracted = true; -uintqueue Q; -uintqueue D; + uintqueue Q; + uintqueue D; -int attractExt(int i, int r, std::vector *R); -std::pair attractLosing(int i, int r, std::vector *S, int next_r); + int attractExt(int i, int r, std::vector *R); + std::pair attractLosing(int i, int r, std::vector *S, int next_r); }; } #endif From 2f3a091d6a499a411da230c6da4de43b6e34a75d Mon Sep 17 00:00:00 2001 From: Ruben Lapauw Date: Wed, 22 Jan 2020 21:21:53 -0600 Subject: [PATCH 3/4] Fixing flagged issues --- src/ppj.cpp | 9 +++++++-- src/ppj.hpp | 6 ------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ppj.cpp b/src/ppj.cpp index 195d36d..3166b9d 100644 --- a/src/ppj.cpp +++ b/src/ppj.cpp @@ -26,16 +26,21 @@ namespace pg { +struct escape { + int region; + int play; + int to; +}; int pm_cmp(int pl, int p_left, int p_right) { p_left += pl; p_right += pl; return p_left == p_right ? 0 : (p_right & 1) != (p_left & 1) ? (p_right & 1) - (p_left & 1) : - (p_left & 1) ^ (p_left < p_right) ? -1 : 1; + ((p_left & 1) ^ (p_left < p_right)) ? -1 : 1; } -PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game) +PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game), waitingPriority(0) { } diff --git a/src/ppj.hpp b/src/ppj.hpp index 5525c6c..138b06f 100644 --- a/src/ppj.hpp +++ b/src/ppj.hpp @@ -25,12 +25,6 @@ namespace pg { -struct escape { - int region; - int play; - int to; -}; - class PPJSolver : public PPSolver { public: From b44fb1c82f92f091755ad47e221e311783ef4524 Mon Sep 17 00:00:00 2001 From: Ruben Lapauw Date: Wed, 22 Jan 2020 21:30:59 -0600 Subject: [PATCH 4/4] Fixing flagged issues (2) --- src/ppj.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ppj.cpp b/src/ppj.cpp index 3166b9d..14e7823 100644 --- a/src/ppj.cpp +++ b/src/ppj.cpp @@ -40,7 +40,7 @@ int pm_cmp(int pl, int p_left, int p_right) { ((p_left & 1) ^ (p_left < p_right)) ? -1 : 1; } -PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game), waitingPriority(0) +PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game), waitingPriority(0), waiting(NULL) { }