#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal.h>
 
#define LED_PIN    6
#define LED_COUNT 64
 
#define LEDS_ON_LINE 8
#define LEDS_ON_COL 8
 
#define LINES 3
#define COLS 3
 
#define CRT_PLAYER_FACT_MAX 30
#define CRT_PLAYER_FACT_MIN 0
 
#define BUTTON_LEFT 9
#define BUTTON_OK 8
#define BUTTON_RIGHT 7
#define BUTTON_COOLDOWN 200
 
int button_cooldown;
 
#define P1 0
#define P2 1
#define EMPTY 2
 
int crt_player;
int crt_player_fact;
int crt_player_dir;
int crt_line;
int crt_col;
int col_players[2];
int grid[LINES][COLS] = {EMPTY, EMPTY, EMPTY,
                         EMPTY, EMPTY, EMPTY,
                         EMPTY, EMPTY, EMPTY};
int score[2];
 
char col[6][8] = {"red    ", "green  ", "blue   ", "yellow ", "magenta", "cyan   "};
int rgb[6][3] = { 1, 0, 0, // red
                  0, 1, 0, // green
                  0, 0, 1, // blue
                  1, 1, 0, // yellow
                  1, 0, 1, // magenta
                  0, 1, 1}; // cyan
 
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
 
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
 
void game_start();
void restart();
void pick_color();
 
void setup() {
  // led matrix setup
  // Initialize all pixels to 'off'
  strip.begin();
  strip.show();
 
  // game setup
  crt_player = P1;
  crt_player_fact = 0;
  crt_player_dir = 1;
  crt_line = 0;
  crt_col = 0;
 
  // lcd display setup
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Hello!");
 
  // buttons setup
  pinMode(BUTTON_LEFT, INPUT_PULLUP);
  pinMode(BUTTON_OK, INPUT_PULLUP);
  pinMode(BUTTON_RIGHT, INPUT_PULLUP);
  button_cooldown = 0;
 
  pick_color();
  game_start();
}
 
void color_border();
void update_crt_player_factor();
void color_grid();
void blink_crt();
 
void right_push();
void left_push();
void ok_push();
void switch_crt_player();
void celebrate(int winner);
void draw();
void check_for_winner();
 
