SNAKE GAME
Dependencies: mbed wave_player 4DGL-uLCD-SE MMA8452
Diff: main.cpp
- Revision:
- 0:24041b847eb5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Wed Nov 25 04:25:25 2020 +0000 @@ -0,0 +1,698 @@ +//================================================================= +// The main program file. +// +// Copyright 2020 Georgia Tech. All rights reserved. +// The materials provided by the instructor in this course are for +// the use of the students currently enrolled in the course. +// Copyrighted course materials may not be further disseminated. +// This file must not be made publicly available anywhere. +//================================================================== + +// Project includes +#include "globals.h" +#include "hardware.h" +#include "map.h" +#include "graphics.h" +#include "snake.h" +#include "mbed.h" +#include "Speaker.h" +//#include "wave_player.h" +#include "SDFileSystem.h" +#include <math.h> +#include<stdio.h> + + + + +#define CITY_HIT_MARGIN 1 +#define CITY_UPPER_BOUND (SIZE_Y-(LANDSCAPE_HEIGHT+MAX_BUILDING_HEIGHT)) +int go_right(int x, int y); +int go_left(int x, int y); +int go_up(int x, int y); +int go_down(int x, int y); +// Helper function declarations +void playSound(char* wav); +void makenoise(); +/** + * The main game state. Must include snake locations and previous locations for + * drawing to work properly. Other items can be added as needed. + */ + +/** + * Given the game inputs, determine what kind of update needs to happen. + * Possbile return values are defined below. + */ +Snake snake; +Speaker mySpeaker(p25); //define mySpeaker and pin +// Function prototypes + +/** + * Given the game inputs, determine what kind of update needs to happen. + * Possible return values are defined below. + */ +#define NO_RESULT 0 +#define NO_ACTION 0 +#define ACTION_BUTTON 1 +#define MENU_BUTTON 2 +#define GO_LEFT 3 +#define GO_RIGHT 4 +#define GO_UP 5 +#define GO_DOWN 6 +#define GAME_OVER 7 +#define FULL_DRAW 8 +#define WON 9 +#define FOODN 10 +#define FOODS 11 +#define FOODW 12 +#define FOODE 13 + +// Get Actions from User (push buttons & accelerometer) +// Based on push button and accelerometer inputs, determine which action +// needs to be performed (may be no action). +int get_action(GameInputs inputs) +{ + MapItem* N=get_north(snake.head_x,snake.head_y); + MapItem* S=get_south(snake.head_x,snake.head_y); + MapItem* W=get_east(snake.head_x, snake.head_y); + MapItem* E=get_west(snake.head_x, snake.head_y); + + if (button3 == 0) { //pause game + while(button3 == 0) + {} + } + + if (button2 == 1) { // keep living + if (snake.invincTimer == 0) + snake.invincible = !snake.invincible; + else + snake.invincible = 1; + } + if ((E->type==WALL || N->type==WALL || S->type==WALL ||W->type==WALL) && !snake.invincible) return GAME_OVER; + if (inputs.ay >= 0.2) return GO_DOWN; + if (inputs.ay < -0.2) return GO_UP; + if (inputs.ax < -0.2) return GO_RIGHT; + if (inputs.ax >= 0.2) return GO_LEFT; + else return NO_ACTION; +} + +void checkCollisions() { + //Check Objects here + if (get_here(snake.head_x,snake.head_y)->type == RANDOM) { + makenoise(); + void* d = get_here(snake.head_x,snake.head_y)->data; + if (((int)d % 4) + 13 == 13) + snake.pointLockTime = 8000; + if (((int)d % 4) + 13 == 14) + snake.speedupTime = 16000; + if (((int)d % 4) + 13 == 15) + snake.slowdownTime = 10000; + if (((int)d % 4) + 13 == 16) { + if (snake.length > 1) { + map_erase(snake.locations[snake.length - 2].x, snake.locations[snake.length - 2].y); + map_erase(snake.locations[snake.length - 1].x, snake.locations[snake.length - 1].y); + snake.length -= 2; + } + } + else + snake.pointLockTime = 10000; + return; + } + + if (get_here(snake.head_x,snake.head_y)->type == INVINC) { + makenoise(); + snake.invincTimer = 10000; + snake.invincible = 1; + } + + //Check Goodie + if (get_here(snake.head_x,snake.head_y)->type == GOODIE) { + // Speaker + makenoise(); + snake.length++; + if (snake.pointLockTime == 0) + snake.score++; + } + + if (get_here(snake.head_x,snake.head_y)->type == INCLENGTH) { + // Speaker + makenoise(); + snake.length += 6; + } + + if (get_here(snake.head_x,snake.head_y)->type == MOVING) { + makenoise(); + snake.score += 5; //hit moving will add 5 score + } + + if (get_here(snake.head_x,snake.head_y)->type == POISON) { + makenoise(); + snake.pointLockTime += 8000; //8 seccong + } + + if (get_here(snake.head_x,snake.head_y)->type == SPEEDUP) { + makenoise(); + snake.speedupTime += 16000; //16 sec + } + + if (get_here(snake.head_x,snake.head_y)->type == SLOWDOWN) { + makenoise(); + snake.slowdownTime += 10000; //10 sec + } + + if (get_here(snake.head_x,snake.head_y)->type == DECLENGTH) { + makenoise(); + if (snake.length > 1) { + map_erase(snake.locations[snake.length - 2].x, snake.locations[snake.length - 2].y); + map_erase(snake.locations[snake.length - 1].x, snake.locations[snake.length - 1].y); + snake.length -= 2; + } + + } +} + +/** + * Update the game state based on the user action. For example, if the user + * requests GO_UP, then this function should determine if that is possible by + * consulting the map, and update the snake position accordingly. + * + * Return values are defined below. FULL_DRAW indicates that for this frame, + * draw_game should not optimize drawing and should draw every tile, even if + * the snake has not moved. + */ +int update_game(int action) +{ + + snake.head_px = snake.head_x; + snake.head_py = snake.head_y; + + int oldx = snake.head_px; + int oldy = snake.head_py; + + map_erase(oldx, oldy); + + switch(action) { + case GO_UP: + snake.head_y -= 1; + + checkCollisions(); + //Check un-walkable object + if (get_here(snake.head_x, snake.head_y)->type != SNAKE_HEAD && + get_here(snake.head_x, snake.head_y)->walkable == 0 + && !snake.invincible) { + action = GAME_OVER; + break; + } + //Add snake head now + add_snake_head(snake.head_x, snake.head_y); + //Check max length + if (snake.length == SNAKE_MAX_LENGTH && !snake.invincible) { + action = GAME_OVER; + break; + } + for (int i = 0; i < snake.length; i++) { + map_erase(oldx, oldy); + int tempy = snake.locations[i].y; + snake.locations[i].y = oldy; + + int tempx = snake.locations[i].x; + snake.locations[i].x = oldx; + + oldy = tempy; + oldx = tempx; + map_erase(oldx, oldy); + + if (i < snake.length - 1) + add_snake_body(snake.locations[i].x, snake.locations[i].y); + else + add_snake_tail(snake.locations[i].x, snake.locations[i].y); + } + return ACTION_BUTTON; + case GO_LEFT: + snake.head_x -= 1; + + checkCollisions(); + + //Check un-walkable object + if (get_here(snake.head_x, snake.head_y)->type != SNAKE_HEAD && + get_here(snake.head_x, snake.head_y)->walkable == 0 + && !snake.invincible) { + action = GAME_OVER; + break; + } + + add_snake_head(snake.head_x, snake.head_y); + //Check max length + if (snake.length == SNAKE_MAX_LENGTH && !snake.invincible) { + action = GAME_OVER; + break; + } + for (int i = 0; i < snake.length; i++) { + map_erase(oldx, oldy); + int tempy = snake.locations[i].y; + snake.locations[i].y = oldy; + + int tempx = snake.locations[i].x; + snake.locations[i].x = oldx; + + oldy = tempy; + oldx = tempx; + map_erase(oldx, oldy); + + if (i < snake.length - 1) + add_snake_body(snake.locations[i].x, snake.locations[i].y); + else + add_snake_tail(snake.locations[i].x, snake.locations[i].y); + } + return ACTION_BUTTON; + case GO_DOWN: + snake.head_y += 1; + + checkCollisions(); + + //Check un-walkable object + if (get_here(snake.head_x, snake.head_y)->type != SNAKE_HEAD && + get_here(snake.head_x, snake.head_y)->walkable == 0 + && !snake.invincible) { + action = GAME_OVER; + break; + } + + add_snake_head(snake.head_x, snake.head_y); + //Check max length + if (snake.length == SNAKE_MAX_LENGTH && !snake.invincible) { + action = GAME_OVER; + break; + } + for (int i = 0; i < snake.length; i++) { + int tempy = snake.locations[i].y; + snake.locations[i].y = oldy; + + int tempx = snake.locations[i].x; + snake.locations[i].x = oldx; + + oldy = tempy; + oldx = tempx; + map_erase(oldx, oldy); + + if (i < snake.length - 1) + add_snake_body(snake.locations[i].x, snake.locations[i].y); + else + add_snake_tail(snake.locations[i].x, snake.locations[i].y); + } + return ACTION_BUTTON; + + case GO_RIGHT: + snake.head_x += 1; + + checkCollisions(); + + //Over if hit un-walkable object or not invinvible + if (get_here(snake.head_x, snake.head_y)->type != SNAKE_HEAD && + get_here(snake.head_x, snake.head_y)->walkable == 0 + && !snake.invincible) { + action = GAME_OVER; + break; + } + + add_snake_head(snake.head_x, snake.head_y); + //Check max length + if (snake.length == SNAKE_MAX_LENGTH && !snake.invincible) { + action = GAME_OVER; + break; + } + for (int i = 0; i < snake.length; i++) { + int tempy = snake.locations[i].y; + snake.locations[i].y = oldy; + + int tempx = snake.locations[i].x; + snake.locations[i].x = oldx; + + oldy = tempy; + oldx = tempx; + map_erase(oldx, oldy); + + if (i < snake.length - 1) + add_snake_body(snake.locations[i].x, snake.locations[i].y); + else + add_snake_tail(snake.locations[i].x, snake.locations[i].y); + } + return ACTION_BUTTON; + + case GAME_OVER: + {uLCD.color(RED); + uLCD.cls(); + uLCD.text_width(3); + uLCD.text_height(3); + uLCD.printf("GAME\n OVER"); + uLCD.color(GREEN); + uLCD.text_width(2); + uLCD.text_height(2); + uLCD.printf("\n\n\n\n Score %d", snake.score); + uLCD.color(GREEN);} + while(1 == 1); + } + + if (action == GAME_OVER) { + uLCD.color(RED); + uLCD.cls(); + uLCD.text_width(3); + uLCD.text_height(3); + uLCD.printf("GAME\n OVER"); + uLCD.color(GREEN); + uLCD.text_width(2); + uLCD.text_height(2); + uLCD.printf("\n\n\n\n Score %d", snake.score); + uLCD.color(GREEN); + while(1 == 1); + } + + return NO_RESULT; + +} + +/** + * Draw the upper status bar. + */ +void draw_upper_status() +{ + uLCD.line(0, 9, 127, 9, GREEN); + +} + +/** + * Draw the lower status bar. + */ +void draw_lower_status() +{ + uLCD.line(0, 118, 127, 118, GREEN); +} + +/** + * Draw the border for the map. + */ +void draw_border() +{ + uLCD.filled_rectangle(0, 9, 127, 14, WHITE); // Top + uLCD.filled_rectangle(0, 13, 2, 114, WHITE); // Left + uLCD.filled_rectangle(0, 114, 127, 117, WHITE); // Bottom + uLCD.filled_rectangle(124, 14, 127, 117, WHITE); // Right +} + +/** + * Entry point for frame drawing. This should be called once per iteration of + * the game loop. This draws all tiles on the screen, followed by the status + * bars. Unless init is nonzero, this function will optimize drawing by only + * drawing tiles that have changed from the previous frame. + */ +void draw_game(int draw_option) +{ + uLCD.locate(0, 0); + uLCD.text_width(1.5); + uLCD.text_height(1.5); + uLCD.color(RED); + uLCD.printf("Pos:%d %d", snake.head_x, snake.head_y); + + uLCD.locate(10, 0); + uLCD.text_width(1.5); + uLCD.text_height(1.5); + //uLCD.printf("Loc: %d,%d ", snake.head_x, snake.head_y); + uLCD.color(RED); + uLCD.printf("Score:%d", snake.score); + + + // Draw game border first + if(draw_option == FULL_DRAW) { + draw_border(); + int u = 58; + int v = 56; + draw_snake_head(u, v); + draw_snake_body(u-11, v); + draw_snake_tail(u-22, v); + int i = 1; + for (; i < snake.length; i++) { + int x = u - (11 * (snake.head_x - snake.locations[i].x)); + int y = v - (11 * (snake.head_y - snake.locations[i].y)); + if (i < snake.length - 1) { + add_snake_body(snake.locations[i].x, snake.locations[i].y); + draw_snake_body(x, y); + } + else { + add_snake_tail(snake.locations[i].x, snake.locations[i].y); + draw_snake_tail(x, y); + } + } + } + + // Iterate over all visible map tiles + for (int i = -5; i <= 5; i++) { // Iterate over columns of tiles + for (int j = -4; j <= 4; j++) { // Iterate over one column of tiles + // Here, we have a given (i,j) + // Compute the current map (x,y) of this tile + int x = i + snake.head_x; + int y = j + snake.head_y; + + // Compute the previous map (px, py) of this tile + int px = i + snake.head_px; + int py = j + snake.head_py; + + // Compute u,v coordinates for drawing + int u = (i+5)*11 + 3; + int v = (j+4)*11 + 15; + // Figure out what to draw + DrawFunc draw = NULL; + if (x >= 0 && y >= 0 && x < map_width() && y < map_height()) { // Current (i,j) in the map + MapItem* curr_item = get_here(x, y); + MapItem* prev_item = get_here(px, py); + if (draw_option || curr_item != prev_item) { // Only draw if they're different + if (curr_item) { // There's something here! Draw it + draw = curr_item->draw; + } else { // There used to be something, but now there isn't + draw = draw_nothing; + } + } else if (curr_item && curr_item->type == CLEAR) { + // This is a special case for erasing things like doors. + draw = curr_item->draw; // i.e. draw_nothing + } + } else if (draw_option) { // If doing a full draw, but we're out of bounds, draw the walls. + draw = draw_wall; + } + + // Actually draw the tile + if (draw) draw(u, v); + } + } + + // Draw status bars + draw_upper_status(); + draw_lower_status(); +} + +/** + * Initialize the main world map. Add walls around the edges, interior chambers, + * and plants in the background so you can see motion. + */ +void init_main_map() +{ + Timer t; + t.start(); + Map* map = set_active_map(0); + + pc.printf("plants\r\n"); + + pc.printf("Adding walls!\r\n"); + add_wall(0, 0, HORIZONTAL, map_width()); + add_wall(0, map_height()-1, HORIZONTAL, map_width()); + add_wall(0, 0, VERTICAL, map_height()); + add_wall(map_width()-1, 0, VERTICAL, map_height()); + pc.printf("Walls done!\r\n"); + + add_snake_head(snake.locations[0].x, snake.locations[0].y); + add_snake_body(snake.locations[1].x, snake.locations[1].y); + add_snake_tail(snake.locations[2].x, snake.locations[2].y); + + pc.printf("Add extra chamber\r\n"); + add_wall(30, 0, VERTICAL, 10); + add_wall(30, 10, HORIZONTAL, 10); + add_wall(39, 0, VERTICAL, 10); + pc.printf("Added!\r\n"); + + //ADD POISON, ANY OTHER BUFFS/DEBUFFS HERE!!******************************************************* + //Buffs: Decrement length, Slow (slows time) + //Debuffs: Poison (prevents gaining points for certain time), Speedup thing (literally just 1.5x time or something), + //Extra: Random, Strength (prevent death once or something), Pause button (just pause time), Moving object, multiple lives + //Also: Button or sprite to invert controls + + srand(time(0)); // reset seedtime + + for(int i = map_width() + 4; i < map_area(); i += 29) { // debuff + add_poison(i % map_width() + 4, i / map_width()); + } + + for (int i=0; i<3;i++){ + add_slowdown(rand()%28+1, rand()%49); //buff + add_slowdown(rand()%9+40, rand()%49); + add_decrease_length(rand()%28+1, rand()%49); //buff + add_decrease_length(rand()%9+40, rand()%49); + + add_speedup(rand()%28+1, rand()%49); //debuff + add_speedup(rand()%9+40, rand()%49); + + add_random(rand()%9+40, rand()%49, t.read_ms()); //extra randon + add_random(rand()%28+1, rand()%49, t.read_ms()); + + add_moving(rand()%28+1, rand()%49); + add_moving(rand()%9+40, rand()%49); + + add_inclength(rand()%28+1, rand()%49); + add_inclength(rand()%9+40, rand()%49); } + + add_invinc(9, 5); // mulitple life + add_slowdown(10, 5); + add_moving(8, 6); + add_inclength(5,7); + for(int i = map_width() + 3; i < map_area(); i += 39) { + //add_goodie(i % map_width(), i / map_width()); + add_goodie(rand()%map_width(), rand()% map_height()); + } + + // Add stairs to chamber (map 1) + //add_stairs(15, 5, 1, 5, 5); + +// profile_hashtable(); + print_map(); +} + +/** + * Program entry point! This is where it all begins. + * This function or all the parts of the game. Most of your + * implementation should be elsewhere - this holds the game loop, and should + * read like a road map for the rest of the code. + */ +int main() +{ + // First things first: initialize hardware + ASSERT_P(hardware_init() == ERROR_NONE, "Hardware init failed!"); + + snake_init(&snake); + // 0. Initialize the maps -- implement this function: + maps_init(); + init_main_map(); + playSound("/sd/snakeplay.wav"); + + // Initialize game state + set_active_map(0); + //snake.head_x = snake.head_y = 5 ; + + GameInputs inputs = read_inputs(); + + // Start page + int difficulty = 0; + draw_welcome(difficulty, true); + while (1) { + inputs = read_inputs(); + if (!inputs.b2) { + makenoise(); + difficulty++; + difficulty = difficulty % 3; + draw_welcome(difficulty, false); + snake.speedupTime += 16000; //16 sec + wait_ms(200); + } + if (!inputs.b1) { + // draw_game_over(1); + makenoise(); + break; + } + } + + // Initial drawing + draw_game(FULL_DRAW); + // Main game loop + + + + while(1) { + // Timer to measure game update speed + Timer t; + t.start(); + + // 1. Read inputs -- implement this function: + GameInputs inputs = read_inputs(); + + // 2. Determine action (move, act, menu, etc.) -- implement this function: + int action = get_action(inputs); + + // 3. Update game -- implement this function: + int result = update_game(action); + + // 3b. Check for game over based on result + // and if so, handle game over -- implement this. + + // 4. Draw screen -- provided: + draw_game(result); + + // Compute update time + t.stop(); + int dt = t.read_ms(); + + + if (dt % 8 == 0 || dt % 8 == 1) { + map_erase(5, 5); + add_moving(10, 10); + } + else if (dt % 8 == 2 || dt % 8 == 3) { + map_erase(10, 10); + add_moving(5, 5); + } + else if (dt % 8 == 4 || dt % 8 == 5) { + map_erase(5, 10); + add_moving(10, 5); + } + else { + map_erase(10, 5); + add_moving(5, 10); + } + + if (snake.invincTimer > 0) { + snake.invincTimer -= 100; + if (snake.invincTimer == 0) + snake.invincible = 0; + } + + // Display and wait + if (snake.speedupTime > 0 && snake.slowdownTime > 0) { + if (dt < 100) wait_ms(100 - dt); + snake.speedupTime -= 100; + snake.slowdownTime -= 100; + } + else if (snake.speedupTime > 0) { + if (dt < 80) wait_ms(80 - dt); + snake.speedupTime -= 100; + } + else if (snake.slowdownTime > 0) { + if (dt < 125) wait_ms(1000 - dt); + snake.slowdownTime -= 100; + } + else + if (dt < 100) wait_ms(100 - dt); + + //Subtract from point lock time, if it's on + if (snake.pointLockTime > 0) + snake.pointLockTime -= 100; + + } +} + +// Plays a wavfile +void playSound(char* wav) +{ + +} + +// Play speaker +void makenoise() +{ + mySpeaker.PlayNote(368.0,0.16,0.1); + mySpeaker.PlayNote(565.0,0.25,0.3); + mySpeaker.PlayNote(400.0,0.18,0.2); +}