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
Diff: HighScoreTable.cpp
- Revision:
- 0:5fa232ee5fdf
- Child:
- 2:bb0f631a6068
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HighScoreTable.cpp Tue Jun 04 20:16:33 2013 +0000 @@ -0,0 +1,259 @@ +/* + * 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 I2C EEPROM which contains the high scores. +HighScoreTable::HighScoreTable( I2CEEPROM *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 ); +}