@@ -127,7 +127,7 @@ void GameSatSolver::AddChefConstraints(int chef, int chef_number) {
127127 if (role_claims_[i][0 ] == RECLUSE) {
128128 evil_options_i.push_back (
129129 model_.NewEquivalentVarAnd (
130- {RoleVar (i, RECLUSE, night1), Not (PoisonerPickVar (i, night1))},
130+ {RoleVar (i, RECLUSE, night1), Not (PoisonedVar (i, night1))},
131131 absl::StrFormat (" healthy_recluse_%s_%s" , g_.PlayerName (i),
132132 night1)));
133133 }
@@ -151,7 +151,7 @@ void GameSatSolver::AddChefConstraints(int chef, int chef_number) {
151151 evil_pairs, chef_number, absl::StrFormat (
152152 " chef_%s_number_%d" , g_.PlayerName (chef), chef_number));
153153 model_.AddOr (
154- {Not (RoleVar (chef, CHEF, night1)), PoisonerPickVar (chef, night1), correct});
154+ {Not (RoleVar (chef, CHEF, night1)), PoisonedVar (chef, night1), correct});
155155}
156156
157157void GameSatSolver::AddEmpathConstraints () {
@@ -167,17 +167,19 @@ void GameSatSolver::AddEmpathConstraints() {
167167}
168168
169169void GameSatSolver::AddEmpathConstraints (
170- int player, int number, const Time& time) {
170+ int player, int number, const Time& night_time) {
171+ // Empath goes after Evils, so the time of all registration checks needs
172+ // to be the daytime:
173+ const Time time = night_time + 1 ;
171174 vector<int > alive_neighbors = g_.AliveNeighbors (player, time); // Size 2.
172175 const int ping1 = alive_neighbors[0 ], ping2 = alive_neighbors[1 ];
173- // We assume a Spy will not be poisoned. But Empath goes after the Imp, so
174- // need to check day role.
176+ // We assume a Spy will not be poisoned.
175177 BoolVar ping1_regs_good = model_.NewEquivalentVarOr (
176- {Not (StartingEvilVar (ping1)), RoleVar (ping1, SPY, time + 1 )},
178+ {Not (StartingEvilVar (ping1)), RoleVar (ping1, SPY, time)},
177179 absl::StrFormat (" empath_%s_registers_%s_good_%s" , g_.PlayerName (player),
178180 g_.PlayerName (ping1), time));
179181 BoolVar ping2_regs_good = model_.NewEquivalentVarOr (
180- {Not (StartingEvilVar (ping2)), RoleVar (ping2, SPY, time + 1 )},
182+ {Not (StartingEvilVar (ping2)), RoleVar (ping2, SPY, time)},
181183 absl::StrFormat (" empath_%s_registers_%s_good_%s" , g_.PlayerName (player),
182184 g_.PlayerName (ping2), time));
183185 vector<BoolVar> ping1_regs_evil_cases ({StartingEvilVar (ping1)});
@@ -252,11 +254,13 @@ void GameSatSolver::AddFortuneTellerConstraints() {
252254}
253255
254256void GameSatSolver::AddFortuneTellerConstraints (
255- int player, int pick1, int pick2, bool yes, const Time& time) {
257+ int player, int pick1, int pick2, bool yes, const Time& night_time) {
258+ // Fortune Teller goes after Evils, so time of all registration checks needs
259+ // to be the daytime:
260+ const Time time = night_time + 1 ;
256261 vector<BoolVar> yes_options ({
257- // Fortune Teller goes after the Imp, so we need to check day roles.
258- RoleVar (pick1, IMP, time + 1 ), RedHerringVar (pick1),
259- RoleVar (pick2, IMP, time + 1 ), RedHerringVar (pick2)});
262+ RoleVar (pick1, IMP, time), RedHerringVar (pick1),
263+ RoleVar (pick2, IMP, time), RedHerringVar (pick2)});
260264 BoolVar poisoned_recluse;
261265 if (yes) { // We can only infer Recluse possibilities from Yes answer.
262266 for (int pick : {pick1, pick2}) {
@@ -427,16 +431,20 @@ BoolVar GameSatSolver::AliveRoleVar(Role role, const Time& time) {
427431 absl::StrFormat (" alive_%s_%s" , Role_Name (role), time));
428432}
429433
434+ // This works for everyone except the Imp. The Imp is poisoned iff they were
435+ // picked by the Poisoner. Everyone else also need to account for the
436+ // Poisoner being alive on the *day after* the pick.
430437BoolVar GameSatSolver::PoisonedVar (int player, const Time& time) {
431438 Time night = time.is_day ? time - 1 : time;
432- BoolVar picked = PoisonerPickVar (player, time );
439+ BoolVar picked = PoisonerPickVar (player, night );
433440 const auto night_deaths = g_.Deaths (night);
434441 if (night_deaths.empty ()) {
435442 return picked;
436443 }
437- // At most one night death in TB:
444+ // This accounts for both a starpass to the Poisoner and Imp killing the
445+ // Poisoner in the night.
438446 return model_.NewEquivalentVarAnd (
439- {Not ( RoleVar (night_deaths[ 0 ], POISONER, night) ), picked},
447+ {AliveRoleVar ( POISONER, night + 1 ), picked},
440448 absl::StrFormat (" poisoned_%s_%s" , g_.PlayerName (player), night));
441449}
442450
@@ -591,7 +599,8 @@ void GameSatSolver::AddNoVictoryConstraints(const Time& time) {
591599 vector<BoolVar> cases;
592600 // No Mayor win.
593601 if (num_alive - g_.Deaths (time).size () == 3 &&
594- g_.Execution (time) == kNoPlayer ) {
602+ g_.Execution (time) == kNoPlayer &&
603+ (time < g_.CurrentTime () || g_.NoExecutionsDeclared ())) {
595604 // Mayor was not alive or poisoned.
596605 for (int mayor : AliveRolePossibilities (MAYOR, time)) {
597606 cases.push_back (model_.NewEquivalentVarOr (
@@ -971,7 +980,7 @@ void GameSatSolver::AddImpStarpassConstraints(const Time& time) {
971980 const Role role_claim = role_claims_[i][time.count - 1 ];
972981 BoolVar healthy_recluse_i = role_claim == IMP ?
973982 model_.NewEquivalentVarAnd (
974- {RoleVar (i, RECLUSE, time), Not (PoisonerPickVar (i, time))},
983+ {RoleVar (i, RECLUSE, time), Not (PoisonedVar (i, time))},
975984 absl::StrFormat (" healthy_recluse_%s_%s" , g_.PlayerName (i), time)) :
976985 model_.FalseVar ();
977986 for (Role role : {POISONER, SPY, SCARLET_WOMAN, BARON, RECLUSE}) {
@@ -1017,12 +1026,12 @@ void GameSatSolver::AddImpActionConstraints(const internal::RoleAction& ra) {
10171026 // Target was not a healthy Soldier
10181027 if (IsRolePossible (imp_kill, SOLDIER, time)) {
10191028 model_.AddOr ({Not (RoleVar (imp_kill, SOLDIER, time)),
1020- PoisonerPickVar (imp_kill, time)});
1029+ PoisonedVar (imp_kill, time)});
10211030 }
10221031 // Target was not healthy Monk protected
10231032 for (int monk : PossibleMonkProtecting (imp_kill, time)) {
10241033 model_.AddOr ({Not (RoleVar (monk, MONK, time)),
1025- PoisonerPickVar (monk, time)});
1034+ PoisonedVar (monk, time)});
10261035 }
10271036 return ;
10281037 }
@@ -1034,25 +1043,25 @@ void GameSatSolver::AddImpActionConstraints(const internal::RoleAction& ra) {
10341043 if (IsRolePossible (target, SOLDIER, time)) {
10351044 cases.push_back (model_.NewEquivalentVarAnd (
10361045 {RoleVar (target, SOLDIER, time),
1037- Not (PoisonerPickVar (target, time))},
1046+ Not (PoisonedVar (target, time))},
10381047 absl::StrFormat (" healthy_SOLDIER_%s_%s" , g_.PlayerName (target),
10391048 time)));
10401049 }
10411050 // Target was healthy Monk protected
10421051 for (int monk : PossibleMonkProtecting (target, time)) {
10431052 cases.push_back (model_.NewEquivalentVarAnd (
10441053 {RoleVar (monk, MONK, time),
1045- Not (PoisonerPickVar (monk, time))},
1054+ Not (PoisonedVar (monk, time))},
10461055 absl::StrFormat (" healthy_MONK_%s_%s" , g_.PlayerName (monk), time)));
10471056 }
10481057 // Target was a healthy Mayor, not healthy Monk protected, and bounced to no
10491058 // kill.
10501059 if (IsRolePossible (target, MAYOR, time)) {
10511060 vector<BoolVar> mayor_bounce_no_kill (
1052- {RoleVar (target, MAYOR, time), Not (PoisonerPickVar (target, time))});
1061+ {RoleVar (target, MAYOR, time), Not (PoisonedVar (target, time))});
10531062 for (int monk : PossibleMonkProtecting (target, time)) {
10541063 mayor_bounce_no_kill.push_back (model_.NewEquivalentVarOr (
1055- {Not (RoleVar (monk, MONK, time)), PoisonerPickVar (monk, time)},
1064+ {Not (RoleVar (monk, MONK, time)), PoisonedVar (monk, time)},
10561065 absl::StrFormat (" not_healthy_MONK_%s_%s" , g_.PlayerName (monk),
10571066 time)));
10581067 }
@@ -1063,13 +1072,13 @@ void GameSatSolver::AddImpActionConstraints(const internal::RoleAction& ra) {
10631072 // Healthy Soldier.
10641073 if (IsRolePossible (i, SOLDIER, time)) {
10651074 no_kill_cases.push_back (model_.NewEquivalentVarAnd (
1066- {RoleVar (i, SOLDIER, time), Not (PoisonerPickVar (i, time))},
1075+ {RoleVar (i, SOLDIER, time), Not (PoisonedVar (i, time))},
10671076 absl::StrFormat (" healthy_SOLDIER_%s_%s" , g_.PlayerName (i),
10681077 time)));
10691078 }
10701079 for (int monk : PossibleMonkProtecting (i, time)) {
10711080 no_kill_cases.push_back (model_.NewEquivalentVarAnd (
1072- {RoleVar (monk, MONK, time), Not (PoisonerPickVar (monk, time))},
1081+ {RoleVar (monk, MONK, time), Not (PoisonedVar (monk, time))},
10731082 absl::StrFormat (" healthy_MONK_%s_%s" , g_.PlayerName (monk),
10741083 time)));
10751084 }
@@ -1088,10 +1097,10 @@ void GameSatSolver::AddImpActionConstraints(const internal::RoleAction& ra) {
10881097 // Kill bounces. Target is a healthy Mayor, not healthy Monk protected.
10891098 if (IsRolePossible (target, MAYOR, time)) {
10901099 model_.AddEquality (RoleVar (target, MAYOR, time), true );
1091- model_.AddEquality (PoisonerPickVar (target, time), false );
1100+ model_.AddEquality (PoisonedVar (target, time), false );
10921101 for (int monk : PossibleMonkProtecting (target, time)) {
10931102 model_.AddOr ({Not (RoleVar (monk, MONK, time)),
1094- PoisonerPickVar (monk, time)});
1103+ PoisonedVar (monk, time)});
10951104 }
10961105 return ;
10971106 }
@@ -1125,12 +1134,12 @@ void GameSatSolver::AddImpConstraints(
11251134 // Target was not a healthy Soldier
11261135 if (IsRolePossible (imp_kill, SOLDIER, time)) {
11271136 model_.AddOr ({Not (RoleVar (imp_kill, SOLDIER, time)),
1128- PoisonerPickVar (imp_kill, time)});
1137+ PoisonedVar (imp_kill, time)});
11291138 }
11301139 // Target was not healthy Monk protected
11311140 for (int monk : PossibleMonkProtecting (imp_kill, time)) {
11321141 model_.AddOr ({Not (RoleVar (monk, MONK, time)),
1133- PoisonerPickVar (monk, time)});
1142+ PoisonedVar (monk, time)});
11341143 }
11351144 return ;
11361145 }
@@ -1147,7 +1156,7 @@ void GameSatSolver::AddImpConstraints(
11471156 for (int i : AliveRolePossibilities (role, time)) {
11481157 cases.push_back (model_.NewEquivalentVarAnd (
11491158 {RoleVar (i, role, time),
1150- Not (PoisonerPickVar (i, time))},
1159+ Not (PoisonedVar (i, time))},
11511160 absl::StrFormat (" healthy_%s_%s_%s" , Role_Name (role),
11521161 g_.PlayerName (i), time)));
11531162 }
0 commit comments