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()