// voyager.c // The Segan Voyager // A SplitBit with a screen and a speaker attached // Written by Anachronaut // // ---- What this is ---- // // The same machine SplitBit runs, presented through a window instead of a terminal. Every // instruction, every device and every cycle is in machine.c and shared; this file opens a // window, gives the machine a slice of time per frame, and shows what came out. // // THAT ORDER MATTERS AND IS THE WHOLE DESIGN. The devices belong to the machine and advance // on emulated cycles, so the same program produces the same frames and the same samples // whether or not anybody is looking. Raylib presents; it does not decide. Which is what // lets a test suite with no display hold this binary to the same behaviour as the other // one. // // At this stage the window is empty. There is no video device yet, and inventing a // temporary way to draw would mean building something to throw away. #include "machine.h" #include "utility.h" #include "raylib.h" #include #include #include // The screen the Voyager will have, scaled up because a 320 by 200 window is a postage // stamp on a modern display. Both numbers are provisional until the video device decides // them for real. #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 200 #define SCREEN_SCALE 3 // ---- Running without a window ---- // // Taken out of the arguments here rather than in the shared parser, because it is a fact // about this front end and the shared parser should not learn about a window that only one // binary has. Everything else on the command line means exactly what it means to SplitBit. // // It exists so the suite can run this binary at all: a test machine has no display, and a // front end that could only be exercised by a person looking at it would be a front end // nothing checks. Headless, Voyager must print byte for byte what SplitBit prints, and // Tests/voyager.sh holds it to that. static int takeHeadless(int *argc, char *argv[]) { int headless = 0; int out = 0; for (int i = 0; i < *argc; i++) { if (strcmp(argv[i], "--headless") == 0) { headless = 1; continue; } argv[out++] = argv[i]; } argv[out] = NULL; *argc = out; return headless; } int main(int argc, char *argv[]) { int headless = takeHeadless(&argc, argv); EmulatorOptions options; uint8_t result = parseOptions(argc, argv, &options); if (result == OPTIONS_HELP) { printf(" --headless Run with no window, which is how the tests run it.\n"); return 0; } else if (result == OPTIONS_ERROR) { return 1; } char *programFile = NULL; if (optind < argc) { programFile = argv[optind]; optind++; } if (optind < argc) { fprintf(stderr, "Error: Unexpected argument: %s\n", argv[optind]); return 1; } Machine machine; uint8_t started = machineStart(&machine, &options, programFile); if (started == MACHINE_NOTHING_TO_RUN) { fprintf(stderr, "Error: No boot image and no disk, so there is nothing to run.\n"); printHelp(argv[0]); return 1; } else if (started != MACHINE_OK) { return 1; } if (headless) { // The same three lines SplitBit runs, and deliberately so: a headless Voyager is // not a reduced machine, it is the machine with nobody watching. while (machineRunning(&machine)) { machineRunSlice(&machine); } } else { InitWindow(SCREEN_WIDTH * SCREEN_SCALE, SCREEN_HEIGHT * SCREEN_SCALE, "Segan Voyager"); SetTargetFPS(60); // ---- A slice a frame ---- // // The machine gets its turn, then the window gets its turn. Closing the window // stops the machine, and the machine halting leaves the window up so that whatever // it drew is still there to look at - a program that ends should not take its // output off the screen with it. while (!WindowShouldClose()) { if (machineRunning(&machine)) { machineRunSlice(&machine); } BeginDrawing(); // Not black. A screen with nothing driving it should look like a screen that // is on, rather than like a window that failed to open. ClearBackground((Color){ 18, 22, 20, 255 }); EndDrawing(); } CloseWindow(); } machineStop(&machine); return machineReport(&machine); }