Programming lesson
Mastering Inheritance and Abstract Classes in Java with BlockAddiction – A Tetris-Style Game Tutorial
Learn how to implement inheritance and abstract classes in Java by building concrete piece types for a Tetris-style game called BlockAddiction. This tutorial covers abstract classes, interfaces, 2D arrays, and practical OOP design.
Introduction: Why Inheritance and Abstract Classes Matter in Game Development
If you've ever played a falling-blocks game like Tetris or the trending BlockAddiction, you've experienced the power of object-oriented programming (OOP) in action. In this tutorial, we'll explore how to use inheritance and abstract classes in Java to build the six concrete piece types required for Homework 4 in COMS 2270. We'll connect these concepts to real-world scenarios, like how a popular mobile game uses polymorphism to handle different block shapes, or how an AI-powered app might reuse common behavior across multiple entities.
Understanding the Assignment: The Piece Interface and Its Subtypes
The assignment asks you to implement six concrete subtypes of the Piece interface: LShapedPiece, TeePiece, QuotesPiece, RotatingSPiece, CirclingPiece, and SnakingPiece. Each piece has a 3×3 bounding square and a set of cells (position + color). The key methods are transform() (changes cell positions within the bounding square) and cycle() (changes colors). Your job is to create an abstract class AbstractPiece that contains common code for all pieces, and optionally additional abstract classes for groups of pieces that share behavior.
Step 1: Designing the Abstract Class Hierarchy
Think of an abstract class as a blueprint for a family of related objects. In the world of gaming, consider a popular battle royale game where different characters (like the LShapedPiece and TeePiece) share common attributes (health, position) but have unique abilities (transformations). Your AbstractPiece will hold the position of the bounding square's top-left corner, the cells array, and implement common methods like getCells() and shiftDown(). The abstract method transform() will be overridden by each concrete piece.
public abstract class AbstractPiece implements Piece {
protected Position position;
protected Cell[][] cells; // 3x3 grid
public AbstractPiece(Position position, Icon[][] icons) {
this.position = position;
// Initialize cells based on icons
}
public Cell[][] getCells() {
return cells;
}
public void shiftDown() {
position = new Position(position.row() + 1, position.col());
}
public abstract void transform();
}Step 2: Implementing Concrete Pieces – LShapedPiece and TeePiece
Let's start with the LShapedPiece. In the game, it occupies cells at relative positions (0,0), (0,1), (1,1), (2,1) with different colors. When transform() is called, the shape rotates 90 degrees clockwise. This is similar to how a character in a platformer game might change stance. For the TeePiece, the shape looks like a T: cells at (0,1), (1,0), (1,1), (2,1). Both pieces share a common rotation mechanism, so you could create an intermediate abstract class RotatingPiece that implements transform() using a 2D array rotation algorithm.
public class LShapedPiece extends AbstractPiece {
public LShapedPiece(Position position) {
super(position, new Icon[][]{
{Icon.YELLOW, Icon.CYAN, null},
{null, Icon.GREEN, null},
{null, Icon.RED, null}
});
}
@Override
public void transform() {
// rotate cells 90 degrees clockwise
Cell[][] rotated = new Cell[3][3];
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
rotated[c][2 - r] = cells[r][c];
}
}
cells = rotated;
}
}Step 3: Pieces That Cycle Colors – RotatingSPiece and CirclingPiece
Some pieces, like RotatingSPiece and CirclingPiece, not only transform but also cycle colors. The cycle() method shifts the color of each cell to the next in a sequence. This is analogous to a music visualizer app that cycles through color palettes. You can create another abstract class CyclingPiece that extends AbstractPiece and implements cycle() to rotate the icons array. For example, the CirclingPiece might have a fixed shape but cycles through four colors.
public abstract class CyclingPiece extends AbstractPiece {
protected Icon[] colorCycle;
protected int cycleIndex;
public void cycle() {
cycleIndex = (cycleIndex + 1) % colorCycle.length;
// update each cell's icon to the new color
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
if (cells[r][c] != null) {
cells[r][c] = new Cell(cells[r][c].row(), cells[r][c].col(), colorCycle[cycleIndex]);
}
}
}
}
}Step 4: SnakingPiece – A Special Case
The SnakingPiece moves like a snake: its cells shift in a pattern when transformed. This piece might not fit neatly into the RotatingPiece or CyclingPiece categories, so it can directly extend AbstractPiece. This demonstrates that your hierarchy should be flexible; not every piece must share the same abstract parent beyond the base AbstractPiece. In the world of AI, think of different neural network architectures that share a common base but have unique layers.
Step 5: Putting It All Together – The Game Grid and Collapse Logic
The provided AbstractGame class handles the game loop, collision detection, and collapse of matching icons (3 or more adjacent same-colored icons). You don't need to implement determinePositionsToCollapse(), but understanding it helps. The grid is a 2D array of Icon objects. When a piece lands, its cells are copied to the grid, and the collapse logic removes sets of three or more matching icons. This is similar to how a puzzle game like Candy Crush clears matches. The use of 2D arrays and lists is crucial here.
Step 6: Testing and Debugging Tips
Start early and test incrementally. Write a simple test class that creates each piece, calls transform() and cycle(), and prints the cell positions. Use the provided JAR file to see the pieces in action (though the JAR has different pieces, the UI will help you understand the game flow). Avoid the common pitfall of forgetting to update the position after shifting. Remember, the bounding square's top-left corner changes as the piece moves down.
Conclusion: Real-World Applications
Inheritance and abstract classes are not just for games. They are used in frameworks like Android's Activity class, where you extend it to create your own screens. In machine learning, abstract classes define the interface for models, and concrete implementations (like neural networks) inherit from them. By mastering these concepts in the context of BlockAddiction, you're building skills that transfer to any large-scale software project.
Now go ahead, start coding, and remember: don't wait until the night before the deadline. Happy coding!