Added fortification to the basic Player AI.
authorW. Trevor King <wking@drexel.edu>
Sat, 27 Mar 2010 01:56:41 +0000 (21:56 -0400)
committerW. Trevor King <wking@drexel.edu>
Sat, 27 Mar 2010 01:56:41 +0000 (21:56 -0400)
This will excercise the fortification code during random_game()s.

Also moved Player.border_territories functionality into a combo of
Player.territories and a new Territory.border.  This makes it easy to
iterate through non-border territories looking for out-of-the-way
armies for fortification.

Finally, limit the players to one fortification per turn ;).

pyrisk/base.py

index 7f9fcef769ea398d95628e89b41605a8411dc40f..8229f6080e0e9f40519cc93d7d048f7e54972464 100644 (file)
@@ -20,7 +20,8 @@
 import random
 
 from .log import Logger, BeginGame, EndGame, Killed, StartTurn, DealtCards, \
 import random
 
 from .log import Logger, BeginGame, EndGame, Killed, StartTurn, DealtCards, \
-    EarnsArmies, SelectTerritory, PlaceArmies, PlayCards, Attack, Conquer
+    EarnsArmies, SelectTerritory, PlaceArmies, PlayCards, Attack, Conquer, \
+    Fortify
 
 
 class PlayerError (Exception):
 
 
 class PlayerError (Exception):
@@ -75,6 +76,11 @@ class Territory (NameMixin, ID_CmpMixin, list):
             if id(t) == id(other):
                 return True
         return False
             if id(t) == id(other):
                 return True
         return False
+    def border(self):
+        for t in self:
+            if t.player != self.player:
+                return True
+        return False
 
 class Continent (NameMixin, ID_CmpMixin, list):
     """A group of Territories.
 
 class Continent (NameMixin, ID_CmpMixin, list):
     """A group of Territories.
@@ -400,15 +406,6 @@ class Player (NameMixin, ID_CmpMixin):
         for t in world.territories():
             if t.player == self:
                 yield t
         for t in world.territories():
             if t.player == self:
                 yield t
-    def border_territories(self, world):
-        """Iterate through all territories owned by this player which
-        border another player's territories.
-        """
-        for t in self.territories(world):
-            for neighbor in t:
-                if neighbor.player != self:
-                    yield t
-                    break
     def report(self, world, log):
         """Send reports about death and game endings.
 
     def report(self, world, log):
         """Send reports about death and game endings.
 
@@ -453,8 +450,9 @@ class Player (NameMixin, ID_CmpMixin):
 
         Return {territory_name: num_armies, ...}
         """
 
         Return {territory_name: num_armies, ...}
         """
-        t = random.sample(list(self.border_territories(world)), 1)[0]
-        return {t.name: this_round}
+        terr = random.sample([t for t in self.territories(world)
+                              if t.border()], 1)[0]
+        return {terr.name: this_round}
     def attack_and_fortify(self, world, log, error=None,
                            mode='attack'):
         """Return list of (source, target, armies) tuples.  Place None
     def attack_and_fortify(self, world, log, error=None,
                            mode='attack'):
         """Return list of (source, target, armies) tuples.  Place None
@@ -462,15 +460,29 @@ class Player (NameMixin, ID_CmpMixin):
         """
         assert mode != 'fortify', mode
         possible_attacks = []
         """
         assert mode != 'fortify', mode
         possible_attacks = []
-        for t in self.border_territories(world):
+        for t in self.territories(world):
+            if not t.border():
+                continue
             if t.armies <= 3: #1: # be more conservative, only attack with 3 dice
                 continue
             targets = [border_t for border_t in t if border_t.player != self]
             for tg in targets:
             if t.armies <= 3: #1: # be more conservative, only attack with 3 dice
                 continue
             targets = [border_t for border_t in t if border_t.player != self]
             for tg in targets:
-                possible_attacks.append((t.name, tg.name, min(3, t.armies-1)))
+                possible_attacks.append(
+                    (t.name, tg.name, min(3, t.armies-1)))
         if len(possible_attacks) == 0:
         if len(possible_attacks) == 0:
-            return [None, None] # stop attack phase, then stop fortification phase
-        return random.sample(possible_attacks, 1) # + [None]
+            fortifications = []
+            for t in self.territories(world):
+                if t.border() or t.armies == 1:
+                    continue
+                targets = list(t)
+                for tg in targets:
+                    fortifications.append(
+                        (t.name, tg.name, t.armies-1))
+            if len(fortifications) > 1:
+                fortifications = random.sample(fortifications, 1)
+            # stop attack phase, fortify, stop fortification phase
+            return [None] + fortifications + [None]
+        return random.sample(possible_attacks, 1)
     def support_attack(self, world, log, error,
                        source, target):
         """Follow up on a conquest by moving additional armies.
     def support_attack(self, world, log, error,
                        source, target):
         """Follow up on a conquest by moving additional armies.
@@ -683,6 +695,7 @@ class Engine (ID_CmpMixin):
                     else:
                         assert mode == 'fortify', mode
                         self.fortify(source, target, armies)
                     else:
                         assert mode == 'fortify', mode
                         self.fortify(source, target, armies)
+                        return captures # only allow one fortification
             except PlayerError, error:
                 continue
     def attack(self, source, target, armies):
             except PlayerError, error:
                 continue
     def attack(self, source, target, armies):