Code critique: softball scoreboard

My first project. I simulated this on TINKERCAD & got it to work OK. Still needs some tweaking. I am not a programmer & I cobbled together the code elements from Google, etc. There are some issues I am still working on. For example, when a team is at bat, I light an LED to indicate either Visitor or Home. Initially I thought I could use one block of code for either the Visitor or the Home team. Didn’t happen (yet).

I also noticed if I press the minus button when the score or out or innings are zero the LSD will “go down” to nine. I need to correct this in the future.

I had to use A0 & A1 because I ran out of pins. I know this is verbotten but I plan to use a Mega in the prototype I am building next.

Prototyping on a very small scale. I need a recommendation on how to build the prototype, wiring, soldering etc. I know this is not the correct forum.

I find programming difficult. It doesn’t seem to be sinking in yet. My 68 yr. old brain seems a little slow on the intake. I am enjoying it though.

So please take a look at the code & give me your expert opinions.

Cheers!

code attached, I exceed the 9000 character limit. Criminy!

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

0TL_Scoreboard_01.ino (11.1 KB)

Please post your code using code brackets. People don't want to go through the trouble of downloading and opening up code.

As far as the using the minus and the number going to 9 just check it before updating using

if (minus_btn == pressed && variable > 0 )
variable = variable -1

that just says if the minus button is pressed and the variable is greater than zero
then go ahead and subtract 1

if the variable is equal to zero then the subtraction won't take place because the if is false

Thanks for the tip.

I tried to post my code directly in the post but I got an error, 9000 character limit exceeded. Is it better to split such posts into post 1 of 2 & post 2 of 2?

signature.PNG

Bwanajohn51:
My 68 yr. old brain seems a little slow on the intake. I am enjoying it though.

Your problem may be worse than you realize! :grinning:

signature.PNG

That is a lot of code for me to go through : )

Can you whittle if down to a part you are having problems with.

Normally when developing you want to work on small part of it at a time not the whole thing.

So get one piece working then get the next piece working etc...

noweare:
Can you whittle if down to a part you are having problems with.

To whittle code down to the part you are having problems with, you need to be able to identify the part you are having problems with. Not everyone who asks for help here is able to do this.

Often on these forums, people post only the part of their code which they believe is relevant to their problem. It often turns out that their problem is in a different part of their code. Or, it turns out that the problem is in how different parts of their code interact with each other, and it is impossible to see this without seeing more of the code than just the part that was posted. Posting only part of your code often leads to much frustration and wasted time for everyone. It can lead to being snarkily told to seek help at https://snippets-r-us.com/ (which, after a number of such snarky replies, was turned into a real page).

The general consensus here seems to be that a person asking for help with code should post the entire sketch.

I had to use A0 & A1 because I ran out of pins. I know this is verbotten but I plan to use a Mega in the prototype I am building next.

That's not fobidden. Who told you that? They're just regular digital pins after all. Their analog abilities are extra.

Where are the pinMode calls for all the shift register output pins?

#define vLATCH 4  // Output Register Clock 12-595
#define vCLK 3    // Shift Register Clock 11-595
#define vDATA 2   // Input 14-595
#define hLATCH 7  // Output Register Clock 12-595
#define hCLK 6    // Shift Register Clock 11-595
#define hDATA 5   // Input 14-595
#define iLATCH A2 // Output Register Clock 12-595
#define iCLK A1   // Shift Register Clock 11-595
#define iDATA A0  // Input 14-595
#define oLATCH A5 // Output Register Clock 12-595
#define oCLK A4   // Shift Register Clock 11-595
#define oDATA A3  // Input 14-595

Creating/using a shiftRegister object (you have three of them) would clean up you code considerably.

Why are you using eight identical tables and why are they located in RAM?

Hi,
Do you have a basic schematic?
How are you driving your digits?
A Mega has many. many I/O, what display do you aim to use?

Have you written any code to your Mega?

As you are working in TinkerCAD, its now time to get the Mega out and try some code?
DO NOT try and code your project all at once.
Do it in stages.
Get your buttons working.
Get you display working, just one so you can just multipy your work, BUT get one display working first.

This may sound slow but it has its rewards as you develop your project.

Tom....62yo.... :slight_smile:

I made a shiftReg and a repeatBlock object, using those makes the code a lot shorter.

The display of information (shift/or digitalWrite) could be done only when there are changes,
but it does not hurt to display the same information again.

I think there is code missing, that enforces only one of visitorState or homeState.

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

#include <IRremote.h>

byte digitTable[10] = {
  B11000000, //0
  B11111001, //1
  B10100100, //2
  B10110000, //3
  B10011001, //4
  B10010010, //5
  B10000010, //6
  B11111000, //7
  B10000000, //8
  B10010000  //9
};

