// @(#)root/test:$Id$
// Author: Valeriy Onuchin & Fons Rademakers   04/10/98

///////////////////////////////////////////////////////////////////
//  ROOT implementation of the simple Tetris game
//  Layout and some hints were taken from Qt /examples/tetris
//
//  To run this game do the following:
//  $ root
//  root [0] gSystem.Load("libGpad")
//  root [1] gSystem.Load("Tetris")
//  root [2] Tetris t
//  <play game>
//  root [2] .q
//
///////////////////////////////////////////////////////////////////

#include <TVirtualX.h>
#include <TGClient.h>
#include <KeySymbols.h>
#include <TRootCanvas.h>
#include <TApplication.h>
#include <TList.h>
#include "Tetris.h"

static Tetris *gTetris;                    // game manager

static const UInt_t gBoxPixelSize = 20;   // size of TetrisBox in pixels


ClassImp(Tetris)

///////////////////////////////////////////////////////////////////
//  TetrisBox - the main brick of the game
///////////////////////////////////////////////////////////////////
TetrisBox::TetrisBox(Int_t x, Int_t y, UInt_t type, TPad* pad) :
   TWbox(0,0,1,1,33,2,1)
{
   // Create brick

   fType = type;
   fPad  = pad;

   //-------  append box to pad
   SetBit(kMustCleanup);
   SetBit(kCanDelete);
   pad->GetListOfPrimitives()->Add(this);
   SetXY(x,y);
}

void TetrisBox::SetX(Int_t x)
{
   // Set X measured in boxes units

   // width in pixels of pad
   Float_t width  = (Float_t)fPad->XtoPixel(fPad->GetX2());

   Coord_t x1 = ((Float_t)x)*gBoxPixelSize/width;
   Coord_t x2 = ((Float_t)x+1)*gBoxPixelSize/width;

   SetX1(x1);
   SetX2(x2);
   fX = x;
}

void TetrisBox::SetY(Int_t y)
{
   // Set Y measured in boxes units

   // height in pixels of pad
   Float_t height = (Float_t)fPad->YtoPixel(fPad->GetY1());

   Coord_t y1 = ((Float_t)y)*gBoxPixelSize/height;
   Coord_t y2 = ((Float_t)y+1)*gBoxPixelSize/height;

   SetY1(y1);
   SetY2(y2);
   fY = y;
}

void TetrisBox::Paint(Option_t *option)
{
   // Paint box if it's not hidden.

   if (!IsHidden() && fPad) TWbox::Paint(option);
}

void TetrisBox::Erase()
{
   // erase box

   Double_t fX1sav = fX1;
   Double_t fY1sav = fY1;
   Double_t fX2sav = fX2;
   Double_t fY2sav = fY2;

   // erase 2 pix extra
   fX1 = fX1-fPad->PixeltoX(2);
   fY1 = fY1+fPad->PixeltoY(2);
   fX2 = fX2+fPad->PixeltoX(2);
   fY2 = fY2-fPad->PixeltoY(2);

   SetFillColor(fPad->GetFillColor());
   SetBorderMode(0);
   Paint();

   fX1 = fX1sav;
   fY1 = fY1sav;
   fX2 = fX2sav;
   fY2 = fY2sav;
}


