// Tetris Applet
// Written by Michael Fried for the Technology House Sci-Li Tetris project
// This code is not meant to be an example of good Object Oriented programming.
// Rather, it is meant to be a functional, minimal, compatible (JDK 1.0) applet.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.applet.*;
import java.util.*;

public class Tetris extends Applet implements Runnable
{
   public static void main(String args[])
   {
      AppletFrame af = new AppletFrame(new Tetris());
      af.setSize(200, 400);
      af.show();
   }

   private Image buffer = null;
   private int appletwidth, appletheight; // The pixel width and height of the applet
   private int rectwidth, rectheight; // The pixel width and height of a tetris block
   private int fieldwidth, fieldheight; // Regulation tetris is 10x16
   private Color colors[][]; // This matrix will hold the colors of all the blocks
   private Color defaultColor; // The default color of the background. (This will be a picture later...)
   private boolean matrix[][]; // This matrix will hold values for testing if the blocks are filled
   private int leftKey, rightKey, dropKey, rotateLeftKey, rotateRightKey, pauseKey;
   private boolean playing = false, paused = false;
   private int linesCleared = 0;
   private int delay = 400; // Update 5 times every 2 seconds.
   private boolean started = false;

   private boolean usingImageMap = false;
   Image IM_blank, IM_full, IM_frame;
   int IM_locx[][], IM_locy[][], IM_width[][], IM_height[][];


   public Tetris()
   {
   }

   public void start()
   {
      if (started) return;
      started = true;
      Dimension s = size();
      appletwidth = s.width;
      appletheight = s.height;
      setBackground(Color.black);
      buffer = createImage(appletwidth, appletheight);
      Graphics g = buffer.getGraphics();
      g.setColor(Color.black);
      g.fillRect(0, 0, appletwidth, appletheight);
      init_applet();
      new Thread(this).start();
   }

   private String getParameter(String name, String defaultValue)
   {
      String retval = null;
      try
      {
         retval = getParameter(name);
      }
      catch (Exception e)
      {
      }
      return (retval == null || retval.equals("")) ? defaultValue : retval;
   }

   private int getParameter(String name, int defaultValue)
   {
      int retval = defaultValue;
      try
      {
         retval = Integer.parseInt(getParameter(name));
      }
      catch (Exception e)
      {
         retval = defaultValue;
      }
      return retval;
   }

   // Set up initial values
   private void init_applet()
   {
      leftKey = getParameter("leftKey", 'j');
      rightKey = getParameter("rightKey", 'l');
      dropKey = getParameter("dropKey", ' ');
      rotateLeftKey = getParameter("rotateLeftKey", 'k');
      rotateRightKey = getParameter("rotateRightKey", 'm');
      pauseKey = getParameter("pauseKey", 'p');

      fieldwidth = getParameter("fieldWidth", 10);
      fieldheight = getParameter("fieldHeight", 10);

      String imagemap = getParameter("imagemap", "");
      if (!(imagemap.equals("")))
      {
         init_imagemap(imagemap);
      }

      defaultColor = Color.black;
      rectwidth = appletwidth / fieldwidth;
      rectheight = appletheight / fieldheight;
      colors = new Color[fieldwidth][fieldheight];
      matrix = new boolean[fieldwidth][fieldheight];
      for (int i = 0; i < fieldwidth; i++)
      {
         for (int j = 0; j < fieldheight; j++)
         {
            colors[i][j] = defaultColor;
            matrix[i][j] = true;
         }
      }
   }

