For translation I took Nebo's mafia set /topic/at/845532/
In places it didn't come out as nicely as I'd have liked, some things I didn't do at all, but overall I'm happy with the result.
So let it lie here in case someone else is interested.
Notes are welcome here, in PM or on Skype. Suggestions are also welcome. Maybe someone knows how to make the code simpler or the scheme clearer. Because with the redirects, for example, such a mess turns out that I didn't even bother to describe them.
It computes the night's results and prints the log of that night.
the data is entered as lists:
1. players - data on roles: player nick, quenta, team, role, status, vote weight at the start of the game
2. priority_list - data on all the night actions (NAs) of the set: priority, performer, NA name, display in the newspaper, as well as technical info for creating the NA
3. operations - NAs applied during the current night: performer, NA name, applied to whom, additional info
4. current_alive_dict - player state at the start of the current night: alive, killed, executed, etc.
from the first array's data the roles are created - instances of the Role superclass
from the second array's data all the set's NAs are created and appended to the roles. Each NA is a subclass of the Action class.
Example run.
-----------------------------------------------
night number 1
-----------------------------------------------
Arandor13 (role Madness) applied action Change statuses to Arandor3 (role Holkhak),
result ==> cold / non-human; display in the newspaper --> fact
Arandor4 (role Hjaljot) applied action Block to Arandor14 (role Death),
result ==> success; display in the newspaper --> yes
Arandor2 (role Yoroar) applied action Compare to Arandor11 (role Cold), to Arandor12 (role Famine),
result ==> statuses are equal; display in the newspaper --> no
Arandor3 (role Holkhak) applied action Check to Arandor11 (role Cold),
result ==> cold; display in the newspaper --> no
Arandor5 (role Glariel) applied action Check to Arandor3 (role Holkhak),
result ==> non-human; display in the newspaper --> no
Arandor6 (role Flavius) applied action Protect to Arandor2 (role Yoroar),
result ==> success; display in the newspaper --> yes
Arandor9 (role Ralfjor) applied action Protect from Famine to Arandor12 (role Famine),
result ==> success; display in the newspaper --> yes
Arandor12 (role Famine) applied action Kill to Arandor2 (role Yoroar),
result ==> Quenta12; display in the newspaper --> yes
Arandor8 (role Stigjor) applied action Heal to Arandor6 (role Flavius),
result ==> heal not required; display in the newspaper --> yes
Arandor7 (role Esben) applied action Put in fetters to Arandor6 (role Flavius),
result ==> successful; display in the newspaper --> yes
-----------------------------------------------
{'killed': ['Arandor2'], 'in fetters': ['Arandor6'], 'alive': ['Arandor9', 'Arandor12', 'Arandor7', 'Arandor10', 'Arandor3', 'Arandor13', 'Arandor11', 'Arandor4', 'Arandor14', 'Arandor5', 'Arandor1', 'Arandor8'], 'executed': []}
-----------------------------------------------
13 votes and 13 players in the game
player votes:
{'Arandor9': 1, 'Arandor12': 1, 'Arandor7': 1, 'Arandor10': 1, 'Arandor3': 1, 'Arandor13': 1, 'Arandor11': 1, 'Arandor6': 0, 'Arandor4': 1, 'Arandor14': 1, 'Arandor5': 1, 'Arandor1': 2, 'Arandor8': 1}
-----------------------------------------------
night number 2
-----------------------------------------------
Arandor13 (role Madness) applied action Change statuses to Arandor1 (role Fritjof),
*** target is in the exception list
result ==> failure; display in the newspaper --> no
Arandor4 (role Hjaljot) applied action Block to Arandor10 (role Aurelius),
result ==> success; display in the newspaper --> yes
Arandor3 (role Holkhak) applied action Check to Arandor6 (role Flavius),
at the moment the action is applied the target is in fetters
result ==> warm; display in the newspaper --> no
Arandor5 (role Glariel) applied action Check to Arandor1 (role Fritjof),
result ==> human; display in the newspaper --> no
Arandor6 (role Flavius) applied action Protect to Arandor9 (role Ralfjor),
*** performer is in fetters or blocked
result ==> failure; display in the newspaper --> no
Arandor9 (role Ralfjor) applied action Protect from Famine to Arandor9 (role Ralfjor),
*** target is in the exception list
result ==> failure; display in the newspaper --> no
Arandor13 (role Madness) applied action Kill to Arandor6 (role Flavius),
at the moment the action is applied the target is in fetters
result ==> Quenta13; display in the newspaper --> yes
Arandor7 (role Esben) applied action Put in fetters to Arandor13 (role Madness),
result ==> successful; display in the newspaper --> yes
-----------------------------------------------
{'killed': ['Arandor2', 'Arandor6'], 'executed': ['Arandor8'], 'alive': ['Arandor1', 'Arandor4', 'Arandor12', 'Arandor14', 'Arandor3', 'Arandor7', 'Arandor9', 'Arandor5', 'Arandor10', 'Arandor11'], 'in fetters': ['Arandor13']}
-----------------------------------------------
11 votes and 11 players in the game
player votes:
{'Arandor7': 1, 'Arandor1': 2, 'Arandor4': 1, 'Arandor10': 1, 'Arandor9': 1, 'Arandor12': 1, 'Arandor5': 1, 'Arandor3': 1, 'Arandor13': 0, 'Arandor11': 1, 'Arandor14': 1}
Added after 3 minutes
# "player nick" and "role" must be unique - used as lookup keys.
# player nick, quenta, team, role, status, vote (at game start)
players = [ ["Arandor1", "Quenta1", "Team", "Fritjof", "cold / human" , 2],
["Arandor2", "Quenta2", "Team", "Yoroar" , "warm / human" , 1],
["Arandor3", "Quenta3", "Team", "Holkhak", "cold / non-human" , 1],
["Arandor4", "Quenta4", "Team", "Hjaljot", "warm / human" , 1],
["Arandor5", "Quenta5", "Team", "Glariel", "cold / non-human" , 1],
["Arandor6", "Quenta6", "Team", "Flavius", "warm / non-human" , 1],
["Arandor7", "Quenta7", "Team", "Esben", "warm / human" , 1],
["Arandor8", "Quenta8", "Team", "Stigjor", "warm / human" , 1],
["Arandor9", "Quenta9", "Team", "Ralfjor", "warm / human" , 1],
["Arandor10", "Quenta10", "Team", "Aurelius", "warm / human" , 1],
["Arandor11", "Quenta11", "*", "Cold" , "cold / non-human" , 1],
["Arandor12", "Quenta12", "*", "Famine" , "cold / non-human" , 1],
["Arandor13", "Quenta13", "*", "Madness", "warm / non-human" , 1],
["Arandor14", "Quenta14", "*", "Death" , "cold / human" , 1]
]
# data at the start of the night:
# --- filled in automatically on the first night, on subsequent nights filled in by copy-paste + adding the day's results ---
night_no = 2 # night number
#current_alive_dict = {"alive":[], "in fetters":[], "killed":[], "executed":[]} # at the start of the first night everyone is alive
current_alive_dict = {'killed': ['Arandor2'], 'in fetters': ['Arandor6'], 'alive': ['Arandor9', 'Arandor12', 'Arandor7', 'Arandor10', 'Arandor3', 'Arandor13', 'Arandor11', 'Arandor4', 'Arandor14', 'Arandor5', 'Arandor1'], 'executed': ['Arandor8']}
class Role: # role superclass
# the __init__ constructor creates a role with properties: self, player nick, whose side, role, status, info for clues, vote weight)
def __init__(self, nickname, team, rolename, statuses, quenta="", vote=1):
self.nickname = nickname # player nick
self.quenta = quenta # info for clues
self.team = team # whose side
self.rolename = rolename # role name
self.vote = vote # vote weight
self.actions = {} # action list is empty for now (dict looked up by NA name)
self.statuses = statuses # status
self.alive = "alive" # alive, can take values {"alive", "in fetters", "killed", "executed"}
self.defender = None # defender (Flavius) among followers
self.killer = None # killer
self.blocked = False
self.deblocked = False
def set_block(self): # apply a block
self.blocked = True
return "success"
def set_deblock(self): # deblock
self.deblocked = True
return "success"
def change_alive(self, param): # death or resurrection of the role, jailing
self.alive = param # param in {"alive", "in fetters", "killed", "executed"}
return self.alive
def set_vote(self, weight): # set vote weight = weight
self.vote = weight
return self.vote
def set_statuses(self, param): # set the status value = param
self.statuses = param
return self.statuses
def get_status_no(self, no): # return the value of status number no (no = 1 or 2)
replay = self.statuses.rsplit("/")
if (len(self.statuses) < no) or (no not in {1, 2}):
print ("status check error for " + self.rolename)
return False
return str(replay[no-1]).strip()
def add_action(self, name_action, action): # add an element keyed by name_action to the role's action list
self.actions[name_action] = action
return self.actions
class Action: # base superclass for NAs
# the __init__ constructor creates an action with properties: performer, action name, priority, display, exceptions, number of NA targets
def __init__(self, owner, actionname, priority, display, exceptions=[], targets_qlty=1):
self.owner = owner # performer
self.actionname = actionname # action name
self.priority = priority # priority
self.display = display # display in the newspaper
self.targets_qlty = targets_qlty # number of targets for this NA
self.exceptions = exceptions+[owner] # which roles the NA cannot be applied to; the owner is appended to the exception list as "cannot use an NA on yourself (owner)"
def add_exception (self, exception):
self.exceptions.append(exception)
return self.exceptions
def print_characters(self, targets): # info about the performer, targets and the NA applied
target_string = ""
for target in targets:
target_string = target_string + " on "+target.nickname+" (role "+target.rolename+"), "
print (self.owner.nickname+" (role "+self.owner.rolename+") applied action "+self.actionname + target_string)
def do(self,targets=[],params=[]): # if the performer is dead, print the error "operation failed", otherwise carry out the "do nothing" action
self.print_characters(targets) # info about the performer, target and the NA applied
if (self.owner.blocked) and not(self.owner.deblocked):
print("*** performer is in fetters or blocked")
return False # "action failed"
if not(self.owner.alive == "alive"):
print("*** performer is dead")
return False # "action failed"
for target in targets:
if target in self.exceptions:
print("*** target is in the exception list")
return False # "action failed"
if (target.alive == "in fetters"):
print("at the moment the action is applied the target is in fetters")
if (target.alive == "killed") or (target.alive == "executed"):
print("at the moment the action is applied the target is dead")
return True
####### --- role creation, filling helper dictionaries ---
nicknames_dict = {} # helper dict "player nick" -> "corresponding role" for accessing roles by player nicks
rolenames_dict = {} # helper dict "role name" -> "corresponding role" for accessing roles by name
current_votes_dict = {} # actual, not reference, votes for printing the vote-count table
for row in players:
# an instance of the "Role" object (player nick, team, role, status, quenta, vote) is created and stored in the dict keyed by "player nick"
nicknames_dict[row[0]] = Role(row[0], row[2], row[3], row[4], row[1], row[5])
rolenames_dict[row[3]] = nicknames_dict[row[0]] # the same "Role" instance is stored in the dict keyed by "role name"
if night_no > 1 and len(current_alive_dict["alive"])>0: # on every night except the first
for row in (current_alive_dict["in fetters"]):
nicknames_dict[row].change_alive("in fetters") # set alive="in fetters" for everyone put in fetters
nicknames_dict[row].set_block() # set "blocked" for everyone put in fetters
nicknames_dict[row].set_vote(0) # - vote weight = 0 for everyone put in fetters
for row in (current_alive_dict["killed"]):
nicknames_dict[row].change_alive("killed") # set alive="killed" for everyone killed
for row in (current_alive_dict["executed"]):
nicknames_dict[row].change_alive("executed") # set alive="executed" for everyone executed
####### we append NAs and features to the roles
### Fritjof
# Features:
rolenames_dict["Fritjof"].set_vote(2) # - double vote
# - Madness's NAs don't work on Fritjof - ADDED TO THE EXCEPTION LIST
# DA: once per game executes a chosen player, closes the day - NOT IMPLEMENTED
# Yoroar
# Features: - is not redirected
# NA1: - compares one of the statuses of two players
class Action_compare(Action): # class to create the NA "compare status number no" (no = 1 or 2)
def __init__(self, owner, actionname, priority, display, exceptions=[], targets_qlty=2):
Action.__init__(self, owner, actionname, priority, display, exceptions, 2)# call superclass constructor
def do(self,targets,params): # compare targets by status number params[0] (params[0] = 1 or 2)
if Action.do(self, targets): # call superclass method
if targets[0].get_status_no(params[0]) == targets[1].get_status_no(params[0]):
rec = "statuses are equal"
else:
rec = "statuses differ"
return rec # result of comparing statuses
return False
# NA2: - reports the comparison result to Fritjof
class Action_message(Action): # class to create the NA "send a message to Fritjof"
def __init__(self, owner, actionname, priority, display, exceptions=[], targets_qlty=0):
Action.__init__(self, owner, actionname, priority, display, exceptions, 0)# call superclass constructor
def do(self,empty,params): # send message params[0] to Fritjof
if Action.do(self, [rolenames_dict["Fritjof"]]): # call superclass method
return "send message '"+params[0]+"' to "+rolenames_dict["Fritjof"].nickname+" (role Fritjof) " # send message
return False
### Holkhak
# NA: checks the first status of the chosen player
class Action_check_no_one(Action): # class to create the NA "check status number 1"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
return targets[0].get_status_no(1) # result of checking status number 1
return False
### Hjaljot
# NA: - blocks the chosen player
class Action_block(Action): # class for the NA "block target"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
return targets[0].set_block() # block target
return False
### Glariel
# NA: - checks the second status of the chosen player
class Action_check_no_two(Action): # class to create the NA "check status number 2"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
return targets[0].get_status_no(2) # result of checking status number 2
return False
### Flavius
# Features:
# - first status cannot be changed - NOT IMPLEMENTED
# - cannot be killed by Cold
# NA1: - protects the chosen player
class Action_defend(Action): # class for the NA "protect target"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
targets[0].defender = self.owner # add the defender to the target's followers :)
return "success"
return False
# NA2: - heals the chosen player from Cold
class Action_heal_frozen(Action): # class for the NA "heal target from Cold"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
if targets[0].killer == rolenames_dict["Cold"]: # if the killer Cold successfully visited the target
targets[0].change_alive("alive") # heal the target
return "heal successful"
return "heal from Cold is not required"
return False
### Esben
# Features: - is not redirected
# NA1: - puts the chosen player in fetters; the player cannot perform NAs at night, the vote doesn't count, can take part in daytime discussion (after death all those in fetters are released)
class Action_put_on_fetters(Action): # class to create the NA "put in fetters"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
if targets[0].alive == "alive":
targets[0].set_vote(0)
targets[0].change_alive("in fetters") # in fetters
return "successful"
return False
# NA2: - takes the fetters off the chosen player
class Action_take_off_fetters(Action): # class to create the NA "remove fetters"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
targets[0].set_vote(1)
if targets[0].rolename == "Fritjof":
targets[0].set_vote(2)
if targets[0].alive == "in fetters":
targets[0].change_alive("alive") # off the chain
return "successful"
return False
# if Esben is killed, the fetters come off everyone, but only at dawn
# if Esben is both killed and healed, everyone stays in fetters
# release from a block grants the right to act to a player in fetters
### Stigjor
# Features: - Madness's NAs don't work on Stigjor
# NA: - heals the chosen player (cannot heal oneself)
class Action_heal(Action): # class for the NA "heal target"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
if targets[0].killer != None: # if a killer successfully visited the target
targets[0].change_alive("alive") # heal the target
return "heal successful"
return "heal not required"
return False
### Ralfjor
# Features: - cannot be killed by Famine
# NA1: - lets a player use an NA twice per night - NOT IMPLEMENTED
# NA2: - protects the chosen player from Famine - (from being killed) - adds its target to its own exception list (i.e. the protection doesn't know whether it was needed or not)
class Action_defend_from_Famine(Action): # class for the NA "protect target from Famine"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
rolenames_dict["Famine"].actions["Kill"].add_exception(targets[0]) # add the target to the exception list of the "Famine Kill" NA
return "success" # add the target to the exception list of the "Famine Kill" NA
return False
### Aurelius
# NA1: - watches the chosen player, if that player is killed, learns the killer's nick (once every two nights) - NOT IMPLEMENTED
# NA2: - releases from all blocks - NOT IMPLEMENTED
### Madness
# NA1: - redirects the chosen player - NOT IMPLEMENTED
# NA2: - changes the chosen player's statuses for one night (I ASSUME THE STATUS IS CHANGED FOR THE CURRENT NIGHT)
class Action_change_statuses(Action): # class for the NA "change status"
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
return targets[0].set_statuses(params[0]) # change status
return False
# NA3: - kills the chosen player
class Action_kill(Action): # class for the NA "kill target", returns the killer's quenta
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
if targets[0].defender == None:
targets[0].change_alive("killed") # kill the target
targets[0].killer = self.owner # record the killer
return self.owner.quenta # quenta for the killer
print ("target is protected by Flavius")
return False
### Cold
# NA1: - restricts the daytime voting of the chosen player to three candidates chosen by the mafia and a "NL" option. The GM sends the list to the player. - NOT IMPLEMENTED
# NA2: - kills the chosen player, ignores Stigjor's heal - i.e. adds its target to its own exception list if the kill succeeded.
class Action_kill_through_heal(Action_kill): # class for the NA "kill target ignoring Stigjor's heal"
def do(self, targets, params):
if Action_kill.do(self, targets): # call superclass method
rolename_dict["Stigjor"].actions["Heal"].add_exception(targets[0]) # add the target to the exception list of the "Stigjor Heal" NA
return self.owner.quenta # quenta for the killer
return False
### Famine
# NA1: - blocks the chosen player - class Action_block(Action)
# NA2: - kills the chosen player, ignores Flavius's protection
class Action_kill_through_protection(Action): # class for the NA "kill target ignoring Flavius's protection", returns the killer's quenta
def do(self, targets, params):
if Action.do(self, targets): # call superclass method
targets[0].change_alive("killed") # kill the target
targets[0].killer = self.owner # record the killer
return self.owner.quenta # quenta for the killer
return False
### Death
# NA1: - kills the chosen player - see class Action_kill
# NA2: - kills the chosen player, leaves no clues (once every three nights, starting from the second)
class Action_kill_clean(Action_kill): # class for the NA "kill target without clues"
def do(self, targets, params):
if Action_kill.do(self, targets): # call superclass method
return "no clues" # no quenta for the killer
return False
####### NA ORDER (an NA's priority depends on the row number in the list)
# performer, NA name and display, as well as technical info - class to create the NA, and exceptions
priority_list = [
["Madness", "Change statuses", "fact", Action_change_statuses, ["Fritjof", "Stigjor"]],
["Hjaljot", "Block", "yes", Action_block],
["Famine", "Block", "yes", Action_block],
["Yoroar", "Compare", "no", Action_compare],
["Yoroar", "Message", "no", Action_message],
["Holkhak", "Check", "no", Action_check_no_one],
["Glariel", "Check", "no", Action_check_no_two],
["Flavius", "Protect", "yes", Action_defend],
["Ralfjor", "Protect from Famine", "yes", Action_defend_from_Famine],
["Cold", "Kill", "yes", Action_kill_through_heal, ["Flavius"]],
["Famine", "Kill", "yes", Action_kill_through_protection, ["Ralfjor"]],
["Madness", "Kill", "yes", Action_kill, ["Fritjof", "Stigjor"]],
["Death", "Kill", "yes", Action_kill],
["Death", "Kill without clues", "yes", Action_kill_clean],
["Flavius", "Heal from Cold", "yes", Action_heal_frozen],
["Stigjor", "Heal", "yes", Action_heal],
["Esben", "Put in fetters", "yes", Action_put_on_fetters],
["Esben", "Remove fetters", "yes", Action_take_off_fetters]
]
for i in range(1,len(priority_list)+1): # NA priority = row number in the list (1-based)
row = priority_list[i-1]
if len(row)<4:
print("error generating NA - not enough data in priority_list in row "+str(i))
else:
if len(row) == 4: # create a role (owner, actionname, priority, display):
rolenames_dict[row[0]].add_action(row[1], row[3](rolenames_dict[row[0]], row[1], i, row[2])) # create the NA, add it to the performer's NA list
else:
exception_list = []
for row4 in row[4]:
exception_list.append(rolenames_dict[row4])
rolenames_dict[row[0]].add_action(row[1], row[3](rolenames_dict[row[0]], row[1], i, row[2], exception_list)) # create the NA, add it to the performer's NA list
# actions of the current night
operations = [
["Ralfjor", "Protect from Famine", "Arandor9"],
["Esben", "Put in fetters", "Arandor13"],
["Flavius", "Protect", "Arandor9"],
["Madness", "Change statuses", "Arandor1", "cold / non-human"],
["Hjaljot", "Block", "Arandor10"],
["Madness", "Kill", "Arandor6"],
["Holkhak", "Check", "Arandor6"],
["Glariel", "Check", "Arandor1"]
]
print ("-----------------------------------------------")
print("night number "+str(night_no))
print ("-----------------------------------------------")
result=[] # parse the data from the operations array, convert it into a form convenient for sorting and processing: NA, [list of targets], [list of parameters]
for row in operations:
if row[1] in rolenames_dict[row[0]].actions:
q = 2 + rolenames_dict[row[0]].actions[row[1]].targets_qlty
targetname_list = row[2:q]
targets_list = []
for row2 in targetname_list:
targets_list.append(nicknames_dict[row2])
params = row[q:]
result.append([rolenames_dict[row[0]].actions[row[1]],targets_list,params])
else:
print("NA does not exist - "+row[1])
result.sort(key=lambda t:t[0].priority)
for row in result:
res = row[0].do(row[1],row[2])
print ("result ==> " + (res if res else "failure") + "; display in the newspaper --> " + (row[0].display if res else "no"))
print ("-----------------------------------------------")
current_alive_dict = {"alive":[], "in fetters":[], "killed":[], "executed":[]} # reset
current_vote_dict = {}
votes_in_game = 0
players_in_game = 0
for row in nicknames_dict:
current_alive_dict[nicknames_dict[row].alive].append(row)
if nicknames_dict[row].alive == "alive":
current_vote_dict[row]=nicknames_dict[row].vote
votes_in_game += nicknames_dict[row].vote
players_in_game += 1
if nicknames_dict[row].alive == "in fetters":
current_vote_dict[row]=nicknames_dict[row].vote
players_in_game += 1
print (current_alive_dict)
print ("-----------------------------------------------")
print ("in the game "+str(votes_in_game)+" votes and "+str(players_in_game)+ " players")
print ("player votes:")
print (current_vote_dict)
Знание некоторых закономерностей освобождает от изучения многих фактов.
Мечты сбываются рано или поздно, так или иначе.