from player import * from misc import * import os import commands from random import randint #import game_data # cannot 'import from' because of mutual recursion # Folders of games of various states # IMPORTANT: This is indexed by the game state enumeration below! GAME_STATUS_DIRS = ['open','init','cont','done'] GAME_STATUS_NAMES = dict([('open','Open Games'), \ ('init','Initialized Games'), \ ('cont','Ongoing Games'), \ ('done','Concluded Games')]) class Game: # Primitive state enumeration OPEN = 0 INIT = 1 CONT = 2 DONE = 3 def __init__(self, title = "", req_players = 8, initial_comment = "", \ in_own_game = True, quorum_percent = 50): self.title = title self.req_players = int(req_players) self.day = True # determines day/night status while game.state = CONT self.in_own_game = in_own_game # If false, you do not have a # player in your own game # Percent of votes that must be the same before the day ends self.quorum_percent = quorum_percent # Setup the comments self.comments = [] if initial_comment != "": self.comments.append(initial_comment) # Dict: job (all except townie) -> number in game self.job_qty = {} # All the players in the game self.players = [] self.set_open() # sets self.state def __repr__(self): return 'Game %s' % self.title # -- Action functions - change the game based on some happenings -- # Execute a daytime or nightime activity cycle # (only called after a game is initialized) def play(self): if not self.is_cont(): raise ValueError, 'Cannot "play" a game (%s) with status %s' % \ (self.title, self.status()) # Email last people to play players_not_chosen = self.players_not_chosen() if len(players_not_chosen) <= NUM_OF_LAST_PLAYERS: self.email_reminder(players_not_chosen, True) # If the day/night is over, then we continue onto the # calculations. Otherwise we simply return because there is nothing # else to do as the round isn't over yet. if not self.day_night_is_over(): return # If the night/day is not over, then calculate end of phase (dawn/dusk) # Record-keeping # Have each player 1) make a new record # 2) record their peon status for player in self.players: player.record.new_record(self.day) player.record_peon_statuses() # day/night is over, do calculations if self.day: self.dusk() # day->night effects else: self.dawn() # night->day effects # Check if game is over # (if there is > 1 winning player(s), game is over) # (See player.py for specific winning conditions) # MUST be done after dawn/dusk is calculated if self.get_winning_players() != []: self.set_done() # Send email notification of new phase self.email_reminder(self.players) # If the game isn't over, have AIs take their turns if self.is_cont(): self.take_ai_turns() # possible mutual recursion with play() # Check to see if the day/night is over. # # night - all players must have a choice # day - night PLUS there one particular choice must have 50% majority def day_night_is_over(self): majority = general_vote(self.get_player_choices(), self.quorum_percent) return self.all_players_chosen() and \ ( not(self.day) or majority != None or self.quorum_percent == 0) # day->night transition calculations # # Figure out who was hanged in each player's world def dusk(self): player_choices = self.get_player_choices() # Let each player calculate their own hanging vote for player in self.players: player.calc_vote(player_choices) self.swap_day_status() # night->day transition calculations # # Each player points at another player. These choices are talleyed # and given to each player to determine what happens. def dawn(self): player_choices = self.get_player_choices() for player in self.players: player.calc_pointing(player_choices) self.swap_day_status() # Make dict: voting_player_name -> player_name they voted for # Helper function for dawn/dusk # # Note: * dead should be discluded from day voting # * undead day votes are counted separately # * dead or undead pointing during th night should have no effect # Therefore, we only account for alive people choices def get_player_choices(self): player_choices = {} for player in self.players: player_choices[player.name] = player.choice return player_choices def swap_day_status(self): self.day = not self.day # Reset players choices for new phase for player in self.players: player.reset_chosen() def take_ai_turns(self): for player in self.players: if player.is_ai(): player.ai.take_turn() # See if all the AIs taking their turns does something # If not, play() will just return, no harm done self.play() def configure_ai(self): for player in self.players: if player.is_ai(): player.ai.configure() # If all players are AIs, continue on self.configure_check() # Check to see if everyone is configured, if so, start the game def configure_check(self): # Email last people to configure players_not_configured = self.players_not_configured() if len(players_not_configured) <= NUM_OF_LAST_PLAYERS: self.email_reminder(players_not_configured, True) # If all the players are initialized, start the game if self.all_players_configured(): self.set_cont() # -- Information function - get information from the game -- def has_req_players(self): return len(self.players) >= self.req_players def all_players_configured(self): return len(self.players_not_configured()) <= 0 def players_not_configured(self): return [player for player in self.players \ if not player.is_configured()] def all_players_chosen(self): return len(self.players_not_chosen()) <= 0 def players_not_chosen(self): return [player for player in self.players \ if not player.has_chosen()] def get_player(self, player_name, usr_msg = \ DEFAULT_INTERNAL_ERROR_MESSAGE): for player in self.players: if player.name == player_name: return player my_error('attempting to find player', \ 'Player %s does not exist in game %s' % \ (player_name, self.title), usr_msg) def get_rand_player_name(self): return rand_elem(self.players).name; # Returns a list of players that have currently won (see player.py for more info) # Usually, this list will be empty def get_winning_players(self): return [player for player in self.players if player.has_won()] # True if mafia is less than (or equal to) half the total number of players def mafia_is_light(self): return (self.job_qty[MAFIA] <= self.req_players/2) # Get state def is_open(self): return self.state == Game.OPEN def is_init(self): return self.state == Game.INIT def is_cont(self): return self.state == Game.CONT def is_done(self): return self.state == Game.DONE # Set state def set_open(self): self.state = Game.OPEN self.record_state() def set_init(self): self.state = Game.INIT self.record_state() self.email_reminder(self.players) self.configure_ai() def set_cont(self): self.state = Game.CONT self.record_state() self.email_reminder(self.players) self.take_ai_turns() # Have AIs take their first turn # Subsequent turns are handled by play() def set_done(self): self.state = Game.DONE self.record_state() # email handled in play() #game_data.update_game_data() # add this game's into the repository # Records game state by making a file in the directory # corresponding to the game's status. This is so index.cgi doesn't # take forever to load. def record_state(self): # Remove any previous record for folder in GAME_STATUS_DIRS: os.system('rm -f "%s/%s/%s"' % (GAME_DIR, folder, self.title)) # Make record in correct folder os.system('touch "%s/%s/%s"' \ % (GAME_DIR, GAME_STATUS_DIRS[self.state], self.title)) def status(self): if self.is_open(): return 'Open' if self.is_init(): return 'Initializing' if self.is_cont(): return 'Ongoing (%s)' % self.day_status() if self.is_done(): return 'Concluded' return 'Invalid Status!' def day_status(self): if self.day: return 'Daytime' else: return 'Night' # -- Email Notification Functions -- # 'players' are the players we want to notify # 'last_one' sends a particularly scathing email reminding them # to take their turn now def email_reminder(self, players, last_one = False): # Semi-definable variables email_filename = 'email.tmp' return_addr = 'No-Reply@cs.hmc.edu' return_name = 'Quantum Mafia Mangement' subject = 'Quantum Mafia Reminder' if last_one: subject += ': You are the last to move!' # Begin generating email body email = 'The game "%s" ' % self.title # Different messages for each game state if self.is_open(): raise ValueError, \ 'Tried to email a reminder when the game is still open' if self.is_init(): if last_one: email += 'has been initialized and you are the LAST ONE ' + \ 'to configure your player. Please configure ' + \ 'your player as soon as you can.' else: email += 'has just been initialized. Please configure ' + \ 'your player when you have time.' if self.is_cont(): if self.day: email += 'has started the day phase. Please, exercise ' + \ 'your right to vote for the death of one of your own.' else: email += 'has started the night phase. Please, go and ' + \ 'murder in the night, raise the dead as your ' + \ 'unwilling political supporters, save unwitting ' + \ 'victims, battle the forces of the undead, or ' + \ 'just laze about, as befits your station in life' if last_one: email += '\n\nYou are the LAST ONE to submit your ' + \ ('%s Action. Please take ' % self.day_status()) + \ 'your turn as soon as possible so you don\'t ' + \ 'hold up the game.' if self.is_done(): email += 'is over! Check out how you did.' # Link to the game (with correct URL spaces) email += '\n\nA link directly to the game:' + \ '\n\t%sviewGame.cgi?' % HTML_BASE + \ urlencode({'game_name': self.title}); # Sig email += '\n\nQuantum Mafia Management' try: f = open(email_filename, 'w') f.write(email) except: my_error('sending an email', \ 'Error writing to "%s"' % email_filename) f.close() # Actually send the emails for player in players: if player.email_addr != "": e = commands.getstatusoutput( \ "mailx -s '%s' '%s' -- -f '%s' -F '%s' < '%s'" % \ (subject, player.email_addr, return_addr, \ return_name, email_filename) ) #print "Exit status %s
\nOutput: %s
" % e # -- Independent functions for saving and loading games -- def load_game(game_name, silent_error = False): try: f = open('%s/%s' % (GAME_DIR, game_name), 'r') game = pickle.load(f) # FIX #if not isinstance(game, Game): # raise ValueError # We better be loading an actual game except: if silent_error: return None usr_msg = """ Would you like to make a new game by that name? """ % HTML_BASE my_error('getting the game', \ 'Could not find/load game "%s"' % game_name, usr_msg) f.close() return game def save_game(game): try: f = open('%s/%s' % (GAME_DIR, game.title), 'w') pickle.dump(game, f) except: my_error('saving the game', \ 'Could not save game "%s"' % game_name) f.close()