bytepusher.c 13 KB

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