Tetris/Java
< Tetris
Java
<lang java>import java.awt.*; import java.awt.event.*; import static java.lang.String.format; import java.util.Arrays; import javax.swing.*;
public class Tetris extends JPanel implements Runnable {
public static final int EMPTY = -1; public static final int BORDER = -2;
final Color[] colors = {Color.green, Color.red, Color.blue, Color.pink, Color.orange, Color.cyan, Color.magenta};
// for centering the shapes in the preview window final int[][] previewOffets = {{16, 15}, {-15, 15}, {0, 0}, {0, 0}, {-15, 5}, {16, 15}, {-15, 15}};
int[][] grid;
Shape selectedShape; Shape preSelectedShape;
int blockSize = 30; int nRows = 18; int nCols = 12; int shapeRow; int shapeCol; int topMargin = 50; int leftMargin = 20;
Thread fallingThread; Scoreboard scoreboard;
Font smallFont; Rectangle gridRect, previewRect;
public Tetris() { setPreferredSize(new Dimension(640, 640)); setBackground(new Color(0xDDEEFF)); setFont(new Font("Monospaced", Font.BOLD, 48)); setFocusable(true);
smallFont = getFont().deriveFont(Font.BOLD, 18);
gridRect = new Rectangle(46, 47, 308, 517); previewRect = new Rectangle(387, 47, 200, 200);
scoreboard = new Scoreboard(); grid = new int[nRows][nCols]; initGrid(); selectShape();
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (scoreboard.isGameOver()) { startNewGame(); repaint(); } } });
addKeyListener(new KeyAdapter() { boolean fastDown;
@Override public void keyPressed(KeyEvent e) {
if (scoreboard.isGameOver()) return;
switch (e.getKeyCode()) {
case KeyEvent.VK_UP: if (canRotate(selectedShape)) rotate(selectedShape); break;
case KeyEvent.VK_LEFT: if (canMove(selectedShape, -1, 0)) move(selectedShape, -1, 0); break;
case KeyEvent.VK_RIGHT: if (canMove(selectedShape, 1, 0)) move(selectedShape, 1, 0); break;
case KeyEvent.VK_DOWN: if (!fastDown) { fastDown = true; while (canMove(selectedShape, 0, 1)) { move(selectedShape, 0, 1); repaint(); } shapeHasLanded(); } } repaint(); }
@Override public void keyReleased(KeyEvent e) { fastDown = false; } }); }
void selectShape() { shapeRow = 1; shapeCol = 5; selectedShape = preSelectedShape; preSelectedShape = Shape.values()[(int) (Math.random() * colors.length)]; if (selectedShape != null) selectedShape.reset(); }
void startNewGame() { stop(); initGrid(); selectShape(); scoreboard.reset(); (fallingThread = new Thread(this)).start(); }
void stop() { if (fallingThread != null) { Thread tmp = fallingThread; fallingThread = null; tmp.interrupt(); } }
void initGrid() { for (int r = 0; r < nRows; r++) { Arrays.fill(grid[r], EMPTY); for (int c = 0; c < nCols; c++) { if (c == 0 || c == nCols - 1 || r == nRows - 1) grid[r][c] = BORDER; } } }
@Override public void run() {
while (Thread.currentThread() == fallingThread) {
try { Thread.sleep(scoreboard.getSpeed()); } catch (InterruptedException ignored) { }
if (!scoreboard.isGameOver()) { if (canMove(selectedShape, 0, 1)) { move(selectedShape, 0, 1); } else { shapeHasLanded(); } repaint(); } } }
void drawStartScreen(Graphics2D g) { g.setFont(getFont());
g.setColor(new Color(0xFFFFFF)); g.fillRect(leftMargin + 80, topMargin + 35, 252, 100); g.fillRect(leftMargin + 30, topMargin + 325, 252, 40);
g.setColor(Color.black); g.drawString("Tetris", leftMargin + 110, topMargin + 100);
g.setFont(smallFont); g.drawString("click to start", leftMargin + 100, topMargin + 350); }
void drawSquare(Graphics2D g, Color color, int r, int c) { g.setStroke(new BasicStroke(2));
g.setColor(color); g.fillRect(leftMargin + c * blockSize, topMargin + r * blockSize, blockSize, blockSize);
g.setColor(Color.white); g.drawRect(leftMargin + c * blockSize, topMargin + r * blockSize, blockSize, blockSize); }
void drawUI(Graphics2D g) { g.setStroke(new BasicStroke(5)); g.setColor(new Color(0xBECFEA)); g.fill(gridRect);
g.setColor(new Color(0x7788AA)); g.draw(gridRect); g.draw(previewRect);
for (int r = 0; r < nRows; r++) { for (int c = 0; c < nCols; c++) { int id = grid[r][c]; if (id > EMPTY) drawSquare(g, colors[id], r, c); } }
g.setColor(Color.black); g.setFont(smallFont); g.drawString(format("hiscore %6d", scoreboard.getTopscore()),400, 330); g.drawString(format("level %6d", scoreboard.getLevel()), 400, 360); g.drawString(format("lines %6d", scoreboard.getLines()), 400, 390); g.drawString(format("score %6d", scoreboard.getScore()), 400, 420); }
void drawPreview(Graphics2D g) { int ord = preSelectedShape.ordinal(); g.translate(previewOffets[ord][0], previewOffets[ord][1]); for (int[] p : preSelectedShape.shapes[ord]) { drawSquare(g, colors[ord], 2 + p[1], 15 + p[0]); } g.translate(-previewOffets[ord][0], -previewOffets[ord][1]); }
void drawFallingShape(Graphics2D g) { Color c = colors[selectedShape.ordinal()]; for (int[] p : selectedShape.pos) drawSquare(g, c, shapeRow + p[1], shapeCol + p[0]); }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawUI(g);
if (fallingThread == null) { drawStartScreen(g); } else { drawFallingShape(g); }
drawPreview(g); }
boolean canRotate(Shape s) { if (s == Shape.Square) return false;
int[][] pos = new int[4][2]; for (int i = 0; i < pos.length; i++) { pos[i] = s.pos[i].clone(); }
for (int[] row : pos) { int tmp = row[0]; row[0] = row[1]; row[1] = -tmp; }
for (int[] p : pos) { int newCol = shapeCol + p[0]; int newRow = shapeRow + p[1]; if (grid[newRow][newCol] != EMPTY) { return false; } } return true; }
void rotate(Shape s) { if (s == Shape.Square) return;
for (int[] row : s.pos) { int tmp = row[0]; row[0] = row[1]; row[1] = -tmp; } }
void move(Shape s, int xIncr, int yIncr) { shapeRow += yIncr; shapeCol += xIncr; }
boolean canMove(Shape s, int xIncr, int yIncr) { for (int[] p : s.pos) { int newCol = shapeCol + xIncr + p[0]; int newRow = shapeRow + yIncr + p[1]; if (grid[newRow][newCol] != EMPTY) return false; } return true; }
void shapeHasLanded() { addShape(selectedShape); if (shapeRow < 2) { scoreboard.setGameOver(); scoreboard.setTopscore(); stop(); } else { scoreboard.addLines(removeLines()); } selectShape(); }
int removeLines() { int count = 0; for (int r = 0; r < nRows - 1; r++) { for (int c = 1; c < nCols - 1; c++) { if (grid[r][c] == EMPTY) break; if (c == nCols - 2) { count++; removeLine(r); } } } return count; }
void removeLine(int line) { for (int c = 0; c < nCols; c++) grid[line][c] = EMPTY;
for (int c = 0; c < nCols; c++) { for (int r = line; r > 0; r--) grid[r][c] = grid[r - 1][c]; } }
void addShape(Shape s) { for (int[] p : s.pos) grid[shapeRow + p[1]][shapeCol + p[0]] = s.ordinal(); }
public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setTitle("Tetris"); f.setResizable(false); f.add(new Tetris(), BorderLayout.CENTER); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); }); }
}
class Scoreboard {
static final int MAXLEVEL = 9;
private int level; private int lines; private int score; private int topscore; private boolean gameOver = true;
void reset() { setTopscore(); level = lines = score = 0; gameOver = false; }
void setGameOver() { gameOver = true; }
boolean isGameOver() { return gameOver; }
void setTopscore() { if (score > topscore) topscore = score; }
int getTopscore() { return topscore; }
int getSpeed() {
switch (level) { case 0: return 700; case 1: return 600; case 2: return 500; case 3: return 400; case 4: return 350; case 5: return 300; case 6: return 250; case 7: return 200; case 8: return 150; case 9: return 100; default: return 100; } }
void addScore(int sc) { score += sc; }
void addLines(int line) {
switch (line) { case 1: addScore(10); break; case 2: addScore(20); break; case 3: addScore(30); break; case 4: addScore(40); break; default: return; }
lines += line; if (lines > 10) addLevel(); }
void addLevel() { lines %= 10; if (level < MAXLEVEL) level++; }
int getLevel() { return level; }
int getLines() { return lines; }
int getScore() { return score; }
}
enum Shape {
ZShape, SShape, Straight, TShape, Square, LShape, JShape;
private Shape() { pos = new int[4][2]; reset(); }
void reset() { for (int i = 0; i < pos.length; i++) { pos[i] = shapes[ordinal()][i].clone(); } }
final int[][] pos;
final int[][][] shapes = { {{0, -1}, {0, 0}, {-1, 0}, {-1, 1}}, {{0, -1}, {0, 0}, {1, 0}, {1, 1}}, {{0, -1}, {0, 0}, {0, 1}, {0, 2}}, {{-1, 0}, {0, 0}, {1, 0}, {0, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{-1, -1}, {0, -1}, {0, 0}, {0, 1}}, {{1, -1}, {0, -1}, {0, 0}, {0, 1}}};
}</lang>