///////////////////////////////////////////////////////////////////////////////////////////
//// 1A2B 遊戲
// 引用arduino內建 SPI 程式庫
#include<SPI.h>

//// 接線 
// CS : GPIO5 // CLK : GPIO18 // DATA(MOSI) : GPIO23 
const byte CS = 5;            // Chip Select晶片選擇線(SS周邊選擇線)
const int deviceNnm = 16;     // 串連設備數
const int symbolNum = 38;     // 符號數

const byte colPins[4] = {27, 14, 12, 13};     // 設定「行」腳位
const byte rowPins[4] = {22, 33, 25, 26};     // 設定「列」腳位
const char keymap[4][4] = {                   // 設定按鍵的「行、列」代表值
    {'1','2','3','A'}, 
    {'4','5','6','B'}, 
    {'7','8','9','C'},
    {'*','0','#','D'}
};

char num[10000][4];
char m[10000];

//// 繪圖資料
const char symbol[symbolNum] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'j',
                                'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                                'U', 'V', 'W', 'X', 'Y', 'Z',
                                ':','!'
 };

const byte pixel[symbolNum + 1][8] = {  
  {0x00,0x3c,0x66,0x6e,0x76,0x66,0x3c,0x00},  //0
  {0x00,0x18,0x38,0x18,0x18,0x18,0x3c,0x00},  //1
  {0x00,0x3c,0x66,0x06,0x3c,0x60,0x7e,0x00},  //2
  {0x00,0x3c,0x06,0x1c,0x06,0x66,0x3c,0x00},  //3
  {0x00,0x0c,0x1c,0x3c,0x6c,0x7e,0x0c,0x00},  //4
  {0x00,0x7c,0x60,0x7c,0x06,0x66,0x3c,0x00},  //5
  {0x00,0x3c,0x60,0x7c,0x66,0x66,0x3c,0x00},  //6
  {0x00,0x7e,0x66,0x06,0x0c,0x18,0x18,0x00},  //7
  {0x00,0x3c,0x66,0x3c,0x66,0x66,0x3c,0x00},  //8
  {0x00,0x3c,0x66,0x66,0x3e,0x06,0x3c,0x00},  //9
  {0x00,0x3c,0x66,0x66,0x7e,0x66,0x66,0x00},  //A
  {0x00,0x7c,0x66,0x7c,0x66,0x66,0x7c,0x00},  //B
  {0x00,0x3c,0x66,0x60,0x60,0x66,0x3c,0x00},  //C
  {0x00,0x7c,0x66,0x66,0x66,0x66,0x7c,0x00},  //D
  {0x00,0x7e,0x60,0x7c,0x60,0x60,0x7e,0x00},  //E
  {0x00,0x7e,0x60,0x60,0x7c,0x60,0x60,0x00},  //F
  {0x00,0x3c,0x66,0x60,0x6e,0x66,0x3e,0x00},  //G
  {0x00,0x66,0x66,0x7e,0x66,0x66,0x66,0x00},  //H
  {0x00,0x3c,0x18,0x18,0x18,0x18,0x3c,0x00},  //I
  {0x00,0x0e,0x06,0x06,0x06,0x66,0x3c,0x00},  //J
  {0x00,0x66,0x6c,0x78,0x78,0x6c,0x66,0x00},  //K
  {0x00,0x60,0x60,0x60,0x60,0x60,0x7e,0x00},  //L
  {0x00,0x62,0x76,0x7e,0x6a,0x62,0x62,0x00},  //M
  {0x00,0x62,0x72,0x7a,0x6e,0x66,0x62,0x00},  //N
  {0x00,0x3c,0x66,0x66,0x66,0x66,0x3c,0x00},  //O
  {0x00,0x7c,0x66,0x66,0x7c,0x60,0x60,0x00},  //P
  {0x00,0x3c,0x66,0x66,0x6e,0x64,0x3a,0x00},  //Q
  {0x00,0x7c,0x66,0x66,0x7c,0x66,0x66,0x00},  //R
  {0x00,0x3c,0x60,0x3c,0x06,0x66,0x3c,0x00},  //S
  {0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x00},  //T
  {0x00,0x66,0x66,0x66,0x66,0x66,0x3c,0x00},  //U
  {0x00,0x66,0x66,0x66,0x66,0x3c,0x18,0x00},  //V
  {0x00,0x62,0x62,0x6a,0x7e,0x76,0x62,0x00},  //W
  {0x00,0x66,0x66,0x3c,0x3c,0x66,0x66,0x00},  //X
  {0x00,0x66,0x66,0x66,0x3c,0x18,0x18,0x00},  //Y
  {0x00,0x7e,0x0e,0x1c,0x38,0x70,0x7e,0x00},  //Z

  {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00},  // :
  {0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x00},  // !
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}   //空白
  };

