0

I have a World class which stands as the root of a game simulation. All other classes are instantiated either directly from World's methods or from objects instantiated within World. I want to generate a list of specific objects and scalars for later use.

During runtime, I have another class Inspector which will run a single time, once the game world is initialized. Inspector's job is to find all marked objects or scalars and return a list of references to them for later value extraction. The need for this job is born out of the necessity of feeding various game states as input into the AI's neural net. The AI does not care what they are named, so long as they return in the same order on each run.

For instance, World has a Table, and on this Table is a Deck of cards. I'd like to be able to mark this Deck to be picked up by the Inspector.

Additionally, I'd like for the suit (str) and rank (int) of each Card in the deck to also be picked up by the Inspector. The AI does not care about the Deck; it cares about the individual suit/rank of each card.

Current python code goes something like this:

class Inspector:
  @staticmethod
  def inspect(obj):
    found = []

    # search through each class member
    for entity in vars(obj):
      if is_marked(entity):
        # the neural net only cares about ints/chars
        if isinstance(entity, int) or isinstance(entity, str):
          found.append(entity)
        else:
          # let's explore deeper
          found.append(inspect(entity))

    return found

  @staticmethod
  def is_marked(entity):
    if ???:
      return true
    else:
      return false

I imagine using this code to run the game and build the list of marked objects:

def main():
  world = World()
  ai = AI()
  world.init()
  marked = Inspector(world).inspect()
  ai.init(marked)

  simulate(world, ai)      

I'm tripping up a bit on the inner workings of is_marked(). How can an object or scalar be marked for later inspection, scalars in particular when you cannot create a second reference to a scalar?

Edit: I'm irrationally afraid of using a Singleton and manually adding things as I go. Pretending that this is a fine thing to do, how would I go about dealing with the no-second-reference-to-scalar factor?

circles
  • 23
  • 4

1 Answers1

0

This problem is a prime candidate for an Observer/Observable pattern.

There will be several classes of Observers: TableObserver, PlayerObserver, etc.

We will manually push data into a buffer in the Observers.

As InputNeurons are instantiated, they are given an Observer which will feed them values later on.

When the NeuralNet fires, each Neuron asks its Observer for the current state of the world, which is conveniently stored in its buffer.

Pros

  1. We do not wind up with PlayerObserver tightly coupled to Player, MatchObserver tightly coupled to Match, etc.
  2. We can make intelligent choices about the data we receive before feeding it into the InputNeurons. For example, we can check to see if the other player is playing with his hand face up, and if so, use that data; otherwise, provide nulls.
  3. We can add on new Observable types later on with minimal effort, simply implementing a organize_data() function.

Cons

  1. We need to remember to call notify_observers() after each action we take that changes the game state. This may lead to bugs down the road.
  2. More work to implement.

Here's some sample code to get things started:

class Observable(object):
    def __init__(self):
        self._observers = []

    def register_observer(self, obj):
        if obj not in self._observers:
            self._observers.append(obj)

    def notify_observers(self):
        for observer in self._observers:
            observer.observe(self.wrapped.organize_data())


class Observer(object):
    def __init__(self, obj):
        self._observed = obj
        self.buffer = None
        self.register(obj)

    # called by observed object. provides the observer with a list of integers
    def observe(self, int_list):
        raise NotImplementedError("must implement observe")

    def register(self, obj):
        obj.register_observer(self)


class PlayerObserver(Observer):
    def __init__(self, player):
        super(PlayerObserver, self).__init__(player)

    # store a copy of the integer list passed our way
    def observe(self, int_list):
        self.buffer = list(int_list)

There is an example of Python Observer patterns here: Observer Observable classes in python

Unfortunately, the "pythonic" method of implementing an observer pattern by creating a wrapper class contains a fatal flaw of notifying observers BEFORE the wrapped class method is called. We're most interested in knowing the state of an object AFTER a method has been called, and so this method does not work. Best to manually trigger calls to notify_observers() in key points throughout the game.

Community
  • 1
  • 1
circles
  • 23
  • 4