diff --git a/CMakeLists.txt b/CMakeLists.txt index 99e9790..cdcd33a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ set(HEADERS stddclmr.h story.h text.h + ui.h variable.h window.h zscii.h @@ -53,6 +54,7 @@ set(SOURCE state.c story.c text.c + ui.c variable.c window.c zscii.c diff --git a/include/lib.h b/include/lib.h index 5154b9e..12a7d40 100644 --- a/include/lib.h +++ b/include/lib.h @@ -30,7 +30,9 @@ void libMemSet(byte *start, byte val, uint32_t len); char *libStrChr(char *haystack, char needle); +int8_t libStrICmp(char *a, char *b); uint32_t libStrLen(char *str); +char libToLower(char c); #endif // LIB_H diff --git a/include/messages.h b/include/messages.h index d42141d..5fea5c9 100644 --- a/include/messages.h +++ b/include/messages.h @@ -42,11 +42,13 @@ #define MSG_INT_V12_SHIFT "Add V1/V2 shifting." #define MSG_INT_V12_SHIFT_LOCK "Add V1/V2 shift locking." #define MSG_MEM_BUFFER "Unable to allocate memory buffer." +#define MSG_STA_CANNOT_ALLOCATE_STACK "Cannot allocate stack!" #define MSG_OP_CALL_TOO_MANY_LOCALS "Too many local variables! (%d)" #define MSG_OP_CALL_STACK_OVERFLOW "Stack overflow!" #define MSG_OP_INPUT_BUFFER_TOO_SMALL "Text buffer too small for reading." #define MSG_OP_OBJ_MISSING_PROPERTY "Missing object property." #define MSG_OP_WIN_NO_SPLITTING "Window splitting is not supported." +#define MSG_UI_CANNOT_ALLOCATE "Cannot allocate %l bytes!" #else // DEBUGGING #define MSG_UNIMPLEMENTED "" #define MSG_INT_INVALID_EXT_OPCODE "" @@ -61,11 +63,13 @@ #define MSG_INT_V12_SHIFT "" #define MSG_INT_V12_SHIFT_LOCK "" #define MSG_MEM_BUFFER "" +#define MSG_STA_CANNOT_ALLOCATE_STACK "" #define MSG_OP_CALL_TOO_MANY_LOCALS "" #define MSG_OP_CALL_STACK_OVERFLOW "" #define MSG_OP_INPUT_BUFFER_TOO_SMALL "" #define MSG_OP_OBJ_MISSING_PROPERTY "" #define MSG_OP_WIN_NO_SPLITTING "" +#define MSG_UI_CANNOT_ALLOCATE "" #endif // DEBUGGING diff --git a/include/portme.h b/include/portme.h index 308e7d8..3bb23d1 100644 --- a/include/portme.h +++ b/include/portme.h @@ -35,11 +35,20 @@ char portCharGet(void); void portCharGetPos(byte *x, byte *y); void portCharPrint(char c); void portCharSetPos(byte x, byte y); +void portColorSet(byte f, byte b); +void portCursorShow(bool s); void portDie(char *fmt, ...); -bool portFileRestore(void); -bool portFileSave(void); +uint32_t portFClose(void *stream); +void *portFOpen(char *pathname, char *mode); +uint32_t portFRead(void *ptr, uint32_t size, uint32_t nmemb, void *stream); +uint32_t portFWrite(void *ptr, uint32_t size, uint32_t nmemb, void *stream); +void portFileLister(void); +void portFree(void *ptr); +void *portMalloc(uint32_t size); uint16_t portRandomGet(int16_t range); -void portStoryLoad(void); +void portScreenClear(void); +void portStoryLoad(char *story, uint32_t *length); +char *portSymbolsGet(void); uint16_t portWordGet(uint32_t address); void portWordSet(uint32_t address, uint16_t value); diff --git a/include/state.h b/include/state.h index 7eda2c0..2b7c708 100644 --- a/include/state.h +++ b/include/state.h @@ -30,7 +30,7 @@ typedef struct stateS { - uint16_t stack[STACK_SIZE]; + uint16_t *stack; bool quit; uint32_t pc; // Program Counter uint16_t sp; // Stack Pointer diff --git a/include/story.h b/include/story.h index 70d4606..5e8566f 100644 --- a/include/story.h +++ b/include/story.h @@ -120,7 +120,7 @@ void storyChecksumCalculate(void); uint32_t storyLength(void); -void storySetup(byte width, byte height); +void storySetup(void); #endif // STORY_H diff --git a/include/ui.h b/include/ui.h new file mode 100644 index 0000000..bf7e2e1 --- /dev/null +++ b/include/ui.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef UI_H +#define UI_H + + +#include "common.h" + + +#define UI_KEY_UP 255 +#define UI_KEY_RIGHT 254 +#define UI_KEY_DOWN 253 +#define UI_KEY_LEFT 252 + + +void uiFileAdd(char *filename, uint32_t size); +void uiGameSelect(void); +char *uiSaveGet(void); +void uiSizeGet(byte *w, byte *h); +void uiSizeSet(byte w, byte h); +char *uiStoryGet(void); +uint32_t uiStorySizeGet(void); + + +#endif // UI_H diff --git a/ports/dos-like/CMakeLists.txt b/ports/dos-like/CMakeLists.txt index 338610d..d283e07 100644 --- a/ports/dos-like/CMakeLists.txt +++ b/ports/dos-like/CMakeLists.txt @@ -27,6 +27,7 @@ set(HEADERS stddclmr.h story.h text.h + ui.h variable.h window.h zscii.h @@ -52,6 +53,7 @@ set(SOURCE state.c story.c text.c + ui.c variable.c window.c zscii.c diff --git a/ports/dos-like/dos-like.c b/ports/dos-like/dos-like.c index 0657235..04d44a0 100644 --- a/ports/dos-like/dos-like.c +++ b/ports/dos-like/dos-like.c @@ -25,12 +25,12 @@ #include #include #include +#include #include "portme.h" #include "story.h" -#include "state.h" -#include "interpreter.h" #include "text.h" +#include "ui.h" #pragma push_macro("bool") #undef bool @@ -39,35 +39,26 @@ #pragma pop_macro("bool") -static char *_storyFile = NULL; static uint8_t *_RAM = NULL; -static uint8_t _attr = (1 << 4) + 7; +static uint8_t _attr = 0; void portAttributeSet(byte attribute) { switch (attribute) { case TEXT_ATTR_NORMAL: - textbackground(1); - textcolor(7); - _attr = (1 << 4) + 7; + portColorSet(7, 1); break; case TEXT_ATTR_REVERSE: - textbackground(7); - textcolor(1); - _attr = (7 << 4) + 1; + portColorSet(1, 7); break; case TEXT_ATTR_BOLD: - textbackground(1); - textcolor(15); - _attr = (1 << 4) + 15; + portColorSet(15, 1); break; case TEXT_ATTR_EMPHASIS: - textbackground(1); - textcolor(14); - _attr = (1 << 4) + 14; + portColorSet(14, 1); break; } } @@ -100,6 +91,10 @@ char portCharGet(void) { if (key == KEY_RETURN) c = 13; if (key == KEY_BACK) c = 127; if (key == KEY_DELETE) c = 8; + if (key == KEY_UP) c = (char)UI_KEY_UP; + if (key == KEY_RIGHT) c = (char)UI_KEY_RIGHT; + if (key == KEY_DOWN) c = (char)UI_KEY_DOWN; + if (key == KEY_LEFT) c = (char)UI_KEY_LEFT; } while (c == 0); return c; @@ -139,6 +134,22 @@ void portCharSetPos(byte x, byte y) { } +void portColorSet(byte f, byte b) { + textcolor(f); + textbackground(b); + _attr = (f << 4) + b; +} + + +void portCursorShow(bool s) { + if (s) { + curson(); + } else { + cursoff(); + } +} + + void portDie(char *fmt, ...) { va_list ap; @@ -152,56 +163,64 @@ void portDie(char *fmt, ...) { } -bool portFileRestore(void) { - - FILE *in; - bool ok = false; - - in = fopen("save.dat", "rb"); - if (in) { - ok = true; - - // Read in PC. - ok &= (fread(&__state.pc, sizeof(__state.pc), 1, in) == 1); - // Read in SP. - ok &= (fread(&__state.sp, sizeof(__state.sp), 1, in) == 1); - // Read in BP. - ok &= (fread(&__state.bp, sizeof(__state.bp), 1, in) == 1); - // Read in dynamic game RAM. - ok &= (fread(_RAM, storyStaticMemoryBaseAddress(), 1, in) == 1); - // Read in stack. - ok &= (fread(__state.stack, sizeof(__state.stack[0]), __state.sp, in) == __state.sp); - - fclose(in); - } - - return ok; +uint32_t portFClose(void *stream) { + return fclose((FILE *)stream); } -bool portFileSave(void) { - FILE *out; - bool ok = false; +void *portFOpen(char *pathname, char *mode) { + return fopen(pathname, mode); +} - out = fopen("save.dat", "wb"); - if (out) { - ok = true; - // Write out PC. - ok &= (fwrite(&__state.pc, sizeof(__state.pc), 1, out) == 1); - // Write out SP. - ok &= (fwrite(&__state.sp, sizeof(__state.sp), 1, out) == 1); - // Write out BP. - ok &= (fwrite(&__state.bp, sizeof(__state.bp), 1, out) == 1); - // Write out dynamic game RAM. - ok &= (fwrite(_RAM, storyStaticMemoryBaseAddress(), 1, out) == 1); - // Write out stack. - ok &= (fwrite(__state.stack, sizeof(__state.stack[0]), __state.sp, out) == __state.sp); +uint32_t portFRead(void *ptr, uint32_t size, uint32_t nmemb, void *stream) { + return fread(ptr, size, nmemb, (FILE *)stream); +} - fclose(out); + +uint32_t portFWrite(void *ptr, uint32_t size, uint32_t nmemb, void *stream) { + return fwrite(ptr, size, nmemb, (FILE *)stream); +} + + +void portFileLister(void) { + DIR *dir; + struct dirent *dirent; + uint32_t l; + FILE *in; + + if ((dir = opendir("."))) { + for (;;) { + if ((dirent = readdir(dir))) { + // Is this a file or symlink? + if ((dirent->d_type == DT_REG) || (dirent->d_type == DT_LNK)) { + // Get it's length. + in = fopen(dirent->d_name, "rb"); + if (in) { + fseek(in, 0, SEEK_END); + l = ftell(in); + fseek(in, 0, SEEK_SET); + fclose(in); + // Add it to the story chooser. + uiFileAdd(dirent->d_name, l); + } + } + } else { + break; + } + } + closedir(dir); } +} - return ok; + +void portFree(void *ptr) { + free(ptr); +} + + +void *portMalloc(uint32_t size) { + return malloc(size); } @@ -218,32 +237,43 @@ uint16_t portRandomGet(int16_t range) { } -void portStoryLoad(void) { +void portScreenClear(void) { + clrscr(); +} + + +void portStoryLoad(char *story, uint32_t *length) { FILE *in; - uint32_t length; - in = fopen(_storyFile, "rb"); - if (!in) portDie("Unable to open %s!\n", _storyFile); + in = fopen(story, "rb"); + if (!in) portDie("Unable to open %s!\n", story); - fseek(in, 0, SEEK_END); - length = ftell(in); - fseek(in, 0, SEEK_SET); - - _RAM = (byte *)malloc(length); + _RAM = (byte *)malloc(*length); if (!_RAM) { fclose(in); - portDie("Unable to allocate %u bytes!\n", length); + portDie("Unable to allocate %u bytes!\n", *length); } - if (fread(_RAM, length, 1, in) != 1) { + if (fread(_RAM, *length, 1, in) != 1) { free(_RAM); fclose(in); - portDie("Unable to read %s!\n", _storyFile); + portDie("Unable to read %s!\n", story); } fclose(in); +} - storyChecksumCalculate(); + +char *portSymbolsGet(void) { + // Box drawing and other symbols needed by the UI. + // + // 0---1---2 + // | | + // 7 3 + // | | + // 6---5---4 + + return "\xda\xc4\xbf\xb3\xd9\xc4\xc0\xb3"; } @@ -258,24 +288,14 @@ void portWordSet(uint32_t address, uint16_t value) { } +#include "unistd.h" int main(int argc, char *argv[]) { - - if (argc != 2) portDie("Usage: %s [storyfile]\n", argv[0]); - - _storyFile = argv[1]; + chdir("/home/scott/code/zip/stories"); setvideomode(videomode_80x25_9x16); - curson(); - textbackground(1); - textcolor(7); - clrscr(); - gotoxy(0, screenheight() - 1); - stateReset(); - portStoryLoad(); - opcodesSetup(); - storySetup(80, 25); - interpreterRun(); + uiSizeSet(80, 25); + uiGameSelect(); free(_RAM); diff --git a/ports/f256/CMakeLists.txt b/ports/f256/CMakeLists.txt index 92fc3c2..5e02c29 100644 --- a/ports/f256/CMakeLists.txt +++ b/ports/f256/CMakeLists.txt @@ -25,6 +25,7 @@ set(HEADERS stddclmr.h story.h text.h + ui.h variable.h window.h zscii.h @@ -52,6 +53,7 @@ set(SOURCE state.c story.c text.c + ui.c variable.c window.c zscii.c diff --git a/ports/f256/build.sh b/ports/f256/build.sh index 497c4b7..11fee28 100755 --- a/ports/f256/build.sh +++ b/ports/f256/build.sh @@ -24,7 +24,7 @@ PROJECT=f256zip -START=0x200 +START=0x300 F256=$(pwd)/../../../f256 LLVM=${F256}/llvm-mos @@ -50,8 +50,8 @@ ${F256}/header \ pgz 24 \ ../${PROJECT}.pgz \ ${START} \ - ${PROJECT}.bin ${START} \ - ../../../stories/zork1-r119-s880429.z3 0x10000 + ${PROJECT}.bin ${START} +# ../../../stories/zork1-r119-s880429.z3 0x10000 # ../../../tests/testers/czech/czech.z3 0x10000 #llvm-nm ${PROJECT}.elf > ${PROJECT}.lst diff --git a/ports/f256/f256zip.c b/ports/f256/f256zip.c index c1f76a0..51aaf94 100644 --- a/ports/f256/f256zip.c +++ b/ports/f256/f256zip.c @@ -38,35 +38,31 @@ #include "f256.h" #include "portme.h" -#include "story.h" #include "memory.h" -#include "state.h" -#include "interpreter.h" +#include "ui.h" + #include "../../include/text.h" #define BASE_ADDRESS 0x10000 -static char *_saveName = 0; - - void portAttributeSet(byte attribute) { switch (attribute) { case TEXT_ATTR_NORMAL: - textSetColor(7, 1); + portColorSet(7, 1); break; case TEXT_ATTR_REVERSE: - textSetColor(1, 7); + portColorSet(1, 7); break; case TEXT_ATTR_BOLD: - textSetColor(15, 1); + portColorSet(15, 1); break; case TEXT_ATTR_EMPHASIS: - textSetColor(14, 1); + portColorSet(14, 1); break; } } @@ -82,6 +78,26 @@ void portByteSet(uint32_t address, uint8_t value) { } +char portCharGet(void) { + char c; + +#if 0 + static char playback[] = "open mailbox\ntake leaflet\nread leaflet\ndrop leaflet\n"; + static uint32_t pointer = 0; + + if (pointer < libStrLen(playback)) return playback[pointer++]; +#endif + + c = getchar(); + if (c == 16) c = (char)UI_KEY_UP; + if (c == 6) c = (char)UI_KEY_RIGHT; + if (c == 14) c = (char)UI_KEY_DOWN; + if (c == 2) c = (char)UI_KEY_LEFT; + + return c; +} + + void portCharGetPos(byte *x, byte *y) { byte tx; byte ty; @@ -106,6 +122,20 @@ void portCharSetPos(byte x, byte y) { } +void portColorSet(byte f, byte b) { + textSetColor(f, b); +} + + +void portCursorShow(bool s) { + if (s) { + textSetCursor(199); + } else { + textSetCursor(0); + } +} + + void portDie(char *fmt, ...) { #ifdef DEBUGGING printf("%s\n", fmt); // Yeah, this isn't right. @@ -114,78 +144,55 @@ void portDie(char *fmt, ...) { } -bool portFileRestore(void) { - - FILE *in; - bool ok = false; - uint32_t i; - byte b; - - in = fopen(_saveName, "rb"); - if (in) { - ok = true; - - // Read in PC. - ok &= (fread(&__state.pc, sizeof(__state.pc), 1, in) == 1); - // Read in SP. - ok &= (fread(&__state.sp, sizeof(__state.sp), 1, in) == 1); - // Read in BP. - ok &= (fread(&__state.bp, sizeof(__state.bp), 1, in) == 1); - // Read in dynamic game RAM. - for (i=0; id_type)) { + // Add it to the story chooser. + uiFileAdd(dirent->d_name, dirent->d_blocks * 256); + } + } else { + break; + } + } + closedir(dir); + } +} + + +void portFree(void *ptr) { + free(ptr); +} + + +void *portMalloc(uint32_t size) { + return malloc(size); } @@ -204,16 +211,58 @@ uint16_t portRandomGet(int16_t range) { } -void portStoryLoad(void) { +void portScreenClear(void) { + textClear(); +} - // On the F256, the story is loaded into RAM along with the ZIP. - // ***TODO*** This likely breaks the "restart" command. - // Later, we probably want to see if the user has a memory expansion - // installed. If they do, we can use it and then use the lower memory - // for graphics and sound. +void portStoryLoad(char *story, uint32_t *length) { + FILE *in; + uint32_t i; + byte r; + byte b; + uint32_t newLength; - storyChecksumCalculate(); + in = fopen(story, "rb"); + if (!in) portDie("Unable to open %s!\n", story); + + // The F256 only returns file sizes in blocks of 256. + // We load what we can until we get an error and then + // see if we're within 256 to determine success. + i = BASE_ADDRESS; + do { + r = fread(&b, 1, 1, in); + if (r == 1) ZPOKE(i++, b); + } while (r == 1); + + fclose(in); + + newLength = *length - (i - BASE_ADDRESS); + if (newLength > 256) portDie("Unable to read %s!\n", story); + + // Update story length. + *length = newLength; +} + + +char *portSymbolsGet(void) { + // Box drawing and other symbols needed by the UI. + // + // 0---1---2 + // | | + // 7 3 + // | | + // 6---5---4 + + // 160 a0 + // 150 96 + // 161 a1 + // 130 82 + // 163 a3 + // 150 96 + // 162 a2 + // 130 82 + return "\xa0\x96\xa1\x82\xa3\x96\xa2\x82"; } @@ -248,8 +297,6 @@ int main(void) { { 0xff, 0xff, 0xff } }; - _saveName = "zork1.sav"; - f256Init(); // Load EGA palette into text CLUT. @@ -259,24 +306,30 @@ int main(void) { } textEnableBackgroundColors(true); - textSetColor(15, 0); - textPrint("Welcome to MUDDLE Beta 1, Another Z-Machine!\n\n"); - textSetColor(7, 0); - textPrint("Copyright 2024 Scott Duensing, All Rights Reserved.\n"); - textPrint("Kangaroo Punch Studios https://kangaroopunch.com\n\n\n"); - textSetColor(8, 0); - textPrint("Thinking..."); + /* + uint16_t b; + c = 0; + char ch[2] = {0,0}; + for (b=1; b<256; b++) { + ch[0] = (char)b; // (b == 13 ? ' ' : (char)b); + textPrint(ch); + textPrint("="); + textPrintInt(b); + c++; + if (c > 11) { + c = 0; + textPrint("\n"); + } else { + if (b<100) textPrint(" "); + if (b<10) textPrint(" "); + textPrint(" "); + } + } + portCharGet(); + */ - stateReset(); - portStoryLoad(); - opcodesSetup(); - storySetup(80, 30); - - textSetColor(7, 1); - textClear(); - textSetCursor(199); - - interpreterRun(); + uiSizeSet(80, 30); + uiGameSelect(); return 0; } diff --git a/ports/zip.c b/ports/zip.c index 4a80511..061ae78 100644 --- a/ports/zip.c +++ b/ports/zip.c @@ -40,6 +40,7 @@ #include "../src/state.c" #include "../src/story.c" #include "../src/text.c" +#include "../src/ui.c" #include "../src/variable.c" #include "../src/window.c" #include "../src/zscii.c" diff --git a/src/interpreter.c b/src/interpreter.c index 699ff54..513ca13 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -31,6 +31,7 @@ #include "variable.h" #include "memory.h" #include "lib.h" +#include "text.h" void interpreterDoBranch(int32_t truth) { @@ -136,10 +137,7 @@ void interpreterRun(void) { opcodeT op; bool eight; - __state.savedX = 1; - __state.savedY = storyScreenHeightLines(); - portCharSetPos(__state.savedX, __state.savedY); - + // Go! while (__state.quit == 0) { opcode = ZPEEK(__state.pc++); extended = ((opcode == 190) && (storyVersion() >= 5)) ? true : false; diff --git a/src/lib.c b/src/lib.c index b8ef484..16bf562 100644 --- a/src/lib.c +++ b/src/lib.c @@ -43,8 +43,25 @@ char *libStrChr(char *haystack, char needle) { } +int8_t libStrICmp(char *a, char *b) { + char d; + for (;; a++, b++) { + d = libToLower((unsigned char)*a) - libToLower((unsigned char)*b); + if (d != 0 || !*a) { + return d; + } + } +} + + uint32_t libStrLen(char *str) { uint32_t l = 0; while (str[l] != 0) l++; return l; } + + +char libToLower(char c) { + if ((c >= 'A') && (c <= 'Z')) c = c - ('A' - 'z'); + return c; +} diff --git a/src/oc_save.c b/src/oc_save.c index 1fa9ab0..c3affb0 100644 --- a/src/oc_save.c +++ b/src/oc_save.c @@ -27,30 +27,76 @@ #include "portme.h" #include "story.h" #include "opcodes.h" -#include "memory.h" #include "window.h" +#include "memory.h" +#include "ui.h" void opcodes_save(void) { - interpreterDoBranch(portFileSave() ? 1 : 0); + void *out; + bool ok = false; + uint32_t i; + byte b; + + out = portFOpen(uiSaveGet(), "wb"); + if (out) { + ok = true; + // Write out PC. + ok &= (portFWrite(&__state.pc, sizeof(__state.pc), 1, out) == 1); + // Write out SP. + ok &= (portFWrite(&__state.sp, sizeof(__state.sp), 1, out) == 1); + // Write out BP. + ok &= (portFWrite(&__state.bp, sizeof(__state.bp), 1, out) == 1); + // Write out dynamic game RAM. + for (i=0; i 3) { + if ((filename[l - 2] == 'z') || (filename[l - 2] == 'Z')) { + if (filename[l - 3] == '.') { + v = filename[l - 1] - '0'; + if (v <= 3) { + // Add game to chooser. + + // Create new game entry. + newGame = (fileListT *)portMalloc(sizeof(fileListT)); + if (newGame == 0) portDie(MSG_UI_CANNOT_ALLOCATE, sizeof(fileListT)); + newGame->filename = (char *)portMalloc(l + 1); + if (newGame->filename == 0) portDie(MSG_UI_CANNOT_ALLOCATE, l + 1); + for (v=0; vfilename[v] = filename[v]; + newGame->filesize = size; + newGame->filenameLen = l; + newGame->next = 0; + + // Insert into list, sorted by filename. + if ((_games == 0) || (libStrICmp(_games->filename, newGame->filename) >= 0)) { + newGame->next = _games; + _games = newGame; + } else { + currentGame = _games; + while ((currentGame->next != 0) && (libStrICmp(currentGame->next->filename, newGame->filename) < 0)) { + currentGame = currentGame->next; + } + newGame->next = currentGame->next; + currentGame->next = newGame; + } + } + } + } + } +} + + +void uiGameSelect(void) { + byte x; + byte y; + byte h; + byte w; + byte l; + uint16_t i; + fileListT *g; + byte c; + uint16_t top; + uint16_t selector; + + // ***TODO*** This doesn't check if filenames are too long to fit on the screen. + + portCursorShow(false); + uiTitleDraw(); + portCharGetPos(&x, &y); + y += 2; + + portFileLister(); + + // No games? + if (_games == 0) { + uiDialog(15, 4, 14, y, " No story files found! "); + portCharGet(); + return; + } + + // Count games and find longest. + g = _games; + _gameCount = 0; + while (g) { + _gameCount++; + if (g->filenameLen > _longestName) _longestName = g->filenameLen; + g = g->next; + } + + // Draw chooser window. + x = (_screenWidth - _longestName - 4) * 0.5; + w = _longestName + 4; + h = _screenHeight - y; + portColorSet(15, 1); + uiBox(x, y, w, h); + + // Adjust for future drawing. + x++; + y++; + h -= 2; + w -= 2; + + // Run chooser. + top = 0; + selector = 0; + do { + g = _games; + // Skip games that have scrolled off the top. + for (i=0; inext; + } + // Draw list and selector. + for (i=0; ifilename); + for (c=g->filenameLen+1; cnext; + } + // Get input. + c = portCharGet(); + if (c == UI_KEY_DOWN) { + if ((selector < h-1) && (selector < _gameCount-1)) { + selector++; + } else { + if (top + h < _gameCount) { + top++; + } + } + } + if (c == UI_KEY_UP) { + if (selector > 0) { + selector--; + } else { + if (top > 0) { + top--; + } + } + } + } while (c != 13); + + // Loading dialog. + uiDialog(15, 8, 15, y + 3, " Loading story! "); + + // Generate save game name. + _saveName = (char *)portMalloc(_selected->filenameLen + 2); + if (_saveName == 0) portDie(MSG_UI_CANNOT_ALLOCATE, _selected->filenameLen + 2); + for (i=0; i<_selected->filenameLen; i++) _saveName[i] = _selected->filename[i]; + _saveName[_selected->filenameLen - 2] = 's'; + _saveName[_selected->filenameLen - 1] = 'a'; + _saveName[_selected->filenameLen ] = 'v'; + _saveName[_selected->filenameLen + 1] = 0; + + // Get ready. + stateReset(); +// uiStringPrint("stateReset()\n"); + portStoryLoad(_selected->filename, &_selected->filesize); +// uiStringPrint("portStoryLoad()\n"); + storyChecksumCalculate(); +// uiStringPrint("storyChecksumCalculate()\n"); + opcodesSetup(); + storySetup(); + + // Set up display. + portAttributeSet(TEXT_ATTR_NORMAL); + portScreenClear(); + __state.savedX = 1; + __state.savedY = storyScreenHeightLines(); + portCharSetPos(__state.savedX, __state.savedY); + portCursorShow(true); + + // Start the game! + interpreterRun(); +} + + +char *uiSaveGet(void) { + return _saveName; +} + + +void uiSizeGet(byte *w, byte *h) { + *w = _screenWidth; + *h = _screenHeight; +} + + +void uiSizeSet(byte w, byte h) { + _screenWidth = w; + _screenHeight = h; +} + + +char *uiStoryGet(void) { + return _selected->filename; +} + + +uint32_t uiStorySizeGet(void) { + return _selected->filesize; +} + + +static void uiStringPrint(char *s) { + byte i = 0; + while (s[i] != 0) portCharPrint(s[i++]); +} + + +static void uiTitleDraw(void) { + + portColorSet(9, 0); + portScreenClear(); + portCharSetPos(1, 1); + + uiCenter(" Welcome to... "); + portColorSet(11, 0); + uiCenter(" __ __ _ _ _ "); + uiCenter("| \\/ | | | | | | "); + uiCenter("| \\ / |_ _ __| | __| | | ___ "); + uiCenter("| |\\/| | | | |/ _` |/ _` | |/ _ \\"); + uiCenter("| | | | |_| | (_| | (_| | | __/"); + uiCenter("|_| |_|\\__,_|\\__,_|\\__,_|_|\\___|"); + uiCenter(""); + portColorSet(9, 0); + uiCenter(" Release 1.0 Beta 2 "); + uiCenter(""); + portColorSet(12, 0); + uiCenter("Copyright 2024 Scott Duensing, All Rights Reserved"); + portColorSet(4, 0); + uiCenter("2024 Kangaroo Punch Studios https://kangaroopunch.com"); +}