Subversion Repositories Code-Repo

Rev

Rev 276 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

#include "defines.h"
#include "CONTROLLERS.h"
#include "SNAKE.h"
#include "TIMER4.h"

static SNAKE_DATA *snake_data_p;
static uint32_t rand_value __attribute__((persistent));

void Snake_Init(SNAKE_DATA *data) {
    snake_data_p = data;

    // Set starting point
    snake_data_p->body[0] = (SNAKE_POINT){0,0,7};

    snake_data_p->pos_head = 0;
    snake_data_p->pos_tail = 0;
    snake_data_p->length = 1;
    snake_data_p->level = 0;
    snake_data_p->delay = SNAKE_MAXIMUM_DELAY;

    // Initialize the starting direction
    snake_data_p->direction = (SNAKE_POINT){1,0,7};
    snake_data_p->last_direction = 0x08;

    srand(rand_value);

    // Generate a starting location for the candy
    snake_data_p->candy_loc = Snake_Generate_Candy();

    // Draw the snake (head)
    Cube_Clear();
    uint32_t index = snake_data_p->pos_head;
    Cube_Set_Pixel(snake_data_p->body[index].z, snake_data_p->body[index].x, snake_data_p->body[index].y, SNAKE_HEAD_COLOR);
    while (index != snake_data_p->pos_tail) {
        if (snake_data_p->length > 1) {
            index = (index == 0) ? CUBE_PIXELS - 1 : index - 1;
            Cube_Set_Pixel(snake_data_p->body[index].z, snake_data_p->body[index].x, snake_data_p->body[index].y, SNAKE_BODY_COLOR);
        }
    }
}

void Snake_Main(void) {
    // Main function, loops and delays while updating the frame every x milliseconds

    // Ensure that a controller is connected before starting
    while(!Controller_Get_Connected()) {
        Delay_MS(100);
        Controller_Poll_Connected();
    }

    // Set the first controller as active and indicate it on its LEDs
    Controller_Set_Active(0);
    Delay_MS(20);
    Controller_Set_Left_Leds(0, 0x1);
    TIMER4_Start();
    Delay_MS(1000);
    while (1) {
        // Regenerate the seed upon each update so that the candy starts somewhere new every time
        rand_value = rand();

        Snake_Update_Frame();
        Delay_MS(snake_data_p->delay);
    }
}

void Snake_Update_Direction(uint8_t controller, CTRL_BTN_STATUS value) {
    // Determine the next direction for the snake based off the last button press
    if (controller == 0) {
        snake_data_p->last_direction = value.w;
        SNAKE_POINT point = snake_data_p->body[snake_data_p->pos_head];

        if (value.BTN_L_N || value.BTN_L_E) {    // Up
            point.z = (point.z == CUBE_LAYER_COUNT - 1) ? 0 : point.z + 1;
        } else if (value.BTN_L_W || value.BTN_L_S) { // Down
            point.z = (point.z == 0) ? CUBE_LAYER_COUNT - 1 : point.z - 1;
        } else if (value.BTN_R_N) { // Forward
            point.x = (point.x == CUBE_ROW_COUNT - 1) ? 0 : point.x + 1;
        } else if (value.BTN_R_W) { // Right
            point.y = (point.y == CUBE_COLUMN_COUNT - 1) ? 0 : point.y + 1;
        } else if (value.BTN_R_S) { // Backward
            point.x = (point.x == 0) ? CUBE_ROW_COUNT - 1 : point.x - 1;
        } else if (value.BTN_R_E) { // Left
            point.y = (point.y== 0) ? CUBE_COLUMN_COUNT - 1 : point.y - 1;
        }

        snake_data_p->direction = point;
    }

    // Update the overlay with the candy location
    Cube_Overlay_Clear();
    Cube_Overlay_Set_Pixel(snake_data_p->candy_loc.z, snake_data_p->candy_loc.x, snake_data_p->candy_loc.y, SNAKE_CANDY_COLOR);
}

