micro:drop - A simple game for the BBC micro:bit Fruits falling from the sky. Use buttons A & B to control the player and catch them all. Drop speed increases every 10 catches. How much can you get? After game over press AB to restart.

Dependencies:   microbit

Committer:
novecento
Date:
Fri Jan 26 23:48:45 2018 +0000
Revision:
1:89b525515734
Parent:
0:53f603890bd2
Disabled BLE stack

Who changed what in which revision?

UserRevisionLine numberNew contents of line
novecento 0:53f603890bd2 1 /*
novecento 0:53f603890bd2 2 MIT License
novecento 0:53f603890bd2 3
novecento 0:53f603890bd2 4 Copyright (c) 2018 Alexander Dölz
novecento 0:53f603890bd2 5
novecento 0:53f603890bd2 6 Permission is hereby granted, free of charge, to any person obtaining a copy
novecento 0:53f603890bd2 7 of this software and associated documentation files (the "Software"), to deal
novecento 0:53f603890bd2 8 in the Software without restriction, including without limitation the rights
novecento 0:53f603890bd2 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
novecento 0:53f603890bd2 10 copies of the Software, and to permit persons to whom the Software is
novecento 0:53f603890bd2 11 furnished to do so, subject to the following conditions:
novecento 0:53f603890bd2 12
novecento 0:53f603890bd2 13 The above copyright notice and this permission notice shall be included in all
novecento 0:53f603890bd2 14 copies or substantial portions of the Software.
novecento 0:53f603890bd2 15
novecento 0:53f603890bd2 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
novecento 0:53f603890bd2 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
novecento 0:53f603890bd2 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
novecento 0:53f603890bd2 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
novecento 0:53f603890bd2 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
novecento 0:53f603890bd2 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
novecento 0:53f603890bd2 22 SOFTWARE.
novecento 0:53f603890bd2 23 */
novecento 0:53f603890bd2 24
novecento 0:53f603890bd2 25 /*
novecento 0:53f603890bd2 26 micro:drop - A simple game for the BBC micro:bit
novecento 0:53f603890bd2 27
novecento 0:53f603890bd2 28 Fruits falling from the sky. Use buttons A & B to control the player
novecento 0:53f603890bd2 29 and catch them all.
novecento 0:53f603890bd2 30
novecento 0:53f603890bd2 31 Drop speed increases every 10 catches. How much can you get?
novecento 0:53f603890bd2 32
novecento 0:53f603890bd2 33 After game over press AB to restart.
novecento 0:53f603890bd2 34 */
novecento 0:53f603890bd2 35 #include "MicroBit.h"
novecento 0:53f603890bd2 36
novecento 0:53f603890bd2 37 struct Sprite {
novecento 0:53f603890bd2 38 int x;
novecento 0:53f603890bd2 39 int y;
novecento 0:53f603890bd2 40 };
novecento 0:53f603890bd2 41
novecento 0:53f603890bd2 42 const MicroBitImage imageCross("255,0,0,0,255\n0,255,0,255,0\n0,0,255,0,0\n0,255,0,255,0\n255,0,0,0,255\n");
novecento 0:53f603890bd2 43 const MicroBitImage imageFlash("255,255,255,255,255\n255,255,255,255,255\n255,255,255,255,255\n255,255,255,255,255\n255,255,255,255,255\n");
novecento 0:53f603890bd2 44
novecento 0:53f603890bd2 45 MicroBit uBit;
novecento 0:53f603890bd2 46 Sprite player;
novecento 0:53f603890bd2 47 Sprite fruit;
novecento 0:53f603890bd2 48
novecento 0:53f603890bd2 49 int game_over;
novecento 0:53f603890bd2 50 int paused;
novecento 0:53f603890bd2 51 int life;
novecento 0:53f603890bd2 52 int score;
novecento 0:53f603890bd2 53 int level;
novecento 0:53f603890bd2 54 int game_speed;
novecento 0:53f603890bd2 55 int high_score;
novecento 0:53f603890bd2 56 int new_high_score;
novecento 0:53f603890bd2 57
novecento 0:53f603890bd2 58 /* Move player sprite to the left */
novecento 0:53f603890bd2 59 void onButtonA(MicroBitEvent e)
novecento 0:53f603890bd2 60 {
novecento 0:53f603890bd2 61 if (player.x > 0 && !paused && !game_over) {
novecento 0:53f603890bd2 62 uBit.display.image.setPixelValue(player.x, player.y, 0);
novecento 0:53f603890bd2 63 player.x--;
novecento 0:53f603890bd2 64 uBit.display.image.setPixelValue(player.x, player.y, 255);
novecento 0:53f603890bd2 65 }
novecento 0:53f603890bd2 66 }
novecento 0:53f603890bd2 67
novecento 0:53f603890bd2 68 /* Move player sprite to the right */
novecento 0:53f603890bd2 69 void onButtonB(MicroBitEvent e)
novecento 0:53f603890bd2 70 {
novecento 0:53f603890bd2 71 if (player.x < 4 && !paused && !game_over) {
novecento 0:53f603890bd2 72 uBit.display.image.setPixelValue(player.x, player.y, 0);
novecento 0:53f603890bd2 73 player.x++;
novecento 0:53f603890bd2 74 uBit.display.image.setPixelValue(player.x, player.y, 255);
novecento 0:53f603890bd2 75 }
novecento 0:53f603890bd2 76 }
novecento 0:53f603890bd2 77
novecento 0:53f603890bd2 78 /* Move fruit down and check for missed fruits */
novecento 0:53f603890bd2 79 void dropFruit()
novecento 0:53f603890bd2 80 {
novecento 0:53f603890bd2 81 while(!game_over) {
novecento 0:53f603890bd2 82 if(!paused) {
novecento 0:53f603890bd2 83 uBit.display.image.setPixelValue(fruit.x, fruit.y, 0);
novecento 0:53f603890bd2 84 fruit.y++;
novecento 0:53f603890bd2 85 // When the fruit is moved below the screen, it's a miss
novecento 0:53f603890bd2 86 if (fruit.y > 4) {
novecento 0:53f603890bd2 87 // Create a new fruit
novecento 0:53f603890bd2 88 fruit.x=uBit.random(5);
novecento 0:53f603890bd2 89 fruit.y=0;
novecento 0:53f603890bd2 90
novecento 0:53f603890bd2 91 // Show a cross to indicate the miss
novecento 0:53f603890bd2 92 uBit.display.print(imageCross);
novecento 0:53f603890bd2 93 uBit.sleep(25);
novecento 0:53f603890bd2 94 uBit.display.clear();
novecento 0:53f603890bd2 95
novecento 0:53f603890bd2 96 // Reduce life
novecento 0:53f603890bd2 97 life--;
novecento 0:53f603890bd2 98
novecento 0:53f603890bd2 99 // No more lifes means game over
novecento 0:53f603890bd2 100 if (life == 0) game_over = true;
novecento 0:53f603890bd2 101 }
novecento 0:53f603890bd2 102 uBit.display.image.setPixelValue(fruit.x, fruit.y, 255);
novecento 0:53f603890bd2 103 }
novecento 0:53f603890bd2 104 // Wait some time to give the human a chance
novecento 0:53f603890bd2 105 uBit.sleep(game_speed);
novecento 0:53f603890bd2 106 }
novecento 0:53f603890bd2 107 }
novecento 0:53f603890bd2 108
novecento 0:53f603890bd2 109 /* Update player sprite and check for catched fruits*/
novecento 0:53f603890bd2 110 void updatePlayer()
novecento 0:53f603890bd2 111 {
novecento 0:53f603890bd2 112 while(!game_over) {
novecento 0:53f603890bd2 113 uBit.display.image.setPixelValue(player.x, player.y, 255);
novecento 0:53f603890bd2 114 // Check if the player catched the fruit
novecento 0:53f603890bd2 115 if (player.x==fruit.x && player.y==fruit.y) {
novecento 0:53f603890bd2 116 // Create a new fruit above the screen; dropFruit will do the rest
novecento 0:53f603890bd2 117 fruit.x=uBit.random(5);
novecento 0:53f603890bd2 118 fruit.y=-1;
novecento 0:53f603890bd2 119
novecento 0:53f603890bd2 120 // Flash all LEDs to indicate the catch
novecento 0:53f603890bd2 121 uBit.display.print(imageFlash);
novecento 0:53f603890bd2 122 uBit.sleep(25);
novecento 0:53f603890bd2 123 uBit.display.clear();
novecento 0:53f603890bd2 124
novecento 0:53f603890bd2 125 // Score a point
novecento 0:53f603890bd2 126 score++;
novecento 0:53f603890bd2 127
novecento 0:53f603890bd2 128 // Level and speed will be incressed every 10 points
novecento 0:53f603890bd2 129 if (score % 10 == 0) {
novecento 0:53f603890bd2 130 // Pause the game during the "Next Level" message
novecento 0:53f603890bd2 131 paused = true;
novecento 0:53f603890bd2 132 level++;
novecento 0:53f603890bd2 133 if (game_speed > 50) game_speed -= 50;
novecento 0:53f603890bd2 134 uBit.display.clear();
novecento 0:53f603890bd2 135 uBit.display.scroll("LEVEL:");
novecento 0:53f603890bd2 136 uBit.display.scroll(level);
novecento 0:53f603890bd2 137 // Bring Play back to the initial position
novecento 0:53f603890bd2 138 uBit.display.image.setPixelValue(player.x, player.y, 0);
novecento 0:53f603890bd2 139 player.x = 2;
novecento 0:53f603890bd2 140 uBit.display.image.setPixelValue(player.x, player.y, 255);
novecento 0:53f603890bd2 141 // Resume the game
novecento 0:53f603890bd2 142 paused = false;
novecento 0:53f603890bd2 143 }
novecento 0:53f603890bd2 144 }
novecento 0:53f603890bd2 145 // Give the CPU a rest
novecento 0:53f603890bd2 146 uBit.sleep(25);
novecento 0:53f603890bd2 147 }
novecento 0:53f603890bd2 148 }
novecento 0:53f603890bd2 149 /* Game over message */
novecento 0:53f603890bd2 150 void gameOver()
novecento 0:53f603890bd2 151 {
novecento 0:53f603890bd2 152 // Concatenate everthiny to ensure it will be in the right order at async
novecento 0:53f603890bd2 153 // output
novecento 0:53f603890bd2 154 const int cScore = score;
novecento 0:53f603890bd2 155 ManagedString msGameOver = ManagedString("GAME OVER! SCORE: ") + ManagedString(cScore);
novecento 0:53f603890bd2 156 if (new_high_score) msGameOver = msGameOver + ManagedString(" NEW HIGH SCORE!");
novecento 0:53f603890bd2 157
novecento 0:53f603890bd2 158 uBit.display.scrollAsync(msGameOver);
novecento 0:53f603890bd2 159 }
novecento 0:53f603890bd2 160
novecento 0:53f603890bd2 161 /* The Game */
novecento 0:53f603890bd2 162 void dropGame()
novecento 0:53f603890bd2 163 {
novecento 0:53f603890bd2 164 // Show current high score
novecento 0:53f603890bd2 165 if (high_score > 0) {
novecento 0:53f603890bd2 166 uBit.display.scroll("HIGH SCORE:");
novecento 0:53f603890bd2 167 uBit.display.scroll(high_score);
novecento 0:53f603890bd2 168 }
novecento 0:53f603890bd2 169 uBit.display.scroll("LEVEL: 1");
novecento 0:53f603890bd2 170
novecento 0:53f603890bd2 171 // Initialize player sprite
novecento 0:53f603890bd2 172 player.x=2;
novecento 0:53f603890bd2 173 player.y=4;
novecento 0:53f603890bd2 174 uBit.display.image.setPixelValue(player.x, player.y, 255);
novecento 0:53f603890bd2 175
novecento 0:53f603890bd2 176 // Initialize fruit sprite above the screen; dropFruit will do the rest
novecento 0:53f603890bd2 177 fruit.x=uBit.random(5);
novecento 0:53f603890bd2 178 fruit.y=-1;
novecento 0:53f603890bd2 179
novecento 0:53f603890bd2 180 // Reset game state
novecento 0:53f603890bd2 181 paused = false;
novecento 0:53f603890bd2 182 game_speed = 400;
novecento 0:53f603890bd2 183 level = 1;
novecento 0:53f603890bd2 184 score = 0;
novecento 0:53f603890bd2 185 life = 3;
novecento 0:53f603890bd2 186 new_high_score = false;
novecento 0:53f603890bd2 187 game_over = false;
novecento 0:53f603890bd2 188
novecento 0:53f603890bd2 189 //Create background tasks for player and fruit update
novecento 0:53f603890bd2 190 create_fiber(updatePlayer);
novecento 0:53f603890bd2 191 create_fiber(dropFruit);
novecento 0:53f603890bd2 192
novecento 0:53f603890bd2 193 // Game logik is in fibers updatePlayer and dropFruit
novecento 0:53f603890bd2 194 // So, while playing do nothing
novecento 0:53f603890bd2 195 while(!game_over)
novecento 0:53f603890bd2 196 uBit.sleep(200);
novecento 0:53f603890bd2 197
novecento 0:53f603890bd2 198 // Check for new high score
novecento 0:53f603890bd2 199 if (score > high_score) {
novecento 0:53f603890bd2 200 high_score = score;
novecento 0:53f603890bd2 201 // Maker it persistent
novecento 0:53f603890bd2 202 uBit.storage.put("high_score", (uint8_t *)&high_score, sizeof(int));
novecento 0:53f603890bd2 203 new_high_score = true;
novecento 0:53f603890bd2 204 }
novecento 0:53f603890bd2 205
novecento 0:53f603890bd2 206 // Show "Game Over" screen and score until A&B ist pressed together
novecento 0:53f603890bd2 207 uBit.display.clear();
novecento 0:53f603890bd2 208
novecento 0:53f603890bd2 209 while(!uBit.buttonAB.isPressed()) {
novecento 0:53f603890bd2 210 gameOver();
novecento 0:53f603890bd2 211 uBit.sleep(200);
novecento 0:53f603890bd2 212 }
novecento 0:53f603890bd2 213 uBit.display.stopAnimation();
novecento 0:53f603890bd2 214 }
novecento 0:53f603890bd2 215
novecento 0:53f603890bd2 216 int main(void)
novecento 0:53f603890bd2 217 {
novecento 0:53f603890bd2 218 // Initialize the micro:bit runtime
novecento 0:53f603890bd2 219 uBit.init();
novecento 0:53f603890bd2 220
novecento 0:53f603890bd2 221 // Game titel
novecento 0:53f603890bd2 222 uBit.display.scroll("MICRO:DROP");
novecento 0:53f603890bd2 223
novecento 0:53f603890bd2 224 // Get current high score from storage
novecento 0:53f603890bd2 225 KeyValuePair* stroredHighScore = uBit.storage.get("high_score");
novecento 0:53f603890bd2 226 if(stroredHighScore == NULL) {
novecento 0:53f603890bd2 227 high_score = 0; // Seems to be the first boot; there is no high score
novecento 0:53f603890bd2 228 } else {
novecento 0:53f603890bd2 229 // Copy high score from key value pair into int var
novecento 0:53f603890bd2 230 memcpy(&high_score, stroredHighScore->value, sizeof(int));
novecento 0:53f603890bd2 231 delete stroredHighScore;
novecento 0:53f603890bd2 232 }
novecento 0:53f603890bd2 233
novecento 0:53f603890bd2 234 //Disable buttons & background tasks at startup
novecento 0:53f603890bd2 235 game_over = true;
novecento 0:53f603890bd2 236
novecento 0:53f603890bd2 237 //Register functions to buttons A & B
novecento 0:53f603890bd2 238 uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_DOWN, onButtonA);
novecento 0:53f603890bd2 239 uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_DOWN, onButtonB);
novecento 0:53f603890bd2 240
novecento 0:53f603890bd2 241 // Play forever ;-)
novecento 0:53f603890bd2 242 while(1) {
novecento 0:53f603890bd2 243 dropGame();
novecento 0:53f603890bd2 244 }
novecento 0:53f603890bd2 245 }