//// 轉為pixel
int findMsg(char c){
  for(int i = 0; i < symbolNum; i++){
    if(c == symbol[i]){
      return i;
    }
  }
  return symbolNum;
}

//// bit reversal
// 將資料位元翻轉成所需的順位(高位-低位? 低位- 高位?)
byte reverse(byte b){
  b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
  b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
  b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
  return(b);
};    

//// MAX7219暫存器位址(16進制)
// 資料暫存器位址為 0x1~0x8
const byte NOOP = 0x0;          // 不運作
const byte DECODEMODE = 0x9;    // 解碼模式
const byte INTENSITY = 0xA;     // 顯示強度
const byte SCANLIMIT = 0xB;     // 掃描限制
const byte SHUTDOWN = 0xC;      // 停機
const byte DISPLAYTEST = 0xF;   // 顯示器檢測

//// 設定 MAX7219 暫存器資料的自訂函數
// MAX7219每次接收16位元的數據
// reg 為暫存器的位址 ; data 為要傳送的資料
void max7219(byte reg, byte data, int x = deviceNnm){
  digitalWrite(CS, LOW);        // 傳送或接收資料前CS腳位需設為0   
  for(int i = 1; i < x; i++){
    SPI.transfer(NOOP);
    SPI.transfer(0x00);
  }  
  SPI.transfer(reg);            // 傳送暫存器的位址
  SPI.transfer(reverse(data));  // 傳送資料
  for(int i = deviceNnm; i > x; i--){
    SPI.transfer(NOOP);
    SPI.transfer(0x00);
  } 
  digitalWrite(CS, HIGH);       // 傳送結束CS腳位需設為1
}

void light(char c, int x = deviceNnm){
  int j = findMsg(c);
  for(byte i = 0; i < 8; i++){    
    max7219(i + 1 , pixel[j][i], x);        
  }
}

char keyboard() {
  byte scanVal;   // 暫存掃描到的按鍵值
  char k;
  while(1){
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        digitalWrite(colPins[j], LOW);
        scanVal = digitalRead(rowPins[i]);
        if (scanVal == LOW) {                 // 如果輸入值是「低電位」…
          //Serial.println(keymap[i][j]);     // 輸出按鍵代表的字元
          k = keymap[i][j];
          delay(150);  // 掃描按鍵的間隔時間
          digitalWrite(colPins[j], HIGH);        
          return k;
        }
        digitalWrite(colPins[j], HIGH);
      }
    }
  }  
}

char mode = '0';

void setup() {
  // 啟用鍵盤
  Serial.begin(9600);
  for (int i = 0; i < 4; i++) {
    pinMode(rowPins[i], INPUT);
    pinMode(colPins[i], OUTPUT);
    pinMode(rowPins[i], INPUT_PULLUP);  //啟用上拉電阻
    digitalWrite(colPins[i], HIGH);
    digitalWrite(rowPins[i], HIGH);
  }
  // 啟用LED矩陣
  pinMode(CS, OUTPUT);      // 設定CS腳位為輸出模式
  digitalWrite(CS, HIGH);   // CS腳位設為高電位表示尚不傳輸
  SPI.begin();              // 啟動SPI連線
  max7219(DECODEMODE, 0);   // 解碼模式   // 設定為0表示不使用BCD解碼
  max7219(INTENSITY, 8);    // 顯示強度   // 設定亮度0~15
  max7219(SCANLIMIT, 7);    // 掃描限制   // 設定掃描顯示器的個數0~7,設定為7為顯示LED矩陣所有行數8行
  max7219(SHUTDOWN, 1);     // 停機      // 關閉停機模式,亦即開機  
  
  max7219(DISPLAYTEST, 0);  // 顯示器檢測 // 關閉顯示器檢測  

  // 清除顯示畫面(LED矩陣中的8行都設為0)
  for(byte i = 0; i < 8; i++){
    max7219(i + 1 , 0);
  }
  // 顯示遊戲模式選擇
  char msg[deviceNnm] = { '1', 'A', '2', 'B', 'G', 'A', 'M', 'E', '1', ':', 'A', 'T', '2', ':', 'D', 'E'};    
  for(int j = 0; j < deviceNnm; j++){   
    light(msg[j], j + 1);
  }  
  while(mode == '0'){
    char k = keyboard();
    if(k =='1'){
      mode = '1';     
      char msg1[deviceNnm] = { 'G', 'A', 'M', 'E', 'M', 'O', 'D', 'E', 'A', 'T', 'T', 'A', 'C', 'K', 'E', 'R'};
      for(int j = 0; j < deviceNnm; j++){   
        light(msg1[j], j + 1);
      }
    }else if(k =='2'){
      mode = '2';
      char msg2[deviceNnm] = { 'G', 'A', 'M', 'E', 'M', 'O', 'D', 'E', 'D', 'E', 'F', 'E', 'N', 'D', 'E', 'R'};
      for(int j = 0; j < deviceNnm; j++){   
        light(msg2[j], j + 1);
      }      
    }else{
      char msg3[deviceNnm] = { 'R', 'E', 'E', 'N', 'T', 'E', 'R', '!', '1', ':', 'A', 'T', '2', ':', 'D', 'E'};
      for(int j = 0; j < deviceNnm; j++){   
        light(msg3[j], j + 1);
      }
    }
  }
  delay(500);
  char msg4[deviceNnm] = { 'G', 'A', 'M', 'E', 'G', 'O', '!', '!', 'C', 'O', 'M', 'E', 'O', 'N', '!', '!'};
  for(int j = 0; j < deviceNnm; j++){   
    light(msg4[j], j + 1);
  }
  delay(500);  
}