class shiftReg {
  protected:
    byte latch;
    byte clock;
    byte data;
  public:
    shiftReg(byte iLatch, byte iClock, byte iData) : latch(iLatch), clock(iClock), data(iData) {}
    void begin() {
      pinMode(latch, OUTPUT);
      pinMode(clock, OUTPUT);
      pinMode(data, OUTPUT);
    }
    void twoDigits(int value) {
      byte tens = value / 10;
      byte ones = value % 10;
      digitalWrite(latch, LOW);
      shiftOut(data, clock, MSBFIRST, ~digitTable[tens]);
      shiftOut(data, clock, MSBFIRST, ~digitTable[ones]);
      digitalWrite(latch, HIGH);
    }
};

class repeatBlock {
  protected:
    uint32_t lastTime;
  public:
    repeatBlock() : lastTime(0) {}
    bool distantEnough() {
      bool retVal = false;
      if (millis() - lastTime >= 150) {
        retVal = true;
      }
      lastTime = millis();
      return retVal;
    }
};

const int receiver = 11; // pin 1 of IR receiver to Arduino digital pin 11
IRrecv irrecv(receiver);
decode_results results;

shiftReg visitor(4, 3, 2), home(7, 6, 5), innings(A2, A1, A0), outs(A5, A4, A3);

//====== LEDs ============
const byte visitorAtBat = 12;  // lights Visitor on scoreboard & uses Visitor score function
const byte homeAtBat = 13;     //
const byte base_1_led = 8;     // toggles 1st base led
const byte base_2_led = 9;     // toggles 2nd base led
const byte base_3_led = 10;    // toggles 3rd base led
const byte HR_Visitor_led = 1; // toggles Visitor led
const byte HR_Home_led = 0;    // toggles Visitor led

int scoreVcounter = 0;        // Visitor team score
int scoreHcounter = 0;        // Home team score
int inningsCounter = 0;       // Innings
int outsCounter = 0;          // Outs

//====== Logic ============
boolean visitorState = false;
boolean homeState = false;
boolean baseState1 = false;
boolean baseState2 = false;
boolean baseState3 = false;
boolean vScoreState = false;
boolean hScoreState = false;
boolean inningState = false;
boolean outState = false;
boolean visitorHR = false;
boolean homeHR = false;

//====== Timings ============
repeatBlock times[15];

void setup() {
  Serial.begin(250000);
  visitor.begin();
  home.begin();
  innings.begin();
  outs.begin();
  pinMode(visitorAtBat, OUTPUT);
  pinMode(homeAtBat, OUTPUT);
  pinMode(base_1_led, OUTPUT);
  pinMode(base_2_led, OUTPUT);
  pinMode(base_3_led, OUTPUT);
  pinMode(HR_Visitor_led, OUTPUT);
  pinMode(HR_Home_led, OUTPUT);
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    switch (results.value) {
      ///////  At Bat Team  ///////
      case 0xFD00FF: // //button power, R2C1, visitor at bat
        if (times[0].distantEnough())
          visitorState = !visitorState;
        digitalWrite(visitorAtBat, visitorState);
        break;
      case 0xFD40BF: // //button func/stop, R1C3, home at bat
        if (times[1].distantEnough())
          homeState = !homeState;
        digitalWrite(homeAtBat, homeState);
        break;
      ///////  Bases  ///////
      case 0xFD58A7: // first base, button 9, R7B3
        if (times[2].distantEnough())
          baseState1 = !baseState1;
        digitalWrite(base_1_led, baseState1);
        break;
      case 0xFD9867: // second base, button 8, R7B2
        if (times[3].distantEnough())
          baseState2 = !baseState2;
        digitalWrite(base_2_led, baseState2);
        break;
      case 0xFD18E7: // third base, button 7, R7B1
        if (times[4].distantEnough())
          baseState3 = !baseState3;
        digitalWrite(base_3_led, baseState3);
        break;
      ///////  HR Indicators  ///////
      case 0xFD28D7: // home run Visitor, button 4, R6B1
        if (times[5].distantEnough())
          visitorHR = !visitorHR;
        digitalWrite(HR_Visitor_led, visitorHR);
        break;
      case 0xFD6897: // home run Visitor, button 6, R6B3
        if (times[6].distantEnough())
          homeHR = !homeHR;
        digitalWrite(HR_Home_led, homeHR);
        break;
      ///////  Score Visitor  ///////
      case 0xFD30CF: //visitor score + 1, button 0, R4B1
        if (times[7].distantEnough())
          scoreVcounter = scoreVcounter + 1;
        visitor.twoDigits(scoreVcounter);
        break;
      case 0xFD08F7: //visitor score - 1, button 1, R5B1
        if (times[8].distantEnough())
          scoreVcounter = scoreVcounter - 1;
        visitor.twoDigits(scoreVcounter);
        break;
      ///////  Score Home  ///////
      case 0xFD708F: //home score + 1, button stRept, R4B3
        if (times[9].distantEnough())
          scoreHcounter = scoreHcounter + 1;
        home.twoDigits(scoreHcounter);
        break;
      case 0xFD48B7: //home score - 1, button 3, R5B3
        if (times[10].distantEnough())
          scoreHcounter = scoreHcounter - 1;
        home.twoDigits(scoreHcounter);
        break;
      //////  INNINGS  ///////
      case 0xFD20DF: //inning - 1, button <<, R2B1
        if (times[11].distantEnough())
          inningsCounter = inningsCounter - 1;
        innings.twoDigits(inningsCounter);
        break;
      case 0xFD609F: //inning + 1, button >>, R2B3
        if (times[12].distantEnough())
          inningsCounter = inningsCounter + 1;
        innings.twoDigits(inningsCounter);
        break;
      ////// OUTS //////
      case 0xFD10EF: //out - 1, button dn, R3B1
        if (times[13].distantEnough())
          outsCounter = outsCounter - 1;
        outs.twoDigits(outsCounter);
        break;
      case 0xFD50AF: //out + 1, button ^up, R3B3
        if (times[14].distantEnough())
          outsCounter = outsCounter + 1;
        outs.twoDigits(outsCounter);
        break;
    }
    irrecv.resume();
  }
}