///////////////////////////////////////////////////////////////////
//  TetrisPiece - tetris piece is set of up to 4 TetrisBoxes
///////////////////////////////////////////////////////////////////
static Int_t gPieceTypes[10][4][2] = {
                                      {{-1,1},   //   *
                                       {-1,0},   //   * *
                                       { 0,0},   //     *
                                       { 0,-1}},

                                      {{ 1, 1},  //    *
                                       { 1, 0},  //  * *
                                       { 0, 0},  //  *
                                       { 0,-1}},

                                      {{ 0, 1},  //   *
                                       { 0, 0},  //   *
                                       { 0,-1},  //   *
                                       { 0,-2}}, //   *

                                      {{ 0, 1},  //    *
                                       { 1, 0},  //  * * *
                                       { 0, 0},
                                       { -1,0}},

                                      {{ 1, 1},  //  * *
                                       { 0, 1},  //  * *
                                       { 1, 0},  //
                                       { 0, 0}}, //

                                      {{ 0, 1},   //   *
                                       { 0, 0},   //   *
                                       { 0,-1},   // * *
                                       { -1,-1}},

                                      {{ 0, 1},   //  *
                                       { 0, 0},   //  *
                                       { 0,-1},   //  * *
                                       { 1,-1}},

                                      {{ 0, 1},    // *
                                       { 0, 1},    //      hidden
                                       { 0, 1},    //      hidden
                                       { 0, 1}},   //      hidden

                                      {{ 0, 1},   //  *
                                       { 0, 0},   //  *
                                       { 0, 1},   //       hidden
                                       { 0, 0}},  //       hidden

                                      {{ 0, 1},   //   *
                                       { 0, 0},   //   *
                                       { 0,-1},   //   *
                                       { 0, 0}}}; //       hidden

static Color_t gPieceColors[10] = { 2,3,4,5,6,7,13,9,28,41 };


TetrisPiece::~TetrisPiece()
{
   // Clean up piece.

   for (int i = 0; i < 4; i++) delete fBoxes[i];
}

void TetrisPiece::Initialize(UInt_t type, TPad* pad)
{
   for (int i = 0; i < 4; i++) fBoxes[i] = new TetrisBox(0,0,0,pad);
   fX = 0;
   fY = 0;
   SetType(type);
}

void TetrisPiece::SetType(UInt_t type)
{
   // re-initialization

   if (type < 1 || type > 10)
      type = 9;

   for (int i = 0 ; i < 4 ; i++) {
      fBoxes[i]->SetType(type);
      fBoxes[i]->SetFillColor(gPieceColors[type-1]);
      fBoxes[i]->SetX(gPieceTypes[type-1][i][0]+fX);
      fBoxes[i]->SetY(gPieceTypes[type-1][i][1]+fY);
   }
   HideSomeBoxes(type);
   fType = type;
}

void TetrisPiece::HideSomeBoxes(UInt_t type)
{
   // Make invisible some boxes for 1,2,3 length pieces

   switch(type) {
      case 8:  fBoxes[1]->Hide();
      case 9:  fBoxes[2]->Hide();
      case 10: fBoxes[3]->Hide();
      default: return;
   }
}

Bool_t TetrisPiece::RotateRight()
{
   // Rotate anticlockwise  around (fX,fY) point

   // don't rotate square pieces
   if (GetType() == 5 || GetType() == 8) return kFALSE;

   Int_t tmp;

   for (int i = 0 ; i < 4 ; i++) {
      tmp = GetXx(i);
      SetXx(i,-GetYy(i));
      SetYy(i,tmp);
   }
   return kTRUE;
}

Bool_t TetrisPiece::RotateLeft()
{
   // Rotate clockwise  around (fX,fY) point

   // don't rotate square pieces
   if (GetType() == 5 || GetType() == 8) return kFALSE;

   Int_t tmp;

   for (int i = 0 ; i < 4 ; i++) {
      tmp = GetXx(i);
      SetXx(i,GetYy(i));
      SetYy(i,-tmp);
   }
   return kTRUE;
}

void  TetrisPiece::Hide()
{
   // Hide this piece

   for (int i = 0 ; i < 4 ; i++) {
      fBoxes[i]->Hide();
   }
}

void  TetrisPiece::Show()
{
   // Show this piece

   for (int i = 0 ; i < 4 ; i++) {
      fBoxes[i]->SetType(fType);
   }
   HideSomeBoxes(fType);
}

void  TetrisPiece::SetX(Int_t x)
{
   // Change of X position of the whole piece

   for (int i = 0 ; i < 4 ; i++) SetX(i,x+GetXx(i));
   fX = x;
}

void TetrisPiece::SetY(Int_t y)
{
   // Change of Y position of the whole piece

   for (int i = 0 ; i < 4 ; i++) SetY(i,y+GetYy(i));
   fY = y;
}

