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
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
Something like the following might work:
CurrentGame
has a Board
, which has a collection of Squares
. A Square
could have an IBonus, however there is no Calculate()
method on a Square
. A Square
may have a Piece
, and a Piece
may have a Square
(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 a calculateScoreForTurn()
method which would accept a collection of Pieces
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.
This strikes me as bad as basically it needs to know a whole lot of information
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.
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.
精彩评论