3x3x3 tic tac toe with 2 colored leds mix colors

Good day together,
let me first introduce myself im relative new to the arduino or general codeing thing a couple weeks maybe and was now working on this 3x3x3 tic tac toe project (what isnt realy new as i noticed during that process but no realy code examples)
i went through a lot of troubles and headachs even fixed a example code for interrupts on this site (the void had to be above setup)...

to the build of my game its 9 catodes that each conect 3 leds across the 3 layers
its 6 anodes 2 for each layer of 9 leds... cause they are 2 colored (red and yellow) leds you get it i think...

there is a poti for the selection and a tap thingy to enter the selection (actualy it only switches to next player)

the proplem ocurres if multiple leds of the same colume row (the leds layered above/below) if there are 2 of them selected chances are like 70% one of them gets to a mixed colored orange if all 3 layeres of a specific colum row field are selected they 100% all get to that mixed oranged color...

well heres my buggy baby sry if it looks still a bit messy i already tryed to clean it up a bit

typedef enum
{
  //feld status spieler 1 und 2
  feld_P1         = 0,
  feld_P2         = 1,
  //feld status unbelegt
  feld_X          = 2,
  //feld status ausgewählt
  feld_selection1 = 3,
} feld_t;

const byte interPin = 2;

feld_t spielfeld[3][3][3];

byte katPin[9] = {1, 3, 4, 5, 6, 7, 8, 9, 10};

byte anoPin[2][3] = {
  {A3, A4, A5},
  {11, 12, 13}
};

int feldSelect_X;
int feldSelectRandom;
int turnNr = 0;

bool spieler = true;
int spielerState = 1;

int s = 0;
int i = 0;
int z = 0;
int x = 0;
int pin;
int apin;
int taster = 2;

int farbe;
int spalte;
int zeile;
int ebene;
int spalteS;
int zeileS;
int ebeneS;


//interrupt auf taster eingabe
void wechsel()
{
  spieler = !spieler;
}

void setup()
//randomSeed für den ersten zug bias auszuschalten
{
  randomSeed(analogRead(A1));

  //taster und interrupt pin
  pinMode (interPin, INPUT_PULLUP);

  //kathoden pins die jeweils alle 3 ebenen der 9 felder verbinden
  for (int i = 0; i <= 8; i++)
  {
    pinMode(katPin[i], OUTPUT);
  }

  //anoden pins jede der 3 ebenen wird von jeweils 2 anoden pins angesteuert für die farben der spieler
  for (int i = 0; i <= 1; i++)
  {
    for (int o = 0; o <= 2; o++)
    {
      pinMode(anoPin[i][o], OUTPUT);
    }
  }

  // alle felder auf status unbelegt setzen
  for (int spalte = 0; spalte <= 2; spalte++)
  {
    for (int zeile = 0; zeile <= 2; zeile++)
    {
      for (int ebene = 0; ebene <= 2; ebene++)
      {
        spielfeld[spalte][zeile][ebene] = feld_X ;
      }
    }
  }

  attachInterrupt(0, wechsel, FALLING);
}

// funktion gegen ersten zug bias von tic tac toe

void first()
{ feldSelectRandom = random(26); if (feldSelectRandom == 13) {
    feldSelectRandom = -1;
  }
  else {
    int spalteS = feldSelectRandom % 3;
    int zeileS = (feldSelectRandom % 9) / 3;
    int ebeneS = feldSelectRandom / 9;
    for (ebene = 0; ebene <= 2; ebene++)
    { for (int farbe = 0; farbe <= 1; farbe++)
      {
        for (spalte = 0; spalte <= 2; spalte++)
        {
          for (zeile = 0; zeile <= 2; zeile++)
          {
            if ((spalte == spalteS) && (zeile == zeileS) && (ebene == ebeneS))
            {

              spielfeld[spalteS][zeileS][ebeneS] = feld_P2;
            }
          }
        }
      }
    } spieler = !spieler;
    spielerState = spieler;
  }
}

