369f5478cbf8e54d8463a955ffb1dafe18a6cbd2
[pyrisk.git] / risk / player / email.py
1 """An email interface for players.
2 """
3
4 from ..base import Player
5
6 class EmailPlayer (Player):
7     """Human Player with an email interface.
8
9     TODO: details on procmail setup
10     """
11     def __init__(self, name, address, return_address):
12         Player.__init__(self, name)
13         self.address = address
14         self.return_address = return_address
15     def _send_email(self, world, log, subject, body):
16         wpart = self._world_part(world)
17         lpart = self._world_part(log)
18         print 'Subject: %s\n\n%s\n' % (subject, body)
19     def _get_email(self):
20         body = raw_input('response? ')
21         body = body.replace(r'\n', '\n')
22         if len(body) == 0 or body[-1] != '\n':
23             body += '\n'
24         return body
25     def _world_part(self, world):
26         pass
27     def _log_part(self, log):
28         pass
29     def report(self, world, log):
30         """Send reports about death and game endings.
31
32         These events mark the end of contact and require no change in
33         player status or response, so they get a special command
34         seperate from the usual action family.  The action commands in
35         Player subclasses can notify the player (possibly by calling
36         report internally) if they feel so inclined.
37         
38         See also
39         --------
40         draw - another notification-only method
41         """
42         self._send_email(world, log, 'Report', Player.report())
43     def draw(self, world, log, cards=[]):
44         """Only called if you earned a new card (or cards).
45
46         See also
47         --------
48         report - another notification-only method
49         """
50         Player.draw(self, world, log, cards)
51         body = ['New cards:']
52         body.extend(['  %s' % c for c in cards])
53         body = ['Current Hand:']
54         for c in self.hand:
55             if c.territory != None and c.territory.player == self:
56                 body.append('  %s (owned)' % c)
57             else:
58                 body.append('  %s' % c)
59         self._send_email(world, log, 'Drawing cards', '\n'.join(body))
60     def select_territory(self, world, log):
61         """Return the selected territory's name.
62         """
63         body = [
64             'Reply with first line of the body of your email set',
65             'to the name (long or short, case insenitive) of the',
66             'territory you wish to occupy.  Available territories',
67             'are:']
68         for t in world.territories():
69             if t.player == None:
70                 body.append('  %s' % t)
71         self._send_email(world, log, 'Select territory', '\n'.join(body))
72         body = self._get_email()
73         name = body.splitlines()[0].strip()
74         return name
75     def play_cards(self, world, log, play_required=True):
76         """Decide whether or not to turn in a set of cards.
77
78         Return a list of cards to turn in or None.  If play_required
79         is True, you *must* play.
80         """
81         possibles = list(self.hand.possible())
82         if len(possibles) == 0:
83             return None
84         subject = 'Play cards'
85         if play_required == True:
86             subject += ' (required)'
87         body = [
88             'Reply with first line of the body of your email set',
89             'to the number of the set you wish to play (leave body',
90             'blank to pass).  Available sets are:']
91         for i,h in enumerate(possibles):
92             body.append('  %d: %s' % (i, h))
93         self._send_email(world, log, subject, '\n'.join(body))
94         body = self._get_email()
95         text = body.splitlines()[0].strip()
96         if text == '':
97             return None
98         return possibles[int(text)]
99     def place_armies(self, world, log, remaining=1, this_round=1):
100         """Both during setup and before each turn.
101
102         Return {territory_name: num_armies, ...}
103         """
104         subject = 'Place %d of %d armies' % (this_round, remaining)
105         body = [
106             'You can place %d armies this round (out of %d in'
107             % (this_round, remaining),
108             'this phase).',
109             '',
110             'Reply with first line(s) of the body of your email set',
111             'to "<number_of_armies> : <territory_name>" followed by',
112             'a blank line.  For example',
113             '  1 : gbr',
114             '  4 : indo',
115             '  ...',
116             ''
117             'Your current disposition is:']
118         for t in self.territories(world):
119             body.append('  %d : %s' % (t.armies, t))
120         self._send_email(world, log, subject, '\n'.join(body))
121         body = self._get_email()
122         placements = {}
123         for line in body.splitlines():
124             line = line.strip()
125             if len(line) == 0:
126                 break
127             armies,terr_name = [x.strip() for x in line.split(':')]
128             placements[terr_name] = int(armies)
129         return placements
130     def attack_and_fortify(self, world, log, mode='attack'):
131         """Return list of (source, target, armies) tuples.  Place None
132         in the list to end this phase.
133         """
134         if mode == 'attack':
135             subject = 'Attack and fortify'
136             body = [
137                 'You can attack as many times as you like, and fortify',
138                 'once at the end of the round.  Reply with first line(s)',
139                 'of the body of your email set to',
140                 '  "<source_name> : <target_name> : <number_of_armies>',
141                 'When you are done attacking or in place of a',
142                 'fortification, insert the line "Pass".  For example',
143                 '  gbr : ice : 3',
144                 '  jap : chi : 3',
145                 '  jap : chi : 3',
146                 '  Pass',
147                 '  gbr : seu : 7',
148                 '  ',
149                 'or',
150                 '  jap : chi : 3',
151                 '  Pass',
152                 '  Pass',
153                 '  ']
154         else:
155             assert mode == 'fortify', mode
156             subject = 'Fortify'
157             body = [
158                 'You can fortify once.  Reply with first line of the',
159                 'body of your email set to',
160                 '  "<source_name> : <target_name> : <number_of_armies>',
161                 'Or, if you choose to pass, either a blank line or',
162                 '"Pass".  For example',
163                 '  gbr : seu : 7',
164                 'or',
165                 '  ',
166                 'or',
167                 '  Pass']
168         self._send_email(world, log, subject, '\n'.join(body))
169         body = self._get_email()
170         if mode == 'fortify':
171             return [self._parse_attack_or_fortify_line(
172                         body.splitlines()[0], mode)]
173         actions = []
174         pass_count = 0
175         for line in body.splitlines():
176             action = self._parse_attack_or_fortify_line(line, mode)
177             if action == None:
178                 pass_count += 1
179                 if pass_count == 2:
180                     break
181             actions.append(action)
182         return actions
183     def _parse_attack_or_fortify_line(self, line, mode):
184         line = line.strip()
185         if line.count(':') == 2:
186             fields = [x.strip() for x in line.split(':')]
187             fields[2] = int(fields[2])
188             return fields
189         elif line.lower() == 'pass' \
190                 or (mode == 'fortify' and len(line) == 0):
191             return None
192     def support_attack(self, world, log, source, target):
193         """Follow up on a conquest by moving additional armies.
194         """
195         subject = 'Support conquest of %s by %s' % (target, source)
196         body = [
197             'You can move up to %d of the %d armies remaining on'
198             % (source.armies - 1, source.armies),
199             '%s to %s following your conquest.'
200             % (source, target),
201             '',
202             'Reply with first line(s) of the body of your email set',
203             'to "<number_of_armies>", or leave the first line blank',
204             'to pass.']
205         self._send_email(world, log, subject, '\n'.join(body))
206         body = self._get_email()
207         text = body.splitlines()[0].strip()
208         if text == '':
209             return 0
210         return int(text)