Java program for Tetris Game.

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 {
 
 //
 // STATIC MEMBERS
 //
 
 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);
 }
}
Java program for Tetris Game. Java program for Tetris Game. Reviewed by on December 20, 2019 Rating: 5

No comments:

Powered by Blogger.