// Space Taxi front-end harness: the immortal-cabbies table and the name // entry screen, driven with scripted keys on the host (BLANK port). // // spacetaxi.c is INCLUDED so its static state and routines are visible; // its main() is renamed away. jlInputGetChar, jlSaveWrite and jlSaveRead // are replaced through the linker's --wrap with a key script and an // in-memory save image (see the Makefile). #define main staxiMain #include "spacetaxi.c" #undef main #include #define SAVE_IMAGE_MAX 512u bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len); uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max); int __wrap_jlInputGetChar(void); static void check(bool ok, const char *what); static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out); static void setScore(uint8_t player, const uint8_t *digits); static const char *gKeys = ""; static uint8_t gSaveImage[SAVE_IMAGE_MAX]; static uint32_t gSaveLen = 0u; static int gFails = 0; int __wrap_jlInputGetChar(void) { if (*gKeys == 0) { return -1; } return (unsigned char)*gKeys++; } uint32_t __wrap_jlSaveRead(const char *name, void *buf, uint32_t max) { (void)name; if (gSaveLen == 0u || gSaveLen > max) { return 0u; } memcpy(buf, gSaveImage, gSaveLen); return gSaveLen; } bool __wrap_jlSaveWrite(const char *name, const void *buf, uint32_t len) { (void)name; if (len > sizeof(gSaveImage)) { return false; } memcpy(gSaveImage, buf, len); gSaveLen = len; return true; } static void check(bool ok, const char *what) { printf("%s %s\n", ok ? "ok " : "FAIL", what); if (!ok) { gFails++; } } // `n` screen cells of one row as a C string. static void rowText(uint8_t row, uint8_t col, uint8_t n, char *out) { uint8_t k; for (k = 0u; k < n; k++) { out[k] = (char)gSim.screen[ST_CELL(row, col + k)]; } out[n] = 0; } // A player's score as the sim keeps it: seven screen-code digits with // the point at ST_HS_POINT_AT. static void setScore(uint8_t player, const uint8_t *digits) { uint8_t k; for (k = 0u; k < ST_NUMBER_CHARS; k++) { gSim.scoreBackup[player][k] = (uint8_t)(ST_CHAR_DIGIT_BASE + digits[k]); } gSim.scoreBackup[player][ST_HS_POINT_AT] = ST_CHAR_POINT; } int main(void) { static const uint8_t kDigits[ST_NUMBER_CHARS] = { 1u, 2u, 3u, 4u, 0u, 5u, 6u }; jlConfigT config; jlSurfaceT *stage; char buf[48]; uint8_t k; config.codegenBytes = 160UL * 1024; config.audioBytes = 32UL * 1024; if (!jlInit(&config)) { printf("jlInit failed\n"); return 1; } stage = jlStageGet(); memset(&gGame, 0, sizeof(gGame)); memset(&gSim, 0, sizeof(gSim)); stRenderInit(stage); stAudioInit(); stSimNewGame(&gSim, 2u, false); // ---- ranking ($4D92) ---- check(highScoreInsert((const uint8_t *)" 49.99") == ST_HS_ENTRIES, "49.99 is below the $50 floor"); check(highScoreInsert((const uint8_t *)" 50.00") == ST_HS_ENTRIES, "50.00 passes the floor but beats nobody"); check(highScoreInsert((const uint8_t *)" 500.00") == 8u, "500.00 lands in slot 8 (under 504.97, above 498.77)"); check(memcmp(gHighScores[9].score, " 498.77", 7) == 0 && memcmp(gHighScores[7].score, " 504.97", 7) == 0, "older entries shifted down, the last one dropped"); check(highScoreInsert((const uint8_t *)"3877.56") == 0u, "a tie goes above the older entry"); check(memcmp(gHighScores[1].score, "3877.56", 7) == 0 && memcmp(gHighScores[1].name, "MICHAEL PLATE ", 15) == 0, "the tied old entry is now second"); // ---- the entry screen for cabbie 2 with $1234.56 on shift 3 ($4D52 / $4E35) ---- gSim.player = 1u; gGame.variation = 3u; setScore(1u, kDigits); gKeys = "zz"; // typed before the screen: flushed like $0277 check(nameEntryStart(), "1234.56 ranks"); check(gGame.state == ST_STATE_NAME_ENTRY && gGame.hsSlot == 3u, "slot 3 (under the two 3877.56 and 1809.51)"); check(gSim.borderColor == 2u && gSim.bgColor == 15u, "red border, grey field"); rowText(3u, 7u, 25u, buf); check(strcmp(buf, "CONGRATULATIONS CABBIE 2,") == 0, buf); rowText(0u, 15u, 8u, buf); check(strcmp(buf, "$1234.56") == 0, buf); rowText(9u, 9u, 21u, buf); check(strcmp(buf, "ENTER YOUR NAME BELOW") == 0, buf); check(*gKeys == 0, "earlier keystrokes discarded"); // ---- typing ($4ED8): s c o 9 t t T ---- gKeys = "sco9tt\bT"; runNameEntry(1u); rowText(ST_HS_NAME_ROW, ST_HS_NAME_COL, 5u, buf); check(strcmp(buf, "SCOTT") == 0, buf); check(gGame.hsLen == 5u, "five characters kept ('9' ignored, one deleted, one retyped)"); check(gGame.state == ST_STATE_NAME_ENTRY, "still entering"); for (k = 0u; k < 2u; k++) { runNameEntry(1u); } check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == ' ', "first flip blanks the cursor ($4F4D)"); for (k = 0u; k < 3u; k++) { runNameEntry(1u); } check(gSim.screen[ST_CELL(ST_HS_NAME_ROW, ST_HS_NAME_COL + 5u)] == '%', "second flip shows it"); gKeys = "\r"; runNameEntry(1u); check(gGame.state != ST_STATE_NAME_ENTRY, "RETURN leaves the screen"); check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && gHighScores[3].shift == 3u, "name and shift stored"); check(gSaveLen == 240u && gSaveImage[3 * 24 + 7] == 0 && memcmp(&gSaveImage[3 * 24 + 8], "SCOTT ", 6) == 0 && gSaveImage[3 * 24 + 23] == 3u, "saved in the C64 image layout"); check(gSim.playersDone == 1u, "game-over bookkeeping continued"); // ---- reload from the image ($23E5) ---- memset(gHighScores, 0, sizeof(gHighScores)); highScoreLoad(); check(memcmp(gHighScores[3].name, "SCOTT ", 15) == 0 && memcmp(gHighScores[0].score, "3877.56", 7) == 0, "table restored from the save"); // ---- an empty name becomes "*" ---- gSim.player = 0u; setScore(0u, kDigits); check(nameEntryStart() && gGame.hsSlot == 3u, "second entry, a tie goes above"); gKeys = "\r"; runNameEntry(1u); check(gHighScores[3].name[0] == '*' && memcmp(gHighScores[4].name, "SCOTT", 5) == 0, "an empty name is stored as *"); // ---- the table screen ($4C1F) ---- highScoreDraw(); rowText(1u, 9u, 20u, buf); check(strcmp(buf, "THE IMMORTAL CABBIES") == 0, buf); rowText(12u, 4u, 26u, buf); check(strcmp(buf, "$1234.56 SCOTT ") == 0, buf); check(gSim.screen[ST_CELL(12, 34)] == '3' && gSim.screen[ST_CELL(10, 15)] == '*', "shift digit in the SHIFT column, * name above"); rowText(24u, 3u, 34u, buf); check(strcmp(buf, "PRESS FIRE TO RETURN TO TITLE PAGE") == 0, buf); check(jlMusicIsPlaying(), "song 7 under the table"); printf("%s (%d failures)\n", gFails ? "staxiharness: FAIL" : "staxiharness: PASS", gFails); jlShutdown(); return gFails ? 1 : 0; }