Version of Robotron arcade game using LPC1768, a Gameduino shield, a serial EEPROM (for high scores), two microswitch joysticks and two buttons plus a box to put it in. 20 levels of mayhem.

Dependencies:   25LCxxx_SPI CommonTypes Gameduino mbed

HighScoreTable.cpp

Committer:
RichardE
Date:
2013-06-06
Revision:
2:bb0f631a6068
Parent:
0:5fa232ee5fdf
Child:
3:a6a0cd726ca0

File content as of revision 2:bb0f631a6068:

/*
 * SOURCE FILE : HighScoreTable.cpp
 *
 * Definition of class HighScoreTable.
 * Maintains a table of high scores with a name and a score for each entry in the table.
 *
 * The table is stored in EEPROM at an address specified when you call the constructor.
 * The table is structured as follows. This shows a table with a capacity of 3.
 *
 * Byte offset      Usage
 * -----------      -----
 * 0                Index of highest score in data that follows
 * 1                Index of second highest score in data that follows
 * 2                Index of third highest score in data that follows
 *
 * 3                First character of player's name
 * 4                Second character of player's name
 * 5                Third character of player's name
 * 6                LSB of score (in BCD)
 * 7                Byte 1 of score (in BCD)
 * 8                Byte 2 of score (in BCD)
 * 9                MSB of score (in BCD)
 * 10               Unused
 *
 * 11               First character of player's name
 * 12               Second character of player's name
 * 13               Third character of player's name
 * 14               LSB of score (in BCD)
 * 15               Byte 1 of score (in BCD)
 * 16               Byte 2 of score (in BCD)
 * 17               MSB of score (in BCD)
 * 18               Unused
 *
 * 19               First character of player's name
 * 20               Second character of player's name
 * 21               Third character of player's name
 * 22               LSB of score (in BCD)
 * 23               Byte 1 of score (in BCD)
 * 24               Byte 2 of score (in BCD)
 * 25               MSB of score (in BCD)
 * 26               Unused
 *
 * So, assuming the capacity of the table is N, the first N bytes form an index which is used to locate
 * items in the remaining N*8 bytes that follow. This is done so that inserting a new entry only involves
 * overwriting one name/score record and updating the index. You don't have to re-write all the records
 * that move down the table to make room for the new one.
 *
 */

#include "HighScoreTable.h"

/***************/
/* CONSTRUCTOR */
/***************/
// Pass pointer to an SPI EEPROM which contains the high scores.
HighScoreTable::HighScoreTable( SPIEEPROM *e ) :
    eeprom( e )
{
}

/**************/
/* DESTRUCTOR */
/**************/
HighScoreTable::~HighScoreTable() {
}

/****************************************/
/* VALIDATE EEPROM USED FOR HIGH SCORES */
/****************************************/
// Checks EEPROM used for high scores and
// if any of it looks like nonsense it
// rewrites the whole table with defaults.
void HighScoreTable::ValidateEEPROM( void ) {
  // Check if contents of EEPROM make sense.
  // If not then rewrite EEPROM with defaults.
  if( ! EEPROMValid() ) {
    WriteEEPROMDefaults();
  }
}

/**********************************************/
/* DETERMINE POSITION OF A SCORE IN THE TABLE */
/**********************************************/
// Pass score in score.
// Returns position in table (0 is top score).
// If position returned is >= capacity of table then score is not high
// enough to place in table.
UInt8 HighScoreTable::GetPositionInTable( UInt32 score ) const {
  // Look through table for a score less than the one passed.
  PlayerName name;
  UInt32 tableScore = (UInt32)0;
  for( UInt8 i = 0; i < capacity; ++i ) {
    Get( i, &name, &tableScore );
    if( tableScore < score ) {
      // Found a score that is less.
      // Return index at which it was found.
      return i;
    }
  }
  // No score found that is less than the one passed.
  // Return capacity of table to indicate not found.
  return capacity;
}

/*********************************/
/* ADD ENTRY TO HIGH SCORE TABLE */
/*********************************/
// Pass position in table to put entry in pos.
// Pass name of player in name.
// Pass score in score.
void HighScoreTable::Add( UInt8 pos, const PlayerName *name, UInt32 score ) {
    // Read the entire index, high scores and names out of EEPROM.
    // Going to do manipulations in RAM to minimise the number of
    // writes we need to do to EEPROM. Remember every time we write
    // a single byte a whole page is written so might as well
    // write a whole page in one go. Only drawback is more RAM
    // is required.
    UInt8 buffer[ memoryUsed ];
    if( eeprom->ReadBytes( eepromAddress, buffer, memoryUsed ) ) {
        // Fetch index for lowest score in the table.
        UInt8 index = buffer[ capacity - 1 ];
        // Make sure index is within range.
        if( index < capacity ) {
            // Point to section of buffer that contains name and score for
            // lowest score.
            UInt8 *address = buffer + capacity + ( index << 3 );
            // Copy characters of name into buffer.
            for( UInt8 i = 0; i < PlayerName::Length; ++i ) {
                *address++ = name->Name[ i ];
            }
            // Copy bytes of score into buffer.
            *((UInt32*)address) = score;
            address += 4;
            // Move all entries in the index below insertion point down one
            // to make room for new entry.
            for( UInt8 i = capacity - 1; i > pos; --i ) {
                buffer[ i ] = buffer[ i - 1 ];
            }
            // Insert index of newly written record at insertion point.
            buffer[ pos ] = index;
            // Write the buffer back to EEPROM.
            eeprom->WriteBytes( eepromAddress, buffer, memoryUsed ); 
        }
    }
}