The above was compiled, but not tested.

P.S. I’m 63.

The flipping leds make nice objects too, shortening the code further.

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

#include <IRremote.h>

byte digitTable[10] = {
  B11000000, //0
  B11111001, //1
  B10100100, //2
  B10110000, //3
  B10011001, //4
  B10010010, //5
  B10000010, //6
  B11111000, //7
  B10000000, //8
  B10010000  //9
};

class shiftReg {
  protected:
    byte latch;
    byte clock;
    byte data;
  public:
    shiftReg(byte iLatch, byte iClock, byte iData) : latch(iLatch), clock(iClock), data(iData) {}
    void begin() {
      pinMode(latch, OUTPUT);
      pinMode(clock, OUTPUT);
      pinMode(data, OUTPUT);
    }
    void twoDigits(int value) {
      byte tens = value / 10;
      byte ones = value % 10;
      digitalWrite(latch, LOW);
      shiftOut(data, clock, MSBFIRST, ~digitTable[tens]);
      shiftOut(data, clock, MSBFIRST, ~digitTable[ones]);
      digitalWrite(latch, HIGH);
    }
};

class repeatBlock {
  protected:
    uint32_t lastTime;
  public:
    repeatBlock() : lastTime(0) {}
    bool distantEnough() {
      bool retVal = false;
      if (millis() - lastTime >= 150) {
        retVal = true;
      }
      lastTime = millis();
      return retVal;
    }
};

class flipLed : public repeatBlock {
  protected:
    bool state;
    byte pin;
  public:
    flipLed(byte iPin) : state(false), pin(iPin) {}
    void begin() {
      pinMode(pin, OUTPUT);
    }
    bool distantEnough() {
      if (repeatBlock::distantEnough()) {
        state = !state;
        digitalWrite(pin, state ? HIGH : LOW);
        return true;
      }
      return false;
    }
};

const int receiver = 11;      // pin 1 of IR receiver to Arduino digital pin 11
IRrecv irrecv(receiver);
decode_results results;

flipLed visitorAtBat(12), homeAtBat(13), base1(8), base2(9), base3(10), HRVisitor(1), HRHome(0);
shiftReg visitor(4, 3, 2), home(7, 6, 5), innings(A2, A1, A0), outs(A5, A4, A3);

int scoreVcounter = 0;        // Visitor team score
int scoreHcounter = 0;        // Home team score
int inningsCounter = 0;       // Innings
int outsCounter = 0;          // Outs

repeatBlock sTimes[8];

