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..14e7823 --- /dev/null +++ b/src/ppj.cpp @@ -0,0 +1,299 @@ +/* + * 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 { + +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; +} + +PPJSolver::PPJSolver(Oink *oink, Game *game) : PPSolver(oink, game), waitingPriority(0), waiting(NULL) +{ +} + +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..138b06f --- /dev/null +++ b/src/ppj.hpp @@ -0,0 +1,51 @@ +/* + * 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 { + +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..f378b6d --- /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..ad771b0 --- /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..436eb45 --- /dev/null +++ b/src/zlkjb.cpp @@ -0,0 +1,598 @@ +/* + * 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..777c0ea --- /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