ECE 2036 Project

Dependencies:   mbed wave_player 4DGL-uLCD-SE

game.cpp

Committer:
abraha2d
Date:
2019-11-21
Revision:
2:2042f29de6b7
Parent:
1:c18c231cb299

File content as of revision 2:2042f29de6b7:

#include "game.h"

#include "globals.h"
#include "physics.h"
#include "wall.h"


/** Erases the ball from the screen by drawing over it with the background color. */
void erase_ball(Ball* ball)
{
    // Draw background color over previously drawn ball location
    uLCD.filled_circle(ball->x, ball->y, radius, BLACK);
}

/** Draws the ball on the screen at the updated location (according to the state) */
void draw_ball(Ball* ball, Physics* state)
{
    // Draw ball in its updated location
    uLCD.filled_circle(state->px, state->py, radius, WHITE);
    // Save that updated ball position for later erasing
    ball->x = state->px;
    ball->y = state->py;
}

/** Draws a hole on the screen */
void draw_hole(Hole* hole)
{
    if (hole->should_draw) {
        // Draw hole in its location
        uLCD.filled_circle(hole->x, hole->y, radius - 1, RED);
        hole->should_draw = 0;
    }
}

/** Checks whether the ball has hit a hole */
int do_hole(Hole* hole, Physics* curr)
{
    double dist = sqrt(pow(curr->px - hole->x, 2) + pow(curr->py - hole->y, 2));
    if (dist < radius * 2) {
        hole->should_draw = 1;
    }
    if (dist < radius) {
        return 2;
    }
    return 0;
}

/** Draws the goal on the screen */
void draw_goal(Goal* goal)
{
    if (goal->should_draw) {
        // Draw goal in its location
        uLCD.filled_circle(goal->x, goal->y, radius + 1, GREEN);
        goal->should_draw = 0;
    }
}

/** Checks whether the ball has hit the goal */
int do_goal(Goal* goal, Physics* curr)
{
    double dist = sqrt(pow(curr->px - goal->x, 2) + pow(curr->py - goal->y, 2));
    if (dist < radius * 2) {
        goal->should_draw = 1;
    }
    if (dist < radius) {
        return 1;
    }
    return 0;
}

/** Reads inputs to the game, such as accelerometer and buttons */
GameInputs read_inputs()
{
    GameInputs inputs = {0};

    // Get acceleration vector from accelerometer
    acc.readXYZGravity(&inputs.ax, &inputs.ay, &inputs.az);

    return inputs;
}

int lrpb = left_pb || right_pb;
int lpb = 1;
int upb = up_pb;
int dpb = down_pb;

int update_game(DLinkedList* arena, Physics* curr, GameInputs inputs, DLinkedList* checkpoints, float delta)
{

    // Cheat codes!
    if (left_pb == 0 && right_pb == 0 && lrpb == 1) {
        lpb = 1;
        uLCD.cls();
        uLCD.printf("\n\n\n\n    Cheat code!");
        wait(0.5);
        return 30;
    }

    // Help!
    if (left_pb == 1 && lpb == 0) {
        lpb = 1;
        return 32;
    }

    // Save/restore state
    if (up_pb == 0 && upb == 1) {
        greenLED = 1;
        Physics* saveState = (Physics*) malloc(sizeof(Physics));
        *saveState = *curr;
        insertTail(checkpoints, saveState);
    }
    if (down_pb == 0 && dpb == 1) {
        Physics* restoreState = (Physics*) getTail(checkpoints);
        if (restoreState != NULL) {
            *curr = *restoreState;
        }
    }

    // Save previous inputs
    lrpb = left_pb || right_pb;
    lpb = left_pb;
    upb = up_pb;
    dpb = down_pb;

    ///////////////////////////////
    // Prepare for physics update
    ///////////////////////////////
    // Make a copy of the current state for modification
    Physics next = *curr;

    // No acceleration unless the ArenaElements apply them. (Newton's 1st law)
    next.ax = next.ay = 0.0;


    // Loop over all arena elements
    ArenaElement* elem = (ArenaElement*)getHead(arena);
    do {
        switch(elem->type) {
            case WALL:
                do_wall(&next, curr, (Wall*) elem, delta);
                break;
            case BALL:
                next.ax += inputs.ay * ACCELERATION;
                next.ay += inputs.ax * ACCELERATION;
                forward_euler(&next, delta);
                break;
            case HOLE:
                if (do_hole((Hole*) elem, curr)) {
                    return 31;
                }
                break;
            case GOAL:
                if (do_goal((Goal*) elem, curr)) {
                    return 1;
                }
                break;
            default:
                break;
        }
    } while(elem = (ArenaElement*)getNext(arena));

    // Last thing! Update state, so it will be saved for the next iteration.
    *curr = next;

    // Zero means we aren't done yet
    // 1 means we've hit the goal
    // 31 means we've hit a pothole
    // 32 means we need help!
    // 30 means we used the cheat code
    return 0;
}

