changeset 28:c03982fe3c70

Protagonist and environment.
author Jeremy Thurgood <firxen@gmail.com>
date Sun, 01 Sep 2013 16:07:39 +0200
parents 3e4d8091268c
children 58505d3482b6
files nagslang/environment.py nagslang/protagonist.py nagslang/tests/__init__.py nagslang/tests/test_environment.py
diffstat 3 files changed, 300 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/environment.py	Sun Sep 01 16:07:39 2013 +0200
@@ -0,0 +1,85 @@
+class ProtagonistCondition(object):
+    """A condition on the protagonist that can be checked.
+    """
+
+    def check(self, protagonist):
+        """Check if this condition applies.
+        """
+        raise NotImplementedError()
+
+
+class AllConditions(ProtagonistCondition):
+    def __init__(self, *conditions):
+        self.conditions = conditions
+
+    def check(self, protagonist):
+        for condition in self.conditions:
+            if not condition.check(protagonist):
+                return False
+        return True
+
+
+class AnyCondition(ProtagonistCondition):
+    def __init__(self, *conditions):
+        self.conditions = conditions
+
+    def check(self, protagonist):
+        for condition in self.conditions:
+            if condition.check(protagonist):
+                return True
+        return False
+
+
+class WolfFormCondition(ProtagonistCondition):
+    def check(self, protagonist):
+        return protagonist.in_wolf_form()
+
+
+class HumanFormCondition(ProtagonistCondition):
+    def check(self, protagonist):
+        return protagonist.in_human_form()
+
+
+class ItemRequiredCondition(ProtagonistCondition):
+    def __init__(self, required_item):
+        self.required_item = required_item
+
+    def check(self, protagonist):
+        return protagonist.has_item(self.required_item)
+
+
+class Action(object):
+    """Representation of an action that can be performed.
+
+    If the (optional) condition is met, the provided function will be called
+    with the protagonist and the target as parameters.
+    """
+    def __init__(self, func, condition=None):
+        self.func = func
+        self.condition = condition
+
+    def check(self, protagonist):
+        if self.condition is None:
+            return True
+        return self.condition.check(protagonist)
+
+    def perform(self, protagonist, target):
+        if not self.check(protagonist):
+            raise ValueError("Attempt to perform invalid action.")
+        return self.func(protagonist, target)
+
+
+class Interactible(object):
+    """The property of interactibility on a thing.
+    """
+
+    def __init__(self, *actions):
+        self.actions = actions
+
+    def select_action(self, protagonist):
+        """Select a possible action given the protagonist's state.
+        """
+        for action in self.actions:
+            if action.check(protagonist):
+                return action
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/protagonist.py	Sun Sep 01 16:07:39 2013 +0200
@@ -0,0 +1,54 @@
+
+
+class Protagonist(object):
+    """Representation of our fearless protagonist.
+
+    TODO: Factor out a bunch of this stuff when we need it for other objects.
+    """
+
+    HUMAN_FORM = 'human'
+    WOLF_FORM = 'wolf'
+
+    def __init__(self):
+        self.inventory = {}
+        self.form = self.HUMAN_FORM
+
+    @classmethod
+    def from_saved_state(cls, saved_state):
+        """Create an instance from the provided serialised state.
+        """
+        obj = cls()
+        # TODO: Update from saved state.
+        return obj
+
+    def act_on(self, target):
+        """Perform an action on the target.
+        """
+        # TODO: Decide how best to do this.
+        pass
+
+    def change_to_form(self, form):
+        """Change to a particular form.
+
+        This will be a no-op if we're already in this form.
+        """
+        pass
+
+    def swap_form(self):
+        """Swap to your other form.
+        """
+        pass
+
+    def attack(self):
+        """Attempt to hurt something.
+        """
+        pass
+
+    def in_wolf_form(self):
+        return self.form == self.WOLF_FORM
+
+    def in_human_form(self):
+        return self.form == self.HUMAN_FORM
+
+    def has_item(self, item):
+        return item in self.inventory
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/tests/test_environment.py	Sun Sep 01 16:07:39 2013 +0200
@@ -0,0 +1,161 @@
+from unittest import TestCase
+
+from nagslang import environment
+
+
+class YesCondition(environment.ProtagonistCondition):
+    def check(self, protagonist):
+        return True
+
+
+class NoCondition(environment.ProtagonistCondition):
+    def check(self, protagonist):
+        return False
+
+
+class ErrorCondition(environment.ProtagonistCondition):
+    def check(self, protagonist):
+        raise Exception()
+
+
+class FakeProtagonist(object):
+    def __init__(self, wolf=False, human=False, item=False):
+        self._wolf = wolf
+        self._human = human
+        self._item = item
+
+    def in_wolf_form(self):
+        return self._wolf
+
+    def in_human_form(self):
+        return self._human
+
+    def has_item(self, item):
+        return self._item
+
+
+class TestConditions(TestCase):
+    def assert_met(self, condition, protagonist=None):
+        self.assertTrue(condition.check(protagonist))
+
+    def assert_not_met(self, condition, protagonist=None):
+        self.assertFalse(condition.check(protagonist))
+
+    def assert_error(self, condition, protagonist=None):
+        self.assertRaises(Exception, condition.check, protagonist)
+
+    def test_test_conditions(self):
+        self.assert_met(YesCondition())
+        self.assert_not_met(NoCondition())
+        self.assert_error(ErrorCondition())
+
+    def test_all_conditions(self):
+        yes = YesCondition()
+        no = NoCondition()
+        err = ErrorCondition()
+        self.assert_met(environment.AllConditions(yes, yes))
+        self.assert_not_met(environment.AllConditions(yes, no))
+        self.assert_not_met(environment.AllConditions(no, err))
+        self.assert_error(environment.AllConditions(err, yes))
+        self.assert_error(environment.AllConditions(yes, err))
+
+    def test_any_condition(self):
+        yes = YesCondition()
+        no = NoCondition()
+        err = ErrorCondition()
+        self.assert_met(environment.AnyCondition(no, yes))
+        self.assert_not_met(environment.AnyCondition(no, no))
+        self.assert_met(environment.AnyCondition(yes, err))
+        self.assert_error(environment.AnyCondition(err, yes))
+        self.assert_error(environment.AnyCondition(no, err))
+
+    def test_wolf_form_condition(self):
+        wolf = environment.WolfFormCondition()
+        self.assert_met(wolf, FakeProtagonist(wolf=True, human=True))
+        self.assert_met(wolf, FakeProtagonist(wolf=True, human=False))
+        self.assert_not_met(wolf, FakeProtagonist(wolf=False, human=True))
+        self.assert_not_met(wolf, FakeProtagonist(wolf=False, human=False))
+
+    def test_human_form_condition(self):
+        human = environment.HumanFormCondition()
+        self.assert_met(human, FakeProtagonist(human=True, wolf=True))
+        self.assert_met(human, FakeProtagonist(human=True, wolf=False))
+        self.assert_not_met(human, FakeProtagonist(human=False, wolf=True))
+        self.assert_not_met(human, FakeProtagonist(human=False, wolf=False))
+
+    def test_item_required_condition(self):
+        item = environment.ItemRequiredCondition('item')
+        self.assert_met(item, FakeProtagonist(item=True))
+        self.assert_not_met(item, FakeProtagonist(item=False))
+
+
+class TestActions(TestCase):
+    def setUp(self):
+        self.state = {}
+
+    def make_action_func(self, name):
+        self.state.setdefault(name, [])
+
+        def action_func(protagonist, target):
+            self.state[name].append((protagonist, target))
+            return len(self.state[name])
+
+        return action_func
+
+    def make_action(self, name, condition=None):
+        return environment.Action(self.make_action_func(name), condition)
+
+    def assert_state(self, **kw):
+        self.assertEqual(self.state, kw)
+
+    def assert_action_selected(self, action, interactible, protagonist):
+        self.assertEqual(action, interactible.select_action(protagonist))
+
+    def test_unconditional_action(self):
+        action = self.make_action('action')
+        self.assert_state(action=[])
+        self.assertTrue(action.check(None))
+        self.assert_state(action=[])
+        self.assertEqual(1, action.perform('p', 't'))
+        self.assert_state(action=[('p', 't')])
+        self.assertEqual(2, action.perform('p2', 't2'))
+        self.assert_state(action=[('p', 't'), ('p2', 't2')])
+
+    def test_conditional_action(self):
+        yes_action = self.make_action('yes_action', YesCondition())
+        no_action = self.make_action('no_action', NoCondition())
+        self.assert_state(yes_action=[], no_action=[])
+        self.assertTrue(yes_action.check(None))
+        self.assert_state(yes_action=[], no_action=[])
+        self.assertFalse(no_action.check(None))
+        self.assert_state(yes_action=[], no_action=[])
+        self.assertEqual(1, yes_action.perform('p', 't'))
+        self.assert_state(yes_action=[('p', 't')], no_action=[])
+
+    def test_perform_bad_action(self):
+        action = self.make_action('action', NoCondition())
+        self.assert_state(action=[])
+        self.assertFalse(action.check(None))
+        self.assert_state(action=[])
+        self.assertRaises(ValueError, action.perform, 'p', 't')
+
+    def test_interactible_no_actions(self):
+        interactible = environment.Interactible()
+        self.assert_action_selected(None, interactible, None)
+
+    def test_interactible_unconditional_action(self):
+        action = self.make_action('action')
+        interactible = environment.Interactible(action)
+        self.assert_action_selected(action, interactible, None)
+
+    def test_interactible_conditional_actions(self):
+        wolf_action = self.make_action('wolf', environment.WolfFormCondition())
+        item_action = self.make_action(
+            'item', environment.ItemRequiredCondition('item'))
+        interactible = environment.Interactible(wolf_action, item_action)
+        self.assert_action_selected(
+            wolf_action, interactible, FakeProtagonist(wolf=True))
+        self.assert_action_selected(
+            item_action, interactible, FakeProtagonist(item=True))
+        self.assert_action_selected(
+            wolf_action, interactible, FakeProtagonist(wolf=True, item=True))