package tetris; import java.awt.*; import java.applet.*; import java.awt.event.*; import java.util.Random; import java.net.URL; import java.net.MalformedURLException; public class tetris extends Applet { private final static int INITIAL_DELAY = 1000; private final static byte ROWS = 18; private final static byte COLUMNS = 10; private final static int EMPTY = -1; private final static int DELETED_ROWS_PER_LEVEL = 5; private final static Color PIECE_COLORS[] = { new Color(0xFF00FF), // fucia new Color(0xDC143C), // crimson new Color(0x00CED1), // dark turquoise new Color(0xFFD700), // gold new Color(0x32CD32), // lime green new Color(0x008080), // teal new Color(0xFFA500), // orange }; private final static Color BACKGROUND_COLORS[] = { new Color(0xFFDAB9), // peachpuff new Color(0xFFC0CB), // pink new Color(0xFF99CC), // hot pink new Color(0x0099CC), // sky blue new Color(0x9966CC), // lavender }; private final static Color BACKGROUND_COLOR = new Color(0x99FFCC); // * ** * * * * // * * * ** ** ** ** // * * ** * * * ** // * // 0 1 2 3 4 5 6 private final static boolean PIECE_BITS[][][] = { { {false, true, false, false}, {false, true, false, false}, {false, true, false, false}, {false, true, false, false}, }, { {false, false, false, false}, {false, true, true, false}, {false, true, false, false}, {false, true, false, false}, }, { {false, false, false, false}, {false, true, false, false}, {false, true, false, false}, {false, true, true, false}, }, { {false, false, false, false}, {false, true, false, false}, {false, true, true, false}, {false, false, true, false}, }, { {false, false, false, false}, {false, false, true, false}, {false, true, true, false}, {false, true, false, false}, }, { {false, false, false, false}, {false, true, false, false}, {false, true, true, false}, {false, true, false, false}, }, { {false, false, false, false}, {false, false, false, false}, {false, true, true, false}, {false, true, true, false}, }, }; private static boolean tmp_grid[][] = new boolean[4][4]; // scratch space private static Random random = new Random(); private static class TetrisLabel extends Label { private final static Font LABEL_FONT = new Font("Serif", Font.BOLD, 18); private TetrisLabel(String text) { super(text); setFont(LABEL_FONT); } private void addValue(int val) { setText(Integer.toString((Integer.parseInt(getText())) + val )); } } // // INSTANCE DATA // private int grid[][] = new int[ROWS][COLUMNS]; private int next_piece_grid[][] = new int[4][4]; private int num_rows_deleted = 0; private GridCanvas game_grid = new GridCanvas(grid, true); private GridCanvas next_piece_canvas = new GridCanvas(next_piece_grid, false); private Timer timer; private TetrisPiece cur_piece; private TetrisPiece next_piece = randomPiece(); private TetrisSound sounds;// = new TetrisSound(this); private TetrisLabel rows_deleted_label = new TetrisLabel("0"); private TetrisLabel level_label = new TetrisLabel("1"); private TetrisLabel score_label = new TetrisLabel("0"); private TetrisLabel high_score_label = new TetrisLabel(""); final Button start_newgame_butt = new TetrisButton("Start"); final Button pause_resume_butt = new TetrisButton("Pause"); // // INNER CLASSES // private class TetrisButton extends Button { public TetrisButton(String label) { super(label); } public Dimension getPreferredSize() { return new Dimension(120, super.getPreferredSize().height); } } private class TetrisPiece { private boolean squares[][]; private int type; private Point position = new Point(3, -4); // -4 to start above top row public int getX() { return position.x; } public int getY() { return position.y; } public void setX(int newx) { position.x = newx; } public void setY(int newy) { position.y = newy; } public void setPosition(int newx, int newy) { setX(newx); setY(newy); } public TetrisPiece(int type) { this.type = type; this.squares = new boolean[4][4]; for(int i=0; i<4; i++) for(int j=0; j<4; j++) this.squares[i][j] = PIECE_BITS[type][i][j]; } public boolean canStepDown() { synchronized(timer) { cut(); position.y++; boolean OK = canPaste(); position.y--; paste(); return OK; } } public boolean canPaste() { for(int i=0; i<4; i++) { for(int j=0; j<4; j++) { int to_x = j + position.x; int to_y = i + position.y; if(squares[i][j]) { // piece contains this square? if(0 > to_x || to_x >= COLUMNS // square too far left or right? || to_y >= ROWS) // square off bottom? { return false; // note: it's always considered OK to paste a square // *above* the grid though attempting to do so does nothing. // This allows the user to move a piece before it drops // completely into view. } if(to_y >= 0 && grid[to_y][to_x] != EMPTY) return false; } } } return true; } public void stepDown() { position.y++; } public void cut() { for(int i=0; i<4; i++) for(int j=0; j<4; j++) if(squares[i][j] && position.y+i>=0) grid[position.y + i][position.x + j] = EMPTY; } /** * Paste the color info of this piece into the given grid */ public void paste(int into[][]) { for(int i=0; i<4; i++) for(int j=0; j<4; j++) if(squares[i][j] && position.y+i>=0) into[position.y + i][position.x + j] = type; } /** * No argument version assumes pasting into main game grid */ public void paste() { paste(grid); } public void rotate() { // copy the piece's data into a temp array for(int i=0; i<4; i++) for(int j=0; j<4; j++) tmp_grid[i][j] = squares[i][j]; // copy back rotated 90 degrees for(int i=0; i<4; i++) for(int j=0; j<4; j++) squares[j][i] = tmp_grid[i][3-j]; } public void rotateBack() { // copy originally saved version back // this of course assumes this call was preceeded by // a call to rotate() for the same piece for(int i=0; i<4; i++) for(int j=0; j<4; j++) squares[i][j] = tmp_grid[i][j]; } // this method is a bit of a hack to check for the case // where a piece may be safely on the grid but have one or more // rows of empty squares that are above the grid and therefore OK public boolean isTotallyOnGrid() { for(int i=0; i<4; i++) { if(position.y + i >= 0) return true; //everything from here down is on grid // this row is above grid so look for non-empty squares for(int j=0; j<4; j++) if(squares[i][j]) return false; } System.err.println("TetrisPiece.isTotallyOnGrid internal error"); return false; } } // end class TetrisPiece private class Timer extends Thread { private long m_delay; private boolean m_paused = true; private boolean m_fast = false; private ActionListener m_cb; public Timer(long delay, ActionListener cb) { setDelay(delay); m_cb = cb; } public void setPaused(boolean pause) { m_paused = pause; if(m_paused) { sounds.stopSoundtrack(); } else { sounds.playSoundtrack(); synchronized(this) { this.notify(); } } } public boolean isPaused() { return m_paused; } public void setDelay(long delay) { m_delay = delay; } public boolean isRunning() { return !m_paused; } public void setFast(boolean fast) { m_fast = fast; if(m_fast) { try { this.checkAccess(); this.interrupt(); // no exception, so OK to interrupt } catch(SecurityException se) {} } } public boolean isFast() { return m_fast; } public void faster() { m_delay = (int)(m_delay * .9); //increase the speed exponentially in reverse } public void run() { while(true) { try { sleep(m_fast ? 30 : m_delay); } catch (Exception e) {} if(m_paused) { try { synchronized(this) { this.wait(); } } catch(InterruptedException ie) {} } synchronized(this) { m_cb.actionPerformed(null); } } } } // end class Timer private class GridCanvas extends DoubleBufferedCanvas { private int grid[][]; private boolean paint_background; public GridCanvas(int[][] grid, boolean do_background) { this.grid = grid; paint_background = do_background; clear(); } private void clear() { for(int i=0; i grid_aspect_ratio) { // extra space on sides cell_size = (int)((double)height/grid.length + 0.5); xstart = (int)(width/2 - (grid[0].length/2.0 * cell_size + 0.5)); ystart = 0; } else { // extra vertical space cell_size = (int)((double)width/grid[0].length + 0.5); xstart = 0; ystart = (int)(height/2 - (grid.length/2.0 * cell_size + 0.5)); } if(paint_background) { g.setColor(BACKGROUND_COLORS[(num_rows_deleted / DELETED_ROWS_PER_LEVEL) % BACKGROUND_COLORS.length]); g.fillRect(xstart, ystart, COLUMNS*cell_size, ROWS*cell_size); } for(int i=0; i soundid || soundid >= destroyRowSounds.length || destroyRowSounds[soundid] == null) return; destroyRowSounds[soundid].play(); } public void playGameOverSound() { if(gameOverSound == null) return; gameOverSound.play(); } public void stopSoundtrack() { if(soundTrack == null) return; soundTrack.stop(); } } // end class TetrisSound // // INSTANCE METHODS // private TetrisPiece randomPiece() { int rand = Math.abs(random.nextInt()); return new TetrisPiece(rand % (PIECE_COLORS.length)); } private void installNewPiece() { next_piece_canvas.clear(); cur_piece = next_piece; cur_piece.setPosition(3, -4); //-4 to start above top of grid if(cur_piece.canPaste()) { next_piece = randomPiece(); next_piece.setPosition(0, 0); next_piece.paste(next_piece_grid); next_piece_canvas.repaint(); } else gameOver(); } private void gameOver() { System.out.println("Game Over!"); timer.setPaused(true); pause_resume_butt.setEnabled(false); int score = Integer.parseInt(score_label.getText()); int high_score = high_score_label.getText().length() > 0 ? Integer.parseInt(high_score_label.getText()) : 0; if(score > high_score) high_score_label.setText("" + score); sounds.playGameOverSound(); } private boolean rowIsFull(int row) { for(int i=0; i0; i--) { for(int j=0; j=0; i--) while(rowIsFull(i)) removeRow(i); game_grid.repaint(); } public void start() { timer = new Timer(INITIAL_DELAY, new ActionListener() { public void actionPerformed(ActionEvent ae) { synchronized(timer) { if(cur_piece.canStepDown()) { cur_piece.cut(); cur_piece.stepDown(); cur_piece.paste(); if(timer.isFast()) score_label.addValue(1); // a small reward for using fast mode } else { // it hit something timer.setFast(false); if( ! cur_piece.isTotallyOnGrid()) gameOver(); else { removeFullRows(); installNewPiece(); } } } game_grid.repaint(); } }); timer.start(); // pauses immediately } public void stop() { pauseGame(); synchronized(timer){ timer.stop(); } timer = null; } private void startGame() { timer.setDelay(INITIAL_DELAY); timer.setPaused(false); start_newgame_butt.setLabel("Start New Game"); pause_resume_butt.setEnabled(true); // stays enabled from here on pause_resume_butt.setLabel("Pause"); pause_resume_butt.validate(); sounds.playSoundtrack(); } private void newGame() { game_grid.clear(); installNewPiece(); num_rows_deleted = 0; rows_deleted_label.setText("0"); level_label.setText("1"); score_label.setText("0"); startGame(); } private void pauseGame() { timer.setPaused(true); pause_resume_butt.setLabel("Resume"); sounds.stopSoundtrack(); } private void resumeGame() { timer.setPaused(false); pause_resume_butt.setLabel("Pause"); sounds.playSoundtrack(); } public void init() { sounds = new TetrisSound(); // NOTE: Must be initialized after Applet fully constructed! installNewPiece(); pause_resume_butt.setEnabled(false); start_newgame_butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if(start_newgame_butt.getLabel().equals("Start")) startGame(); else newGame(); } }); pause_resume_butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if(pause_resume_butt.getLabel().equals("Pause")) pauseGame(); else resumeGame(); } }); //create key listener for rotating, moving left, moving right KeyListener key_listener = new KeyAdapter() { public void keyPressed(KeyEvent e) { if(timer.isPaused()) //don't do anything if game is paused return; if (e.getKeyCode() == 37 || e.getKeyCode() == 39) { //left or right arrow pressed int dir = e.getKeyCode() == 37 ? -1 : 1; synchronized(timer) { cur_piece.cut(); cur_piece.setX(cur_piece.getX() + dir); // try to move if( ! cur_piece.canPaste()) cur_piece.setX(cur_piece.getX() - dir); // undo move cur_piece.paste(); } game_grid.repaint(); } else if (e.getKeyCode() == 38) { //rotate synchronized(timer) { cur_piece.cut(); cur_piece.rotate(); if( ! cur_piece.canPaste()) cur_piece.rotateBack(); cur_piece.paste(); } game_grid.repaint(); } if (e.getKeyCode() == 40) { //down arrow pressed; drop piece timer.setFast(true); } } }; // add the key listener to all components that might get focus // so that it'll work regardless of which has focus start_newgame_butt.addKeyListener(key_listener); pause_resume_butt.addKeyListener(key_listener); Panel right_panel = new Panel(new GridLayout(3, 1)); right_panel.setBackground(BACKGROUND_COLOR); Panel control_panel = new Panel(); control_panel.add(start_newgame_butt); control_panel.add(pause_resume_butt); control_panel.setBackground(BACKGROUND_COLOR); right_panel.add(control_panel); Panel tmp = new Panel(new BorderLayout()); tmp.add("North", new TetrisLabel(" Next Piece:")); tmp.add("Center", next_piece_canvas); tmp.setBackground(BACKGROUND_COLOR); right_panel.add(tmp); Panel stats_panel = new Panel(new GridLayout(4, 2)); stats_panel.add(new TetrisLabel(" Rows Deleted: ")); stats_panel.add(rows_deleted_label); stats_panel.add(new TetrisLabel(" Level: ")); stats_panel.add(level_label); stats_panel.add(new TetrisLabel(" Score: ")); stats_panel.add(score_label); stats_panel.add(new TetrisLabel(" High Score: ")); stats_panel.add(high_score_label); tmp = new Panel(new BorderLayout()); tmp.setBackground(BACKGROUND_COLOR); tmp.add("Center", stats_panel); right_panel.add(tmp); // finaly, add all the main panels to the applet panel this.setLayout(new GridLayout(1, 2)); this.add(game_grid); this.add(right_panel); this.setBackground(BACKGROUND_COLOR); this.validate(); } public static void main(String[] args) { Frame frame = new Frame("Tetris"); tetris tetris = new tetris(); frame.add(tetris); tetris.init(); tetris.start(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setSize(489, 441); frame.setResizable(false); frame.setVisible(true); } } // end class Tetris class DoubleBufferedCanvas extends Canvas { private Image mActiveOffscreenImage = null; private Dimension mOffscreenSize = new Dimension(-1,-1); private Graphics mActiveOffscreenGraphics = null; private Graphics mSystemGraphics = null; DoubleBufferedCanvas() { /* this.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { repaint(); } });*/ } /** * NOTE: when extending applets: * this overrides update() to *not* erase the background before painting */ public void update(Graphics g) { paint(g); } public Graphics startPaint (Graphics sysgraph) { mSystemGraphics = sysgraph; // Initialize if this is the first pass or the size has changed Dimension d = getSize(); if ((mActiveOffscreenImage == null) || (d.width != mOffscreenSize.width) || (d.height != mOffscreenSize.height)) { mActiveOffscreenImage = createImage(d.width, d.height); mActiveOffscreenGraphics = mActiveOffscreenImage.getGraphics(); mOffscreenSize = d; mActiveOffscreenGraphics.setFont(getFont()); } //mActiveOffscreenGraphics.clearRect(0, 0, mOffscreenSize.width, mOffscreenSize.height); return mActiveOffscreenGraphics; } public void endPaint () { // Start copying the offscreen image to this canvas // The application will begin drawing into the other one while this happens mSystemGraphics.drawImage(mActiveOffscreenImage, 0, 0, null); } }