void TetrisPiece::SetXY(Int_t x,Int_t y)
{
   // Change of X,Y position of the whole piece

   for (int i = 0 ; i < 4 ; i++) {
      SetX(i,x+GetXx(i));
      SetY(i,y+GetYy(i));
   }
   fX = x;
   fY = y;
}


///////////////////////////////////////////////////////////////////
//  CurrentPiece =  TetrisPiece + TTimer = live TetrisPiece
///////////////////////////////////////////////////////////////////
CurrentPiece::CurrentPiece(UInt_t type,TetrisBoard* board) :
   TetrisPiece(type,board), TTimer(1000,kTRUE)
{
   // Initialize new piece

   fBoard = board;

   Int_t line      = fBoard->GetHeight()-2;
   Int_t xPosition = fBoard->GetWidth()/2;

   SetXY(xPosition,line);

   if (!CanMoveTo(xPosition,line))    { gTetris->StopGame(); return; }

   fBoard->Modified();
   fBoard->Update();
   fBoard->SetDropped(kFALSE);
   SetSpeed();       // set speed of moving according to game level
   Start();          // add this timer to sytem timers list = start moving
}

void CurrentPiece::MoveTo(int x, int y)
{
   // Move this to (x,y) and  draw it there

   Erase();
   SetXY(x,y);            // set new coordinates
   fBoard->Modified();    // drawing
   fBoard->Update();
}

Bool_t CurrentPiece::CanMoveTo(int x, int y)
{
   // Can move this piece to (x,y)?

   Bool_t return_value;

   int  savX = fX;
   int  savY = fY;

   SetXY(x,y);                    // set new coordinates
   return_value = CanPosition();  // if inside board and no non-zero squares underneath
   SetXY(savX,savY);              // go back

   return return_value;
}

Bool_t CurrentPiece::CanPosition()
{
   // Check if piece position is allowed

   int x, y;

   for (int i = 0 ; i < 4 ; i++) {
      GetXY(i,x,y);      // coordinates of piece boxes at test position
      if (x < 0 || x >= fBoard->GetWidth()  ||
          y < 0 || y >= fBoard->GetHeight() ||
          !fBoard->IsEmpty(x,y))          return kFALSE;
   }
   return kTRUE;      // Inside board and no non-zero squares underneath.
}

Bool_t CurrentPiece::RotateRight()
{
   // Rotate clockwise. Returns kTRUE if succeeded.

   Bool_t return_value;

   Erase();
   TetrisPiece::RotateRight();
   return_value = CanPosition();
   if (!return_value) TetrisPiece::RotateLeft();    // rotate back

   fBoard->Modified();    // drawing
   fBoard->Update();
   return return_value;
}

Bool_t CurrentPiece::RotateLeft()
{
   // Rotate anti clockwise. Returns kTRUE if succeeded.

   Bool_t return_value;

   Erase();
   TetrisPiece::RotateLeft();
   return_value = CanPosition();
   if (!return_value) TetrisPiece::RotateRight();     // rotate back

   fBoard->Modified();    // drawing
   fBoard->Update();
   return return_value;
}

Bool_t CurrentPiece::OneLineDown()
{
   // Move one line down. Returns kTRUE if succeeded.

   int y = GetY();
   int x = GetX();

   y--;
   if (!CanMoveTo(x,y))  return kFALSE;

   MoveTo(x,y);
   return kTRUE;
}

Bool_t CurrentPiece::DropDown()
{
   // Move the piece to lowest allowed line. Returns kTRUE if succeeded.

   int y = GetY();
   int x = GetX();
   int dropHeight = 0;

   while (CanMoveTo(x,--y)) dropHeight++;  //  find lower allowed line

   y++;
   MoveTo(x,y);         //  .. and move to
   Stop();              //  stop moving
   fBoard->PieceDropped(this, dropHeight);
   return kTRUE;
}

Bool_t CurrentPiece::MoveLeft(int steps)
{
   // Move piece to the left. Return kTRUE if succeeded.

   int y = GetY();
   int x = GetX();

   while(steps) {
      if (!CanMoveTo(--x ,y)) return kFALSE;  // can't move
      MoveTo(x,y);
      steps--;
   }
   return kTRUE;
}

