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

Committer:
RichardE
Date:
Fri Jun 07 20:29:59 2013 +0000
Revision:
3:a6a0cd726ca0
Parent:
2:bb0f631a6068
Abandoned writing serial EEPROM class and used Ser25LCxxx library instead. HighScoreTable class appears to be reading and writing correctly to EEPROM and high scores are displayed correctly.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
RichardE 2:bb0f631a6068 1 /*
RichardE 2:bb0f631a6068 2 * SOURCE FILE : HighScoreTable.cpp
RichardE 2:bb0f631a6068 3 *
RichardE 2:bb0f631a6068 4 * Definition of class HighScoreTable.
RichardE 2:bb0f631a6068 5 * Maintains a table of high scores with a name and a score for each entry in the table.
RichardE 2:bb0f631a6068 6 *
RichardE 2:bb0f631a6068 7 * The table is stored in EEPROM at an address specified when you call the constructor.
RichardE 2:bb0f631a6068 8 * The table is structured as follows. This shows a table with a capacity of 3.
RichardE 2:bb0f631a6068 9 *
RichardE 2:bb0f631a6068 10 * Byte offset Usage
RichardE 2:bb0f631a6068 11 * ----------- -----
RichardE 2:bb0f631a6068 12 * 0 Index of highest score in data that follows
RichardE 2:bb0f631a6068 13 * 1 Index of second highest score in data that follows
RichardE 2:bb0f631a6068 14 * 2 Index of third highest score in data that follows
RichardE 2:bb0f631a6068 15 *
RichardE 2:bb0f631a6068 16 * 3 First character of player's name
RichardE 2:bb0f631a6068 17 * 4 Second character of player's name
RichardE 2:bb0f631a6068 18 * 5 Third character of player's name
RichardE 2:bb0f631a6068 19 * 6 LSB of score (in BCD)
RichardE 2:bb0f631a6068 20 * 7 Byte 1 of score (in BCD)
RichardE 2:bb0f631a6068 21 * 8 Byte 2 of score (in BCD)
RichardE 2:bb0f631a6068 22 * 9 MSB of score (in BCD)
RichardE 2:bb0f631a6068 23 * 10 Unused
RichardE 2:bb0f631a6068 24 *
RichardE 2:bb0f631a6068 25 * 11 First character of player's name
RichardE 2:bb0f631a6068 26 * 12 Second character of player's name
RichardE 2:bb0f631a6068 27 * 13 Third character of player's name
RichardE 2:bb0f631a6068 28 * 14 LSB of score (in BCD)
RichardE 2:bb0f631a6068 29 * 15 Byte 1 of score (in BCD)
RichardE 2:bb0f631a6068 30 * 16 Byte 2 of score (in BCD)
RichardE 2:bb0f631a6068 31 * 17 MSB of score (in BCD)
RichardE 2:bb0f631a6068 32 * 18 Unused
RichardE 2:bb0f631a6068 33 *
RichardE 2:bb0f631a6068 34 * 19 First character of player's name
RichardE 2:bb0f631a6068 35 * 20 Second character of player's name
RichardE 2:bb0f631a6068 36 * 21 Third character of player's name
RichardE 2:bb0f631a6068 37 * 22 LSB of score (in BCD)
RichardE 2:bb0f631a6068 38 * 23 Byte 1 of score (in BCD)
RichardE 2:bb0f631a6068 39 * 24 Byte 2 of score (in BCD)
RichardE 2:bb0f631a6068 40 * 25 MSB of score (in BCD)
RichardE 2:bb0f631a6068 41 * 26 Unused
RichardE 2:bb0f631a6068 42 *
RichardE 2:bb0f631a6068 43 * So, assuming the capacity of the table is N, the first N bytes form an index which is used to locate
RichardE 2:bb0f631a6068 44 * items in the remaining N*8 bytes that follow. This is done so that inserting a new entry only involves
RichardE 2:bb0f631a6068 45 * overwriting one name/score record and updating the index. You don't have to re-write all the records
RichardE 2:bb0f631a6068 46 * that move down the table to make room for the new one.
RichardE 2:bb0f631a6068 47 *
RichardE 2:bb0f631a6068 48 */
RichardE 2:bb0f631a6068 49
RichardE 3:a6a0cd726ca0 50 // Define this for debugging messages to be sent to serial port.
RichardE 3:a6a0cd726ca0 51 #undef CHATTY
RichardE 3:a6a0cd726ca0 52
RichardE 3:a6a0cd726ca0 53 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 54 #include "mbed.h"
RichardE 3:a6a0cd726ca0 55 extern Serial pc;
RichardE 3:a6a0cd726ca0 56 #endif
RichardE 3:a6a0cd726ca0 57
RichardE 2:bb0f631a6068 58 #include "HighScoreTable.h"
RichardE 2:bb0f631a6068 59
RichardE 2:bb0f631a6068 60 /***************/
RichardE 2:bb0f631a6068 61 /* CONSTRUCTOR */
RichardE 2:bb0f631a6068 62 /***************/
RichardE 2:bb0f631a6068 63 // Pass pointer to an SPI EEPROM which contains the high scores.
RichardE 3:a6a0cd726ca0 64 HighScoreTable::HighScoreTable( Ser25LCxxx *e ) :
RichardE 2:bb0f631a6068 65 eeprom( e )
RichardE 2:bb0f631a6068 66 {
RichardE 2:bb0f631a6068 67 }
RichardE 2:bb0f631a6068 68
RichardE 2:bb0f631a6068 69 /**************/
RichardE 2:bb0f631a6068 70 /* DESTRUCTOR */
RichardE 2:bb0f631a6068 71 /**************/
RichardE 2:bb0f631a6068 72 HighScoreTable::~HighScoreTable() {
RichardE 2:bb0f631a6068 73 }
RichardE 2:bb0f631a6068 74
RichardE 2:bb0f631a6068 75 /****************************************/
RichardE 2:bb0f631a6068 76 /* VALIDATE EEPROM USED FOR HIGH SCORES */
RichardE 2:bb0f631a6068 77 /****************************************/
RichardE 2:bb0f631a6068 78 // Checks EEPROM used for high scores and
RichardE 2:bb0f631a6068 79 // if any of it looks like nonsense it
RichardE 2:bb0f631a6068 80 // rewrites the whole table with defaults.
RichardE 2:bb0f631a6068 81 void HighScoreTable::ValidateEEPROM( void ) {
RichardE 2:bb0f631a6068 82 // Check if contents of EEPROM make sense.
RichardE 2:bb0f631a6068 83 // If not then rewrite EEPROM with defaults.
RichardE 2:bb0f631a6068 84 if( ! EEPROMValid() ) {
RichardE 2:bb0f631a6068 85 WriteEEPROMDefaults();
RichardE 2:bb0f631a6068 86 }
RichardE 2:bb0f631a6068 87 }
RichardE 2:bb0f631a6068 88
RichardE 2:bb0f631a6068 89 /**********************************************/
RichardE 2:bb0f631a6068 90 /* DETERMINE POSITION OF A SCORE IN THE TABLE */
RichardE 2:bb0f631a6068 91 /**********************************************/
RichardE 2:bb0f631a6068 92 // Pass score in score.
RichardE 2:bb0f631a6068 93 // Returns position in table (0 is top score).
RichardE 2:bb0f631a6068 94 // If position returned is >= capacity of table then score is not high
RichardE 2:bb0f631a6068 95 // enough to place in table.
RichardE 2:bb0f631a6068 96 UInt8 HighScoreTable::GetPositionInTable( UInt32 score ) const {
RichardE 2:bb0f631a6068 97 // Look through table for a score less than the one passed.
RichardE 2:bb0f631a6068 98 PlayerName name;
RichardE 2:bb0f631a6068 99 UInt32 tableScore = (UInt32)0;
RichardE 2:bb0f631a6068 100 for( UInt8 i = 0; i < capacity; ++i ) {
RichardE 2:bb0f631a6068 101 Get( i, &name, &tableScore );
RichardE 2:bb0f631a6068 102 if( tableScore < score ) {
RichardE 2:bb0f631a6068 103 // Found a score that is less.
RichardE 2:bb0f631a6068 104 // Return index at which it was found.
RichardE 2:bb0f631a6068 105 return i;
RichardE 2:bb0f631a6068 106 }
RichardE 2:bb0f631a6068 107 }
RichardE 2:bb0f631a6068 108 // No score found that is less than the one passed.
RichardE 2:bb0f631a6068 109 // Return capacity of table to indicate not found.
RichardE 2:bb0f631a6068 110 return capacity;
RichardE 0:5fa232ee5fdf 111 }
RichardE 2:bb0f631a6068 112
RichardE 2:bb0f631a6068 113 /*********************************/
RichardE 2:bb0f631a6068 114 /* ADD ENTRY TO HIGH SCORE TABLE */
RichardE 2:bb0f631a6068 115 /*********************************/
RichardE 2:bb0f631a6068 116 // Pass position in table to put entry in pos.
RichardE 2:bb0f631a6068 117 // Pass name of player in name.
RichardE 2:bb0f631a6068 118 // Pass score in score.
RichardE 2:bb0f631a6068 119 void HighScoreTable::Add( UInt8 pos, const PlayerName *name, UInt32 score ) {
RichardE 2:bb0f631a6068 120 // Read the entire index, high scores and names out of EEPROM.
RichardE 2:bb0f631a6068 121 // Going to do manipulations in RAM to minimise the number of
RichardE 2:bb0f631a6068 122 // writes we need to do to EEPROM. Remember every time we write
RichardE 2:bb0f631a6068 123 // a single byte a whole page is written so might as well
RichardE 2:bb0f631a6068 124 // write a whole page in one go. Only drawback is more RAM
RichardE 2:bb0f631a6068 125 // is required.
RichardE 3:a6a0cd726ca0 126 char *buffer = eeprom->read( eepromAddress, memoryUsed );
RichardE 3:a6a0cd726ca0 127 if( buffer != NULL ) {
RichardE 2:bb0f631a6068 128 // Fetch index for lowest score in the table.
RichardE 2:bb0f631a6068 129 UInt8 index = buffer[ capacity - 1 ];
RichardE 2:bb0f631a6068 130 // Make sure index is within range.
RichardE 2:bb0f631a6068 131 if( index < capacity ) {
RichardE 2:bb0f631a6068 132 // Point to section of buffer that contains name and score for
RichardE 2:bb0f631a6068 133 // lowest score.
RichardE 3:a6a0cd726ca0 134 UInt8 *address = (UInt8*)( buffer + capacity + ( index << 3 ) );
RichardE 2:bb0f631a6068 135 // Copy characters of name into buffer.
RichardE 2:bb0f631a6068 136 for( UInt8 i = 0; i < PlayerName::Length; ++i ) {
RichardE 2:bb0f631a6068 137 *address++ = name->Name[ i ];
RichardE 2:bb0f631a6068 138 }
RichardE 2:bb0f631a6068 139 // Copy bytes of score into buffer.
RichardE 2:bb0f631a6068 140 *((UInt32*)address) = score;
RichardE 2:bb0f631a6068 141 address += 4;
RichardE 2:bb0f631a6068 142 // Move all entries in the index below insertion point down one
RichardE 2:bb0f631a6068 143 // to make room for new entry.
RichardE 2:bb0f631a6068 144 for( UInt8 i = capacity - 1; i > pos; --i ) {
RichardE 2:bb0f631a6068 145 buffer[ i ] = buffer[ i - 1 ];
RichardE 2:bb0f631a6068 146 }
RichardE 2:bb0f631a6068 147 // Insert index of newly written record at insertion point.
RichardE 2:bb0f631a6068 148 buffer[ pos ] = index;
RichardE 2:bb0f631a6068 149 // Write the buffer back to EEPROM.
RichardE 3:a6a0cd726ca0 150 eeprom->write( eepromAddress, memoryUsed, buffer );
RichardE 3:a6a0cd726ca0 151 // Free memory used by buffer.
RichardE 3:a6a0cd726ca0 152 free( buffer );
RichardE 2:bb0f631a6068 153 }
RichardE 2:bb0f631a6068 154 }
RichardE 2:bb0f631a6068 155 }
RichardE 2:bb0f631a6068 156
RichardE 2:bb0f631a6068 157 /****************************/
RichardE 2:bb0f631a6068 158 /* GET ENTRY FROM THE TABLE */
RichardE 2:bb0f631a6068 159 /****************************/
RichardE 2:bb0f631a6068 160 // Pass position to fetch from in pos.
RichardE 2:bb0f631a6068 161 // Player name is returned in object pointed to by name.
RichardE 2:bb0f631a6068 162 // Player score is returned in integer pointed to by score.
RichardE 2:bb0f631a6068 163 void HighScoreTable::Get( UInt8 pos, PlayerName *name, UInt32 *score ) const {
RichardE 3:a6a0cd726ca0 164 // Write default values to name and score.
RichardE 2:bb0f631a6068 165 for( UInt8 i = 0; i < PlayerName::Length; ++i ) {
RichardE 2:bb0f631a6068 166 name->Name[ i ] = (UInt8)'X';
RichardE 2:bb0f631a6068 167 }
RichardE 2:bb0f631a6068 168 name->Name[ PlayerName::Length ] = 0;
RichardE 3:a6a0cd726ca0 169 *score = 0;
RichardE 2:bb0f631a6068 170 // Fetch index from EEPROM.
RichardE 3:a6a0cd726ca0 171 UInt8 *index = (UInt8*)eeprom->read( eepromAddress + pos, 1 );
RichardE 3:a6a0cd726ca0 172 if( index == NULL ) {
RichardE 2:bb0f631a6068 173 return;
RichardE 3:a6a0cd726ca0 174 }
RichardE 2:bb0f631a6068 175 // Point to appropriate part of data table.
RichardE 3:a6a0cd726ca0 176 UInt16 address = eepromAddress + capacity + ( *index << 3 );
RichardE 3:a6a0cd726ca0 177 // Free buffer containing index.
RichardE 3:a6a0cd726ca0 178 free( index );
RichardE 2:bb0f631a6068 179 // Read out characters and store in name.
RichardE 3:a6a0cd726ca0 180 char *rawName = eeprom->read( address, PlayerName::Length );
RichardE 3:a6a0cd726ca0 181 if( rawName == NULL ) {
RichardE 3:a6a0cd726ca0 182 return;
RichardE 3:a6a0cd726ca0 183 }
RichardE 3:a6a0cd726ca0 184 memcpy( name->Name, rawName, PlayerName::Length );
RichardE 2:bb0f631a6068 185 name->Name[ PlayerName::Length ] = 0;
RichardE 3:a6a0cd726ca0 186 address += PlayerName::Length;
RichardE 3:a6a0cd726ca0 187 free( rawName );
RichardE 2:bb0f631a6068 188 // Read out score.
RichardE 3:a6a0cd726ca0 189 char *rawScore = eeprom->read( address, 4 );
RichardE 3:a6a0cd726ca0 190 if( rawScore == NULL ) {
RichardE 3:a6a0cd726ca0 191 return;
RichardE 3:a6a0cd726ca0 192 }
RichardE 3:a6a0cd726ca0 193 *score = *((UInt32*)rawScore);
RichardE 3:a6a0cd726ca0 194 free( rawScore );
RichardE 2:bb0f631a6068 195 }
RichardE 2:bb0f631a6068 196
RichardE 2:bb0f631a6068 197 /********************************/
RichardE 2:bb0f631a6068 198 /* DETERMINE IF EEPROM IS VALID */
RichardE 2:bb0f631a6068 199 /********************************/
RichardE 2:bb0f631a6068 200 // Returns true if EEPROM is valid.
RichardE 2:bb0f631a6068 201 bool HighScoreTable::EEPROMValid( void ) {
RichardE 3:a6a0cd726ca0 202 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 203 pc.printf( "Checking validity.\r\n" );
RichardE 3:a6a0cd726ca0 204 #endif
RichardE 2:bb0f631a6068 205 UInt8 b, b2;
RichardE 3:a6a0cd726ca0 206 // Read index from EEPROM.
RichardE 3:a6a0cd726ca0 207 char *index = eeprom->read( eepromAddress, capacity );
RichardE 3:a6a0cd726ca0 208 if( index == NULL ) {
RichardE 3:a6a0cd726ca0 209 return false;
RichardE 3:a6a0cd726ca0 210 }
RichardE 2:bb0f631a6068 211 // Check all entries in the index are within range and are unique.
RichardE 2:bb0f631a6068 212 for( UInt8 i = 0; i < capacity; ++i ) {
RichardE 3:a6a0cd726ca0 213 b = index[ i ];
RichardE 3:a6a0cd726ca0 214 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 215 pc.printf( "index[ %u ] = %u\r\n", (unsigned)i, (unsigned)b );
RichardE 3:a6a0cd726ca0 216 #endif
RichardE 2:bb0f631a6068 217 if( b >= capacity ) {
RichardE 3:a6a0cd726ca0 218 free( index );
RichardE 2:bb0f631a6068 219 return false;
RichardE 2:bb0f631a6068 220 }
RichardE 2:bb0f631a6068 221 // Check if any of the following bytes in the index have
RichardE 2:bb0f631a6068 222 // the same value.
RichardE 2:bb0f631a6068 223 for( UInt8 j = i + 1; j < capacity; ++j ) {
RichardE 3:a6a0cd726ca0 224 b2 = index [ j ];
RichardE 2:bb0f631a6068 225 if( b == b2 ) {
RichardE 3:a6a0cd726ca0 226 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 227 pc.printf( "index[ %u ] has the same value!\r\n", (unsigned)j );
RichardE 3:a6a0cd726ca0 228 #endif
RichardE 3:a6a0cd726ca0 229 free( index );
RichardE 2:bb0f631a6068 230 return false;
RichardE 2:bb0f631a6068 231 }
RichardE 2:bb0f631a6068 232 }
RichardE 2:bb0f631a6068 233 }
RichardE 3:a6a0cd726ca0 234 // Free memory used by index.
RichardE 3:a6a0cd726ca0 235 free( index );
RichardE 2:bb0f631a6068 236 // Check all entries in the data part of the table are valid.
RichardE 2:bb0f631a6068 237 UInt16 address = eepromAddress + capacity;
RichardE 2:bb0f631a6068 238 for( UInt8 i = 0; i < capacity; ++i ) {
RichardE 3:a6a0cd726ca0 239 // Read name and score.
RichardE 3:a6a0cd726ca0 240 char *entry = eeprom->read( address, 8 );
RichardE 3:a6a0cd726ca0 241 if( entry == NULL ) {
RichardE 3:a6a0cd726ca0 242 return false;
RichardE 3:a6a0cd726ca0 243 }
RichardE 3:a6a0cd726ca0 244 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 245 pc.printf( "Checking entry %u.\r\n", i );
RichardE 3:a6a0cd726ca0 246 pc.puts( "Name:" );
RichardE 3:a6a0cd726ca0 247 #endif
RichardE 2:bb0f631a6068 248 // Check name consists only of uppercase letters.
RichardE 2:bb0f631a6068 249 for( UInt8 j = 0; j < PlayerName::Length; ++j ) {
RichardE 3:a6a0cd726ca0 250 b = (UInt8)entry[ j ];
RichardE 3:a6a0cd726ca0 251 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 252 pc.putc( b );
RichardE 3:a6a0cd726ca0 253 #endif
RichardE 2:bb0f631a6068 254 if( ( b < PlayerName::MinChar ) || ( b > PlayerName::MaxChar ) ) {
RichardE 3:a6a0cd726ca0 255 free( entry );
RichardE 2:bb0f631a6068 256 return false;
RichardE 2:bb0f631a6068 257 }
RichardE 2:bb0f631a6068 258 }
RichardE 3:a6a0cd726ca0 259 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 260 pc.puts( "\r\nScore:" );
RichardE 3:a6a0cd726ca0 261 #endif
RichardE 2:bb0f631a6068 262 // Check score consists only of valid BCD numbers.
RichardE 3:a6a0cd726ca0 263 for( UInt8 j = PlayerName::Length; j < PlayerName::Length + 4; ++j ) {
RichardE 3:a6a0cd726ca0 264 b = (UInt8)entry[ j ];
RichardE 3:a6a0cd726ca0 265 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 266 pc.printf( "%02X ", (int)b );
RichardE 3:a6a0cd726ca0 267 #endif
RichardE 2:bb0f631a6068 268 if( ( ( b & 0x0F ) > 0x09 ) || ( ( b & 0xF0 ) > 0x90 ) ) {
RichardE 3:a6a0cd726ca0 269 free( entry );
RichardE 2:bb0f631a6068 270 return false;
RichardE 2:bb0f631a6068 271 }
RichardE 2:bb0f631a6068 272 }
RichardE 3:a6a0cd726ca0 273 #ifdef CHATTY
RichardE 3:a6a0cd726ca0 274 pc.puts( "\r\n" );
RichardE 3:a6a0cd726ca0 275 #endif
RichardE 3:a6a0cd726ca0 276 // Finished with name and score.
RichardE 3:a6a0cd726ca0 277 free( entry );
RichardE 3:a6a0cd726ca0 278 // Skip to next entry.
RichardE 3:a6a0cd726ca0 279 address += 8;
RichardE 2:bb0f631a6068 280 }
RichardE 2:bb0f631a6068 281 // EEPROM is valid
RichardE 2:bb0f631a6068 282 return true;
RichardE 2:bb0f631a6068 283 }
RichardE 2:bb0f631a6068 284
RichardE 2:bb0f631a6068 285 /****************************/
RichardE 2:bb0f631a6068 286 /* WRITE DEFAULTS TO EEPROM */
RichardE 2:bb0f631a6068 287 /****************************/
RichardE 2:bb0f631a6068 288 // This may take a second or two to execute!
RichardE 2:bb0f631a6068 289 void HighScoreTable::WriteEEPROMDefaults( void ) {
RichardE 3:a6a0cd726ca0 290 UInt8 buffer[ memoryUsed ];
RichardE 2:bb0f631a6068 291 // Write index with ascending integers.
RichardE 3:a6a0cd726ca0 292 UInt8 *ptr = buffer;
RichardE 2:bb0f631a6068 293 for( UInt8 i = 0; i < capacity; ++i ) {
RichardE 2:bb0f631a6068 294 *ptr++ = i;
RichardE 2:bb0f631a6068 295 }
RichardE 2:bb0f631a6068 296 // Write data table with zero score entries.
RichardE 2:bb0f631a6068 297 for( UInt8 i = 0; i < capacity; ++i ) {
RichardE 2:bb0f631a6068 298 // Write a name of "AAA".
RichardE 2:bb0f631a6068 299 for( UInt8 j = 0; j < PlayerName::Length; ++j ) {
RichardE 3:a6a0cd726ca0 300 *ptr++ = (UInt8)'A';
RichardE 2:bb0f631a6068 301 }
RichardE 3:a6a0cd726ca0 302 // Write a score of zero.
RichardE 2:bb0f631a6068 303 *((UInt32*)ptr) = 0;
RichardE 3:a6a0cd726ca0 304 ptr += 4;
RichardE 3:a6a0cd726ca0 305 // Write zero to unused byte.
RichardE 3:a6a0cd726ca0 306 *ptr++ = 0;
RichardE 3:a6a0cd726ca0 307 }
RichardE 3:a6a0cd726ca0 308 // Write the buffer to EEPROM.
RichardE 3:a6a0cd726ca0 309 eeprom->write( eepromAddress, memoryUsed, (char*)buffer );
RichardE 2:bb0f631a6068 310 }