Added player-specific log reporting
authorW. Trevor King <wking@drexel.edu>
Thu, 25 Mar 2010 23:30:21 +0000 (19:30 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 25 Mar 2010 23:30:21 +0000 (19:30 -0400)
risk/base.py [changed mode: 0755->0644]
risk/log.py [new file with mode: 0644]

old mode 100755 (executable)
new mode 100644 (file)
index dbff83b..8d8ef33
@@ -1,9 +1,9 @@
-#!/usr/bin/python
-#
-# A Python engine for Risk-like games
+"""A Python engine for Risk-like games
+"""
 
 import random
 
+from .log import Logger
 
 VERSION='0.1'
 
@@ -124,7 +124,7 @@ class World (list, ID_CmpMixin):
                 raise ValueError('%s shared by %s and %s'
                                  % (t.short_name, ts[t.short_name], t))
     def production(self, player):
-        ts = list(player.territories(world))
+        ts = list(player.territories(self))
         production = max(3, len(ts) / 3)
         continents = set([t.continent.name for t in ts])
         for c_name in continents:
@@ -240,6 +240,7 @@ class Player (ID_CmpMixin):
         ID_CmpMixin.__init__(self)
         self.alive = True
         self.hand = Hand()
+        self._message_index = 0
     def territories(self, world):
         for t in world.territories():
             if t.player == self:
@@ -250,6 +251,10 @@ class Player (ID_CmpMixin):
                 if neighbor.player != self:
                     yield t
                     break
+    def phase_report(self, world, log):
+        print 'Reporting for %s:\n  %s' \
+            % (self, '\n  '.join(log[self._message_index:]))
+        self._message_index = len(log)
     def phase_select_territory(self, world):
         """Return the selected territory
         """
@@ -291,10 +296,11 @@ class Player (ID_CmpMixin):
         self.hand.extend(cards)
 
 class Engine (ID_CmpMixin):
-    def __init__(self, world, players, deck_class=Deck):
+    def __init__(self, world, players, deck_class=Deck, logger_class=Logger):
         ID_CmpMixin.__init__(self)
         self.world = world
         self.deck = deck_class(world.territories())
+        self.log = logger_class()
         self.players = players
     def __str__(self):
         return '<engine %s %s>' % (self.world, self.players)
@@ -303,10 +309,13 @@ class Engine (ID_CmpMixin):
     def run(self):
         self.setup()
         self.play()
+        self.log('Game over.')
+        for p in self.players:
+            p.phase_report(self.world, self.log)
     def setup(self):
         for p in self.players:
             p.alive = True
-        random.shuffle(players)
+        random.shuffle(self.players)
         self.select_territories()
         self.place_initial_armies()
         self.deal()
@@ -320,9 +329,10 @@ class Engine (ID_CmpMixin):
             active_player = (active_player + 1) % living
             turn += 1
     def play_turn(self, player):
-        print "%s's turn (territory score: %s)" \
-            % (player, [(p,len(list(p.territories(self.world))))
-                        for p in self.players])
+        self.log("%s's turn (territory score: %s)"
+                 % (player, [(p,len(list(p.territories(self.world))))
+                             for p in self.players]))
+        player.phase_report(self.world, self.log)
         self.play_cards_and_place_armies(player)
         captures = self.attack_phase(player)
         if captures > 0 and len(self.deck) > 0 and len(self.living_players()) > 1:
@@ -332,11 +342,12 @@ class Engine (ID_CmpMixin):
             t.player = None
         for i in range(len(list(self.world.territories()))):
             p = self.players[i % len(self.players)]
+            p.phase_report(self.world, self.log)
             t = p.phase_select_territory(self.world)
             if t.player != None:
                 raise PlayerError('Cannot select %s owned by %s'
                                   % (t, t.player))
-            print '%s selects %s' % (p, t)
+            self.log('%s selects %s' % (p, t))
             t.player = p
             t.armies = 1
     def place_initial_armies(self):
@@ -347,10 +358,12 @@ class Engine (ID_CmpMixin):
             assert min(s) == max(s)-1, 'Min %d, max %d' % (min(s), max(s))
             for p,placed in zip(self.players, already_placed):
                 if placed == min(s):
+                    p.phase_report(self.world, self.log)
                     self.player_place_armies(p, remaining, 1)
         remaining = self.world.initial_armies[len(self.players)] - max(s)
         while remaining > 0:
             for p in self.players:
+                p.phase_report(self.world, self.log)
                 self.player_place_armies(p, remaining, 1)
             remaining -= 1
     def player_place_armies(self, player, remaining=1, this_round=1):
@@ -358,16 +371,16 @@ class Engine (ID_CmpMixin):
         if sum(placements.values()) != this_round:
             raise PlayerError('Placing more than %d armies' % this_round)
         for ter_name,armies in placements.items():
-            t = world.territory_by_name(ter_name)
+            t = self.world.territory_by_name(ter_name)
             if t.player != player:
                 raise PlayerError('Placing armies in %s owned by %s'
                                   % (t, t.player))
             if armies < 0:
                 raise PlayerError('Placing a negative number of armies (%d) in %s'
                                   % (armies, t))
-        print '%s places %s' % (player, placements)
+        self.log('%s places %s' % (player, placements))
         for ter_name,armies in placements.items():
-            t = world.territory_by_name(ter_name)
+            t = self.world.territory_by_name(ter_name)
             t.armies += armies
     def deal(self):
         for p in self.players:
@@ -375,7 +388,7 @@ class Engine (ID_CmpMixin):
             for i in range(3):
                 cards.append(self.deck.pop())
             p.phase_draw(cards)
-        print 'Initial hands dealt'
+        self.log('Initial hands dealt')
     def play_cards_and_place_armies(self, player, additional_armies=0):
         cards_required = len(player.hand) >= 5
         cards = player.phase_play_cards(
@@ -384,11 +397,11 @@ class Engine (ID_CmpMixin):
             raise PlayerError('You have %d >= 5 cards in your hand, you must play'
                               % len(player.hand))
         w_prod,w_terr_prod = self.world.production(player)
-        print '%s earned %d armies from territories' % (player, w_prod)
+        self.log('%s earned %d armies from territories' % (player, w_prod))
         c_prod,c_terr_prod = self.deck.production(player, cards)
         if c_prod > 0:
-            print '%s played %s, earning %d armies' \
-                % (player, cards, c_prod+sum(c_terr_prod.values()))
+            self.log('%s played %s, earning %d armies'
+                     % (player, cards, c_prod+sum(c_terr_prod.values())))
         if cards != None:
             for c in cards:
                 player.hand.remove(c)
@@ -399,7 +412,7 @@ class Engine (ID_CmpMixin):
                 w_terr_prod[terr] = prod
         self.world.place_territory_production(w_terr_prod)
         if len(w_terr_prod) > 0:
-            print '%s was required to place %s' % (player, w_terr_prod)
+            self.log('%s was required to place %s' % (player, w_terr_prod))
         armies = w_prod + c_prod
         self.player_place_armies(player, armies, armies)
     def attack_phase(self, player):
@@ -443,14 +456,14 @@ class Engine (ID_CmpMixin):
         target.armies -= t_dead
         if target.armies == 0:
             self.takeover(source, target, remaining_attackers=armies-a_dead)
-            print '%s conquered %s from %s with %d:%d.  Deaths %d:%d.  Remaining %d:%d' \
-                % (source.player, target, source, armies, len(t_dice),
-                   a_dead, t_dead, source.armies, target.armies)
+            self.log('%s conquered %s from %s with %d:%d.  Deaths %d:%d.  Remaining %d:%d'
+                     % (source.player, target, source, armies, len(t_dice),
+                        a_dead, t_dead, source.armies, target.armies))
             assert target.armies > 0, target
             return True
-        print '%s attacked %s from %s with %d:%d.  Deaths %d:%d.  Remaining %d:%d' \
-            % (source.player, target, source, armies, len(t_dice),
-               a_dead, t_dead, source.armies, target.armies)
+        self.log('%s attacked %s from %s with %d:%d.  Deaths %d:%d.  Remaining %d:%d' \
+                     % (source.player, target, source, armies, len(t_dice),
+                        a_dead, t_dead, source.armies, target.armies))
         assert target.armies > 0, target
         return False
     def takeover(self, source, target, remaining_attackers):
@@ -469,7 +482,10 @@ class Engine (ID_CmpMixin):
         if len(self.living_players()) > 1:
             while len(killer.hand) > 5:
                 self.play_cards_and_place_armies(killer)
-        print '%s killed by %s' % (player, killer)
+        self.log('%s killed by %s' % (player, killer))
+        if len(self.living_players()) > 1:
+            player.phase_report(self.world, self.log)
+            # else the game is over, and killed will hear about this then.
     def living_players(self):
         return [p for p in self.players if p.alive == True]
 
@@ -540,15 +556,20 @@ def generate_earth():
     w._resolve_link_names()
     return w
 
-if __name__ == '__main__':
+def test():
     import doctest
-    import sys
-
     failures,tests = doctest.testmod()
-    if failures > 0:
-        sys.exit(1)
+    return failures
 
+def random_game():
     world = generate_earth()
-    players = [Player('A'), Player('B')]
+    players = [Player('Alice'), Player('Bob'), Player('Charlie')]
     e = Engine(world, players)
     e.run()
+
+if __name__ == '__main__':
+    import sys
+    failures = self.test()
+    if failures > 0:
+        sys.exit(1)
+    self.random_game()
diff --git a/risk/log.py b/risk/log.py
new file mode 100644 (file)
index 0000000..24a8e8e
--- /dev/null
@@ -0,0 +1,6 @@
+"""Define Logger class for saving and formatting game activity.
+"""
+
+class Logger (list):
+    def __call__(self, message):
+        self.append(message)