#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; }