Bool_t CurrentPiece::MoveRight(int steps)
{
   // Move piece to the right. Return kTRUE if succeeded.

   int y = GetY();
   int x = GetX();

   while(steps) {
      if (!CanMoveTo(++x,y)) return kFALSE;  // can't move
      MoveTo(x,y);
      steps--;
   }
   return kTRUE;
}

Bool_t CurrentPiece::Notify()
{
   // Actions after time out.

   if (OneLineDown()) {
      TTimer::Reset();
      return kFALSE;
   } else {
      Stop();                        // stop moving
      fBoard->PieceDropped(this,0);  // piece can't move -> stop moving update state of TetrisBoard
      return kTRUE;
  }
}

void CurrentPiece::SetSpeed()
{
   // Set speed according to level.

   const Int_t factor = 2;
   SetTime(1000/(1 + gTetris->GetLevel()*factor));
}

void  CurrentPiece::Paint(Option_t*)
{
   // Paint it in fBoard.

   TPad* padsav = (TPad*)TVirtualPad::Pad();
   fBoard->cd();

   for (int i = 0 ; i < 4 ; i++) {
      fBoxes[i]->SetBorderMode(1);
      fBoxes[i]->SetFillColor(gPieceColors[fType-1]);
      fBoxes[i]->Paint();
   }
   padsav->cd();
}

void CurrentPiece::Erase()
{
   // Erase = paint with the same FillColor as fBoard has

   TPad* padsav = (TPad*)TVirtualPad::Pad();
   fBoard->cd();

   for (int i = 0 ; i < 4 ; i++) {
      fBoxes[i]->Erase();
   }
   padsav->cd();
}


///////////////////////////////////////////////////////////////////
//  Game  board
///////////////////////////////////////////////////////////////////
TetrisBoard::TetrisBoard(Float_t xlow, Float_t ylow,Float_t xup,Float_t yup) :
   TPad("tetris_board","Tetris Board",xlow,ylow,xup,yup,17,4,-1)
{
   // Game board constructor.

   fWidth  = (int)(fMother->XtoAbsPixel(GetX2())*(xup-xlow))/gBoxPixelSize;
   fHeight = (int)(fMother->YtoAbsPixel(GetY1())*(yup-ylow))/gBoxPixelSize;
   Double_t box = fMother->PixeltoX(gBoxPixelSize);
   Double_t xx = xlow + box*fWidth;

   if (xx<xup) {
      xx += fMother->PixeltoX(1);
      SetPad(xlow, ylow, xx, yup);
   }

   fBoard = new TetrisBoxPtr[fWidth*fHeight];
   fFilledLines = 0;
   Clear();
}

void TetrisBoard::Clear(Option_t *)
{
   // Delete/clear all objects.

   GetListOfPrimitives()->Delete();   // delete all object in this pad (including TetrisPiece)

   for (int i = 0; i < fWidth; i++)
      for (int j = 0; j < fHeight; j++)
         Board(i,j) = 0;              // clear board map

   fIsDropped = kTRUE;
}

void TetrisBoard::Hide()
{
   // Hide all objects.

   TetrisBox *box;
   TIter nextin(GetListOfPrimitives());

   while ((box = (TetrisBox*)nextin())) box->Hide();
   Modified();
   Update();
}

void TetrisBoard::Show()
{
   // Show all objects

   TetrisBox *box;
   TIter nextin(GetListOfPrimitives());

   while ((box = (TetrisBox*)nextin())) box->Show();
   Modified();
   Update();
}

Bool_t TetrisBoard::IsFullLine(Int_t line)
{
   // Check if line is full.

   Bool_t return_value = kTRUE;

   for (int i = 0; i < fWidth; i++)
      return_value = return_value && !IsEmpty(i,line);

   return return_value;
}

Bool_t TetrisBoard::IsEmptyLine(Int_t line)
{
   // Check if line is empty

   Bool_t return_value = kTRUE;

   for (int i = 0; i < fWidth; i++)
      return_value = return_value && IsEmpty(i,line);

   return return_value;
}

