import sys
class Setup:
def __init__(self, playerlist):
self.town = len([c for c in playerlist if not c])
self.scum = len([c for c in playerlist if c])
self.playerlist = playerlist
self.phase = 0
self.victory = 0
def calculate(self):
while not self.check_victory():
self.phase += 1
self.elimination()
self.night_actions()
if self.check_victory() == 1:
return True
else:
return False
def elimination(self):
self.remove_player(len(self.playerlist) - 1)
def nightkill(self):
idx = 0
while self.playerlist[idx]:
idx += 1
self.remove_player(idx)
def night_actions(self):
self.nightkill()
def remove_player(self, idx):
align = self.playerlist.pop(idx)
if align:
self.scum -= 1
return 1
else:
self.town -= 1
return 0
def check_victory(self):
if self.scum >= self.town or self.victory == 1:
return 1
elif self.scum == 0 or self.victory == -1:
return -1
else:
return 0
class Mountainous(Setup):
pass
class WhiteFlag(Setup):
def check_victory(self):
if self.scum >= self.town:
return 1
elif self.scum <= 1:
return -1
else:
return 0
class Nightless(Setup):
def night_actions(self):
pass
class TrustFall(Nightless):
def __init__(self, playerlist):
Nightless.__init__(self, playerlist)
self.auto = False
def elimination(self):
if self.playerlist[0] == self.playerlist[1] == True:
self.auto = True
self.remove_player(0)
self.remove_player(0)
def check_victory(self):
if self.auto:
return -1
if self.scum >= self.town:
return -1
elif self.scum == 0:
return 1
else:
return 0
class GoldenSun(Setup):
def elimination(self):
if self.phase <= 4:
align = self.remove_player(len(self.playerlist) - 1)
if align:
Setup.night_actions(self)
else:
self.victory = -1
for j in range(len(self.playerlist) - self.scum, len(self.playerlist)):
if not self.playerlist[j]:
self.victory = 1
def night_actions(self):
if self.phase == 2:
Setup.night_actions(self)
class GoldenSunV2(Setup):
def elimination(self):
if self.town > 4:
align = self.remove_player(len(self.playerlist) - 1)
if align:
Setup.night_actions(self)
else:
self.victory = -1
for j in range(len(self.playerlist) - self.scum, len(self.playerlist)):
if not self.playerlist[j]:
self.victory = 1
def night_actions(self):
pass
def calculate_ev(setup_type, town_count, scum_count):
permutations = permute(scum_count, town_count)
total_cases = len(permutations)
town_wins = 0.0
for p in permutations:
s = setup_type(p)
res = s.calculate()
if not res:
town_wins += 1
return town_wins / total_cases
def permute(trues, falses):
res = []
if trues == 0:
res.append([])
for x in range(falses):
res[0].append(False)
return res
if falses == 0:
res.append([])
for x in range(trues):
res[0].append(True)
return res
nw = permute(trues - 1, falses)
for l in nw:
res.append([True] + l)
nw = permute(trues, falses - 1)
for l in nw:
res.append([False] + l)
return res
def main():
print(calculate_ev(TrustFall, 8, 2))
if __name__ == "__main__":
if len(sys.argv) < 4:
main()
else:
setup_str = sys.argv[1]
setup = getattr(sys.modules[__name__], setup_str)
town_count = int(sys.argv[2])
scum_count = int(sys.argv[3])
print(calculate_ev(setup, town_count, scum_count))
If you have Python on your computer, you should just be able to paste it in and run it; otherwise, you can use a website such as this to run it.
You can either run it with command line arguments [setupname] [#town] [#scum], or just go in and edit line 155 ("print(calculate_ev(TrustFall, 8, 2))") with the appropriate setup name, town count, and scum count.
It uses the Charisma Model of EV Calculation, which assumes that players are randomly ordered from towniest to scummiest, and scummiest players are eliminated while towniest townies are NK'd.
Current setups supported are Mountainous, White Flag, Nightless, Trust Fall, and a couple versions of Isis's Golden Sun. So far it doesn't have support for PR's; I could add it, but I don't really feel like EV is that useful for PR setups. Maybe very simple ones.
To add another setup, just create a new class like the others and override whichever methods are different:
calculate - for the overall day/night structure
elimination - for determining who gets eliminated and anything else that happens during the day
nightkill - for determining who, if anyone, dies during the night
check_victory - for determining who won; 1 if scum won, -1 if town won, 0 if the game is still going.
(remove_player handles the death of a particular slot, but there shouldn't be a need to override this.)
The living players are stored in playerlist, with the towniest players at the front of the list and the scummiest players at the back; the list contains True if the player is scum, and False if they are town.
If you have any questions, let me know.
It's always the same. When you fire that first shot, no matter how right you feel, you have no idea who's going to die. You don't know whose children are going to scream and burn. How many hearts will be broken. How many lives shattered. How much blood will spill, until everybody does what they're always going to have to do from the very beginning... SIT DOWN AND TALK!
import sys
class Setup:
def __init__(self, playerlist):
self.town = len([c for c in playerlist if not c])
self.scum = len([c for c in playerlist if c])
self.playerlist = playerlist
self.phase = 0
self.victory = 0
def calculate(self):
while not self.check_victory():
self.phase += 1
self.elimination()
self.night_actions()
if self.check_victory() == 1:
return 0
else:
return 1
def elimination(self):
self.remove_player(len(self.playerlist) - 1)
def nightkill(self):
idx = 0
while self.playerlist[idx] >= 1:
idx += 1
return self.remove_player(idx)
def night_actions(self):
self.nightkill()
def remove_player(self, idx):
align = self.playerlist.pop(idx)
if align >= 1:
self.scum -= 1
else:
self.town -= 1
return align
def move_to_position(self, starting, ending):
val = self.playerlist.pop(starting)
if starting < ending:
self.playerlist.insert(ending - 1, val)
else:
self.playerlist.insert(ending, val)
def check_victory(self):
if self.scum >= self.town or self.victory == 1:
return 1
elif self.scum == 0 or self.victory == -1:
return -1
else:
return 0
class Mountainous(Setup):
pass
class WhiteFlag(Setup):
def check_victory(self):
if self.scum >= self.town:
return 1
elif self.scum <= 1:
return -1
else:
return 0
class Nightless(Setup):
def night_actions(self):
pass
class TrustFall(Nightless):
def __init__(self, playerlist):
Nightless.__init__(self, playerlist)
self.auto = False
def elimination(self):
if self.playerlist[0] == self.playerlist[1] == True:
self.auto = True
self.remove_player(0)
self.remove_player(0)
def check_victory(self):
if self.auto:
return -1
if self.scum >= self.town:
return -1
elif self.scum == 0:
return 1
else:
return 0
class GoldenSun(Setup):
def elimination(self):
if self.phase <= 4:
align = self.remove_player(len(self.playerlist) - 1)
if align:
Setup.night_actions(self)
else:
self.victory = -1
for j in range(len(self.playerlist) - self.scum, len(self.playerlist)):
if not self.playerlist[j]:
self.victory = 1
def night_actions(self):
if self.phase == 2:
Setup.night_actions(self)
class GoldenSunV2(Setup):
def elimination(self):
if self.town > 4:
align = self.remove_player(len(self.playerlist) - 1)
if align:
Setup.night_actions(self)
else:
self.victory = -1
for j in range(len(self.playerlist) - self.scum, len(self.playerlist)):
if not self.playerlist[j]:
self.victory = 1
def night_actions(self):
pass
class Vengescum(Setup):
def elimination(self):
align = self.remove_player(len(self.playerlist) - 1)
if align:
Setup.night_actions(self)
def night_actions(self):
pass
class DesperationDay(Setup):
def elimination(self):
align = self.remove_player(len(self.playerlist) - 1)
if align and self.phase == 3:
self.victory = -1
class DoubleDay(Setup):
def elimination(self):
Setup.elimination(self)
if not self.check_victory():
Setup.elimination(self)
class GreyFlagNightless(Vengescum, WhiteFlag):
pass
class NightlessVengefulMayhem(Setup):
def night_actions(self):
if self.phase == 2 or self.phase == 3:
Setup.night_actions(self)
class PRSetup(Setup):
def __init__(self, playerlist, pr_count):
Setup.__init__(self, playerlist)
self.pr_count = pr_count
def calculate(self):
town_wins = 0
pr_arrangements = permute(self.pr_count, self.town - self.pr_count)
ordering = self.playerlist + []
for s in pr_arrangements:
Setup.__init__(self, ordering + [])
index = 0
for j in range(len(self.playerlist)):
if self.playerlist[j] == 0:
if s[index] == 1:
self.playerlist[j] = -1
index += 1
while self.check_victory() == 0:
self.phase += 1
self.elimination()
self.night_actions()
if self.check_victory() != 1:
town_wins += 1
return town_wins / len(pr_arrangements)
class Science(PRSetup):
def __init__(self, playerlist, pr_count=2):
PRSetup.__init__(self, playerlist, pr_count)
def elimination(self):
while self.playerlist[-1] == -1:
self.move_to_position(len(self.playerlist) - 1, 0)
self.remove_player(len(self.playerlist) - 1)
class Cop9er(PRSetup):
def __init__(self, playerlist, pr_count=1):
PRSetup.__init__(self, playerlist, pr_count)
def cop_claim(self):
inno_indices = [i for i in range(len(self.playerlist)) if self.playerlist[i] == -2]
for i in inno_indices:
self.move_to_position(i, 0)
if -1 in self.playerlist:
self.move_to_position(self.playerlist.index(-1), 0)
if 2 in self.playerlist:
self.move_to_position(self.playerlist.index(2), len(self.playerlist))
def elimination(self):
if -1 in self.playerlist:
if 2 in self.playerlist or self.playerlist[-1] <= -1:
self.cop_claim()
PRSetup.elimination(self)
def night_actions(self):
if -1 in self.playerlist and self.check_victory() == 0 and self.playerlist[0] != -1:
check = int(len(self.playerlist) / 2)
while self.playerlist[check] == -2 or self.playerlist[check] == -1:
check -= 1
if self.playerlist[check] == 0:
# Inno
self.playerlist[check] = -2
elif self.playerlist[check] == 1:
# Guilty
self.playerlist[check] = 2
align = self.nightkill()
if align == -1:
self.cop_claim()
def calculate_ev(setup_type, town_count, scum_count):
permutations = permute(scum_count, town_count)
total_cases = len(permutations)
town_wins = 0.0
for p in permutations:
s = setup_type(p)
res = s.calculate()
town_wins += res
return town_wins / total_cases
def permute(trues, falses):
res = []
if trues == 0:
res.append([])
for x in range(falses):
res[0].append(0)
return res
if falses == 0:
res.append([])
for x in range(trues):
res[0].append(1)
return res
nw = permute(trues - 1, falses)
for l in nw:
res.append([1] + l)
nw = permute(trues, falses - 1)
for l in nw:
res.append([0] + l)
return res
def main():
print(calculate_ev(Cop9er, 7, 2))
if __name__ == "__main__":
if len(sys.argv) < 4:
main()
else:
setup_str = sys.argv[1]
setup = getattr(sys.modules[__name__], setup_str)
town_count = int(sys.argv[2])
scum_count = int(sys.argv[3])
print(calculate_ev(setup, town_count, scum_count))
I happened to be working on some setups for the Approved Open Setup discussion, and then I felt like modifying this so it could support simple PR play.
It's now possible to have a setup with any number of copies of a single town PR. To do this, extend PRSetup and override __init__ as demonstrated to set the number of PR's. In the playerlist variable, VT's are 0, scum are 1, and town PR's are -1 by default, but you can play with the numbers as long as you keep scum values >= 1 and town values <= 0. (In the Cop 9er, I used -2 to represent townies that were cleared by the cop and 2 to represent scum that were guiltied.)
The following setups have been added: Vengescum (nightless but scum are vengeful; No Eliminating Town is equivalent to this), Desperation Day, Double Day, Grey Flag Nightless (TIL that python has multiple inheritance), Nightless Vengeful Mayhem, SCIENCE! (2 goons, 2 masons, 3 VT's), and Cop 9er (2 goons, 1 cop, 6 VT's).
For pretty much any setup with PR's, some simplifying assumptions will need to be made. I've been using the following assumptions: scum never claim PR, scum can't PR hunt with better than random accuracy, cops check in the middle of the playerlist, cops claim only if they have a guilty or if they or an inno are about to be executed, and cops can crumb their results perfectly. These assumptions significantly decrease the objectivity of the result (since you can EV-hack by changing your assumptions) but the result is still useful as a ballpark estimate.
It's always the same. When you fire that first shot, no matter how right you feel, you have no idea who's going to die. You don't know whose children are going to scream and burn. How many hearts will be broken. How many lives shattered. How much blood will spill, until everybody does what they're always going to have to do from the very beginning... SIT DOWN AND TALK!
Hmm, after rereading Mathdino's thread, it would be pretty easy to assume that scum will always claim PR if they're about to be executed. Let me try that.
It's always the same. When you fire that first shot, no matter how right you feel, you have no idea who's going to die. You don't know whose children are going to scream and burn. How many hearts will be broken. How many lives shattered. How much blood will spill, until everybody does what they're always going to have to do from the very beginning... SIT DOWN AND TALK!
Whoa, allowing scum to claim cop and assuming that the real cop always outs to execute the fake cop causes the EV to drop from 45% to 31%. This is what I mean about EV-hacking.
It's always the same. When you fire that first shot, no matter how right you feel, you have no idea who's going to die. You don't know whose children are going to scream and burn. How many hearts will be broken. How many lives shattered. How much blood will spill, until everybody does what they're always going to have to do from the very beginning... SIT DOWN AND TALK!