repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

gbc / examples
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.