scg/ch13/core/Board

From FANG

Jump to: navigation, search

001 package scg.ch13.core;
002 
003 import java.awt.Color;
004 import java.util.ArrayList;
005 
006 import fang2.attributes.Location2D;
007 import fang2.core.Game;
008 import fang2.core.Sprite;
009 import fang2.sprites.CompositeSprite;
010 import fang2.sprites.RectangleSprite;
011 
012 import scg.ch13.pieces.Piece;
013 
014 /**
015  * The board for playing a game of BlockDrop (aka Tetris). The code here
016  * was inspired by Javier LÜÜpez's C++ Tetris Tutorial (one of the best
017  * simplifications I have seen)
018  * http://gametuto.com/tetris-tutorial-in-c-render-independent/<br />
019  * The board includes the background, the border, the visible squares,
020  * and an internal representation of the on-screen grid (for collision
021  * detection, line removal, and scoring). The board tracks its current
022  * piece. It is associated with a {@link ScoreSprite} (so that the score
023  * is automatically updated).<br />
024  * Assumption: rows > columns
025  */
026 
027 public class Board
028   extends CompositeSprite {
029   /** Constants for reporting whether a piece is in the wrong place */
030   public static final int CLEAR = 0;
031   public static final int HIT = 1;
032   public static final int HIGH = 2;
033   public static final int LOW = 3;
034   public static final int LEFT = 4;
035   public static final int RIGHT = 5;
036 
037   /** the default color of the main board */
038   public static final Color DEFAULT_BACKGROUND_COLOR = 
039     Game.getColor("misty rose");
040 
041   /** the default color of the border around the board */
042   public static final Color DEFAULT_EDGE_COLOR = 
043     Game.getColor("SCG Red");
044   
045  
046   /** display of the background */
047   private final RectangleSprite background;
048 
049   /** board grid; each location is either null or filled with a block */
050   private final ArrayList<ArrayList<RectangleSprite>> blocks;
051 
052   /** size in internal coordinates of one block edge */
053   private final double blockSize;
054 
055   /** width, in blocks, of the board */
056   private final int columns;
057 
058   /** the piece which is falling */
059   private Piece currentPiece;
060 
061   /** display of 3-sided border around board */
062   private final RectangleSprite edges;
063 
064   /** height, in blocks, of the board */
065   private final int rows;
066 
067   /** where removed row count is recorded */
068   private final ScoreSprite score;
069 
070   /** internal coordinate point of upper-left corner of board */
071   private final double ulcX;
072   private final double ulcY;
073 
074   /**
075    * Construct a new {@link Board} object. The score is a {@link
076    * ScoreSprite} where this board will report cleared rows. The row and
077    * column count are the size of the board. It is assumed that row >
078    * columns.
079    *
080    @param  score {@link ScoreSprite} where cleared rows are reported.
081    @param  rows     the number of rows high the {@link Board} should
082    *                  be; > columns
083    @param  columns  the number of columns wide the {@link Board}
084    *                  should be; < rows
085    */
086   public Board(ScoreSprite score, int rows, int columns{
087     this.score = score;
088     this.rows = rows;
089     this.columns = columns;
090 
091     blockSize = 1.0 / rows;
092 
093     // Show one square width on each of 3 sides
094     edges = new RectangleSprite((columns + 2* blockSize,
095         (rows + 1* blockSize);
096     edges.setLocation(0.0, blockSize / 2);
097     setEdgeColor(DEFAULT_EDGE_COLOR);
098     addSprite(edges);
099 
100     background = new RectangleSprite(blockSize * columns, 1.0);
101     setColor(DEFAULT_BACKGROUND_COLOR);
102     addSprite(background);
103 
104     ulcX = -blockSize / (columns - 1);
105     ulcY = -0.5 + (blockSize / 2);
106 
107     blocks = initBlocks();
108   }
109 
110   /**
111    * Add the given piece to the board as the currentPiece. The piece is
112    * rescaled and positioned according to its initial position.<br />
113    * Should only be called when currentPiece is no longer needed.
114    *
115    @param  currentPiece  the {@link Piece} to make current.
116    */
117   public void add(Piece currentPiece{
118     this.currentPiece = currentPiece;
119     currentPiece.setBoard(this);
120     currentPiece.setScale(currentPiece.getRowSize() * blockSize);
121     currentPiece.setRow(currentPiece.getInitialRow());
122     currentPiece.setColumn(currentPiece.getInitialColumn() +
123       (columns / 2));
124     addSprite(currentPiece);
125   }
126 
127   /**
128    * Can the given {@link Piece}, piece, occupy the given position (row,
129    * column) in the given rotation (facing) without colliding with
130    * existing squares (already frozen on the board) or the edges? The
131    * values row, column, and facing are provided so that any piece can
132    * be tested in any position.
133    *
134    @param   piece   the piece to test
135    @param   row     the row where the piece should be imagined
136    @param   column  the column where the piece should be imagined
137    @param   facing  the facing the piece is to be imagined to have
138    *
139    @return  CLEAR if safe to move; HIT, HIGH, LOW, LEFT, or RIGHT
140    *          otherwise
141    */
142   public int canMoveTo(Piece piece, int row, int column, int facing{
143     int canMove = CLEAR;
144     for (int = 0(canMove == CLEAR&& (!= piece.getRowSize());
145         ++r{
146       int boardRow = row + r;
147       for (int = 0;
148           (canMove == CLEAR&& (!= piece.getColumnSize())++c{
149         int boardCol = column + c;
150         if (piece.hasBlock(r, c, facing)) {
151           canMove = positionOnBoard(boardRow, boardCol);
152           if (canMove == CLEAR{
153             if (hasBlock(boardRow, boardCol)) {
154               canMove = HIT;
155             }
156           }
157         }
158       }
159     }
160     return canMove;
161   }
162 
163   /**
164    * Move the piece down until it freezes.
165    */
166   public void drop() {
167     while (moveDownIfPossible()) {
168       ;// don't do anything (empty loop)
169     }
170   }
171 
172   /**
173    * Get the edge size used by the board.
174    *
175    @return  the edge size
176    */
177   public double getBlockSize() {
178     return blockSize;
179   }
180 
181   /**
182    * Test whether or not the game is over.
183    *
184    @return  true if the game is over, false otherwise
185    */
186   public boolean isGameOver() {
187     boolean retval = false;
188     ArrayList<RectangleSprite> topRow = blocks.get(0);
189 
190     for (int = 0; c != columns; ++c{
191       retval = retval || (topRow.get(c!= null);
192     }
193     return retval;
194   }
195 
196   /**
197    * Attempt to move the current piece down one grid location. If it
198    * cannot move down, freeze the piece in place, remove all filled
199    * rows, and update score.
200    *
201    @return  true if the piece moved down one row; false if it was
202    *          frozen
203    */
204   public boolean moveDownIfPossible() {
205     if (currentPiece.canMoveDown() == CLEAR{
206       currentPiece.moveDown();
207       return true;
208     } else if (contains(currentPiece)) {
209       freeze(currentPiece);
210       int moveScore = deleteAllFilledRows();
211       if (score != null{
212         score.increment(moveScore);
213       }
214       return false;
215     }
216     return false;
217   }
218 
219   /**
220    * Attempt to move the current piece left one column. Leave it
221    * unchanged if it could not move.
222    *
223    @return  true if the piece was moved; false otherwise
224    */
225   public boolean moveLeftIfPossible() {
226     if (currentPiece.canMoveLeft() == CLEAR{
227       currentPiece.moveLeft();
228       return true;
229     }
230     return false;
231   }
232 
233   /**
234    * Attempt to move the current piece right one column. Leave it
235    * unchanged if it was blocked.
236    *
237    @return  true if the piece was moved; false otherwise
238    */
239   public boolean moveRightIfPossible() {
240     if (currentPiece.canMoveRight() == CLEAR{
241       currentPiece.moveRight();
242       return true;
243     }
244     return false;
245   }
246 
247   /**
248    * Rotate the current piece anti-clockwise, if possible. If rotation
249    * would put the piece into the edge of the board or an adjacent
250    * piece, "bump" the piece over to make it clear the edge. Note that
251    * that it might not be possible to clear the obstacle (think narrow
252    * piece in a narrow "alley"). If it cannot clear it will hit
253    * something on the other side. If that happens, reset everything and
254    * get out. Otherwise do the rotation.
255    *
256    @return  true if the piece was rotated; false otherwise
257    */
258   public boolean rotateCCWIfPossible() {
259     int startColumn = currentPiece.getColumn();
260     if (currentPiece.canRotateCCW() == LEFT{
261       while (currentPiece.canRotateCCW() == LEFT{
262         currentPiece.moveRight();
263       }
264       if (currentPiece.canRotateCCW() != CLEAR{
265         currentPiece.setColumn(startColumn);
266       }
267     } else if (currentPiece.canRotateCCW() == RIGHT{
268       while (currentPiece.canRotateCCW() == RIGHT{
269         currentPiece.moveLeft();
270       }
271       if (currentPiece.canRotateCCW() != CLEAR{
272         currentPiece.setColumn(startColumn);
273       }
274     }
275 
276     if (currentPiece.canRotateCCW() == CLEAR{
277       currentPiece.rotateCCW();
278       return true;
279     }
280     return false;
281   }
282 
283   /**
284    Set the color (the background color) of this sprite. Caught here to
285    * set background, too.
286    *
287    @param  color  the color to set
288    */
289   @Override
290   public void setColor(Color color{
291     super.setColor(color);
292     background.setColor(color);
293   }
294 
295   /**
296    Set the color of edge, the edge box around the piece box.
297    *
298    @param  color  the color to set
299    */
300   public void setEdgeColor(Color color{
301     edges.setColor(color);
302   }
303 
304   /**
305    Set the given piece's location based on its current board location.
306    * Note that this takes into account the size of the {@link Piece} (in
307    * rows and columns)
308    *
309    @param  piece  the {@link Piece} to position
310    */
311   public void setPieceLocation(Piece piece{
312     piece.setLocation((blockSize * (piece.getColumn() + 2)) + ulcX,
313       (blockSize * (piece.getRow() + 2)) + ulcY);
314   }
315 
316   /**
317    * Steal a block for the given position. Modify the scale and location
318    * (since it will be in our space rather than in the {@link Piece}'s
319    * space) and return a reference to the block.
320    *
321    @param   block        the block to steal from the {@link Piece}
322    @param   blockRow     board row of block
323    @param   blockColumn  board column of block to create
324    *
325    @return  reference to the new {@link RectangleSprite}
326    */
327   private RectangleSprite acquireBlock(RectangleSprite block,
328     int blockRow, int blockColumn{
329     block.setScale(blockSize);
330     block.setLocation(locationFromRowColumn(blockRow, blockColumn));
331     return block;
332   }
333 
334   /**
335    * Traverse the board from top to bottom. When a full row is found,
336    * remove the row. This moves all rows down one. This is safe (we will
337    * still check all the rows) because immediately the row index is
338    * incremented to the NEXT ROW after the part that was changed by the
339    * delete operation. The method counts the number of rows removed (so
340    * that score can be kept).
341    *
342    @return  the number of rows actually deleted. Should be on the
343    *          range 0-4.
344    */
345   private int deleteAllFilledRows() {
346     int rowsDeleted = 0;
347     for (int = 0; r != rows; ++r{
348       if (isRowFilled(r)) {
349         deleteRow(r);
350         ++rowsDeleted;
351       }
352     }
353     return rowsDeleted;
354   }
355 
356   /**
357    * Move all rows above deadRowNdx down one row. Insert a new row 0.
358    *
359    @param  deadRowNdx  the index of the row to remove
360    */
361   private void deleteRow(int deadRowNdx{
362     removeRowOfSprites(blocks.get(deadRowNdx));
363     for (int = deadRowNdx; r != 0--r{// counts backwards!
364       blocks.set(r, blocks.get(r - 1));// (r - 1) >= 0
365       ArrayList<RectangleSprite> currRow = blocks.get(r);
366       for (int = 0; c != columns; ++c{
367         if (currRow.get(c!= null{
368           currRow.get(c).setLocation(locationFromRowColumn(r, c));
369         }
370       }
371     }
372     // add a new, empty row at the top.
373     blocks.set(0, initRow());
374   }
375 
376   /**
377    Lock the piece down at its current location.
378    *
379    @param  piece  typically the current piece; the piece to freeze
380    */
381   private void freeze(Piece piece{
382     for (int = 0; r != piece.getRowSize()++r{
383       int boardRow = piece.getRow() + r;
384       for (int = 0; c != piece.getColumnSize()++c{
385         int boardCol = piece.getColumn() + c;
386         if ((piece.hasBlock(r, c)) &&
387             (positionOnBoard(boardRow, boardCol== CLEAR)) {
388           RectangleSprite block = acquireBlock(piece.blockAt(r, c),
389               boardRow, boardCol);
390           blocks.get(boardRow).set(boardCol, block);
391           addSprite(block);
392         }
393       }
394     }
395     removeSprite(piece);// remove the CompositeSprite
396   }
397 
398   /**
399    * Is the given position on the board full?
400    *
401    @param   row     row of position to check
402    @param   column  column of position to check
403    *
404    @return  true if position is full (!empty); false otherwise
405    */
406   private boolean hasBlock(int row, int column{
407     return (blocks.get(row).get(column!= null);
408   }
409 
410   /**
411    * Initialize the positions: all positions are initially empty; set to
412    null. When full they will hold a reference to the {@link
413    * RectangleSprite} in that location. The returned data structure is a
414    * 2D list of lists. Number of lists in this list is the number of
415    * rows. The
416    */
417   private ArrayList<ArrayList<RectangleSprite>> initBlocks() {
418     ArrayList<ArrayList<RectangleSprite>> allPositions =
419       new ArrayList<ArrayList<RectangleSprite>>();
420     for (int = 0; r != rows; ++r{
421       allPositions.add(initRow());
422     }
423     return allPositions;
424   }
425 
426   /**
427    * Generate one row of positions. One entry per column, each
428    * initialized to null.
429    *
430    @return  reference to the new row of positions
431    */
432   private ArrayList<RectangleSprite> initRow() {
433     ArrayList<RectangleSprite> nextRow =
434       new ArrayList<RectangleSprite>();
435     for (int = 0; c != columns; ++c{
436       nextRow.add(null);
437     }
438     return nextRow;
439   }
440 
441   /**
442    * Is the given row full of blocks?
443    *
444    @param   rowNdx  the index into {@link #blocks} of the row to check
445    *
446    @return  true if row is filled with blocks; false otherwise
447    */
448   private boolean isRowFilled(int rowNdx{
449     ArrayList<RectangleSprite> currRow = blocks.get(rowNdx);
450     int = 0;
451     for (= 0; c != columns; ++c{
452       if (currRow.get(c== null{
453         break;// get out of the loop
454       }
455     }
456     return (== columns);// did we finish the loop?
457   }
458 
459   /**
460    * Calculate the {@link Sprite} location for a {@link Piece} at the
461    * given board position Works on individual blocks rather than {@link
462    * Piece} objects
463    *
464    @param   row     board row
465    @param   column  board column
466    *
467    @return  {@link Location2D} where the {@link Piece} must be
468    *          positioned
469    */
470   private Location2D locationFromRowColumn(int row, int column{
471     return new Location2D(ulcX + (blockSize * column),
472         ulcY + (blockSize * row));
473   }
474 
475   /**
476    * Is the given location on the board? If not, how did it miss?
477    *
478    @param   row     position row
479    @param   column  position column
480    *
481    @return  CLEAR if it is on the board; HIGH, LOW, LEFT, or RIGHT to
482    *          indicate one way it missed otherwise
483    */
484   private int positionOnBoard(int row, int column{
485     int retval = CLEAR;
486     if (row < 0{
487       retval = HIGH;
488     }
489     if (rows <= row{
490       retval = LOW;
491     }
492     if (column < 0{
493       retval = LEFT;
494     }
495     if (columns <= column{
496       retval = RIGHT;
497     }
498     return retval;
499   }
500 
501   /**
502    * Remove the sprites in the deadRow from the display of the {@link
503    * Board}
504    *
505    @param  deadRow  the row of sprites (and, perhaps, nulls) to remove
506    */
507   private void removeRowOfSprites(ArrayList<RectangleSprite> deadRow{
508     for (int = 0; c != columns; ++c{
509       if (deadRow.get(c!= null{
510         this.removeSprite(deadRow.get(c));
511       }
512     }
513   }
514 }
515 
516 //Uploaded on Mon Mar 29 21:39:46 EDT 2010


Download/View scg/ch13/core/Board.java





Views
Personal tools
Add to 
del.icio.usAdd to 
diggAdd to 
FacebookAdd to 
favoritesAdd to 
GoogleAdd to 
MySpaceAdd to 
PrintAdd to 
SlashdotAdd to 
StumbleUponAdd to 
Twitter

Games
Games