	

    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.length; i++)
                                    for(int j=0; j<grid[0].length; j++)
                                            grid[i][j] = EMPTY;
                    }              
                    public Dimension getPreferredSize() {
                            return new Dimension(grid[0].length * 30, grid.length * 30);
                    }
                    public void paint(Graphics g) {
                            g = this.startPaint(g); // returned g paints into offscreen image
                            int width = this.getSize().width;
                            int height = this.getSize().height;
                            g.clearRect(0, 0, width, height);
                            int cell_size, xstart, ystart;
                            double panel_aspect_ratio = (double)width/height;
                            double grid_aspect_ratio = (double)grid[0].length/grid.length;
                            if(panel_aspect_ratio > 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<grid.length; i++) {
                                    for(int j=0; j<grid[0].length; j++) {
                                            if(grid[i][j] != EMPTY) {
                                                    g.setColor(PIECE_COLORS[grid[i][j]]);
                                                    int x = xstart + j*cell_size;
                                                    int y = ystart + i*cell_size;
                                                    g.fill3DRect(x, y, cell_size, cell_size, true);
                                            }
                                    }
                            }
                            this.endPaint(); // paints accumulated image in one shot
                    }
            } // end class GridCanvas
           
            private class TetrisSound {
                    private AudioClip soundTrack = null;
                    private AudioClip destroyRowSounds[] = new AudioClip[4];
                    private AudioClip gameOverSound = null;
                    private AudioClip getClip(String name) throws MalformedURLException {
                            URL soundFileUrl = new URL(getCodeBase(), name);
                            try {
                                    AudioClip clip = getAudioClip(soundFileUrl);
                                    return clip;
                            } catch(NullPointerException npe) {
                                    System.err.println("exception " + npe);
                                    return null;
                            }
                    }
                    public TetrisSound() {
                            //load sound files
                            try {
                                    soundTrack          = getClip("theme.au");
                                    destroyRowSounds[0] = getClip("quiteImpressive.au");
                                    destroyRowSounds[1] = getClip("smashing.au");
                                    destroyRowSounds[2] = getClip("yeahbaby.au");
                                    destroyRowSounds[3] = getClip("great.au");
                                    gameOverSound       = getClip("groovy.au");
                            }
                            catch (Exception e) {
                                    System.err.println(e.getMessage());
                            }
                    }
                    public void playSoundtrack() {
                            if(soundTrack == null)
                                    return;
                            soundTrack.loop();
                    }
                    public void playDestroyRows(int rows) {
                            int soundid = rows - 1;
                            if(0 > 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; i<COLUMNS; i++)
                            if(grid[row][i] == EMPTY)
                                    return false;
                    return true;
            }
           
            private int countFullRows() {
                    int n_full_rows = 0;
                    for(int i=0; i<ROWS; i++)
                            if(rowIsFull(i))
                                    n_full_rows++;
                    return n_full_rows;
            }      
           
            private void removeRow(int row) {
                    for(int j=0; j<COLUMNS; j++)
                            grid[row][j] = EMPTY;
                    for(int i=row; i>0; i--) {
                            for(int j=0; j<COLUMNS; j++) {
                                    grid[i][j] = grid[i-1][j];
                            }
                    }
            }
           
            private void removeFullRows() {
                    int n_full = countFullRows();
                    score_label.addValue((int)(10 * Math.pow(2, n_full) - 10)); //give points exponentially
                    if(n_full == 0)
                    return;
                    sounds.playDestroyRows(n_full);
                    if(num_rows_deleted / DELETED_ROWS_PER_LEVEL != (num_rows_deleted+n_full) / DELETED_ROWS_PER_LEVEL) {
                            timer.faster();
                            level_label.addValue(n_full / DELETED_ROWS_PER_LEVEL + 1);
                            level_label.repaint();
                    }
                    rows_deleted_label.addValue(n_full);
                    num_rows_deleted += n_full;
                    for(int i=ROWS-1; i>=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);
            }
    }

