#include <client_api.h>
#include <stdint.h>
#if 0
#define BLOCK_SIZE 6
#define BOARD_W 25
#define BOARD_H 22
#else
#define BLOCK_SIZE 8
#define BOARD_W 19
#define BOARD_H 16
#endif
uint8_t* board;
#define EMPTY 0
#define BLOCK 1
#define SN_RIGHT 2
#define SN_UP 3
#define SN_LEFT 4
#define SN_DOWN 5
#define FRUIT 6
#define NUM_LEVEL_FRUIT 5
#define PAD_X (BLOCK_SIZE + (SCREEN_W - BLOCK_SIZE * (BOARD_W + 1)) / 2)
#define PAD_Y 2
#define INDEX(x, y) (y * BOARD_W + x)
#define INITIAL_FILL_MS 30
#define FRUIT_FLASH 600
#define COUNTDOWN 3
#define COUNTDOWN_MS 800
int countdown = COUNTDOWN;
int countdown_timer = COUNTDOWN_MS;
int fillMS = INITIAL_FILL_MS;
int fillTimer = 0;
int tailWaitTimer = 0;
int level;
int lastGoodLevel = 0;
typedef struct
{
int x, y;
int px, py;
int fill;
} snake_t;
bool boardValid = false;
int emptyTiles = 0;
float scoreIncrease = 10;
int score = 0;
int lastPScore = 0;
int prestige = 1;
void writeUI();
snake_t tail, head;
void coords(int index, int* x, int* y)
{
*y = index / BOARD_W;
*x = index - *y * BOARD_W;
}
uint8_t snakeState(snake_t* snake)
{
if (snake->px < snake->x) return SN_RIGHT;
if (snake->px > snake->x) return SN_LEFT;
if (snake->py < snake->y) return SN_DOWN;
if (snake->py > snake->y) return SN_UP;
return EMPTY;
}
void nextBlock(int x, int y, uint8_t state, int* nx, int* ny)
{
*nx = x;
*ny = y;
switch (state)
{
case SN_RIGHT: (*nx)++; break;
case SN_LEFT: (*nx)--; break;
case SN_DOWN: (*ny)++; break;
case SN_UP: (*ny)--; break;
}
}
void prevBlock(int x, int y, uint8_t state, int* nx, int* ny)
{
*nx = x;
*ny = y;
switch (state)
{
case SN_RIGHT: (*nx)--; break;
case SN_LEFT: (*nx)++; break;
case SN_DOWN: (*ny)--; break;
case SN_UP: (*ny)++; break;
}
}
void drawSnake(int x, int y, int fill, uint8_t in_state, uint8_t fill_state, uint8_t out_state, uint8_t color);
bool spawnFruit();
bool setStart(int x, int y, uint8_t dir)
{
if (tail.x != -1)
{
printf("Board has multiple starts\n");
return false;
}
board[INDEX(x, y)] = dir;
tail.x = head.px = x;
tail.y = head.py = y;
nextBlock(x, y, dir, &head.x, &head.y);
if (board[INDEX(head.x, head.y)] != EMPTY)
{
printf("Snake start point would put the head of the snake in a wall\n");
return false;
}
board[INDEX(head.x, head.y)] = dir;
emptyTiles -= 2;
return true;
}
void clearSnake(snake_t* s)
{
s->x = -1;
s->y = -1;
s->px = -1;
s->py = -1;
s->fill = 0;
}
pbm_t* block;
int num_fruit = 0;
pbm_t** fruit;
int level_fruit[NUM_LEVEL_FRUIT];
int current_fruit = 0;
void startLevel(int l);
void reset()
{
fillMS = INITIAL_FILL_MS;
score = 0;
lastPScore = 0;
scoreIncrease = 10;
prestige = 1;
startLevel(1);
}
void loadBoard(char* path);
void startLevel(int l)
{
level = l;
char b[32];
sprintf(b, "/board%d_%d.txt", BLOCK_SIZE, level);
loadBoard(b);
writeUI();
countdown = COUNTDOWN;
countdown_timer = COUNTDOWN_MS;
}
void loadBoard(char* path)
{
d_clear();
clearSnake(&tail);
clearSnake(&head);
head.fill = 3;
tail.fill = 0;
fillTimer = 0;
tailWaitTimer = BLOCK_SIZE * 3;
boardValid = false;
current_fruit = 0;
emptyTiles = BOARD_W * BOARD_H;
int x = 0, y = 0;
size_t board_desc_len;
char* board_desc = f_contents(path, &board_desc_len);
if (!board_desc)
{
printf("Invalid %s\n", path);
goto cleanup;
}
if (!board) board = malloc(BOARD_W * BOARD_H);
if (!board)
{
printf("Couldn't alloc board\n");
goto cleanup;
}
memset(board, EMPTY, BOARD_W * BOARD_H);
for (int i = 0; i < board_desc_len; i++)
{
if (y >= BOARD_H) break;
char b = board_desc[i];
bool whitespace = isspace(b);
if (b == '\n')
{
x = 0;
y++;
continue;
}
if (x >= BOARD_W)
{
if (whitespace) continue;
while (i + 1 < board_desc_len && board_desc[i + 1] != '\n') i++;
printf("y=%d has too many characters\n", y);
continue;
}
if (b == ' ' || b == '0')
{
x++;
continue;
}
if (whitespace) continue;
switch (b)
{
case '^': if (!setStart(x, y, SN_UP)) goto cleanup; break;
case '>': if (!setStart(x, y, SN_RIGHT)) goto cleanup; break;
case '<': if (!setStart(x, y, SN_LEFT)) goto cleanup; break;
case 'v': if (!setStart(x, y, SN_DOWN)) goto cleanup; break;
default:
{
board[INDEX(x, y)] = BLOCK;
emptyTiles--;
}
break;
}
x++;
}
if (tail.x == -1)
{
printf("Board didn't include start position\n");
goto cleanup;
}
boardValid = true;
for (int y = 0; y < BOARD_H; y++)
{
for (int x = 0; x < BOARD_W; x++)
{
if (board[INDEX(x, y)] == BLOCK)
{
d_pbm(PAD_X + x * BLOCK_SIZE, PAD_Y + y * BLOCK_SIZE, block, 0, 0, 0, 0, BLACK, WHITE, R_NONE);
}
}
}
uint8_t startState = board[INDEX(head.x, head.y)];
drawSnake(tail.x, tail.y, BLOCK_SIZE - 1, 0, startState, startState, BLACK);
drawSnake(tail.x, tail.y, tail.fill, 0, startState, startState, WHITE);
drawSnake(head.x, head.y, head.fill, startState, startState, startState, BLACK);
for (int i = 0; i < NUM_LEVEL_FRUIT; i++)
{
level_fruit[i] = random(0, num_fruit);
}
spawnFruit();
cleanup:
if (board_desc) free(board_desc);
}
int fruitFlashTimer = FRUIT_FLASH;
void drawFruitUIIndex(int i, int yOrder, bool clear);
void drawFruitUI()
{
d_fillRect(0, 0, PAD_X, (BLOCK_SIZE + 2) * NUM_LEVEL_FRUIT + 10, WHITE);
for (int i = current_fruit; i < NUM_LEVEL_FRUIT; i++)
{
drawFruitUIIndex(i, i - current_fruit, false);
}
}
void drawFruitUIIndex(int i, int yOrder, bool clear)
{
int x = (PAD_X - BLOCK_SIZE) / 2;
int y = 3 + (BLOCK_SIZE + 2) * yOrder;
if (clear) d_fillRect(x, y, BLOCK_SIZE, BLOCK_SIZE, WHITE);
if (yOrder != 0 || fruitFlashTimer > 0)
{
d_pbm(x, y, fruit[level_fruit[i]], 0, 0, 0, 0, BLACK, WHITE, R_NONE);
}
}
bool spawnFruit()
{
if (emptyTiles <= 0) return false;
int fruit_type = level_fruit[current_fruit];
long r = random(0, emptyTiles);
int c = -1;
int i = -1;
while (c < r)
{
i++;
while (board[i] != EMPTY) i++;
c++;
}
board[i] = FRUIT;
int x, y;
coords(i, &x, &y);
printf("Fruit_%d at %d, index %d: %dx%d\n", fruit_type, r, i, x, y);
d_pbm(PAD_X + x * BLOCK_SIZE, PAD_Y + y * BLOCK_SIZE, fruit[fruit_type], 0, 0, 0, 0, BLACK, WHITE, R_NONE);
emptyTiles--;
drawFruitUI();
return true;
}
void clearBlock(int x, int y)
{
d_fillRect(PAD_X + x * BLOCK_SIZE, PAD_Y + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE, WHITE);
}
void drawSnake(int x, int y, int fill, uint8_t in_state, uint8_t fill_state, uint8_t out_state, uint8_t color)
{
int tlX = PAD_X + x * BLOCK_SIZE;
int tlY = PAD_Y + y * BLOCK_SIZE;
if (in_state != EMPTY)
{
if (in_state == SN_RIGHT) d_fillRect(tlX, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (in_state == SN_DOWN) d_fillRect(tlX + 1, tlY, BLOCK_SIZE - 2, 1, color);
if (in_state == SN_LEFT) d_fillRect(tlX + BLOCK_SIZE - 1, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (in_state == SN_UP) d_fillRect(tlX + 1, tlY + BLOCK_SIZE - 1, BLOCK_SIZE - 2, 1, color);
}
for (int i = 0; i < min(fill, BLOCK_SIZE - 2); i++)
{
if (fill_state == SN_RIGHT) d_fillRect(tlX + i + 1, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (fill_state == SN_DOWN) d_fillRect(tlX + 1, tlY + i + 1, BLOCK_SIZE - 2, 1, color);
if (fill_state == SN_LEFT) d_fillRect(tlX + BLOCK_SIZE - 1 - i - 1, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (fill_state == SN_UP) d_fillRect(tlX + 1, tlY + BLOCK_SIZE - 1 - i - 1, BLOCK_SIZE - 2, 1, color);
}
if (fill == BLOCK_SIZE - 1 && out_state != EMPTY)
{
if (out_state == SN_LEFT) d_fillRect(tlX, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (out_state == SN_UP) d_fillRect(tlX + 1, tlY, BLOCK_SIZE - 2, 1, color);
if (out_state == SN_RIGHT) d_fillRect(tlX + BLOCK_SIZE - 1, tlY + 1, 1, BLOCK_SIZE - 2, color);
if (out_state == SN_DOWN) d_fillRect(tlX + 1, tlY + BLOCK_SIZE - 1, BLOCK_SIZE - 2, 1, color);
}
}
int setup(int arg)
{
char p[32];
sprintf(p, "/block%d.pbm", BLOCK_SIZE);
printf("PATH: %s\n", p);
block = f_bitmap(p);
if (!block) return 1;
sprintf(p, "/fruit%d_%d.pbm", BLOCK_SIZE, num_fruit + 1);
while (f_exists(p))
{
num_fruit++;
sprintf(p, "/fruit%d_%d.pbm", BLOCK_SIZE, num_fruit + 1);
}
if (!num_fruit)
{
printf("Found no fruit graphics\n");
return 1;
}
fruit = malloc(sizeof(pbm_t*) * num_fruit);
for (int i = 0; i < num_fruit; i++)
{
sprintf(p, "/fruit%d_%d.pbm", BLOCK_SIZE, i + 1);
fruit[i] = f_bitmap(p);
if (!fruit[i])
{
printf("Failed to load %s\n", p);
return 1;
}
}
printf("Found %d fruit graphics\n", num_fruit);
reset();
if (!boardValid) return 1;
return 0;
}
void writeUI()
{
int y = SCREEN_H - BLOCK_SIZE * 2 + 3;
d_fillRect(0, y, SCREEN_W, BLOCK_SIZE * 2, WHITE);
d_text(8, y, BLACK, "SCORE: %d", score);
d_textR(SCREEN_W - 8, y, BLACK, "P%d", prestige);
}
int loop(int ms)
{
if (!boardValid) return 1;
if (countdown > 0)
{
}
uint8_t headState = board[INDEX(head.x, head.y)];
uint8_t tailState = board[INDEX(tail.x, tail.y)];
fillTimer += ms;
while (fillTimer > fillMS)
{
fillTimer -= fillMS;
// move tail
if (tailWaitTimer > 0)
{
tailWaitTimer--;
}
else
{
tail.fill++;
}
if (tail.fill == BLOCK_SIZE)
{
clearBlock(tail.x, tail.y);
board[INDEX(tail.x, tail.y)] = EMPTY;
tail.px = tail.x;
tail.py = tail.y;
nextBlock(tail.px, tail.py, tailState, &tail.x, &tail.y);
tail.fill = 0;
emptyTiles++;
}
drawSnake(tail.x, tail.y, tail.fill, snakeState(&tail), tailState, tailState, WHITE);
// move head
head.fill++;
uint8_t prevHeadMovement = snakeState(&head);
if (head.fill == BLOCK_SIZE)
{
// redraw the last block in case we changed direction
clearBlock(head.x, head.y);
drawSnake(head.x, head.y, BLOCK_SIZE - 1, prevHeadMovement, prevHeadMovement, headState, BLACK);
head.fill = 0;
head.px = head.x;
head.py = head.y;
nextBlock(head.px, head.py, headState, &head.x, &head.y);
bool newFruit = false;
if (board[INDEX(head.x, head.y)] == FRUIT)
{
score += (int)roundf(scoreIncrease);
scoreIncrease *= (1.0f + 0.1f * prestige);
writeUI();
// don't do emptyTiles-- here, this tile wasn't empty
newFruit = true;
tailWaitTimer = BLOCK_SIZE * 2;
}
else if (board[INDEX(head.x, head.y)] != EMPTY)
{
reset();
return 0;
}
else
{
emptyTiles--;
}
board[INDEX(head.x, head.y)] = headState;
if (newFruit)
{
if (current_fruit == NUM_LEVEL_FRUIT - 1)
{
startLevel(level + 1);
if (boardValid)
{
lastGoodLevel = level;
}
else
{
startLevel(lastGoodLevel);
}
return 0;
}
current_fruit++;
spawnFruit();
}
//printf("%d %d %d\n", headX, headY, headState);
//
prevHeadMovement = snakeState(&head);
}
clearBlock(head.x, head.y);
drawSnake(head.x, head.y, head.fill, prevHeadMovement, prevHeadMovement, headState, BLACK);
}
if (button_down(DPAD_RIGHT) && head.px <= head.x) board[INDEX(head.x, head.y)] = SN_RIGHT;
if (button_down(DPAD_LEFT) && head.px >= head.x) board[INDEX(head.x, head.y)] = SN_LEFT;
if (button_down(DPAD_UP) && head.py >= head.y) board[INDEX(head.x, head.y)] = SN_UP;
if (button_down(DPAD_DOWN) && head.py <= head.y) board[INDEX(head.x, head.y)] = SN_DOWN;
//printf("%d %d %d %d\n", button_down(DPAD_RIGHT), button_down(DPAD_LEFT), button_down(DPAD_UP), button_down(DPAD_DOWN));
if (button_pressed(BUTTON_A) && fillMS > 5)
{
prestige++;
fillMS = max(5, fillMS - 5);
writeUI();
scoreIncrease = fmax(10, (score - lastPScore) * 0.1f);
lastPScore = score;
int nx, ny;
nextBlock(tail.x, tail.y, board[INDEX(tail.x, tail.y)], &nx, &ny);
while (nx != head.x || ny != head.y)
{
board[INDEX(tail.x, tail.y)] = EMPTY;
printf("%d %d %d %d %d %d %d\n", tail.x, tail.y, nx, ny, board[INDEX(nx, ny)], head.x, head.y);
clearBlock(tail.x, tail.y);
tail.px = tail.x;
tail.py = tail.y;
tail.x = nx;
tail.y = ny;
nextBlock(tail.x, tail.y, board[INDEX(tail.x, tail.y)], &nx, &ny);
emptyTiles++;
}
tail.fill = 0;
uint8_t tailState = board[INDEX(tail.x, tail.y)];
drawSnake(tail.x, tail.y, BLOCK_SIZE - 1, snakeState(&tail), tailState, tailState, BLACK);
drawSnake(tail.x, tail.y, tail.fill, snakeState(&tail), tailState, tailState, WHITE);
tailWaitTimer = BLOCK_SIZE * 3;
}
int oldFruitFlash = fruitFlashTimer;
fruitFlashTimer -= ms;
if (fruitFlashTimer <= -FRUIT_FLASH)
{
fruitFlashTimer = FRUIT_FLASH;
}
if ((oldFruitFlash < 0) != (fruitFlashTimer < 0))
{
drawFruitUIIndex(current_fruit, 0, true);
}
return 0;
}