void loop() {
  char answer[4] = {0, 0, 0, 0};  
  while(mode == '1'){        
    int times = 0;
    char count_a = '0';
    char count_b = '0';
    answer[0] = random('0', '9');
    answer[1] = random('0', '9');
    while(answer[1] == answer[0]){
      answer[1] = random('0', '9');
    }
    answer[2] = random('0', '9');
    while(answer[2] == answer[0] || answer[2] == answer[1]){
      answer[2] = random('0', '9');
    }
    answer[3] = random('0', '9');
    while(answer[3] == answer[0] || answer[3] == answer[1] || answer[3] == answer[2]){
      answer[3] = random('0', '9');
    } 
    for(int j = 8; j < deviceNnm; j++){   
      light(' ', j + 1);
    }
    // //// 輸出答案
    // for(int i = 0; i < 4; i++){   
    //   Serial.print(answer[i]);
    // }

    while(times < 10000 && count_a != '4'){
      times++;
      count_a = '0';
      count_b = '0';
      for(int j = 8; j < deviceNnm; j++){   
        light(' ', j + 1);
      }             
      // 顯示目前猜測次數
      int t = times;
      int t1 = t/1000;
      char t11 = t1 + 48 ;
      t = t - t1 * 1000;           
      int t2 = t/100;
      char t22 = t2 + 48 ;
      t = t - t2 * 100;      
      int t3 = t/10;
      char t33 = t3 + 48; 
      t = t - t3 * 10;      
      char t44 = t + 48;
      light(t11, 5);
      light(t22, 6);
      light(t33, 7);
      light(t44, 8);

      //使用者輸入
      char k[4] = {0, 0, 0, 0};
      k[0] = keyboard();
      while((k[0] < '0' || k[0] > '9')){
        k[0] = keyboard();
      }
      light(k[0], 9);
      k[1] = keyboard();
      while((k[1] < '0' || k[1] > '9') || k[1] == k[0]){
        k[1] = keyboard();
      }
      light(k[1], 10);
      k[2] = keyboard();
      while((k[2] < '0' || k[2] > '9') || k[2] == k[0] || k[2] == k[1]){
        k[2] = keyboard();
      }
      light(k[2], 11);
      k[3] = keyboard();
      while((k[3] < '0' || k[3] > '9') || k[3] == k[0] || k[3] == k[1] || k[3] == k[2]){
        k[3] = keyboard();
      }
      light(k[3], 12);

      // 驗證
      for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
          if(k[i] == answer[j]){
            if(i == j){
              count_a++;
            }else{
              count_b++;
            }
          }
        }
      }
      light(count_a, 13);
      light('A', 14);
      light(count_b, 15);
      light('B', 16);
      delay(3000);     
    }
    mode = '0';
    char msg5[deviceNnm] = { 'G', 'O', 'O', 'D', 'Y', 'O', 'U', ' ', 'W', 'I', 'N', ' ', answer[0], answer[1], answer[2], answer[3]};
    for(int j = 0; j < deviceNnm; j++){   
      light(msg5[j], j + 1);
    }    
  }
  int guess = 0;
  while(mode == '2'){
    //char num[10000][4];
    //char m[10000];
    int times = 0;
    char k[2] = {0, 0};
    char check_A = '0';
    char check_B = '0';        

    for(int i = 0; i < 10000; i++){      
      if(i < 10){
        int temp = i;
        num[i][0] = '0';        
        num[i][1] = '0';        
        num[i][2] = '0';        
        char tempD = temp + 48;
        num[i][3] = tempD;               
      }else if(i < 100){
        num[i][0] = '0';        
        num[i][1] = '0';        
        int temp = i;
        int c = temp/10;
        char tempC = c + 48;        
        num[i][2] = tempC;             
        temp = temp - 10 * c;
        char tempD = temp + 48;
        num[i][3] = tempD;        
      }else if(i < 1000){
        num[i][0] = '0';        
        int temp = i;
        int b = temp/100;        
        char tempB = b + 48;  
        num[i][1] = tempB;        
        temp = temp - 100 * b;
        int c = temp/10;
        char tempC = c + 48;        
        num[i][2] = tempC;                 
        temp = temp - 10 * c;
        char tempD = temp + 48;
        num[i][3] = tempD;                 
      }else{        
        int temp = i;
        int a = temp/1000;
        char tempA = a + 48;  
        num[i][0] = tempA;        
        temp = temp - 1000 * a;
        int b = temp/100;
        char tempB = b + 48;  
        num[i][1] = tempB;        
        temp = temp - 100 * b;
        int c = temp/10;
        char tempC = c + 48;        
        num[i][2] = tempC;               
        temp = temp - 10 * c;
        char tempD = temp + 48;
        num[i][3] = tempD;               
      }
      m[i] = '0';                                 
    }
    for(int i = 0; i < 10000; i++){
      if(num[i][0] == num[i][1]){
        m[i] = '1';        
        continue;
      }
      if(num[i][0] == num[i][2]){
        m[i] = '1';       
        continue;
      }
      if(num[i][0] == num[i][3]){
        m[i] = '1';       
        continue;
      }
      if(num[i][1] == num[i][2]){
        m[i] = '1';        
        continue;
      }
      if(num[i][1] == num[i][3]){
        m[i] = '1';        
        continue;
      }
      if(num[i][2] == num[i][3]){
        m[i] = '1';        
        continue;
      }           
    }  
    while(times < 10000 && k[0] != '4'){
      times++;   
      for(int j = 8; j < deviceNnm; j++){   
        light(' ', j + 1);
      }             
      // 顯示目前猜測次數
      int t = times;
      int t1 = t/1000;
      char t11 = t1 + 48 ;
      t = t - t1 * 1000;           
      int t2 = t/100;
      char t22 = t2 + 48 ;
      t = t - t2 * 100;      
      int t3 = t/10;
      char t33 = t3 + 48; 
      t = t - t3 * 10;      
      char t44 = t + 48;
      light(t11, 5);
      light(t22, 6);
      light(t33, 7);
      light(t44, 8);
      
      while( m[guess] == '1' && guess < 10000){
        guess++;               
      }      
      light(num[guess][0], 9);
      light(num[guess][1], 10);
      light(num[guess][2], 11);
      light(num[guess][3], 12);
      
      //使用者輸入      
      k[0] = keyboard();
      while((k[0] < '0' || k[0] > '4')){
        k[0] = keyboard();
      }      
      light(k[0], 13);
      light('A', 14);
      k[1] = keyboard();
      while((k[1] < '0' || k[1] > '4') || ( (k[1] - '0') + (k[0] - '0') > 4)){
        k[1] = keyboard();
      }      
      light(k[1], 15);
      light('B', 16);

      int Check = guess;
      while(Check < 10000){
        if(m[Check] == '0'){
          for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
              if(num[guess][i] == num[Check][j]){
                if(i == j ){
                  check_A = check_A + 1;
                }else{
                  check_B = check_B + 1;
                }
              }
            }
          }
          if(check_A != k[0] || check_B != k[1]){           
            m[Check] = '1';
          }
        }
        Check = Check + 1;
        check_A = '0';
        check_B = '0';                
      }
    }
    mode = '0';
    char msg5[deviceNnm] = { 'H', 'A', 'H', 'A', 'Y', 'O', 'U', 'R', 'N', 'U', 'M', ' ', num[guess][0], num[guess][1], num[guess][2], num[guess][3]};
    for(int j = 0; j < deviceNnm; j++){   
      light(msg5[j], j + 1);
    }       
  }
  //Serial.println("遊戲結束");
  //delay(120000);
}