summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c')
-rw-r--r--src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c b/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c
new file mode 100644
index 0000000..0aca862
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/examples/demo/01-snake/snake.c
@@ -0,0 +1,350 @@
1/*
2 * Logic implementation of the Snake game. It is designed to efficiently
3 * represent the state of the game in memory.
4 *
5 * This code is public domain. Feel free to use it for any purpose!
6 */
7
8#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
9#include <SDL3/SDL.h>
10#include <SDL3/SDL_main.h>
11
12#define STEP_RATE_IN_MILLISECONDS 125
13#define SNAKE_BLOCK_SIZE_IN_PIXELS 24
14#define SDL_WINDOW_WIDTH (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_WIDTH)
15#define SDL_WINDOW_HEIGHT (SNAKE_BLOCK_SIZE_IN_PIXELS * SNAKE_GAME_HEIGHT)
16
17#define SNAKE_GAME_WIDTH 24U
18#define SNAKE_GAME_HEIGHT 18U
19#define SNAKE_MATRIX_SIZE (SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT)
20
21#define THREE_BITS 0x7U /* ~CHAR_MAX >> (CHAR_BIT - SNAKE_CELL_MAX_BITS) */
22#define SHIFT(x, y) (((x) + ((y) * SNAKE_GAME_WIDTH)) * SNAKE_CELL_MAX_BITS)
23
24typedef enum
25{
26 SNAKE_CELL_NOTHING = 0U,
27 SNAKE_CELL_SRIGHT = 1U,
28 SNAKE_CELL_SUP = 2U,
29 SNAKE_CELL_SLEFT = 3U,
30 SNAKE_CELL_SDOWN = 4U,
31 SNAKE_CELL_FOOD = 5U
32} SnakeCell;
33
34#define SNAKE_CELL_MAX_BITS 3U /* floor(log2(SNAKE_CELL_FOOD)) + 1 */
35
36typedef enum
37{
38 SNAKE_DIR_RIGHT,
39 SNAKE_DIR_UP,
40 SNAKE_DIR_LEFT,
41 SNAKE_DIR_DOWN
42} SnakeDirection;
43
44typedef struct
45{
46 unsigned char cells[(SNAKE_MATRIX_SIZE * SNAKE_CELL_MAX_BITS) / 8U];
47 char head_xpos;
48 char head_ypos;
49 char tail_xpos;
50 char tail_ypos;
51 char next_dir;
52 char inhibit_tail_step;
53 unsigned occupied_cells;
54} SnakeContext;
55
56typedef struct
57{
58 SDL_Window *window;
59 SDL_Renderer *renderer;
60 SnakeContext snake_ctx;
61 Uint64 last_step;
62} AppState;
63
64SnakeCell snake_cell_at(const SnakeContext *ctx, char x, char y)
65{
66 const int shift = SHIFT(x, y);
67 unsigned short range;
68 SDL_memcpy(&range, ctx->cells + (shift / 8), sizeof(range));
69 return (SnakeCell)((range >> (shift % 8)) & THREE_BITS);
70}
71
72static void set_rect_xy_(SDL_FRect *r, short x, short y)
73{
74 r->x = (float)(x * SNAKE_BLOCK_SIZE_IN_PIXELS);
75 r->y = (float)(y * SNAKE_BLOCK_SIZE_IN_PIXELS);
76}
77
78static void put_cell_at_(SnakeContext *ctx, char x, char y, SnakeCell ct)
79{
80 const int shift = SHIFT(x, y);
81 const int adjust = shift % 8;
82 unsigned char *const pos = ctx->cells + (shift / 8);
83 unsigned short range;
84 SDL_memcpy(&range, pos, sizeof(range));
85 range &= ~(THREE_BITS << adjust); /* clear bits */
86 range |= (ct & THREE_BITS) << adjust;
87 SDL_memcpy(pos, &range, sizeof(range));
88}
89
90static int are_cells_full_(SnakeContext *ctx)
91{
92 return ctx->occupied_cells == SNAKE_GAME_WIDTH * SNAKE_GAME_HEIGHT;
93}
94
95static void new_food_pos_(SnakeContext *ctx)
96{
97 while (true) {
98 const char x = (char) SDL_rand(SNAKE_GAME_WIDTH);
99 const char y = (char) SDL_rand(SNAKE_GAME_HEIGHT);
100 if (snake_cell_at(ctx, x, y) == SNAKE_CELL_NOTHING) {
101 put_cell_at_(ctx, x, y, SNAKE_CELL_FOOD);
102 break;
103 }
104 }
105}
106
107void snake_initialize(SnakeContext *ctx)
108{
109 int i;
110 SDL_zeroa(ctx->cells);
111 ctx->head_xpos = ctx->tail_xpos = SNAKE_GAME_WIDTH / 2;
112 ctx->head_ypos = ctx->tail_ypos = SNAKE_GAME_HEIGHT / 2;
113 ctx->next_dir = SNAKE_DIR_RIGHT;
114 ctx->inhibit_tail_step = ctx->occupied_cells = 4;
115 --ctx->occupied_cells;
116 put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_SRIGHT);
117 for (i = 0; i < 4; i++) {
118 new_food_pos_(ctx);
119 ++ctx->occupied_cells;
120 }
121}
122
123void snake_redir(SnakeContext *ctx, SnakeDirection dir)
124{
125 SnakeCell ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
126 if ((dir == SNAKE_DIR_RIGHT && ct != SNAKE_CELL_SLEFT) ||
127 (dir == SNAKE_DIR_UP && ct != SNAKE_CELL_SDOWN) ||
128 (dir == SNAKE_DIR_LEFT && ct != SNAKE_CELL_SRIGHT) ||
129 (dir == SNAKE_DIR_DOWN && ct != SNAKE_CELL_SUP)) {
130 ctx->next_dir = dir;
131 }
132}
133
134static void wrap_around_(char *val, char max)
135{
136 if (*val < 0) {
137 *val = max - 1;
138 } else if (*val > max - 1) {
139 *val = 0;
140 }
141}
142
143void snake_step(SnakeContext *ctx)
144{
145 const SnakeCell dir_as_cell = (SnakeCell)(ctx->next_dir + 1);
146 SnakeCell ct;
147 char prev_xpos;
148 char prev_ypos;
149 /* Move tail forward */
150 if (--ctx->inhibit_tail_step == 0) {
151 ++ctx->inhibit_tail_step;
152 ct = snake_cell_at(ctx, ctx->tail_xpos, ctx->tail_ypos);
153 put_cell_at_(ctx, ctx->tail_xpos, ctx->tail_ypos, SNAKE_CELL_NOTHING);
154 switch (ct) {
155 case SNAKE_CELL_SRIGHT:
156 ctx->tail_xpos++;
157 break;
158 case SNAKE_CELL_SUP:
159 ctx->tail_ypos--;
160 break;
161 case SNAKE_CELL_SLEFT:
162 ctx->tail_xpos--;
163 break;
164 case SNAKE_CELL_SDOWN:
165 ctx->tail_ypos++;
166 break;
167 default:
168 break;
169 }
170 wrap_around_(&ctx->tail_xpos, SNAKE_GAME_WIDTH);
171 wrap_around_(&ctx->tail_ypos, SNAKE_GAME_HEIGHT);
172 }
173 /* Move head forward */
174 prev_xpos = ctx->head_xpos;
175 prev_ypos = ctx->head_ypos;
176 switch (ctx->next_dir) {
177 case SNAKE_DIR_RIGHT:
178 ++ctx->head_xpos;
179 break;
180 case SNAKE_DIR_UP:
181 --ctx->head_ypos;
182 break;
183 case SNAKE_DIR_LEFT:
184 --ctx->head_xpos;
185 break;
186 case SNAKE_DIR_DOWN:
187 ++ctx->head_ypos;
188 break;
189 }
190 wrap_around_(&ctx->head_xpos, SNAKE_GAME_WIDTH);
191 wrap_around_(&ctx->head_ypos, SNAKE_GAME_HEIGHT);
192 /* Collisions */
193 ct = snake_cell_at(ctx, ctx->head_xpos, ctx->head_ypos);
194 if (ct != SNAKE_CELL_NOTHING && ct != SNAKE_CELL_FOOD) {
195 snake_initialize(ctx);
196 return;
197 }
198 put_cell_at_(ctx, prev_xpos, prev_ypos, dir_as_cell);
199 put_cell_at_(ctx, ctx->head_xpos, ctx->head_ypos, dir_as_cell);
200 if (ct == SNAKE_CELL_FOOD) {
201 if (are_cells_full_(ctx)) {
202 snake_initialize(ctx);
203 return;
204 }
205 new_food_pos_(ctx);
206 ++ctx->inhibit_tail_step;
207 ++ctx->occupied_cells;
208 }
209}
210
211static SDL_AppResult handle_key_event_(SnakeContext *ctx, SDL_Scancode key_code)
212{
213 switch (key_code) {
214 /* Quit. */
215 case SDL_SCANCODE_ESCAPE:
216 case SDL_SCANCODE_Q:
217 return SDL_APP_SUCCESS;
218 /* Restart the game as if the program was launched. */
219 case SDL_SCANCODE_R:
220 snake_initialize(ctx);
221 break;
222 /* Decide new direction of the snake. */
223 case SDL_SCANCODE_RIGHT:
224 snake_redir(ctx, SNAKE_DIR_RIGHT);
225 break;
226 case SDL_SCANCODE_UP:
227 snake_redir(ctx, SNAKE_DIR_UP);
228 break;
229 case SDL_SCANCODE_LEFT:
230 snake_redir(ctx, SNAKE_DIR_LEFT);
231 break;
232 case SDL_SCANCODE_DOWN:
233 snake_redir(ctx, SNAKE_DIR_DOWN);
234 break;
235 default:
236 break;
237 }
238 return SDL_APP_CONTINUE;
239}
240
241SDL_AppResult SDL_AppIterate(void *appstate)
242{
243 AppState *as = (AppState *)appstate;
244 SnakeContext *ctx = &as->snake_ctx;
245 const Uint64 now = SDL_GetTicks();
246 SDL_FRect r;
247 unsigned i;
248 unsigned j;
249 int ct;
250
251 // run game logic if we're at or past the time to run it.
252 // if we're _really_ behind the time to run it, run it
253 // several times.
254 while ((now - as->last_step) >= STEP_RATE_IN_MILLISECONDS) {
255 snake_step(ctx);
256 as->last_step += STEP_RATE_IN_MILLISECONDS;
257 }
258
259 r.w = r.h = SNAKE_BLOCK_SIZE_IN_PIXELS;
260 SDL_SetRenderDrawColor(as->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
261 SDL_RenderClear(as->renderer);
262 for (i = 0; i < SNAKE_GAME_WIDTH; i++) {
263 for (j = 0; j < SNAKE_GAME_HEIGHT; j++) {
264 ct = snake_cell_at(ctx, i, j);
265 if (ct == SNAKE_CELL_NOTHING)
266 continue;
267 set_rect_xy_(&r, i, j);
268 if (ct == SNAKE_CELL_FOOD)
269 SDL_SetRenderDrawColor(as->renderer, 80, 80, 255, SDL_ALPHA_OPAQUE);
270 else /* body */
271 SDL_SetRenderDrawColor(as->renderer, 0, 128, 0, SDL_ALPHA_OPAQUE);
272 SDL_RenderFillRect(as->renderer, &r);
273 }
274 }
275 SDL_SetRenderDrawColor(as->renderer, 255, 255, 0, SDL_ALPHA_OPAQUE); /*head*/
276 set_rect_xy_(&r, ctx->head_xpos, ctx->head_ypos);
277 SDL_RenderFillRect(as->renderer, &r);
278 SDL_RenderPresent(as->renderer);
279 return SDL_APP_CONTINUE;
280}
281
282static const struct
283{
284 const char *key;
285 const char *value;
286} extended_metadata[] =
287{
288 { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/01-snake/" },
289 { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" },
290 { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" },
291 { SDL_PROP_APP_METADATA_TYPE_STRING, "game" }
292};
293
294SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
295{
296 size_t i;
297
298 if (!SDL_SetAppMetadata("Example Snake game", "1.0", "com.example.Snake")) {
299 return SDL_APP_FAILURE;
300 }
301
302 for (i = 0; i < SDL_arraysize(extended_metadata); i++) {
303 if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) {
304 return SDL_APP_FAILURE;
305 }
306 }
307
308 if (!SDL_Init(SDL_INIT_VIDEO)) {
309 return SDL_APP_FAILURE;
310 }
311
312 AppState *as = (AppState *)SDL_calloc(1, sizeof(AppState));
313 if (!as) {
314 return SDL_APP_FAILURE;
315 }
316
317 *appstate = as;
318
319 if (!SDL_CreateWindowAndRenderer("examples/demo/snake", SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT, 0, &as->window, &as->renderer)) {
320 return SDL_APP_FAILURE;
321 }
322
323 snake_initialize(&as->snake_ctx);
324
325 as->last_step = SDL_GetTicks();
326
327 return SDL_APP_CONTINUE;
328}
329
330SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
331{
332 SnakeContext *ctx = &((AppState *)appstate)->snake_ctx;
333 switch (event->type) {
334 case SDL_EVENT_QUIT:
335 return SDL_APP_SUCCESS;
336 case SDL_EVENT_KEY_DOWN:
337 return handle_key_event_(ctx, event->key.scancode);
338 }
339 return SDL_APP_CONTINUE;
340}
341
342void SDL_AppQuit(void *appstate, SDL_AppResult result)
343{
344 if (appstate != NULL) {
345 AppState *as = (AppState *)appstate;
346 SDL_DestroyRenderer(as->renderer);
347 SDL_DestroyWindow(as->window);
348 SDL_free(as);
349 }
350}