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-17
Revision:
18:70190f956a24
Parent:
3:a6a0cd726ca0

File content as of revision 18:70190f956a24:

/*
 * 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.
 *
 */

// Define this for debugging messages to be sent to serial port.
#undef CHATTY

#ifdef CHATTY
    #include "mbed.h"
    extern Serial pc;
#endif

#include "HighScoreTable.h"

/***************/
/* CONSTRUCTOR */
/***************/
// Pass pointer to an SPI EEPROM which contains the high scores.
HighScoreTable::HighScoreTable( Ser25LCxxx *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.
    char *buffer = eeprom->read( eepromAddress, memoryUsed );
    if( buffer != NULL ) {
        // 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 = (UInt8*)( 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->write( eepromAddress, memoryUsed, buffer );
            // Free memory used by buffer.
            free( buffer );
        }
    }
}

/****************************/
/* 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 = (UInt8*)eeprom->read( eepromAddress + pos, 1 );
  if( index == NULL ) {
        return;
  }
  // Point to appropriate part of data table.
  UInt16 address = eepromAddress + capacity + ( *index << 3 );
  // Free buffer containing index.
  free( index );
  // Read out characters and store in name.
  char *rawName = eeprom->read( address, PlayerName::Length );
  if( rawName == NULL ) {
    return;
  }
  memcpy( name->Name, rawName, PlayerName::Length );
  name->Name[ PlayerName::Length ] = 0;
  address += PlayerName::Length;
  free( rawName );
  // Read out score.
  char *rawScore = eeprom->read( address, 4 );
  if( rawScore == NULL ) {
    return;
  }
  *score = *((UInt32*)rawScore);
  free( rawScore );
}

/********************************/
/* DETERMINE IF EEPROM IS VALID */
/********************************/
// Returns true if EEPROM is valid.
bool HighScoreTable::EEPROMValid( void ) {
  #ifdef CHATTY
    pc.printf( "Checking validity.\r\n" );
  #endif
  UInt8 b, b2;
  // Read index from EEPROM.
  char *index = eeprom->read( eepromAddress, capacity );
  if( index == NULL ) {
    return false;
  }
  // Check all entries in the index are within range and are unique.
  for( UInt8 i = 0; i < capacity; ++i ) {
    b = index[ i ];
    #ifdef CHATTY
      pc.printf( "index[ %u ] = %u\r\n", (unsigned)i, (unsigned)b );
    #endif
    if( b >= capacity ) {
      free( index );
      return false;
    }
    // Check if any of the following bytes in the index have
    // the same value.
    for( UInt8 j = i + 1; j < capacity; ++j ) {
      b2 = index [ j ];
      if( b == b2 ) {
        #ifdef CHATTY
            pc.printf( "index[ %u ] has the same value!\r\n", (unsigned)j );
        #endif
        free( index );
        return false;
      }
    }
  }
  // Free memory used by index.
  free( index );
  // Check all entries in the data part of the table are valid.
  UInt16 address = eepromAddress + capacity;
  for( UInt8 i = 0; i < capacity; ++i ) {
    // Read name and score.
    char *entry = eeprom->read( address, 8 );
    if( entry == NULL ) {
      return false;
    }
    #ifdef CHATTY
        pc.printf( "Checking entry %u.\r\n", i );
        pc.puts( "Name:" );
    #endif
    // Check name consists only of uppercase letters.
    for( UInt8 j = 0; j < PlayerName::Length; ++j ) {
      b = (UInt8)entry[ j ];
      #ifdef CHATTY
        pc.putc( b );
      #endif
      if( ( b < PlayerName::MinChar ) || ( b > PlayerName::MaxChar ) ) {
        free( entry );
        return false;
      }
    }
    #ifdef CHATTY
        pc.puts( "\r\nScore:" );
    #endif
    // Check score consists only of valid BCD numbers.
    for( UInt8 j = PlayerName::Length; j < PlayerName::Length + 4; ++j ) {
      b = (UInt8)entry[ j ];
      #ifdef CHATTY
        pc.printf( "%02X ", (int)b );
      #endif
      if( ( ( b & 0x0F ) > 0x09 ) || ( ( b & 0xF0 ) > 0x90 ) ) {
        free( entry );
        return false;
      }
    }
    #ifdef CHATTY
        pc.puts( "\r\n" );
    #endif
    // Finished with name and score.
    free( entry );
    // Skip to next entry.
    address += 8;
  }
  // 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->write( eepromAddress, memoryUsed, (char*)buffer ); 
}