// diese funktion fragt status der felder ab und steuert sie entsprechend an

void spielanzeige()
{ poti();

  for (int farbe = 0; farbe <= 1; farbe++)
  {
    for (ebene = 0; ebene <= 2; ebene++)
    {
      apin = anoPin[farbe][ebene];
      for (spalte = 0; spalte <= 2; spalte++)
      {
        for (zeile = 0; zeile <= 2; zeile++)
        {
          pin = katPin[zeile * 3 + spalte];
          feld_t feld = spielfeld[spalte][zeile][ebene];
          if (feld == feld_X)
          {
            digitalWrite(pin, HIGH);
          }
          else if (feld == farbe)
          {
            digitalWrite(pin, LOW);
          }
          else if ((feld == feld_selection1) && (farbe == spieler))
          {
            digitalWrite(pin, LOW);
          }
        }
      }
      digitalWrite(apin, HIGH);
      delay(1);
      digitalWrite(apin, LOW);


    }
  }
}


//funktion (ist in die poti() funktion verkapselt)
//die nach dem auslösen des interrupts/tasters
//die aktuelle auswahl des poti´s ans spielfeld übergibt
//und zum anderen spieler wechselt

void auswahl()
{
  if (spielerState != spieler)
  {
    feldSelect_X = map(analogRead(A0), 0, 1023, 0, 26);

    // Serial.print(feldSelect_X);
    int spalteS = feldSelect_X % 3;
    int zeileS = (feldSelect_X % 9) / 3;
    int ebeneS = feldSelect_X / 9;
    for (ebene = 0; ebene <= 2; ebene++)
    { for (int farbe = 0; farbe <= 1; farbe++)
      {
        for (spalte = 0; spalte <= 2; spalte++)
        {
          for (zeile = 0; zeile <= 2; zeile++)
          {
            if ((spalte == spalteS) && (zeile == zeileS) && (ebene == ebeneS))
            {
              if (spielfeld[spalteS][zeileS][ebeneS] == feld_selection1)
              {
                if (spieler == true)
                {
                  spielfeld[spalteS][zeileS][ebeneS] = feld_P1;
                  //  unsigned long currentTime = millis();
                  //lastTimeP1 = currentTime;
                }
                else
                {
                  spielfeld[spalteS][zeileS][ebeneS] = feld_P2;
                  // unsigned long currentTime = millis();
                  //lastTimeP2 = currentTime;
                }
              }
            }
          }
        }
      }
    }
  }
  spielerState = spieler;
}

//funktion (ist in die spielanzeige() verkapselt) die
//die aktuell ausgewähltes feld ans spielfeld übergibt
//um sie anzeigen zu lassen

void poti()
{
  auswahl();
  feldSelect_X = map(analogRead(A0), 0, 1023, 0, 26);
  int spalteS = feldSelect_X % 3;
  int zeileS = (feldSelect_X % 9) / 3;
  int ebeneS = feldSelect_X / 9;
  for (ebene = 0; ebene <= 2; ebene++)
  {
    for (spalte = 0; spalte <= 2; spalte++)
    {
      for (zeile = 0; zeile <= 2; zeile++)
      {
        if ((spalte == spalteS) && (zeile == zeileS) && (ebene == ebeneS))
        {
          if (spielfeld[spalteS][zeileS][ebeneS] == feld_X)
            spielfeld[spalteS][zeileS][ebeneS] = feld_selection1;
        }
        else if (spielfeld[spalte][zeile][ebene] == feld_selection1)
        {
          spielfeld[spalte][zeile][ebene] = feld_X;
        }
      }
    }
  }
}

void loop()
//anti erster zug bias
{
  if (turnNr == 0)
  {
    first();
    turnNr = +1;
  }
  //das spiel :)
  spielanzeige();


}

there is a build suspicion of me that are related to the build cause the resistors connected to the catodes right now im checking on that but code related my only suspicion is im an idiot :sunglasses:

