Added Event classes to pyrisk.log
authorW. Trevor King <wking@drexel.edu>
Sat, 27 Mar 2010 01:30:50 +0000 (21:30 -0400)
committerW. Trevor King <wking@drexel.edu>
Sat, 27 Mar 2010 01:30:50 +0000 (21:30 -0400)
This should make logging easier to configure.  For example, a
Player.report function now has access to the raw data used to compose
the string.  Well, if the mutables haven't changed ;).  If that's an
issue for you, stick some copy.deepcopy calls into the Event subclass
__init__ definitions.

pyrisk/base.py
pyrisk/log.py

index 1cdd30f..38d3b23 100644 (file)
@@ -19,7 +19,8 @@
 
 import random
 
-from .log import Logger
+from .log import Logger, BeginGame, EndGame, Killed, StartTurn, DealtCards, \
+    EarnsArmies, SelectTerritory, PlaceArmies, PlayCards, Attack, Conquer
 
 
 class PlayerError (Exception):
@@ -422,7 +423,7 @@ class Player (NameMixin, ID_CmpMixin):
         draw - another notification-only method
         """
         print 'Reporting for %s:\n  %s' \
-            % (self, '\n  '.join(log[self._message_index:]))
+            % (self, '\n  '.join([str(e) for e in log[self._message_index:]]))
         self._message_index = len(log)
     def draw(self, world, log, cards=[]):
         """Only called if you earned a new card (or cards).
@@ -510,7 +511,7 @@ class Engine (ID_CmpMixin):
         for p in self.players:
             p.alive = True
         random.shuffle(self.players)
-        self.log('Game started with %s' % self.players)
+        self.log(BeginGame(self.players))
         self.deck.shuffle()
         self.select_territories()
         self.place_initial_armies()
