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