Project tutorial
Multi Player Score Tracker

Multi Player Score Tracker © GPL3+

Device for tracking multiple players' or teams' score for games and activities.

  • 1,051 views
  • 0 comments
  • 2 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

About this project

Assume you are part of a game or activity with following requirements

  • Multiple players or teams will be participating, don't know the count of players in advance.
  • Each player or team will be earning or losing points based on the rule of the game.
  • Score of every players need to tracked using one device instead of multiple devices.
  • Game or activity may span across days, option to store and retrieve score during breaks or even after days.
  • Option to reset score of all players when you restart or reset the game.
  • Option to decide on increment and decrement step for the score.
  • Game is planned to happen outdoor (or indoor) with no access to power supply.

Multi Player Score Tracker (MPST) explained here has the features to support all of the requirements listed above.

This unit can be a gift to kids for their daily game and competitions.

Description of the system

MPST is built with one Arduino (any variant), one 16x2 LCD display, 3 push button switches and few other components. Different combinations of button strokes are used for achieving various operations. This unit can be powered by 5V USB supply or by 9V battery. While turning the device off score of all players get stored in EEPROM for later retrieval and for resuming game. On powering up the device previously stored score get from EEPROM and can be resumed. As the number of components are minimal it can be assembled in a small box (please refer to prototype image) for easy handling. By powering it with 9V battery unit can be taken for outdoor games as well.

System can be operated and controlled in 3 different modes with the help of 3 push buttons named ‘player’, ‘score up’ and ‘score down’ buttons. All 3 button’s status is scanned continuously to detect short press, long press and to decide on operation mode.

  • View score mode - This is the default mode, in this mode score of every player can be reviewed by just pressing ‘score up’ or ‘score down’ buttons. With every press of ‘up’ or ‘down’ buttons system cycles through score of each player.
  • Update score mode - This mode is for score update operation. Just press ‘player’ button to enter into this mode. With every push of ‘player’ button system cycle through each player’s score update mode. ‘Up’ and ‘down’ buttons are used for incrementing and decrementing score by the configured step value. To exit from this mode press and hold ‘player’ button for 2 secs.
  • Config mode - Press and hold both ‘score up’ and ‘score down’ simultaneously for 2 seconds to enter into this mode (make sure the first button to press is ‘score up’ followed by ‘score down’). There are 3 different configurable parameters, with every push of ‘player’ button system cycle through following 3 configurable parameters.
  • 1.Number of players : This is the default option in config mode. Use ‘score up’ and ‘score down’ buttons to increment and decrement player count.
  • 2.Reset score ? : This is for resetting every players score to zero. Use ‘score up’ and ‘score down’ button to answer ‘YES’ or ‘NO’ to this question.
  • 3.Score step : This is for setting incremental / decremental step for score while updating score. Minimum is 1 and it can be set to any integer number. Use ‘score up’ and ‘score down’ buttons to choose the number needed.

In order to exit from config mode press and hold ‘player’ button for 2 secs.

There is a 10ms timer configured with the help of MsTimer2 library. Purpose of the timer function powerLossDetector() is to monitor the voltage level at A3 input and to keep C2 charged at a steady level. During power failure A3 input will show higher voltage level which will trigger EEPROM write operation to save all scores. High value capacitor C1 is a must for powering Arduino unit while writing data into EEPROM.

Program flow chart

Additional details

Please visit my github for extra information on this project : https://github.com/shajeebtm/Arduino-multi-player-score-tracker/

Code

Multi_Player_Score_Tracker.inoArduino
#include <LiquidCrystal.h>
#include <MsTimer2.h>
#include <EEPROM.h>

#define MAX_PLAYERS 50    // maximum allowed, depends on EEPROM size & memory available
#define MIN_PLAYERS 1     // minimum players needed
#define debounce 50       // switch debounce time, in milli seconds
#define HOLDTIME 2000     // for switch long click , in milli seconds
#define INT_SIZE 2
#define MAX_SCORE_STEP 10 // maximum number allowed for scoreStep variable

