Snake game for IMP class.

Dependencies:   mbed Terminal SerialTerminal TSI MMA8451Q

main.cpp

Committer:
lpiwowar
Date:
2019-12-13
Revision:
0:ca75772f63a0

File content as of revision 0:ca75772f63a0:

#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);
}