Snake game for IMP class.

Dependencies:   mbed Terminal SerialTerminal TSI MMA8451Q

Revision:
0:ca75772f63a0
--- /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