Atlas - bytepusher.c

Home / ext / SDL / examples / demo / 04-bytepusher Lines: 1 | Size: 12251 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 * An implementation of the BytePusher VM. 3 * 4 * For example programs and more information about BytePusher, see 5 * https://esolangs.org/wiki/BytePusher 6 * 7 * This code is public domain. Feel free to use it for any purpose! 8 */ 9 10#define SDL_MAIN_USE_CALLBACKS 11#include <SDL3/SDL.h> 12#include <SDL3/SDL_main.h> 13#include <stdarg.h> 14 15#define SCREEN_W 256 16#define SCREEN_H 256 17#define RAM_SIZE 0x1000000 18#define FRAMES_PER_SECOND 60 19#define SAMPLES_PER_FRAME 256 20#define NS_PER_SECOND (Uint64)SDL_NS_PER_SECOND 21#define MAX_AUDIO_LATENCY_FRAMES 5 22 23#define IO_KEYBOARD 0 24#define IO_PC 2 25#define IO_SCREEN_PAGE 5 26#define IO_AUDIO_BANK 6 27 28typedef struct { 29 Uint8 ram[RAM_SIZE + 8]; 30 Uint64 last_tick; 31 Uint64 tick_acc; 32 SDL_Window* window; 33 SDL_Renderer* renderer; 34 SDL_Palette* palette; 35 SDL_Texture* texture; 36 SDL_Texture* rendertarget; /* we need this render target for text to look good */ 37 SDL_AudioStream* audiostream; 38 char status[SCREEN_W / 8]; 39 int status_ticks; 40 Uint16 keystate; 41 bool display_help; 42 bool positional_input; 43} BytePusher; 44 45static const struct { 46 const char *key; 47 const char *value; 48} extended_metadata[] = { 49 { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/04-bytepusher/" }, 50 { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" }, 51 { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" }, 52 { SDL_PROP_APP_METADATA_TYPE_STRING, "game" } 53}; 54 55static inline Uint16 read_u16(const BytePusher* vm, Uint32 addr) { 56 const Uint8* ptr = &vm->ram[addr]; 57 return ((Uint16)ptr[0] << 8) | ((Uint16)ptr[1]); 58} 59 60static inline Uint32 read_u24(const BytePusher* vm, Uint32 addr) { 61 const Uint8* ptr = &vm->ram[addr]; 62 return ((Uint32)ptr[0] << 16) | ((Uint32)ptr[1] << 8) | ((Uint32)ptr[2]); 63} 64 65static void set_status(BytePusher* vm, const char* fmt, ...) { 66 va_list args; 67 va_start(args, fmt); 68 SDL_vsnprintf(vm->status, sizeof(vm->status), fmt, args); 69 va_end(args); 70 vm->status[sizeof(vm->status) - 1] = 0; 71 vm->status_ticks = FRAMES_PER_SECOND * 3; 72} 73 74static bool load(BytePusher* vm, SDL_IOStream* stream, bool closeio) { 75 size_t bytes_read = 0; 76 bool ok = true; 77 78 SDL_memset(vm->ram, 0, RAM_SIZE); 79 80 if (!stream) { 81 return false; 82 } 83 84 while (bytes_read < RAM_SIZE) { 85 size_t read = SDL_ReadIO(stream, &vm->ram[bytes_read], RAM_SIZE - bytes_read); 86 bytes_read += read; 87 if (read == 0) { 88 ok = SDL_GetIOStatus(stream) == SDL_IO_STATUS_EOF; 89 break; 90 } 91 } 92 if (closeio) { 93 SDL_CloseIO(stream); 94 } 95 96 SDL_ClearAudioStream(vm->audiostream); 97 98 vm->display_help = !ok; 99 return ok; 100} 101 102static const char* filename(const char* path) { 103 size_t i = SDL_strlen(path) + 1; 104 while (i > 0) { 105 i -= 1; 106 if (path[i] == '/' || path[i] == '\\') { 107 return path + i + 1; 108 } 109 } 110 return path; 111} 112 113static bool load_file(BytePusher* vm, const char* path) { 114 if (load(vm, SDL_IOFromFile(path, "rb"), true)) { 115 set_status(vm, "loaded %s", filename(path)); 116 return true; 117 } else { 118 set_status(vm, "load failed: %s", filename(path)); 119 return false; 120 } 121} 122 123static void print(BytePusher* vm, int x, int y, const char* str) { 124 SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 125 SDL_RenderDebugText(vm->renderer, (float)(x + 1), (float)(y + 1), str); 126 SDL_SetRenderDrawColor(vm->renderer, 0xff, 0xff, 0xff, SDL_ALPHA_OPAQUE); 127 SDL_RenderDebugText(vm->renderer, (float)x, (float)y, str); 128 SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 129} 130 131SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { 132 BytePusher* vm; 133 SDL_Rect usable_bounds; 134 SDL_AudioSpec audiospec = { SDL_AUDIO_S8, 1, SAMPLES_PER_FRAME * FRAMES_PER_SECOND }; 135 SDL_DisplayID primary_display; 136 int zoom = 2; 137 int i; 138 Uint8 r, g, b; 139 140 if (!SDL_SetAppMetadata("SDL 3 BytePusher", "1.0", "com.example.SDL3BytePusher")) { 141 return SDL_APP_FAILURE; 142 } 143 144 for (i = 0; i < (int)SDL_arraysize(extended_metadata); i++) { 145 if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) { 146 return SDL_APP_FAILURE; 147 } 148 } 149 150 if (!SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO)) { 151 return SDL_APP_FAILURE; 152 } 153 154 if (!(vm = (BytePusher *)SDL_calloc(1, sizeof(*vm)))) { 155 return SDL_APP_FAILURE; 156 } 157 *(BytePusher**)appstate = vm; 158 159 vm->display_help = true; 160 161 primary_display = SDL_GetPrimaryDisplay(); 162 if (SDL_GetDisplayUsableBounds(primary_display, &usable_bounds)) { 163 int zoom_w = (usable_bounds.w - usable_bounds.x) * 2 / 3 / SCREEN_W; 164 int zoom_h = (usable_bounds.h - usable_bounds.y) * 2 / 3 / SCREEN_H; 165 zoom = zoom_w < zoom_h ? zoom_w : zoom_h; 166 if (zoom < 1) { 167 zoom = 1; 168 } 169 } 170 171 if (!SDL_CreateWindowAndRenderer("SDL 3 BytePusher", 172 SCREEN_W * zoom, SCREEN_H * zoom, SDL_WINDOW_RESIZABLE, 173 &vm->window, &vm->renderer 174 )) { 175 return SDL_APP_FAILURE; 176 } 177 178 if (!SDL_SetRenderLogicalPresentation( 179 vm->renderer, SCREEN_W, SCREEN_H, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE 180 )) { 181 return SDL_APP_FAILURE; 182 } 183 184 if (!(vm->palette = SDL_CreatePalette(256))) { 185 return SDL_APP_FAILURE; 186 } 187 i = 0; 188 for (r = 0; r < 6; ++r) { 189 for (g = 0; g < 6; ++g) { 190 for (b = 0; b < 6; ++b, ++i) { 191 SDL_Color color = { (Uint8)(r * 0x33), (Uint8)(g * 0x33), (Uint8)(b * 0x33), SDL_ALPHA_OPAQUE }; 192 vm->palette->colors[i] = color; 193 } 194 } 195 } 196 for (; i < 256; ++i) { 197 SDL_Color color = { 0, 0, 0, SDL_ALPHA_OPAQUE }; 198 vm->palette->colors[i] = color; 199 } 200 201 vm->texture = SDL_CreateTexture(vm->renderer, SDL_PIXELFORMAT_INDEX8, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H); 202 vm->rendertarget = SDL_CreateTexture(vm->renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_TARGET, SCREEN_W, SCREEN_H); 203 if (!vm->texture || !vm->rendertarget) { 204 return SDL_APP_FAILURE; 205 } 206 SDL_SetTexturePalette(vm->texture, vm->palette); 207 SDL_SetTextureScaleMode(vm->texture, SDL_SCALEMODE_NEAREST); 208 SDL_SetTextureScaleMode(vm->rendertarget, SDL_SCALEMODE_NEAREST); 209 210 if (!(vm->audiostream = SDL_OpenAudioDeviceStream( 211 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audiospec, NULL, NULL 212 ))) { 213 return SDL_APP_FAILURE; 214 } 215 SDL_SetAudioStreamGain(vm->audiostream, 0.1f); /* examples are loud! */ 216 SDL_ResumeAudioStreamDevice(vm->audiostream); 217 218 set_status(vm, "renderer: %s", SDL_GetRendererName(vm->renderer)); 219 220 vm->last_tick = SDL_GetTicksNS(); 221 vm->tick_acc = NS_PER_SECOND; 222 223 if (argc > 1) { 224 load_file(vm, argv[1]); 225 } 226 227 return SDL_APP_CONTINUE; 228} 229 230SDL_AppResult SDL_AppIterate(void* appstate) { 231 BytePusher* vm = (BytePusher*)appstate; 232 233 Uint64 tick = SDL_GetTicksNS(); 234 Uint64 delta = tick - vm->last_tick; 235 bool updated, skip_audio; 236 237 vm->last_tick = tick; 238 239 vm->tick_acc += delta * FRAMES_PER_SECOND; 240 updated = vm->tick_acc >= NS_PER_SECOND; 241 skip_audio = vm->tick_acc >= MAX_AUDIO_LATENCY_FRAMES * NS_PER_SECOND; 242 243 if (skip_audio) { 244 // don't let audio fall too far behind 245 SDL_ClearAudioStream(vm->audiostream); 246 } 247 248 while (vm->tick_acc >= NS_PER_SECOND) { 249 Uint32 pc; 250 int i; 251 252 vm->tick_acc -= NS_PER_SECOND; 253 254 vm->ram[IO_KEYBOARD] = (Uint8)(vm->keystate >> 8); 255 vm->ram[IO_KEYBOARD + 1] = (Uint8)(vm->keystate); 256 257 pc = read_u24(vm, IO_PC); 258 for (i = 0; i < SCREEN_W * SCREEN_H; ++i) { 259 Uint32 src = read_u24(vm, pc); 260 Uint32 dst = read_u24(vm, pc + 3); 261 vm->ram[dst] = vm->ram[src]; 262 pc = read_u24(vm, pc + 6); 263 } 264 265 if (!skip_audio || vm->tick_acc < NS_PER_SECOND) { 266 SDL_PutAudioStreamData( 267 vm->audiostream, 268 &vm->ram[(Uint32)read_u16(vm, IO_AUDIO_BANK) << 8], 269 SAMPLES_PER_FRAME 270 ); 271 } 272 } 273 274 if (updated) { 275 const void *pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16]; 276 SDL_UpdateTexture(vm->texture, NULL, pixels, SCREEN_W); 277 278 SDL_SetRenderTarget(vm->renderer, vm->rendertarget); 279 SDL_RenderTexture(vm->renderer, vm->texture, NULL, NULL); 280 281 if (vm->display_help) { 282 print(vm, 4, 4, "Drop a BytePusher file in this"); 283 print(vm, 8, 12, "window to load and run it!"); 284 print(vm, 4, 28, "Press ENTER to switch between"); 285 print(vm, 8, 36, "positional and symbolic input."); 286 } 287 288 if (vm->status_ticks > 0) { 289 vm->status_ticks -= 1; 290 print(vm, 4, SCREEN_H - 12, vm->status); 291 } 292 } 293 294 SDL_SetRenderTarget(vm->renderer, NULL); 295 SDL_RenderClear(vm->renderer); 296 SDL_RenderTexture(vm->renderer, vm->rendertarget, NULL, NULL); 297 SDL_RenderPresent(vm->renderer); 298 299 return SDL_APP_CONTINUE; 300} 301 302static Uint16 keycode_mask(SDL_Keycode key) { 303 int index; 304 if (key >= SDLK_0 && key <= SDLK_9) { 305 index = key - SDLK_0; 306 } else if (key >= SDLK_A && key <= SDLK_F) { 307 index = key - SDLK_A + 10; 308 } else { 309 return 0; 310 } 311 return (Uint16)1 << index; 312} 313 314static Uint16 scancode_mask(SDL_Scancode scancode) { 315 int index; 316 switch (scancode) { 317 case SDL_SCANCODE_1: index = 0x1; break; 318 case SDL_SCANCODE_2: index = 0x2; break; 319 case SDL_SCANCODE_3: index = 0x3; break; 320 case SDL_SCANCODE_4: index = 0xc; break; 321 case SDL_SCANCODE_Q: index = 0x4; break; 322 case SDL_SCANCODE_W: index = 0x5; break; 323 case SDL_SCANCODE_E: index = 0x6; break; 324 case SDL_SCANCODE_R: index = 0xd; break; 325 case SDL_SCANCODE_A: index = 0x7; break; 326 case SDL_SCANCODE_S: index = 0x8; break; 327 case SDL_SCANCODE_D: index = 0x9; break; 328 case SDL_SCANCODE_F: index = 0xe; break; 329 case SDL_SCANCODE_Z: index = 0xa; break; 330 case SDL_SCANCODE_X: index = 0x0; break; 331 case SDL_SCANCODE_C: index = 0xb; break; 332 case SDL_SCANCODE_V: index = 0xf; break; 333 default: return 0; 334 } 335 return (Uint16)1 << index; 336} 337 338SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { 339 BytePusher* vm = (BytePusher*)appstate; 340 341 switch (event->type) { 342 case SDL_EVENT_QUIT: 343 return SDL_APP_SUCCESS; 344 345 case SDL_EVENT_DROP_FILE: 346 load_file(vm, event->drop.data); 347 break; 348 349 case SDL_EVENT_KEY_DOWN: 350#ifndef __EMSCRIPTEN__ 351 if (event->key.key == SDLK_ESCAPE) { 352 return SDL_APP_SUCCESS; 353 } 354#endif 355 if (event->key.key == SDLK_RETURN) { 356 vm->positional_input = !vm->positional_input; 357 vm->keystate = 0; 358 if (vm->positional_input) { 359 set_status(vm, "switched to positional input"); 360 } else { 361 set_status(vm, "switched to symbolic input"); 362 } 363 } 364 if (vm->positional_input) { 365 vm->keystate |= scancode_mask(event->key.scancode); 366 } else { 367 vm->keystate |= keycode_mask(event->key.key); 368 } 369 break; 370 371 case SDL_EVENT_KEY_UP: 372 if (vm->positional_input) { 373 vm->keystate &= ~scancode_mask(event->key.scancode); 374 } else { 375 vm->keystate &= ~keycode_mask(event->key.key); 376 } 377 break; 378 } 379 380 return SDL_APP_CONTINUE; 381} 382 383void SDL_AppQuit(void* appstate, SDL_AppResult result) { 384 if (result == SDL_APP_FAILURE) { 385 SDL_Log("Error: %s", SDL_GetError()); 386 } 387 if (appstate) { 388 BytePusher* vm = (BytePusher*)appstate; 389 SDL_DestroyAudioStream(vm->audiostream); 390 SDL_DestroyTexture(vm->rendertarget); 391 SDL_DestroyTexture(vm->texture); 392 SDL_DestroyPalette(vm->palette); 393 SDL_DestroyRenderer(vm->renderer); 394 SDL_DestroyWindow(vm->window); 395 SDL_free(vm); 396 } 397} 398
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.