void TetrisBoard::RemoveLine(Int_t line)
{
   // Remove all TetrisBoxes of the line

   for (int i=0; i<fWidth; i++) {
      if (Board(i,line))  // when you delete TObject it's also removed from Pad
      delete Board(i,line);

      Board(i,line) = 0;
   }
}

void TetrisBoard::MoveOneLineDown(Int_t line)
{
   // All  boxes of this line move to (line-1)

   if (!line) return;   // don't move line==0

   for (int i = 0; i < fWidth; i++) {
      if (!IsEmpty(i,line)) {
         Board(i,line)->MoveOneLineDown();     // change  position of Boxes
         Board(i,line-1) = Board(i,line);      // remapping
      }
      Board(i,line)=0;   // this line become empty
   }
   Modified();
   Update();
}

void TetrisBoard::RemoveFullLines()
{
   // Remove full lines

   for (int i = 0; i < fFilledLines; i++) {
      while (IsFullLine(i)) {
         RemoveLine(i);
         gTetris->UpdateLinesRemoved();
         AllAboveLinesDown(i);
         fFilledLines--;
      }
   }
}

void  TetrisBoard::GluePiece(TetrisPiece* piece)
{
   // Add pointers to piece boxes to fBoard::fBoard

   int x,y;
   TetrisBox *box;

   for (int i = 0 ; i < 4 ; i++) {
     piece->GetXY(i,x,y);
     box = piece->GetTetrisBox(i);
     if (box->IsHidden()) { delete  box; continue;}     // delete hidden boxes
     Board(x,y) = piece->GetTetrisBox(i);     // add pointers to piece boxes to board map
     if (y>fFilledLines) fFilledLines = y+1;  // update number of non empty lines
   }
}

void TetrisBoard::PieceDropped(TetrisPiece* piece, int height)
{
   // Actions after piece was droped

   Int_t add2score = height*gTetris->GetLevel() + 10;    // update score policy (could be modified)

   fIsDropped = kTRUE;
   GluePiece(piece);
   RemoveFullLines();
   //Print();             // possible printig of board map on the terminal

   gTetris->UpdatePiecesDropped();
   gTetris->UpdateScore(add2score);
   gTetris->CreateNewPiece();        // create new CurrentPiece
   fIsDropped = kFALSE;
}

void TetrisBoard::Print(const char *) const
{
   // Used for testing

   printf("\n");

   for (int j = fHeight-1; j > -1; j--) {
      for (int i = 0; i < fWidth; i++)
         ((TetrisBoard*)this)->IsEmpty(i,j) ? printf("|   ") : printf("| * ") ;
      printf("|\n");
   }
}

void TetrisBoard::PaintModified()
{
   // Overload this method to acelerate graphics
   // (do not draw tens of heap boxes while current box is moving)

   if (!fIsDropped && gTetris->IsGameOn() && !gTetris->IsPaused())
      gTetris->fCurrentPiece->Paint();
   else
      TPad::PaintModified();
}

///////////////////////////////////////////////////////////////////
//  NextPiecePad
//  used to show next piece.
///////////////////////////////////////////////////////////////////
NextPiecePad::NextPiecePad(Float_t xlow, Float_t ylow, Float_t xup, Float_t yup)
   : TPad("next_piece","Next Piece Pad",xlow,ylow,xup,yup,17,4,-1)
{
   // Next piece pad ctor.

   fPiece = new TetrisPiece(this);
   fPiece->Hide();        // hide piece at start

   // (how to get pixel size?)
   Int_t x = (int)(fMother->XtoAbsPixel(GetX2())*(xup-xlow))/gBoxPixelSize/2;
   Int_t y = (int)(fMother->YtoAbsPixel(GetY1())*(yup-ylow))/gBoxPixelSize/2;

   fPiece->SetXY(x,y);    // move to the center of pad
   Modified(kTRUE);
   Update();
}


