I am working on coding a Tetris clone in XNA C# and am unsure of the best way to approach the data structure side of the game on a high level.
I am totally fine with the collision detection, rotations, animation etc. What I need to know the best way to do the storing of "dropped blocks" - ie blocks that are no longer under tha player's control.
I think that each Tetromino block should be stored in its own class that consists of a 4x4 array so that the block can easily be rotated. The problem is then how to I store the tetromino's final position into the game grid by then cutting the tetromino up into individual blocks(for each cell) and then set the main game grid's corresponding positions to hold these same blocks, then disappearing the tetromino once it has reached its final position. Maybe there is some drawback to my method.
Should I create a 10x20 matrix for the main game grid which can then store? or should I use stacks or queues to somehow store the dropped blocks. Or maybe there is some better method/data structure to store things?
I am sure my way would work, but I am reaching out to see if anybody knows a better way or if my way is good enough?
P.S. Not homework, this will be a project for my portfolio. Thanks.
Once a block is immobile, there's nothing that distinguishes it from any other block that is now immobile. In that regard, I think it makes the most sense to store the entire grid as a matrix, where each square is either filled or not (along with the color of the block if it is).
I feel like the matrix has many advantages. It'll make collision detection simple (no having to compare with multiple objects, just locations on a matrix). Storing it as a matrix will also make it easier to determine when a full line has been created. On top of that, you don't have to worry about splicing an immobile Tetromino when a line disappears. And when one does, you can just shift the entire matrix down in one fell swoop.
This smells like homework, but my take on an object-oriented approach to Tetris would be to have each individual square be an object, and both "blocks" (tetrominos) and the grid itself would be collections of the same square objects.
Block objects manage the rotation and position of the falling squares, and the grid handles displaying them and detroying completed rows. Each block would have a colour or texture associated with it that would be provided by the original block object it came from, but otherwise squares at the base of the grid would have no other indication that they were ever part of the same original block.
To elaborate, when you create a new block object, it creates a set of 4 squares with the same colour/texture on the grid. The grid manages their display. So when the block hits the bottom, you just forget about the block, and the squares remain referenced by the grid.
Rotations and dropping are operations only a block need deal with, and only one its four squares (though it will need to be able to query the grid to make sure the rotation can fit).
Not making blocks actually look like autonomous blocks is - in my opinion - a big failing of many a Tetris clone. I put special effort into ensuring my clone always looked right, whether the block is still "in play" or dropped. This meant going slightly beyond the simple matrix data structure and coming up with something that supported the concept of "connection" between block parts.
I had a class called BlockGrid that is used as a base class for both a Block and the Board. BlockGrid has an abstract (pure virtual in C++) method called AreBlockPartsSameBlock that subclasses must override to determine whether two different block parts belong to the same block. For the implementation in Block, it simply returns true if there are block parts at both locations. For the implementation in Board, it returns true if both locations contain the same Block.
The BlockGrid class uses this information to "fill in" the details in the rendered blocks, so that they actually look like blocks.
Using arrays would be the easiest way to handle tetris. There is a direct correlation between what you see on screen and the structures used in memory. Using stack/queues would be an overkill and unnecessarily complicated.
You can have 2 copies of a falling block. One will be for display (Alpha) and the other one will be movement (Beta).
You will need a structure like
class FallingBlock
{
int pos_grid_x;
int pos_grid_y;
int blocks_alpha[4][4];
int blocks_beta[4][4];
function movedDown();
function rotate(int direction();
function checkCollision();
function revertToAlpha();
function copyToBeta()
};
The _beta array would be move or rotated and checked against the board for collisions. If there is a collision, revert it to _alpha, if not, copy _beta onto _alpha.
And if there is a collision on movedDown(), the block's life is over and the _alpha grid would have to copied onto the game board and the FallingBlock object deleted.
The board would of course have to be another structure like:
class Board
{
int gameBoard[10][20];
//some functions go here
}
I used int to represent a block, each value (like 1,2,3) representing a different texture or color (0 would mean an empty spot).
Once the block is part of the gameboard, it would only need a texture/color identifier to be displayed.
I actually just did this a few days ago except in WPF rather than XNA. Here's what I did:
Edit:
Seems like I define "Block" differently than other people. What I define as a Block is one of 4 cells that make up a Tetromino, and an actual Tetromino itself as a Piece.
Have a Block as a struct that had X, Y coordinates and Color. (I later added a bool IsSet to indicate whether it was in a floating piece or on the actual board, but that was just because I wanted to distinguish them visually)
As methods on Block, I had Left, Right, Down, and Rotate(Block center) which returned a new shifted Block. This allowed me to rotate or move any piece without knowing the shape or orientation of the piece.
I had a generic Piece object that had a List of all the blocks it contained and the index of the Block that was the center, which is used as the center of rotation.
I then made a PieceFactory that could produce all the different pieces, and with a Piece not needing to know what kind of piece it was, I could (and did) easily add variation of Pieces consisting of more or less than 4 Blocks without needing to create any new classes
The Board consisted of a Dictionary which was all the blocks that were currently on the board, as well as the dimensions of the board that was configurable. You can use a Matrix just as well probably, but with a Dictionary I only needed to iterate through Blocks without white spaces.
My Solution (design), with examples in Python as a good substitute for pseudo code.
Use a grid 20 x 10, that the tetrominoes fall down.
Tetrominoes are made up of blocks, which have attributes of coordinate (x,y) and colour.
So, for example, the T-shape tetrominoe looks like this...
. 4 5 6 7 8 .
.
19 # # #
20 #
.
Thus, the T-shape is a collection of blocks with the coords (5,19), (6,19), (7,19), (6,20).
Moving the shape is a matter of applying a simple transformation to all the coords in the group. e.g. to move the shape down add (0,1), left (-1,0) or right (1,0) to all coords in the collection that make the shape.
This also lets you use some simple trig to rotate the shape by 90-degrees. The rule is that when rotating 90-degrees relative to an origin, then (x,y) becomes equal to (-y,x).
Here is an example to explain it. Taking the T-shape from above, use the (6,19) as the centre block to rotate around. For simplicity, make this the first coordinate in the collection, so...
t_shape = [ [6,19], [5,19], [7,19], [6,20] ]
Then, here is a simple function to rotate that collection of coordinates by 90-degrees
def rotate( shape ):
X=0 # for selecting the X and Y coords
Y=1
# get the middle block
middle = shape[0]
# work out the coordinates of the other blocks relative to the
# middle block
rel = []
for coords in shape:
rel.append( [ coords[X]-middle[X], coords[Y]-middle[Y] ] )
# now rotate 90-degrees; x,y = -y, x
new_shape = []
for coords in rel:
new_shape.append( [ middle[X]-coords[Y], middle[Y]+coords[X] ] )
return new_shape
Now, if you apply this function to our collection of coordinate for the T-shape...
new_t_shape = rotate( t_shape )
new_t_shape
[[6, 19], [6, 18], [6, 20], [5, 19]]
Plot this out in the coordinate system and it looks like this...
. 4 5 6 7 8 .
.
18 #
19 # #
20 #
.
That was the hardest bit for me, hope this helps someone.
Keep in mind that a previous winner of the Obfuscated C Code Contest implemented a pretty good tetris game (for VT100 terminals on BSD unix) in fewer than 512 bytes of obfuscated C:
long h[4];t(){h[3]-=h[3]/3000;setitimer(0,h,0);}c,d,l,v[]={(int)t,0,2},w,s,I,K
=0,i=276,j,k,q[276],Q[276],*n=q,*m,x=17,f[]={7,-13,-12,1,8,-11,-12,-1,9,-1,1,
12,3,-13,-12,-1,12,-1,11,1,15,-1,13,1,18,-1,1,2,0,-12,-1,11,1,-12,1,13,10,-12,
1,12,11,-12,-1,1,2,-12,-1,12,13,-12,12,13,14,-11,-1,1,4,-13,-12,12,16,-11,-12,
12,17,-13,1,-1,5,-12,12,11,6,-12,12,24};u(){for(i=11;++i<264;)if((k=q[i])-Q[i]
){Q[i]=k;if(i-++I||i%12<1)printf("\033[%d;%dH",(I=i)/12,i%12*2+28);printf(
"\033[%dm "+(K-k?0:5),k);K=k;}Q[263]=c=getchar();}G(b){for(i=4;i--;)if(q[i?b+
n[i]:b])return 0;return 1;}g(b){for(i=4;i--;q[i?x+n[i]:x]=b);}main(C,V,a)char*
*V,*a;{h[3]=1000000/(l=C>1?atoi(V[1]):2);for(a=C>2?V[2]:"jkl pq";i;i--)*n++=i<
25||i%12<2?7:0;srand(getpid());system("stty cbreak -echo stop u");sigvec(14,v,
0);t();puts("\033[H\033[J");for(n=f+rand()%7*4;;g(7),u(),g(0)){if(c<0){if(G(x+
12))x+=12;else{g(7);++w;for(j=0;j<252;j=12*(j/12+1))for(;q[++j];)if(j%12==10){
for(;j%12;q[j--]=0);u();for(;--j;q[j+12]=q[j]);u();}n=f+rand()%7*4;G(x=17)||(c
=a[5]);}}if(c==*a)G(--x)||++x;if(c==a[1])n=f+4**(m=n),G(x)||(n=m);if(c==a[2])G
(++x)||--x;if(c==a[3])for(;G(x+12);++w)x+=12;if(c==a[4]||c==a[5]){s=sigblock(
8192);printf("\033[H\033[J\033[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]=
0);while(getchar()-a[4]);puts("\033[H\033[J\033[7m");sigsetmask(s);}}d=popen(
"stty -cbreak echo stop \023;cat - HI|sort -rn|head -20>/tmp/$$;mv /tmp/$$ HI\
;cat HI","w");fprintf(d,"%4d on level %1d by %s\n",w,l,getlogin());pclose(d);}
http://www.ioccc.org/1989/tromp.hint
I'm by no means a Tetris expert, but as you described a 10x20 matrix seems like a natural choice to me.
It will make it very easy when the time comes to check if you have completed a line or not, and dealing with it. Simply iterating over the 2d-array looking at boolean values of each position to see if they add up to 10 block positions.
However, you'll have some manual clean up to do if there is a completed line. Having to shift everything down. All though it isn't that big of a deal when it comes down to it.
Using Simon Peverett logic, here is what I ended up with in c#
public class Tetromino
{
// Block is composed of a Point called Position and the color
public Block[] Blocks { get; protected internal set; }
// Constructors, etc.
// Rotate the tetromino by 90 degrees, clock-wise
public void Rotate()
{
Point middle = Blocks[0].Position;
List<Point> rel = new List<Point>();
foreach (Block b in Blocks)
rel.Add(new Point(b.Position.x - middle.x, b.Position.y - middle.y));
List<Block> shape = new List<Block>();
foreach (Point p in rel)
shape.Add(new Block(middle.x - p.y, middle.y + p.x));
Blocks = shape.ToArray();
}
public void Translate(Point p)
{
// Block Translation: Position+= p;
foreach (Block b in Blocks)
b.Translate(p);
}
}
Note: Using XNA, Point structure could be swapped for Vector2D
in my example (Java) - all figures have lists of blocks - which can be removed whenever needed. Also in my Board class I have a list of figures and a field variable figure - which is controlled by the user. When the figure is "landed" - it goes into the list of other figures, and a new figure is made controllable by the user.
A better explanation here: http://bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/
Related
Ok, so I have a 2D array of objects that are represented in a 2D space with columns and raws translated into (x,y) points (Cartesian coordinate system).
The problem began when I wanted to get access from one object to their neighbours.
I immediately thought that I can get access to them by looking i,j from an iteration into the 2D array like this:
(i,j+1)
↑
(i-1,j) ← (i,j) → (i+1,j)
↓
(i,j-1)
Everything was fine until my grid changed shape into a non-rectangle shape.
Then a java guy told me that I should use a custom data structure to hold the references of my objects that is literally a Double Link List with not only next and previous pointers but instead with top , down, right and left pointers. That I shouldn't write code like this for a regular array, and to use a proper data structure that is going to give me more power and flexibility on how to insert, delete, initialize and many other functions for my objects.
For some reason, I thought he is right.
In this way, I think that I can create more complex functions like getting all objects in a radius from one point. This is for game dev purposes and latter on algorithms like A* for pathfinding are going to be developed and run on this structure.
So my question is:
Is any build-in structure in c# that can help me with this or if not, should I try to create one custom structure from scratch or try to extend-inherit an already build-in c# structure.
What is the proper terminology for that structure? Maxtrix Link List? Multiway Link List?
P.S. I avoided posting code because it's from a custom game engine that needs a lot of explanation.
just an idea...
I would develop a class (i.e. Space) holding a simple list of another custom class (i.e. Point).
The master class could have some properties defining space shape and extension, and can implement a series of question method (Point GetNearest(Point p), List<Point> GetPointsInRadius(int center, int radius), ...) each of them iterating throw the list of points, and a set of add/delete/move Points methods
Your Point class have a list of properties( int x, int y, Point top, Point down, Point left, Point right) and a series of method (int DistanceFrom(Point p), bool IncludedIn(int center, int radius), int DistanceFromLeft/Right/Top/Down(), ... and so on
I trying to make a game where player only move forward in an infinity map, and the path (just thing of them like points, the path is only the visual) is procedurally generated. I want those path to have different length (something like the tree of life, but only branches of the selected path are generated).
This is how I generate branches without overlap:
List<Vector3> everyPos; //predetermined position
public void Spawn(int amount)
{
List<Vector3> possiblePos = new List<Vector3>(everyPos);
for (int i = 0; i < amount; i++)
{
int index = Random(0, possiblePos.Count); //Find a random position
SpawnObjectAt(currentPosition+possiblePos[index]));//Create a point there
possiblePos.RemoveAt(index); //Remove that position from the list
}
}
The problem is , look at this image(I can't embed image yet):
Red is where player start, green is possible spawn position in the first move.
If there are 2 point spawned at 1 and 2, player choose point1, then the possible position in the second time will be a point in the black zone, which include point2, so if I keep continue there will eventually overlap.
How can I avoid this? I'm making a mobile game so I don't want to cache every single point. Any help would be really appreciated! Thanks!
This is a small web game that have somewhat similar mechanic to what I trying to achieve: newgrounds.com/portal/view/592325/
This is an attempt here to answer, but honestly, you need to provide more information.
Depending on the language you are writing in, you can handle this differently. You may need dynamic allocation, but for now lets assume, since your idea is quite small, that you can just do one large array predefined before compile time.
I assume you know how to make an array, so create one with say, 500 length to start. If you want to 'generate' a link like they did in that game, you simply need a random function, (there is a built in library in pretty much every language I think) and you need to do a little math.
Whatever language you use will surely have a built in graphics library, or you can use a popular easy to use one. I'll just draw a picture to make this clear.
There are a number of ways you can do this mathematically as shown in the image, using angles for example, the simplest way, however, is just to follow the boxes.
If you have worked with graphics before, you know what a vector is, if not, you will need to learn. The 9 vectors presented in this image (0,1) (1,0) (1,1) etc. can be created as vector objects, or even stored as individual ints.
To make your nodes 'move' into another path, you can simply do a rand 1-9 and then correlated the result to one of 9 possible vectors, and then add them to your position vector. It is easiest to do this in array and just use the rand int as the index. In most c derived languages you do that like this:
positionVector += changeVectorArray[rand(1,9)];
You then increment your position vector by one of the 9 vectors as shown above.
The simplest way of making the 'path' is to copy the position before you add the change vector, and then store all of the changes sequentially in another 'path' array.
To show the path on screen, simply draw a line between the first and second, second and third, third and forth elements of your path array. This formula (of joining lines) is discrete mathematics if I'm not mistaken, and you can do much more complicated path shapes if you want, but you get the gist.
That should at least start you off. Without more info I can't really help you.
I could go off on a tangent describe a bunch of different ways you can make this happen differently but its probably easier if you just ask for specifics.
EDIT>>>
Continuing with this answer, yes, looking at it now, the nodes can definitely overlap. To solve this problem you could use collision detection, every time you generate a new 'position', before adding it and drawing the line you have to loop through your array like this:
boolean copy = true;
for(int i = 0; i < getLength(pathArray); i++){
if( newVector == pathArray[i]){
copy=false;
}
}
Then of course, if copy still is true, copy the new position int the pathArray. NOTE: this whole solution is sloppy as hell, and as your array gets larger, your program is going to take longer and longer to search through that loop. This may not also guarantee that the path goes in one direction, but it is likely. And note that the lines will still be able to overlap each other, even though the position vectors can't be on top of one another.
All this considered, I think it will work, the optimization is up to you. I would suggest that there is probably a much more efficient solution using a discrete formula. You can also use such a formula to make the path go in particular directions and do other more complicated things.
You could also quite easily apply constraints on your random rolls if you want to make the path go in a particular direction. But there are so many ways of doing this I can't begin to explain. You could google path-finding algorithms for that.
Good luck.
I'm trying to rotate a polygon in windows forms using C# and below is code written .
Please tell me what wrong in the code, there is no output on windows form.
Before and after rotation polygons not visible.
public void RotatePolygon(PaintEventArgs e)
{
pp[0] = new Point(624, 414);
pp[1] = new Point(614, 416);
pp[2] = new Point(626, 404);
e.Graphics.DrawPolygon(myPen2, pp);
System.Drawing.Drawing2D.Matrix myMatrix1 = new System.Drawing.Drawing2D.Matrix();
myMatrix.Rotate(45, MatrixOrder.Append);
myMatrix.TransformPoints(pp);
e.Graphics.Transform = myMatrix1;
e.Graphics.DrawPolygon(myPen, pp);
}
Thanks
You code does not compile if left unmodified. There are two matrices used - one declared in your method (myMatrix1) attached to the graphics object and one declared outside your method (myMatrix without the 1) used to explicitly transform the point array.
I tried the code with the required changes and it works flawless - I used myMatrix1 for both transformations and the effective rotation angle was, as expected, twice the one specified. So I guess you are using two transformation that cancel if the transformed points end where they began.
There could be this problems:
[1] your pens have no color/thickness (where do you define them?)
[2] your polygone is to big, so you only see the inside of it but not the border. --> Test Graphics.FillPolygon-Methode so you will see if [2] is right
You're both transforming the points and changing the transform matrix for the Graphics object - you need to do one or the other.
You also need to think about the fact that the rotate is going to be happing about (0,0) rather than about some part of the object - you may well need a translate in there too.
Bear in mind that TransformPoints just manipulates some numbers in an array - which you can easily inspect with the debugger - this will be a more effective technique than displaying an invisible object and wondering where it went.
Starting with a much smaller rotation angle (10 deg, perhaps?) may also help with the problem of losing the object - it will be easier to work out what's happening if you haven't moved so far.
I am making a turn based hex-grid game. The player selects units and moves them across the hex grid. Each tile in the grid is of a particular terrain type (eg desert, hills, mountains, etc) and each unit type has different abilities when it comes to moving over the terrain (e.g. some can move over mountains easily, some with difficulty and some not at all).
Each unit has a movement value and each tile takes a certain amount of movement based on its terrain type and the unit type. E.g it costs a tank 1 to move over desert, 4 over swamp and cant move at all over mountains. Where as a flying unit moves over everything at a cost of 1.
The issue I have is that when a unit is selected, I want to highlight an area around it showing where it can move, this means working out all the possible paths through the surrounding hexes, how much movement each path will take and lighting up the tiles based on that information.
I got this working with a recursive function and found it took too long to calculate, I moved the function into a thread so that it didn't block the game but still it takes around 2 seconds for the thread to calculate the moveable area for a unit with a move of 8.
Its over a million recursions which obviously is problematic.
I'm wondering if anyone has an clever ideas on how I can optimize this problem.
Here's the recursive function I'm currently using (its C# btw):
private void CalcMoveGridRecursive(int nCenterIndex, int nMoveRemaining)
{
//List of the 6 tiles adjacent to the center tile
int[] anAdjacentTiles = m_ThreadData.m_aHexData[nCenterIndex].m_anAdjacentTiles;
foreach(int tileIndex in anAdjacentTiles)
{
//make sure this adjacent tile exists
if(tileIndex == -1)
continue;
//How much would it cost the unit to move onto this adjacent tile
int nMoveCost = m_ThreadData.m_anTerrainMoveCost[(int)m_ThreadData.m_aHexData[tileIndex].m_eTileType];
if(nMoveCost != -1 && nMoveCost <= nMoveRemaining)
{
//Make sure the adjacent tile isnt already in our list.
if(!m_ThreadData.m_lPassableTiles.Contains(tileIndex))
m_ThreadData.m_lPassableTiles.Add(tileIndex);
//Now check the 6 tiles surrounding the adjacent tile we just checked (it becomes the new center).
CalcMoveGridRecursive(tileIndex, nMoveRemaining - nMoveCost);
}
}
}
At the end of the recursion, m_lPassableTiles contains a list of the indexes of all the tiles that the unit can possibly reach and they are made to glow.
This all works, it just takes too long. Does anyone know a better approach to this?
As you know, with recursive functions you want to make the problem as simple as possible. This still looks like it's trying to bite off too much at once. A couple thoughts:
Try using a HashSet structure to store m_lPassableTiles? You could avoid that Contains condition this way, which is generally an expensive operation.
I haven't tested the logic of this in my head too thoroughly, but could you set a base case before the foreach loop? Namely, that nMoveRemaining == 0?
Without knowing how your program is designed internally, I would expect m_anAdjacentTiles to contain only existing tiles anyway, so you could eliminate that check (tileIndex == -1). Not a huge performance boost, but makes your code simpler.
By the way, I think games which do this, like Civilization V, only calculate movement costs as the user suggests intention to move the unit to a certain spot. In other words, you choose a tile, and it shows how many moves it will take. This is a much more efficient operation.
Of course, when you move a unit, surrounding land is revealed -- but I think it only reveals land as far as the unit can move in one "turn," then more is revealed as it moves. If you choose to move several turns into unknown territory, you better watch it carefully or take it one turn at a time. :)
(Later...)
... wait, a million recursions? Yeah, I suppose that's the right math: 6^8 (8 being the movements available) -- but is your grid really that large? 1000x1000? How many tiles away can that unit actually traverse? Maybe 4 or 5 on average in any given direction, assuming different terrain types?
Correct me if I'm wrong (as I don't know your underlying design), but I think there's some overlap going on... major overlap. It's checking adjacent tiles of adjacent tiles already checked. I think the only thing saving you from infinite recursion is checking the moves remaining.
When a tile is added to m_lPassableTiles, remove it from any list of adjacent tiles received into your function. You're kind of doing something similar in your line with Contains... what if you annexed that if statement to include your recursive call? That should cut your recursive calls down from a million+ to... thousands at most, I imagine.
Thanks for the input everyone. I solved this by replacing the Recursive function with Dijkstra's Algorithm and it works perfectly.
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.