| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- /*
- * Logic implementation of the Snake game. It is designed to efficiently
- * represent in memory the state of the game.
- *
- * This code is public domain. Feel free to use it for any purpose!
- */
- #include "snake.h"
- #include <limits.h> /* CHAR_BIT, CHAR_MAX */
- #include <string.h> /* memcpy() */
- #define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
- #define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
- static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
- {
- const int shift = SHIFT(x, y);
- const int adjust = shift % CHAR_BIT;
- unsigned char *const pos = ctx->cells + (shift / CHAR_BIT);
- unsigned short range;
- memcpy(&range, pos, sizeof(range));
- range &= ~(THREE_BITS << adjust); /* clear bits */
- range |= (ct & THREE_BITS) << adjust;
- memcpy(pos, &range, sizeof(range));
- }
- static int are_cells_full_(SnakeContext *ctx)
- {
- return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
- }
- static void new_food_pos_(SnakeContext *ctx, RandFunc rand)
- {
- char x;
- char y;
- for (;;) {
- x = (char) rand(SNAKE_GAME_WIDTH);
- y = (char) rand(SNAKE_GAME_HEIGHT);
- if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
- put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
- break;
- }
- }
- }
- void snake_initialize(SnakeContext *ctx, RandFunc rand)
- {
- int i;
- memset(ctx, 0, sizeof ctx->cells);
- ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
- ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
- ctx->next_dir = SNAKE_DIR_RIGHT;
- ctx->inhibit_tail_step = ctx->occupied_cells = 4;
- --ctx->occupied_cells;
- put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
- for (i = 0; i < 4; i++) {
- new_food_pos_(ctx, rand);
- ++ctx->occupied_cells;
- }
- }
- void snake_redir(SnakeContext *ctx, SnakeDirection dir)
- {
- SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
- if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
- (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
- (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
- (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP))
- ctx->next_dir = dir;
- }
- static void wrap_around_(char *val, char max)
- {
- if (*val < 0)
- *val = max - 1;
- if (*val > max - 1)
- *val = 0;
- }
- void snake_step(SnakeContext *ctx, RandFunc rand)
- {
- const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
- SnakeCell ct;
- char prev_xpos;
- char prev_ypos;
- /* Move tail forward */
- if (--ctx->inhibit_tail_step == 0) {
- ++ctx->inhibit_tail_step;
- ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
- put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
- switch (ct) {
- case SNAKE_CELL_SRIGHT:
- ctx->tail_xpos++;
- break;
- case SNAKE_CELL_SUP:
- ctx->tail_ypos--;
- break;
- case SNAKE_CELL_SLEFT:
- ctx->tail_xpos--;
- break;
- case SNAKE_CELL_SDOWN:
- ctx->tail_ypos++;
- break;
- default:
- break;
- }
- wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
- wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
- }
- /* Move head forward */
- prev_xpos = ctx->head_xpos;
- prev_ypos = ctx->head_ypos;
- switch (ctx->next_dir) {
- case SNAKE_DIR_RIGHT:
- ++ctx->head_xpos;
- break;
- case SNAKE_DIR_UP:
- --ctx->head_ypos;
- break;
- case SNAKE_DIR_LEFT:
- --ctx->head_xpos;
- break;
- case SNAKE_DIR_DOWN:
- ++ctx->head_ypos;
- break;
- }
- wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
- wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
- /* Collisions */
- ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
- if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
- snake_initialize(ctx, rand);
- return;
- }
- put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
- put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
- if (ct == SNAKE_CELL_FOOD) {
- if (are_cells_full_(ctx)) {
- snake_initialize(ctx, rand);
- return;
- }
- new_food_pos_(ctx, rand);
- ++ctx->inhibit_tail_step;
- ++ctx->occupied_cells;
- }
- }
- SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
- {
- const int shift = SHIFT(x, y);
- unsigned short range;
- memcpy(&range, ctx->cells + (shift / CHAR_BIT), sizeof(range));
- return (SnakeCell)((range >> (shift % CHAR_BIT)) & THREE_BITS);
- }
|