///////////////////////////////////////////////////////////////////
//  PauseButton - push button
//  ExecuteEvent mehtod used to pause the game
///////////////////////////////////////////////////////////////////
PauseButton::PauseButton(Float_t xlow, Float_t ylow, Float_t xup, Float_t yup) :
   TButton("Pause"," ",xlow,ylow,xup,yup)
{
   // Pause button constructor

   SetBorderSize(5);     //  decoration stuff....
   SetTextSize(0.45);
   SetFillColor(42);
}

void PauseButton::ExecuteEvent(Int_t event, Int_t, Int_t)
{
   // Action after mouse click

   if (event == kButton1Up) {
      IsPressed() ? gTetris->Continue() :  gTetris->Pause();
      Modified(kTRUE);
   }
}


///////////////////////////////////////////////////////////////////
//  QuitButton - push button
//  ExecuteEvent mehtod used to quit the game
///////////////////////////////////////////////////////////////////
QuitButton::QuitButton(Float_t xlow, Float_t ylow, Float_t xup, Float_t yup) :
   TButton("Quit"," ",xlow,ylow,xup,yup)
{
   // Quit button constructor

   SetBorderSize(5);   //  decoration stuff....
   SetTextSize(0.45);
   SetFillColor(42);
}

void QuitButton::ExecuteEvent(Int_t event, Int_t, Int_t)
{
   // Action after mouse click

   if (event == kButton1Up) gTetris->Quit();
}


///////////////////////////////////////////////////////////////////
//  NewGameButton - push button
//  ExecuteEvent mehtod used to start new game the game
///////////////////////////////////////////////////////////////////
NewGameButton::NewGameButton(Float_t xlow, Float_t ylow, Float_t xup, Float_t yup)
   : TButton("New Game"," ",xlow,ylow,xup,yup)
{
   // New game button constructor

   SetBorderSize(5);
   SetTextSize(0.45);
   SetFillColor(42);
}

void NewGameButton::ExecuteEvent(Int_t event, Int_t, Int_t)
{
   // Ation after mouse click

   if (event == kButton1Up) {
      gTetris->NewGame();      // always starts new game
      Modified(kTRUE);
   }
}


///////////////////////////////////////////////////////////////////
//  InfoPad -
///////////////////////////////////////////////////////////////////
InfoPad::InfoPad(const char* title, Float_t xlow, Float_t ylow, Float_t xup, Float_t yup)
   : TPad("info_pad",title,xlow,ylow,xup,yup,17,4,-1), TAttText(22,0,2,71,0.65)
{
   // InfoPad constructor

   SetBit(kCanDelete);

   TText *text = new TText(xlow,yup,title);   // draw title of the information pad
   text->SetTextSize(0.45*(yup-ylow));
   text->SetY(yup+0.2*text->GetTextSize());
   fMother->GetListOfPrimitives()->Add(text);

   text = new TText(0.5,0.5,"0");          // this text used to display fValue
   GetListOfPrimitives()->Add(text);

   fValue = 0;
   Modified(kTRUE);
   Update();
}

void InfoPad::PaintModified()
{
   // Actions after pad was modified (resize event, user's Modified(kTRUE) ...)

   char    str[40];

   snprintf(str,40,"%d",fValue);

   TObject *obj = GetListOfPrimitives()->First();

   if (obj && obj->InheritsFrom("TText")) {
      TText *text = (TText*)obj;
      text->SetTitle(str);                // set title according to fValue

      text->SetTextSize(GetTextSize());
      text->SetTextFont(GetTextFont());
      text->SetTextAlign(GetTextAlign());
      text->SetTextColor(GetTextColor());
      text->SetTextAngle(GetTextAngle());
      text->TAttText::Modify();

      text->SetX(0.5);        // draw centered
      text->SetY(0.5);
   }
   TPad::PaintModified();
}