sorry for the long introduction n stuff but explaining that issue kind of brings my english to it limits ^^ hope you can understand what i mean. some parts of the code are german if needed i could make a translated version if it helps you to help me :slight_smile:

thanks upfront for your time investment

well the suspicion with the resistors is half true it can cause yellow to get much darker if there is a led with red also connected means i for sure will have to rebuild the cube anyway... yeah ;D but still there has to be a code issue too... cause even if all 3 on the same catode are same color they suddenly all get mixed to orange...

Thanks for using code tags in your first post. +1 Karma.

Can we have a schematic please? Hand drawn is ok. If you want to use Fritzing, please ensure you use the schematic view not the breadboard view.

i worked with fritzing for the first time so hope this works i used rgb led here in reality its only 2 colored leds that i couldnt find in fritzing so i just didnt connect one of the colors hope the sketch is any readable

Your schematic drawing skills need improving, its quite a mess. Take some time to study the schematics you see on-line to see how they are drawn. I would suggest:

  • Keep connection lines horizontal or vertical wherever possible
  • Don't draw connection lines over the top of components
  • Use the "Vcc" and "GND" symbols as many times as you like to reduce the number of connection lines

well yeah i agree as sayed was my first time and the 3 dimensional cube made it a bit of a challange :frowning: sry but i will work on it...
but there are good news
i finaly managed to fix that proplem i had and basicly finished my project :slight_smile:
even added winconditions (even its most likely not in the efficients way since im still kind of a newby),
a win state and another tapswitch for reset the game.
only bug the color/player wount switch properly everytime after confirm selection... its a 1 out of 10 thing but i already have some idea why this could happen...

another question do you think i should share my sketch and schematic after cleaning up and translating it?
hm anyway thx already for your time taken

Yes, absolutely you should share it. It may be useful to other beginners in future and we can suggest ways to improve your circuit and code. Perhaps we can fix your button problem.

unfortunaly the code itself exceeds 9000 characters already :fearful:
i attached the file for now. :confused:
well so far i only translated the comments and added some more... i got some help with my player switch issue it was like i expected the tap input (that is a interrupt) wasnt debounced. since i couldnt work with a simple delay during the interrupt there is a inline assembler work around to use it. now works fine.

im going to make a full translated version during next couple days cause i will have to complete rewrite and make the schematic more readable+ add the reset button.

hopefuly its going to be helpful for some was a lot trouble to make it work. till then.

3D_TicTacToe_Final_v2-8.ino (10.6 KB)

typedef enum
{
  //position player1 state
  pos_P1         = 0,
  //position player2 state
  pos_P2         = 1,
  //position empty state
  pos_X          = 2,
  //position selected state
  pos_Selected = 3,
} pos_t;

int cPin;
int aPin;
const byte interruptPin = 2;
int resetPin = 0;


// playground that array stores all position values
pos_t gamegrid[3][3][3];

byte cathodePin[9] = {1, 3, 4, 5, 6, 7, 8, 9, 10};

byte anodePin[2][3] = {
  {A3, A4, A5},
  {11, 12, 13}
};

byte select;
byte select1;
byte select2;
byte selectionPoti;
byte selectionRandom;

bool firstTurn = true;

volatile bool player = true;
bool playerState = true;

byte i = 0;
byte x = 0;

int color;
int column;
int row;
int layer;
int columnX;
int rowX;
int layerX;

byte winner = 3;

volatile bool is_called = false;

//interrupt on tapswitch falling to change player "player= !player;" cause it didnt change player properly
//i got some help with this so far i understand it interrupts the interrupt to make the delay possible
//its needed to debounce the input what most likely was the issue befor... now works fine

void endTurn()
{
  volatile byte statusReg = SREG;
  if (is_called) return;
  is_called = true;
  asm (
    "sei"
  );
  delay(250);
  player = !player;
  asm (
    "cli"
  );
  is_called = false;
  SREG = statusReg;
}

