Monday, October 22, 2007

Where do you put the rules of Monopoly?

It's apparently a favorite interview question. I have to admit, the first response in my head wasn't a great one, which was "everywhere".

What are game rules? When browsing the rules of popular games like Monopoly and Scrabble, they seem to follow a similar format:

  • The initial conditions of the game (the setup)
  • Then given a condition,
    • the set of allowable actions for the player to do
    • the effects of the condition
I embarked on somewhat of the same problem, though on a much smaller scale. I wanted to implement a point system that gave points to those that participated in a Rails app. My first reaction was to make a Rule abstract class, and have different rule classes that subclass it. Something like this (variable names have been changed to protect the innocent):
class SceneRules
def initialize(model)
@scene = model
@user = @scene.submitting_user
end

def on_submit
@user.points += 1
end
end

class SceneshotRules
def initialize(model)
@sceneshot = model
@user = @sceneshot.sceneshot_uploader
end

def on_submit
@user.points += 10
@scene.submitting_user.points += 5
end

end
Then I'd be able to call it from the controllers. However, on second thought, it's rather ugly, since I'd be updating the karma everywhere in the controllers. If I understand cross-cutting concerns correctly, scoring karma would be a good example of one. I suppose it's a good candidate for aspect orientated programming, so I scrapped the code above.

Logging is often cited as the poster-child problem to solve with AOP. Logging needs to be done everywhere in the code, but it really has nothing to do with the responsibilities of the class that it's performed in. So you have the same code doing the same thing, duplicated everywhere because there's no one place to put it to make things easy to change.

By the same token, game rules and scoring are of the same nature. And because game rules involve lots of different objects at once, and scoring is interspersed throughout, I think that makes it a good candidate for AOP. However, Ruby has no such direct support for AOP. Instead, the closest thing we have are observers, before/after/around filters (in Rails), and some meta-programming.

I wanted something that allowed me to list out rules like games like Monopoly and Scrabble. I'd have a setup, and some conditions and their effects. Scoring is simplified here because the only time you can score is when one of the models is created or changes state. This is a good fit to the observers and the filters available in Rails.

class ScoringRules < ActiveRecord::Observer
observe Sceneshot, Scene
include Rules

setup { :scene => Proc.new { |sceneshot| sceneshot.scene } },
{ :sceneshot_uploader => Proc.new { |sceneshot| sceneshot.sceneshot_uploader },
:scene_submitting_user => Proc.new { |sceneshot| sceneshot.scene.submitting_user } }

rule :after_create_sceneshot do |board, players|
players[:scene_submitting_user].score += 5
end

# put more rules here

def after_create(model)
rule_dispatch(:after_create, model)
end

end
Here, the Rules module is what encapsulates the setup, rule, and rule_dispatch calls. I needed setup so that I can access different "game elements" (the board and the players) to update the scoring. It basically stores the setup as a list of lambdas that it can execute at a later time when the rule needs to be executed. Now, when a model is created, we ask the rule dispatcher to figure out which rules execute based on the rules we've named, and then execute the attached block. The block is passed a hash of different game pieces that it needs to update the score and the game conditions. That's it.

I thought it was an interesting way about it and probably warranted some criticism. Is there any particular disadvantage of doing it this way? And if you can think of a way to not have to explicitly state the model relationships in the setup, that'd be nice. half-tip!

3 comments:

  1. Aren't games a classic example of a state machine? There are only some states you can be in, and the transitions between states are strictly defined. You define point changes between the states.

    One place to look at this is in Ragel, the state machine compiler. http://www.cs.queensu.ca/~thurston/ragel/

    Just a thought.

    ReplyDelete
  2. Anonymous1:55 PM

    I've spent so much time in Emacs lately, I assume that everything should be coded and configured in the same way.

    Functional programming concepts seem to be useful in separating out the various bits of "code" and rules.

    This was my answer (without having spent much time trying to understand yours):

    http://lispy.wordpress.com/2007/10/23/just-put-all-monopoly-rules-in-the-monopoly-file-and-be-done-with-it/

    What do you think?

    ReplyDelete
  3. The original reason I posed the question was whether code that would normally be distributed everywhere, such as scoring or logging, could be stated in one place in a succinct way--which makes it easier for maintainence. People have rallied around Aspect Orientated Programming for it, but I was wondering if it was enough to do a DSL in Ruby (or some other language), and what I posted was my first stab at it.

    Right now we have to rely on working around a programming language to be able to declare a set of conditions that the code can be in and the code that needs to be executed when those conditions are met. Something like Ragel seems like (to me) an interesting candidate to bake right into a programming language, so we can do that without having to build the structure ourselves.

    ReplyDelete