SNAKE GAME

Dependencies:   mbed wave_player 4DGL-uLCD-SE MMA8452

Revision:
0:24041b847eb5
diff -r 000000000000 -r 24041b847eb5 main.cpp
--- /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);  
+}