void loop() {
  if (button_cooldown <= 0 && digitalRead(BUTTON_LEFT) == 0) {
    left_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
  if (button_cooldown <= 0 && digitalRead(BUTTON_OK) == 0) {
    ok_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
  if (button_cooldown <= 0 && digitalRead(BUTTON_RIGHT) == 0) {
    right_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
 
  color_border();
  update_crt_player_factor();
  color_grid();
  blink_crt();
 
  strip.show();
 
  delay(50);
  if (button_cooldown > 0) {
    button_cooldown -= 50;
  }
 
  check_for_winner();
}
 
//////////////////////////////////////////////////////////////////////////////////
 
void game_start() {
  lcd.clear();
  lcd.print("Game starts!");
  lcd.setCursor(0, 1);
  lcd.print("Good luck!");
  delay(3000);
}
 
void restart() {
  // led matrix setup
  // Initialize all pixels to 'off'
  for (int j = 0; j < LED_COUNT; j++)
      strip.setPixelColor(j, 0, 0, 0);
  strip.show();
 
  // game setup
  crt_player = P1;
  crt_player_fact = 0;
  crt_player_dir = 1;
  crt_line = 0;
  crt_col = 0;
 
  for (int i = 0; i < LINES; i++)
    for (int j = 0; j < COLS; j++)
      grid[i][j] = EMPTY;
 
  button_cooldown = 0;
 
  pick_color();
  game_start();
}
 
void pick_color() {
  for (int i = 0; i < 2; i++) {
    col_players[i] = 0;
    lcd.clear();
    lcd.print(String("Player ") + String(i + 1) + String(" color:"));
 
    int crt_col = 0;
    if (i == 1 && crt_col == col_players[0]) {
      crt_col = (crt_col - 1 + 6) % 6;
    }
    while (1) {
      lcd.setCursor(0, 1);
      lcd.print(col[crt_col]);
 
      if (digitalRead(BUTTON_OK) == 0) {
        col_players[i] = crt_col;
        delay(BUTTON_COOLDOWN);
        break;
      }
      if (digitalRead(BUTTON_LEFT) == 0) {
        crt_col = (crt_col - 1 + 6) % 6;
        if (i == 1 && crt_col == col_players[0]) {
          crt_col = (crt_col - 1 + 6) % 6;
        }
      }
      if (digitalRead(BUTTON_RIGHT) == 0) {
        crt_col = (crt_col + 1) % 6;
        if (i == 1 && crt_col == col_players[0]) {
          crt_col = (crt_col + 1) % 6;
        }
      }
      delay(BUTTON_COOLDOWN);
    }
  }
}
 
//////////////////////////////////////////////////////////////////////////////////
 
void color_border() {
  int r, g, b;
 
  r = rgb[col_players[crt_player]][0];
  g = rgb[col_players[crt_player]][1];
  b = rgb[col_players[crt_player]][2];
 
  // upper border
  for (int i = 0; i < LEDS_ON_COL; i++) {
    strip.setPixelColor(i, r * crt_player_fact,
                        g * crt_player_fact, b * crt_player_fact);
  }
 
  // lower border
  for (int i = 0; i < LEDS_ON_COL; i++) {
    strip.setPixelColor((LEDS_ON_COL - 1) * LEDS_ON_LINE + i,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
 
  // left border
  for (int i = 1; i < LEDS_ON_LINE; i++) {
    strip.setPixelColor(i * LEDS_ON_LINE,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
 
  // right border
  for (int i = 1; i < LEDS_ON_LINE; i++) {
    strip.setPixelColor(i * LEDS_ON_LINE + LEDS_ON_LINE - 1,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
}
 
void update_crt_player_factor() {
  if (crt_player_fact + crt_player_dir <= CRT_PLAYER_FACT_MAX &&
      crt_player_fact + crt_player_dir >= CRT_PLAYER_FACT_MIN) {
        crt_player_fact += crt_player_dir;
  } else {
    crt_player_dir *= -1;
  }
}
 
void color_grid() {
 int r, g, b;
  for (int line = 0; line < LINES; line++) {
    for (int col = 0; col < COLS; col++) {
      int line_index = line * 2 + 1;
      int col_index = col * 2 + 1;
      int elem1 = line_index * LEDS_ON_LINE + col_index;
      int elem2 = elem1 + 1;
      int elem3 = elem1 + LEDS_ON_LINE;
      int elem4 = elem3 + 1;
 
      if (grid[line][col] == P1) {
        r = 20 * rgb[col_players[P1]][0];
        g = 20 * rgb[col_players[P1]][1];
        b = 20 * rgb[col_players[P1]][2];
      } else if (grid[line][col] == P2){
        r = 20 * rgb[col_players[P2]][0];
        g = 20 * rgb[col_players[P2]][1];
        b = 20 * rgb[col_players[P2]][2];
      } else {
        r = g = b = 0;
      }
      strip.setPixelColor(elem1, r, g, b);
      strip.setPixelColor(elem2, r, g, b);
      strip.setPixelColor(elem3, r, g, b);
      strip.setPixelColor(elem4, r, g, b);
    }
  }
}
 
void blink_crt() {
  int line_index = crt_line * 2 + 1;
  int col_index = crt_col * 2 + 1;
  int elem1 = line_index * LEDS_ON_LINE + col_index;
  int elem2 = elem1 + 1;
  int elem3 = elem1 + LEDS_ON_LINE;
  int elem4 = elem3 + 1;
 
  if (crt_player_fact % 10) {
    strip.setPixelColor(elem1, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem2, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem3, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem4, crt_player_fact, crt_player_fact, crt_player_fact);
  } else {
    strip.setPixelColor(elem1, 0, 0, 0);
    strip.setPixelColor(elem2, 0, 0, 0);
    strip.setPixelColor(elem3, 0, 0, 0);
    strip.setPixelColor(elem4, 0, 0, 0);
  }
}
 
void right_push() {
  crt_col++;
  if (crt_col == COLS) {
    crt_col = 0;
    crt_line++;
  }
  if (crt_line == LINES) {
    crt_line = 0;
  }
}
 
void left_push() {
  crt_col--;
  if (crt_col == -1) {
    crt_col = COLS - 1;
    crt_line--;
  }
  if (crt_line == -1) {
    crt_line = LINES - 1;
  }
}
 
void ok_push() {
  if (grid[crt_line][crt_col] == EMPTY) {
    grid[crt_line][crt_col] = crt_player;
    switch_crt_player();
    crt_line = 0;
    crt_col = 0;
  }
}
 
void switch_crt_player() {
  if (crt_player == P1) {
    crt_player = P2;
  } else {
    crt_player = P1;
  }
}
 
void celebrate(int winner) {
  lcd.clear();
  lcd.print(String("Player ") + String(winner + 1) + String(" wins!"));
 
  for (int i = 0; i < LED_COUNT; i++)
    strip.setPixelColor(i, 0, 0, 0);
 
  int r = rgb[col_players[winner]][0] * 20;
  int g = rgb[col_players[winner]][1] * 20;
  int b = rgb[col_players[winner]][2] * 20;
 
  score[winner]++;
 
  // drawing
  strip.setPixelColor(2, r, g, b);
  strip.setPixelColor(3, r, g, b);
  strip.setPixelColor(4, r, g, b);
  strip.setPixelColor(5, r, g, b);
 
  strip.setPixelColor(9, r, g, b);
  strip.setPixelColor(14, r, g, b);
 
  strip.setPixelColor(16, r, g, b);
  strip.setPixelColor(18, r, g, b);
  strip.setPixelColor(21, r, g, b);
  strip.setPixelColor(23, r, g, b);
 
  strip.setPixelColor(24, r, g, b);
  strip.setPixelColor(31, r, g, b);
 
  strip.setPixelColor(32, r, g, b);
  strip.setPixelColor(34, r, g, b);
  strip.setPixelColor(37, r, g, b);
  strip.setPixelColor(39, r, g, b);
 
  strip.setPixelColor(40, r, g, b);
  strip.setPixelColor(43, r, g, b);
  strip.setPixelColor(44, r, g, b);
  strip.setPixelColor(47, r, g, b);
 
  strip.setPixelColor(49, r, g, b);
  strip.setPixelColor(54, r, g, b);
 
  strip.setPixelColor(58, r, g, b);
  strip.setPixelColor(59, r, g, b);
  strip.setPixelColor(60, r, g, b);
  strip.setPixelColor(61, r, g, b);
 
  strip.show();
  delay(3000);
 
  lcd.clear();
  lcd.print(String("Player 1: ") + String(score[P1]));
  lcd.setCursor(0, 1);
  lcd.print(String("Player 2: ") + String(score[P2]));
 
  delay(10000);
}
 
void draw() {
  lcd.clear();
  lcd.print("Game over!");
  lcd.setCursor(0, 1);
  lcd.print("Draw");
 
  // drawing
  int r1 = rgb[col_players[0]][0] * 30;
  int r2 = rgb[col_players[1]][0] * 30;
  int g1 = rgb[col_players[0]][1] * 30;
  int g2 = rgb[col_players[1]][1] * 30;
  int b1 = rgb[col_players[0]][2] * 30;
  int b2 = rgb[col_players[1]][2] * 30;
 
  for (int i = 0; i < LED_COUNT; i++)
    strip.setPixelColor(i, 0, 0, 0);
 
  // X
  strip.setPixelColor(0, r1, g1, b1);
  strip.setPixelColor(3, r1, g1, b1);
  strip.setPixelColor(9, r1, g1, b1);
  strip.setPixelColor(10, r1, g1, b1);
  strip.setPixelColor(17, r1, g1, b1);
  strip.setPixelColor(18, r1, g1, b1);
  strip.setPixelColor(24, r1, g1, b1);
  strip.setPixelColor(27, r1, g1, b1);
 
  // 0
  strip.setPixelColor(37, r2, g2, b2);
  strip.setPixelColor(38, r2, g2, b2);
  strip.setPixelColor(44, r2, g2, b2);
  strip.setPixelColor(47, r2, g2, b2);
  strip.setPixelColor(52, r2, g2, b2);
  strip.setPixelColor(55, r2, g2, b2);
  strip.setPixelColor(61, r2, g2, b2);
  strip.setPixelColor(62, r2, g2, b2);
 
  // diag
  for (int i = 0; i < LEDS_ON_COL; i++)
    strip.setPixelColor((i + 1) * LEDS_ON_LINE - i - 1, 10, 10, 10);
 
 
  strip.show();
  delay(3000);
 
  lcd.clear();
  lcd.print(String("Player 1: ") + String(score[P1]));
  lcd.setCursor(0, 1);
  lcd.print(String("Player 2: ") + String(score[P2]));
 
  delay(10000);
}
 
void check_for_winner() {
  int found = -1;
  int done = 1;
 
  // check lines
  for (int i = 0; i < LINES && found == -1; i++) {
    if (grid[i][0] != EMPTY && grid[i][0] == grid[i][1] &&
        grid[i][0] == grid[i][2])
      found = grid[i][0];
  }
 
  // check cols
  for (int i = 0; i < COLS && found == -1; i++) {
    if (grid[0][i] != EMPTY && grid[0][i] == grid[1][i] &&
        grid[0][i] == grid[2][i])
      found = grid[0][i];
  }
 
  // check diags
  if (found == -1 && grid[0][0] != EMPTY &&
      grid[0][0] == grid[1][1] && grid[0][0] == grid[2][2])
    found = grid[0][0];
  if (found == -1 && grid[0][2] != EMPTY &&
      grid[0][2] == grid[1][1] && grid[0][2] == grid[2][0])
    found = grid[0][2];
 
  if (found != -1) {
    celebrate(found);
    restart();
  } else {
    for (int i = 0; i < LINES; i++) {
      for (int j = 0; j < COLS; j++) {
        if (grid[i][j] == EMPTY) {
          done = 0;
        }
      }
    }
 
    if (done) {
      draw();
      restart();
    }
  }
}