   private void init_imagemap(String imagemap)
   {
      try
      {
         usingImageMap = true;
         URL IM_url = new URL(getDocumentBase(), imagemap);
         DataInputStream dis = new DataInputStream(IM_url.openStream());
         IM_blank = getImage(IM_url, dis.readLine());
         IM_full = getImage(IM_url, dis.readLine());
         IM_frame = getImage(IM_url, dis.readLine());
         System.out.println("Loaded Images...");
         fieldwidth = Integer.parseInt(dis.readLine());
         fieldheight = Integer.parseInt(dis.readLine());
         rectwidth = appletwidth / fieldwidth;
         rectheight = appletheight / fieldheight;
         IM_locx   = new int[fieldwidth][fieldheight];
         IM_locy   = new int[fieldwidth][fieldheight];
         IM_width  = new int[fieldwidth][fieldheight];
         IM_height = new int[fieldwidth][fieldheight];
         for (int j = 0; j < fieldheight; j++)
         {
            for (int i = 0; i < fieldwidth; i++)
            {
               int lx = i*rectwidth, ly = j*rectheight;
               int w = rectwidth - 1, h = rectheight - 1;
               try
               {
                  IM_locx[i][j] = Integer.parseInt(dis.readLine());
                  IM_locy[i][j] = Integer.parseInt(dis.readLine());
                  IM_width[i][j] = Integer.parseInt(dis.readLine());
                  IM_height[i][j] = Integer.parseInt(dis.readLine());
               }
               catch (NumberFormatException nfe)
               {
                  IM_locx[i][j] = lx;
                  IM_locy[i][j] = ly;
                  IM_width[i][j] = w;
                  IM_height[i][j] = h;
               }
            }
         }
      }
      catch (Exception e)
      {
         usingImageMap = false;
      }
   }

   public boolean handleEvent(Event e)
   {
      if (e.id == Event.KEY_ACTION || e.id == Event.KEY_PRESS)
      {
         int key = e.key;
         if (paused)
         {
            paused = false;
         }
         else if (!playing)
         {
            for (int i = 0; i < fieldwidth; i++)
            {
               for (int j = 0; j < fieldheight; j++)
               {
                  colors[i][j] = defaultColor;
                  matrix[i][j] = true;
               }
            }
            playing = true;
            newPiece();
         }
         else
         {
            if (key == pauseKey) paused = !paused;
            else if (key == leftKey) move(-1);
            else if (key == rightKey) move(1);
            else if (key == dropKey) drop();
            else if (key == rotateLeftKey) rotate(-1);
            else if (key == rotateRightKey) rotate(1);
         }
         return true;
      }
      return false;
   }

   // Determine if a filled tetris block is at (x,y)
   public boolean check(int x, int y)
   {
      return (x >= 0 &&
              x < fieldwidth &&
              y < fieldheight && // We can be off the top of the board...
              (y < 0 || matrix[x][y]));
   }

   // Determine if we can place a tetris block at (x,y)
   public boolean checkPlace(int x, int y)
   {
      return (x >= 0 &&
              x < fieldwidth &&
              y >= 0 && // We can not place off the top of the board...
              y < fieldheight &&
              matrix[x][y]);
   }

   Piece currentPiece = null;

   int xarrays[][] = {
      { 0,-1, 1, 0, 0, 0, 0,-1, 0,-1, 1, 0, 0, 0, 0, 1}, // T junction
      { 0, 0, 0, 0,-1, 0, 1, 2, 0, 0, 0, 0, 1, 0,-1,-2}, // Line
      {-1, 0, 0, 1, 0, 0,-1,-1, 1, 0, 0,-1, 0, 0, 1, 1}, // Left zig-zag
      { 1, 0, 0,-1, 0, 0,-1,-1,-1, 0, 0, 1, 0, 0, 1, 1}, // Right zig-zag
      {-1,-1, 0, 1,-1, 0, 0, 0, 1, 1, 0,-1, 1, 0, 0, 0}, // L shape
      { 1, 1, 0,-1,-1, 0, 0, 0,-1,-1, 0, 1, 1, 0, 0, 0}, // Reversed L shape
      { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0}};// Cube
   int yarrays[][] = {
      { 0, 0, 0, 1, 0,-1, 1, 0, 0, 0, 0,-1, 0,-1, 1, 0}, // T junctions
      {-1, 0, 1, 2, 0, 0, 0, 0, 1, 0,-1,-2, 0, 0, 0, 0}, // Line
      { 0, 0,-1,-1, 1, 0, 0,-1, 0, 0, 1, 1,-1, 0, 0, 1}, // Left zig-zag
      { 0, 0,-1,-1,-1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0,-1}, // Right zig-zag
      { 1, 0, 0, 0,-1,-1, 0, 1,-1, 0, 0, 0, 1, 1, 0,-1}, // L shape
      { 1, 0, 0, 0, 1, 1, 0,-1,-1, 0, 0, 0,-1,-1, 0, 1}, // Reversed L shape
      { 0, 0,-1,-1, 0, 0,-1,-1, 0, 0,-1,-1, 0, 0,-1,-1}};// Cube
   Color pieceColors[] = {
      Color.yellow, Color.green, Color.red, Color.blue,
      Color.cyan, Color.orange, Color.pink};

