132 lines
3.9 KiB
C
132 lines
3.9 KiB
C
// Simple game of snake to show how to do animation and draw cells.
|
|
|
|
#include "tim.h"
|
|
|
|
#define TPS 10
|
|
|
|
#define FG 0x10
|
|
#define BG 0xdd
|
|
#define BTN (FG << 16 | BG << 8 | FG)
|
|
|
|
#define NEW 0
|
|
#define RUN 1
|
|
#define PAUSE 2
|
|
#define OVER 3
|
|
|
|
typedef union {
|
|
struct {
|
|
i32 x;
|
|
i32 y;
|
|
};
|
|
i64 xy;
|
|
} point;
|
|
|
|
static struct {
|
|
i32 state; // game state (NEW RUN PAUSE OVER)
|
|
i64 tick; // updates every 10 ms
|
|
i32 len; // snake length
|
|
point food; // food position
|
|
point look; // active direction
|
|
point body[200]; // snake body
|
|
} snek;
|
|
|
|
static void start(void) {
|
|
memset(snek.body, -1, sizeof(snek.body));
|
|
snek.len = 2;
|
|
snek.body[0] = (point){{1, tim->h / 2}};
|
|
snek.food = (point){{tim->w / 8, tim->h / 2}};
|
|
snek.look = (point){{1, 0}};
|
|
}
|
|
|
|
static void game(void) {
|
|
// update game state about every 10 ms
|
|
i64 tick = tim_time_usec() / (1000000/TPS);
|
|
if (snek.tick != tick) {
|
|
snek.tick = tick;
|
|
// move one unit
|
|
memmove(snek.body + 1, snek.body, sizeof(snek.body) - sizeof(point));
|
|
snek.body[0].x = snek.body[1].x + snek.look.x;
|
|
snek.body[0].y = snek.body[1].y + snek.look.y;
|
|
// self crash
|
|
bool crash = false;
|
|
for (i32 i = 1; i < snek.len; i++) {
|
|
crash |= snek.body[0].xy == snek.body[i].xy;
|
|
}
|
|
// border crash
|
|
crash |= snek.body[0].x < 0 || snek.body[0].x >= tim->w / 2 ||
|
|
snek.body[0].y < 0 || snek.body[0].y >= tim->h;
|
|
snek.state = crash ? OVER : snek.state;
|
|
// food
|
|
if (snek.food.xy == snek.body[0].xy) {
|
|
snek.len = MIN(snek.len + 2, (i32)ARRAY_SIZE(snek.body));
|
|
snek.food.x = rand() % (tim->w / 2 - 2) + 1;
|
|
snek.food.y = rand() % (tim->h - 2) + 1;
|
|
}
|
|
}
|
|
|
|
// draw
|
|
if (tim->event.type == TimEvent_Draw) {
|
|
// food
|
|
tim_draw_chr(tim_cell(" ", 0, 0xc5), snek.food.x * 2 + 0, snek.food.y);
|
|
tim_draw_chr(tim_cell(" ", 0, 0xc5), snek.food.x * 2 + 1, snek.food.y);
|
|
// snek
|
|
TimCell s = tim_cell(" ", 0, 0);
|
|
for (i32 i = 0; i < snek.len; i++) {
|
|
s.bg = (i / 2) % 2 ? 0xe3 : 0xea;
|
|
i32 x = snek.body[i].x * 2;
|
|
i32 y = snek.body[i].y;
|
|
tim_draw_chr(s, x + 0, y);
|
|
tim_draw_chr(s, x + 1, y);
|
|
}
|
|
}
|
|
|
|
// user input
|
|
if (tim->event.type == KEY_EVENT) {
|
|
i32 key = tim->event.key;
|
|
if ((key == TimKey_Right || key == 'd') && snek.look.x != -1) {
|
|
snek.look = (point){{1, 0}};
|
|
} else if ((key == TimKey_Left || key == 'a') && snek.look.x != 1) {
|
|
snek.look = (point){{-1, 0}};
|
|
} else if ((key == TimKey_Down || key == 's') && snek.look.y != -1) {
|
|
snek.look = (point){{0, 1}};
|
|
} else if ((key == TimKey_Up || key == 'w') && snek.look.y != 1) {
|
|
snek.look = (point){{0, -1}};
|
|
}
|
|
else if (key == 'q' || key == TimKey_Escape){
|
|
snek.state = PAUSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void menu(void) {
|
|
tim_scope(A, A, 20, 13) {
|
|
char* lbl = snek.state == OVER ? "GAME OVER" : "SNEK - THE GAME";
|
|
char* btn = snek.state == PAUSE ? "Resume" : "Play";
|
|
tim_label(lbl, A, 0, A, A, BTN);
|
|
if (tim_button(btn, A, 2, 20, 5, BTN) || tim_is_key_press(TimKey_Enter)) {
|
|
if (snek.state != PAUSE) {
|
|
start();
|
|
}
|
|
snek.state = RUN;
|
|
}
|
|
if (tim_button("Exit", A, 8, 20, 5, BTN) || tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) {
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
i32 main(void) {
|
|
// draw every 10 ms
|
|
while (tim_run(60)) {
|
|
TimCell bg = tim_cell(" ", 0, BG);
|
|
tim_draw_lot(bg, 0, 0, tim->w, tim->h);
|
|
|
|
if (snek.state == RUN) {
|
|
game();
|
|
} else {
|
|
menu();
|
|
}
|
|
|
|
}
|
|
}
|