void Snake_Update_Frame(void) {
    uint8_t om_nom_nom = 0;

    // Check if we are moving onto a candy, if so extend body
    if (snake_data_p->direction.x == snake_data_p->candy_loc.x &&
            snake_data_p->direction.y == snake_data_p->candy_loc.y &&
            snake_data_p->direction.z == snake_data_p->candy_loc.z) {
        snake_data_p->pos_head = (snake_data_p->pos_head == CUBE_PIXELS - 1) ? 0 : snake_data_p->pos_head + 1;
        snake_data_p->body[snake_data_p->pos_head] = snake_data_p->direction;
        snake_data_p->length++;
        snake_data_p->candy_loc = Snake_Generate_Candy();
        om_nom_nom = 1;
    }

    // Check if the location that we are moving to is overlapping the body
    uint32_t pos = snake_data_p->pos_tail;
    while (pos != snake_data_p->pos_head) {
        if (snake_data_p->direction.x == snake_data_p->body[pos].x &&
                snake_data_p->direction.y == snake_data_p->body[pos].y &&
                snake_data_p->direction.z == snake_data_p->body[pos].z) {
            // Indicate the overlapping pixel, delay, then return to idle state
            Cube_Set_Pixel(snake_data_p->direction.z, snake_data_p->direction.x, snake_data_p->direction.y, SNAKE_COLLISION_COLOR);
            Delay_MS(3000);
            Cube_Overlay_Clear();
            Animation_Cube_In_Out(200, ORANGE);
            Reset_Board(BOARD_MODE_IDLE);
        }
        pos = (pos == CUBE_PIXELS - 1) ? 0 : pos + 1;
    }

    // If we didnt eat a candy, increment the frame to move the body along
    if (!om_nom_nom) {
        snake_data_p->pos_head = (snake_data_p->pos_head == CUBE_PIXELS - 1) ? 0 : snake_data_p->pos_head + 1;
        snake_data_p->pos_tail = (snake_data_p->pos_tail == CUBE_PIXELS - 1) ? 0 : snake_data_p->pos_tail + 1;
        snake_data_p->body[snake_data_p->pos_head] = snake_data_p->direction;
    }

    // Draw updated snake location
    Cube_Clear();
    uint32_t index = snake_data_p->pos_head;
    Cube_Set_Pixel(snake_data_p->body[index].z, snake_data_p->body[index].x, snake_data_p->body[index].y, SNAKE_HEAD_COLOR);
    while (index != snake_data_p->pos_tail) {
        if (snake_data_p->length > 1) {
            index = (index == 0) ? CUBE_PIXELS - 1 : index - 1;
            Cube_Set_Pixel(snake_data_p->body[index].z, snake_data_p->body[index].x, snake_data_p->body[index].y, SNAKE_BODY_COLOR);
        }
    }

    // Determine the next point to move to
    Snake_Update_Direction(0, (CTRL_BTN_STATUS)snake_data_p->last_direction);

    // If we ate a candy, delay for a bit to rest
    if (om_nom_nom) {
        // Increase the level by one
        snake_data_p->level += 1;

        TIMER4_Stop();
        Controller_Set_Middle_Leds(0, snake_data_p->level);
        if (snake_data_p->level >= 256)
            Controller_Set_Left_Leds(0, 0x9);
        TIMER4_Start();

        // Decrease the delay between frame updates by 5ms
        if (snake_data_p->delay > SNAKE_MINIMUM_DELAY)
            snake_data_p->delay -= 5;
        // Clear the watchdog timer to prevent resets in a middle of a game
        ClearWDT();
    }
}

SNAKE_POINT Snake_Generate_Candy(void) {
    // Generates a random position within the cube that doesnt overlap anything
    SNAKE_POINT ret;
    uint32_t x, y, z, brk = 0;
    while(1) {
        x = rand() % 8;
        y = rand() % 8;
        z = rand() % 8;

        if (snake_data_p->length != 1) {
            uint32_t pos = snake_data_p->pos_tail;
            uint32_t overlap = 0;
            // Iterate through the frame till we finish or find an overlap
            while (pos != snake_data_p->pos_head) {
                if (snake_data_p->body[pos].x == x &&
                        snake_data_p->body[pos].y == y &&
                        snake_data_p->body[pos].z == z) {
                    overlap = 1;
                    break;
                } else {
                    pos = (pos == CUBE_PIXELS - 1) ? 0 : pos + 1;
                }
            }
            if (!overlap)
                brk = 1;
        } else {
            uint32_t pos = snake_data_p->pos_tail;
            if (snake_data_p->body[pos].x != x &&
                    snake_data_p->body[pos].y != y &&
                    snake_data_p->body[pos].z != z) {
                brk = 1;
            }
        }

        if (brk)
            break;
    }

    ret.x = x;
    ret.y = y;
    ret.z = z;
    return ret;
}