///////////////////////////////////////////////////////////////////
//  KeyHandler - virtual frame used to catch and handle key events
///////////////////////////////////////////////////////////////////
KeyHandler::KeyHandler() : TGFrame(gClient->GetRoot(),0,0)
{
   // Key handler constructor.

   // get main frame of Tetris canvas
   TRootCanvas *main_frame = (TRootCanvas*)(gTetris->GetCanvasImp());

   // bind arrow keys and space-bar key
   main_frame->BindKey((const TGWindow*)this, gVirtualX->KeysymToKeycode(kKey_Up),    0);
   main_frame->BindKey((const TGWindow*)this, gVirtualX->KeysymToKeycode(kKey_Left),  0);
   main_frame->BindKey((const TGWindow*)this, gVirtualX->KeysymToKeycode(kKey_Right), 0);
   main_frame->BindKey((const TGWindow*)this, gVirtualX->KeysymToKeycode(kKey_Down),  0);
   main_frame->BindKey((const TGWindow*)this, gVirtualX->KeysymToKeycode(kKey_Space), 0);
}

KeyHandler::~KeyHandler()
{
   // Cleanup key handler.

   // get main frame of Tetris canvas
   TRootCanvas *main_frame = (TRootCanvas*)(gTetris->GetCanvasImp());

   // remove binding of arrow keys and space-bar key
   main_frame->RemoveBind(this, gVirtualX->KeysymToKeycode(kKey_Up),    0);
   main_frame->RemoveBind(this, gVirtualX->KeysymToKeycode(kKey_Left),  0);
   main_frame->RemoveBind(this, gVirtualX->KeysymToKeycode(kKey_Right), 0);
   main_frame->RemoveBind(this, gVirtualX->KeysymToKeycode(kKey_Down),  0);
   main_frame->RemoveBind(this, gVirtualX->KeysymToKeycode(kKey_Space), 0);
   // restore key auto repeat functionality, was turned off in TGMainFrame::HandleKey()
   gVirtualX->SetKeyAutoRepeat(kTRUE);
}


Bool_t KeyHandler::HandleKey(Event_t *event)
{
   // Handle arrow and spacebar keys

   char tmp[2];
   UInt_t keysym;

   gVirtualX->LookupString(event, tmp, sizeof(tmp), keysym);

   if (event->fType == kGKeyPress) {
      switch ((EKeySym)keysym) {
         case kKey_Left:
            gTetris->MoveLeft();
            break;
         case kKey_Right:
            gTetris->MoveRight();
            break;
         case kKey_Down:
            gTetris->RotateRight();
            break;
         case kKey_Up:
            gTetris->RotateLeft();
            break;
         case kKey_Space:
            gTetris->DropDown();
            break;
         default:
            return kTRUE;
      }
   }
   return kTRUE;
}

///////////////////////////////////////////////////////////////////
//  UpdateLevelTimer
///////////////////////////////////////////////////////////////////
UpdateLevelTimer::UpdateLevelTimer(ULong_t time) : TTimer(time,kTRUE)
{
   // Update level timer constructor

   SetBit(kCanDelete);   // delete this when gTetris is deleted
   gTetris->GetListOfPrimitives()->Add(this);
}

Bool_t UpdateLevelTimer::Notify()
{
   // Actions after time out

   if (!gTetris->IsGameOn()) {
      Remove();
      return kTRUE;
   }
   gTetris->UpdateLevel();
   TTimer::Reset();
   return kFALSE;
}


///////////////////////////////////////////////////////////////////
//   Tetris =  Game manager
///////////////////////////////////////////////////////////////////
Tetris::Tetris() :
   TCanvas("Tetris","Have a little fun with ROOT!",200,200,700,500)
{
   // Tetris constructor

   gTetris = this;

   fCurrentPiece = 0;

   //-----------  play board ------------
   fBoard            = new  TetrisBoard(0.35,0.05,0.7,0.95);
   fBoard->Draw();

   // ----------  info pads -------------
   fNextPiece        =  new  NextPiecePad(0.05,0.65,0.25,0.95);
   fNextPiece->Draw();

   fLinesRemoved     =  new  InfoPad("Lines Removed",0.75,0.8,0.95,0.9);
   fLinesRemoved->Draw();

   fLevel            =  new  InfoPad("Level",0.75,0.6,0.95,0.7);
   fLevel->Draw();

   fScore            =  new  InfoPad("Score",0.75,0.4,0.95,0.5);
   fScore->Draw();

   //------------ buttons ----------------
   fNewGame          =  new  NewGameButton(0.05,0.05,0.25,0.15);
   fNewGame->Draw();

   fQuit             =  new  QuitButton(0.05,0.2,0.25,0.3);
   fQuit->Draw();

   fPause            =  new  PauseButton(0.05,0.35,0.25,0.45);
   fPause->Draw();

   fPiecesDropped = 0;
   SetFillColor(21);

   fKeyHandler = new KeyHandler();
   fUpdateLevelTimer = new UpdateLevelTimer(60000);  // every  minute
   SetFixedSize();
   Update();
   PrintHelpInfo();
   fEditable = kFALSE;
}

