
Snake game for IMP class.
Dependencies: mbed Terminal SerialTerminal TSI MMA8451Q
Diff: main.cpp
- 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