void setup() {
  Serial.begin(250000);
  visitor.begin();
  home.begin();
  innings.begin();
  outs.begin();
  visitorAtBat.begin();
  homeAtBat.begin();
  base1.begin();
  base2.begin();
  base3.begin();
  HRVisitor.begin();
  HRHome.begin();
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    switch (results.value) {
      ///////  At Bat Team  ///////
      case 0xFD00FF: // //button power, R2C1, visitor at bat
        visitorAtBat.distantEnough();
        break;
      case 0xFD40BF: // //button func/stop, R1C3, home at bat
        homeAtBat.distantEnough();
        break;
      ///////  Bases  ///////
      case 0xFD58A7: // first base, button 9, R7B3
        base1.distantEnough();
        break;
      case 0xFD9867: // second base, button 8, R7B2
        base2.distantEnough();
        break;
      case 0xFD18E7: // third base, button 7, R7B1
        base3.distantEnough();
        break;
      ///////  HR Indicators  ///////
      case 0xFD28D7: // home run Visitor, button 4, R6B1
        HRVisitor.distantEnough();
        break;
      case 0xFD6897: // home run Home, button 6, R6B3
        HRHome.distantEnough();
        break;

      ///////  Score Visitor  ///////
      case 0xFD30CF: //visitor score + 1, button 0, R4B1
        if (sTimes[0].distantEnough())
          scoreVcounter++;
        visitor.twoDigits(scoreVcounter);
        break;
      case 0xFD08F7: //visitor score - 1, button 1, R5B1
        if (sTimes[1].distantEnough())
          scoreVcounter--;
        visitor.twoDigits(scoreVcounter);
        break;
      ///////  Score Home  ///////
      case 0xFD708F: //home score + 1, button stRept, R4B3
        if (sTimes[2].distantEnough())
          scoreHcounter++;
        home.twoDigits(scoreHcounter);
        break;
      case 0xFD48B7: //home score - 1, button 3, R5B3
        if (sTimes[3].distantEnough())
          scoreHcounter--;
        home.twoDigits(scoreHcounter);
        break;
      //////  INNINGS  ///////
      case 0xFD609F: //inning + 1, button >>, R2B3
        if (sTimes[4].distantEnough())
          inningsCounter++;
        innings.twoDigits(inningsCounter);
        break;
      case 0xFD20DF: //inning - 1, button <<, R2B1
        if (sTimes[5].distantEnough())
          inningsCounter--;
        innings.twoDigits(inningsCounter);
        break;
      ////// OUTS //////
      case 0xFD10EF: //out - 1, button dn, R3B1
        if (sTimes[6].distantEnough())
          outsCounter--;
        outs.twoDigits(outsCounter);
        break;
      case 0xFD50AF: //out + 1, button ^up, R3B3
        if (sTimes[7].distantEnough())
          outsCounter++;
        outs.twoDigits(outsCounter);
        break;
    }
    irrecv.resume();
  }
}
  visitor.begin();
  home.begin();
  innings.begin();
  outs.begin();
  visitorAtBat.begin();
  homeAtBat.begin();
  base1.begin();
  base2.begin();
  base3.begin();
  HRVisitor.begin();
  HRHome.begin();

That list was ugly, hard to read and error prone (did you ever forget to call begin in setup for some object?).

A linked list of objects that need a call to begin can be easily be generated in the construction process.
One more class (needBegin) that is inherited by the shiftReg and flipLed objects does the job.

class needBegin {
  protected:
    needBegin* next;
    virtual void begin() {}
    static needBegin* anchor;
  public:
    needBegin() : next(anchor) {
      anchor = this;
    }
    static void doIt() {
      for (needBegin* p = anchor; p; p = p->next) {
        p->begin();
      }
    }
};

needBegin* needBegin::anchor = nullptr;

A nice example of using static attributes IMHO.

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

#include <IRremote.h>

byte digitTable[10] = {
  B11000000, //0
  B11111001, //1
  B10100100, //2
  B10110000, //3
  B10011001, //4
  B10010010, //5
  B10000010, //6
  B11111000, //7
  B10000000, //8
  B10010000  //9
};

class needBegin {
  protected:
    needBegin* next;
    virtual void begin() {}
    static needBegin* anchor;
  public:
    needBegin() : next(anchor) {
      anchor = this;
    }
    static void doIt() {
      for (needBegin* p = anchor; p; p = p->next) {
        p->begin();
      }
    }
};

needBegin* needBegin::anchor = nullptr;

class shiftReg : public needBegin {
  protected:
    byte latch;
    byte clock;
    byte data;
  public:
    shiftReg(byte iLatch, byte iClock, byte iData) : needBegin(), latch(iLatch), clock(iClock), data(iData) {}
    void begin() {
      pinMode(latch, OUTPUT);
      pinMode(clock, OUTPUT);
      pinMode(data, OUTPUT);
    }
    void twoDigits(int value) {
      byte tens = value / 10;
      byte ones = value % 10;
      digitalWrite(latch, LOW);
      shiftOut(data, clock, MSBFIRST, ~digitTable[tens]);
      shiftOut(data, clock, MSBFIRST, ~digitTable[ones]);
      digitalWrite(latch, HIGH);
    }
};