   int tetrisBanner[][] = {
      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
      {0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
      {1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,1},
      {1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1},
      {1,1,1,0,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,1},
      {1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0},
      {1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0},
      {1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,1},
      {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}};
      

   public void newPiece()
   {
      int whichPiece = (int) (Math.random() * 7);
      currentPiece = new Piece(xarrays[whichPiece], yarrays[whichPiece], pieceColors[whichPiece]);
      if (!(currentPiece.showAt(fieldwidth/2, 0)))
      {
         currentPiece = null; // end game
         showStatus("Game Over. Lines cleared:" + linesCleared);
         linesCleared = 0;
         playing = false;
      }
   }

   private void move(int way)
   {
      if (currentPiece.move(way)) repaint();
   }

   private void drop()
   {
      while (currentPiece.moveDown()) {}
   }

   private void rotate(int way)
   {
      if (currentPiece.rotate(way)) repaint();
   }

   private int eliminate()
   {
      int i, j;
      int lines = 0;
      for (i = 0; i < fieldheight; i++)
      {
         boolean tokeep = false;
         for (j = 0; j < fieldwidth; j++)
         {
            tokeep |= matrix[j][i];
         }
         if (!tokeep)
         {
            lines++;
            clear(i);
         }
      }
      return lines;
   }

   private void clear(int row)
   {
      int i,j;
      for (i = row-1; i > 0; i--)
      {
         for (j = 0; j < fieldwidth; j++)
         {
            matrix[j][i+1] = matrix[j][i];
            colors[j][i+1] = colors[j][i];
         }
      }
      for (j = 0; j < fieldwidth; j++)
      {
         matrix[j][0] = true;
         colors[j][0] = defaultColor;
      }
   }

   // Timer to update the board
   public void run()
   {
      int i = 0, l = 0, m = 0;
      boolean done = false;
      while (!done)
      {
         try
         {
            Thread.currentThread().sleep(delay);
         }
         catch (InterruptedException e)
         {
         }
         if (playing && paused) {}
         else if (playing)
         {
            if (!(currentPiece.moveDown()))
            {
               if (currentPiece.place())
               {
                  linesCleared += eliminate();
                  showStatus("Lines cleared:" + linesCleared);
                  newPiece();
               }
               else // The current piece cannot be placed
               {
                  showStatus("Game Over. Lines cleared:" + linesCleared);
                  linesCleared = 0;
                  playing = false;
               }
            }
            // Repaint the board
            repaint();
         }
         else
         {
            linesCleared = 0;
            for (int j = 0; j < fieldwidth; j++)
            {
               for (int k = 0; k < fieldheight; k++)
               {
                  colors[j][k] = pieceColors[(i+j+k) % 7];
                  if (usingImageMap)
                  {
                     colors[j][k] = defaultColor;
                  }
               }
               if (j > 0 && j < fieldwidth - 1)
               for (int k = 0; k < 9; k++)
               {
                  int where = (l - fieldwidth + j + 94) % 47;
                  if (where < l + j && l + j - fieldwidth < 47)
                  {
                     int tb = tetrisBanner[k % 9][where];
                     int which = (i+j+k) % 7;
                     if (tb == 0) colors[j][k] = defaultColor;

                     if (usingImageMap)
                     {
                        if (tb == 0) colors[j][k] = pieceColors[0];
                        else colors[j][k] = defaultColor;
                     }
                  }
               }
            }
            i+=m; i %= 7;
            l++; l %= 55;
            if (l == 0) m = (m + 1) % 7;
            // Repaint the board
            repaint();
         }
      }
   }

   public void paint(Graphics g) {update(g);}

   private void placeImage(Graphics buffer, Image img, int i, int j)
   {
      int lx = IM_locx[i][j], ly = IM_locy[i][j];
      int lx2 = lx + IM_width[i][j], ly2 = ly + IM_height[i][j];
      try
      {
         buffer.drawImage(img, lx, ly, lx2, ly2, lx, ly, lx2, ly2, defaultColor, this);
      }
      catch (NoSuchMethodError e)
      {
         // Netscape might have a broken 1.1 implementation...
         // Therefore we cannot arbitrarily draw and scale an
         // arbitrary frame from the second picture. We'll
         // scale a third picture and place it arbitrarily,
         // But it won't look as real...
         buffer.drawImage(IM_frame, lx, ly, lx2 - lx, ly2 - ly, defaultColor, this);
      }
   }

   // Redraws the board (uses double buffering)
   public void update(Graphics g)
   {
      if (buffer != null)
      {
         Graphics b = buffer.getGraphics();
         if (usingImageMap)
         {
            b.drawImage(IM_blank, 0, 0, defaultColor, this);
            for (int i = 0; i < fieldwidth; i++)
            {
               for (int j = 0; j < fieldheight; j++)
               {
                  if (!(colors[i][j].equals(defaultColor)))
                  {
                     placeImage(b, IM_full, i, j);
                  }
               }
            }
         }
         else
         {
            for (int i = 0; i < fieldwidth; i++)
            {
               for (int j = 0; j < fieldheight; j++)
               {
                  b.setColor(colors[i][j]);
                  b.fillRect(i*rectwidth, j*rectheight, rectwidth-1, rectheight-1);
               }
            }
         }
         g.drawImage(buffer, 0, 0, Color.black, this);
      }
   }

   class Piece
   {
      int x[], y[]; // These coordinate pairs represent the locations of the four blocks in all rotations

      // If there are more blocks/rotations (i.e. battletris like feared weird)
      int blocks;
      int rotations;
      int locx, locy;

      int rotation = 0;
      Color color = Color.white;

      public Piece(int _x[], int _y[], Color _color) {this(_x, _y, _color, 4);}
      public Piece(int _x[], int _y[], Color _color, int _rotations) {this(_x, _y, _color, 4, 4);}
      public Piece(int _x[], int _y[], Color _color, int _blocks, int _rotations)
      {
         x = new int[_x.length];
         y = new int[_y.length];
         for (int i = 0; i < x.length; i++) x[i] = _x[i];
         for (int j = 0; j < y.length; j++) y[j] = _y[j];
         blocks = _blocks;
         rotations = _rotations;
         color = _color;
      }

      public boolean place()
      {
         return place(locx, locy);
      }

      public boolean place(int _x, int _y)
      {
         int i, j = rotation*blocks;
         boolean success = true;
         for (i = 0; i < blocks; i++)
         {
            success &= checkPlace(_x+x[i+j], _y+y[i+j]);
         }
         if (success)
         {
            for (i = 0; i < blocks; i++)
            {
               colors[_x+x[i+j]][_y+y[i+j]] = color;
               matrix[_x+x[i+j]][_y+y[i+j]] = false;
            }
         }
         return success;
      }

      public boolean showAt(int _x, int _y)
      {
         int i, j = rotation*blocks;
         boolean success = true;
         for (i = 0; i < blocks; i++)
         {
            success &= check(_x+x[i+j], _y+y[i+j]);
         }
         if (success)
         {
            for (i = 0; i < blocks; i++)
            {
               if (checkPlace(_x+x[i+j], _y+y[i+j]))
                  colors[_x+x[i+j]][_y+y[i+j]] = color;
            }
            locx = _x;
            locy = _y;
         }
         return success;
      }

      public boolean hideAt(int _x, int _y)
      {
         int i, j = rotation*blocks;
         boolean success = true;
         for (i = 0; i < blocks; i++)
         {
            success &= check(_x+x[i+j], _y+y[i+j]);
         }
         if (success)
         {
            for (i = 0; i < blocks; i++)
            {
               if (checkPlace(_x+x[i+j], _y+y[i+j]))
                  colors[_x+x[i+j]][_y+y[i+j]] = defaultColor;
            }
         }
         return success;
      }

      public boolean moveDown()
      {
         hideAt(locx, locy);
         if (showAt(locx, locy+1)) return true;
         showAt(locx, locy);
         return false;
      }

      public boolean rotate(int i)
      {
         int rot = rotation;
         if (i >= 0)
         {
            hideAt(locx, locy);
            rotation = (rotation + i) % rotations;
            if (showAt(locx, locy))
               return true;
            rotation = rot;
            showAt(locx, locy);
            return false;
         }
         else
         {
            hideAt(locx, locy);
            rotation = (rotation + i + rotations) % rotations;
            if (showAt(locx, locy))
               return true;
            rotation = rot;
            showAt(locx, locy);
            return false;
         }
      }

      public boolean move(int i)
      {
         int oldlocx = locx;
         hideAt(locx, locy);
         locx += i;
         if (showAt(locx, locy)) return true;
         locx = oldlocx;
         showAt(locx, locy);
         return false;
      }
   }
}

class AppletFrame extends Frame implements AppletStub, AppletContext
{
   public static void main(String args[]) throws Exception
   {
      class myClassLoader extends ClassLoader
      {
         Hashtable cache = new Hashtable();
 
         public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
         {
            Class c = (Class) cache.get(name);
            if (c == null)
            {
              c = findSystemClass(name);
              cache.put(name, c);
            }
            if (resolve) resolveClass(c);
            return c;
         }
      };

      new AppletFrame((Applet) new myClassLoader().loadClass(args[0], true).newInstance()).show();
   }

