PTetriS.java.html [eNTitánok: Kincskereső Kisgömböc]
/* PTetriS.java -- PTetriS v0.3, a really minimalistic Tetris clone
 * (C) <pts@fazekas.hu> in Early October 2001.
 *
 * This is a JDK1.1 applet and application, and does graphics with Java AWT.
 * See EOF for revision history.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Credits: color palette, geometry and brick shapes are modelled after
 * FUXOFT's Tetris2 (architecture: ZX/Spectrum).
 */

import java.applet.Applet;
import java.awt.Color;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowEvent;
import java.awt.Graphics;

public class PTetriS extends Applet implements RunnableKeyListenerFocusListenerMouseListenerWindowListener {
  // vvv these are the 1st column in VIM's syntax.txt
  //     color values are stolen from TETRIS2 for ZX/Spectrum
  static final Color black=new Color(0),
                     darkblue=new Color(0xbf)// ??
                     darkgreen=new Color(0xbf00)// ??
                     darkcyan=new Color(0xbfbf),
                     darkred=new Color(0xbf0000),
                     darkmagenta=new Color(0xbf00bf),
                     brown=new Color(0xbfbf00)// rather darkyellow
                     lightgray=new Color(0xbfbfbf),
                     darkgray=new Color(0x5f5f5f)// ??
                     blue=new Color(0xff)// ??
                     green=new Color(0xff00),
                     cyan=new Color(0xffff),
                     red=new Color(0xff0000)// ??
                     magenta=new Color(0xff00ff),
                     yellow=new Color(0xffff00),
                     white=new Color(0xffffff),
                     BG=black,
                     BORDER=darkmagenta,
                     MSG=white,
                     C[]={darkcyan,darkgreen,lightgray,cyan,yellow,green,brown};
  // sorry, no java classes for the shapes :-(
  static final String S[][]={
   {"......#.......#.",
    "####..#.####..#.",
    "......#.......#.",
    "......#.......#."},
   
   {"................",
    ".##..##..##..##.",
    ".##..##..##..##.",
    "................"},

   {"................",
    "..##.#....##.#..",
    ".##..##..##..##.",
    "......#.......#."},

   {"................",
    ".##...#..##...#.",
    "..##.##...##.##.",
    ".....#.......#.."},

   {"................",
    "..#...#.......#.",
    ".###.##..###..##",
    "......#...#...#."},

   {"................",
    ".#....#.......##",
    ".###..#..###..#.",
    ".....##....#..#."},

   {"................",
    "...#.##.......#.",
    ".###..#..###..#.",
    "......#..#....##"},
  };
  static final String titlee="PTetriS v0.3 (C) pts@fazekas.hu, GNU GPL";

  // vvv I'm not sure whether we require these, so I've included them. Maybe
  //     we won't be able to get focus if we don't have these.
  public void focusGained(FocusEvent e) {}
  public void focusLost(FocusEvent e) {}

  public void windowActivated(WindowEvent e) { /*requestFocus();*/ } /*useless*/
  public void windowDeactivated(WindowEvent e) {} // stop(); comment OK
  public void windowClosed(WindowEvent e) {}
  public void windowClosing(WindowEvent e) {
    ((Frame)e.getComponent()).setVisible(false);
    destroy();
  }
  public void windowOpened(WindowEvent e) { requestFocus()}
  public void windowIconified(WindowEvent e) { stop()}
  public void windowDeiconified(WindowEvent e) { start()} // BRR, Linux

  // vvv these MouseEvent handlers are _required_ for JDK1.1+appletviewer,
  //     because we can get KeyEvents only if we've already got the keyboard
  //     focus, and we don't have focus by default :-(( 
  public void mouseClicked(MouseEvent e) {
    requestFocus();
    th_playing=!th_playing;
    // System.out.println("Klikk.");
    try { synchronized(this) { notify()} } catch (Exception ee) {}
  }
  public void mouseReleased(MouseEvent e) {}
  public void mousePressed(MouseEvent e) {}
  public void mouseEntered(MouseEvent e) { requestFocus()}
  public void mouseExited(MouseEvent e) {}
  
  // vvv no need for an extra Canvas, we'll use our Applet.
  // static class Palya extends Canvas {
  static final int WDB=14, HTB=24, // pályaméret, 2 vastag kerettel
                   BS=20, // block size, in pixels
                   WD=WDB*BS, HT=HTB*BS;
  static final Dimension d=new Dimension(WD,HT);
  public void setSize(int w, int h) { super.setSize(WD,HT)}
  public void setSize(Dimension d) { super.setSize(WD,HT)}
  public Dimension getPreferredSize() { return d; }
  public Dimension getMinimumSize() { return d; }
  public Dimension getMaximumSize() { return d; }
  // vvv for Java version 1.1.7 compatibility
  static final int VK_KP_UP=0xE0, VK_KP_DOWN=0xE1, VK_KP_LEFT=0xE2, VK_KP_RIGHT=0xE3;