class repeatBlock {
  protected:
    uint32_t lastTime;
  public:
    repeatBlock() : lastTime(0) {}
    bool distantEnough() {
      bool retVal = false;
      if (millis() - lastTime >= 150) {
        retVal = true;
      }
      lastTime = millis();
      return retVal;
    }
};

class flipLed : public needBegin, repeatBlock {
  protected:
    bool state;
    byte pin;
  public:
    flipLed(byte iPin) : needBegin(), repeatBlock(), state(false), pin(iPin) {}
    void begin() {
      pinMode(pin, OUTPUT);
    }
    bool distantEnough() {
      if (repeatBlock::distantEnough()) {
        state = !state;
        digitalWrite(pin, state ? HIGH : LOW);
        return true;
      }
      return false;
    }
};

const int receiver = 11;      // pin 1 of IR receiver to Arduino digital pin 11
IRrecv irrecv(receiver);
decode_results results;

flipLed visitorAtBat(12), homeAtBat(13), base1(8), base2(9), base3(10), HRVisitor(1), HRHome(0);
shiftReg visitor(4, 3, 2), home(7, 6, 5), innings(A2, A1, A0), outs(A5, A4, A3);

int scoreVcounter = 0;        // Visitor team score
int scoreHcounter = 0;        // Home team score
int inningsCounter = 0;       // Innings
int outsCounter = 0;          // Outs

repeatBlock sTimes[8];

void setup() {
  Serial.begin(250000);
  needBegin::doIt();
}

void loop() {
  if (irrecv.decode(&results)) {
    switch (results.value) {
      ///////  At Bat Team  ///////
      case 0xFD00FF: // //button power, R2C1, visitor at bat
        visitorAtBat.distantEnough();
        break;
      case 0xFD40BF: // //button func/stop, R1C3, home at bat
        homeAtBat.distantEnough();
        break;
      ///////  Bases  ///////
      case 0xFD58A7: // first base, button 9, R7B3
        base1.distantEnough();
        break;
      case 0xFD9867: // second base, button 8, R7B2
        base2.distantEnough();
        break;
      case 0xFD18E7: // third base, button 7, R7B1
        base3.distantEnough();
        break;
      ///////  HR Indicators  ///////
      case 0xFD28D7: // home run Visitor, button 4, R6B1
        HRVisitor.distantEnough();
        break;
      case 0xFD6897: // home run Home, button 6, R6B3
        HRHome.distantEnough();
        break;

      ///////  Score Visitor  ///////
      case 0xFD30CF: //visitor score + 1, button 0, R4B1
        if (sTimes[0].distantEnough())
          scoreVcounter++;
        visitor.twoDigits(scoreVcounter);
        break;
      case 0xFD08F7: //visitor score - 1, button 1, R5B1
        if (sTimes[1].distantEnough())
          scoreVcounter--;
        visitor.twoDigits(scoreVcounter);
        break;
      ///////  Score Home  ///////
      case 0xFD708F: //home score + 1, button stRept, R4B3
        if (sTimes[2].distantEnough())
          scoreHcounter++;
        home.twoDigits(scoreHcounter);
        break;
      case 0xFD48B7: //home score - 1, button 3, R5B3
        if (sTimes[3].distantEnough())
          scoreHcounter--;
        home.twoDigits(scoreHcounter);
        break;
      //////  INNINGS  ///////
      case 0xFD609F: //inning + 1, button >>, R2B3
        if (sTimes[4].distantEnough())
          inningsCounter++;
        innings.twoDigits(inningsCounter);
        break;
      case 0xFD20DF: //inning - 1, button <<, R2B1
        if (sTimes[5].distantEnough())
          inningsCounter--;
        innings.twoDigits(inningsCounter);
        break;
      ////// OUTS //////
      case 0xFD10EF: //out - 1, button dn, R3B1
        if (sTimes[6].distantEnough())
          outsCounter--;
        outs.twoDigits(outsCounter);
        break;
      case 0xFD50AF: //out + 1, button ^up, R3B3
        if (sTimes[7].distantEnough())
          outsCounter++;
        outs.twoDigits(outsCounter);
        break;
    }
    irrecv.resume();
  }
}

There is not much left in setup now.

void setup() {
  Serial.begin(250000);
  needBegin::doIt();
}

The counters had to go to the shiftRegs, after that the switch only contained single function calls of objects.
So the check for the ir code could also be moved to the objects,
creating another list of objects, this time objects interested in IR codes.

The leds got one value to check the shiftRegisters got two.

Now loop got quite short too.

void loop() {
  if (irrecv.decode(&results)) {
    wantIR::doIt(results.value);
    irrecv.resume();
  }
}