int players;
int scoreStep = 1 ;      // default value for score increase/decrease by this number
int currentPlayer;
int playerButtonState;
int upButtonState;
int dnButtonState;
int mode = 0 ; // operation modes:  0 = view score, 1 = update score , 2 = configure
int POINTS[MAX_PLAYERS+1] ;   // runtime score stored in an array
unsigned long configWait;
boolean configStarted = LOW;
int configMode = 1; // configuration modes: 1 = no of players, 2 = reset score , 3 = score STEP
boolean resetScore = LOW;
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // initialize LCD


// ++++++++++ BEGIN: Class sButton ++++++++++
// orginal button click logic is from http://jmsarduino.blogspot.com
// I did convert it into a Class so that I can create multiple objects
class sButton {
    const byte pin;
    int state;
    int prevState;
    int overallState; // 0 = not clicked , 1 = short click , 2 = long click (hold)
    boolean ignoreUp;
    boolean hold;
    unsigned long buttonUpMs;
    unsigned long buttonDownMs;
    

  public:
    sButton(byte attachTo) :
      pin(attachTo)
    {
    }

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      state = LOW;
      hold = false;
      prevState = LOW;
      ignoreUp = false;
    }

    int stateCheck() {
      overallState = 0;
      state = digitalRead(pin);
      // Test for button pressed and store the down time
      if (state  == LOW && prevState == HIGH && (millis() - buttonUpMs ) > long(debounce))
      {
           buttonDownMs = millis();
      }
      // Test for button release and store the up time
      if (state == HIGH && prevState == LOW && (millis() - buttonDownMs) > long(debounce))
      {
        if (ignoreUp == false) {
            overallState = 1;
        }
        else ignoreUp = false;
        buttonUpMs = millis();
       }
       
    // Test for button held down for longer than the hold time
     if (state == LOW && (millis() - buttonDownMs) > long(HOLDTIME))  
     {
        ignoreUp = true;
        hold = true;
        buttonDownMs = millis();
        overallState = 2;
      }
      
      prevState = state;
      return overallState;
    }
};
// ------------END:  Class sButton --------------



// +++++++++++++ Main : Starts here ++++++++++++++++

sButton playerButton(4);      // initilaize didital pin 4 for Player sellection
sButton upButton(5);          // initialize digitla pin 5 for score increment operation
sButton dnButton(6);          // initialize digital pin 6 for score decrement operaiton

void setup() {
  playerButton.setup();
  upButton.setup();
  dnButton.setup();
  initScores();
  MsTimer2::set(10, powerLossDetector); // 10ms timer to check powerloss
  MsTimer2::start();
  lcd.begin(16, 2);
  currentPlayer=0;
  showWelcome();
}

void loop() {
  playerButtonState = playerButton.stateCheck();
  upButtonState = upButton.stateCheck();
  dnButtonState = dnButton.stateCheck();

  // +++ BEGIN: Block for handling configuration  +++++++++
  // In confure mode number of players can be set & can reset every player's score to 0
  //
  // config mode begins by holding upbutton for HOLDTIME
  if (upButtonState == 2 || mode == 2 ) { 
    if (mode != 2 ) {
      configWait = millis();            // config mode begins, up button in hold, waititng for dn button
    }
    mode = 2;
    if (dnButtonState == 2) {           // dn button also held for HOLDTIME
        configStarted = HIGH;           // now we enter into configuraiton mode
        lcd.clear();
    }
    
    // to return back to view mode if the dn button wasnt long-clicked within HOLDTIME window
    if (millis() - configWait > HOLDTIME && configStarted == LOW) mode = 0;
    
    if (configStarted == HIGH) {          // now we are in configuraiton mode
       switch(configMode){
        case 1: doPlayerCountConfig();  break;
        case 2: doResetScore(); break;
        case 3: doScoreStep(); break;
        default: break;
       } 

      if ( playerButtonState == 1) {      // changing config mode
        switch (configMode) {
          case 1: configMode = 2 ; playerButtonState = 0 ; lcd.clear(); break;
          case 2: configMode = 3 ; playerButtonState = 0 ; lcd.clear(); break;
          case 3: configMode = 1 ; playerButtonState = 0 ; lcd.clear(); break;
          default:  break;
        }
      }

        if ( playerButtonState == 2) {    // exit from configuration mode
          mode = 0;
          configMode = 1 ;
          showWelcome();
        }
    }

    // returning to the begining op loop as we are in configuraiton mode
    return ;          
  }
  configStarted = LOW;
  // ---- END: Block for handling configuration (number of players) ---------

 
    
  // +++ BEGIN: Block for updating score of each player  +++++++++ 
  if ( playerButtonState == 1) {    // jump to update score mode if update button pressed
    lcd.clear();
    if (currentPlayer == 0) currentPlayer++; 
    if (mode == 1 ) currentPlayer++;  // move to next player
    mode = 1;
    if (currentPlayer > players ) currentPlayer = 1 ;
  }

  if (mode == 1 ) {               // to stay in update score mode, show score on display
    doUpdateScore();
  }
  // ----  END: Block for updating score of each player  -------  


  
  // +++ BEGIN: Block for displaying score of each player  +++++++++ 
   if (mode == 0 && (upButtonState == 1 || dnButtonState == 1)) { // to view score
    lcd.clear();
    showPlayerScore();
   }
 // --- END : Block for displaying score of each player  --------


  // +++ BEGIN: Block for exiting from any mode to welcome screen  +++++++++    
    if ( playerButtonState == 2) {    // to exit from current  mode to  home screen
        mode = 0 ;
        currentPlayer = 0 ; 
        showWelcome();
    }
  // ---- END: Block for exiting from any mode to welcome screen  --------     
  
}

