changeset 137:fb8037bc22f1

More flexible (and less boilerplatey) mission stuff.
author Jeremy Thurgood <firxen@gmail.com>
date Thu, 10 May 2012 20:56:40 +0200
parents 1a648d07d67e
children 14917385a0fd abbceec3cc8b
files gamelib/gamestate.py gamelib/missions.py
diffstat 2 files changed, 151 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/gamelib/gamestate.py	Thu May 10 19:20:57 2012 +0200
+++ b/gamelib/gamestate.py	Thu May 10 20:56:40 2012 +0200
@@ -65,7 +65,7 @@
         # Attempt the missions
         mission_results = []
         for mission, equipment in self.cur_missions:
-            mission_results.append(mission.attempt(equipment, self))
+            mission_results.append(mission.attempt_mission(equipment, self))
         if not self.cur_missions and self.reputation > 0:
             # If you're not doing stuff, you're not in the news
             self.reputation -= M_VALS[self.milestone]
@@ -80,7 +80,7 @@
         messages = []
         for result in mission_results:
             result.apply(self)
-            messages.append((result.outcome, result.message))
+            messages.append((result.outcome, result.text))
             if result.outcome == NEW_MILESTONE:
                 self.milestone = MILESTONES[M_VALS[self.milestone] + 1]
         for science in new_stuff:
--- a/gamelib/missions.py	Thu May 10 19:20:57 2012 +0200
+++ b/gamelib/missions.py	Thu May 10 20:56:40 2012 +0200
@@ -7,14 +7,17 @@
 from gamelib.schematics import cat
 
 
-class Result(object):
-    """Results of a mission"""
+class Result(Exception):
+    """Results of a mission.
 
-    def __init__(self, outcome, money, reputation, msg):
+    This is an exception so we can throw it from inside helper methods.
+    """
+
+    def __init__(self, outcome, msg, money=0, rep=0):
         self.outcome = outcome
         self.money = money
-        self.reputation = reputation
-        self.message = msg
+        self.reputation = rep
+        self.text = msg
         self.applied = False
 
     def apply(self, state):
@@ -39,6 +42,12 @@
     MINIMUM_MILESTONE = "neighbourhood"
     MINIONS_REQUIRED = 1
 
+    GENERIC_FAILURE = "You fail to inspire sufficient fear."
+    GENERIC_SUCCESS = "Victory is sweet."
+    NO_EQUIP_FAILURE = (
+        "Really? You're going in completely unequipped? How brave. And "
+        " foolish. And ultimately unsuccessful.")
+
     def __init__(self, init_data=None):
         self.data = {}
 
@@ -83,7 +92,25 @@
             return False
         return True
 
-    def attempt(self, equipment, state):
+    def fail(self, msg=None, money=0, rep=0):
+        if msg is None:
+            msg = self.GENERIC_FAILURE
+        raise Result(FAILURE, msg, money=money, rep=rep)
+
+    def succeed(self, msg=None, money=0, rep=0):
+        if msg is None:
+            msg = self.GENERIC_SUCCESS
+        raise Result(SUCCESS, msg, money=money, rep=rep)
+
+    def categorise_equipment(self, equipment):
+        # Categorise equipment for easier decision-making.
+        categorised = {}
+        for item in equipment:
+            for category in item.CATEGORIES:
+                categorised.setdefault(category, []).append(item)
+        return categorised
+
+    def attempt_mission(self, equipment, state):
         """Attempt the mission with the given equipment list.
         Returns a result object with the results of the mission."""
 
@@ -91,32 +118,27 @@
         if self.get_data('completed'):
             raise RuntimeError('Cannot attempt a completed mission')
 
+        try:
+            self.attempt(equipment, state)
+        except Result, e:
+            return e
+
+    def attempt(self, equipment, state):
         # No equipment is usually a special case.
         if not equipment:
             return self.attempt_no_equipment(state)
 
-        # Categorise equipment for easier decision-making.
-        categorised = {}
-        for item in equipment:
-            for category in item.CATEGORIES:
-                categorised.setdefault(category, []).append(item)
-        result = self.attempt_with(categorised, state)
+        # Try with some equipment.
+        self.attempt_with(self.categorise_equipment(equipment), state)
 
