(*
   National russian game "Tetris"
   Created by Alexey Pajitnov
   Programmed in JOB by S.Sverdlov
   8.03.98/23.04.98

   <applet code="Tetris_Game" width=180 height=360>
   </applet>
*)

MODULE Tetris;

IMPORT
   app := javaapplet,
   awt := javaawt,
   ut  := javautil,
   Timers, SYSTEM;

CONST
   n = 24;
   m = 12;
   s = 14;

   UP    = 1004;
   DOWN  = 1005;
   LEFT  = 1006;
   RIGHT = 1007;

TYPE
   Field    = ARRAY n, m OF awt.PColor;
   Figure   = ARRAY 4, 4 OF BOOLEAN;
   Figures  = ARRAY 7 OF Figure;
   Colors   = ARRAY 7 OF awt.PColor;

   PGame* = POINTER TO Game;
   Game* = RECORD (app.Applet)
      g        : awt.PGraphics;
      field    : Field;
      figs     : Figures;
      bc       : awt.PColor;
      cols     : Colors;
      ncurr    : INTEGER;
      curr     : Figure;
      x, y     : INTEGER;
      timer    : Timers.Timer;
      rand     : ut.PRandom;
      fly      : BOOLEAN;
      timeevent: BOOLEAN;
      keyevent : BOOLEAN;
   END;

PROCEDURE ( a : PGame ) paint*( g : awt.PGraphics );
VAR
   i, j  : INTEGER;
   x, y  : INTEGER;
BEGIN
   y := 0;
   FOR i := 0 TO n-1 DO
      x := 0;
      FOR j := 0 TO m-1 DO
         g.setColor(a.field[i,j]);
         g.fillRect(x,y,s,s);
         INC( x, s+1 );
      END;
      INC( y, s+1 );
   END;
END paint;

PROCEDURE InitField( a : PGame );
VAR
   i, j  : INTEGER;
BEGIN
   FOR i := 0 TO n-1 DO
      FOR j := 0 TO m-1 DO
         a.field[i,j] := a.bc;
      END;
   END;
END InitField;

PROCEDURE Draw( a : PGame; col : awt.PColor );
VAR
   i, j  : INTEGER;
   x, y  : INTEGER;
BEGIN
   y := a.y;
   FOR i := 0 TO 3 DO
      x := a.x;
      FOR j := 0 TO 3 DO
         IF a.curr[i,j] & ( 0 <= x ) & ( x < m ) & ( 0 <= y ) & ( y < n ) THEN
            a.g.setColor(col);
            a.g.fillRect(x*(s+1),y*(s+1),s,s);
         END;
         INC(x);
      END;
      INC(y);
   END;
END Draw;

PROCEDURE Free( a : PGame; VAR f : Figure; x0, y0 : INTEGER ) : BOOLEAN;
VAR
   i, j  : INTEGER;
   x, y  : INTEGER;
   F     : BOOLEAN;