  long releaseWhen=0L;

  public void keyPressed(KeyEvent e) {
    //System.out.println(e.getKeyCode());
    //System.out.println(e.getKeyChar());
    if (!th_playing) return;
    char c=Character.toUpperCase(e.getKeyChar());
    int kc=e.getKeyCode();
    if (kc==VK_KP_UP || kc==e.VK_UP) c='R';
    else if (kc==VK_KP_DOWN || kc==e.VK_DOWN ) c='F';
    else if (kc==VK_KP_LEFT || kc==e.VK_LEFT ) c='D';
    else if (kc==VK_KP_RIGHT|| kc==e.VK_RIGHT) c='G';
    if (c=='F' && no_fast_downfall && releaseWhen==e.getWhen()) c='X';
    if (c=='R' || c=='D' || c=='F' || c=='G') {
      no_fast_downfall=false;
      synchronized(t) {
        int sbx=bx, sba=ba, sby=by;
        putBrick(BG);
        if (c=='R') ba=(ba+1)%4;
        else if (c=='D') bx--;
        else if (c=='F') by++;
        else if (c=='G') bx++;
        if (!isBrickOK()) { bx=sbx; ba=sba; by=sby; }
        putBrick(C[bt]);
      }
      repaint();
    }
  }

  public void keyReleased(KeyEvent e) {
    // no_fast_downfall=false;
    // System.out.println();
    releaseWhen=e.getWhen();
  }
  public void keyTyped(KeyEvent e) { }
  
  Color t[][]// t[y][x]
  /**
   * x,y block offset of current brick from the top-left
   */
  int bx, by;
  /**
   * type of current brick: 0..6
   */
  int bt;
  /**
   * angle of current brick: 0..3
   */
  int ba;
  Thread th;
  boolean th_to_stop;
  boolean th_playing;
  boolean no_fast_downfall=false;

  /**
   * Queries current glyph validity.
   * @return true iff current glyph is valid, i.e no collissions
   */
  boolean isBrickOK() {
    String s[]=S[bt];
    int x, y;
    for (y=0;y<4;y++) for (x=0;x<4;x++) {
      if (s[y].charAt(x+ba*4)!='.' && t[y+by][x+bx]!=BG) return false;
    }
    return true;
  }

  /**
   * Puts the current brick into <code>this.t</code>.
   * @param c uses the specified color.
   */
  void putBrick(Color c) {
    String s[]=S[bt];
    int x, y;
    for (y=0;y<4;y++) for (x=0;x<4;x++) {
      if (s[y].charAt(x+ba*4)!='.') t[y+by][x+bx]=c;
    }
  }

  void newBrick() {
    bt=(int)(Math.random()*7);
    by=-1+2; bx=3+2; ba=0;
  }
  
  void newGame() {
    int x, y;
    Color u[];
    t=new Color[HTB+2][];
    for (y=0;y<HTB;y++) {
      t[y]=u=new Color[WDB+2];
      u[0]=u[1]=u[WDB-2]=u[WDB-1]=BORDER;
      for (x=2;x<WDB-2;x++) u[x]=BG;
    }
    for (x=2;x<WDB-2;x++) t[0][x]=t[1][x]=t[HTB-2][x]=t[HTB-1][x]=BORDER;
    newBrick();
    putBrick(C[bt]);
  }

  void removeFullLines() {
    int x, yup=HTB-3, ydown=HTB-3;
    boolean hole;
    while (true) {
      for (hole=false;!hole && yup>=2;yup--) {
        for (x=2;x<WDB-2;x++) if (t[yup][x]==BG) { hole=truebreak}
      }
      if (!hole) break;
      for (x=2;x<WDB-2;x++) t[ydown][x]=t[yup+1][x];
      ydown--;
    }
    for (;ydown>=2;ydown--) for (x=2;x<WDB-2;x++) t[ydown][x]=BG;
  }

  /**
   * This is ugly, slow, and not double-buffered. Those goals are beyond the
   * scope of this code. (Maybe beyond the scope of efficient Java??)
   */
  public void paint(Graphics g) {
    int x, y;
    Color u[];
    for (y=0;y<HTB;y++) { u=t[y];
      for (x=0;x<WDB;x++) {
        g.setColor(u[x]);
        g.fillRect(x*BS,y*BS,BS,BS);
      }
    }
  }
  public void update(Graphics g) { paint(g)}


  //Palya p;
  public void init() {
    //System.out.println("Hi!");
    setSize(WD,HT);
    setBackground(green);
    // setResizable(false); // undefined method
    //setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
    setLayout(new BorderLayout(0,0));
    requestFocus()/*useless*/
    addKeyListener(this);
    addFocusListener(this);
    addMouseListener(this);
    requestFocus()/*useless*/
    //add(p=new Palya());
    th=new Thread(this)// timer+game logic thread
    th.setDaemon(true)// so it won't prevent the process from exiting
    newGame();
    th.start();
  }