-        if result is not None:
-            # We have a mission result.
-            return result
-
-        # Generic fallback result.
-        return Result(FAILURE, 0, 0, (
-                "You fail to inspire sufficient fear."))
+        # No result, so generic failure.
+        self.fail()
 
     def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, 0, (
-                "Really? You're going in completely unequipped?"
-                " How brave. And foolish. And ultimately unsuccessful."))
+        self.fail(self.NO_EQUIP_FAILURE)
 
     def attempt_with(self, categorised, state):
-        return Result(FAILURE, 0, 0, "You can't succceed at this mission.")
+        self.fail("You can't succceed at this mission.")
 
 
 class PlaygroundBully(Mission):
@@ -128,14 +150,13 @@
         " cares how you earn the money.")
 
     MINIMUM_MILESTONE = "basement"
+    GENERIC_SUCCESS = (
+        "You devote your resources to robbing kids in a playpark. It's not the"
+        " finest moment in your reign of terror, but at least you walked away"
+        " with a surprising amount of small change.")
 
     def attempt(self, equipment, state):
-        haul = randint(90, 110)
-        return Result(SUCCESS, haul, -1, (
-                "You devote your resources to robbing kids in a playpark."
-                " It's not the finest moment in your reign of terror, but at"
-                " least you walked away with a surprising amount of small"
-                " change."))
+        self.succeed(money=randint(90, 110), rep=-1)
 
 
 class RansomChina(Mission):
@@ -155,39 +176,41 @@
         self.data['prior_attempts'] = []
 
     def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, -10, (
-                "It takes three different interpreters before the Chinese"
-                " government finally understand that you're trying to hold"
-                " them ransom with... well... nothing. Nothing at all. This"
-                " one will probably make a good anecdote at diplomatic"
-                " cocktail parties. But not for you. No, not for you at all."))
+        self.fail(
+            "It takes three different interpreters before the Chinese"
+            " government finally understand that you're trying to hold"
+            " them ransom with... well... nothing. Nothing at all. This"
+            " one will probably make a good anecdote at diplomatic"
+            " cocktail parties. But not for you. No, not for you at all.",
+            rep=-10)
 
     def attempt_with(self, categorised, state):
         dooms = categorised.get(cat.DOOMSDAY_DEVICE, [])
 
         if not dooms:
-            return Result(FAILURE, 0, -1, (
-                    "You completely fail to inspire the requisite level of"
-                    " terror. Maybe a more impressive threat will fare"
-                    " better."))
+            self.fail(
+                "You completely fail to inspire the requisite level of"
+                " terror. Maybe a more impressive threat will fare better.",
+                rep=-1)
 
         if len(dooms) > 1:
-            return Result(FAILURE, 0, 0, (
-                    "Everyone seems confused as to how you actually plan"
-                    " to cause widepsread distruction and mayhem, and"
-                    " negotiations break down. Perhaps it's better to stick"
-                    " to one weapon of mass destruction at a time."))
+            self.fail(
+                "Everyone seems confused as to how you actually plan"
+                " to cause widepsread distruction and mayhem, and"
+                " negotiations break down. Perhaps it's better to stick"
+                " to one weapon of mass destruction at a time.")
 
         [doom] = dooms
         if doom.NAME in self.data['prior_attempts']:
-            return Result(FAILURE, 0, 0, (
-                    "'We have devised countermeasures since last time, doctor."
-                    " You cannot threaten us with that again.'"))
+            self.fail(
+                "'We have devised countermeasures since last time, doctor."
+                " You cannot threaten us with that again.'")
 
         self.data['prior_attempts'].add(doom.NAME)
-        return Result(SUCCESS, 1000000, 10, (
-                "Trembling at you threat of certain doom, the Chinese"
-                " government pays the ransom."))
+        self.succeed(
+            "Trembling at you threat of certain doom, the Chinese"
+            " government pays the ransom.",
+            money=randint(800000, 1200000), rep=10)
 
 
 class ToppleThirdWorldGovernment(Mission):
@@ -201,25 +224,21 @@
 
     MINIMUM_REPUTATION = 50
 
-    def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, 0, (
-                "The border post may be poorly guarded, but you need to"
-                " bring *some* kind of weaponry along. Your troops sulk"
-                " on the way home."))
+    GENERIC_FAILURE = (
+        "Nobody seems to quite understand what it is you're threatening them"
+        " with. Eventually you have to give up and go home.")
+
+    NO_EQUIP_FAILURE = (
+        "The border post may be poorly guarded, but you need to bring *some*"
+        " kind of weaponry along. Your troops sulk on the way home.")
 
     def attempt_with(self, categorised, state):
         if any(c in categorised for c in (cat.VEHICLE, cat.HAND_WEAPON)):
