///////////////////////////////////////////////////////////////////////////////
//                              Lisaac Example                               //
//                                                                           //
//                   LSIIT - ULP - CNRS - INRIA - FRANCE                     //
//                                                                           //
//   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 3 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, see <http://www.gnu.org/licenses/>.   //
//                                                                           //
//                     http://isaacproject.u-strasbg.fr/                     //
///////////////////////////////////////////////////////////////////////////////
Section Header
  
  + name     := TETRIS;
  
  - comment  := "The TETRIS game";
  
  - author   := "Benoit Sonntag (bsonntag@loria.fr)";

  - version  := 1;
  
Section Inherit  
  
  + parent_area:Expanded AREA;
  
  + parent_g_expr:Expanded G_EXPR;
  
Section Private
  
  - level:UINTEGER_8;
  - line_count:UINTEGER_32;
  
  - face:FAST_ARRAY3[INTEGER];
  
  - screen:FAST_ARRAY2[UINTEGER_8];
  
  - face_to_color n:UINTEGER_8 :UINTEGER_32 <-
  ( + result:UINTEGER_32;

    n
    .when 1 then { result := 02020E0h; }  // Blue.
    .when 2 then { result := 0E07020h; }  // Orange.
    .when 3 then { result := 0E0E020h; }  // Yellow.
    .when 4 then { result := 020E0E0h; }  // Cyan.
    .when 5 then { result := 0E02020h; }  // Red.
    .when 6 then { result := 0E020E0h; }  // Magenta.
    .when 7 then { result := 020E020h; }; // Green.
    result
  );
  
  - case_busy (x,y:INTEGER) face n:UINTEGER_8 <-
  ( + x0,y0:INTEGER;
    + col:UINTEGER_32;
    x0 := x << 4;
    y0 := y << 4;
    col := face_to_color n;
    // Fill.
    rectangle_fill (x0+1,y0+1) to (x0+14,y0+14) color col;    
    // More light.
    line_v (x0,y0+14) until y0 color (col + 1F1F1Fh);
    line_h_until (x0+15);
    // More dark.
    line_v_until (y0+15) color (col - 1F1F1Fh);
    line_h_until x0;    
  );
    
  - delete_line <-
  ( + x,y:INTEGER;
    
    y := 21;
    {
      x := 0;
      {(x < 10) && {screen.item (x,y) != 0}}.while_do {
        x := x+1;
      };
      (x = 10).if {
        y.downto 1 do { yy:INTEGER;
          0.to 9 do { xx:INTEGER;
            screen.put (screen.item (xx,yy-1)) to (xx,yy);
          };
        };
        line_count := line_count + 1;
        (((line_count % 10) = 0) && {level < 20}).if {
          level := level + 1;
        };
        refresh;
      } else {
        y := y-1;
      };
    }.do_while {y != 0};
  );
  
  - load_face p:INTEGER <-
  //  0 |  1  |  2  |  3  |  4  |  5  |  6  |
  //----|-----|-----|-----|-----|-----|-----|
  //  1 |     |     |     |     |     |     |
  //  2 | 3 1 | 2   | 3   |   2 |     | 2   |
  //  X | X   | X 1 | X   | X 1 | X 1 | X 1 |
  //  3 | 2   | 3   | 2 1 | 3   | 2 3 |   3 |
  ( + tmp:BOOLEAN;
    + tmp1,tmp2:INTEGER;
    
    // First graph.
    face.put ((p!=0).to_integer)      to (0,1,0);
    face.put ((p-2)*(p<4).to_integer) to (0,1,1);
    tmp1:=((p&1)<<1)-1;
    face.put ((p=4).to_integer) to (0,2,0);
    face.put tmp1               to (0,2,1);    
    tmp:=(p>4);
    tmp2:=tmp.to_integer;
    face.put tmp2                           to (0,3,0);
    face.put (-tmp1*(!tmp).to_integer+tmp2) to (0,3,1);
    
    // 3 rotation (-pi/2).
    1.to 3 do { r:INTEGER;      
      0.to 3 do { c:INTEGER;
        tmp1 := r - 1;
        face.put ( face.item (tmp1,c,1)) to (r,c,0);   // x' = y
        face.put (-face.item (tmp1,c,0)) to (r,c,1);  // y' = -x
      };      
    };    
  );

  - pos_r:INTEGER;
  
  - pos_x:INTEGER;
  
  - pos_y:INTEGER;
  
  - n_face:UINTEGER_8;
  
  - move (x,y:INTEGER) rotation r:INTEGER :BOOLEAN <-
  ( + j,px,py:INTEGER;
    
    j:=0;
    {
      (j < 4) && 
      {(px:=x + face.item (r,j,0)).in_range 0 to 9}  &&
      {(py:=y + face.item (r,j,1)).in_range 0 to 21} &&
      {screen.item (px,py) = 0}
    }.while_do {
      j := j + 1;
    };
    (j = 4).if {
      pos_x := x;
      pos_y := y;
      pos_r := r;
    }
  );
  
  - put_face <-
  ( + x,y:INTEGER;
    0.to 3 do { j:INTEGER;
      x := pos_x + face.item (pos_r,j,0);
      y := pos_y + face.item (pos_r,j,1);
      screen.put n_face to (x,y);
    };
  );

  - remove_face <-
  ( + x,y:INTEGER;
    
    0.to 3 do { j:INTEGER;
      x := pos_x + face.item (pos_r,j,0);
      y := pos_y + face.item (pos_r,j,1);
      screen.put 0 to (x,y);    
    };
  );
  
