为 Scrabble 游戏实施设计灵活且可扩展的奖励系统
假设我正在实现我自己的拼字游戏版本。 我目前有一个 Board
类,其中包含大量 Squares
。 Square
又由 IBonus
和 Piece
组成。奖励实现实际上是 Scrabble 的常见奖励,但我可能会尝试添加一些新的和扭曲的奖励来为游戏增添趣味——这里的灵活性至关重要!
经过思考一段时间,我得出的结论是,要使 IBonus
实现发挥作用,他们需要知道整个 Board
及其当前位置(在 Board
上),这样它就知道它在哪里,并且可以检查同一块中的棋子平方作为奖金)。这让我感到很糟糕,因为它基本上需要了解大量信息。
因此,我的幼稚实现是将 Board 作为参数传递给 IBonus.calculate() 方法,IBonus.calculate(Board board, Pointposition)IBonus.calculate() 方法代码>,即。
此外,它似乎创建了一个循环引用。还是我错了?
我不太喜欢这种方法,所以我正在寻找其他可能的方法。我知道我可以让 calculate
接受一个接口而不是具体的类,即 calculate(IBoard board)
但我认为这并不比第一种情况更好。
我担心过于关注我当前的实现而无法想到至少可以适合这个问题的完全不同的设计以及解决方案。 也许我可以重新构建整个游戏并将奖金放在其他地方,这样可以方便计算?也许我太专注于让他们出现在董事会
上?我当然希望有其他方法可以解决这个问题!
谢谢
Let's say I'm implementing my own version of Scrabble.
I currently have a Board
class that contains lots of Squares
. A Square
in turn is composed of a IBonus
and a Piece
. The bonus implementations are actually the usual bonus for Scrabble, but it is possible that I might try to add some new and twisted bonus to spice the game -- flexibility here is paramount!
After thinking for a while I came to the conclusion that for IBonus
implementations to work, they'll need to know the whole Board
and also its current position(on the Board
, so it knows where it is and it can check for the piece that's in the same square as the bonus is). This strikes me as bad as basically it needs to know a whole lot of information.
So, my naive implementation would be to pass the Board
as argument to IBonus.calculate()
method, IBonus.calculate(Board board, Point position)
, that is.
Also, it seems to create a circular reference. Or am I wrong?
I don't particulary like this approach, so I am looking for other possible approaches. I know I can make calculate
accept an interface instead of a concrete class, i.e., calculate(IBoard board)
but I IMO that isn't all that better than the first case.
I fear being too focused on my current implementation to be able to think of whole different designs that could fit at least as well as solutions to this problem.
Maybe I could re-architect the whole game and have the bonuses in other place, so it would facilitate this calculation? Maybe I am too focused on having them on the Board
? I certainly hope there are other approaches to this problem out there!
Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我假设 Board 具有游戏的可见状态,并且还有其他对象,例如 Rack(每个玩家一个)和 DrawPile。
“如果单词包含真实(非空白)Z,则加倍得分” - 需要您输入单词或棋盘以及单词的位置。
“如果单词是板上最长的单词,则加倍分数”需要整个板。
“如果单词的第一个字母与从 DrawPile 中随机选择的字母匹配,则获得双倍分数”当然需要 DrawPile。
所以对我来说,这仅取决于您实施的规则。我很愿意将 Board 传递给 IBonus Score() 实现。
编辑-更多想法。
所以一块棋盘有 17x17 的方格,或者其他什么。我会将 IBonus 实现分配给棋盘的每个方格(会有一个名为 PlainEmptySquare 的惰性实现。)您只需要实例化 IBonus 的每个实现一次 - 它可以被引用多次。我可能会采取低级方式并显式实例化每一个,并传递所需的参数。如果一种类型需要 Board,则将其传入。如果另一种类型需要 DrawPile,则将其传入。在您的实现中,您可能会有 12 行丑陋的代码。 /耸肩
I assume Board has the visible state of the game, and there would be other objects such as Rack (one per Player,) and a DrawPile.
"Double Score if word contains a real (non-blank) Z" - would require you pass in the Word, or the Board and the position of the word.
"Double Score if the word is the longest on the board" requires the entire Board.
"Double Score if the first letter of the word matches a randomly selected letter from the DrawPile" requires the DrawPile of course.
So to me it just depends on the rules you implement. I'd be comfortable with passing Board to the IBonus score() implementation.
edit - more thoughts.
So a board has 17x17 squares, or whatever. I'd assign an IBonus implementation to each square of the board (there would be an implementation called PlainEmptySquare that was inert.) You'd only need to instantiate each implementation of IBonus once - it could be referenced many times. I'd probably take the low road and instantiate each one explicitly, passing in the arguments needed. If one type needs the Board, pass it in. If another needs the DrawPile, pass it in. In your implementation, you'd have perhaps 12 lines of ugliness. /shrug
类似下面的内容可能会起作用:
CurrentGame
有一个Board
,其中包含Squares
的集合。Square
可以有 IBonus,但是Square
上没有Calculate()
方法。一个Square
可能有一个Piece
,一个Piece
可能有一个Square
(即一个正方形可能会也可能不会是空的,并且棋盘上可能已放置或未放置棋子)。Board
还有一个calculateScoreForTurn()
方法,该方法将接受代表该回合刚刚放置在棋盘上的棋子的Pieces
集合。棋盘
知道有关刚刚放置的棋子和方块的所有信息,以及周围或相交的棋子和方块(如果适用),因此拥有计算分数所需的所有信息。Something like the following might work:
CurrentGame
has aBoard
, which has a collection ofSquares
. ASquare
could have an IBonus, however there is noCalculate()
method on aSquare
. ASquare
may have aPiece
, and aPiece
may have aSquare
(ie a square may or may not be empty, and a piece may or may not have been placed on the board).Board
also has acalculateScoreForTurn()
method which would accept a collection ofPieces
representing the pieces that have just been placed on the board for that turn.Board
knows all the information about the pieces and squares that have just been placed, as well as the surrounding or intersecting pieces and squares (if applicable) and thus has all the information required to calculate the score.我觉得很有必要。您只是传递对板的引用,实际上并没有导致大量数据被移动。
I think it's necessary. You're just passing a reference to the board, not actually causing large quantities of data to be moved around.
董事会本身可能必须推动特定回合的得分。当放置每一块瓷砖时,董事会都会记录下来。当最后一个图块(一轮)被放置后,棋盘必须获得所有有新添加图块的方格(将计算这些方格的奖金)以及所有先前放置的当前回合为“的图块”重用”。
例如,玩 CAT
C AT
C 属于双字母分数。因此,本回合的得分为 C.Value*2 + A.Value + T.Value。
下一个玩家放置一个 S 来制作 CATS。 S 落在三重词分数上。因此,本回合的得分为(C.Value + A.Value + T.Value + S.Value)*3。应用图块的奖励后,必须将其“停用”,以便将来“重复使用”该图块时也不会获得该奖励。
这意味着一些奖励适用于放置在正方形中的图块,而其他奖励则应用于在计算出各个字母的奖励后组成新单词的图块集合。
给定一个或多个在回合中已填充图块的方格,棋盘可以通过向左移动直到棋盘边缘(或直到空方格)找到已创建的单词的开头并遍历直到达到相同的条件。董事会可以找到通过类似的向右和向下遍历创建的单词结尾。每次新放置的图块与现有图块相邻时,您还必须遍历单词的开头和结尾(您可以在一个回合中创建许多单词)。
给定一个单词集合(每个单词由一个包含可能的 LetterBonus 的 Square 和一个具有值的 Tile 组成),棋盘(或每个单词本身)计算 BaseValue(Tile 值的总和 - 应用任何 LetterBonuses),然后应用 WordBonus (如果有的话)以获得圣言的最终价值。
The Board itself will probably have to drive the scoring for a given round. As each Tile is placed, the Board makes note of it. When the last Tile (for a turn) has been placed, the Board must get all of the Squares that have a newly added tile (the Bonus for these Squares will be calculated) AND all of the previously placed Tiles that the current turn is "reusing".
For example, play CAT
C A T
C falls on Double Letter Score. So, the score for the turn is C.Value*2 + A.Value + T.Value.
Next player places an S to make CATS. S falls on Triple Word Score. So, the score for the turn is (C.Value + A.Value + T.Value + S.Value)*3. When a Tile's Bonus has been applied, it must be "Deactivated" so that future "reuses" of that Tile do not also get the Bonus.
The implication is that some Bonuses apply the Tile placed in the Square while others apply to the collection of Tiles that make up the new Word AFTER the Bonuses for the individual letters have been calculated.
Given one or more Squares that have been filled with Tile(s) during a turn, the Board can find the Start of the Word(s) that have been created by traversing left until the edge of the board (or until an empty Square) and traversing up until the same condition. The Board can find the End of the Word(s) that have been created by similarly traversing right and down. You also have to traverse to the Start and End of words each time a newly placed Tile is Adjacent to an existing Tile (you could create many words during a turn).
Given a collection of Words (each composed of a Square containing a possible LetterBonus and a Tile with a Value), the Board (or each Word itself) computes the BaseValue (Sum of Values of Tiles - applying any LetterBonuses) and then applies the WordBonus (if any) to get the ultimate value of the Word.