int run_game(DLinkedList* arena, Physics* state)
{
    // Initialize game loop timers
    int tick, phys_tick, draw_tick;
    Timer timer;
    timer.start();
    tick = timer.read_ms();
    phys_tick = tick;
    draw_tick = tick;

    // Initialize debug counters
    int count = 0;
    int count2 = 0;

    // Initialize checkpoint list
    DLinkedList* checkpoints = create_dlinkedlist();

    // Initial draw of the game
    uLCD.background_color(BLACK);
    uLCD.cls();

    // Save start time
    set_time(0);
    time_t startTime = time(NULL);

    ///////////////////
    // Main game loop
    ///////////////////
    while(1) {
        // Read timer to determine how long the last loop took
        tick = timer.read_ms();

        ///////////////////
        // Physics Update
        ///////////////////
        // Rate limit: 1 ms
        int diff = tick - phys_tick;
        if (diff < 1) continue;
        phys_tick = tick;

        // Compute elapsed time in milliseconds
        float delta = diff*1e-3;

        // Read inputs
        GameInputs inputs = read_inputs();

        // Update game state
        int done = update_game(arena, state, inputs, checkpoints, delta);
        if (done == 1) return 30 - (time(NULL) - startTime);
        if (done != 0) return done;

        // Debug: Count physics updates
        count2++;

        //////////////////
        // Render update
        //////////////////
        // Rate limit: 40ms
        if(tick - draw_tick < 40) continue;
        draw_tick = tick;

        // Erase moving stuff
        ArenaElement* elem = (ArenaElement*)getHead(arena);
        do {
            switch(elem->type) {
                case BALL:
                    erase_ball((Ball*) elem);
                    break;
                default:
                    break;
            }
        } while(elem = (ArenaElement*)getNext(arena));

        // Draw everything
        elem = (ArenaElement*)getHead(arena);
        do {
            switch(elem->type) {
                case WALL:
                    draw_wall((Wall*) elem);
                    break;
                case BALL:
                    draw_ball((Ball*) elem, state);
                    break;
                case HOLE:
                    draw_hole((Hole*) elem);
                    break;
                case GOAL:
                    draw_goal((Goal*) elem);
                    break;
                default:
                    break;
            }
        } while(elem = (ArenaElement*)getNext(arena));

        uLCD.line(0, 0, 127, 0, 0);
        uLCD.line(0, 0, (30 - (time(NULL) - startTime)) * (127/30), 0, GREEN);

        ///////////////
        // Debug info
        ///////////////
        // Displays rate info in the top corner
        //  First number is total time to update and render this frame
        //  Second number is how many physics iterations between drawing frames
        //  Only displayed every 10th render update (roughly 2.5 Hz)
        // TODO: Take this out before you turn your code in!
        //if ((count = (count+1)%10) == 0) {
        //    uLCD.locate(0, 0);
        //    uLCD.printf("%d %d \r\n", timer.read_ms()-tick, count2);
        //}

        // Reset physics iteration counter after every render update
        count2 = 0;
    }
}