// -------------  Main : Ends here ---------------


// function for showing welcome screen
void showWelcome() {
  if (resetScore == HIGH ) {
    for (int p = 1 ; p <= players ; p++ ) {
      POINTS[p]=0;
    }

    resetScore = LOW;
  }
  
  lcd.clear();
  lcd.print ("*Score Tracker*");
  lcd.setCursor(0, 1);
  lcd.print ("No of players ");
  lcd.print (players);
  lcd.noBlink();
}


// function for showing individual player's score
void showPlayerScore () {
    if (upButtonState == 1 ) currentPlayer++ ; 
    if (dnButtonState == 1) currentPlayer--;
    if (currentPlayer > players) currentPlayer = 1;
    if (currentPlayer < 1) currentPlayer = players;
    lcd.setCursor(0, 0);
    lcd.print ("View score:");
    lcd.setCursor(0, 1);
    lcd.print ("Player ");
    lcd.print (currentPlayer);
    lcd.print (" = ");
    lcd.print (POINTS[currentPlayer]);
    lcd.print (" ");
}

// function for updating individual player's score
void doUpdateScore () {
    lcd.setCursor(0, 0);
    lcd.print ("Update score:");
    lcd.setCursor(0, 1);
    lcd.print ("Player ");
    lcd.print (currentPlayer);
    lcd.print (" = ");
    lcd.print (POINTS[currentPlayer]);
    lcd.print (" ");
  
   if (mode == 1 && upButtonState == 1) {   // increment score if up button pressed
    POINTS[currentPlayer] += scoreStep;
   }
   if (mode == 1 && dnButtonState == 1) {   // decrement point if down button pressed
    if (POINTS[currentPlayer] != 0 )  
      POINTS[currentPlayer] -= scoreStep;
   }
}

// function for  reset all  player's score
void doResetScore() {
  lcd.setCursor(0, 0);
  lcd.print("Configure: Reset");
  lcd.setCursor(0, 1);
  lcd.print("score ? ");
  if (resetScore == LOW ) {
    lcd.print("NO ");
  } else {
    lcd.print("YES");
  }
  
  if (upButtonState == 1) {   // increment score if up button pressed
    resetScore = HIGH;
   }
  if (dnButtonState == 1) {   // decrement point if down button pressed
    resetScore = LOW;
   }
}

// function for configuring number of players
void doPlayerCountConfig () {
     lcd.setCursor(0, 0);
     lcd.print ("Configure: No of ");
     lcd.setCursor(0, 1);
     lcd.print("Players = ");
     lcd.print(players);
     lcd.print(" ");
     if (upButtonState == 1) {         // increment number of players
         players++;
         if (players > MAX_PLAYERS) { 
           players = MAX_PLAYERS;        // limiting players to MAX_PLAYERS
         }
     }
     if (dnButtonState == 1) {         // decement number of players
        players--;
        if (players < MIN_PLAYERS) {
          players = MIN_PLAYERS;
        }
            
     }
}


