@Override public void paintComponent(Graphics g) { super.paintComponent(g); // This helps simplify the positioning of things. g.translate(BORDER_WIDTH, BORDER_WIDTH); /* * Draw the board differently depending on the current game state. */ if (tetris.isPaused()) { g.setFont(LARGE_FONT); g.setColor(Color.WHITE); String msg = "PAUSED"; g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, CENTER_Y); } else if (tetris.isNewGame() || tetris.isGameOver()) { g.setFont(LARGE_FONT); g.setColor(Color.WHITE); /* * Because both the game over and new game screens are nearly identical, * we can handle them together and just use a ternary operator to change * the messages that are displayed. */ String msg = tetris.isNewGame() ? "TETRIS" : "GAME OVER"; g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, 150); g.setFont(SMALL_FONT); msg = "Press Enter to Play" + (tetris.isNewGame() ? "" : " Again"); g.drawString(msg, CENTER_X - g.getFontMetrics().stringWidth(msg) / 2, 300); } else { /* * Draw the tiles onto the board. */ for (int x = 0; x < COL_COUNT; x++) { for (int y = HIDDEN_ROW_COUNT; y < ROW_COUNT; y++) { TileType tile = getTile(x, y); if (tile != null) { drawTile(tile, x * TILE_SIZE, (y - HIDDEN_ROW_COUNT) * TILE_SIZE, g); } } } /* * Draw the current piece. This cannot be drawn like the rest of the * pieces because it's still not part of the game board. If it were * part of the board, it would need to be removed every frame which * would just be slow and confusing. */ TileType type = tetris.getPieceType(); int pieceCol = tetris.getPieceCol(); int pieceRow = tetris.getPieceRow(); int rotation = tetris.getPieceRotation(); // Draw the piece onto the board. for (int col = 0; col < type.getDimension(); col++) { for (int row = 0; row < type.getDimension(); row++) { if (pieceRow + row >= 2 && type.isTile(col, row, rotation)) { drawTile( type, (pieceCol + col) * TILE_SIZE, (pieceRow + row - HIDDEN_ROW_COUNT) * TILE_SIZE, g); } } } /* * Draw the ghost (semi-transparent piece that shows where the current piece will land). I couldn't think of * a better way to implement this so it'll have to do for now. We simply take the current position and move * down until we hit a row that would cause a collision. */ Color base = type.getBaseColor(); base = new Color(base.getRed(), base.getGreen(), base.getBlue(), 20); for (int lowest = pieceRow; lowest < ROW_COUNT; lowest++) { // If no collision is detected, try the next row. if (isValidAndEmpty(type, pieceCol, lowest, rotation)) { continue; } // Draw the ghost one row higher than the one the collision took place at. lowest--; // Draw the ghost piece. for (int col = 0; col < type.getDimension(); col++) { for (int row = 0; row < type.getDimension(); row++) { if (lowest + row >= 2 && type.isTile(col, row, rotation)) { drawTile( base, base.brighter(), base.darker(), (pieceCol + col) * TILE_SIZE, (lowest + row - HIDDEN_ROW_COUNT) * TILE_SIZE, g); } } } break; } /* * Draw the background grid above the pieces (serves as a useful visual * for players, and makes the pieces look nicer by breaking them up. */ g.setColor(Color.DARK_GRAY); for (int x = 0; x < COL_COUNT; x++) { for (int y = 0; y < VISIBLE_ROW_COUNT; y++) { g.drawLine(0, y * TILE_SIZE, COL_COUNT * TILE_SIZE, y * TILE_SIZE); g.drawLine(x * TILE_SIZE, 0, x * TILE_SIZE, VISIBLE_ROW_COUNT * TILE_SIZE); } } } /* * Draw the outline. */ g.setColor(Color.WHITE); g.drawRect(0, 0, TILE_SIZE * COL_COUNT, TILE_SIZE * VISIBLE_ROW_COUNT); }