void Tetris::PrintHelpInfo()
{
   // Prints help info

   printf("\n\n\n");
   printf("             Move   Piece Left     ---------     left-arrow\n");
   printf("             Move   Piece Right    ---------     right-arrow\n");
   printf("             Rotate Piece          ---------     up/down-arrow \n");
   printf("             Drop   Piece Down     ---------     space-bar\n");
   printf("\n\n\n");
}

void Tetris::CreateNewPiece()
{
   // Create  current and next pieces

   UInt_t type = fNextPiece->GetPiece()->GetType();
   fNextPiece->NewPiece();
   fCurrentPiece = new CurrentPiece(type,fBoard);
}

void Tetris::SetFixedSize()
{
   // Set size of canvas

   ((TRootCanvas*)fCanvasImp)->SetWMSizeHints(fCw,fCh+20,fCw,fCh,0,0);
}

void Tetris::Quit()
{
   // Stop game and delete canvas (i.e. tetris itself)

   delete fKeyHandler; fKeyHandler = 0;
   StopGame();
   ((TRootCanvas*)fCanvasImp)->CloseWindow();
}

void Tetris::NewGame()
{
   // Start new game

   gVirtualX->SetInputFocus(((TRootCanvas*)fCanvasImp)->GetId());

   if (IsGameOn()) StopGame();       // stop privious game
   fScore->Reset();
   fLinesRemoved->Reset();
   fPiecesDropped = 0;
   SetLevel(1);
   fUpdateLevelTimer->Start();
   fBoard->Clear();
   fNewGame->SetPressed(kTRUE);
   CreateNewPiece();                 // start game
}

void Tetris::StopGame()
{
   // Stop the game

   fUpdateLevelTimer->Stop();
   if (fCurrentPiece) fCurrentPiece->Stop();
   fNewGame->SetPressed(kFALSE);
   fPause->SetPressed(kFALSE);
}

void Tetris::Pause()
{
   // Pause the game

   if (!IsGameOn())  return;
   if (fCurrentPiece) fCurrentPiece->Stop();
   fPause->SetPressed(kTRUE);
   fBoard->Hide();
}

void  Tetris::Continue()
{
   // Continue the game

   if (!IsGameOn()) return;
   fBoard->Show();
   fPause->SetPressed(kFALSE);
   if (fCurrentPiece) fCurrentPiece->Start();
}

void Tetris::MoveLeft()
{
   // Move pice to the left

   if (!IsGameOn() || IsPaused() || IsWaiting())  return;
   fCurrentPiece->MoveLeft();
}

void Tetris::MoveRight()
{
   // Move piece to the right

   if (!IsGameOn() || IsPaused() || IsWaiting())  return;
   fCurrentPiece->MoveRight();
}

void Tetris::DropDown()
{
   // Drop piece down

   if (!IsGameOn() || IsPaused() || IsWaiting())  return;
   fCurrentPiece->DropDown();
}

void Tetris::RotateRight()
{
   // Rotate piece right

   if (!IsGameOn() || IsPaused() || IsWaiting())  return;
   fCurrentPiece->RotateRight();
 }

void Tetris::RotateLeft()
{
   // Rotate piece left

   if (!IsGameOn() || IsPaused() || IsWaiting())  return;
   fCurrentPiece->RotateLeft();
 }

void Tetris::SetLevel(int level)
{
   // Set difficulty level

   fLevel->SetValue(level);
   if (fCurrentPiece) fCurrentPiece->SetSpeed();
}