  boolean n1ss;
  public void start() {
    requestFocus()/*useless*/
    if (n1ss) {
      th_playing=true;
      try { synchronized(this) { notify()} } catch (Exception ee) {}
    }
    n1ss=true;
  }
  public void stop() {
    th_playing=false;
  }

  public boolean running_as_application;
  public void destroy() {
    try { th_to_stop=true; th.interrupt()} catch (Exception e) {}
    if (running_as_application) System.out.println("Bye!");
    if (running_as_application) System.exit(0);
  }

  /**
   * This gets run in Thread <code>this.th</code>.
   */
  public void run() {
    boolean have_game=true;
    while (!th_to_stop) {
      try { synchronized(this) {
        if (!th_playing) {
          wait()// the monitor is the Applet object itself.
          // System.out.println("Woken up."); // DEBUG
          continue;
        }
      } } catch(InterruptedException e) { continue}
      // System.out.println("Time is going"); // DEBUG
      synchronized (t) {
        if (!have_game) {
          newGame();
          have_game=true;
        } else {
          putBrick(BG); by++;
          if (isBrickOK()) {
            putBrick(C[bt]);
          } else {
            // brick reached its final position
            no_fast_downfall=true// next brick will begin falling slowly
            by--; putBrick(C[bt]);
            removeFullLines();
            newBrick();
            if (isBrickOK()) {
              putBrick(C[bt]);
            } else {
              if (running_as_application)
                System.out.println("Game Over. Click on the window for a new game.");
              have_game=false;
              th_playing=false;
            }
          }
        }
      }
      repaint();
      try { Thread.sleep(300)} catch (InterruptedException e) {} //millis
    }
  }
  /**
   * Only for the Application version.
   */
  static class PtsFrame extends Frame {
    static final Dimension d=new Dimension(WD,HT);

// BUG01 fixed at Fri Dec 28 15:01:03 CET 2001
// Advice: never ever override these methods of Frame/Window:
//    public void setSize(int w, int h) {}
//    public void setSize(Dimension d) {}
//    public Dimension getPreferredSize() { return d; }
//    public Dimension getMinimumSize() { return d; }
//    public Dimension getMaximumSize() { return d; }

    public PtsFrame() {
      setBackground(magenta);
      setTitle(titlee);
      //super.setSize(d);
      // ^^^ BUG01 fixed. Advice: never-ever call Frame/Window#setSize
      setResizable(false);
    }
  }

  /**
   * Only for the Application version.
   */
  public static void main(String argv[]) {
    System.out.println("This is "+titlee);
    System.out.println(
      "http://borsodi.iit.bme.hu/entitanok/ptetris/\n"+
      "THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK!\n"+
      "This program is free software, covered by the GNU GPL.\n"+
      "\n"+
      "Please resize window if it's too small!\n"+
      "Click on the window to start a new game.\n"+
      "Click on the window to pause/continue current game.\n"+
      "Close the window to exit from the program.\n"+
      "Use the keys D, F, G, R or the arrow keys to control the falling brick.\n"+
      "The goal of the game is survival.\n"+
      "If you create a row without holes, it disappears immediately.\n"
    );
    PtsFrame f=new PtsFrame();
    PTetriS p=new PTetriS();
    p.running_as_application=true;
    // vvv only FlowLayout can guarantee that PTetriS won't get resized
    //f.setLayout(new BorderLayout(0,0)); // bad!!
    f.addWindowListener(p);
    f.add(p);
    f.doLayout()/*useless*/
    p.init();
    f.pack();
    // ^^^ helps (solves) the Linux/X11/JDK1.1 problem (BUG01)
    //     Advice: just put a Component with the proper size inside
    //     Window/Frame, and then call Frame#pack(). Never ever call
    //     Frame#setSize() or Frame#size(), never ever override
    //     Frame#get(preferred|minimum|maximum)Size. You may call
    //     Frame#setLayout and Frame#show after this.
    f.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
    f.show();
    p.start();
  }
}

/* Revision history (CHANGES, ChangeLog):
 *
 * v0.1 Tue Oct  2 19:58:32 CEST 2001 -- Tue Oct  2 23:30:29 CEST 2001
 *      (3.5 hours of work) playable version, HTML, applet, application
 * v0.2 Tue Oct  2 23:37:58 CEST 2001
 *      Dedicated to Rév Szilvia, added more docs, added licensing, added
 *      help at application startup. Source converted to HTML etc.
 * v0.3 Fri Dec 28 15:07:02 CET 2001
 *      BUG01 solved at last. Slower falls. Faster downfall cancelled for
 *      the next brick.
 */