Snake
Dependencies: microbit
Fork of microbit_snake by
main.cpp
00001 /* 00002 The MIT License (MIT) 00003 Copyright (c) 2016 British Broadcasting Corporation. 00004 This software is provided by Lancaster University by arrangement with the BBC. 00005 Permission is hereby granted, free of charge, to any person obtaining a 00006 copy of this software and associated documentation files (the "Software"), 00007 to deal in the Software without restriction, including without limitation 00008 the rights to use, copy, modify, merge, publish, distribute, sublicense, 00009 and/or sell copies of the Software, and to permit persons to whom the 00010 Software is furnished to do so, subject to the following conditions: 00011 The above copyright notice and this permission notice shall be included in 00012 all copies or substantial portions of the Software. 00013 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 00014 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 00015 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 00016 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 00017 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 00018 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 00019 DEALINGS IN THE SOFTWARE. 00020 */ 00021 00022 #include "MicroBit.h" 00023 #include "DigitalOut.h" 00024 00025 #define SNAKE_HEAD_PIXEL_BRIGHTNESS 150 00026 #define SNAKE_BONE_PIXEL_BRIGHTNESS 15 00027 #define SNAKE_FOOD_PIXEL_BRIGHTNESS 255 00028 00029 template <class T> class Node{ 00030 public: 00031 Node(T data): d(data), next(NULL), prev(NULL) {} 00032 00033 Node * getNext(){ 00034 return next; 00035 } 00036 Node * getPrev(){ 00037 return prev; 00038 } 00039 void detach () { 00040 if (prev){ 00041 prev->next = next; 00042 } 00043 if (next){ 00044 next->prev = prev; 00045 } 00046 00047 next = NULL; 00048 prev = NULL; 00049 } 00050 void append(Node * node) { 00051 Node * n = this; 00052 while (n->getNext()){ 00053 n = n->getNext(); 00054 } 00055 n->next = node; 00056 if (node){ 00057 node->prev = n; 00058 } 00059 } 00060 public: 00061 T d; 00062 private: 00063 Node * next; 00064 Node * prev; 00065 }; 00066 00067 template <class T> class List { 00068 public: 00069 List(): head(NULL), tail(NULL){ 00070 } 00071 void prepend(Node<T> * node){ 00072 if (node){ 00073 if (head == NULL && tail == NULL) { 00074 head = tail = node; 00075 } else { 00076 node->append(head); 00077 head = node; 00078 } 00079 } 00080 } 00081 Node<T> * removeTail(){ 00082 Node<T> * node = tail; 00083 if (tail) { 00084 tail = tail->getPrev(); 00085 node->detach(); 00086 if (tail == NULL){ 00087 head = NULL; 00088 } 00089 } 00090 return node; 00091 } 00092 Node<T> * getHead(){ 00093 return head; 00094 } 00095 Node<T> * getTail(){ 00096 return tail; 00097 } 00098 void cleanup(){ 00099 Node<T> * node = tail; 00100 while(node){ 00101 Node<T> * temp = node; 00102 node = node->getPrev(); 00103 00104 temp->detach(); 00105 delete temp; 00106 } 00107 head = tail = NULL; 00108 } 00109 private: 00110 Node<T> * head; 00111 Node<T> * tail; 00112 }; 00113 00114 class Dimension 00115 { 00116 public: 00117 Dimension (int start, int end) { 00118 this->start = start; 00119 this->end = end; 00120 this->cur = start; 00121 } 00122 00123 Dimension (int start, int end, int cur) { 00124 this->start = start; 00125 this->end = end; 00126 this->cur = cur; 00127 } 00128 00129 void operator ++(int){ 00130 cur++; 00131 if (cur > end){ 00132 cur = start; 00133 } 00134 } 00135 00136 void operator --(int){ 00137 cur--; 00138 if (cur < start){ 00139 cur = end; 00140 } 00141 } 00142 00143 int operator -(int i){ 00144 int r = cur - i; 00145 if (r < start){ 00146 r = end; 00147 } 00148 return r; 00149 } 00150 00151 operator int(){ 00152 return cur; 00153 } 00154 private: 00155 int start; 00156 int end; 00157 int cur; 00158 }; 00159 00160 00161 class SnakeBone { 00162 public: 00163 SnakeBone(int startx, int endx, int curx, int starty, int endy, int cury): 00164 x(startx, endx, curx), y(starty, endy, cury) 00165 { 00166 } 00167 00168 Dimension getX(){ 00169 return x; 00170 } 00171 00172 Dimension getY(){ 00173 return y; 00174 } 00175 00176 private: 00177 Dimension x; 00178 Dimension y; 00179 }; 00180 00181 00182 class Snake { 00183 public: 00184 enum Direction { 00185 UP, 00186 DOWN, 00187 LEFT, 00188 RIGHT 00189 }; 00190 00191 Snake (): d(UP){ 00192 bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2))); 00193 } 00194 00195 Node<SnakeBone> * getHead(){ 00196 return bones.getHead(); 00197 } 00198 Dimension getHeadX(){ 00199 return _getNodeX(bones.getHead()); 00200 } 00201 Dimension getHeadY(){ 00202 return _getNodeY(bones.getHead()); 00203 } 00204 Dimension getTailX(){ 00205 return _getNodeX(bones.getTail()); 00206 } 00207 Dimension getTailY(){ 00208 return _getNodeY(bones.getTail()); 00209 } 00210 00211 void grow(){ 00212 // Find next position to place new head 00213 Dimension nextX = getHeadX(); 00214 Dimension nextY = getHeadY(); 00215 00216 switch (d){ 00217 case UP: { 00218 nextY--; 00219 } 00220 break; 00221 case DOWN: { 00222 nextY++; 00223 } 00224 break; 00225 case LEFT: { 00226 nextX--; 00227 } 00228 break; 00229 case RIGHT: { 00230 nextX++; 00231 } 00232 break; 00233 } 00234 00235 // New bone at head 00236 bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, nextX, 0, 4, nextY))); 00237 } 00238 00239 void reduce(){ 00240 Node<SnakeBone> * tail = bones.removeTail(); 00241 if (tail){ 00242 delete tail; 00243 } 00244 } 00245 00246 void left(){ 00247 switch (d){ 00248 case UP: 00249 case DOWN: { 00250 d = LEFT; 00251 } 00252 break; 00253 case LEFT: { 00254 d = DOWN; 00255 } 00256 break; 00257 case RIGHT: { 00258 d = UP; 00259 } 00260 break; 00261 } 00262 } 00263 00264 void right(){ 00265 switch (d){ 00266 case UP: 00267 case DOWN: { 00268 d = RIGHT; 00269 } 00270 break; 00271 case LEFT: { 00272 d = UP; 00273 } 00274 break; 00275 case RIGHT: { 00276 d = DOWN; 00277 } 00278 break; 00279 } 00280 } 00281 00282 void reset(){ 00283 bones.cleanup(); 00284 bones.prepend(new Node<SnakeBone>(SnakeBone(0, 4, 2, 0, 4, 2))); 00285 d = UP; 00286 } 00287 00288 Direction getDirection(){ 00289 return d; 00290 } 00291 00292 private: 00293 Dimension _getNodeX(Node<SnakeBone> * node){ 00294 if (node){ 00295 return node->d.getX(); 00296 } 00297 return Dimension(-1, -1, -1); 00298 } 00299 00300 Dimension _getNodeY(Node<SnakeBone> * node){ 00301 if (node){ 00302 return node->d.getY(); 00303 } 00304 return Dimension(-1, -1, -1); 00305 } 00306 00307 List<SnakeBone> bones; 00308 Direction d; 00309 }; 00310 00311 class Game { 00312 public: 00313 Game(MicroBit & ubit): uBit(ubit), foodx(-1), foody(-1), image(5,5), score(0), 00314 buttonPressed(false) { 00315 uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE); 00316 uBit.display.print(image); 00317 } 00318 00319 void left(){ 00320 if (!buttonPressed){ 00321 buttonPressed = true; 00322 snake.left(); 00323 } 00324 } 00325 00326 void right(){ 00327 if (!buttonPressed){ 00328 buttonPressed = true; 00329 snake.right(); 00330 } 00331 } 00332 00333 bool move(){ 00334 // Dim old head 00335 Dimension x = snake.getHeadX(); 00336 Dimension y = snake.getHeadY(); 00337 image.setPixelValue(x, y, SNAKE_BONE_PIXEL_BRIGHTNESS); 00338 00339 snake.grow(); 00340 00341 Dimension nextX = snake.getHeadX(); 00342 Dimension nextY = snake.getHeadY(); 00343 00344 // check if we grew by eating. 00345 if (nextX == foodx && nextY == foody){ 00346 // food gone 00347 foodx = -1; 00348 foody = -1; 00349 score++; 00350 00351 // Since we have grown to the head. no need to delete tail. 00352 } else { 00353 // Switch off end. 00354 int endx = snake.getTailX(); 00355 int endy = snake.getTailY(); 00356 if (endx != -1 && endy != -1){ 00357 image.setPixelValue(endx, endy, 0); 00358 } 00359 snake.reduce(); 00360 } 00361 00362 // Check snake bite 00363 if (image.getPixelValue(nextX, nextY) == 15){ 00364 // Game Over 00365 return false; 00366 } 00367 00368 // Turn On head. 00369 image.setPixelValue(nextX, nextY, SNAKE_HEAD_PIXEL_BRIGHTNESS); 00370 uBit.display.print(image); 00371 buttonPressed = false; 00372 return true; 00373 } 00374 00375 bool isGoodFood(int x, int y){ 00376 bool ret = image.getPixelValue(x, y) == 0; 00377 00378 // also don't accept food in the direction of movement 00379 if (ret){ 00380 switch(snake.getDirection()){ 00381 case Snake::UP: 00382 case Snake::DOWN: { 00383 ret = x != snake.getHeadX(); 00384 } 00385 break; 00386 case Snake::LEFT: 00387 case Snake::RIGHT: { 00388 ret = y != snake.getHeadY(); 00389 } 00390 break; 00391 } 00392 } 00393 return ret; 00394 } 00395 00396 void play(){ 00397 while (true) { 00398 if (!move()){ 00399 showGameOver(); 00400 break; 00401 } 00402 00403 // Put food 00404 if (foodx == -1 && foody == -1){ 00405 while (true){ 00406 int x = uBit.random(5); 00407 int y = uBit.random(5); 00408 00409 if (isGoodFood(x, y)){ 00410 foodx = x; 00411 foody = y; 00412 image.setPixelValue(foodx, foody, 255); 00413 uBit.display.print(image); 00414 break; 00415 } 00416 } 00417 } 00418 uBit.sleep(500); 00419 } 00420 } 00421 00422 void animateSnake(){ 00423 #define ANIMATION_SPEED 50 00424 // UP 2 00425 foodx = 2;foody = 1; 00426 move(); 00427 uBit.sleep(ANIMATION_SPEED); 00428 00429 foodx = 2;foody = 0; 00430 move(); 00431 uBit.sleep(ANIMATION_SPEED); 00432 00433 // LEFT 00434 foodx = 1;foody = 0; 00435 left(); 00436 move(); 00437 uBit.sleep(ANIMATION_SPEED); 00438 00439 // DOWN 00440 foodx = 1;foody = 1; 00441 left(); 00442 move(); 00443 uBit.sleep(ANIMATION_SPEED); 00444 00445 foodx = 1;foody = 2; 00446 move(); 00447 uBit.sleep(ANIMATION_SPEED); 00448 move(); 00449 uBit.sleep(ANIMATION_SPEED); 00450 move(); 00451 uBit.sleep(ANIMATION_SPEED); 00452 00453 for (int i = 0; i < 4; i++){ 00454 // LEFT 00455 left(); 00456 move(); 00457 uBit.sleep(ANIMATION_SPEED); 00458 00459 // UP 00460 right(); 00461 move(); 00462 uBit.sleep(ANIMATION_SPEED); 00463 00464 move(); 00465 uBit.sleep(ANIMATION_SPEED); 00466 move(); 00467 uBit.sleep(ANIMATION_SPEED); 00468 move(); 00469 uBit.sleep(ANIMATION_SPEED); 00470 00471 // LEFT 00472 left(); 00473 move(); 00474 uBit.sleep(ANIMATION_SPEED); 00475 00476 // DOWN 00477 left(); 00478 move(); 00479 uBit.sleep(ANIMATION_SPEED); 00480 00481 move(); 00482 uBit.sleep(ANIMATION_SPEED); 00483 move(); 00484 uBit.sleep(ANIMATION_SPEED); 00485 move(); 00486 uBit.sleep(ANIMATION_SPEED); 00487 } 00488 00489 // Back to the centre 00490 left(); 00491 move(); 00492 uBit.sleep(ANIMATION_SPEED); 00493 00494 left(); 00495 move(); 00496 uBit.sleep(ANIMATION_SPEED); 00497 00498 move(); 00499 uBit.sleep(ANIMATION_SPEED); 00500 move(); 00501 uBit.sleep(ANIMATION_SPEED); 00502 00503 // eat the tail 00504 while(snake.getHead()){ 00505 image.setPixelValue(snake.getTailX(), snake.getTailY(), 0); 00506 uBit.display.print(image); 00507 snake.reduce(); 00508 uBit.sleep(ANIMATION_SPEED); 00509 } 00510 00511 reset(); 00512 } 00513 00514 void showGameOver(){ 00515 // switch off food 00516 if (foodx != -1 && foody != -1){ 00517 image.setPixelValue(foodx, foody, 0); 00518 uBit.display.print(image); 00519 } 00520 00521 // change brightness to 255 as pixels with custom brightness 00522 // are not affected by display.setBrightness(). Possible Bug!!! 00523 Node<SnakeBone> * bone = snake.getHead(); 00524 while (bone){ 00525 image.setPixelValue(bone->d.getX(), bone->d.getY(), SNAKE_FOOD_PIXEL_BRIGHTNESS); 00526 bone = bone->getNext(); 00527 } 00528 // Flash snake 00529 uBit.display.print(image); 00530 bool toggle = false; 00531 for (int i = 0; i < 10; i++, toggle = !toggle){ 00532 if (toggle) 00533 uBit.display.setBrightness(255); 00534 else 00535 uBit.display.setBrightness(SNAKE_BONE_PIXEL_BRIGHTNESS); 00536 uBit.sleep(500); 00537 } 00538 uBit.display.print(MicroBitImage(5,5)); 00539 uBit.display.setBrightness(255); 00540 uBit.display.scroll("SCORE-"); // don't mind spaces in a scrolling display. 00541 uBit.display.scroll(score, 150); 00542 } 00543 00544 void reset(){ 00545 // cleanup snake and state 00546 snake.reset(); 00547 00548 foodx = -1; 00549 foody = -1; 00550 score = 0; 00551 image = MicroBitImage(5, 5); 00552 uBit.display.setDisplayMode(DISPLAY_MODE_GREYSCALE); 00553 image.setPixelValue(snake.getHeadX(), snake.getHeadY(), SNAKE_HEAD_PIXEL_BRIGHTNESS); 00554 uBit.display.print(image); 00555 } 00556 00557 00558 private: 00559 MicroBit & uBit; 00560 int foodx; 00561 int foody; 00562 Snake snake; 00563 MicroBitImage image; 00564 int score; 00565 00566 // Sync button press and movement. Don't let quick button presses 00567 // change snake direction twice/thrice/... before moving. As a 00568 // double press can reverse the snake on its own and Game Over!!! 00569 bool buttonPressed; 00570 }; 00571 00572 00573 MicroBit uBit; 00574 Game game(uBit); 00575 00576 00577 void onButtonA(MicroBitEvent){ 00578 game.left(); 00579 } 00580 00581 void onButtonB(MicroBitEvent){ 00582 game.right(); 00583 } 00584 00585 00586 int main() 00587 { 00588 // Initialise the micro:bit runtime. 00589 uBit.init(); 00590 00591 uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA); 00592 uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButtonB); 00593 00594 // Continous play 00595 while (true){ 00596 game.animateSnake(); 00597 game.play(); 00598 game.reset(); 00599 } 00600 }
Generated on Sun Jul 24 2022 05:49:02 by