The IR-codes moved to the objects.

flipLed visitorAtBat(12, 0xFD00FF); // button power, R2C1
flipLed homeAtBat(13, 0xFD40BF);    // button func/stop, R1C3
flipLed base1(8, 0xFD58A7);         // button 9, R7B3
flipLed base2(9, 0xFD9867);         // button 8, R7B2
flipLed base3(10, 0xFD18E7);        // button 7, R7B1
flipLed HRVisitor(1, 0xFD28D7);     // button 4, R6B1
flipLed HRHome(0, 0xFD6897);        // button 6, R6B3
shiftReg visitor(4, 3, 2, 0xFD30CF, 0xFD08F7);    // +1 button 0, R4B1, -1 button 1, R5B1
shiftReg home(7, 6, 5, 0xFD708F, 0xFD48B7);       // +1 button stRept, R4B3, -1 button 3, R5B3
shiftReg innings(A2, A1, A0, 0xFD609F, 0xFD20DF); // +1 button >>, R2B3, -1 button <<, R2B1
shiftReg outs(A5, A4, A3, 0xFD50AF, 0xFD10EF);    // +1 button ^up, R3B3, -1, button dn, R3B1

Here the whole sketch.

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

#include <IRremote.h>

const byte digitTable[10] PROGMEM = {
  0b00111111, //0
  0b00000110, //1
  0b01011011, //2
  0b01001111, //3
  0b01100110, //4
  0b01101101, //5
  0b01111101, //6
  0b00000111, //7
  0b01111111, //8
  0b01101111  //9
};

class needBegin {
  protected:
    needBegin* next;
    virtual void begin() {}
    static needBegin* allBegins;
  public:
    needBegin() : next(allBegins) {
      allBegins = this;
    }
    static void doIt() {
      for (needBegin* p = allBegins; p; p = p->next) {
        p->begin();
      }
    }
};

needBegin* needBegin::allBegins = nullptr;

class wantIR {
  protected:
    wantIR* next;
    virtual bool checkCode(uint32_t) {
      return false;
    }
    static wantIR* allIRs;
  public:
    wantIR() : next(allIRs) {
      allIRs = this;
    }
    static bool doIt(uint32_t irCode) {
      for (wantIR* p = allIRs; p; p = p->next) {
        if (p->checkCode(irCode)) {
          return true;
        }
      }
      return false;
    }
};

wantIR* wantIR::allIRs = nullptr;

class repeatBlock {
  protected:
    uint32_t lastTime;
  public:
    repeatBlock() : lastTime(0) {}
    bool distantEnough() {
      bool retVal = false;
      if (millis() - lastTime >= 150) {
        retVal = true;
      }
      lastTime = millis();
      return retVal;
    }
};

class shiftReg : public needBegin, wantIR {
  protected:
    byte latch;
    byte clock;
    byte data;
    repeatBlock up, down;
    uint32_t upCode, downCode;
  public:
    int value;
    shiftReg(byte iLatch, byte iClock, byte iData, uint32_t iCUp, uint32_t iCDown) :
      needBegin(), wantIR(), latch(iLatch), clock(iClock), data(iData), up(), down(), upCode(iCUp), downCode(iCDown), value(0) {}
    void begin() {
      pinMode(latch, OUTPUT);
      pinMode(clock, OUTPUT);
      pinMode(data, OUTPUT);
    }
    void twoDigits(int value) {
      byte tens = value / 10;
      byte ones = value % 10;
      digitalWrite(latch, LOW);
      shiftOut(data, clock, MSBFIRST, pgm_read_byte(digitTable + tens));
      shiftOut(data, clock, MSBFIRST, pgm_read_byte(digitTable + ones));
      digitalWrite(latch, HIGH);
    }
    bool upDistantEnough() {
      if (up.distantEnough()) {
        value++;
        twoDigits(value);
        return true;
      }
      return false;
    }
    bool downDistantEnough() {
      if (down.distantEnough()) {
        value--;
        twoDigits(value);
        return true;
      }
      return false;
    }
    bool checkCode(uint32_t inIR) {
      if (inIR == upCode) {
        upDistantEnough();
        return true;
      }
      if (inIR == downCode) {
        downDistantEnough();
        return true;
      }
      return false;
    }
};

