From: W. Trevor King Date: Fri, 26 Mar 2010 05:46:54 +0000 (-0400) Subject: Added risk.player.email with EmailPlayer. X-Git-Tag: 0.1~34 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=2e1f7ef28a6c72c0545201030eef9b190f2f6174;p=pyrisk.git Added risk.player.email with EmailPlayer. Currently only a text-mode interface (no actual email), but it supports all the necessary methods. --- diff --git a/risk/base.py b/risk/base.py index 6f62c78..d54b52f 100644 --- a/risk/base.py +++ b/risk/base.py @@ -419,7 +419,7 @@ class Player (NameMixin, ID_CmpMixin): """ pass def select_territory(self, world, log): - """Return the selected territory + """Return the selected territory's name. """ free_territories = [t for t in world.territories() if t.player == None] return random.sample(free_territories, 1)[0].name @@ -672,12 +672,15 @@ class Engine (ID_CmpMixin): source.armies -= remaining_attackers target.armies += remaining_attackers target.player = source.player - support = source.player.support_attack(self.world, self.log, source, target) - if support < 0 or support >= source.armies: - raise PlayerError('Cannot support from %s to %s with %d armies, only %d available' - % (source, target, support, source.armies-1)) - source.armies -= support - target.armies += support + if source.armies > 1: + support = source.player.support_attack( + self.world, self.log, source, target) + if support < 0 or support >= source.armies: + raise PlayerError( + 'Cannot support from %s to %s with %d armies, only %d available' + % (source, target, support, source.armies-1)) + source.armies -= support + target.armies += support def player_killed(self, player, killer): player.alive = False killer.hand.extend(player.hand) @@ -769,8 +772,10 @@ def test(): return failures def random_game(): + from player.email import EmailPlayer world = generate_earth() - players = [Player('Alice'), Player('Bob'), Player('Charlie')] + players = [EmailPlayer('Alice', 'alice@example.com', 'server@example.com'), + Player('Bob'), Player('Charlie')] e = Engine(world, players) e.run() diff --git a/risk/player/__init__.py b/risk/player/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/risk/player/email.py b/risk/player/email.py new file mode 100644 index 0000000..369f547 --- /dev/null +++ b/risk/player/email.py @@ -0,0 +1,210 @@ +"""An email interface for players. +""" + +from ..base import Player + +class EmailPlayer (Player): + """Human Player with an email interface. + + TODO: details on procmail setup + """ + def __init__(self, name, address, return_address): + Player.__init__(self, name) + self.address = address + self.return_address = return_address + def _send_email(self, world, log, subject, body): + wpart = self._world_part(world) + lpart = self._world_part(log) + print 'Subject: %s\n\n%s\n' % (subject, body) + def _get_email(self): + body = raw_input('response? ') + body = body.replace(r'\n', '\n') + if len(body) == 0 or body[-1] != '\n': + body += '\n' + return body + def _world_part(self, world): + pass + def _log_part(self, log): + pass + def report(self, world, log): + """Send reports about death and game endings. + + These events mark the end of contact and require no change in + player status or response, so they get a special command + seperate from the usual action family. The action commands in + Player subclasses can notify the player (possibly by calling + report internally) if they feel so inclined. + + See also + -------- + draw - another notification-only method + """ + self._send_email(world, log, 'Report', Player.report()) + def draw(self, world, log, cards=[]): + """Only called if you earned a new card (or cards). + + See also + -------- + report - another notification-only method + """ + Player.draw(self, world, log, cards) + body = ['New cards:'] + body.extend([' %s' % c for c in cards]) + body = ['Current Hand:'] + for c in self.hand: + if c.territory != None and c.territory.player == self: + body.append(' %s (owned)' % c) + else: + body.append(' %s' % c) + self._send_email(world, log, 'Drawing cards', '\n'.join(body)) + def select_territory(self, world, log): + """Return the selected territory's name. + """ + body = [ + 'Reply with first line of the body of your email set', + 'to the name (long or short, case insenitive) of the', + 'territory you wish to occupy. Available territories', + 'are:'] + for t in world.territories(): + if t.player == None: + body.append(' %s' % t) + self._send_email(world, log, 'Select territory', '\n'.join(body)) + body = self._get_email() + name = body.splitlines()[0].strip() + return name + def play_cards(self, world, log, play_required=True): + """Decide whether or not to turn in a set of cards. + + Return a list of cards to turn in or None. If play_required + is True, you *must* play. + """ + possibles = list(self.hand.possible()) + if len(possibles) == 0: + return None + subject = 'Play cards' + if play_required == True: + subject += ' (required)' + body = [ + 'Reply with first line of the body of your email set', + 'to the number of the set you wish to play (leave body', + 'blank to pass). Available sets are:'] + for i,h in enumerate(possibles): + body.append(' %d: %s' % (i, h)) + self._send_email(world, log, subject, '\n'.join(body)) + body = self._get_email() + text = body.splitlines()[0].strip() + if text == '': + return None + return possibles[int(text)] + def place_armies(self, world, log, remaining=1, this_round=1): + """Both during setup and before each turn. + + Return {territory_name: num_armies, ...} + """ + subject = 'Place %d of %d armies' % (this_round, remaining) + body = [ + 'You can place %d armies this round (out of %d in' + % (this_round, remaining), + 'this phase).', + '', + 'Reply with first line(s) of the body of your email set', + 'to " : " followed by', + 'a blank line. For example', + ' 1 : gbr', + ' 4 : indo', + ' ...', + '' + 'Your current disposition is:'] + for t in self.territories(world): + body.append(' %d : %s' % (t.armies, t)) + self._send_email(world, log, subject, '\n'.join(body)) + body = self._get_email() + placements = {} + for line in body.splitlines(): + line = line.strip() + if len(line) == 0: + break + armies,terr_name = [x.strip() for x in line.split(':')] + placements[terr_name] = int(armies) + return placements + def attack_and_fortify(self, world, log, mode='attack'): + """Return list of (source, target, armies) tuples. Place None + in the list to end this phase. + """ + if mode == 'attack': + subject = 'Attack and fortify' + body = [ + 'You can attack as many times as you like, and fortify', + 'once at the end of the round. Reply with first line(s)', + 'of the body of your email set to', + ' " : : ', + 'When you are done attacking or in place of a', + 'fortification, insert the line "Pass". For example', + ' gbr : ice : 3', + ' jap : chi : 3', + ' jap : chi : 3', + ' Pass', + ' gbr : seu : 7', + ' ', + 'or', + ' jap : chi : 3', + ' Pass', + ' Pass', + ' '] + else: + assert mode == 'fortify', mode + subject = 'Fortify' + body = [ + 'You can fortify once. Reply with first line of the', + 'body of your email set to', + ' " : : ', + 'Or, if you choose to pass, either a blank line or', + '"Pass". For example', + ' gbr : seu : 7', + 'or', + ' ', + 'or', + ' Pass'] + self._send_email(world, log, subject, '\n'.join(body)) + body = self._get_email() + if mode == 'fortify': + return [self._parse_attack_or_fortify_line( + body.splitlines()[0], mode)] + actions = [] + pass_count = 0 + for line in body.splitlines(): + action = self._parse_attack_or_fortify_line(line, mode) + if action == None: + pass_count += 1 + if pass_count == 2: + break + actions.append(action) + return actions + def _parse_attack_or_fortify_line(self, line, mode): + line = line.strip() + if line.count(':') == 2: + fields = [x.strip() for x in line.split(':')] + fields[2] = int(fields[2]) + return fields + elif line.lower() == 'pass' \ + or (mode == 'fortify' and len(line) == 0): + return None + def support_attack(self, world, log, source, target): + """Follow up on a conquest by moving additional armies. + """ + subject = 'Support conquest of %s by %s' % (target, source) + body = [ + 'You can move up to %d of the %d armies remaining on' + % (source.armies - 1, source.armies), + '%s to %s following your conquest.' + % (source, target), + '', + 'Reply with first line(s) of the body of your email set', + 'to "", or leave the first line blank', + 'to pass.'] + self._send_email(world, log, subject, '\n'.join(body)) + body = self._get_email() + text = body.splitlines()[0].strip() + if text == '': + return 0 + return int(text)