void setup()
{
  //randomSeed to make the firstTurnRng to counter first move bias
  randomSeed(analogRead(A1));
  pinMode (resetPin, INPUT_PULLUP);
  pinMode (interruptPin, INPUT_PULLUP);

  for (int i = 0; i <= 8; i++)
  {
    pinMode(cathodePin[i], OUTPUT);
  }

  for (int i = 0; i <= 1; i++)
  {
    for (int o = 0; o <= 2; o++)
    {
      pinMode(anodePin[i][o], OUTPUT);
    }
  }

  // set all fields to empty befor start
  for (int column = 0; column <= 2; column++)
  {
    for (int row = 0; row <= 2; row++)
    {
      for (int layer = 0; layer <= 2; layer++)
      {
        gamegrid[column][row][layer] = pos_X ;
      }
    }
  }

  attachInterrupt(0, endTurn, FALLING);
}


void firstTurnRng()
{

  if (firstTurn == true) {
    selectionRandom = random(25);
    if (selectionRandom >= 13) {
      selectionRandom = selectionRandom + 1;
    }
    int columnX = selectionRandom % 3;
    int rowX = (selectionRandom % 9) / 3;
    int layerX = selectionRandom / 9;

    gamegrid[columnX][rowX][layerX] = pos_P2;
    player = !player;
    playerState = player;
    firstTurn = !firstTurn;
    select = selectionPoti;
  }
}

void showGame()
{
  potsValSelect();

  //loops through the color / player values
  for (int color = 0; color <= 1; color++)
  {
    //this 3 loops go through all possible positions
    for (layer = 0; layer <= 2; layer++)
    {
      aPin = anodePin[color][layer];
      for (column = 0; column <= 2; column++)
      {
        for (row = 0; row <= 2; row++)
        {
          cPin = cathodePin[row * 3 + column];
          pos_t pos = gamegrid[column][row][layer];

          //checking there values and set the pins accordingly
          if (pos == pos_X)
          {
            digitalWrite(cPin, HIGH);
          }
          else if (pos == color)
          {
            digitalWrite(cPin, LOW);
          }
          else if ((pos != color) && (pos != pos_Selected))
          {
            digitalWrite(cPin, HIGH);
          }
          else if (pos == pos_Selected)
          {
            digitalWrite(cPin, (color == player) ? LOW : HIGH);
          }
        }
      }
      digitalWrite(aPin, HIGH);
      delay(1);
      digitalWrite(aPin, LOW);
    }
  }
  //checks if player entered a selection
  if (playerState != player)
  {
    enterSelection();
  }
}

void enterSelection()
{
  // brakeing down the value between 0-26 to get the dedicated pos in the 3 dimensional array
  int columnX = select1 % 3;
  int rowX = (select1 % 9) / 3;
  int layerX = select1 / 9;
  if (gamegrid[columnX][rowX][layerX] == pos_Selected)
  {
    //it changes it value to the actual player
    if (player == true)
    {
      gamegrid[columnX][rowX][layerX] = pos_P1;
    }
    else
    {
      gamegrid[columnX][rowX][layerX] = pos_P2;
    }
    //saves change to next player in playerState
    playerState = player; select++;
  }
}

void setPotsValue()
{
  selectionPoti = map(analogRead(A0), 0, 1023, 0, 26);
  if (select != selectionPoti)
  {
    select = selectionPoti;
    for (select2 = select; select2 <= 26; select2++)
    {
      int columnX = select2 % 3;
      int rowX = (select2 % 9) / 3;
      int layerX = select2 / 9;

      if (gamegrid[columnX][rowX][layerX] == pos_X)
      {
        select1 = select2; return;
      }
    }
  }
}