@@ -535,15 +536,13 @@ class Engine (ID_CmpMixin):
 
         Currently just a notification hook.
         """
-        self.log('Game over.')
+        self.log(EndGame(self.players))
         for p in self.players:
             p.report(self.world, self.log)
     def play_turn(self, player):
         """Work through the phases of player's turn.
         """
-        self.log("%s's turn (territory score: %s)"
-                 % (player, [(p,len(list(p.territories(self.world))))
-                             for p in self.players]))
+        self.log(StartTurn(player, self.players, self.world))
         self.play_cards_and_place_armies(player)
         captures = self.attack_and_fortify(player)
         self.end_of_turn_cards(player, captures)
@@ -567,7 +566,7 @@ class Engine (ID_CmpMixin):
                     break
                 except PlayerError, error:
                     continue
-            self.log('%s selects %s' % (p, t))
+            self.log(SelectTerritory(p, t))
             t.player = p
             t.armies = 1
         # last player has no choice.
@@ -611,7 +610,7 @@ class Engine (ID_CmpMixin):
                 break
             except PlayerError, error:
                 continue
-        self.log('%s places %s' % (player, placements))
+        self.log(PlaceArmies(player, placements))
         for terr_name,armies in placements.items():
             t = self.world.territory_by_name(terr_name)
             t.armies += armies
@@ -621,7 +620,7 @@ class Engine (ID_CmpMixin):
             cards.append(self.deck.pop())
         player.hand.extend(cards)
         player.draw(self.world, self.log, cards)
-        self.log('%s dealt %d cards' % (player, number))
+        self.log(DealtCards(player, number, len(self.deck)))
     def play_cards_and_place_armies(self, player, additional_armies=0):
         cards_required = len(player.hand) >= 5
         error = None
@@ -637,21 +636,17 @@ class Engine (ID_CmpMixin):
             except PlayerError, error:
                 continue
         w_prod,w_terr_prod = self.world.production(player)
-        self.log('%s earned %d armies from territories' % (player, w_prod))
-        if c_prod > 0:
-            self.log('%s played %s, earning %d armies'
-                     % (player, cards, c_prod+sum(c_terr_prod.values())))
+        self.log(EarnsArmies(player, w_prod, w_terr_prod))
         if cards != None:
             for c in cards:
                 player.hand.remove(c)
+            self.log(PlayCards(player, cards, c_prod, c_terr_prod))
         for terr,prod in c_terr_prod.items():
             if terr in w_terr_prod:
                 w_terr_prod[terr] += prod
             else:
                 w_terr_prod[terr] = prod
         self.world.place_territory_production(w_terr_prod)
-        if len(w_terr_prod) > 0:
-            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_and_fortify(self, player):
@@ -703,29 +698,25 @@ class Engine (ID_CmpMixin):
         if armies >= source.armies:
             raise PlayerError('%s attacking %s with %d armies, but only %d are available.'
                               % (source, target, armies, source.armies-1))
-        a_dice = sorted([random.randint(1, 6) for i in range(armies)],
+        s_dice = sorted([random.randint(1, 6) for i in range(armies)],
                         reverse=True)
         t_dice = sorted([random.randint(1, 6) for i in range(min(2, target.armies))],
                         reverse=True)
-        a_dead = 0
+        s_dead = 0
         t_dead = 0
-        for a,d in zip(a_dice, t_dice):
+        for a,d in zip(s_dice, t_dice):
             if d >= a:
-                a_dead += 1
+                s_dead += 1
             else:
                 t_dead += 1
-        source.armies -= a_dead
+        source.armies -= s_dead
         target.armies -= t_dead
         if target.armies == 0:
-            self.takeover(source, target, remaining_attackers=armies-a_dead)
-            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))
+            self.takeover(source, target, remaining_attackers=armies-s_dead)
+            self.log(Conquer(source, target, s_dice, t_dice, s_dead, t_dead))
             assert target.armies > 0, target
             return True
-        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))
+        self.log(Attack(source, target, s_dice, t_dice, s_dead, t_dead))
         assert target.armies > 0, target
         return False
     def takeover(self, source, target, remaining_attackers):
@@ -753,7 +744,7 @@ class Engine (ID_CmpMixin):
         if len(self.living_players()) > 1:
             while len(killer.hand) > 5:
                 self.play_cards_and_place_armies(killer)
-        self.log('%s killed by %s' % (player, killer))
+        self.log(Killed(player, killer))
         if len(self.living_players()) > 1:
             player.report(self.world, self.log)
             # else the game is over, and killed will hear about this then.
index e795374..b629616 100644 (file)
 """Define Logger class for saving and formatting game activity.
 """
 
+class Event (object):
+    """Base class for logged actions.
+    """
+    def __init__(self, message):
+        self.message = message
+    def __str__(self):
+        return self.message
+    def __repr__(self):
+        return self.__str__()
+
+class Announcement (Event):
+    """A statement about game status.
+    """
+
+class BeginGame (Announcement):
+    def __init__(self, players=[]):
+        # perhaps copy the list of players?
+        self.players = players
+        Announcement.__init__(self, 'Game started with %s' % self.players)
+
+class EndGame (Announcement):
+    def __init__(self, players=[]):
+        self.players = players
+        Announcement.__init__(self, 'Game over')
+        # perhaps add turns-survived ranking
+
+class Killed (Announcement):
+    def __init__(self, player, killer):
+        self.player = player
+        self.killer = killer
+        Announcement.__init__(
+            self, '%s killed by %s' % (self.player, self.killer))
+
+
+class StartTurn (Announcement):
+    def __init__(self, player, players=[], world=None):
+        self.player = player
+        self.players = players
+        self.world = world
+        Announcement.__init__(
+            self,
+            "%s's turn (territory score: %s)"
+            % (self.player,
+               [(p,len(list(p.territories(self.world))))
+                for p in self.players]))
+
+class DealtCards (Announcement):
+    """A player recieves cards from the deck.
+    """
+    def __init__(self, player, num_cards, num_remaining):
+        self.player = player
+        self.num_cards = num_cards
+        self.num_remaining = num_remaining
+        Announcement.__init__(
+            self,
+            '%s dealt %d cards (%d remaining)'
+            % (self.player, self.num_cards, self.num_remaining))
+
+class EarnsArmies (Announcement):
+    """A player recieves armies.
+    """
+    def __init__(self, player, prod=0, terr_prod={}, source='territories'):
+        self.player = player
+        self.prod = prod
+        self.terr_prod = terr_prod
+        self.source = source
+        if len(terr_prod) == 0:
+            msg = 'earns %d free armies from %s' % (self.prod, self.source)
+        elif self.prod == 0:
+            msg = 'earns %s territory armies from %s' % (self.terr_prod, self.source)
+        else:
+            msg = 'earns %d free armies and %s territory armies from %s' \
+                % (self.prod, self.terr_prod, self.source)
+        Announcement.__init__(self, msg)
+
+
+class PlayerAction (Event):
+    """Report a player decision and its result.
+    """
+    def __init__(self, player, message):
+        Event.__init__(self, message)
+        self.player = player
+    def __str__(self):
+        return '%s: %s' % (self.player, self.message)
+
+class SelectTerritory (PlayerAction):
+    def __init__(self, player, territory):
+        self.territory = territory
+        PlayerAction.__init__(
+            self, player, 'selects %s' % self.territory)
+
+class PlaceArmies (PlayerAction):
+    def __init__(self, player, placements):
+        self.placements = placements
+        PlayerAction.__init__(
+            self, player, 'places %s' % self.placements)
+
+class PlayCards (PlayerAction, EarnsArmies):
+    def __init__(self, player, cards, prod=0, terr_prod={}, source='cards'):
+        self.cards = cards
+        EarnsArmies.__init__(
+            self, player, prod, terr_prod, source)
+        msg = self.message
+        PlayerAction.__init__(self, player, 'plays %s' % self.cards)
+        self.message += ', %s' % msg
+
+class MoveArmies (PlayerAction):
+    def __init__(self, player, source, target, armies):
+        self.source = source
+        self.target = target
+        self.armies = armies
+        PlayerAction.__init__(
+        self, player, 'moves %d from %s to %s' % (armies, source, target))
+
+class Attack (MoveArmies):
+    def __init__(self, source, target, s_dice, t_dice, s_dead, t_dead, armies=None):
+        if armies == None:
+            armies = len(s_dice)
+        MoveArmies.__init__(self, source.player, source, target, armies)
+        self.s_dice = s_dice
+        self.t_dice = t_dice
+        self.s_dead = s_dead
+        self.t_dead = t_dead
+        self.message = 'attacked %s from %s with %d:%d.  Deaths %d:%d.  Remaining %d:%d' \
+            % (self.target, self.source, len(self.s_dice), len(self.t_dice),
+               self.s_dead, self.t_dead,
+               self.source.armies, self.target.armies)
+
+class Conquer (Attack):
+    def __init__(self, source, target, s_dice, t_dice, s_dead, t_dead):
+        Attack.__init__(self, source, target, s_dice, t_dice, s_dead, t_dead,
+                        armies=target.armies)
+        self.message = self.message.replace('attacked', 'conquered')
+
+class Fortify (MoveArmies):
+    def __init__(self, *arg, **kwarg):
+        MoveArmies.__init__(self, *arg, **kwarg)
+        self.message = 'fortifies %s from %s with %d' \
+            % (self.target, self.source, self.armies)
+
 class Logger (list):
     """Log messages generated by risk.base.Engine.