BEGIN
   F := TRUE;
   i := 0; y := y0;
   REPEAT
      j := 0; x := x0;
      REPEAT
         IF f[i,j] &
            ( (x<0) OR (x>=m) OR (y>=n) OR
              (y>=0) & (a.field[y,x] # a.bc) )
         THEN
            F := FALSE;
         END;
         INC(j); INC(x);
      UNTIL ~F OR (j=4);
      INC(i); INC(y);
   UNTIL ~F OR (i=4);
   RETURN F;
END Free;

PROCEDURE Move( a : PGame; dx, dy : INTEGER );
BEGIN
   Draw( a, a.bc );
   INC( a.x, dx );  INC( a.y, dy );
   Draw( a, a.cols[a.ncurr] );
END Move;

PROCEDURE Fix( a : PGame );
VAR
   i, j  : INTEGER;
   x, y  : INTEGER;
BEGIN
   y := a.y;
   FOR i := 0 TO 3 DO
      x := a.x;
      FOR j := 0 TO 3 DO
         IF a.curr[i,j] & ( 0<=x ) & ( x<m ) & ( 0<=y ) & ( y<n ) THEN
            a.field[y,x] := a.cols[a.ncurr];
         END;
         INC(x);
      END;
      INC(y);
   END;
END Fix;

PROCEDURE RowEmpty( a : PGame; r : INTEGER ) : BOOLEAN;
VAR
   e  : BOOLEAN;
   j  : INTEGER;
BEGIN
   j := m;
   REPEAT
      DEC(j);
      e := a.field[r,j] = a.bc;
   UNTIL (j=0) OR ~e;
   RETURN e;
END RowEmpty;

PROCEDURE ClearRow( a : PGame; r : INTEGER );
BEGIN
   WHILE (r>0) & ~RowEmpty(a, r) DO
      a.field[r] := a.field[r-1];
      DEC(r);
   END;
   a.paint(a.g);
END ClearRow;

PROCEDURE RowFull( a : PGame; r : INTEGER ) : BOOLEAN;
VAR
   j     : INTEGER;
   full  : BOOLEAN;
BEGIN
   j := m;
   REPEAT
      DEC(j);
      full := a.field[r,j] # a.bc;
   UNTIL (j=0) OR ~full;
   RETURN full;
END RowFull;

PROCEDURE TestRows( a : PGame );
VAR
   i, j  : INTEGER;
   r     : INTEGER;
BEGIN
   i := 3;
   r := a.y + i;
   REPEAT
      IF ( r < n ) & ( r>=0 ) & RowFull( a, r ) THEN
         ClearRow( a, r );
      ELSE
         DEC(r);
      END;
      DEC(i);
   UNTIL i<0;
END TestRows;

PROCEDURE FigDown( a : PGame );
BEGIN
   IF Free( a, a.curr, a.x, a.y+1 ) THEN
      Move( a, 0, 1 )
   ELSE
      a.fly := FALSE;
      Fix(a);
      TestRows(a);
   END;
END FigDown;

PROCEDURE Rotate( f : Figure; VAR fr : Figure );
VAR
   i, j : INTEGER;
BEGIN
   i := 0; j := 3;
   REPEAT
      fr[i,0] := f[0,j];
      fr[3,i] := f[i,0];
      fr[j,3] := f[3,i];
      fr[0,i] := f[i,3];
      INC(i); DEC(j);
   UNTIL j<0;
   fr[1,1] := f[1,2];
   fr[2,1] := f[1,1];
   fr[2,2] := f[2,1];
   fr[1,2] := f[2,2];
END Rotate;

PROCEDURE FigRotate(a : PGame);
VAR
   temp : Figure;
BEGIN
   Rotate( a.curr, temp );
   IF Free( a, temp, a.x, a.y ) THEN
      Draw( a, a.bc );
      a.curr := temp;
      Draw( a, a.cols[a.ncurr] );
   END;
END FigRotate;

PROCEDURE FigRelease(a : PGame);
BEGIN
   WHILE Free( a, a.curr, a.x, a.y+1 ) DO
      Move( a, 0, 1 );
   END;
END FigRelease;

PROCEDURE FigRight(a : PGame);
BEGIN
   IF Free( a, a.curr, a.x+1, a.y ) THEN
      Move( a, 1, 0 );
   END;
END FigRight;

PROCEDURE FigLeft(a : PGame);
BEGIN
   IF Free( a, a.curr, a.x-1, a.y ) THEN
      Move( a, -1, 0 );
   END;
END FigLeft;

PROCEDURE NewColor( r, g, b : INTEGER ) : awt.PColor;
VAR
   c : awt.PColor;
BEGIN
   SYSTEM.NEW( c, r, g, b );
   RETURN c;
END NewColor;

PROCEDURE InitColors( a : PGame );
BEGIN
   a.bc := NewColor( 220, 220, 220 );

   a.cols[0] := NewColor( 255, 0, 0 );
   a.cols[1] := NewColor( 255, 128, 0 );
   a.cols[2] := NewColor( 255, 255, 0 );
   a.cols[3] := NewColor( 0, 255, 0 );
   a.cols[4] := NewColor( 0, 255, 255 );
   a.cols[5] := NewColor( 0, 0, 255 );
   a.cols[6] := NewColor( 255, 0, 255 );
END InitColors;

PROCEDURE InitFigures( a : PGame );

VAR
   i, j, k  : INTEGER;
BEGIN
   FOR i := 0 TO 6 DO
      FOR j := 0 TO 3 DO
         FOR k := 0 TO 3 DO
            a.figs[i,j,k] := FALSE;
         END;
      END;
   END;

   a.figs[0,1,0] := TRUE;
   a.figs[0,1,1] := TRUE;
   a.figs[0,1,2] := TRUE;
   a.figs[0,1,3] := TRUE;

   a.figs[1,1,2] := TRUE;
   a.figs[1,2,0] := TRUE;
   a.figs[1,2,1] := TRUE;
   a.figs[1,2,2] := TRUE;

   a.figs[2,1,0] := TRUE;
   a.figs[2,1,1] := TRUE;
   a.figs[2,1,2] := TRUE;
   a.figs[2,2,2] := TRUE;

   a.figs[3,1,1] := TRUE;
   a.figs[3,1,2] := TRUE;
   a.figs[3,2,1] := TRUE;
   a.figs[3,2,2] := TRUE;

   a.figs[4,1,0] := TRUE;
   a.figs[4,1,1] := TRUE;
   a.figs[4,2,1] := TRUE;
   a.figs[4,2,2] := TRUE;

   a.figs[5,0,1] := TRUE;
   a.figs[5,1,1] := TRUE;
   a.figs[5,1,2] := TRUE;
   a.figs[5,2,2] := TRUE;

   a.figs[6,1,0] := TRUE;
   a.figs[6,1,1] := TRUE;
   a.figs[6,1,2] := TRUE;
   a.figs[6,2,1] := TRUE;
END InitFigures;

PROCEDURE Rnd( r : ut.PRandom; n : INTEGER ) : INTEGER;
BEGIN
   RETURN SHORT(ENTIER(n*r.nextFloat()));
END Rnd;

PROCEDURE NewFigure( a : PGame );
VAR
   rot   : INTEGER;
BEGIN
   a.x := m DIV 2 - 2;
   a.y := -2;
   a.ncurr := Rnd( a.rand, 7 );
   a.curr := a.figs[a.ncurr];
   rot := Rnd( a.rand, 4 );
   WHILE rot > 0 DO
      Rotate( a.curr, a.curr );
      DEC(rot);
   END;
END NewFigure;

PROCEDURE TimerStep( a : PGame );
BEGIN
   IF a.fly THEN
      FigDown(a);
   ELSE
      NewFigure(a);
      IF Free( a, a.curr, a.x, a.y ) THEN
         Draw(a, a.cols[a.ncurr]);
         a.fly := TRUE;
      ELSE
         InitField(a);
         a.paint(a.g);
      END;
   END;
END TimerStep;

PROCEDURE ( a : PGame ) keyDown*( e : awt.PEvent; key : INTEGER ) : BOOLEAN;
BEGIN
   REPEAT UNTIL ~a.timeevent;
   a.keyevent := TRUE;
   CASE key OF
   |UP      : FigRotate(a);
   |DOWN    : FigRelease(a);
   |LEFT    : FigLeft(a);
   |RIGHT   : FigRight(a);
   ELSE
   END;
   a.keyevent := FALSE;
   RETURN TRUE;
END keyDown;

PROCEDURE ( a : PGame ) handleEvent*( e : awt.PEvent ) : BOOLEAN;
BEGIN
   IF ( e.id = Timers.TimerEVENT ) & ~a.keyevent THEN
      a.timeevent := TRUE;
      TimerStep(a);
      a.timeevent := FALSE;
      RETURN TRUE;
   END;
   RETURN a.handleEvent^(e);
END handleEvent;

PROCEDURE ( a : PGame ) init*();
BEGIN
   InitColors( a );
   InitFigures( a );
   InitField( a );
   SYSTEM.NEW( a.rand );
   a.g := a.getGraphics();
   a.fly := FALSE;
   a.timer.Start( a, 400 );
   a.timeevent := FALSE;
   a.keyevent := FALSE;
END init;

END Tetris.