xplshn
·
2025-08-13
snake.b
B
1// Copyright 2025 Yui <[email protected]>
2//
3// Permission is hereby granted, free of charge, to any person obtaining
4// a copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so, subject to
9// the following conditions:
10//
11// The above copyright notice and this permission notice shall be
12// included in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22// tested using the B compiler made by tsoding
23// https://github.com/tsoding/b
24// (26/05/2025 01:49)
25//
26// This program uses POSIX specific functions to handle input
27// also ANSI escape code for drawing to the screen
28// so there's no chance to get it to run on html-js target
29//
30// due to the compiler still being early in development
31// there are alot of "hacks" in the code to get around
32// some limitations, like assuming the memory layout of
33// C structures, usage of magic constants, usage of
34// memset/memcopy to write specific memory regions that are
35// smaller than the machine word size, replacing structs and
36// array with pointers (thus having to malloc all of them), etc..
37//
38// also the rendering code is just stupid, it redraws the entire
39// screen every single frame ...
40
41
42// constants
43W;
44W2;
45X;
46Y;
47STDIN;
48STDOUT;
49TCSAFLUSH;
50FIONREAD;
51TIOCGWINSZ;
52
53// enum Tile
54EMPTY;
55BODY;
56HEAD;
57APPLE;
58
59// enum Direction
60UP;
61RIGHT;
62DOWN;
63LEFT;
64
65// state
66screen;
67screen_size;
68input_ch;
69bytes_remaining;
70head;
71facing;
72body;
73body_len;
74score;
75frame_time;
76apple;
77width;
78height;
79apple_size;
80frame;
81dead;
82rgb;
83just_died;
84
85body_i(i) return (body + i*W2);
86screen_xy(x, y) return (screen + (x + y*width)*W);
87
88is_colliding_with_apple(pos) {
89 auto x, y, dx, dy, is_colliding;
90 is_colliding = 0;
91 dy=0; while (dy < apple_size) {
92 dx=0; while (dx < apple_size) {
93 x = apple[X] + dx;
94 y = apple[Y] + dy;
95 is_colliding |= pos[X]==x & pos[Y]==y;
96 dx += 1;
97 }
98 dy += 1;
99 }
100 return (is_colliding);
101}
102
103randomize_apple() {
104 extrn rand;
105 auto again; again = 1; while (again) {
106 apple[X] = rand() % (width - apple_size - 2) + 1;
107 apple[Y] = rand() % (height - apple_size - 2) + 1;
108 again = is_colliding_with_apple(head);
109
110 auto i; i = 0; while (i < body_len) {
111 again |= is_colliding_with_apple(body_i(i));
112 i += 1;
113 }
114
115 }
116}
117
118init_globals() {
119 extrn malloc, memset, ioctl, srand, time;
120 srand(time(0));
121
122 W = 8;
123 W2 = W*2;
124 X = 0;
125 Y = 1;
126 STDIN = 0;
127 STDOUT = 1;
128 TCSAFLUSH = 2;
129 TIOCGWINSZ = 21523;
130 FIONREAD = 21531;
131
132 auto ws;
133 ws = malloc(16);
134 ioctl(STDOUT, TIOCGWINSZ, ws);
135
136 height = (*ws & 0xffff)-3;
137 width = (*ws >> 17 & 0x7fff)-2;
138 apple_size = 3;
139 dead = 0;
140 frame = 0;
141 just_died = 0;
142
143
144 EMPTY = 0;
145 BODY = 1;
146 HEAD = 2;
147 APPLE = 4;
148
149 UP = 0;
150 RIGHT = 1;
151 DOWN = 2;
152 LEFT = 3;
153
154 screen_size = width*height*W;
155 screen = malloc(screen_size);
156 memset(screen, EMPTY, screen_size);
157
158 // add `&` to take address of variable
159 // instead of spamming malloc everywhere
160 input_ch = malloc(W);
161 bytes_remaining = malloc(W);
162
163 head = malloc(W2);
164 head[X] = width>>1;
165 head[Y] = height>>1;
166
167 body = malloc(screen_size*W2);
168 memset(body, 255, screen_size); // fill -1
169 body_len = 5;
170 score = 0;
171
172 apple = malloc(W2);
173 randomize_apple();
174
175 frame_time = 100;
176 facing = RIGHT;
177}
178
179unreachable(message) {
180 extrn printf, abort;
181 printf("\nUNREACHABLE: %s\n", message);
182 abort();
183}
184
185render() {
186 extrn printf;
187 auto y, x, tile;
188
189 // stb_c_lexer does not support hex or octal escape-
190 // sequences in strings, check `stb__clex_parse_char`.
191
192 printf("%c[H", 27);
193 x=0; while (x < width+2) { x+=1;
194 printf("%c[38;5;238mββ", 27);
195 }
196 printf("%c[H", 27);
197 printf("%c[48;5;238m%c[38;5;232m", 27, 27);
198 printf(" Score: %-4d Body length: %-4d Frame time(ms): %d\n", score, body_len, frame_time);
199
200
201 // !when for loops
202 y=0; while (y < height) {
203 printf("%c[38;5;238mββ", 27);
204 x=0; while (x < width) {
205 tile = *screen_xy(x, y);
206 if (tile == EMPTY) {
207 printf("%c[48;5;233m ", 27);
208 } else if (tile == BODY) {
209 if (just_died) printf("%c[38;5;65mββ", 27);
210 else if (dead) printf("%c[38;5;245mββ", 27);
211 else if (rgb) {
212 auto r, g, b, f;
213 f = frame*35;
214 r = (f) % 510;
215 if (r >= 256) r = 510-r;
216 g = ((f>>1) + 64) % 510;
217 if (g >= 256) g = 510-g;
218 b = ((f>>2)*3 + 128) % 510;
219 if (b >= 256) b = 510-b;
220 printf("%c[38;2;%d;%d;%dmββ", 27, r, g, b);
221 } else printf("%c[38;5;41mββ", 27);
222 } else if (tile == HEAD) {
223 if (just_died) printf("%c[48;5;65m", 27);
224 else if (dead) printf("%c[48;5;245m", 27);
225 else if (rgb) {
226 auto r, g, b, f;
227 f = frame*35;
228 r = (f) % 510;
229 if (r >= 256) r = 510-r;
230 g = ((f>>1) + 64) % 510;
231 if (g >= 256) g = 510-g;
232 b = ((f>>2)*3 + 128) % 510;
233 if (b >= 256) b = 510-b;
234 printf("%c[48;2;%d;%d;%dm", 27, r, g, b);
235 } else printf("%c[48;5;41m", 27);
236 printf("%c[38;5;22m", 27);
237 if (facing == UP) {
238 printf("ββ");
239 } else if (facing == DOWN) {
240 printf("ββ");
241 } else if (facing == RIGHT) {
242 printf("ββ");
243 } else if (facing == LEFT) {
244 printf("ββ");
245 } else {
246 unreachable("in render(), autovar `facing` containt unknown Direction variant");
247 }
248 } else if (tile == APPLE) {
249 printf("%c[38;5;196mββ", 27);
250 } else {
251 unreachable("in render(), autovar `tile` containt unknown Tile variant");
252 }
253 x+=1;
254 }
255 printf("%c[38;5;238mββ", 27);
256 printf("\n");
257 y+=1;
258 }
259
260 x=0; while (x < width+2) { x+=1;
261 printf("%c[38;5;238mββ", 27);
262 }
263 printf("\n");
264}
265
266orig_termios;
267enable_raw_mode() {
268 extrn malloc, tcgetattr, tcsetattr, printf, memset;
269 orig_termios = malloc(64);
270 tcgetattr(STDIN, orig_termios);
271
272 auto raw;
273 raw = malloc(64);
274 memset(raw, 0, 64);
275 *raw = *orig_termios & 0xfffffffffffffff5;
276
277 tcsetattr(STDIN, TCSAFLUSH, raw);
278 printf("%c[?25l", 27);
279}
280
281disable_raw_mode() {
282 extrn tcsetattr, printf;
283 tcsetattr(STDIN, TCSAFLUSH, orig_termios);
284 printf("%c[?25h", 27);
285}
286
287handle_user_input() {
288 extrn read, ioctl, usleep;
289 if (just_died) {
290 just_died = 0;
291 usleep(1000000);
292 ioctl(STDIN, FIONREAD, bytes_remaining);
293 auto i; i = 0; while (i < *bytes_remaining) { i += 1;
294 read(STDIN, input_ch, 1);
295 }
296 } else {
297 auto f;
298 f = facing;
299 ioctl(STDIN, FIONREAD, bytes_remaining);
300 while (*bytes_remaining != 0) {
301 read(STDIN, input_ch, 1);
302 if (*input_ch == 113) { return (1); } // q -> exit=true
303 else if (!dead) {
304 if (*input_ch == 119 & f != DOWN) { facing = UP; } // w
305 if (*input_ch == 97 & f != RIGHT) { facing = LEFT; } // a
306 if (*input_ch == 115 & f != UP) { facing = DOWN; } // s
307 if (*input_ch == 100 & f != LEFT) { facing = RIGHT; } // d
308 if (*input_ch == 114) { rgb = !rgb; } // r
309 } else {
310 init_globals();
311 }
312 ioctl(STDIN, FIONREAD, bytes_remaining);
313 }
314 }
315 return (0); // exit=false
316}
317
318draw_screen() {
319 extrn memset;
320 memset(screen, EMPTY, screen_size);
321
322 auto i; i = 0; while (i < body_len) {
323 auto segment;
324 segment = body_i(i);
325 if(segment[X] != -1 & segment[Y] != -1)
326 *screen_xy(segment[X], segment[Y]) = BODY;
327 i += 1;
328 }
329
330 *screen_xy(head[X], head[Y]) = HEAD;
331
332 auto x, y, dx, dy;
333 dy = 0; while (dy < apple_size) {
334 dx = 0; while (dx < apple_size) {
335 x = apple[X] + dx;
336 y = apple[Y] + dy;
337 *screen_xy(x, y) = APPLE;
338 dx += 1;
339 }
340 dy += 1;
341 }
342 *screen_xy(x, y) = APPLE;
343 render();
344}
345
346update() {
347 extrn memset, memmove, memcpy;
348
349 memmove(body_i(1), body, (body_len-1)*W2);
350 memcpy(body, head, W2);
351
352 // need ternary operator
353 if (facing == UP) {
354 head[Y] = head[Y] - 1;
355 if (head[Y] < 0) head[Y] += height;
356 } else if (facing == DOWN){
357 head[Y] = (head[Y]+1) % height;
358 } else if (facing == RIGHT){
359 head[X] = (head[X]+1) % width;
360 } else if (facing == LEFT){
361 head[X] = head[X] - 1;
362 if (head[X] < 0) head[X] += width;
363 } else {
364 unreachable("in update(), autovar `facing` containt unknown direction variant");
365 }
366
367 if (is_colliding_with_apple(head)) {
368 body_len += 3;
369 score += 1;
370 randomize_apple();
371 }
372
373 auto i; i = 0; while (i < body_len) {
374 auto segment;
375 segment = body_i(i);
376 dead |= head[X]==segment[X] & head[Y]==segment[Y];
377 i += 1;
378 }
379
380 if (dead) {
381 memcpy(head, body, W2);
382 just_died = 1;
383 }
384
385 // !when division
386 frame_time = 80 - (body_len>>2);
387}
388
389main() {
390 extrn usleep;
391 init_globals();
392 randomize_apple();
393 enable_raw_mode();
394
395 auto exit; exit = 0; while (!exit) {
396 draw_screen();
397 usleep(frame_time*1000);
398 exit = handle_user_input();
399 if (!dead) update();
400 frame += 1;
401 }
402
403 disable_raw_mode();
404}
405
406// TODO: does not work on gas-x86_64-windows due to using POSIX stuff
407// Would be nice to research how hard/easy it is to add the Window support.
408// But this is not critical.