├── .gitattributes ├── Artwork ├── Cactus.png ├── Steve 1.png ├── Steve 2.png ├── Steve 3.png ├── Steve 4.png ├── Steve 5.png ├── Steve 6.png ├── Steve 7.png ├── Artwork.xlsx ├── Ground 1.png ├── Ground 2.png ├── Ground 3.png ├── SteveBitmap.png └── SteveOverlap.png ├── SteveTheJumpingDinosaur.docx ├── .gitignore ├── EEPROMUtils.h ├── Collide.ino ├── Images.h └── Steve.ino /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /Artwork/Cactus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Cactus.png -------------------------------------------------------------------------------- /Artwork/Steve 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 1.png -------------------------------------------------------------------------------- /Artwork/Steve 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 2.png -------------------------------------------------------------------------------- /Artwork/Steve 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 3.png -------------------------------------------------------------------------------- /Artwork/Steve 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 4.png -------------------------------------------------------------------------------- /Artwork/Steve 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 5.png -------------------------------------------------------------------------------- /Artwork/Steve 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 6.png -------------------------------------------------------------------------------- /Artwork/Steve 7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Steve 7.png -------------------------------------------------------------------------------- /Artwork/Artwork.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Artwork.xlsx -------------------------------------------------------------------------------- /Artwork/Ground 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Ground 1.png -------------------------------------------------------------------------------- /Artwork/Ground 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Ground 2.png -------------------------------------------------------------------------------- /Artwork/Ground 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/Ground 3.png -------------------------------------------------------------------------------- /Artwork/SteveBitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/SteveBitmap.png -------------------------------------------------------------------------------- /Artwork/SteveOverlap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/Artwork/SteveOverlap.png -------------------------------------------------------------------------------- /SteveTheJumpingDinosaur.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filmote/Steve/HEAD/SteveTheJumpingDinosaur.docx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | Artwork/~$Artwork.xlsx 4 | .vscode/tasks.json 5 | .vscode/arduino.json 6 | .vscode/c_cpp_properties.json 7 | -------------------------------------------------------------------------------- /EEPROMUtils.h: -------------------------------------------------------------------------------- 1 | #ifndef EEPROMUTILS_H 2 | #define EEPROMUTILS_H 3 | 4 | #define EEPROM_START_C1 EEPROM_STORAGE_SPACE_START 5 | #define EEPROM_START_C2 EEPROM_START_C1 + 1 6 | #define EEPROM_SCORE EEPROM_START_C1 + 2 7 | 8 | 9 | /* ---------------------------------------------------------------------------- 10 | * Is the EEPROM initialised? 11 | * 12 | * Looks for the characters 'S' and 'T' in the first two bytes of the EEPROM 13 | * memory range starting from byte EEPROM_STORAGE_SPACE_START. If not found, 14 | * it resets the settings .. 15 | * ---------------------------------------------------------------------------- 16 | */ 17 | void initEEPROM() { 18 | 19 | uint8_t c1 = EEPROM.read(EEPROM_START_C1); 20 | uint8_t c2 = EEPROM.read(EEPROM_START_C2); 21 | 22 | if (c1 != 'S' || c2 != 'T') { 23 | 24 | EEPROM.update(EEPROM_START_C1, 'S'); 25 | EEPROM.update(EEPROM_START_C2, 'T'); 26 | EEPROM.put(EEPROM_SCORE, (uint16_t)0); 27 | 28 | } 29 | 30 | } 31 | #endif 32 | -------------------------------------------------------------------------------- /Collide.ino: -------------------------------------------------------------------------------- 1 | const uint8_t PROGMEM lookup[] { 0xFF >> 8, 0xFF >> 7, 0xFF >> 6, 0xFF >> 5, 0xFF >> 4, 0xFF >> 3, 0xFF >> 2, 0xFF >> 1 }; 2 | 3 | /* ---------------------------------------------------------------------------- 4 | * Detect a collision between two separate images .. 5 | * 6 | */ 7 | bool collide(int16_t x1, int16_t y1, const uint8_t *img1, int16_t x2, int16_t y2, const uint8_t *img2) { 8 | 9 | #define IMG_DATA_OFFSET 2 10 | 11 | uint8_t w1 = pgm_read_byte(&img1[0]); 12 | uint8_t h1 = pgm_read_byte(&img1[1]); 13 | uint8_t w2 = pgm_read_byte(&img2[0]); 14 | uint8_t h2 = pgm_read_byte(&img2[1]); 15 | 16 | #define IMG_DATA_OFFSET 2 17 | 18 | 19 | // Do the images overlap at all ? 20 | 21 | if (!(x2 >= x1 + w1 || 22 | x2 + w2 <= x1 || 23 | y2 >= y1 + h1 || 24 | y2 + h2 <= y1)) { 25 | 26 | 27 | 28 | // Determine overlapping rectangle between the two images .. 29 | 30 | uint16_t overlap_left = max(x1, x2); 31 | uint16_t overlap_right = min(x1 + w1, x2 + w2); 32 | uint16_t overlap_top = max(y1, y2); 33 | uint16_t overlap_bottom = min(y1 + h1, y2 + h2); 34 | 35 | 36 | // The data in an image is defined left to right in bands (rows) of 8 pixel bands with the least 37 | // significant bit at the top and the most significant at the bottom. Consecutive rows of data 38 | // describe vertical bits 8 - 15, 16 - 23 and so on. 39 | 40 | // Determine the portion of the first image that is constrained by the overlapping rectangle. 41 | // The top row and bit describe how far from the top of the image the overlap begins, likewise the 42 | // bottom row and bit describe the lower range. 43 | // 44 | // Consider an image that is 24 x 24 pixels wide. If the overlap started at coordinates (11, 11) 45 | // and continued to (24, 24), then the top row would be equal to 1 (ie. the second row of 8 bits) 46 | // and the top bits would be equal to 3 - the third pixel down. The bottom row would be calculated 47 | // as 2 (ie. the third row) with the bottom bits calculated to 0. 48 | 49 | int16_t img1_left = (overlap_left - x1); 50 | int16_t img1_right = (overlap_right - x1); 51 | int16_t img1_top_row = (overlap_top - y1) / 8; 52 | int16_t img1_top_bit = (overlap_top - y1) % 8; 53 | int16_t img1_bottom_row = (overlap_bottom - y1 - 1) / 8; 54 | int16_t img1_bottom_bit = (overlap_bottom - y1) % 8; 55 | 56 | int16_t img2_left = (overlap_left - x2); 57 | int16_t img2_top_row = (overlap_top - y2) / 8; 58 | int16_t img2_top_bit = (overlap_top - y2) % 8; 59 | int16_t img2_bottom_row = (overlap_bottom - y2 - 1) / 8; 60 | int16_t img2_bottom_bit = (overlap_bottom - y2) % 8; 61 | 62 | 63 | // Data is retrieved from each image using a separate index. The index is calculated by 64 | // multiplying the starting row by the image width and adding the left column of the overlapping 65 | // area. An additional two bytes are added to account for the image height and width that prefix 66 | // image data. 67 | 68 | int16_t i1 = (img1_top_row * w1) + img1_left + IMG_DATA_OFFSET; 69 | int16_t i2 = (img2_top_row * w2) + img2_left + IMG_DATA_OFFSET; 70 | 71 | while (true) { 72 | 73 | 74 | // Retrieve the byte of data from the current column. If the overlap is less than one row high and 75 | // the bottom bit is not zero, then we need to mask off the lower bits as they are out of range .. 76 | 77 | uint16_t d1 = pgm_read_byte(&img1[i1]) & (img1_top_row == img1_bottom_row && img1_bottom_bit != 0 ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF); 78 | uint16_t d2 = pgm_read_byte(&img2[i2]) & (img2_top_row == img2_bottom_row && img2_bottom_bit != 0 ? pgm_read_byte(&lookup[img2_bottom_bit]) : 0xFF); 79 | 80 | 81 | // If we are not at the last row of the overlap, retrieve the byte if data exactly below the one 82 | // retrieved in the last step. The two bytes form an upper and lower byte of a 16 pixel range which 83 | // we will trim down to 8 bits per image for comparison. This is done as the top pixel per image may 84 | // not be zero and hence to compare a contiguous 8 bits we need to harvest them from two rows .. 85 | 86 | if (img1_top_bit > 0 && img1_top_row < img1_bottom_row) { 87 | d1 = d1 | ((pgm_read_byte(&img1[i1 + w1]) & (img1_top_row + 1 == img1_bottom_row ? pgm_read_byte(&lookup[img1_bottom_bit]) : 0xFF )) << 8); 88 | } 89 | 90 | if (img2_top_bit > 0 && img2_top_row < img2_bottom_row) { 91 | d2 = d2 | ((pgm_read_byte(&img2[i2 + w2]) & (img2_top_row + 1 == img2_bottom_row ? pgm_read_byte(&lookup[img2_bottom_bit]) : 0xFF )) << 8); 92 | } 93 | 94 | 95 | // Finally, we bit shift the result if necessary and 'prune' to 8 bits .. 96 | 97 | d1 = (d1 >> img1_top_bit ) & 0xFF; 98 | d2 = (d2 >> img2_top_bit ) & 0xFF; 99 | 100 | 101 | // If there has been a collision, then we can exit out of here! This algorithm checks collisions by 102 | // scanning for collisions left to right then row by row. Collisions detected near the top-left hand 103 | // corner will be found quickly compared to a collision inthe lower right-hand corner .. 104 | 105 | if ((d1 & d2) > 0) { 106 | return true; 107 | } 108 | 109 | 110 | // Increase the column index for both images. If we have exceeded the maximum 111 | // width of the image, then wrap to the next row .. 112 | 113 | if (i1 < (img1_top_row * w1) + img1_right + IMG_DATA_OFFSET) { 114 | ++i1; 115 | ++i2; 116 | } 117 | else { 118 | 119 | if (img1_top_row < img1_bottom_row) { 120 | 121 | ++img1_top_row; 122 | ++img2_top_row; 123 | i1 = (img1_top_row * w1) + img1_left + IMG_DATA_OFFSET; 124 | i2 = (img2_top_row * w2) + img2_left + IMG_DATA_OFFSET; 125 | 126 | } 127 | else { 128 | 129 | return false; 130 | 131 | } 132 | 133 | } 134 | 135 | } 136 | 137 | } 138 | 139 | return false; 140 | 141 | } -------------------------------------------------------------------------------- /Images.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* ----------------------------------------------------------------------------------------------------------------------------- 4 | * Retrieve the width of an image. 5 | * ----------------------------------------------------------------------------------------------------------------------------- 6 | */ 7 | uint8_t getImageWidth(const uint8_t *image) { 8 | 9 | return pgm_read_byte(image); 10 | 11 | } 12 | 13 | 14 | /* ----------------------------------------------------------------------------------------------------------------------------- 15 | * Retrieve the height of an image. 16 | * ----------------------------------------------------------------------------------------------------------------------------- 17 | */ 18 | uint8_t getImageHeight(const uint8_t *image) { 19 | 20 | return pgm_read_byte(image + 1); 21 | 22 | } 23 | 24 | 25 | /* ----------------------------------------------------------------------------------------------------------------------------- 26 | * Steve images and masks .. 27 | * ----------------------------------------------------------------------------------------------------------------------------- 28 | */ 29 | const uint8_t PROGMEM dinosaur_still[] = { 30 | 18, 21, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFB, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 32 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x01, 0x1F, 0x17, 0x03, 0x01, 0x03, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | }; 35 | 36 | const uint8_t PROGMEM dinosaur_running_1[] = { 37 | 18, 21, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFB, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 39 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x01, 0x0F, 0x0B, 0x01, 0x01, 0x03, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | }; 42 | 43 | const uint8_t PROGMEM dinosaur_running_2[] = { 44 | 18, 21, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFB, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 46 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x01, 0x1F, 0x17, 0x03, 0x01, 0x03, 0x0F, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | }; 49 | 50 | const uint8_t PROGMEM dinosaur_ducking_1[] = { 51 | 26, 13, 52 | 0x07, 0x0E, 0x1E, 0x3C, 0x7C, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFC, 0x7C, 0x7C, 0xFE, 0xFF, 0xFB, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x05, 0x01, 0x1F, 0x17, 0x03, 0x01, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | }; 55 | 56 | const uint8_t PROGMEM dinosaur_ducking_2[] = { 57 | 26, 13, 58 | 0x07, 0x0E, 0x1E, 0x3C, 0x7C, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFC, 0x7C, 0x7C, 0xFE, 0xFF, 0xFB, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x17, 0x03, 0x01, 0x01, 0x07, 0x05, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | }; 61 | 62 | const uint8_t PROGMEM dinosaur_still_mask[] = { 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 64 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x01, 0x3F, 0x3F, 0x3F, 0x3F, 0x03, 0x3F, 0x3F, 0x3F, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | }; 67 | 68 | const uint8_t PROGMEM dinosaur_running_1_mask[] = { 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 70 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x01, 0x1F, 0x1F, 0x1F, 0x1F, 0x03, 0x3F, 0x3F, 0x3F, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | }; 73 | 74 | const uint8_t PROGMEM dinosaur_running_2_mask[] = { 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 76 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x01, 0x3F, 0x3F, 0x3F, 0x3F, 0x03, 0x1F, 0x1F, 0x1F, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | }; 79 | 80 | const uint8_t PROGMEM dinosaur_ducking_1_mask[] = { 81 | 0x07, 0x0E, 0x1E, 0x3C, 0x7C, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFC, 0x7C, 0x7C, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 82 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x3F, 0x3F, 0x3F, 0x3F, 0x03, 0x07, 0x07, 0x07, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,}; 83 | 84 | const uint8_t PROGMEM dinosaur_ducking_2_mask[] = { 85 | 0x07, 0x0E, 0x1E, 0x3C, 0x7C, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFC, 0x7C, 0x7C, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xBF, 0x3F, 0x3E, 86 | 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x03, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 87 | }; 88 | 89 | const uint8_t PROGMEM dinosaur_dead_1[] = { 90 | 18, 21, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xE3, 0xEB, 0xE3, 0xBF, 0xBF, 0x3F, 0x3E, 92 | 0x3F, 0x7C, 0xF8, 0xF0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x04, 0x0C, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x01, 0x1F, 0x17, 0x03, 0x01, 0x03, 0x1F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | }; 95 | 96 | const uint8_t PROGMEM dinosaur_dead_2[] = { 97 | 26, 13, 98 | 0x70, 0xE0, 0xE0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0xC0, 0x80, 0xC0, 0xE0, 0x60, 0xE0, 0x60, 0xE0, 0xE0, 0xE0, 0xC0, 99 | 0x00, 0x00, 0x01, 0x13, 0x17, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0F, 0x1F, 0x0F, 0x07, 0x0F, 0x1F, 0x1F, 0x1D, 0x1E, 0x1D, 0x17, 0x17, 0x07, 0x07, 100 | }; 101 | 102 | const uint8_t PROGMEM dinosaur_dead_2_mask[] = { 103 | 0x70, 0xE0, 0xE0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0xC0, 0x80, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 104 | 0x00, 0x01, 0x3B, 0x3F, 0x3F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x3F, 0x1F, 0x0F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x0F, 0x0F, 105 | }; 106 | 107 | 108 | /* ----------------------------------------------------------------------------------------------------------------------------- 109 | * Obstacle Images .. 110 | * ----------------------------------------------------------------------------------------------------------------------------- 111 | */ 112 | const uint8_t PROGMEM pterodactyl_1[] = { 113 | 22, 16, 114 | 0x20, 0x30, 0x38, 0x3C, 0x36, 0x3E, 0x78, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F, 0x1F, 0x0F, 0x07, 0x05, 0x06, 0x07, 0x07, 0x07, 0x07, 0x02, 0x02, 0x00, 116 | }; 117 | 118 | const uint8_t PROGMEM pterodactyl_2[] = { 119 | 22, 16, 120 | 0x20, 0x30, 0x38, 0x3C, 0x36, 0x3E, 0x78, 0xE0, 0xFF, 0xFE, 0xF8, 0xF0, 0xE0, 0xE0, 0xC0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x02, 0x02, 0x00, 122 | }; 123 | 124 | const uint8_t PROGMEM cactus_1[] = { 125 | 11, 20, 126 | 0xC0, 0xE0, 0xC0, 0x00, 0xFE, 0xFF, 0xFE, 0x00, 0xF8, 0xFC, 0xF8, 127 | 0x0F, 0x1F, 0x3F, 0x38, 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0x03, 0x01, 128 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 129 | }; 130 | 131 | const uint8_t PROGMEM cactus_2[] = { 132 | 21, 22, 133 | 0x00, 0x80, 0x00, 0x00, 0xF8, 0xFC, 0xF8, 0x00, 0xE0, 0x00, 0xF8, 0xFC, 0xF8, 0x00, 0xFE, 0xFF, 0xFE, 0x00, 0x80, 0xC0, 0x80, 134 | 0x3F, 0x7F, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0x1C, 0x1F, 0x0E, 0x05, 0x03, 0x07, 0x07, 0xFF, 0xFF, 0xFF, 0x70, 0x7F, 0x3F, 0x1F, 135 | 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 136 | }; 137 | 138 | const uint8_t PROGMEM cactus_3[] = { 139 | 30, 22, 140 | 0xF8, 0xFC, 0xF8, 0x00, 0xFE, 0xFF, 0xFE, 0x00, 0x80, 0xC0, 0x80, 0x00, 0x00, 0x80, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0xF8, 0xFC, 0xF8, 0x00, 0xE0, 0xF0, 0xE0, 141 | 0x01, 0x03, 0x07, 0x07, 0xFF, 0xFF, 0xFF, 0x70, 0x07, 0xFB, 0xFD, 0xF8, 0x80, 0xFF, 0xFF, 0xFF, 0xC0, 0xFC, 0xFE, 0x3D, 0x43, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0x1C, 0x1F, 0x0F, 0x07, 142 | 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x3F, 0x3F, 0x3F, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 143 | }; 144 | 145 | 146 | /* ----------------------------------------------------------------------------------------------------------------------------- 147 | * Ground images .. 148 | * ----------------------------------------------------------------------------------------------------------------------------- 149 | */ 150 | const byte PROGMEM ground_flat[] = { 151 | 32, 8, 152 | 0x14, 0x04, 0x04, 0x24, 0x04, 0x44, 0x44, 0x04, 0x04, 0x04, 0x04, 0x04, 0x24, 0x04, 0x04, 0x04, 0x04, 0x14, 0x14, 0x04, 0x04, 0x04, 0x44, 0x44, 0x04, 0x04, 0x04, 0x04, 0x24, 0x04, 0x04, 0x04, 153 | }; 154 | 155 | const byte PROGMEM ground_bump[] = { 156 | 32, 8, 157 | 0x04, 0x24, 0x24, 0x04, 0x04, 0x04, 0x44, 0x04, 0x04, 0x04, 0x14, 0x04, 0x22, 0x02, 0x01, 0x01, 0x09, 0x81, 0x82, 0x02, 0x04, 0x04, 0x04, 0x14, 0x04, 0x04, 0x04, 0x44, 0x44, 0x04, 0x04, 0x04, 158 | }; 159 | 160 | const byte PROGMEM ground_hole[] = { 161 | 32, 8, 162 | 0x84, 0x84, 0x04, 0x04, 0x04, 0x14, 0x04, 0x14, 0x04, 0x44, 0x04, 0x04, 0x08, 0x08, 0x90, 0x90, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, 0x44, 0x44, 0x04, 0x04, 0x04, 0x14, 0x04, 0x24, 0x04, 0x04, 163 | }; 164 | -------------------------------------------------------------------------------- /Steve.ino: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | #include "EEPROMUtils.h" 4 | #include "Images.h" 5 | 6 | #define NUMBER_OF_OBSTACLES 3 7 | #define GROUND_LEVEL 55 8 | #define STEVE_GROUND_LEVEL GROUND_LEVEL + 7 9 | #define CACTUS_GROUND_LEVEL GROUND_LEVEL + 3 10 | #define JUMP_TOP_HEIGHT 10 11 | #define SCORE_START_PTERODACTYL 300 12 | #define PTERODACTYL_UPPER_LIMIT 27 13 | #define PTERODACTYL_LOWER_LIMIT 48 14 | #define OBSTACLE_LAUNCH_DELAY_MIN 90 15 | #define OBSTACLE_LAUNCH_DELAY_MAX 200 16 | 17 | enum class GameStatus : uint8_t { 18 | Introduction, 19 | PlayGame, 20 | GameOver, 21 | }; 22 | 23 | enum class Stance : uint8_t { 24 | Standing, 25 | Running1, 26 | Running2, 27 | Ducking1, 28 | Ducking2, 29 | Dead1, 30 | Dead2, 31 | }; 32 | 33 | enum class ObstacleType : uint8_t { 34 | SingleCactus, 35 | DoubleCactus, 36 | TripleCactus, 37 | Pterodactyl1, 38 | Pterodactyl2, 39 | Count_CactusOnly = 3, 40 | Count_AllObstacles = 4, 41 | }; 42 | 43 | enum class GroundType : uint8_t { 44 | Flat, 45 | Bump, 46 | Hole, 47 | }; 48 | 49 | struct Steve { 50 | uint8_t x; 51 | uint8_t y; 52 | Stance stance; 53 | bool jumping; 54 | uint8_t jumpIndex; 55 | const uint8_t *image; 56 | const uint8_t *mask; 57 | }; 58 | 59 | struct Obstacle { 60 | int8_t x; 61 | uint8_t y; 62 | ObstacleType type; 63 | bool enabled; 64 | const byte *image; 65 | }; 66 | 67 | Arduboy2 arduboy; 68 | uint8_t groundX = 0; 69 | 70 | Obstacle obstacles[NUMBER_OF_OBSTACLES] = { 71 | { 0, 0, ObstacleType::Pterodactyl1, false, pterodactyl_1 }, 72 | { 0, 0, ObstacleType::Pterodactyl1, false, pterodactyl_1 }, 73 | { 0, 0, ObstacleType::Pterodactyl1, false, pterodactyl_1 }, 74 | }; 75 | 76 | GroundType ground[5] = { 77 | GroundType::Flat, 78 | GroundType::Flat, 79 | GroundType::Hole, 80 | GroundType::Flat, 81 | GroundType::Flat, 82 | }; 83 | 84 | Steve steve = {0, STEVE_GROUND_LEVEL, Stance::Standing, false, false, dinosaur_still, dinosaur_still_mask }; 85 | 86 | uint8_t jumpCoords[] = {55, 52, 47, 43, 40, 38, 36, 34, 33, 31, 30, 29, 28, 27, 26, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 20, 20, 19, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 36, 38, 40, 43, 47, 51, 55, }; 87 | uint16_t score = 0; 88 | uint16_t highScore = 0; 89 | uint16_t obstacleLaunchCountdown = OBSTACLE_LAUNCH_DELAY_MIN; 90 | 91 | GameStatus gameStatus = GameStatus::Introduction; 92 | 93 | const uint8_t *steve_images[] = { dinosaur_still, dinosaur_running_1, dinosaur_running_2, dinosaur_ducking_1, dinosaur_ducking_2, dinosaur_dead_1, dinosaur_dead_2 }; 94 | const uint8_t *steve_masks[] = { dinosaur_still_mask, dinosaur_running_1_mask, dinosaur_running_2_mask, dinosaur_ducking_1_mask, dinosaur_ducking_2_mask, dinosaur_dead_2_mask, dinosaur_dead_2_mask }; 95 | const uint8_t *obstacle_images[] = { cactus_1, cactus_2, cactus_3, pterodactyl_1, pterodactyl_2 }; 96 | const uint8_t *ground_images[] = { ground_flat, ground_bump, ground_hole }; 97 | 98 | 99 | /* ----------------------------------------------------------------------------------------------------------------------------- 100 | * Setup the environment .. 101 | * ----------------------------------------------------------------------------------------------------------------------------- 102 | */ 103 | void setup() { 104 | 105 | initEEPROM(); 106 | arduboy.boot(); 107 | arduboy.setFrameRate(75); 108 | arduboy.initRandomSeed(); 109 | 110 | } 111 | 112 | 113 | /* ----------------------------------------------------------------------------------------------------------------------------- 114 | * Control the various states of the game .. 115 | * ----------------------------------------------------------------------------------------------------------------------------- 116 | */ 117 | void loop() { 118 | 119 | // Pause here until it's time for the next frame .. 120 | 121 | if (!(arduboy.nextFrame())) return; 122 | 123 | arduboy.pollButtons(); 124 | 125 | switch (gameStatus) { 126 | 127 | case GameStatus::Introduction: 128 | introduction(); 129 | break; 130 | 131 | case GameStatus::PlayGame: 132 | playGame(); 133 | break; 134 | 135 | case GameStatus::GameOver: 136 | gameOver(); 137 | break; 138 | 139 | } 140 | 141 | } 142 | 143 | 144 | /* ----------------------------------------------------------------------------------------------------------------------------- 145 | * Reset everything ready for a new game .. 146 | * ----------------------------------------------------------------------------------------------------------------------------- 147 | */ 148 | void initialiseGame() { 149 | 150 | for (uint8_t i = 0; i < NUMBER_OF_OBSTACLES; i++) { 151 | obstacles[i].enabled = false; 152 | } 153 | 154 | score = 0; 155 | steve.x = 2; 156 | steve.y = STEVE_GROUND_LEVEL; 157 | steve.jumping = false; 158 | steve.stance = Stance::Standing; 159 | 160 | } 161 | 162 | 163 | /* ----------------------------------------------------------------------------------------------------------------------------- 164 | * Display the introduction .. 165 | * ----------------------------------------------------------------------------------------------------------------------------- 166 | */ 167 | void introduction() { 168 | 169 | EEPROM.get(EEPROM_SCORE, highScore); 170 | arduboy.clear(); 171 | 172 | initialiseGame(); 173 | 174 | arduboy.setCursor(17, 12); 175 | arduboy.print(F("Press A to begin")); 176 | 177 | drawGround(false); 178 | drawSteve(); 179 | drawScoreboard(false); 180 | arduboy.display(); 181 | 182 | if (arduboy.pressed(A_BUTTON)) { 183 | 184 | gameStatus = GameStatus::PlayGame; 185 | steve.stance = Stance::Running1; 186 | 187 | } 188 | 189 | } 190 | 191 | 192 | /* ----------------------------------------------------------------------------------------------------------------------------- 193 | * Display the 'Game Over' text if Steve has hit an obstacle .. 194 | * ----------------------------------------------------------------------------------------------------------------------------- 195 | */ 196 | void gameOver() { 197 | 198 | if (score > highScore) { 199 | highScore = score; 200 | EEPROM.put(EEPROM_SCORE, highScore); 201 | } 202 | 203 | arduboy.clear(); 204 | 205 | drawObstacles(); 206 | drawGround(false); 207 | drawSteve(); 208 | drawScoreboard(true); 209 | 210 | arduboy.setCursor(40, 12); 211 | arduboy.print(F("Game Over")); 212 | 213 | 214 | // Update Steve's image. If he is dead and still standing, this will change him to lying on the ground .. 215 | 216 | updateSteve(); 217 | 218 | arduboy.display(); 219 | 220 | if (arduboy.justPressed(A_BUTTON)) { 221 | 222 | initialiseGame(); 223 | 224 | gameStatus = GameStatus::PlayGame; 225 | steve.stance = Stance::Running1; 226 | 227 | } 228 | 229 | } 230 | 231 | 232 | /* ----------------------------------------------------------------------------------------------------------------------------- 233 | * Run Steve, run. 234 | * ----------------------------------------------------------------------------------------------------------------------------- 235 | */ 236 | void playGame() { 237 | 238 | arduboy.clear(); 239 | 240 | 241 | // The player can only control Steve if he is running or ducking on the ground .. 242 | 243 | if (!steve.jumping) { 244 | 245 | if (arduboy.justPressed(A_BUTTON)) { steve.jumping = true; steve.jumpIndex = 0; } 246 | if (arduboy.justPressed(B_BUTTON)) { if (steve.stance != Stance::Ducking2) { steve.stance = Stance::Ducking1; } } 247 | if (arduboy.pressed(LEFT_BUTTON) && steve.x > 0) { steve.x--; } 248 | if (arduboy.pressed(RIGHT_BUTTON) && steve.x < 100) { steve.x++; } 249 | 250 | 251 | // If the player has not pressed the B button (or continued to hold it down) 252 | // and Steve is ducking, then return him to an upright position .. 253 | 254 | if (arduboy.notPressed(B_BUTTON) && (steve.stance == Stance::Ducking1 || steve.stance == Stance::Ducking2)) { 255 | steve.stance = Stance::Running1; 256 | } 257 | 258 | } 259 | 260 | 261 | // Should we launch another obstacle? 262 | 263 | --obstacleLaunchCountdown; 264 | 265 | if (obstacleLaunchCountdown == 0) { 266 | 267 | for (uint8_t i = 0; i < NUMBER_OF_OBSTACLES; i++) { 268 | 269 | if (!obstacles[i].enabled) { 270 | launchObstacle(i); 271 | break; 272 | } 273 | 274 | } 275 | 276 | obstacleLaunchCountdown = random(OBSTACLE_LAUNCH_DELAY_MIN, OBSTACLE_LAUNCH_DELAY_MAX); 277 | 278 | } 279 | 280 | 281 | // Has Steve collided with anything? 282 | 283 | if (collision()) { 284 | 285 | steve.jumping = false; 286 | steve.jumpIndex = 0; 287 | 288 | if (steve.stance <= Stance::Running2) { 289 | steve.stance = Stance::Dead1; 290 | } 291 | else { 292 | steve.y = STEVE_GROUND_LEVEL; 293 | steve.stance = Stance::Dead2; 294 | } 295 | gameStatus = GameStatus::GameOver; 296 | 297 | } 298 | else { 299 | 300 | 301 | // if not, move Steve and any visible obstacles and continue play .. 302 | 303 | updateSteve(); 304 | updateObstacles(); 305 | 306 | drawObstacles(); 307 | drawGround(true); 308 | drawSteve(); 309 | drawScoreboard(true); 310 | if (arduboy.everyXFrames(3)) { score++; } 311 | 312 | arduboy.display(); 313 | 314 | } 315 | 316 | } 317 | 318 | 319 | /* ----------------------------------------------------------------------------------------------------------------------------- 320 | * Has Steve collided with any visible obstacle ? 321 | * ----------------------------------------------------------------------------------------------------------------------------- 322 | */ 323 | bool collision () { 324 | 325 | for (uint8_t i = 0; i < NUMBER_OF_OBSTACLES; i++) { 326 | 327 | if (obstacles[i].enabled == true) { 328 | 329 | if (collide(steve.x, steve.y - getImageHeight(steve.image), steve.image, obstacles[i].x, obstacles[i].y - getImageHeight(obstacles[i].image), obstacles[i].image)) { 330 | 331 | return true; 332 | 333 | } 334 | 335 | } 336 | 337 | } 338 | 339 | return false; 340 | 341 | } 342 | 343 | 344 | /* ----------------------------------------------------------------------------------------------------------------------------- 345 | * Update Steve's position and stance .. 346 | * ----------------------------------------------------------------------------------------------------------------------------- 347 | */ 348 | void updateSteve() { 349 | 350 | 351 | // Is Steve jumping ? 352 | 353 | if (steve.jumping) { 354 | 355 | steve.y = jumpCoords[steve.jumpIndex]; 356 | steve.jumpIndex++; 357 | 358 | if (steve.jumpIndex == sizeof(jumpCoords)) { 359 | 360 | steve.jumping = false; 361 | steve.jumpIndex = 0; 362 | steve.y = STEVE_GROUND_LEVEL; 363 | 364 | } 365 | 366 | } 367 | else { 368 | 369 | 370 | // Swap the image to simulate running .. 371 | 372 | if (arduboy.everyXFrames(3)) { 373 | 374 | switch (steve.stance) { 375 | 376 | case Stance::Running1: 377 | steve.stance = Stance::Running2; 378 | break; 379 | 380 | case Stance::Running2: 381 | steve.stance = Stance::Running1; 382 | break; 383 | 384 | case Stance::Ducking1: 385 | steve.stance = Stance::Ducking2; 386 | break; 387 | 388 | case Stance::Ducking2: 389 | steve.stance = Stance::Ducking1; 390 | break; 391 | 392 | case Stance::Dead1: 393 | steve.stance = Stance::Dead2; 394 | break; 395 | 396 | default: 397 | break; 398 | 399 | } 400 | 401 | } 402 | 403 | } 404 | 405 | } 406 | 407 | 408 | /* ----------------------------------------------------------------------------------------------------------------------------- 409 | * Render Steve. 410 | * 411 | * The standing and ducking images are rendered relative to the ground, so the image height is subtracted from the current Y 412 | * position to determine an upper top position. 413 | * ----------------------------------------------------------------------------------------------------------------------------- 414 | */ 415 | void drawSteve() { 416 | 417 | uint8_t imageIndex = static_cast(steve.stance); 418 | 419 | steve.image = steve_images[imageIndex]; 420 | steve.mask = steve_masks[imageIndex]; 421 | Sprites::drawExternalMask(steve.x, steve.y - getImageHeight(steve.image), steve.image, steve.mask, 0, 0); 422 | 423 | } 424 | 425 | 426 | /* ----------------------------------------------------------------------------------------------------------------------------- 427 | * Update the position of any visible obstacles .. 428 | * 429 | * If the obstacle has completely off screen, then disable it. Pterodactyls move 1 pixel per update whereas cacti move one 430 | * pixel every second iteration. 431 | * ----------------------------------------------------------------------------------------------------------------------------- 432 | */ 433 | void updateObstacles() { 434 | 435 | for (uint8_t i = 0; i < NUMBER_OF_OBSTACLES; i++) { 436 | 437 | if (obstacles[i].enabled == true) { 438 | 439 | switch (obstacles[i].type) { 440 | 441 | case ObstacleType::Pterodactyl1: 442 | case ObstacleType::Pterodactyl2: 443 | 444 | if (arduboy.everyXFrames(6)) { 445 | if (obstacles[i].type == ObstacleType::Pterodactyl1) { 446 | obstacles[i].type = ObstacleType::Pterodactyl2; 447 | } 448 | else { 449 | obstacles[i].type = ObstacleType::Pterodactyl1; 450 | } 451 | } 452 | 453 | obstacles[i].x--; 454 | break; 455 | 456 | case ObstacleType::SingleCactus: 457 | case ObstacleType::DoubleCactus: 458 | case ObstacleType::TripleCactus: 459 | 460 | obstacles[i].x--; 461 | break; 462 | 463 | } 464 | 465 | 466 | // Has the obstacle moved out of view ? 467 | 468 | if (obstacles[i].x < -getImageWidth(obstacles[i].image)) { 469 | obstacles[i].enabled = false; 470 | } 471 | 472 | } 473 | 474 | } 475 | 476 | } 477 | 478 | 479 | /* ----------------------------------------------------------------------------------------------------------------------------- 480 | * Render any visible obstacles on the screen .. 481 | * ----------------------------------------------------------------------------------------------------------------------------- 482 | */ 483 | void drawObstacles() { 484 | 485 | for (uint8_t i = 0; i < NUMBER_OF_OBSTACLES; i++) { 486 | 487 | if (obstacles[i].enabled == true) { 488 | 489 | uint8_t imageIndex = static_cast(obstacles[i].type); 490 | obstacles[i].image = obstacle_images[imageIndex]; 491 | Sprites::drawOverwrite(obstacles[i].x, obstacles[i].y - getImageHeight(obstacles[i].image), obstacles[i].image, 0); 492 | 493 | } 494 | 495 | } 496 | 497 | } 498 | 499 | 500 | /* ----------------------------------------------------------------------------------------------------------------------------- 501 | * Render the scoreboard at the top of the screen .. 502 | * ----------------------------------------------------------------------------------------------------------------------------- 503 | */ 504 | void drawScoreboard(bool displayCurrentScore) { 505 | 506 | arduboy.fillRect(0, 0, WIDTH, 10, BLACK); 507 | 508 | if (displayCurrentScore) { 509 | 510 | arduboy.setCursor(1, 0); 511 | arduboy.print(F("Score: ")); 512 | arduboy.setCursor(39, 0); 513 | if (score < 1000) arduboy.print("0"); 514 | if (score < 100) arduboy.print("0"); 515 | if (score < 10) arduboy.print("0"); 516 | arduboy.print(score); 517 | 518 | } 519 | 520 | arduboy.setCursor(72, 0); 521 | arduboy.print(F("High: ")); 522 | arduboy.setCursor(104, 0); 523 | if (highScore < 1000) arduboy.print("0"); 524 | if (highScore < 100) arduboy.print("0"); 525 | if (highScore < 10) arduboy.print("0"); 526 | arduboy.print(highScore); 527 | 528 | arduboy.drawLine(0, 9, WIDTH, 9, WHITE); 529 | 530 | } 531 | 532 | 533 | /* ----------------------------------------------------------------------------------------------------------------------------- 534 | * Launch a new obstacle .. 535 | * ----------------------------------------------------------------------------------------------------------------------------- 536 | */ 537 | void launchObstacle(uint8_t obstacleNumber) { 538 | 539 | 540 | // Randomly pick an obstacle .. 541 | 542 | ObstacleType randomUpper = ObstacleType::SingleCactus; 543 | 544 | switch (score) { 545 | 546 | case 0 ... 99: 547 | randomUpper = ObstacleType::SingleCactus; 548 | break; 549 | 550 | case 100 ... 199: 551 | randomUpper = ObstacleType::DoubleCactus; 552 | break; 553 | 554 | case 200 ... 299: 555 | randomUpper = ObstacleType::TripleCactus; 556 | break; 557 | 558 | default: 559 | randomUpper = ObstacleType::Count_AllObstacles; 560 | break; 561 | 562 | } 563 | 564 | uint8_t randomLowerVal = static_cast(ObstacleType::SingleCactus); 565 | uint8_t randomUpperVal = static_cast(randomUpper); 566 | uint8_t raddomObstacle = random(randomLowerVal, randomUpperVal + 1); 567 | 568 | ObstacleType type = static_cast(raddomObstacle); 569 | 570 | 571 | // Launch the obstacle .. 572 | 573 | obstacles[obstacleNumber].type = type; 574 | obstacles[obstacleNumber].enabled = true; 575 | obstacles[obstacleNumber].x = WIDTH - 1; 576 | 577 | if (type == ObstacleType::Pterodactyl1) { 578 | 579 | obstacles[obstacleNumber].y = random(PTERODACTYL_UPPER_LIMIT, PTERODACTYL_LOWER_LIMIT); 580 | 581 | } 582 | else { 583 | 584 | obstacles[obstacleNumber].y = CACTUS_GROUND_LEVEL; 585 | 586 | } 587 | 588 | } 589 | 590 | 591 | /* ----------------------------------------------------------------------------------------------------------------------------- 592 | * Render the ground. 593 | * ----------------------------------------------------------------------------------------------------------------------------- 594 | */ 595 | void drawGround(bool moveGround) { 596 | 597 | if (moveGround) { 598 | 599 | if (groundX == 32) { 600 | 601 | groundX = 0; 602 | 603 | 604 | // Randomly select a new road type .. 605 | 606 | uint8_t type = random(0, 6); 607 | GroundType groundType; 608 | 609 | switch (type) { 610 | 611 | case 0 ... 3: 612 | groundType = GroundType::Flat; 613 | break; 614 | 615 | case 4: 616 | groundType = GroundType::Bump; 617 | break; 618 | 619 | case 5: 620 | groundType = GroundType::Hole; 621 | break; 622 | 623 | } 624 | 625 | 626 | // Shuffle the road elements along and assign the randomly selected type to the last element .. 627 | 628 | ground[0] = ground[1]; 629 | ground[1] = ground[2]; 630 | ground[2] = ground[3]; 631 | ground[3] = ground[4]; 632 | ground[4] = groundType; 633 | 634 | } 635 | 636 | groundX++; 637 | 638 | } 639 | 640 | 641 | // Render the road. 642 | 643 | for (uint8_t i = 0; i < 5; i++) { 644 | 645 | uint8_t imageIndex = static_cast(ground[i]); 646 | Sprites::drawSelfMasked((i * 32) - groundX, GROUND_LEVEL, ground_images[imageIndex], 0); 647 | 648 | } 649 | 650 | } 651 | --------------------------------------------------------------------------------