Hey everyone,
I am new to this community and was hoping I could work with someone (or a few people) in helping me to understand a program that was written. I am new to Arduino, and have done around 30 lesson from Paul Mcwhorter on Youtube, but there is a lot of differences in the terms used and the commands used that weren't brought up in the tutorials I watched. So I was wondering if anyone would be willing to meet via Zoom and talk the code through with me so I can understand what controls what? Or, if more preferable, we could discuss the code here in a thread!
The code is actually a program written by one of the main contributors to this website, DJ Harrigan, and the program is for his Tomodachi (Tamagotchi spin-off) project. I came across his YouTube video while searching how to start creating the very same type of code in Arduino that he already had developed, but I need to understand all of the components so I can rework the entire thing into a project I am trying to combine with my ceramic artwork. I am a graduate student working toward my MFA in ceramics in the US and am trying to implement some new ways of incorporating technology with clay/ceramics.
If anyone is interested in helping me on this endeavor let me know! I would really appreciate it! Of course, we won't need to go over the entire code in one sitting, but just knowing someone could help guide me in this process would be an immense relief!
The code is attached at the bottom!
Thanks for your time!
-Dylan F.
#include <Arduino.h> #include <U8g2lib.h> #include <SPI.h> #include <Bounce2.h> #include <armus.h> #define PIN_PIEZO 5 #define PIN_BTN_LEFT 6 #define PIN_BTN_RIGHT 9 #define PIN_BTN_OK 10 #define X_WIDTH 128 #define Y_HEIGHT 64 #define TEMPO 400 #define MAX_LEVEL 3 #define MAX_POWER 8 U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 11, /* dc=*/ 13, /* reset=*/ 12); Bounce btnLeft = Bounce(); Bounce btnRight = Bounce(); Bounce btnOk = Bounce(); ArMus piezo(PIN_PIEZO, TEMPO); enum gamestate { home, viewTomo, minigame, stats }; gamestate curState = home; gamestate lastState = viewTomo; boolean displayInverted = false; int curSelection = 0; int lastSelection = -1; char mIntro[] = "-3C -2C -1C C"; char mBeep[] = "C*"; char mOk[] = "2C 3C*"; char mBack[] = "3C 2C*"; char mSuccess[] = "2C 2C 2C 3C*"; char mLevelUp[] = "C C C 1C* 2C 2C 2C 3C*"; char mFailure[] = "F F -2F2"; char *tomoText[] = {"charging up...", "leveling up!", "missing!", "tired.", "strong."}; int tomoAction = 0; // placeholder for future boolean tomoIsLevelingUp = false; int tomoLevel = 0; int tomoPower = 6; // experience int tomoFrame = 0; // frame of animation unsigned long curTime = 0; unsigned long lastTime = -1; int animationInterval = 800; // Minigame vars boolean minigameAvailable = true; int minigameLives = 3; int minigameScore = 0; int curMinigame = 0; int minigameCountdown = 9; boolean minigameCountdownActive = false; //u8g2 u8g2_font_open_iconic_all_2x_t int symbolList[] = {104, 152, 223, 175, 168, 259, 96, 193}; // bug, drop, moon, earth, fire, suns, bolt, key // ******************************** PowerGo ******************************** int powerGoPresses = 0; int powerGoCountdownInterval = 70; boolean powerGoLocation = 0; // ******************************** MemoSym ******************************** enum memoSymState { msRevealed, msHidden, msIncorrect, msCorrect }; const char memoSymSelMax = 8; int memoSymIcons[] = {104, 152, 223, 175, 168, 259, 96, 193}; // bug, drop, moon, earth, fire, suns, bolt, key int memoSymIconsOrder[] = {0, 1, 2, 3, 4, 5, 6, 7}; memoSymState memoSymStates[] = {msRevealed, msRevealed, msRevealed, msRevealed, msRevealed, msRevealed, msRevealed, msRevealed}; //99 = box, 120=correct button, 121=incorrect button int memoSymGuessCount = 0; int memoSymIconToGuess = 0; // ******************************** StackIt ******************************** int stackItBallRad = 4; int stackItBallDir = -1; int stackItBallXPos = 64; int stackItLastBallXPos = -1; int stackItBallYPos = 59; int stackItBallYOffset = 0; int stackItUpdateInterval = 65; int stackItHeight = 0; // ******************************** StackIt ******************************** int lucky64ReelIcons[] = {224, 228, 232, 236}; int lucky64ReelOffset = 0; int lucky64ReelsStopped = 0; int lucky64UpdateInterval = 333; void setup(void){ // **************************************************************** BEGIN SETUP **************************************************************** Serial.begin(115200); u8g2.begin(); u8g2_prepare(); u8g2.setFont(u8g2_font_nokiafc22_tf); drawStrCentered(0, "Element14 Presents"); u8g2.setFont(u8g2_font_sticker_mel_tr); drawStrCentered(20, "TOMODACHI"); u8g2.setFont(u8g2_font_nokiafc22_tf); drawStrCentered(48, "Digital friend!"); u8g2.sendBuffer(); piezo.playMelody(mIntro); pinMode(PIN_BTN_LEFT, INPUT_PULLUP); pinMode(PIN_BTN_RIGHT, INPUT_PULLUP); pinMode(PIN_BTN_OK, INPUT_PULLUP); btnLeft.attach(PIN_BTN_LEFT); btnLeft.interval(8); btnRight.attach(PIN_BTN_RIGHT); btnRight.interval(8); btnOk.attach(PIN_BTN_OK); btnOk.interval(8); while(true){ // hang in the splash screen btnOk.update(); if (btnOk.fell()){ piezo.playMelody(mOk); flashDisplay(8); break; } } } void flashDisplay(int flashes){ for (int ii = 0; ii < flashes; ii++){ invertDisplay(); delay(100); } } void getInput(){ btnLeft.update(); btnRight.update(); btnOk.update(); if (btnOk.fell() || btnLeft.fell() || btnRight.fell()) piezo.playMelody(mBeep); //enable sound } void u8g2_prepare(void) { u8g2.setFontRefHeightExtendedText(); u8g2.setDrawColor(1); u8g2.setFontPosTop(); u8g2.setFontDirection(0); } void drawStrCentered(int yVal, const char *text){ u8g2.drawStr((X_WIDTH - u8g2.getStrWidth(text)) >> 1, yVal, text); } void drawTomo(int xPos, int yPos, int curLevel, int frame){ // Body u8g2.drawBox(xPos-2, yPos, 4, 8+(6*curLevel)); // vertical line u8g2.drawBox(xPos-12, yPos-6, 24, 6); // horizontal line // Upgrades if (curLevel >= 1){ // +antennae u8g2.drawVLine(xPos-11, yPos-11, 5); // L u8g2.drawVLine(xPos+10, yPos-11, 5); // R } if (curLevel >= 2){ // +fancy antennae u8g2.drawCircle(xPos-11, yPos-13, 2); // L u8g2.drawCircle(xPos+10, yPos-13, 2); // R } if (curLevel >= 3){ // +arms u8g2.drawLine(xPos-3, yPos+10, xPos-8, yPos+7); // L u8g2.drawLine(xPos+2, yPos+10, xPos+7, yPos+7); // R } // Eyes u8g2.setDrawColor(0); if (frame){ u8g2.drawVLine(xPos-9, yPos-4, 2); // left eye u8g2.drawVLine(xPos+10, yPos-4, 2); // right eye } else { u8g2.drawVLine(xPos-10, yPos-4, 2); // left eye u8g2.drawVLine(xPos+9, yPos-4, 2); // right eye } u8g2.setDrawColor(1); } void invertDisplay(){ // default to flash displayInverted = !displayInverted; if (displayInverted){ u8g2.sendF("c", 0x0a7); // inverse } else { u8g2.sendF("c", 0x0a6); // normal } } void drawLives(int tempLives){ u8g2.setDrawColor(0); u8g2.drawBox(0, 13, 25, 10); u8g2.setDrawColor(1); u8g2.setFont(u8g2_font_nokiafc22_tf); u8g2.setCursor(0, 13); for (int ii = 0; ii < tempLives; ii++){ u8g2.print("T"); // full battery } u8g2.sendBuffer(); } void drawScore(int tempScore){ u8g2.setDrawColor(0); u8g2.drawBox(128-10, 13, 16, 16); u8g2.setDrawColor(1); u8g2.setFont(u8g2_font_nokiafc22_tf); u8g2.setCursor(128-7, 13); u8g2.print(tempScore); u8g2.sendBuffer(); } void drawPowerGo(){ u8g2.setFont(u8g2_font_freedoomr25_mn); u8g2.setDrawColor(0); u8g2.drawBox(18, 25, 30, 39); u8g2.setDrawColor(1); u8g2.setCursor(64-9, 32-6); u8g2.print(powerGoPresses); u8g2.sendBuffer(); u8g2.setFont(u8g2_font_open_iconic_all_2x_t); } void memoSymNewPattern(){ randomSeed(analogRead(A1)); int tempIcon = 0; for (int ii = 0; ii < 8; ii++){ // shuffled the symbols in place tempIcon = memoSymIcons[ii]; long randIndex = random(8); memoSymIcons[ii] = memoSymIcons[randIndex]; memoSymIcons[randIndex] = tempIcon; } for (int ii = 0; ii < 8; ii++){ // create a random list of indexes for the current symbol to find tempIcon = memoSymIconsOrder[ii]; long randIndex = random(8); memoSymIconsOrder[ii] = memoSymIconsOrder[randIndex]; memoSymIconsOrder[randIndex] = tempIcon; } } void memoSymDrawPattern(){ int iconToDraw = 0; u8g2.setFont(u8g2_font_open_iconic_all_2x_t); //clear area u8g2.setDrawColor(0); if (!minigameCountdownActive)u8g2.drawBox(56, 13, 16, 16); // clear prompt symbol u8g2.drawBox(0, 32, 128, 16); // clear prompt symbol u8g2.setDrawColor(1); if (!minigameCountdownActive)u8g2.drawGlyph(56, 13, memoSymIcons[memoSymIconsOrder[memoSymGuessCount]]); // show a random index of the symbol array for (int ii = 0; ii < 8; ii++){ switch (memoSymStates[ii]){ case msRevealed: iconToDraw = memoSymIcons[ii]; break; case msHidden: iconToDraw = 99; // box break; case msIncorrect: iconToDraw = 121; // x button break; case msCorrect: iconToDraw = 120; // check button break; } u8g2.drawGlyph(ii*16, 32, iconToDraw); } u8g2.sendBuffer(); } void memoSymClearCursor(){ u8g2.setDrawColor(0); u8g2.drawBox(16*curSelection, 50, 16, 16); u8g2.setDrawColor(1); } void memoSymDrawCursor(){ u8g2.setFont(u8g2_font_open_iconic_all_2x_t); u8g2.drawGlyph(16*curSelection, 50, 82); // up arrow symbol u8g2.sendBuffer(); } void memoSymSetPattern(memoSymState tempState){ for (int ii = 0; ii < 8; ii++){ memoSymStates[ii] = tempState; } } void memoSymUpdateCounter(){ u8g2.setFont(u8g2_font_freedoomr10_tu); u8g2.setDrawColor(0); u8g2.drawBox(56, 13, 17, 17); u8g2.setDrawColor(1); u8g2.setCursor(61, 15); u8g2.print(minigameCountdown); u8g2.sendBuffer(); u8g2.setFont(u8g2_font_open_iconic_all_2x_t); } void minigameScoreIncrease(int pts, int maxLevel){ minigameScore+=pts; if (minigameScore > maxLevel){ piezo.playMelody(mSuccess); tomoPower++; checkTomoLevel();// test for level up! curState = stats; getInput(); // avoid triggering menu } else { drawScore(minigameScore); piezo.playMelody(mOk); } } void minigameLoseLife(){ minigameLives--; if (minigameLives < 1){ piezo.playMelody(mFailure); curState = home; getInput(); delay(600); } else { piezo.playMelody(mBack); drawLives(minigameLives); } } void stackItClearBall(){ u8g2.setDrawColor(0); u8g2.drawBox(stackItBallXPos-stackItBallRad, stackItBallYPos-stackItBallRad-stackItBallYOffset, 9, 9); u8g2.setDrawColor(1); } void stackItDrawBall(){ u8g2.drawCircle(stackItBallXPos, stackItBallYPos - stackItBallYOffset, stackItBallRad); } void stackItReset(){ stackItBallXPos = 64; stackItLastBallXPos = -1; stackItBallYPos = 59; stackItBallYOffset = 0; stackItUpdateInterval = 80; stackItHeight = 0; } void lucky64ClearReels(){ u8g2.setDrawColor(0); u8g2.drawBox(20+(24*lucky64ReelsStopped), 24, 128, 17); u8g2.setDrawColor(1); } void lucky64DrawReels(){ u8g2.setFont(u8g2_font_open_iconic_all_2x_t); for (int ii = 0; ii < 4; ii++){ u8g2.drawGlyph(20+(24*ii), 24, lucky64ReelIcons[ii]); } u8g2.sendBuffer(); } void lucky64Reset(){ for (int ii = 0; ii < 4; ii++){ lucky64ReelIcons[ii] = 224 + (4 * ii); } lucky64ReelOffset = 0; lucky64ReelsStopped = 0; } void checkTomoLevel(){ if (tomoPower >= MAX_POWER && tomoLevel < MAX_LEVEL){ tomoIsLevelingUp = true; curState = viewTomo; } else { tomoPower = MAX_POWER; } } void loop() { // **************************************************************** BEGIN MAIN LOOP **************************************************************** getInput(); curTime = millis(); // u8g2.clear(); // First switchcase handles one-time drawing if (curState != lastState){ u8g2.clear(); u8g2.setFont(u8g2_font_nokiafc22_tf); switch (curState){ case home: //print menu minigameScore = 0; curSelection = 0; curMinigame = 0; minigameCountdown = 9; minigameLives = 3; u8g2.setFont(u8g2_font_sticker_mel_tr); u8g2.drawStr(18, 1, "PowerGo"); u8g2.drawStr(18, 33, "MemoSym"); u8g2.drawStr(18, 17, "StackIt"); u8g2.drawStr(18, 49, "Lucky64"); u8g2.setFont(u8g2_font_open_iconic_all_2x_t); u8g2.drawGlyph(0, 16*curSelection, 75); // right arrow button //select menu if (lastState == minigame){ minigameAvailable = false; } break; case viewTomo: minigameAvailable = true; drawStrCentered(0, "Tomo is:"); drawStrCentered(12, tomoText[tomoAction]); drawTomo(X_WIDTH>>1, 8+(Y_HEIGHT>>1), tomoLevel, tomoFrame); while (tomoIsLevelingUp){ u8g2.clear(); u8g2.setFont(u8g2_font_nokiafc22_tf); drawStrCentered(0, "Tomo is:"); delay(700); u8g2.sendBuffer(); u8g2.setFont(u8g2_font_sticker_mel_tr); drawStrCentered(14, "MAXED OUT!"); piezo.playMelody(mLevelUp); delay(200); u8g2.sendBuffer(); flashDisplay(18); for (int ii = 0; ii < 10; ii++){ u8g2.clear(); drawTomo(X_WIDTH>>1, 8+(Y_HEIGHT>>1), tomoLevel, tomoFrame); u8g2.sendBuffer(); flashDisplay(1); delay(100); u8g2.clear(); drawTomo(X_WIDTH>>1, 8+(Y_HEIGHT>>1), tomoLevel+1, tomoFrame); flashDisplay(1); delay(100); u8g2.sendBuffer(); } tomoLevel+=1; delay(1000); u8g2.clear(); drawTomo(X_WIDTH>>1, 8+(Y_HEIGHT>>1), tomoLevel, tomoFrame); u8g2.sendBuffer(); tomoIsLevelingUp = false; curState = stats; tomoPower = 0; } break; case minigame: u8g2.drawHLine(0, 11, X_WIDTH); drawLives(minigameLives); drawScore(minigameScore); switch (curMinigame){ case 0: // PowerGo drawStrCentered(0, "PowerGo: win 1 battery"); drawPowerGo(); break; case 1: // StackIt drawStrCentered(0, "StackIt: win 2 batteries"); u8g2.drawVLine(32, 12, 52); // Borders u8g2.drawVLine(91, 12, 52); stackItReset(); stackItDrawBall(); break; case 2: // MemoSym drawStrCentered(0, "MemoSym: win 3 batteries"); memoSymNewPattern(); memoSymSetPattern(msRevealed); memoSymUpdateCounter(); memoSymDrawPattern(); curSelection = 0; memoSymGuessCount = 0; minigameCountdownActive = true; memoSymDrawCursor(); break; case 3: // Lucky64 drawStrCentered(0, "Lucky64: win X batteries"); lucky64Reset(); lucky64DrawReels(); break; } // on game end, gain power and level up break; case stats: drawStrCentered(0, "Statistics"); u8g2.setFont(u8g2_font_sticker_mel_tr); // Level u8g2.drawStr(0, 20, "LVL;"); // semicolon is swapped with colon in this font u8g2.setCursor(42, 20); u8g2.print(tomoLevel); u8g2.drawStr(0, 46, "PWR;"); u8g2.setFont(u8g2_font_battery19_tn); u8g2.setCursor(46, 42); for (int ii = 0; ii < MAX_POWER; ii++){ if (tomoPower >= ii){ u8g2.print("5"); // full battery } else { u8g2.print("0"); // empty battery } } break; default: break; } lastState = curState; u8g2.sendBuffer(); } // Second switchcase processes input events switch (curState){ // **************************************************************** HANDLE INPUT **************************************************************** case home: if (btnOk.rose()){ u8g2.setDrawColor(0); u8g2.drawBox(0, 16*curSelection, 16, 16+(16*curSelection)); curSelection++; if (curSelection>3)curSelection = 0; u8g2.setDrawColor(1); u8g2.drawGlyph(0, 16*curSelection, 75); u8g2.sendBuffer(); } if (btnOk.read() == 0 && btnOk.duration() > 1200 && minigameAvailable){ curState = minigame; curMinigame = curSelection; piezo.playMelody(mOk); } if (btnLeft.fell()){ curState = viewTomo; } if (btnRight.fell()){ curState = stats; } break; case viewTomo: if (btnOk.fell()){ // play with Tomo? minigameAvailable = true; piezo.playMelody(mOk); } if (btnLeft.fell()){ curState = stats; } if (btnRight.fell()){ curState = home; } if (curTime - lastTime > animationInterval){ tomoFrame = !tomoFrame; drawTomo(X_WIDTH>>1, 8+(Y_HEIGHT>>1), tomoLevel, tomoFrame); u8g2.sendBuffer(); lastTime = curTime; } break; case minigame: switch (curMinigame){ case 0: // PowerGo if (btnOk.fell()){ if (minigameCountdownActive == false){ powerGoPresses++; drawPowerGo(); } if (powerGoPresses >= 9 && minigameCountdownActive == false){ minigameCountdownActive = true; drawPowerGo(); //choose random symbol randomSeed(analogRead(0)); long randomSide = random(100); Serial.print("randomSide "); Serial.println(randomSide); if (randomSide<50){ // draw left side powerGoLocation = 0; u8g2.drawGlyph(0, 28, 104); // bug u8g2.drawGlyph(X_WIDTH-16, 28, 96); // bolt } else { powerGoLocation = 1; u8g2.drawGlyph(X_WIDTH-16, 28, 104); // bug u8g2.drawGlyph(0, 28, 96); // bolt } u8g2.sendBuffer(); } } if (minigameCountdownActive){ if (btnLeft.fell() && powerGoLocation){ // Did you press on a lightning bolt on the correct side? if (powerGoLocation){ powerGoPresses = 0; minigameCountdownActive = false; u8g2.setDrawColor(0); u8g2.drawBox(0, 25, X_WIDTH, 39); u8g2.setDrawColor(1); minigameScore++; if (minigameScore > 9){ piezo.playMelody(mSuccess); tomoPower++; checkTomoLevel(); flashDisplay(6); curState = home; } else { drawScore(minigameScore); piezo.playMelody(mOk); drawPowerGo(); } } else { minigameLoseLife(); } minigameCountdownActive = false; } if (btnRight.fell()){ // Did you press on a lightning bolt on the correct side? if (!powerGoLocation){ powerGoPresses = 0; minigameCountdownActive = false; u8g2.setDrawColor(0); u8g2.drawBox(0, 25, X_WIDTH, 39); u8g2.setDrawColor(1); minigameScore++; if (minigameScore > 9){ piezo.playMelody(mSuccess); tomoPower++; checkTomoLevel(); curState = home; } else { drawScore(minigameScore); piezo.playMelody(mOk); drawPowerGo(); } } else { minigameLoseLife(); } minigameCountdownActive = false; } if (curTime - lastTime > powerGoCountdownInterval && minigameCountdownActive){ // seems redundant, but can lose two lives quickly otherwise powerGoPresses--; if (powerGoPresses < 0){ powerGoPresses = 0; minigameCountdownActive = false; u8g2.setDrawColor(0); u8g2.drawBox(0, 25, X_WIDTH, 39); u8g2.setDrawColor(1); minigameLoseLife(); } drawPowerGo(); lastTime = curTime; } } break; case 2: // MemoSym if (btnOk.fell() && !minigameCountdownActive){ if (memoSymIcons[curSelection] == memoSymIcons[memoSymIconsOrder[memoSymGuessCount]]){ memoSymStates[curSelection] = msCorrect; minigameScoreIncrease(1, 8); } else { memoSymStates[curSelection] = msIncorrect; minigameLoseLife(); } // guesses increase memoSymGuessCount++; if (memoSymGuessCount > 7){ piezo.playMelody(mSuccess); tomoPower+=3; curState = home; checkTomoLevel(); flashDisplay(6); } memoSymDrawPattern(); } if (btnLeft.fell()){ memoSymClearCursor(); curSelection--; if (curSelection < 0) curSelection = 7; memoSymDrawCursor(); } if (btnRight.fell()){ memoSymClearCursor(); curSelection++; if (curSelection > 7) curSelection = 0; memoSymDrawCursor(); } if (curTime - lastTime > 1000 && minigameCountdownActive){ minigameCountdown--; if (minigameCountdown < 0){ // hide boxes memoSymSetPattern(msHidden); minigameCountdownActive = false; } else { memoSymUpdateCounter(); } memoSymDrawPattern(); lastTime = curTime; } break; case 1: // StackIt if (curTime - lastTime > stackItUpdateInterval){ stackItClearBall(); stackItBallXPos += stackItBallDir; if (stackItBallXPos < 38 || stackItBallXPos > 85){ stackItBallDir *= -1; } stackItDrawBall(); u8g2.sendBuffer(); lastTime = curTime; } if (btnOk.fell()){ //test stackable if (stackItHeight == 0){ stackItLastBallXPos = stackItBallXPos; stackItBallYOffset += 9; stackItUpdateInterval -= 16; stackItHeight++; } else if (stackItBallXPos > (stackItLastBallXPos-3) && stackItBallXPos < (stackItLastBallXPos+3)){ stackItLastBallXPos = stackItBallXPos; stackItBallYOffset += 9; stackItUpdateInterval -= 16; stackItHeight++; if (stackItHeight > 4){ piezo.playMelody(mSuccess); // turn into generic minigame success threshold function tomoPower+=2; flashDisplay(4); curState = home; checkTomoLevel();// test level up } } else { minigameLoseLife(); delay(400); } } if (btnLeft.fell()){ // placeholder } if (btnRight.fell()){ // placeholder } break; case 3: // Lucky64 if (curTime - lastTime > lucky64UpdateInterval){ lucky64ClearReels(); for (int ii = lucky64ReelsStopped; ii < 4; ii++){ // update the active reels lucky64ReelIcons[ii]++; if (lucky64ReelIcons[ii] > 239) lucky64ReelIcons[ii] = 224; } lucky64DrawReels(); lastTime = curTime; } if (btnOk.fell()){ lucky64ReelsStopped++; if (lucky64ReelsStopped > 3){ // test matches if (lucky64ReelIcons[0] == lucky64ReelIcons[1]){ minigameScoreIncrease(1, 4); if (lucky64ReelIcons[0] == lucky64ReelIcons[2]){ minigameScoreIncrease(1, 4); if (lucky64ReelIcons[0] == lucky64ReelIcons[3]){ minigameScoreIncrease(2, 4); } } } else { minigameLoseLife(); lucky64Reset(); } } } if (btnLeft.fell()){ // placeholder } if (btnRight.fell()){ // placeholder } break; } // on game end, check for level up break; case stats: if (btnOk.fell()){ // interact with stats? different view? //piezo.playMelody(mBack); } if (btnLeft.fell()){ curState = home; } if (btnRight.fell()){ curState = viewTomo; } break; } // }