package org.sc3d.apt.tetris.v1;

import java.awt.*;

/** A tiny Java Applet that plays tetris. The goal is to make the source file as
 * short as possible. The name of the package and class may not be changed, so
 * as not to provide an incentive to be thoroughly inconvenient. The Applet must
 * die cleanly. For example, it must not leave any Threads running (at least not
 * for ever...). The rules must be exactly as in the original, so the board must
 * be 10x25 and all seven tetrominoes must be about equally probable and so on.
 * Also, the tetrominoes must be different colours.
 * <p>This is a first attempt. It seems silly to spend time making the program
 * illegible for now, so I've included liberal comments and white space. These
 * can easily be taken out in the final version.
 * <p>Currently, the Applet occupies 80x200 pixels and has no configuration
 * parameters. */
public class Tetris extends java.applet.Applet implements Runnable {

  /** The state of the board, represented as a 13x26 grid. */
  int[] b = new int[338];
  
  /** The direction in which the block will move next. The encoding is as
   * follows: <ul>
   * <li> 0 - left
   * <li> 1 - drop
   * <li> 2 - right
   * <li> 3 - rotate
   * </ul>This is currently fairly arbitrary. Room for improvement. */
  int m;

  /** An abbreviation for 13. */
  int s = 13;

  /** General purpose loop counter. */
  int i;

  /** General purpose loop counter. */
  int n;

  /** Position of the block. */
  int p;

  /** Orientation of the block (4, 16, 18, 28). */
  int d = 4;

  /** Used to flag collisions. '0' means no collision. */
  int t;

  /** Random seed. We use a 'char' because it is unsigned. */
  char r = 1;
  
  /** Called by the browser to redraw the Applet. Draws 10x25 pixels of 'b'
   * starting at top-left scaled up 8 times, with colours 0 to 7 interpreted as
   * black, blue, green, ..., white.
   * <p>It is terribly inefficient to draw every pixel every frame, including
   * some off the screen, especially as we construct a new Color for each one.
   * Sometimes you can watch it chug. However it is simple and short.
   * <p>We can save a byte by overriding 'paint()' instead, but that incurs
   * flicker. By overriding 'update()' we get no flicker, but the applet fails
   * to redraw once the game has finished. I think we can live with that.
   */
  public void update(Graphics g){
    for (i=0; i<325; i++) {
      g.setColor(new Color(255 * (b[i]*16513 & 65793)));
      g.fillRect(i%s*8, i/s*8, 8, 8);
    }
  }
  
  /** Called by the browser when the Applet appears. Starts the animation
   * Thread. */
  public void start() {
    new Thread(this).start();
  }
  
  /** The game. */
  public synchronized void run() {
    // Fill the board.
    for (i=10; i<338; b[i++]=7);
    
    // This loop iterates once for each block, and terminates when a block
    // lands in its start position.
    while (p!=43) {

      // Clear any whole rows. This also clears the board at the beginning, and
      // corrupts 'm' usefully between blocks.
      for (m=0; m<325; )
        if (b[m++]==0) m += s - m%s;
        else if (m%s == 0) for (i=m; i>s; ) b[--i] = b[i-s];
        // Equivalent to:  System.arraycopy(b, 0, b, s, m-s);
      
      // Pick a piece roughly at random, and position the piece at (5, 3).
      // The piece is 'r%7'. Arithmetic overflows make the sequence appear
      // random, but unfortunately its period is only 16384.
      r *= p = 43;
      
      // This loop iterates once for each movement of the block.
      // We break out if we collide while moving down.
      // This should be improved.
      while (m!=1 || t==0) {
        // Work out which way the piece should move.
        m = 1; // Drop.
        // The following command will time-out after 60 milliseconds, or
        // when 'handleEvent()' notifies us, whichever is sooner.
        // We've got to get rid of this try block somehow!
        try { wait(600); } catch (Exception e) {}
        
        // Loop through three phases: delete, check, draw.
        for (i=2; 0<=i--; ) {
          
          // Zero the collision counter except when drawing.
          t &= i>>1;
        
          // Move or unmove the piece. Must improve this. Some potential.
          if (i==0) {
            if (m==3) d *= 13;
            else p += m-1 + s*(m&1);
          }
          if (t>0) {
            if (m==3) d *= 21;
            else p -= m-1 + s*(m&1);
          }
          
          // Loop through 2x4 rectangle.
          for (n=0; n<8; n++) {

            // If piece has a solid square. Must be able to ditch some of
            // those brackets!
            if (0 < ((1<<n) & (1 | (0x32C630921A2L >> (6*(r%7)))))) {
              // Read the square.
              t += b[p];
              
              // Delete or draw the square. I expect we can do better here.
              if (i!=0) b[p] = i<0 ? r%7 + 1 : 0;
            }

            // Move and sometimes turn.
            d = d * (1 + 6*(n&2)) % 34;
            p += d - 17;
          }
        }
        
        // This is naughty: we're not in the event Thread! Let's see if it
        // works...
        repaint();
      }
    }
  }
  
  /** Called by the browser on many occasions, including key-presses. */
  public synchronized boolean handleEvent(Event e) {
    if (e.id == 401) {
      // Key-press.
      m = e.key & 3;
      notify();
    }
    return 0>0; // Doesn't matter what we return.
  }
}
