diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87fba9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +output_esp +output_sim +*.sw* +*.vcxproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87fba9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +output_esp +output_sim +*.sw* +*.vcxproj diff --git a/snake/Makefile b/snake/Makefile new file mode 100644 index 0000000..5fb7e59 --- /dev/null +++ b/snake/Makefile @@ -0,0 +1,2 @@ +SOURCES := snake.c +include $(MAKE_ROOT)/Makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87fba9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +output_esp +output_sim +*.sw* +*.vcxproj diff --git a/snake/Makefile b/snake/Makefile new file mode 100644 index 0000000..5fb7e59 --- /dev/null +++ b/snake/Makefile @@ -0,0 +1,2 @@ +SOURCES := snake.c +include $(MAKE_ROOT)/Makefile diff --git a/snake/snake.c b/snake/snake.c new file mode 100755 index 0000000..0a2d9d9 --- /dev/null +++ b/snake/snake.c @@ -0,0 +1,592 @@ +#include +#include + +#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); + } + } + } + + 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); + } +} + +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); + + 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; +}