void potsValSelect()
{

  int columnX = select1 % 3;
  int rowX = (select1 % 9) / 3;
  int layerX = select1 / 9;
  //loop through all possible positions
  for (layer = 0; layer <= 2; layer++)
  {
    for (column = 0; column <= 2; column++)
    {
      for (row = 0; row <= 2; row++)
      { //overwrites the previous selected positions to empty again
        if (gamegrid[column][row][layer] == pos_Selected)
        {
          gamegrid[column][row][layer] = pos_X;
        }
      }
    }
  }
  if ((winner == pos_P1) || (winner == pos_P2)) {
    return;
  }
  gamegrid[columnX][rowX][layerX] = pos_Selected;
  // and yet another check if player state changed
}

//check if win conditions are met
void win()
//loop through both player colors
{ for (color = 0; color <= 1; color++)
  {
    //and all posible positions
    for (layer = 0; layer <= 2; layer++)
    {
      for (column = 0; column <= 2; column++)
      {
        for (row = 0; row <= 2; row++)
        {
          //check conditions
          if
          (
            ((gamegrid[0][row][layer] == color) && (gamegrid[1][row][layer] == color) && (gamegrid[2][row][layer] == color))
            ||
            ((gamegrid[column][0][layer] == color) && (gamegrid[column][1][layer] == color) && (gamegrid[column][2][layer] == color))
            ||
            ((gamegrid[column][row][0] == color) && (gamegrid[column][row][1] == color) && (gamegrid[column][row][2] == color))
            ||
            ((gamegrid[column][0][0] == color) && (gamegrid[column][1][1] == color) && (gamegrid[column][2][2] == color))
            ||
            ((gamegrid[column][2][0] == color) && (gamegrid[column][1][1] == color) && (gamegrid[column][0][2] == color))
            ||
            ((gamegrid[0][0][layer] == color) && (gamegrid[1][1][layer] == color) && (gamegrid[2][2][layer] == color))
            ||
            ((gamegrid[2][0][layer] == color) && (gamegrid[1][1][layer] == color) && (gamegrid[0][2][layer] == color))
            ||
            ((gamegrid[0][row][0] == color) && (gamegrid[1][row][1] == color) && (gamegrid[2][row][2] == color))
            ||
            ((gamegrid[2][row][0] == color) && (gamegrid[1][row][1] == color) && (gamegrid[0][row][2] == color))
            ||
            ((gamegrid[0][0][0] == color) && (gamegrid[1][1][1] == color) && (gamegrid[2][2][2] == color))
            ||
            ((gamegrid[2][0][0] == color) && (gamegrid[1][1][1] == color) && (gamegrid[0][2][2] == color))
            ||
            ((gamegrid[2][2][0] == color) && (gamegrid[1][1][1] == color) && (gamegrid[0][0][2] == color))
            ||
            ((gamegrid[0][2][0] == color) && (gamegrid[1][1][1] == color) && (gamegrid[2][0][2] == color))
          )
          {
            //if check which player met the conditions and set him to winner
            if (color == pos_P1) {
              winner = pos_P1;
            }
            if (color == pos_P2)  {
              winner = pos_P2;
            }
          }
        }
      }
    }
  }
  // make all leds switch to winners color
  if ((winner == pos_P1) || (winner == pos_P2))
  {
    for (layer = 0; layer <= 2; layer++)
    {
      for (column = 0; column <= 2; column++)
      {
        for (row = 0; row <= 2; row++)
        {
          gamegrid[column][row][layer] = winner;
        }
      }
    }
  }
}

void reset() {
  //loop all positions
  for (layer = 0; layer <= 2; layer++)
  {
    for (column = 0; column <= 2; column++)
    {
      for (row = 0; row <= 2; row++)
      {
        //set them to empty
        gamegrid[column][row][layer] = pos_X;
      }
    }
  }
  //set all needed start values to default again
  firstTurn = true;
  player = true;
  playerState = 1;
  winner = 3;
}

void loop()
{
  // well and thats the acutal game. ENJOY!
  firstTurnRng();
  setPotsValue();
  showGame();
  win();
  if (digitalRead(resetPin) == LOW) {
  reset();
  }
}

above you see my final translated and cutted version to fit here.
there is not a single bug left i call it a finished project.
hopefuly it can be of use to someone. if there are questions im glad to help.