views:

458

answers:

4

You are writing a Tetris program in Java. How would you set up your class design with regards to the following aspects?

  • Piece class: Have one Piece class, with an internal array which determines the shape of the piece, versus having seven Piece classes, one for each of the pieces. They are all subclasses of one generic Piece class.
  • Piece class representation: Have an array of 4 instances of Block, representing one square of a piece, and each Block contains its location on the Board (in graphical coordinates) vs. having a 4x4 array where null means there is no block there, and location is determined by the shape of the array.
  • Location: Each Block in the Piece array or on the Board array stores its location vs. the Piece and the Board know the locations of the Blocks that comprise them.
  • Generating a Piece: Have a static method of the Piece class getRandomPiece, or have a PieceFactory which you make one instance of that has the genRandomPiece method on the instance.
  • Manipulating the current piece: Use the Proxy pattern, so that everything that needs access to it just uses the proxy, or have a getCurrentPiece method on the Board class and call that any time you want to do something with the current piece.

This is not homework. I'm just at odds with what the intro CS course teaches at my college and I want to see what people in general believe. What would be thought of as "good" OOP design? Ignore the fact that it's for an intro course - how would you do it?

+3  A: 
  • One Piece interface, with seven classes that implement that interface for the individual pieces (which would also enable the OOP course to discuss interfaces) (EDIT: One Piece class. See comments)
  • I would have a BlockGrid class that can be used for any map of blocks - both the board, and the individual pieces. BlockGrid should have methods to detect intersections - for example, boolean intersects(Block block2, Point location) - as well as to rotate a grid (interesting discussion point for the course: If the Board doesn't need to rotate, should a rotate() method be in BlockGrid?). For a Piece, BlockGrid would represent be a 4x4 grid.
  • I would create a PieceFactory with a method getRandomShape() to get an instance of one of the seven shapes
  • For manipulating the piece, I'd get into a Model-View-Controller architecture. The Model is the Piece. The Controller is perhaps a PieceController, and would also allow or disallow legal/illegal moves. The thing that would show the Piece on the screen is a PieceView (hrm, or is it a BlockGridView that can show Piece.getBlockGrid()? Another discussion point!)

There are multiple legitimate ways to architect this. It would benefit the course to have discussions on the pro's and con's of different OOP principles applied to the problem. In fact, it might be interesting to compare and contrast this with a non-OOP implementation that just uses arrays to represent the board and pieces.

EDIT: Claudiu helped me realize that the BlockGrid would sufficiently differentiate pieces, so there is no need for a Piece interface with multiple subclasses; rather, an instance of a Piece class could differ from other instances based on its BlockGrid.

David
+1 for the idea of comparing and contrasting w/ a non-OO solution instead of just insisting on OO design without any serious discussion of the tradeoffs involved.
dsimcha
You realize that since BlocKGrid is only an interface, you will write 7 separate collision detection functions with the design you have laid out here? As well as 7 separate rotation functions? Would you really do it this way?
Claudiu
Perhaps I wasn't clear in defining BlockGrid. BlockGrid is essentially an n*n array of bits. The collision detection function would be similar to saying, "if ((array1(x,y) == TRUE) }" (with proper math for offsetting the location of one BlockGrid over another)
David
Oops, I said, "BlockGrid is an interface", I should have said, "BlockGrid is a class". Corrected.
David
What's your argument for having 7 pieces vs. 1? 1 seems easier. What if you were loading the piece data from a file? In that case you'd certainly just have one class.
Claudiu
That's a good point. If Piece were a class instead of an interface, and each Piece differed only by the content of its BlockGrid, then yes, only one Piece class would be needed.
David
+5  A: 

Firstly, I wouldn't subclass the Piece class because it's unnecessary. The Piece class should be capable of describing any shape without using inheritance. IMHO, this isn't what inheritance was made for and it just complicates things.

Secondly, I wouldn't store the x/y coordinates in the Block objects because it allows two blocks to exist in the same place. The Piece classes would keep a grid (i.e. 2D array) holding the block objects. The x/y coordinates would be the indexes of the 2D array.

As for the static method vs factory object for getting a random piece, I'd go with the factory object for the simple fact that the factory object can be mocked for testing.

I would treat the board as one large Piece object. The Board class would keep the large Piece object as a member variable, and might keep other Piece objects such as the current piece being played, and the next piece to be played. This is done using composition to avoid inheritance.

Tom Dalling
+1 Inheritance is unnecessary, even harmful, because we want all pieces to have the same behavior. The pieces only differ in their shape and color. The only instance I can think of where you'd want special behavior is something like the "t-spin" bonus scoring in Tetris DS, but that could be better handled as a special case in the scoring code. Don't use the class hierarchy as a substitute for the if() {} statement.
Theran
Can you explain the last sentence? When you said the board would be a large Piece that would contain another Piece, I automatically assumed inheritance
Claudiu
You're right, it is a little unclear. The board would keep the "one large piece" as a member variable.
Tom Dalling
+1  A: 

Piece class: I think that a single class for all the pieces is sufficient. The class functions shoudl be general enough to work for any piece, so there is no need to subclass.

Piece Class Representation: I believe that a 4x4 array is probably a better way as you will then find it much easier to rotate the piece.

Location: Location should definitely be stored by the board, not the piece as otherwise you would have to go through the entire set of blocks to ensure that no two blocks are in the same position.

Generating a Piece: Honestly for this one I do not feel that it will make too much of a difference. Having said that, I would prefer a static function as there is really not so much to this function that it warrants its own class.

Manipulating the Current Piece: I would just implement a getCurrent function as I feel that there is no need to overcomplicate the problem by adding in an extra class to serve as a proxy.

This is how I would do it, but there are many different ways, and at the end of the day, the thing to focus on is simply getting the program running.

Nikwin
A: 

All these classes and stuff... it might be making the problem way too abstract for what it really is. Many different ways to represent tetris pieces (stackoverflow.com/questions/233850/…) and many different ways to manipulate them. If it's for an intro course I wouldn't worry about OOP. Just my opinion, not a real answer to your question.

Having said that, one could suffice with simply a Board and Piece class.

Board class: Encapsulates a simple 2d array of rectangles. Properties like currentpiece, nextpiece. Routines like draw(), fullrows(), drop(), etc.. which manipulate the current piece and the filled in board squares.

Piece class: Encapsulates an array of unsigned 16 bit numbers encoding the pieces in their various rotations. You would track color, current location, and rotation. Perhaps one routine, rotate() would be necessary.

The rest, would be, depending on the environment, handling keyboard events etc...

I've found that placing too much emphasis on design tends to make people forget that what they really need to do is to get something running. I'm not saying don't design, I'm saying that more often than not, there is more value in getting something going, giving you traction and motivation to keep going.

I would say, to the class, you have X hours to make a design for a tetris game. Then they would need to turn in that design. Then I would say, you have X days, to get something running based on the design you turned in or even not based on the design.