Snake game for IMP class.

Dependencies:   mbed Terminal SerialTerminal TSI MMA8451Q

Files at this revision

API Documentation at this revision

Comitter:
lpiwowar
Date:
Fri Dec 13 10:14:54 2019 +0000
Commit message:
Initial commit;

Changed in this revision

MMA8451Q.lib Show annotated file Show diff for this revision Revisions of this file
SerialTerminal.lib Show annotated file Show diff for this revision Revisions of this file
TSI.lib Show annotated file Show diff for this revision Revisions of this file
Terminal.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MMA8451Q.lib	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/emilmont/code/MMA8451Q/#c4d879a39775
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SerialTerminal.lib	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/lpiwowar/code/SerialTerminal/#e171212939f3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TSI.lib	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/TSI/#1a60ef257879
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Terminal.lib	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/lpiwowar/code/Terminal/#169909f7566f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,470 @@
+#include <stdlib.h>
+#include "mbed.h"
+#include "TSISensor.h"
+#include "SerialTerminal.h"
+#include "MMA8451Q.h"
+
+#define MMA8451_I2C_ADDRESS (0x1d<<1)
+
+PwmOut rled(LED_RED);
+PwmOut gled(LED_GREEN);
+Timeout timeout;
+
+PinName const SDA = PTE25;      /** < I2C serial data    */
+PinName const SCL = PTE24;      /** < I2C serial clock   */
+
+/** Accelerometer */
+MMA8451Q acc(SDA, SCL,  MMA8451_I2C_ADDRESS);
+
+//Baud rate values: 57600, 115200, 460800
+/** Access terminal via USB */
+SerialTerminal term(USBTX, USBRX, 460800);
+
+const int FIELD_WIDTH = 60;     /** < Width of game field         */
+const int FIELD_HEIGHT = 20;    /** < Height of game field        */
+const int INIT_SNAKE_X = 30;    /** < Snake initial x coordinates */
+const int INIT_SNAKE_Y = 10;    /** < Snake initial y coordinates */
+
+/** @brief Stores the game field. ('#' = Snake + walls) */
+char field[FIELD_HEIGHT][FIELD_WIDTH] = {};
+
+////////////////////////////////////////////////////////////////////////////////
+///                         SNAKE - start                                    ///
+////////////////////////////////////////////////////////////////////////////////
+
+/** Values for direction of snake */
+enum snake_move {SNAKE_UP, SNAKE_DOWN, SNAKE_LEFT, SNAKE_RIGHT, SNAKE_NONE};
+typedef enum snake_move snake_move_T;
+
+/** Stores facts about one cell of snake */
+typedef struct snake_cell_struct snake_cell_T;
+struct snake_cell_struct {
+    short x;                    /** < X coordinates within @var field   */
+    short y;                    /** < Y coordinates within @var field   */
+    bool head;                  /** < True if cell is head (first cell) */
+    snake_cell_T *next_cell;    /** < Pointer to next cell of snake     */
+};
+
+/**
+ * @brif Creates first cell of snake and returns pointer to it. Atribute
+ *       head is set to true.
+ * @param x X coordinates of location where the snake should be spawned.
+ * @param y Y coordinates of location where the snake should be spawned.
+ * @return    pointer to the first cell of snake
+ */
+snake_cell_T *init_snake(int x, int y) {
+    snake_cell_T *new_snake = (snake_cell_T *)malloc(sizeof(snake_cell_T));
+    if(new_snake == NULL) {
+        term.printf("Error: malloc()\r\n");
+        exit(1);
+    }
+    
+    new_snake->x = x;
+    new_snake->y = y;
+    new_snake->next_cell = NULL;
+    new_snake->head = true;
+    
+    return new_snake;
+}
+
+/**
+ * @brief Frees all memory allocated for Snake.
+ * @param snake Pointer to first cell of Snake
+ */
+void destroy_snake(snake_cell_T *snake) {
+    snake_cell_T *snake_cell_tmp = NULL;
+    while(snake != NULL) {
+        snake_cell_tmp = snake;
+        snake = snake->next_cell;
+        free(snake_cell_tmp);
+    }  
+}
+
+/**
+ * @brief Appends cell to the end of Snake.
+ * @param snake         Snake which should contain the new cell.
+ * @param snake_cell    Cell which should be appended to the Snake.
+ */
+void append_cell_to_snake(snake_cell_T *snake, snake_cell_T *snake_cell) {
+   
+    while(snake != NULL) {
+        if(snake->next_cell == NULL) {
+            snake->next_cell = snake_cell;
+            break;
+        }
+        snake = snake->next_cell;
+    }
+    
+}
+
+const int WALL = -1;        /** < Return value if Snake hit the wall */
+const int FOOD_FOUND = 1;   /** < Return value if Snake ate the food */
+/**
+ * @brief  Updates the position of Snake. If the position where the snake
+ *         wants to move contains food ('*') it returns @var FOOD_FOUND and 
+ *         creates new snake cell and appends it to the snake. If the position
+ *         where the snake wants to move contains wall ('#') it returns 
+ *         WALL. Also updates coordinates coordinates of snake based on 
+ *         direction in which the snake is moving.
+ * @param  snake           Snake whose coordinates should be updated.
+ * @param  move_direction  In which direction is the snake moving.
+ * @return 0 if success. WALL if snake hit the wall. FOOD_FOUND if snake ate
+ *         the food.
+ */       
+int update_snake_pos(snake_cell_T *snake, snake_move_T move_direction) {
+    
+    bool food_found = false;
+
+    switch(move_direction) {
+        case SNAKE_UP:      if(field[snake->y-1][snake->x] == '#') 
+                                return WALL;
+                            else if(field[snake->y-1][snake->x] == '*')
+                                food_found = true;
+                            break;
+                            
+        case SNAKE_DOWN:    if(field[snake->y+1][snake->x] == '#') 
+                                return WALL;
+                            else if(field[snake->y+1][snake->x] == '*')
+                                food_found = true;
+                            break;
+                            
+        case SNAKE_RIGHT:   if(field[snake->y][snake->x+1] == '#') 
+                                return WALL;
+                            else if(field[snake->y][snake->x+1] == '*')
+                                food_found = true;
+                            break;
+                                       
+        case SNAKE_LEFT:    if(field[snake->y][snake->x-1] == '#') 
+                                return WALL;
+                            else if(field[snake->y][snake->x-1] == '*')
+                                food_found = true;  
+                            break;                             
+    }
+    
+    snake_cell_T *new_cell = NULL;
+    snake_cell_T *save_snake = snake;
+    int prev_x = 0;
+    int prev_y = 0;
+    int tmp = 0;
+
+    while(snake != NULL) {
+        
+        if(food_found && snake->next_cell == NULL) { 
+            new_cell = init_snake(snake->x, snake->y); 
+            new_cell->head = false; 
+         }
+        
+        if(snake->head) {
+            switch(move_direction) {
+                case SNAKE_UP:
+                    prev_x = snake->x; prev_y = snake->y;
+                    field[snake->y][snake->x] = ' '; 
+                    snake->y -= 1;
+                    field[snake->y][snake->x] = '#';
+                    break;
+                case SNAKE_DOWN:
+                    prev_x = snake->x; prev_y = snake->y;
+                    field[snake->y][snake->x] = ' '; 
+                    snake->y += 1;
+                    field[snake->y][snake->x] = '#';
+                    break;
+                case SNAKE_RIGHT:
+                    prev_x = snake->x; prev_y = snake->y;
+                    field[snake->y][snake->x] = ' '; 
+                    snake->x += 1;
+                    field[snake->y][snake->x] = '#';
+                    break;
+                case SNAKE_LEFT:
+                    prev_x = snake->x; prev_y = snake->y;
+                    field[snake->y][snake->x] = ' ';  
+                    snake->x -= 1;
+                    field[snake->y][snake->x] = '#';
+                    break;
+            }
+        } else {
+            field[snake->y][snake->x] = ' ';
+            field[prev_y][prev_x] = '#';
+            tmp = snake->x; snake->x = prev_x; prev_x = tmp;
+            tmp = snake->y; snake->y = prev_y; prev_y = tmp;
+        }
+        
+        snake = snake->next_cell;
+    }
+    
+    if(food_found) {
+        append_cell_to_snake(save_snake, new_cell);
+        return FOOD_FOUND;
+    }
+    else {
+        return 0;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///                          SNAKE - end                                     ///
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+///                          MENU - start                                    ///
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum {EASY, DIFFICULT, SUPER_DIFFICULT} difficulty_T;
+TSISensor tsi;
+
+void print_menu(difficulty_T difficulty, float time_left) {
+    term.cls();
+    term.move_cursor_up(FIELD_HEIGHT + 3);
+    
+    for(int i = 0; i != FIELD_HEIGHT/3; i++)
+        term.printf("\r\n");
+    
+    for(int i = 0; i != (FIELD_WIDTH - strlen("Please select dificulty)"))/2; i++)
+        term.printf(" ");
+        
+    term.printf("Please selectt dificulty (%1.1f)", time_left);
+    term.printf("\r\n");
+    
+    for(int i = 0; i != (FIELD_WIDTH - strlen("Please select dificulty)"))/2; i++)
+        term.printf(" ");
+    
+    switch(difficulty) {
+        case EASY:
+            term.printf("[*]Eeasy [ ]Difficult [ ]Super Difficult\r\n");
+            break;
+        case DIFFICULT:
+            term.printf("[ ]Eeasy [*]Difficult [ ]Super Difficult\r\n");
+            break;
+        case SUPER_DIFFICULT:
+            term.printf("[ ]Eeasy [ ]Difficult [*]Super Difficult\r\n");
+            break;
+    }
+}
+
+difficulty_T get_difficulty() {
+    difficulty_T save_state = EASY;
+    float total_time = 10.0;
+    float elapsed_time = 0.0;
+    while(1) {
+        
+        float percentage = tsi.readPercentage();
+        
+        if(percentage <= 0.3 && percentage >= 0.05) { 
+            print_menu(EASY, total_time - elapsed_time);
+            save_state = EASY;
+        } else if(percentage > 0.3 && percentage <= 0.6) { 
+            print_menu(DIFFICULT, total_time - elapsed_time);
+            save_state = DIFFICULT;
+        } else if(percentage > 0.6) {
+            print_menu(SUPER_DIFFICULT, total_time - elapsed_time);
+            save_state = SUPER_DIFFICULT;
+        } else {
+            print_menu(save_state, total_time - elapsed_time);
+        }
+        
+        wait(0.1);
+        elapsed_time += 0.1;
+        if(elapsed_time > 10.0)
+            break;
+    }
+    
+    return save_state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///                           MENU - end                                     ///
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @brief Initializes the game field with walls and snake.
+ */
+void init_field() {
+    for(int i = 0; i < FIELD_WIDTH; i++)
+        field[0][i] = '#';
+        
+    for(int y = 1; y < FIELD_HEIGHT - 1; y++) {
+        field[y][0] = '#';
+        field[y][FIELD_WIDTH - 1] = '#';
+        
+        if(y == INIT_SNAKE_Y)
+            field[INIT_SNAKE_Y][INIT_SNAKE_X] = '#';
+        
+        for(int x = 1; x < FIELD_WIDTH - 1; x++) {
+            field[y][x] = ' ';
+        }
+    }
+        
+    for(int i = 0; i < FIELD_WIDTH; i++)
+        field[FIELD_HEIGHT - 1][i] = '#';
+}
+
+/**
+ * @brief Prints the game field.
+ */
+void print_field(SerialTerminal &term) {
+    for(int y = 0; y < FIELD_HEIGHT; y++) {
+        
+        for(int x = 0; x < FIELD_WIDTH; x++) 
+            term.printf("%c", field[y][x]);
+            
+        term.printf("\r\n");     
+    }      
+}
+
+/**
+ * @brief Clears the terminal.
+ */
+void clear_field(SerialTerminal &term) {
+        term.cls();
+        term.move_cursor_up(FIELD_HEIGHT + 3);
+}
+
+/**
+ * @brief Generates food and puts it to the game field.
+ */
+void generate_food() {
+    bool not_found = true;
+    
+    while(not_found) {
+        int food_x = (rand() % (FIELD_WIDTH - 6)) + 3;
+        int food_y = (rand() % (FIELD_HEIGHT - 6)) + 3;
+        
+        if(field[food_y][food_x] == ' ') {
+            field[food_y][food_x] = '*';
+            not_found = false;
+        }
+    }
+}
+
+/**
+ * @brief Gets the direction of required direction of snake based on values
+ *        from accelerometer.
+ */
+snake_move_T get_direction(int score_counter) {
+    float up_down = acc.getAccX();    /* - = UP, + = DOWN */
+    float left_right = acc.getAccY(); /* - = LEFT, + = RIGHT */
+
+    static snake_move_T remember_last_dir = SNAKE_NONE;
+    
+    if(abs(up_down) > abs(left_right)) {
+        if(up_down <= -0.25) {
+            if(score_counter != 0 && remember_last_dir == SNAKE_DOWN) 
+                return remember_last_dir;
+            remember_last_dir = SNAKE_UP;
+            return SNAKE_UP;
+        }
+        
+        if(up_down >= 0.25) {
+            if(score_counter != 0 && remember_last_dir == SNAKE_UP) 
+                return remember_last_dir;
+            remember_last_dir = SNAKE_DOWN;
+            return SNAKE_DOWN;
+        }
+        
+    } else {
+        if (left_right <= -0.25) {
+            if(score_counter != 0 && remember_last_dir == SNAKE_RIGHT) 
+                return remember_last_dir;
+            remember_last_dir = SNAKE_LEFT;
+            return SNAKE_LEFT;
+        }
+        
+        if (left_right >= 0.25) {
+            if(score_counter != 0 && remember_last_dir == SNAKE_LEFT)
+                return remember_last_dir;
+            remember_last_dir = SNAKE_RIGHT;
+            return SNAKE_RIGHT;
+        }
+    }
+    
+    if(remember_last_dir != SNAKE_NONE)
+        return remember_last_dir;
+    else
+        return SNAKE_RIGHT;
+}
+
+/**
+ * @brief The higher the score the shorter time this function waits. If the
+ *        snake is moving horizontally the function waits shorter period of 
+ *        time to compensate that letter's height is bigger than width;
+ */
+void score_wait(int score_counter, 
+                snake_move_T snake_direction, 
+                difficulty_T difficulty) {
+                    
+        float time = 1;
+        
+        if (difficulty == EASY) time = 1;
+        else if (difficulty == DIFFICULT) time = 0.6;
+        else if(difficulty == SUPER_DIFFICULT) time = 0.4;
+        
+        if (score_counter > 5) time = time / 3;
+        else if (score_counter > 10) time = time / 5;
+        else if (score_counter > 15) time = time / 6;
+        
+        if(snake_direction == SNAKE_RIGHT || snake_direction == SNAKE_LEFT)
+            wait(time/2);
+        else
+            wait(time);
+            
+}
+
+//void blick_red_diode() {
+//}
+
+/**
+ * @brief Lights up diode with collor corresponding to actual speed of the
+ *        snake. (e.g.: snake moves slowly => green, snakes moves fast => red)
+ */
+void light_up_diode(int score_counter, difficulty_T difficulty) {
+    static bool lock = false;
+    if(lock) return;
+    
+    float new_red = 1.0 - (1.0/2.0 * (float)score_counter);
+    float new_green = 0.0 + (1.0/2.0 * (float)score_counter);
+    rled = new_red;
+    gled = new_green;
+    
+    if(new_green >= 0.99) {
+        lock = true;
+        //timeout.attach(&blick_red_diode, 10000);
+    }
+}
+
+int main(void) {
+    rled = 1; gled = 1;
+    
+    term.hide_cursor();
+    init_field();
+    generate_food();
+    
+    difficulty_T difficulty = get_difficulty();
+    snake_cell_T *snake = init_snake(INIT_SNAKE_X, INIT_SNAKE_Y);
+    int ret_update_snake = update_snake_pos(snake, SNAKE_RIGHT);
+    
+    int score_counter = 0;
+    while(42) {
+        clear_field(term);
+        
+        snake_move_T snake_direction = get_direction(score_counter);
+        ret_update_snake = update_snake_pos(snake, snake_direction);
+        
+        if(ret_update_snake == WALL)
+            break;
+        else if(ret_update_snake == FOOD_FOUND) {
+            generate_food();
+            score_counter++;
+        }
+            
+        
+        term.printf("Score:%d\r\n", score_counter);
+        print_field(term);
+        
+        score_wait(score_counter, snake_direction, difficulty);
+        light_up_diode(score_counter, difficulty);
+    }
+    
+    clear_field(term);
+    destroy_snake(snake);
+    term.printf("GAME OVER! (You score: %d)", score_counter);
+    destroy_snake(snake);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Fri Dec 13 10:14:54 2019 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/mbed_official/code/mbed/builds/65be27845400
\ No newline at end of file