-            return Result(SUCCESS, randint(5000, 15000), randint(3, 7), (
-                    "The corruption and oppression continue, but at least"
-                    " the proceeds are making their way into *your*"
-                    " pockets. And you don't even have to dirty your own"
-                    " jackboots."))
-
-        if cat.DOOMSDAY_DEVICE in categorised:
-            return Result(FAILURE, 0, 0, (
-                    "Nobody seems to quite understand what it is you're"
-                    " threatening them with. Eventually you have to give up"
-                    " and go home."))
+            self.succeed(
+                "The corruption and oppression continue, but at least the"
+                " proceeds are making their way into *your* pockets. And you"
+                " don't even have to dirty your own jackboots.",
+                money=randint(5000, 15000), rep=randint(3, 7))
 
 
 class RobBank(Mission):
@@ -232,40 +251,40 @@
 
     MINIMUM_MILESTONE = "basement"
 
-    def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, 0, (
-                "Your attempt to rob the bank barehanded is unsuccessful."
-                " Fortunately, everyone is too stunned to impede your"
-                " escape."))
+    GENERIC_FAILURE = (
+        "The operation was a complete fiasco. Maybe you should try something a"
+        " little less eclectic next time.")
+    NO_EQUIP_FAILURE = (
+        "Your attempt to rob the bank barehanded is unsuccessful. Fortunately,"
+        " everyone is too stunned to impede your escape.")
 
     def attempt_with(self, categorised, state):
         loot = randint(500, 1500)
 
         if cat.VEHICLE in categorised:
-            return Result(FAILURE, 0, 0, (
-                    "Your vehicles are impressively doom-laden, but not really"
-                    " suitable for city traffic. You intimidate the traffic"
-                    " wardens into letting you off without a fine, but by the"
-                    " time you get to the bank it's already closed."))
+            self.fail(
+                "Your vehicles are impressively doom-laden, but not really"
+                " suitable for city traffic. You intimidate the traffic"
+                " wardens into letting you off without a fine, but by the"
+                " time you get to the bank it's already closed.")
 
         if cat.HAND_WEAPON in categorised:
-            return Result(SUCCESS, loot, 0, (
-                    "The threat of your weapons is enough to inspire an"
-                    " impressive level of cooperation. You make off with the"
-                    " loot."))
+            self.succeed(
+                "The threat of your weapons is enough to inspire an impressive"
+                " level of cooperation. You make off with the loot.",
+                money=loot)
 
         if cat.PATHOGEN in categorised:
-            if state.reputation < 10:
-                return Result(FAILURE, 0, 0, (
-                        "The clerk doesn't realise the threat of"
-                        " the vial you hold, and, although watching him"
-                        " die in agony would be statisfying, you decide"
-                        " it's not worth wasting this on something so"
-                        " trivial"))
-            else:
-                return Result(SUCCESS, loot, 1, (
-                        "Holding up a bank with only a small vial of clear"
-                        " liquid. Now that is power."))
+            if randint(5, 15) < state.reputation:
+                self.fail(
+                    "The clerk doesn't realise the threat of the vial you"
+                    " hold, and although watching him die in agony would be"
+                    " statisfying, you decide it's not worth wasting this on"
+                    " something so trivial.")
+            self.succeed(
+                "Holding up a bank with only a small vial of clear liquid. Now"
+                " that is power.",
+                money=loot, rep=1)
 
 
 class DestroyMountRushmore(Mission):
@@ -279,8 +298,7 @@
 
     def attempt(self, equipment, state):
         self.data['completed'] = True
-        return Result(SUCCESS, 0, 50, (
-                "Mount Rushmore is remarkably easy to destroy."))
+        self.succeed("Mount Rushmore is remarkably easy to destroy.", rep=50)
 
 
 class DistributePamphlets(Mission):
@@ -297,29 +315,28 @@
         "A small army of urchins delivers thousands of cheaply printed"
         " pamphlets. %s")
 
+    def _succ(self, message, rep):
+        self.succeed(self.SUCCESS_MESSAGE % (message,), rep=rep)
+
     def attempt_no_equipment(self, state):
         rep = randint(-2, 5)
 
         if rep < 0:
