// 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(); } } }