/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.games.tetris; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Random; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.SwingUtilities; /** * Simple Tetris game. * @author Levente S\u00e1ntha */ public class Tetris extends JComponent implements KeyListener, MouseListener { private static final long serialVersionUID = 1L; private static final int[][][][] BLOCKS = { // * * * // * {{{0, 0}, {1, 0}, {2, 0}, {1, 1}}, {{1, 0}, {0, 1}, {1, 1}, {1, 2}}, {{1, 0}, {0, 1}, {1, 1}, {2, 1}}, {{0, 0}, {0, 1}, {1, 1}, {0, 2}}}, // * * * * {{{0, 0}, {1, 0}, {2, 0}, {3, 0}}, {{0, 0}, {0, 1}, {0, 2}, {0, 3}}, {{0, 0}, {1, 0}, {2, 0}, {3, 0}}, {{0, 0}, {0, 1}, {0, 2}, {0, 3}}}, // * * * // * {{{0, 0}, {1, 0}, {2, 0}, {0, 1}}, {{0, 0}, {1, 0}, {1, 1}, {1, 2}}, {{2, 0}, {0, 1}, {1, 1}, {2, 1}}, {{0, 0}, {0, 1}, {0, 2}, {1, 2}}}, // * * * // * {{{0, 0}, {1, 0}, {2, 0}, {2, 1}}, {{1, 0}, {1, 1}, {0, 2}, {1, 2}}, {{0, 0}, {0, 1}, {1, 1}, {2, 1}}, {{0, 0}, {1, 0}, {0, 1}, {0, 2}}}, // * * // * * {{{1, 0}, {2, 0}, {0, 1}, {1, 1}}, {{0, 0}, {0, 1}, {1, 1}, {1, 2}}, {{1, 0}, {2, 0}, {0, 1}, {1, 1}}, {{0, 0}, {0, 1}, {1, 1}, {1, 2}}}, // * * // * * {{{0, 0}, {1, 0}, {1, 1}, {2, 1}}, {{1, 0}, {0, 1}, {1, 1}, {0, 2}}, {{0, 0}, {1, 0}, {1, 1}, {2, 1}}, {{1, 0}, {0, 1}, {1, 1}, {0, 2}}}, // * * // * * {{{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}}}; private static final int[][] DIMS = { {3, 2}, {2, 3}, {3, 2}, {2, 3}, {4, 1}, {1, 4}, {4, 1}, {1, 4}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {3, 2}, {2, 3}, {2, 2}, {2, 2}, {2, 2}, {2, 2}}; private static final int CELL = 20; private static final int WIDTH_C = 10; private static final int HEIGHT_C = 20; private static final Color[] COLORS = {Color.BLACK, Color.YELLOW, Color.RED, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.ORANGE, Color.LIGHT_GRAY, Color.DARK_GRAY}; private int[][] WORLD = new int[WIDTH_C + 2][HEIGHT_C + 2]; private int next_si = 0; private int si = 0; private int next_bi = 0; private int bi = 0; private int x = 1; private int y = 1; private boolean pause = false; private boolean up = true; private Thread thread; private int score; private boolean end = false; private Image img; private static final Dimension DIM = new Dimension((WIDTH_C + 2) * CELL, (HEIGHT_C + 5 + 2) * CELL); private Random si_rnd = new Random(); private Random bi_rnd = new Random(); private long delay = 500; private final Runnable runRepaint = new Runnable() { public void run() { repaint(); } }; Tetris() { setOpaque(false); for (int i = 0; i < WIDTH_C + 2; i++) { for (int j = 0; j < HEIGHT_C + 2; j++) { if (i == 0 || j == 0 || i == WIDTH_C + 1 || j == HEIGHT_C + 1) WORLD[i][j] = COLORS.length - 1; else WORLD[i][j] = 0; } } addKeyListener(this); addMouseListener(this); setFocusable(true); setRequestFocusEnabled(true); enableEvents(AWTEvent.FOCUS_EVENT_MASK); enableEvents(AWTEvent.KEY_EVENT_MASK); enableEvents(AWTEvent.MOUSE_EVENT_MASK); } private int darken(int i) { int r = i - 64; return r < 0 ? 0 : r; } private int lighten(int i) { int r = i + 64; return r > 255 ? 255 : r; } private void paintBox(Graphics g, int i, int j, Color c) { Color dc = new Color(darken(c.getRed()), darken(c.getGreen()), darken(c .getBlue())); Color lc = new Color(lighten(c.getRed()), lighten(c.getGreen()), lighten(c.getBlue())); g.setColor(c); g.fillRect(i * CELL, j * CELL, CELL - 1, CELL - 1); g.setColor(dc); g.drawLine(i * CELL, (j + 1) * CELL - 1, (i + 1) * CELL - 1, (j + 1) * CELL - 1); g.drawLine((i + 1) * CELL - 1, (j + 1) * CELL - 1, (i + 1) * CELL - 1, j * CELL); g.setColor(lc); g.drawLine(i * CELL, (j + 1) * CELL - 1, i * CELL, j * CELL); g.drawLine(i * CELL, j * CELL, (i + 1) * CELL - 1, j * CELL); } /** * Update the screen. * * @param g the graphics context * @see javax.swing.JComponent#update(java.awt.Graphics) */ public void update(Graphics g) { paint(g); } /** * Paint the game graphics. * @param g the graphics context * @see javax.swing.JComponent#paint(java.awt.Graphics) */ public void paint(Graphics g) { if (img == null) { img = createImage(DIM.width, DIM.height); } Graphics g2 = img.getGraphics(); g2.setColor(COLORS[0]); g2.fillRect(0, 0, DIM.width, DIM.height); for (int i = 0; i < WIDTH_C + 2; i++) { for (int j = 0; j < HEIGHT_C + 2; j++) { int ci = WORLD[i][j]; if (ci > 0) paintBox(g2, i, j, COLORS[ci]); } } Color c = COLORS[COLORS.length - 1]; for (int i = 0; i < WIDTH_C + 2; i++) { paintBox(g2, i, HEIGHT_C + 6, c); } for (int j = 0; j < 4; j++) { paintBox(g2, 0, HEIGHT_C + 2 + j, c); paintBox(g2, 5, HEIGHT_C + 2 + j, c); // paintBox(g2, 6, HEIGHT_C + 2 +j, c ); paintBox(g2, WIDTH_C + 1, HEIGHT_C + 2 + j, c); } if (isUp()) { int[][] b = BLOCKS[si][bi]; for (int i = 0; i < b.length; i++) { paintBox(g2, x + b[i][0], y + b[i][1], COLORS[si + 1]); } g2.setColor(Color.WHITE); g2.drawString("SCORE:", CELL + 2, (HEIGHT_C + 4) * CELL - 4); g2.drawString(String.valueOf(score), 2 * CELL, (HEIGHT_C + 5) * CELL - 4); b = BLOCKS[next_si][next_bi]; for (int i = 0; i < b.length; i++) { paintBox(g2, 7 + b[i][0], HEIGHT_C + 2 + b[i][1], COLORS[next_si + 1]); } } else if (end) { g2.setColor(Color.BLACK); g2.fillRect(2 * CELL, 9 * CELL, 8 * CELL, 4 * CELL); g2.setColor(Color.WHITE); g2.drawRect(2 * CELL, 9 * CELL, 8 * CELL, 4 * CELL); g2.drawString("GAME OVER! SCORE: " + score, (WIDTH_C - 6) * CELL / 2 + 2, (HEIGHT_C + 2) * CELL / 2); } g2.dispose(); g.drawImage(img, 0, 0, this); } private void rot(int i) { int t = (bi + i) % 4; if (hasRoom(t, x, y)) { bi = t; } } private void trans(int i) { int t = x + i; if (hasRoom(bi, t, y)) { x = t; } } private void fall() { while (hasRoom(bi, x, y + 1)) y++; thread.interrupt(); } /** * Handle keys. * @param e the key event */ public void keyPressed(KeyEvent e) { int kc = e.getKeyCode(); if (kc == KeyEvent.VK_N) { newGame(); return; } if (kc == KeyEvent.VK_P) { flipPause(); return; } if (!isUp() || pause) return; switch (e.getKeyCode()) { case KeyEvent.VK_UP: rot(1); break; case KeyEvent.VK_LEFT: trans(-1); break; case KeyEvent.VK_DOWN: rot(3); break; case KeyEvent.VK_RIGHT: trans(1); break; case KeyEvent.VK_SPACE: fall(); break; case KeyEvent.VK_N: newGame(); break; case KeyEvent.VK_P: flipPause(); break; default: return; } SwingUtilities.invokeLater(runRepaint); } private void newGame() { setUp(false); if (thread != null) { if (pause) { flipPause(); } try { thread.join(); } catch (InterruptedException ignore) { //ignore } } for (int i = 0; i < WIDTH_C + 2; i++) { for (int j = 0; j < HEIGHT_C + 2; j++) { if (i == 0 || j == 0 || i == WIDTH_C + 1 || j == HEIGHT_C + 1) WORLD[i][j] = COLORS.length - 1; else WORLD[i][j] = 0; } } requestFocus(); end = false; score = 0; si = si_rnd.nextInt(7); next_si = si_rnd.nextInt(7); bi = bi_rnd.nextInt(4); next_bi = bi_rnd.nextInt(4); x = 1 + bi_rnd.nextInt((WIDTH_C - DIMS[si * 4 + bi][0])); y = 0; thread = new Thread(new Runnable() { public void run() { try { long before, after, sleep; stop: while (isUp()) { before = System.currentTimeMillis(); synchronized (Tetris.class) { while (pause) { try { System.out.println("waiting"); Tetris.class.wait(); System.out.println("back from waiting"); } catch (InterruptedException ignore) { //ignore } if (!isUp()) break stop; } } if (hasRoom(bi, x, y + 1)) { y++; try { SwingUtilities.invokeAndWait(runRepaint); } catch (InterruptedException x) { //ignore } } else { newBlock(); if (!hasRoom(bi, x, y)) { setUp(false); end = true; try { SwingUtilities.invokeAndWait(runRepaint); } catch (InterruptedException x) { //ignore } continue; } try { SwingUtilities.invokeAndWait(runRepaint); } catch (InterruptedException x) { //ignore } } after = System.currentTimeMillis(); sleep = delay - (after - before); sleep = sleep < 0 ? delay : sleep; try { Thread.sleep(sleep); } catch (InterruptedException ignore) { //ignore } } } catch (Throwable ex) { ex.printStackTrace(); } } }, "Tetris"); setUp(true); thread.start(); } private void flipPause() { synchronized (Tetris.class) { pause = !pause; if (!pause) Tetris.class.notifyAll(); } } private void newBlock() { int[][] b = BLOCKS[si][bi]; for (int i = 0; i < b.length; i++) { WORLD[x + b[i][0]][y + b[i][1]] = si + 1; } for (int i = 1; i < HEIGHT_C + 1; i++) { boolean full = true; for (int j = 1; j < WIDTH_C + 1; j++) { if (WORLD[j][i] == 0) { full = false; break; } } if (full) { int s = WIDTH_C; for (int j = 2; j < WIDTH_C + 1; j++) { if (WORLD[j - 1][i] != WORLD[j][i]) { --s; } } score += s; } if (full && i > 1) { for (int k = 0; k < i - 1; k++) { for (int j = 1; j < WIDTH_C + 1; j++) { WORLD[j][i - k] = WORLD[j][i - k - 1]; } } for (int j = 1; j < WIDTH_C + 1; j++) { WORLD[j][1] = 0; } } } si = next_si; next_si = si_rnd.nextInt(7); bi = next_bi; next_bi = bi_rnd.nextInt(4); x = 1 + bi_rnd.nextInt((WIDTH_C - DIMS[si * 4 + bi][0])); y = 1; } private boolean hasRoom(int bi, int x, int y) { boolean hasRoom = true; int[][] b = BLOCKS[si][bi]; for (int i = 0; i < b.length; i++) { if (WORLD[x + b[i][0]][y + b[i][1]] != 0) { hasRoom = false; break; } } return hasRoom; } /** * @see javax.swing.JComponent#getPreferredSize() */ public Dimension getPreferredSize() { return DIM; } /** * @see javax.swing.JComponent#getMinimumSize() * @return */ public Dimension getMinimumSize() { return DIM; } /** * @see javax.swing.JComponent#getMaximumSize() * @return */ public Dimension getMaximumSize() { return DIM; } /** * Handle mouse input. */ public void mousePressed(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) { if (this.contains(e.getX(), e.getY())) { if (!this.hasFocus() && this.isRequestFocusEnabled()) { this.requestFocus(); } } } } /** * Unused. */ public void keyReleased(KeyEvent e) { } /** * Unused. */ public void keyTyped(KeyEvent e) { } /** * Unused. */ public void mouseClicked(MouseEvent e) { } /** * Unused. */ public void mouseEntered(MouseEvent e) { } /** * Unused. */ public void mouseExited(MouseEvent e) { } /** * Unused. */ public void mouseReleased(MouseEvent e) { } /** * Start Tetris. */ public static void main(final String[] argv) { SwingUtilities.invokeLater(new Runnable() { public void run() { int delay = 500; try { if (argv.length > 0) delay = Integer.parseInt(argv[0]); } catch (Exception e) { // ignore } JFrame frame = new JFrame("Tetris"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final Tetris tetris = new Tetris(); tetris.delay = delay; frame.add(tetris, BorderLayout.CENTER); frame.setSize(DIM.width + 7, DIM.height + CELL + CELL / 2); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { tetris.setUp(false); } }); frame.setVisible(true); tetris.requestFocus(); tetris.newGame(); } }); } private synchronized boolean isUp() { return up; } private synchronized void setUp(boolean up) { this.up = up; } }