#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define WIDTH 64 // OLED display width, in pixels
#define HEIGHT 128 // OLED display height, in pixels

Adafruit_SSD1306 display(128, 64, &Wire, -1);

static const unsigned char PROGMEM mantex_logo [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE0,
0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE0,
0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xE0,
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xE0,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xE0, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xE0,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE0,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, 0xE0,
0xFF, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF, 0xE0,
0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xE0,
0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0xFF, 0xE0,
0xFF, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0xF9, 0xFF, 0xE0, 0xFE, 0x3F, 0xFF, 0xF1, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0x1F, 0xFF, 0xF9, 0xFF, 0xF9, 0xFF, 0xE0, 0xFF, 0x8F, 0xFF, 0xFC, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0xC7, 0xFF, 0xFE, 0x7F, 0xF9, 0xFF, 0xE0, 0xFF, 0xE3, 0xFF, 0xFF, 0x3F, 0xF9, 0xFF, 0xE0,
0xFF, 0xF1, 0xFF, 0xFF, 0x9F, 0xF9, 0xFF, 0xE0, 0xFF, 0xF8, 0xFF, 0xFF, 0xCF, 0xF9, 0xFF, 0xE0,
0xFF, 0xFC, 0x7F, 0xFF, 0xE7, 0xF9, 0xFF, 0xE0, 0xFF, 0xFE, 0x3F, 0xFF, 0xF3, 0xF9, 0xFF, 0xE0,
0xFF, 0xFF, 0x1F, 0xFF, 0xF9, 0xF9, 0xFF, 0xE0, 0xFF, 0xFF, 0x8F, 0xFF, 0xFC, 0xF9, 0xFF, 0xE0,
0xFF, 0xFF, 0x87, 0xFF, 0xFE, 0x79, 0xFF, 0xE0, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0x39, 0xFF, 0xE0,
0xFF, 0xFE, 0x31, 0xFF, 0xFF, 0x99, 0xFF, 0xE0, 0xFF, 0xFC, 0x78, 0xFF, 0xFF, 0xC1, 0xFF, 0xE0,
0xFF, 0xF8, 0xFC, 0x7F, 0xFF, 0xE1, 0xFF, 0xE0, 0xFF, 0xF1, 0xFE, 0x3F, 0xFF, 0xF1, 0xFF, 0xE0,
0xFF, 0xE3, 0xFF, 0x1F, 0xFF, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xFF, 0x8F, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xFF, 0xC7, 0xFF, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xFF, 0xE3, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xFF, 0xE1, 0xFF, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xFF, 0xC0, 0xFF, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xFF, 0x80, 0x7F, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xFF, 0x00, 0x3F, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xFE, 0x00, 0x1F, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xFC, 0x00, 0x0F, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xF8, 0x00, 0x07, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xF0, 0x00, 0x03, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0xE0, 0x00, 0x01, 0xF9, 0xFF, 0xE0, 0xFF, 0xE7, 0xC0, 0x00, 0x00, 0xF9, 0xFF, 0xE0,
0xFF, 0xE7, 0x80, 0x00, 0x00, 0x79, 0xFF, 0xE0, 0xFF, 0xE7, 0x00, 0x00, 0x00, 0x39, 0xFF, 0xE0,
0xFF, 0xE6, 0x00, 0x00, 0x00, 0x19, 0xFF, 0xE0, 0xFF, 0xE4, 0x00, 0x00, 0x00, 0x09, 0xFF, 0xE0,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE0, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xE0,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE0,
0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE0,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const char pieces_S_l[2][2][4] = {{
                                    {0, 0, 1, 1}, {0, 1, 1, 2}
                                   },
                                   {
                                    {0, 1, 1, 2}, {1, 1, 0, 0}
                                   }};
const char pieces_S_r[2][2][4]{{
                                  {1, 1, 0, 0}, {0, 1, 1, 2}
                                 },
                                 {
                                  {0, 1, 1, 2}, {0, 0, 1, 1}
                                 }};
                                 
const char pieces_L_l[4][2][4] = {{
                                  {0, 0, 0, 1}, {0, 1, 2, 2}
                                 },
                                 {
                                  {0, 1, 2, 2}, {1, 1, 1, 0}
                                 },
                                 {
                                  {0, 1, 1, 1}, {0, 0, 1, 2}
                                 },
                                 {
                                  {0, 0, 1, 2}, {1, 0, 0, 0}
                                 }};
const char pieces_Sq[1][2][4] = {{
                                  {0, 1, 0, 1}, {0, 0, 1, 1}
                                 }};
const char pieces_T[4][2][4] = {{
                                  {0, 0, 1, 0},{0, 1, 1, 2}
                                 },
                                 {
                                  {0, 1, 1, 2},{1, 0, 1, 1}
                                 },
                                 {
                                  {1, 0, 1, 1},{0, 1, 1, 2}
                                 },
                                 {
                                  {0, 1, 1, 2},{0, 0, 1, 0}
                                 }};
const char pieces_l[2][2][4] = {{
                                  {0, 1, 2, 3}, {0, 0, 0, 0}
                                 },
                                 {
                                  {0, 0, 0, 0}, {0, 1, 2, 3}
                                 }};

const short MARGIN_TOP = 19;
const short MARGIN_LEFT = 3;
const short SIZE = 5;
const short TYPES = 6;


word currentType, nextType, rotation;
short pieceX, pieceY;
short piece[2][4];

int interval = 500, score;
long timer, delayer;

boolean grid[10][18];
boolean b1, b2, b3;

void checkLines();
void breakLine(short line);
void refresh();
void drawGrid();
boolean nextHorizontalCollision(short piece[2][4], int amount);
boolean nextCollision();
void generate();
void drawPiece(short type, short rotation, short x, short y);
void drawNextPiece();
void copyPiece(short piece[2][4], short type, short rotation);
short getMaxRotation(short type);
boolean canRotate(short rotation);
void drawLayout();
short getNumberLength(int n);
void drawText(char text[], short length, int x, int y);

void setup() {

  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.setRotation(1);
  display.clearDisplay();

  /*display.drawBitmap(3, 23, mantex_logo, 64, 82,  WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();*/
  
  //drawLayout();
  
  display.display();
      
  randomSeed(analogRead(0));
  nextType = random(TYPES);
  generate();
  
  timer = millis();
}
int state = 1;
void loop() {
  if(!digitalRead(12)){
    delay(20);
    while(!digitalRead(12));
    state++;
    if(state > 1) state = 0;
    timer = millis();
    display.clearDisplay();
    display.display();
    if(state == 0) generate();
  }
  if(state == 0){
    if(millis() - timer > interval){
      checkLines();
      refresh();
      
      if(nextCollision()){
        for(short i = 0; i < 4; i++)
          grid[pieceX + piece[0][i]][pieceY + piece[1][i]] = 1;
        generate();
      }else
        pieceY++;
        
      timer = millis();
    }

    if(!digitalRead(2)){
      if(b1){
        if(!nextHorizontalCollision(piece, -1)){
          pieceX--;
          refresh();
        }
        b1 = false;
      }
    }else{ 
      b1 = true;
    }

    if(!digitalRead(3)){
      if(b2){
        if(!nextHorizontalCollision(piece, 1)){
          pieceX++;
          refresh();
        }
        b2 = false;
      }
    }else{ 
      b2 = true;
    }

    if(!digitalRead(11)){
      if(b3){
        if(rotation == getMaxRotation(currentType) - 1 && canRotate(0)){
          rotation = 0;
        }else if(canRotate(rotation + 1)){
          rotation++;
        }
          
        copyPiece(piece, currentType, rotation);
        refresh();
        
        b3 = false;
        delayer = millis();
      }
    }else if(millis() - delayer > 50){ 
      b3 = true;
    }
  }else{
    display.drawBitmap(3, 23, mantex_logo, 64, 82,  WHITE);
    display.display();
    nextType = random(TYPES);
    pieceX = pieceY = 0;
    short i,j;
    for (i = 0; i < 2; i++) { //先行
    for (j = 0; j < 4; j++) { //后列
        piece[i][j] = 0; //全赋值为0
    }}
    for (i = 0; i < 10; i++) { //先行
    for (j = 0; j < 18; j++) { //后列
        grid[i][j] = 0; //全赋值为0
    }}
    currentType = rotation = 0;
  }
}
//检查行是否满足条件
void checkLines(){
  boolean full;
  for(short y = 17; y >= 0; y--){
    full = true;
    for(short x = 0; x < 10; x++){
      full = full && grid[x][y];
    }
    if(full){
      breakLine(y);
      y++;
    }
  }
}
//分数统计
void breakLine(short line){
  for(short y = line; y >= 0; y--){
    for(short x = 0; x < 10; x++){
      grid[x][y] = grid[x][y-1];
    }
  }
  for(short x = 0; x < 10; x++){
     grid[x][0] = 0;
  }
  display.invertDisplay(true);
  delay(50);
  display.invertDisplay(false);
  score += 10;
}
//更新显示
void refresh(){
    display.clearDisplay();
    drawLayout();
    drawGrid();
    drawPiece(currentType, 0, pieceX, pieceY);
    display.display();
}
//绘制图形
void drawGrid(){
  for(short x = 0; x < 10; x++)
    for(short y = 0; y < 18; y++)
      if(grid[x][y])
        display.fillRect(MARGIN_LEFT + (SIZE + 1)*x, MARGIN_TOP + (SIZE + 1)*y, SIZE, SIZE, WHITE);
}
//下一次水平碰撞
boolean nextHorizontalCollision(short piece[2][4], int amount){
  for(short i = 0; i < 4; i++){
    short newX = pieceX + piece[0][i] + amount;
    if(newX > 9 || newX < 0 || grid[newX][pieceY + piece[1][i]])
      return true;
  }
  return false;
}
//图形往下移动
boolean nextCollision(){
  for(short i = 0; i < 4; i++){
    short y = pieceY + piece[1][i] + 1;
    short x = pieceX + piece[0][i];
    if(y > 17 || grid[x][y])
      return true;
  }

  return false;
}
//随机生成图形
void generate(){
  currentType = nextType;
  nextType = random(TYPES);
  if(currentType != 5)
    pieceX = random(9);
  else
    pieceX = random(7);
  pieceY = 0;
  rotation = 0;
  copyPiece(piece, currentType, rotation);
}
//绘制图形
void drawPiece(short type, short rotation, short x, short y){
  for(short i = 0; i < 4; i++)
    display.fillRect(MARGIN_LEFT + (SIZE + 1)*(x + piece[0][i]), MARGIN_TOP + (SIZE + 1)*(y + piece[1][i]), SIZE, SIZE, WHITE);
}
//绘制下一个图形
void drawNextPiece(){
  short nPiece[2][4];
  copyPiece(nPiece, nextType, 0);
  for(short i = 0; i < 4; i++)
    display.fillRect(50 + 3*nPiece[0][i], 4 + 3*nPiece[1][i], 2, 2, WHITE);
}
//复制图形
void copyPiece(short piece[2][4], short type, short rotation){
  switch(type){
  case 0: //L_l
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_L_l[rotation][0][i];
      piece[1][i] = pieces_L_l[rotation][1][i];
    }
    break;
   case 1: //S_l
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_S_l[rotation][0][i];
      piece[1][i] = pieces_S_l[rotation][1][i];
    }
    break;
   case 2: //S_r
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_S_r[rotation][0][i];
      piece[1][i] = pieces_S_r[rotation][1][i];
    }
    break;
   case 3: //Sq
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_Sq[0][0][i];
      piece[1][i] = pieces_Sq[0][1][i];
    }
    break;
    case 4: //T
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_T[rotation][0][i];
      piece[1][i] = pieces_T[rotation][1][i];
    }
    break;
    case 5: //l
    for(short i = 0; i < 4; i++){
      piece[0][i] = pieces_l[rotation][0][i];
      piece[1][i] = pieces_l[rotation][1][i];
    }
    break;
  }
}
//获得最大旋转
short getMaxRotation(short type){
  if(type == 1 || type == 2 || type == 5)
    return 2;
  else if(type == 0 || type == 4)
    return 4;
  else if(type == 3)
    return 1;
  else 
    return 0;
}
//图形旋转
boolean canRotate(short rotation){
  short piece[2][4];
  copyPiece(piece, currentType, rotation);
  return !nextHorizontalCollision(piece, 0);
}
//屏幕初始化显示内容
void drawLayout(){
  display.drawLine(0, 15, WIDTH, 15, WHITE);
  display.drawRect(0, 0, WIDTH, HEIGHT, WHITE);
  drawNextPiece();
  char text[6];
  itoa(score, text, 10);
  drawText(text, getNumberLength(score), 7, 4);
}
//获取数字长度
short getNumberLength(int n){
  short counter = 1;
  while(n >= 10){
    n /= 10;
    counter++;
  }
  return counter;
}
//绘制文本
void drawText(char text[], short length, int x, int y){
  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(WHITE); // Draw white text
  display.setCursor(x, y);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  for(short i = 0; i < length; i++)
    display.write(text[i]);
}