class flipLed : public needBegin, repeatBlock, wantIR {
  protected:
    byte pin;
    bool state;
    uint32_t myCode;
    void commit() {
      digitalWrite(pin, state ? HIGH : LOW);
    }
  public:
    flipLed(byte iPin, uint32_t iCode) : needBegin(), repeatBlock(), wantIR(), pin(iPin), state(false), myCode(iCode) {}
    void begin() {
      pinMode(pin, OUTPUT);
    }
    void setState(bool to) {
      if (to != state) {
        state = to;
        commit();
      }
    }
    bool distantEnough() {
      if (repeatBlock::distantEnough()) {
        state = !state;
        digitalWrite(pin, state ? HIGH : LOW);
        return true;
      }
      return false;
    }
    bool checkCode(uint32_t inIR) {
      if (inIR == myCode) {
        distantEnough();
        return true;
      }
      return false;
    }
};

const byte receiver = 11;      // pin 1 of IR receiver to Arduino digital pin 11
IRrecv irrecv(receiver);
decode_results results;

flipLed visitorAtBat(12, 0xFD00FF); // button power, R2C1
flipLed homeAtBat(13, 0xFD40BF);    // button func/stop, R1C3
flipLed base1(8, 0xFD58A7);         // button 9, R7B3
flipLed base2(9, 0xFD9867);         // button 8, R7B2
flipLed base3(10, 0xFD18E7);        // button 7, R7B1
flipLed HRVisitor(1, 0xFD28D7);     // button 4, R6B1
flipLed HRHome(0, 0xFD6897);        // button 6, R6B3
shiftReg visitor(4, 3, 2, 0xFD30CF, 0xFD08F7);    // +1 button 0, R4B1, -1 button 1, R5B1
shiftReg home(7, 6, 5, 0xFD708F, 0xFD48B7);       // +1 button stRept, R4B3, -1 button 3, R5B3
shiftReg innings(A2, A1, A0, 0xFD609F, 0xFD20DF); // +1 button >>, R2B3, -1 button <<, R2B1
shiftReg outs(A5, A4, A3, 0xFD50AF, 0xFD10EF);    // +1 button ^up, R3B3, -1, button dn, R3B1

void setup() {
  Serial.begin(250000);
  needBegin::doIt();
}

void loop() {
  if (irrecv.decode(&results)) {
    wantIR::doIt(results.value);
    irrecv.resume();
  }
}

odometer:
To whittle code down to the part you are having problems with, you need to be able to identify the part you are having problems with. Not everyone who asks for help here is able to do this.

The general consensus here seems to be that a person asking for help with code should post the entire sketch.

Makes it extremely difficult to help someone. Don’t know about you but I have better things to do.
minimal-reproducible-example

Still learning the nuances of forum. I just lost my previous reply, so I am saving as a draft first. Live & learn.

Thanks everyone for all the info. Lots of info to digest.

There seems to be two schools of thought regarding posting code. I can appreciate noweare's suggestion to post only the piece of code in question but as odometer surmised I am a newbie that doesn't know what he doesn't know.

I really appreciated Whandall's thorough replies. This will take me some time to absorb but this is exactly what I was hoping for. I knew my code was crude. Truthfully, I was amazed it worked at all.

Delta_G, I can't tell you where I "learned" my misconception about pins A0 & A1 but thanks for straightening me out.

TomGeorge, thanks for your input as well. After I study Whandall's suggestion I plan to build a working, small scale prototype. I have downloaded Express Schematic for my next step. I will use a modular approach as you suggest for the prototype construction & code.

Thanks All!

Here is a little improvement.
No need for two lists of objects, one list and some options use less memory.
Here even the options would be superfluous as both tyes of objects have a begin and a checkCode,
but for a code exercise it’s cleaner.

// OTL (over the line) Softball Scoreboard project
// Version 01 uses IR remote
// Version 01 was created on TINKERCAD simulator
// Displays which team is at bat, Visitor or Home, Score, Innings, Outs, 1 up HR indicator by team

#include <IRremote.h>

const byte digitTable[10] PROGMEM = {
  0b00111111, //0
  0b00000110, //1
  0b01011011, //2
  0b01001111, //3
  0b01100110, //4
  0b01101101, //5
  0b01111101, //6
  0b00000111, //7
  0b01111111, //8
  0b01101111  //9
};

enum {
  needBegin = 0x01,
  checkIR = 0x02,
};

class threadOpt {
  protected:
    byte opts;
    threadOpt* next;
    virtual void begin() {}
    virtual bool checkCode(uint32_t) {
      return false;
    }
    static threadOpt* anchorOpts;
  public:
    threadOpt(byte optFor) : opts(optFor), next(anchorOpts) {
      anchorOpts = this;
    }
    static void allBegin() {
      for (threadOpt* p = anchorOpts; p; p = p->next) {
        if (p->opts & needBegin) {
          p->begin();
        }
      }
    }
    static bool allCheckCode(uint32_t irCode) {
      for (threadOpt* p = anchorOpts; p; p = p->next) {
        if ((p->opts & checkIR) && p->checkCode(irCode)) {
          return true;
        }
      }
      return false;
    }
};

