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:
Mon Jun 17 15:10:43 2013 +0000
Revision:
18:70190f956a24
Parent:
3:a6a0cd726ca0
Improved response to button 1 when entering high scores (HighScoreEntry.cpp).

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 }