   Applet MyApplet;
   Label statuslabel;

   public AppletFrame(Applet a)
   {
      MyApplet = a;
      //statuslabel = new Label("Applet Frame 1.0 (C) Michael Fried 1998");
      statuslabel = new Label("Techhouse Tetris Applet by Michael Fried");
      add("Center", MyApplet);
      add("South", statuslabel);
      MyApplet.setStub(this);
      MyApplet.init();
      setSize(getPreferredSize());
   }

   public void show()
   {
      super.show();
      MyApplet.start();
   }

   public boolean handleEvent(Event evt)
   {  if (evt.id == Event.WINDOW_DESTROY) 
      {  System.exit(0);
      }
      return super.handleEvent(evt);
   }

   // AppletStub methods
   public boolean isActive() { return true; }
   public URL getDocumentBase() { return null; }
   public URL getCodeBase() { return null; }
   public String getParameter(String name) { return ""; }
   public AppletContext getAppletContext() { return this; }
   public void appletResize(int width, int height) {setSize(width, height);}
   
   // AppletContext methods
   public AudioClip getAudioClip(URL url) { return null; }
   public Image getImage(URL url) { return null; }
   public Applet getApplet(String name) { return null; }
   public Enumeration getApplets() { return null; }
   public void showDocument(URL url) {}
   public void showDocument(URL url, String target) {}
   public void showStatus(String status) {statuslabel.setText(status);}
}

