1 package Tetris; 2 import java.awt.BorderLayout; 3 import java.awt.EventQueue; 4 import javax.swing.JFrame; 5 import javax.swing.JLabel; 6 7 public class Tetris extends JFrame { 8 9 private JLabel statusbar; 10 11 public Tetris() { 12 initUI(); 13 } 14 private void initUI() { 15 16 statusbar= new JLabel(" 0"); 17 add(statusbar, BorderLayout.SOUTH); 18 19 var board = new Board(this); 20 add(board); 21 board.start(); 22 23 setTitle("Tetris"); 24 setSize(200, 400); 25 setDefaultCloseOperation(EXIT_ON_CLOSE); 26 setLocationRelativeTo(null); 27 } 28 JLabel getStatusBar() { 29 return statusbar; 30 } 31 public static void main(String[] args) { 32 EventQueue.invokeLater(() -> { 33 var game = new Tetris(); 34 game.setVisible(true); 35 }); 36 } 37 }
1 package Tetris; 2 import java.util.Random; 3 4 public class Shape { 5 protected enum Tetrominoe { 6 NoShape, ZShape, SShape, LineShape, 7 TShape, SquareShape, LShape, MirroredLShape 8 } 9 private Tetrominoe pieceShape; 10 private int[][] coords; 11 public Shape() { 12 coords = new int[4][2]; 13 setShape(Tetrominoe.NoShape); 14 } 15 void setShape(Tetrominoe shape) { 16 int[][][] coordsTable = new int[][][]{ 17 {{0, 0}, {0, 0}, {0, 0}, {0, 0}}, 18 {{0, -1}, {0, 0}, {-1, 0}, {-1, 1}}, 19 {{0, -1}, {0, 0}, {1, 0}, {1, 1}}, 20 {{0, -1}, {0, 0}, {0, 1}, {0, 2}}, 21 {{-1, 0}, {0, 0}, {1, 0}, {0, 1}}, 22 {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, 23 {{-1, -1}, {0, -1}, {0, 0}, {0, 1}}, 24 {{1, -1}, {0, -1}, {0, 0}, {0, 1}} 25 }; 26 for (int i = 0; i < 4; i++) { 27 System.arraycopy(coordsTable[shape.ordinal()], 0, coords, 0, 4); 28 } 29 pieceShape = shape; 30 } 31 private void setX(int index, int x) { 32 coords[index][0] = x; 33 } 34 private void setY(int index, int y) { 35 coords[index][1] = y; 36 } 37 int x(int index) { 38 return coords[index][0]; 39 } 40 int y(int index) { 41 return coords[index][1]; 42 } 43 Tetrominoe getShape() { 44 return pieceShape; 45 } 46 void setRandomShape() { 47 var r = new Random(); 48 int x = Math.abs(r.nextInt()) % 7 + 1; 49 50 Tetrominoe[] values = Tetrominoe.values(); 51 setShape(values[x]); 52 } 53 public int minX() { 54 int m = coords[0][0]; 55 56 for (int i=0;i<4;i++) { 57 m = Math.min(m, coords[i][0]); 58 } 59 return m; 60 } 61 int minY() { 62 int m = coords[0][1]; 63 for (int i = 0; i < 4; i++) { 64 m = Math.min(m, coords[i][1]); 65 } 66 return m; 67 } 68 Shape rotateLeft() { 69 if (pieceShape == Tetrominoe.SquareShape) { 70 return this; 71 } 72 var result = new Shape(); 73 result.pieceShape = pieceShape; 74 75 for (int i = 0; i < 4; i++) { 76 result.setX(i, y(i)); 77 result.setY(i, -x(i)); 78 } 79 return result; 80 } 81 Shape rotateRight() { 82 if (pieceShape == Tetrominoe.SquareShape) { 83 return this; 84 } 85 var result = new Shape(); 86 result.pieceShape = pieceShape; 87 for (int i = 0; i < 4; i++) { 88 result.setX(i, -y(i)); 89 result.setY(i, x(i)); 90 } 91 return result; 92 } 93 }
1 package Tetris; 2 import Tetris.Shape.Tetrominoe; 3 import javax.swing.JLabel; 4 import javax.swing.JPanel; 5 import javax.swing.Timer; 6 import java.awt.Color; 7 import java.awt.Graphics; 8 import java.awt.event.ActionEvent; 9 import java.awt.event.ActionListener; 10 import java.awt.event.KeyAdapter; 11 import java.awt.event.KeyEvent; 12 13 public class Board extends JPanel { 14 private final int BOARD_WIDTH = 10; 15 private final int BOARD_HEIGHT = 22; 16 private final int PERIOD_INTERVAL = 300; 17 18 private Timer timer; 19 private boolean isFallingFinished = false; 20 private boolean isPaused = false; 21 private int numLinesRemoved = 0; 22 private int curX = 0; 23 private int curY = 0; 24 private JLabel statusbar; 25 private Shape curPiece; 26 private Tetrominoe[] board; 27 28 public Board(Tetris parent) { 29 initBoard(parent); 30 } 31 private void initBoard(Tetris parent) { 32 setFocusable(true); 33 statusbar = parent.getStatusBar(); 34 addKeyListener(new TAdapter()); 35 } 36 private int squareWidth() { 37 return (int) getSize().getWidth() / BOARD_WIDTH; 38 } 39 private int squareHeight() { 40 return (int) getSize().getHeight() / BOARD_HEIGHT; 41 } 42 private Tetrominoe shapeAt(int x, int y) { 43 return board[(y * BOARD_WIDTH) + x]; 44 } 45 void start() { 46 curPiece=new Shape(); 47 board=new Tetrominoe[BOARD_WIDTH * BOARD_HEIGHT]; 48 clearBoard(); 49 newPiece(); 50 timer=new Timer(PERIOD_INTERVAL, new GameCycle()); 51 timer.start(); 52 } 53 private void pause() { 54 isPaused = !isPaused; 55 if (isPaused) { 56 statusbar.setText("paused"); 57 } else { 58 statusbar.setText(String.valueOf(numLinesRemoved)); 59 } 60 repaint(); 61 } 62 @Override 63 public void paintComponent(Graphics g) { 64 super.paintComponent(g); 65 doDrawing(g); 66 } 67 private void doDrawing(Graphics g) { 68 var size = getSize(); 69 int boardTop = (int) size.getHeight() - BOARD_HEIGHT * squareHeight(); 70 for (int i = 0; i < BOARD_HEIGHT; i++) { 71 for (int j = 0; j < BOARD_WIDTH; j++) { 72 Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1); 73 if (shape != Tetrominoe.NoShape) { 74 drawSquare(g, j * squareWidth(), 75 boardTop + i * squareHeight(), shape); 76 } 77 } 78 } 79 if (curPiece.getShape() != Tetrominoe.NoShape) { 80 for (int i = 0; i < 4; i++) { 81 int x = curX + curPiece.x(i); 82 int y = curY - curPiece.y(i); 83 drawSquare(g, x * squareWidth(), 84 boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(), 85 curPiece.getShape()); 86 } 87 } 88 } 89 private void dropDown() { 90 int newY = curY; 91 while (newY > 0) { 92 if (!tryMove(curPiece, curX, newY - 1)) { 93 break; 94 } 95 newY--; 96 } 97 pieceDropped(); 98 } 99 private void oneLineDown() { 100 if (!tryMove(curPiece, curX, curY - 1)) { 101 pieceDropped(); 102 } 103 } 104 private void clearBoard() { 105 for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; i++) { 106 board[i] = Tetrominoe.NoShape; 107 } 108 } 109 private void pieceDropped() { 110 for (int i = 0; i < 4; i++) { 111 int x = curX + curPiece.x(i); 112 int y = curY - curPiece.y(i); 113 board[(y * BOARD_WIDTH) + x] = curPiece.getShape(); 114 } 115 removeFullLines(); 116 if (!isFallingFinished) { 117 newPiece(); 118 } 119 } 120 121 private void newPiece() { 122 curPiece.setRandomShape(); 123 curX = BOARD_WIDTH / 2 + 1; 124 curY = BOARD_HEIGHT - 1 + curPiece.minY(); 125 if (!tryMove(curPiece, curX, curY)) { 126 curPiece.setShape(Tetrominoe.NoShape); 127 timer.stop(); 128 var msg = String.format("Game over. Score: %d", numLinesRemoved); 129 statusbar.setText(msg); 130 } 131 } 132 private boolean tryMove(Shape newPiece, int newX, int newY) { 133 134 for (int i = 0; i < 4; i++) { 135 int x = newX + newPiece.x(i); 136 int y = newY - newPiece.y(i); 137 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) { 138 return false; 139 } 140 if (shapeAt(x, y) != Tetrominoe.NoShape) { 141 return false; 142 } 143 } 144 curPiece = newPiece; 145 curX = newX; 146 curY = newY; 147 repaint(); 148 return true; 149 } 150 private void removeFullLines() { 151 int numFullLines = 0; 152 for (int i = BOARD_HEIGHT - 1; i >= 0; i--) { 153 boolean lineIsFull = true; 154 for (int j = 0; j < BOARD_WIDTH; j++) { 155 if (shapeAt(j, i) == Tetrominoe.NoShape) { 156 lineIsFull = false; 157 break; 158 } 159 } 160 if (lineIsFull) { 161 numFullLines++; 162 for (int k = i; k < BOARD_HEIGHT - 1; k++) { 163 for (int j = 0; j < BOARD_WIDTH; j++) { 164 board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1); 165 } 166 } 167 } 168 } 169 if (numFullLines > 0) { 170 171 numLinesRemoved += numFullLines; 172 173 statusbar.setText(String.valueOf(numLinesRemoved)); 174 isFallingFinished = true; 175 curPiece.setShape(Tetrominoe.NoShape); 176 } 177 } 178 private void drawSquare(Graphics g, int x, int y, Tetrominoe shape) { 179 180 Color colors[] = {new Color(0, 0, 0), new Color(204, 102, 102), 181 new Color(102, 204, 102), new Color(102, 102, 204), 182 new Color(204, 204, 102), new Color(204, 102, 204), 183 new Color(102, 204, 204), new Color(218, 170, 0) 184 }; 185 186 var color = colors[shape.ordinal()]; 187 188 g.setColor(color); 189 g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2); 190 191 g.setColor(color.brighter()); 192 g.drawLine(x, y + squareHeight() - 1, x, y); 193 g.drawLine(x, y, x + squareWidth() - 1, y); 194 195 g.setColor(color.darker()); 196 g.drawLine(x + 1, y + squareHeight() - 1, 197 x + squareWidth() - 1, y + squareHeight() - 1); 198 g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, 199 x + squareWidth() - 1, y + 1); 200 } 201 private class GameCycle implements ActionListener { 202 @Override 203 public void actionPerformed(ActionEvent e) { 204 doGameCycle(); 205 } 206 } 207 private void doGameCycle() { 208 update(); 209 repaint(); 210 } 211 private void update() { 212 if (isPaused) { 213 return; 214 } 215 if (isFallingFinished) { 216 isFallingFinished = false; 217 newPiece(); 218 } else { 219 oneLineDown(); 220 } 221 } 222 class TAdapter extends KeyAdapter { 223 @Override 224 public void keyPressed(KeyEvent e) { 225 226 if (curPiece.getShape() == Tetrominoe.NoShape) { 227 return; 228 } 229 int keycode = e.getKeyCode(); 230 // Java 12 switch expressions 231 switch (keycode) { 232 case KeyEvent.VK_P : pause();break; 233 case KeyEvent.VK_LEFT : tryMove(curPiece, curX - 1, curY);break; 234 case KeyEvent.VK_RIGHT : tryMove(curPiece, curX + 1, curY);break; 235 case KeyEvent.VK_DOWN : tryMove(curPiece.rotateRight(), curX, curY);break; 236 case KeyEvent.VK_UP : tryMove(curPiece.rotateLeft(), curX, curY);break; 237 case KeyEvent.VK_SPACE : dropDown();break; 238 case KeyEvent.VK_D : oneLineDown(); 239 } 240 } 241 } 242 }