// function for configuring sccore increase/decrease step
void doScoreStep () {
     lcd.setCursor(0, 0);
     lcd.print ("Configure: Score ");
     lcd.setCursor(0, 1);
     lcd.print("Step  = ");
     lcd.print(scoreStep);
     lcd.print(" ");
     if (upButtonState == 1) {         // increment number of players
         scoreStep++;
         if (scoreStep > MAX_SCORE_STEP ) {
            scoreStep = MAX_SCORE_STEP;
         }
     }
     if (dnButtonState == 1) {         // decement number of players
        scoreStep--;
        if (scoreStep < 1 ) {
          scoreStep = 1;              // minimum step value is 1
        }
            
     }
}


// function called by timer to detect power loss and save into EEPROM
// coutrsey to Darieee's youtube video on power losss detection
void powerLossDetector () {
  int eeAddress = 0;
  int eeValue = 0;
  digitalWrite(LED_BUILTIN, LOW);
  if (analogRead(A3) < 920) {
        pinMode(A3, OUTPUT);
        digitalWrite (A3, HIGH);
        return;
  }

  pinMode(A3, INPUT);
  if (analogRead(A3) > 1000) {  // this happens only when power fails
    eeAddress = 0;
    EEPROM.put( eeAddress, players);
    eeAddress+=INT_SIZE;
    EEPROM.put( eeAddress, scoreStep);
    eeAddress+=INT_SIZE;
    for(int i=1; i<=players; i++) {
      EEPROM.put( eeAddress, POINTS[i]);    // save score into EEPROM
      eeAddress+=INT_SIZE;
    }
    MsTimer2::stop();
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

// function to read from EEPROM and initailize score 
void initScores() {
  int eeAddress = 512;        // address 512  for tracking EEPROM usage
  int eeValue = 0;
  EEPROM.get( eeAddress, eeValue);
  if (eeValue != 89 ) {       // looks like a fresh EEPROM
    EEPROM.put( eeAddress, 89);
    players = MIN_PLAYERS;
    eeAddress = 0;
    EEPROM.put( eeAddress, players);
    eeAddress+=INT_SIZE;
    EEPROM.put( eeAddress, scoreStep);
    for(int i=1; i<=players; i++) {
        eeAddress+=INT_SIZE;
        EEPROM.put( eeAddress, POINTS[i]);
    }
    return;
  }
  eeAddress = 0;
  EEPROM.get( eeAddress, players);
  eeAddress+=INT_SIZE;
  EEPROM.get( eeAddress, scoreStep);
  
  for(int i=1; i<=players; i++) {
    eeAddress+=INT_SIZE;
    EEPROM.get( eeAddress, eeValue);
    POINTS[i]=eeValue; 
  }
}

Custom parts and enclosures

Component wiring diagram
Multi player point tracker bb b39si5cykv
Prototype unit outside view
Complete unit 1 small tcwtexykgm
Prototype unit inside view
Complete unit 2 small s2fahbtnvm

Schematics

MPST Schematic diagram
Multi player point tracker schem dp9yyrcgba

Comments

Similar projects you might like

Dual Axis Solar Tracker Panel with Auto and Manual Mode

Project tutorial by Giannis Arvanitakis

  • 17,998 views
  • 11 comments
  • 63 respects

Neopixel LED EyeBall

Project tutorial by HomeMadeGarbage

  • 6,808 views
  • 22 comments
  • 28 respects

Arduino Powered Player Pianos and Player Xylophones

Project tutorial by Netcamprojects

  • 5,425 views
  • 2 comments
  • 21 respects

Solar Tracker 35W with DC Motors

Project showcase by DemetrisEng

  • 9,096 views
  • 4 comments
  • 42 respects

The Companion IC

Project tutorial by Srijal Poojari

  • 6,027 views
  • 11 comments
  • 33 respects
Add projectSign up / Login