-            result = (
+            self._succ(
                 "Sadly, the populace was so annoyed by the flood of flyers"
-                " that nobody took any notice of the content.")
-        elif rep == 0:
-            result = "Nobody seems to have noticed."
-        else:
-            result = "The public seemed mildly receptive to your propaganda."
+                " that nobody took any notice of the content.", rep)
+        if rep == 0:
+            self._succ("Nobody seems to have noticed.", rep)
 
-        return Result(SUCCESS, 0, rep, self.SUCCESS_MESSAGE % (result,))
+        self._succ(
+            "The public seemed mildly receptive to your propaganda.", rep)
 
     def attempt_with(self, categorised, state):
-        rep = randint(5, 10)
+        if cat.MIND_CONTROL in categorised:
+            self._succ("Your creative use of science has paid off nicely.",
+                       randint(5, 10))
 
-        if cat.MIND_CONTROL in categorised:
-            result = (
-                "Your creative use of science has paid off nicely.")
-            return Result(SUCCESS, 0, rep, self.SUCCESS_MESSAGE % (result,))
-        else:
-            return self.attempt_no_equipment(state)
+        return self.attempt_no_equipment(state)
 
 
 class TakeOverTheWorld(Mission):
@@ -334,16 +351,15 @@
     MINIMUM_REPUTATION = 200
     MINIMUM_MILESTONE = "city"
 
-    def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, 0, "It's going to take more than your bare"
-                      " hands to take over the world!")
+    NO_EQUIP_FAILURE = (
+        "It's going to take more than your bare hands to take over the world!")
 
     def attempt_with(self, categorised, state):
         if cat.MIND_CONTROL not in categorised:
-            return Result(FAILURE, 0, 5, "If you're going to take over the"
-                          " world, first you must control key elements within"
-                          " the populace.")
-        return Result(GAME_WIN, 0, 100, "The world is yours!")
+            self.fail(
+                "If you're going to take over the world, first you must"
+                " control key elements within the populace.", rep=-5)
+        raise Result(GAME_WIN, "The world is yours!", rep=100)
 
 
 class TakeOverTheNeighbourhood(Mission):
@@ -359,23 +375,28 @@
     MINIMUM_REPUTATION = 20
     MINIMUM_MILESTONE = "basement"
 
-    def attempt_no_equipment(self, state):
-        return Result(FAILURE, 0, 0, "The neighbourhood isn't very big, but"
-                      " you're still not going to take it over bare-handed.")
+    NO_EQUIP_FAILURE = (
+        "The neighbourhood isn't very big, but you're still not going to take"
+        " it over bare-handed.")
+    GENERIC_FAILURE = (
+        "Well, that didn't work. Maybe a better equipment selection is in"
+        " order.")
 
     def attempt_with(self, categorised, state):
         if all(c in categorised for c in (cat.MIND_CONTROL, cat.HAND_WEAPON)):
             self.data['completed'] = True
-            return Result(NEW_MILESTONE, 1000, 5, "Guns and persuasion, that's"
-                          " all you need. It's early days still, but you're"
-                          " finally out of that pokey basement and have some"
-                          " elbow room to work with. Next step: the city!")
+            raise Result(NEW_MILESTONE, "Guns and persuasion, that's"
+                         " all you need. It's early days still, but you're"
+                         " finally out of that pokey basement and have some"
+                         " elbow room to work with. Next step: the city!",
+                         money=randint(1000, 2000), rep=randint(5, 15))
+
         if cat.HAND_WEAPON in categorised:
-            return Result(SUCCESS, 150, 0, "You'll need more than guns to"
-                          " win the people over. But until then, you can take"
-                          " their cash.")
+            self.succeed(
+                "You'll need more than guns to win the people over. But until"
+                " then, you can take their cash.", money=randint(100, 200))
+
         if cat.MIND_CONTROL in categorised:
-            return Result(SUCCESS, 0, 7, "Propaganda and persuasion are good"
-                          " tools, but you'll need something to back them up.")
-        return Result(FAILURE, 0, 0, "Well, that didn't work. Maybe a better"
-                      " equipment selection is in order.")
+            self.succeed(
+                "Propaganda and persuasion are good tools, but you'll need"
+                " some force to back them up.", rep=randint(2, 4))