threadOpt* threadOpt::anchorOpts = nullptr;

class repeatBlock {
  protected:
    uint32_t lastTime;
  public:
    repeatBlock() : lastTime(0) {}
    bool distantEnough() {
      bool retVal = false;
      if (millis() - lastTime >= 150) {
        retVal = true;
      }
      lastTime = millis();
      return retVal;
    }
};

class shiftReg : public threadOpt {
  protected:
    byte latch;
    byte clock;
    byte data;
    repeatBlock up, down;
    uint32_t upCode, downCode;
  public:
    int value;
    shiftReg(byte iLatch, byte iClock, byte iData, uint32_t iCUp, uint32_t iCDown) :
      threadOpt(needBegin + checkIR), latch(iLatch), clock(iClock), data(iData), up(), down(), upCode(iCUp), downCode(iCDown), value(0) {}
    void begin() {
      pinMode(latch, OUTPUT);
      pinMode(clock, OUTPUT);
      pinMode(data, OUTPUT);
    }
    void twoDigits(int value) {
      byte tens = value / 10;
      byte ones = value % 10;
      digitalWrite(latch, LOW);
      shiftOut(data, clock, MSBFIRST, pgm_read_byte(digitTable + tens));
      shiftOut(data, clock, MSBFIRST, pgm_read_byte(digitTable + ones));
      digitalWrite(latch, HIGH);
    }
    bool upDistantEnough() {
      if (up.distantEnough()) {
        value++;
        twoDigits(value);
        return true;
      }
      return false;
    }
    bool downDistantEnough() {
      if (down.distantEnough()) {
        value--;
        twoDigits(value);
        return true;
      }
      return false;
    }
    bool checkCode(uint32_t inIR) {
      if (inIR == upCode) {
        upDistantEnough();
        return true;
      }
      if (inIR == downCode) {
        downDistantEnough();
        return true;
      }
      return false;
    }
};

class flipLed : public threadOpt, repeatBlock {
  protected:
    byte pin;
    bool state;
    uint32_t myCode;
    void commit() {
      digitalWrite(pin, state ? HIGH : LOW);
    }
  public:
    flipLed(byte iPin, uint32_t iCode) : threadOpt(needBegin + checkIR), repeatBlock(), pin(iPin), state(false), myCode(iCode) {}
    void begin() {
      pinMode(pin, OUTPUT);
    }
    void setState(bool to) {
      if (to != state) {
        state = to;
        commit();
      }
    }
    bool distantEnough() {
      if (repeatBlock::distantEnough()) {
        state = !state;
        commit();
        return true;
      }
      return false;
    }
    bool checkCode(uint32_t inIR) {
      if (inIR == myCode) {
        distantEnough();
        return true;
      }
      return false;
    }
};

const byte receiver = 11;      // pin 1 of IR receiver to Arduino digital pin 11
IRrecv irrecv(receiver);
decode_results results;

flipLed visitorAtBat(12, 0xFD00FF); // button power, R2C1
flipLed homeAtBat(13, 0xFD40BF);    // button func/stop, R1C3
flipLed base1(8, 0xFD58A7);         // button 9, R7B3
flipLed base2(9, 0xFD9867);         // button 8, R7B2
flipLed base3(10, 0xFD18E7);        // button 7, R7B1
flipLed HRVisitor(1, 0xFD28D7);     // button 4, R6B1
flipLed HRHome(0, 0xFD6897);        // button 6, R6B3
shiftReg visitor(4, 3, 2, 0xFD30CF, 0xFD08F7);    // +1 button 0, R4B1, -1 button 1, R5B1
shiftReg home(7, 6, 5, 0xFD708F, 0xFD48B7);       // +1 button stRept, R4B3, -1 button 3, R5B3
shiftReg innings(A2, A1, A0, 0xFD609F, 0xFD20DF); // +1 button >>, R2B3, -1 button <<, R2B1
shiftReg outs(A5, A4, A3, 0xFD50AF, 0xFD10EF);    // +1 button ^up, R3B3, -1, button dn, R3B1

void setup() {
  Serial.begin(250000);
  threadOpt::allBegin();
}

void loop() {
  if (irrecv.decode(&results)) {
    threadOpt::allCheckCode(results.value);
    irrecv.resume();
  }
}

Whandall, truly I am not worthy. I suspect C code flows out of your fingers as easily as English flows out of my fingers in this reply.

I have briefly reviewed your code. The next step is to get out a C textbook & see if I can understand it. My 7 yr. old granddaughter is here today so that will have to wait.