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-07
- Revision:
- 3:a6a0cd726ca0
- Parent:
- 2:bb0f631a6068
File content as of revision 3:a6a0cd726ca0:
/* * 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 ); }