views:

51

answers:

4

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!

alt text

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? alt text

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

+4  A: 

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

Tony Ennis
I agree; if you want the most flexibility to design crazy bonuses you need the most information available.
Ron Warholic
+1  A: 

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.

jwaddell
But how do you handle different kinds of bonus, then? Shouldn't the logic of each kind of different bonus be encapsulated in a different class?
devoured elysium
It's probably not ideal from an OO design point of view, but to me it seems better for all the "rules logic" to exist in a single place than having an IBonus object (which concerns a single tile) perform work which concerns the whole Board. You could maybe have a separate Scorer object, so you pass it the Board and a collection of Pieces just placed, and it returns the score for that turn. It seems appropriate that a Scorer know all the rules.
jwaddell
I think the fact that a particular IBonus could affect more than just the particular Piece it is associated with (including pieces placed on previous turns) means you shouldn't encapsulate its scoring method in its Square.
jwaddell
+1  A: 

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.

mootinator
+1  A: 

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.

wageoghe