/****************************/
/* GET ENTRY FROM THE TABLE */
/****************************/
// Pass position to fetch from in pos.
// Player name is returned in object pointed to by name.
// Player score is returned in integer pointed to by score.
void HighScoreTable::Get( UInt8 pos, PlayerName *name, UInt32 *score ) const {
    // Write default values to name and score.
  for( UInt8 i = 0; i < PlayerName::Length; ++i ) {
    name->Name[ i ] = (UInt8)'X';
  }
  name->Name[ PlayerName::Length ] = 0;
    *score = 0;
  // Fetch index from EEPROM.
  UInt8 index;
    if( ! eeprom->ReadBytes( eepromAddress + pos, &index, 1 ) ) {
        return;
    }
  // Point to appropriate part of data table.
  UInt16 address = eepromAddress + capacity + ( index << 3 );
  // Read out characters and store in name.
    if( ! eeprom->ReadBytes( address, (UInt8*)name->Name, PlayerName::Length ) ) {
        return;
    }
  name->Name[ PlayerName::Length ] = 0;
    address += PlayerName::Length;
  // Read out score.
    eeprom->ReadBytes( address, (UInt8*)score, 4 );
}

/********************************/
/* DETERMINE IF EEPROM IS VALID */
/********************************/
// Returns true if EEPROM is valid.
bool HighScoreTable::EEPROMValid( void ) {
  UInt8 b, b2;
  // Check all entries in the index are within range and are unique.
  for( UInt8 i = 0; i < capacity; ++i ) {
    // Read byte from EEPROM.
        if( ! eeprom->ReadBytes( eepromAddress + i, &b, 1 ) ) {
            return false;
        }
    // Check index read is less than capacity.
    if( b >= capacity ) {
      return false;
    }
    // Check if any of the following bytes in the index have
    // the same value.
    for( UInt8 j = i + 1; j < capacity; ++j ) {
            if( ! eeprom->ReadBytes( eepromAddress + j, &b2, 1 ) ) {
                return false;
            }
      if( b == b2 ) {
        return false;
      }
    }
  }
  // Check all entries in the data part of the table are valid.
  UInt16 address = eepromAddress + capacity;
  for( UInt8 i = 0; i < capacity; ++i ) {
    // Check name consists only of uppercase letters.
    for( UInt8 j = 0; j < PlayerName::Length; ++j ) {
            // Read byte from EEPROM.
            if( ! eeprom->ReadBytes( address++, &b, 1 ) ) {
                return false;
            }
      if( ( b < PlayerName::MinChar ) || ( b > PlayerName::MaxChar ) ) {
        return false;
      }
    }
    // Check score consists only of valid BCD numbers.
    for( UInt8 j = 0; j < 4; ++j ) {
            // Read byte from EEPROM.
            if( ! eeprom->ReadBytes( address++, &b, 1 ) ) {
                return false;
            }
      if( ( ( b & 0x0F ) > 0x09 ) || ( ( b & 0xF0 ) > 0x90 ) ) {
        return false;
      }
    }
    // Skip over unused byte.
    address++;
  }
  // EEPROM is valid
  return true;
}

/****************************/
/* WRITE DEFAULTS TO EEPROM */
/****************************/
// This may take a second or two to execute!
void HighScoreTable::WriteEEPROMDefaults( void ) {
    UInt8 buffer[ memoryUsed ];
  // Write index with ascending integers.
    UInt8 *ptr = buffer;
  for( UInt8 i = 0; i < capacity; ++i ) {
    *ptr++ =  i;
  }
  // Write data table with zero score entries.  
  for( UInt8 i = 0; i < capacity; ++i ) {
    // Write a name of "AAA".
    for( UInt8 j = 0; j < PlayerName::Length; ++j ) {
            *ptr++ = (UInt8)'A';
    }
        // Write a score of zero.
    *((UInt32*)ptr) = 0;
        ptr += 4;
        // Write zero to unused byte.
        *ptr++ = 0;    
    }
    // Write the buffer to EEPROM.
    eeprom->WriteBytes( eepromAddress, buffer, memoryUsed ); 
}