Section Public  
  
  //
  // Width / Height
  //
  
  - width_min:INTEGER  := 16 * 10;
  
  - height_min:INTEGER := 16 * 22 + 14;
  
  //
  // Creation.
  //  
  
  - main <-
  (     
    // Init window.
    VIDEO.make (640,400);
        
    // Initialization.
    face  :=FAST_ARRAY3[INTEGER]  .create (4,4,2);
    screen:=FAST_ARRAY2[UINTEGER_8].create (10,22);
    tmp_string:=STRING.create 80;
    level := 1;
    stat  := 2;
    random := 015AAC005h;
    
    // Screen definition.
    INTERFACE.make VIDEO size (VIDEO.width,VIDEO.height) with (
      G_OUT.create "Tetris" justify (G_OUT.center) /
      G_RAW.create (
        Self.fix_height.fix_width 
      )
    );    
    INTERFACE.run; 
  );
  
  //
  // Update position.
  //
  
  - set_position rac:AREA at (x,y:INTEGER) size (w,h:INTEGER) <-
  ( 
    update rac from (x,y) size (w,h);
  );
  
  //
  // Display.
  //
  
  - tmp_string:STRING;
  
  - draw (x0,y0:INTEGER) to (x1,y1:INTEGER) <-  
  ( + n:UINTEGER_8;
    + xx,yy:INTEGER;
    
    clipping (x0,y0) to (x1,y1);
    
    // Game array.
    0.to 21 do { y:INTEGER;
      0.to 9 do { x:INTEGER;
        n := screen.item (x,y);
        (n = 0).if {
          xx := x << 4;
          yy := y << 4;
          rectangle_fill (xx,yy) to (xx+15,yy+15) color black;	  
        } else {
          case_busy (x,y) face n;
        };
      };
    };
    
    // Game info.
    rectangle_fill (0,352) to (159,365) color gray;
    color black;
    tmp_string.copy "Level:";    
    level.append_in tmp_string format 2 with '0';
    tmp_string.append " Lines:";
    line_count.append_in tmp_string format 4 with '0';    
    stat
    .when 0 to 1 then { tmp_string.append " Play"; }
    .when 2 then { tmp_string.append " Stop";  }
    .when 3 then { tmp_string.append " Pause"; };
    print tmp_string to (2,351);
  );

  - display_face <-
  ( + x0,y0,x1,y1:INTEGER;
    
    x0:=((pos_x - 3).max  0) << 4;
    x1:=(((pos_x + 3).min  9) << 4) + 15;
    y0:=((pos_y - 3).max  0) << 4;
    y1:=(((pos_y + 3).min 21) << 4) + 15;
    draw (x0,y0) to (x1,y1);
  );
  
  //
  // Events.
  //
  
  - receive msg:EVENT <-
  ( + evt_keyboard:EVENT_KEYBOARD;
    + evt_time:EVENT_TIMER;
    + evt_mouse:EVENT_MOUSE;
    
    evt_keyboard ?= msg;    
    (evt_keyboard != NULL).if {
      keyboard_event (evt_keyboard.key);
    };
    
    evt_mouse ?= msg;
    (evt_mouse != NULL).if {
      mouse_event evt_mouse;
    };
    
    evt_time ?= msg;    
    (evt_time != NULL).if {
      timer_event evt_time;
    };    
  );
  
  - delay:UINTEGER_32;
  - stat:UINTEGER_8;
  - old_stat:UINTEGER_8;
  - random:UINTEGER_32;
  
  - timer_event evt:EVENT_TIMER <-
  ( + speed:UINTEGER_32;    
        
    delay := delay + evt.delay;
    speed := 10 - (level >> 1); 
    (delay > speed).if {
      delay := delay - speed;
      stat
      .when 0 then {
        // Stat #0 : Load new face.
        random := random * random + evt.count;
        n_face := (random % 7).to_uinteger_8; 
        load_face n_face;
        n_face := n_face + 1;
        (move (4,2) rotation 0).if {
          stat := 1;
          put_face;
          display_face;
        } else {
          // Game over.
          stat := 2;
          refresh;
        };
      }
      .when 1 then {
        // Stat #1 : Moving bottom face.
        remove_face;
        (move (pos_x,pos_y+1) rotation pos_r).if {
          put_face;
          display_face;
        } else {
          stat := 0;
          put_face;	  
          delete_line;
        };
      }.when 2 to 3 then {	
        // Stat #2, #3 : Nothing (Stop or Pause game).	
      };
    };
  );
  
  - keyboard_event key:UINTEGER_16 <-
  (
    (((key & 0800h) != 0) && {stat=1}).if {
      remove_face;
      (key & 0FFh).to_character
      .when 'U' then {
        // Rotation.
        move (pos_x,pos_y) rotation ((pos_r+1) & 3);
      }
      .when 'R' then {
        // Right.
        move (pos_x+1,pos_y) rotation pos_r;
      }
      .when 'L' then {
        // Left.
        move (pos_x-1,pos_y) rotation pos_r;
      }
      .when 'D' then {
        // Speed Down.
        {move (pos_x,pos_y+1) rotation pos_r}.while_do {
          put_face;
          display_face;
          remove_face;
        };
      };
      put_face;
      display_face;
      } else {
        ((key & 0FFh).to_character = ' ').if {
          (stat = 2).if {
            // New game.
            screen.clear_all;
            line_count := stat := 0;
            level := 1;
          }.elseif {stat = 3} then {
            // Restart (after pause).
            stat := old_stat;
          } else {
            // Pause.
            old_stat := stat;
            stat := 3;
          };
          refresh;
        };
      };
  );

  - mouse_event evt:EVENT_MOUSE <-
  ( + win:AREA;
    
    win := INTERFACE.get_object (evt.x_current,evt.y_current);
    (win != Self).if {
      // Pause.
      (stat < 2).if {
        old_stat := stat;      
        stat := 3;	
        refresh;
      };
      INTERFACE.receive evt;
    } else {
      (evt.left_down).if {
        (stat = 2).if {
          // New game.
          screen.clear_all;
          line_count := stat := 0;
          level := 1;
        }.elseif {stat = 3} then {
          // Restart (after pause).
          stat := old_stat;
        } else {
          // Pause.
          old_stat := stat;
          stat := 3;
        };
        refresh;
      };
    };
  );
