/*------------------------------------------------------------------------------
  
  File        : tetris.cpp

  Description : A CImg version of the famous Tetris game

  Author      : David Tschumperlé
    
  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.
  
  -----------------------------------------------------------------------------*/

#include "../CImg.h"
using namespace cimg_library;
// The lines below are not necessary in your own code, it simply allows 
// the source compilation with compilers that do not respect the C++ standart.
#if ( defined(_MSC_VER) && _MSC_VER<=1200 ) || defined(__DMC__)
#define std
#endif

int main(int argc,char **argv) {

  // Read command line argument (if any)
  cimg_usage("An implementation of the well known 'Tetris' game in few lines");
  unsigned int 
    blocdim = cimg_option("-blocdim",16,"Sprite bloc size"),
    speed   = cimg_option("-speed",20,"Initial speed"),
    level   = cimg_option("-level",0,"Level");
  const char *geometry = cimg_option("-g","12x20","Size of the board");
  unsigned int bwidth = 12,bheight = 20; 
  std::sscanf(geometry,"%d%*c%d",&bwidth,&bheight);

  // Create sprite and background graphics, and initial board data
  const CImgl<unsigned char> pieces = CImgl<unsigned char>().
    insert(CImg<unsigned char>(3,2).fill(1,1,1,0,0,1)).
    insert(CImg<unsigned char>(3,2).fill(2,2,2,2,0,0)).
    insert(CImg<unsigned char>(2,2).fill(3,3,3,3)).
    insert(CImg<unsigned char>(4,1).fill(4,4,4,4)).
    insert(CImg<unsigned char>(3,2).fill(5,5,0,0,5,5)).
    insert(CImg<unsigned char>(3,2).fill(0,6,6,6,6,0)).
    insert(CImg<unsigned char>(3,3).fill(0,7,0,7,7,7,0,7,0)).
    insert(CImg<unsigned char>(2,1).fill(8,8)).
    insert(CImg<unsigned char>(3,2).fill(9,9,9,0,9,0)).
    insert(CImg<unsigned char>(2,2).fill(10,10,0,10)).
    insert(CImg<unsigned char>(3,1).fill(11,11,11));

  CImg<unsigned char> board(bwidth,bheight,1,1,0), background(board.width*blocdim,board.height*blocdim,1,3);
  (background.fill(0).noise(30).draw_plasma().noise(30).deriche(10,0,'y'))/=3;
  if (level) (board.ref_lineset(board.height-level,board.height-1).noise(100))%=pieces.size+1;  

  // Create a set of small gradient-colored blocs used to draw the tetris pieces.
  CImgl<unsigned char> blocs(pieces.size,blocdim,blocdim,1,3);
  cimgl_map(blocs,l) {
    CImg<unsigned char> color = CImg<unsigned char>(3).fill(0).noise(255,1).cut(100,255);
    float val;
    cimg_mapXYV(blocs[l],x,y,k) blocs[l](x,y,k) = (unsigned char)((val=(color[k]*0.7f*(x+y+5)/blocdim))>255?255:val);
    blocs[l].draw_line(0,0,0,blocdim-1,(color/2).ptr()).draw_line(0,blocdim-1,blocdim-1,blocdim-1,(color/2).ptr());
    color = (CImg<unsigned int>(color)*2).cut(0,255);
    blocs[l].draw_line(0,0,blocdim-1,0,color.ptr()).draw_line(blocdim-1,0,blocdim-1,blocdim-1,color.ptr());
  }

  // Initialize window display and enter the main event loop
  CImgDisplay disp(background,"CImg Tetris",0);
  const unsigned char white[3]={255,255,255};
  CImg<unsigned char> piece, next, next_mask;
  int cx=-1,cy=-1,cn=-1,nn=rand()%pieces.size,time=0, score=0;
  bool gameover = false, pause = false;

  while (!gameover && !disp.closed && disp.key!=cimg::keyESC && disp.key!=cimg::keyQ) {
    
    if (!pause) {

      // Draw the board on the display window.
      CImg<unsigned char> nboard(board), visu(background);
      if (cx>=0 && cy>=0) cimg_mapXY(piece,x,y) if (piece(x,y)) nboard(cx-piece.dimx()/2+x,cy-piece.dimy()/2+y)=piece(x,y);
      cimg_mapXY(board,xx,yy) if (nboard(xx,yy)) visu.draw_image(blocs[nboard(xx,yy)-1],xx*blocdim,yy*blocdim);
      visu.draw_text(5,5,white,NULL,1,"Lines : %d",score,nn).draw_text(visu.dimx()-75,5,white,NULL,1,"Next :");
      if (next.data) visu.draw_image(next,next_mask,visu.dimx()-next.dimx()-2,10-next.dimy()/2).display(disp).wait(20);
      
      if (cn<0) {

        // Introduce a new piece on the board (if necessary) and create representation of the next piece
        board = nboard;
        piece = pieces[cn=nn];
        nn = rand()%pieces.size;
        cx = board.dimx()/2;
        cy = piece.dimy()/2;
        next = CImg<unsigned char>(pieces[nn].dimx()*blocdim,pieces[nn].dimy()*blocdim,1,3,0);
        cimg_mapXY(pieces[nn],xi,yi) if (pieces[nn](xi,yi)) next.draw_image(blocs[pieces[nn](xi,yi)-1],xi*blocdim,yi*blocdim);
        next_mask = next.resize(-50,-50).get_norm_pointwise().threshold(0);

        // Detect tetris lines and do line removal animation if found.
        cimg_mapY(board,yyy) {
          int Y = yyy*blocdim, line = 1;
          cimg_mapX(board,xxx) if (!board(xxx,yyy)) line=0;
          if (line) {
            board.draw_image(board.get_crop(0,0,board.dimx()-1,yyy-1),0,1);
            if (!((++score)%5)) speed=(unsigned int)(speed*0.9f);
            for (float alpha=0; alpha<=1; alpha+=0.07f)
              CImg<unsigned char>(visu).draw_image(background.get_crop(0,Y,visu.dimx()-1,Y+blocdim-1),0,Y,0,0,alpha).display(disp).wait(20);
            visu.draw_image(background.get_crop(0,Y,visu.dimx()-1,Y+blocdim-1),0,Y,0,0);
          }
        }
      }
      
      // Handle motion & collisions
      const int ox=cx, oy=cy;
      bool rotated = false, collision;
      switch (disp.key) {
      case cimg::keyP:          pause = true; break;
      case cimg::keyARROWUP:    piece.rotate(90); rotated = true; disp.key=0; break;
      case cimg::keyARROWLEFT:  cx--;  disp.key=0; break;
      case cimg::keyARROWRIGHT: cx++;  disp.key=0; break;
      }
      if (cx-piece.dimx()/2<0) cx=piece.dimx()/2;
      if (cy-piece.dimy()/2<0) cy=piece.dimy()/2;
      if (cx+(piece.dimx()-1)/2>=board.dimx()) cx = board.dimx()-1-(piece.dimx()-1)/2;

      // Detect collision along the X axis
      collision = false; cimg_mapXY(piece,i,j) if (piece(i,j) && board(cx-piece.dimx()/2+i,cy-piece.dimy()/2+j)) collision = true;
      if (collision) { cx=ox; if (rotated) piece.rotate(-90); }
      
      if (disp.key==cimg::keyARROWDOWN || !((++time)%speed)) { cy++; disp.key=0; }
      // Detect collisiong along the Y axis
      collision = false; cimg_mapXY(piece,ii,jj) if (piece(ii,jj) && board(cx-piece.dimx()/2+ii,cy-piece.dimy()/2+jj)) collision = true;
      if (collision || cy+(piece.dimy()-1)/2>=board.dimy()) { cy = oy; cn=-1; }
      if (collision && cy==piece.dimy()/2) gameover=true;
    } else {

      // If game is paused (key 'P'), do a little text animation
      float A = 0, B = 0;
      CImg<float> pauselogo = CImg<unsigned char>(80,22,1,1,0).draw_text("Game Paused\nPress a key",0,0,white);
      disp.key=0; while (!disp.key && !disp.closed) {
        CImg<float> pauserotated = pauselogo.get_rotate((float)(30*std::sin(A+=0.08f)),5).resize((int)(-150-80*std::sin(B+=0.043f)),(int)(-150-80*std::sin(B)));
#if ( !defined(_MSC_VER) || _MSC_VER>1200 )
		CImg<unsigned char>(background).
                  draw_image<float,float>(pauserotated.get_resize(-100,-100,1,3,2),pauserotated,
                     (background.dimx()-pauserotated.dimx())/2,(background.dimy()-pauserotated.dimy())/2,0,0,255).display(disp).wait(20);
#else
		CImg<unsigned char>(background).
          draw_image(pauserotated.get_resize(-100,-100,1,3,2),
                     (background.dimx()-pauserotated.dimx())/2,(background.dimy()-pauserotated.dimy())/2,0,0,255).display(disp).wait(20);
#endif          
      }
      disp.key=0;
      pause = false;
    }
  }

  // End of game reached, display the score and do a 'game over' animation
  float A = 0, B = 0;
  CImg<float> gameoverlogo = CImg<unsigned char>(70,11,1,1,0).draw_text("Game Over",0,0,white);
  while (!disp.closed) {
    CImg<float> gameoverrotated = gameoverlogo.get_rotate((float)(30*std::sin(A+=0.08f)),5).
      resize((int)(-150-80*std::sin(B+=0.043f)),(int)(-150-80*std::sin(B)));
#if ( !defined(_MSC_VER) || _MSC_VER>1200 )
    CImg<unsigned char>(background).
      draw_image<float,float>(gameoverrotated.get_resize(-100,-100,1,3,2),gameoverrotated,
                              (background.dimx()-gameoverrotated.dimx())/2,(background.dimy()-gameoverrotated.dimy())/2-60,0,0,255).
      draw_text(background.dimx()/2-50,background.dimy()/2,white,NULL,1,"Your score : %d",score).display(disp).wait(20);
#else
    CImg<unsigned char>(background).
      draw_image(gameoverrotated.get_resize(-100,-100,1,3,2),
                 (background.dimx()-gameoverrotated.dimx())/2,(background.dimy()-gameoverrotated.dimy())/2-60,0,0,255).
      draw_text(background.dimx()/2-50,background.dimy()/2,white,NULL,1,"Your score : %d",score).display(disp).wait(20);

#endif
  }
  return 0;
}
