Monopoly game in OOD?

前端 未结 3 713
情话喂你
情话喂你 2020-12-22 22:04

I found this interesting blog post via CodingHorror: My Favorite Interview Question. In a nutshell, he talks about the object-oriented design challenges of designing the gam

3条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-22 22:48

    Here's how I would design Monopoly. I've taken the liberty of assuming a dynamically-typed language since that makes everything easier. Ruby specifically.

    You have a simple Game object that's mostly a wrapper around an Array of size 40, plus some convenience methods. The Game object also tracks the number of available houses and hotels and the two stacks of Chance and Community Chest cards. A few convenience methods like current_turn and next_turn! are provided — both return a Player object; next_turn! increments the turn index, wrapping to 0 if necessary.

    All locations the player can land on must inherit from a superclass of Property. The Property class defines a few common things like rent, owner, set, houses, purchasable?, and upgradeable?. The rent and owner properties may be nil. The set property returns an Array containing all properties within the group. The set property may vary in size from 1 to 4. The houses property represents a hotel as 5 'houses'.

    The Game object has an Array of Player objects, each with fields like position (an integer from 0 to 39), money (no upper bound — the bank technically never 'runs out of money'), get_out_of_jail_frees, and in_jail? (since position is insufficient for this). The Game object also has an index to track whose turn it is.

    Property-specific rules are all encoded within their respective subclasses. So, for instance, the implementation of rent on a Railroad would be:

    def rent
      owned_count = self.set.select { |rr| rr.owner == self.owner }.size
      return 25 * 2 ** (owned_count - 1)
    end
    

    Chance and Community Chest cards can be simply implemented with a bunch of closures that takes a game and a player object as parameters. For instance:

    # Second place in a beauty contest
    COMMUNITY_CHEST_CARDS << lambda do |game, player|
      player.money += 10
    end
    
    # Advance token to Boardwalk
    CHANCE_CARDS << lambda do |game, player|
      game.advance_token!(player, 39)
    end
    
    # Advance token to nearest railroad, pay double
    CHANCE_CARDS << lambda do |game, player|
      new_position = [5, 15, 25, 35].detect do |p|
        p > player.position
      end || 5
      game.advance_token!(player, new_position)
      # Pay rent again, no-op if unowned
      game.properties[new_position].pay_rent!(player)
    end
    

    And so on. The advance_token! method obviously handles things like passing go.

    Obviously, there are more details — it's a fairly complicated game, but hopefully this gives you the right idea. It'd certainly be more than sufficient for an interview.

    Update

    House rules could be switched on or off by adding a house_rules Array to the Game object. This would allow the FreeParking property to be implemented like this:

    class Game
      def house_rules
        @house_rules ||= []
      end
    
      def kitty
        # Initialize the kitty to $500.
        @kitty ||= 500
      end
    
      def kitty=(new_kitty)
        @kitty = new_kitty
      end
    end
    
    class FreeParking < Property
      def rent
        if self.game.house_rules.include?(:free_parking_kitty)
          # Give the player the contents of the kitty, and then reset it to zero.
          return -(_, self.game.kitty = self.game.kitty, 0)[0]
        else
          return 0
        end
      end
    end
    

提交回复
热议问题