面向对象设计中的大富翁游戏?

24
我通过CodingHorror发现了这篇有趣的博客文章:My Favorite Interview Question。简单来说,他谈论了设计大富翁游戏的面向对象设计挑战,重点在于如何对游戏规则进行建模。例如,“如果一个玩家拥有波罗的海大道,她能在上面添加房子吗?”
有趣的是,在文章的底部,他写道:
“你可以节省很多面试时间。不要花费太多精力,只需让候选人描述一下他们在使用策略、访问者和命令模式时实际应用过哪些框架之外的情况。”
这可能意味着您可以使用设计模式来对游戏规则进行建模(参见上文)。有人曾经这样做过吗?使用设计模式设计大富翁游戏?如果有,它的工作效果如何?
3个回答

31

这是我设计大富翁的方式。我假设采用动态类型语言,因为这可以使一切变得更容易。具体来说,采用Ruby。

您需要一个简单的Game对象,它主要是一个包装了一个大小为40的Array的便利类。 Game对象还记录了可用houseshotels的数量以及两个机会和社区公益基金卡堆栈。提供了一些方便的方法,如current_turnnext_turn! ——两者都返回一个Player对象;next_turn!增加回合索引,如果必要,则循环到0。

玩家可以落地的所有位置都必须继承自Property的超类。 Property类定义了一些共同的属性,例如rentownersethousespurchasable?upgradeable?rentowner属性可以是nilset属性返回一个Array,其中包含组内所有属性。 set属性的大小可以在1到4之间变化。 houses属性表示酒店的5个'houses'。

Game对象具有一个Player对象的Array,每个对象都带有像position(从0到39的整数)、money(没有上限——银行技术上永远不会“用完钱”)、get_out_of_jail_freesin_jail?(因为位置对此不足)。Game对象还具有跟踪轮换的索引。

具体属性规则都被编码在各自的子类中。因此,例如,在Railroad上实现rent的方法是:

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

可以通过一堆闭包简单地实现“机会”和“社区救济”卡牌,这些闭包需要以游戏和玩家对象作为参数。例如:

# 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

等等,advance_token! 方法显然处理像通过前进的事情。

显然,还有更多细节 - 这是一个相当复杂的游戏,但希望这能给你正确的想法。对于面试来说,这肯定已经足够了。

更新

可以通过向 Game 对象添加一个 house_rules Array 来打开或关闭房间规则。这将允许实现类似这样的 FreeParking 属性:

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

1
哇!想得很周到且构造良好。 - SRM
1
如果我正确理解这篇文章,作者的主要关注点在于几乎没有两个人按照相同的规则玩大富翁游戏,因为每个人都有自己习惯的不同房屋规则。在你的架构中,改变规则集是否需要在许多不同的地方进行许多小的更改? - Benno
最常见的房屋规则——在免费停车区获得金钱——可以通过为“免费停车区”属性添加“租金”方法并返回负数来轻松实现。这只是一个地方的微小变化。您可以轻松地添加分支逻辑,以切换房屋规则。 - Bob Aman
我更新了我的答案,并提供了更多关于如何完成此操作的信息。 - Bob Aman
我认为在使用OP链接中建议的某些模式时的重点是要避免您所做的事情,即通过添加另一个属性并显式修改FreeParking的租金方法来修改Game对象。基本上,您希望能够添加规则而不修改任何这些对象(开闭原则)。 - C S

5

我认为你在这里走了错误的道路。

...which probably means that you can use design patterns to model the rules of the game (see above). 

我认为这只是显示你不太理解设计模式的含义。已知的设计模式只是在编码时遇到的反复出现的情况所赋予的名称。在日常生活中,你从不会说“我早上8点起床去了X地方,在那里工作直到下午5点,他们月底给我发薪水。” 你会说,“今天我去工作了。” 你想要挣钱,而解决这个问题的常见方法就是去工作。所以...我们在这里有一个模式!让我们称它为“工作”!
设计模式只是对常见问题的一堆研究过的解决方案。每个解决方案都有一个相关的名称(策略,访问者等)。
回到
...which probably means that you can use design patterns to model the rules of the game 

这并不意味着你可以使用设计模式来建模游戏规则,而是说无论你在解决方案中做什么,它很可能会落到一些已知的设计模式上。把你的解决方案看作一组相互连接的模式比从头开始描述所有内容更容易。


在哲学上是正确的,但在实践中并不那么简单。DP 的主要挑战在于在问题中识别它们,并在解决方案中正确应用它们(顺便说一句,在现实生活中,当问题稍微不太常见时,情况也是如此)。这个问题可能应该重新表述为“哪些已知的设计模式可以应用于简化解决这个问题的方法?” - davka
这个问题可能需要重新表述为“哪些已知的设计模式可以应用于简化解决这个问题?”我同意。只是在SO上看到人们对设计模式感到非常困惑,好像它们是目标(而不是达成目标的手段)一样普遍。 - devoured elysium

2
我从未设计过《大富翁》的规则(我认为太容易了),但我涉足编写其他知名游戏引擎,以满足个人兴趣,并理解这一切都是学术性的练习。
我尝试模拟的两个游戏(并继续尝试)是D&DM:tG
在D&D中,重点是非常好的面向对象设计 - 制作有意义的类和类层次结构。
在M:tG中,你基本上意识到纯面向对象范式对于这种事情是不完整的。然后你就会开始使用代理、事件代理和创建非常复杂的规则集。
这一切对于非游戏设计师来说都是相当无意义的。不过还是很有趣的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接