2
* Logic implementation of the Snake game. It is designed to efficiently
3
* represent in memory the state of the game.
5
* This code is public domain. Feel free to use it for any purpose!
9
#include <limits.h> /* CHAR_BIT, CHAR_MAX */
10
#include <string.h> /* memcpy() */
12
#define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
13
#define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
15
static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
17
const int shift = SHIFT(x, y);
18
const int adjust = shift % CHAR_BIT;
19
unsigned char *const pos = ctx->cells + (shift / CHAR_BIT);
21
memcpy(&range, pos, sizeof(range));
22
range &= ~(THREE_BITS << adjust); /* clear bits */
23
range |= (ct & THREE_BITS) << adjust;
24
memcpy(pos, &range, sizeof(range));
27
static int are_cells_full_(SnakeContext *ctx)
29
return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
32
static void new_food_pos_(SnakeContext *ctx, RandFunc rand)
37
x = (char) rand(SNAKE_GAME_WIDTH);
38
y = (char) rand(SNAKE_GAME_HEIGHT);
39
if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
40
put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
46
void snake_initialize(SnakeContext *ctx, RandFunc rand)
49
memset(ctx, 0, sizeof ctx->cells);
50
ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
51
ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
52
ctx->next_dir = SNAKE_DIR_RIGHT;
53
ctx->inhibit_tail_step = ctx->occupied_cells = 4;
54
--ctx->occupied_cells;
55
put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
56
for (i = 0; i < 4; i++) {
57
new_food_pos_(ctx, rand);
58
++ctx->occupied_cells;
62
void snake_redir(SnakeContext *ctx, SnakeDirection dir)
64
SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
65
if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
66
(dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
67
(dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
68
(dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP))
72
static void wrap_around_(char *val, char max)
80
void snake_step(SnakeContext *ctx, RandFunc rand)
82
const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
86
/* Move tail forward */
87
if (--ctx->inhibit_tail_step == 0) {
88
++ctx->inhibit_tail_step;
89
ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
90
put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
92
case SNAKE_CELL_SRIGHT:
98
case SNAKE_CELL_SLEFT:
101
case SNAKE_CELL_SDOWN:
107
wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
108
wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
110
/* Move head forward */
111
prev_xpos = ctx->head_xpos;
112
prev_ypos = ctx->head_ypos;
113
switch (ctx->next_dir) {
114
case SNAKE_DIR_RIGHT:
127
wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
128
wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
130
ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
131
if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
132
snake_initialize(ctx, rand);
135
put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
136
put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
137
if (ct == SNAKE_CELL_FOOD) {
138
if (are_cells_full_(ctx)) {
139
snake_initialize(ctx, rand);
142
new_food_pos_(ctx, rand);
143
++ctx->inhibit_tail_step;
144
++ctx->occupied_cells;
148
SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
150
const int shift = SHIFT(x, y);
151
unsigned short range;
152
memcpy(&range, ctx->cells + (shift / CHAR_BIT), sizeof(range));
153
return (SnakeCell)((range >> (shift % CHAR_BIT)) & THREE_BITS);