NEWS, EDITORIALS, REFERENCE

Subscribe to C64OS.com with your favorite RSS Reader
January 26, 2024#123 Software

Eliza for C64 OS

Post Archive Icon

I'm super busy, so I'm going to have to keep this blog post short. First a couple of updates. I had a great time at World of Commodore in December 2023. I had a nice vendor table there, I think it was the nicest I've ever put together.

The top image is how I imagined it, when I set it up on my basement floor. The bottom is how it ended up on a table at the show. I had three machines set up, each with a quite different collection of hardware, but each showing C64 OS v1.05 running and available for live demo.

There's an Ulimate64 with a cableless SD2IEC and a 1581 connected to a Commodore 1702 monitor, with a MouSTer and black wireless wheel mouse. This was the machine I used to give my presentation. There is also the C64 Luggable, which is a C64 Reloaded MK2 with a UltimateII+, MicroMys 5 and wired wheel mouse. And squeezed in the middle is TheC64 mini, mounted to the underside of a tiny black HDMI monitor on a stand I built for it. It's running VICE with C64 OS installed on a virtual IDE64. It's got a wireless keyboard and a TheMouse, USB tank mouse.

Vendor table at WoC2023.
Vendor table at WoC2023.

The presentation went well, in my opinion. People *gasped* when I ended the presentation with the 3D teapot. I also demoed a variety of other new technologies that were added to C64 OS throughout 2023; disk image mounting in File Manager, mouse wheel support in Toolkit, Fast App Switching with REU and Fast Reboot, custom boot modes and bitmap boot screens, and of course the new animations and 3D matrix images in Image Viewer.

The presentation was recorded, both myself and the screen, and I am waiting for TPUG to post the video to their YouTube channel, so I can post it here and on social media.

I'm now working on the next thing for C64 OS. I'm working on a multi-line text area Toolkit control. I've got it mostly working, still a few bugs to work out. I'm excited by its addition because each new class opens the door for new opportunities to create software for the C64 with ease.

This video shows it crammed at the bottom of the Today Utility and without a scroll bar, etc. This is just an early test build that I was excited to share.

Some other updates include: I finished and published the Advanced set up for IDE64 section of the C64 OS User's Guide's Chapter 2: Installation. And I split up the Software Updates page which used to have three sections, into three separate pages:

But, following World of Commodore, and in time for Christmas morning, I dropped a little surprise. I released an Eliza Application for C64 OS! And that's what the rest of this post is about.

 

ChatGPT

Everyone is talking about ChatGPT and how amazing it is. And, I get it, it is pretty mind-bending. It's not quite the human-level intelligence that some people pretend it is though. Tricking it, or revealing that it's just a large language model is not hard at all.

As some people know (it's in my Twitter Bio) I speak Esperanto. ChatGPT, amazingly, can shift between languages essentially like a human can, who's fluent in different languages. It's pretty wild. Anyway, Esperanto has a kind of lego-like structure. Words are composed of immutable blocks that can be snapped together to form more complex concepts. Ethnic languages have this essential ability too, but there are always hordes of exceptions. For example, the word for message in English is, message. But the word for a person who carries or delivers a message is not a messager, but a messenger. There is always a long history behind every weird construction, and that's totally fine. However, part of what makes Esperanto easy to learn and easy to use is that this never happens. The word for message in Esperanto is, mesaĝo. The root being "mesaĝ" with the "o" stuck on the end to make it a regular noun. The word for a messenger in Esperanto is, mesaĝisto. The -ist- suffix, meaning someone who does a thing professionally or as a serious hobby, is inserted between the root and the final noun marker.

Blah blah blah, right? This isn't a blog about how cool Esperanto is. It's a blog about the Commodore 64. We're getting there, bear with me. The point is, Esperanto is meant to be easy for people, but incidently it's even easier for computer software because it's just so regular. So I asked ChatGPT, hey, can you give me a "long verb" in Esperanto. I thought it would just look at a list of verbs, count up the letters in those verbs and come back with one. Instead it attempted to compose a novel verb using the Esperanto lego-like word-building mechanism.

Long Verb 1. Long Verb 2.

Then it literally spits out the words, "this is a compound verb formed by combining roots and affixes:" followed by a list of the word parts, ending with "• on (noun-forming suffix)". How can you form a verb, by ending it with a noun-forming suffix? That's easy; it has no clue what it's doing. It sounds good, but it clearly has no intelligence behind it. First of all, if you don't know how the language works, congratulations, join 99.9% of the world. But why pretend like you know what you're doing, only to produce total nonsense?

Next, I asked it how many verbs there are in Esperanto. The answer for French, by the way, (if you can believe ChatGPT's response) is between 12,000 and 18,000, depending on how you count them. But the answer for Esperanto is that there are 16 verbs, and all the others are created by derivations. This is patently insane. My guess is that it's a confabulation from the famous (if you swim in these circles) 16 rules of grammar. Which, by the way, are nowhere even near enough to describe the grammar of Esperanto, so that's not even true. It just comes from the fact that the first textbook explicitly listed 16 rules, followed by a lot of examples of usage, from which people had to implicitly absorb the grammatical complexities the way an infant imbibes the rules of their parents' language when it's spoken at them. Immediately after ChatGPT said there are only 16 verbs, I said, give me a list of 50 Esperanto verbs, and 'lo and behold, it gave me a list of 50 common verbs... none of which are obtained by derivation.

In other words, ChatGPT is an extraordinarily complex version of Eliza. Its scope and breadth of ability is staggering. It's a language model with more or less the entire internet's textual data as its study corpus. And it is still as dumb as a stone. Even a child would know not to say, there are 5 types of toy in the world, and then if asked to show you some toys immediately show you 25 different types of toy. But this is exactly what you can expect from ChatGPT.1

I think my point is that human intelligence is still rather magical. I remember when my sister had kids (before I had my own to torment), I was sitting on the floor with one of my nieces, and they had these little farm animals cut out of foam. The foam was monochromatic, so each animal was effectively just a silhouette. I held up a cow and said, "this one is a pig." At 3 years old, she just laughed and called me silly, and said, "no that one's a cow." Short of seriously gaslighting this child, she knew I was pulling her leg, and never waivered from believing that it's a cow. And the weirdest thing is, it's not really a cow. It's just a small piece of static red foam cut vaguely in the silhouette of one possible angle by which we can perceive one particular type of cow. Furthermore, this child has not been trained on millions and millions of photographs of cows. Nonetheless, she knows I'm playing games with her, even finds the game funny, and still totally knows that this thing represents a cow. That's incredible. Humans are amazing. If you told ChatGPT, no that's a pig, it would probably say, "You're right, I apologize for my prior error, that is in fact a pig," even when it's not.

Eliza

If you, like me, think that ChatGPT is as dumb as a stone, well Eliza is on a whole 'nother level of dumb. But, and this is an important "but," no one ever pretended that Eliza was more than a fun and trivial piece of software. I.e., it's allowed to be dumb, and it's still charming.

When I was a kid, I had a copy of Eliza, written in BASIC, on the Commodore 64. I thought it was fantastic. My friend had something similar on his MS-DOS PC. We played around with both of them quite a bit. I don't know what ever happened to that copy, it's lost somewhere among my hundreds of disks, many of which sadly have succumbed to age and mould. But when my son started asking me about ChatGPT I told him about Eliza, "... back when I was a kid... !"

It got me thinking about Eliza, and I found the source code to a BASIC port on GitHub. And that was it! I was hooked. For a week or two I became totally obsessed with Eliza again. It's only about 200 lines of code, including all the data, but how it works is not immediately obvious. I spent a couple of nights just looking through it, and sorting out what exactly those uncommented lines of code actually do. When I got it figured out, I typed it up on the C64, and made a few slight modifications and fixes so I could play around with it on my Commodore. It is both much simpler than I imagined it was back when I was a kid, and in other ways it is admirably clever for such a short program that was written almost 60 years ago.

Let's analyze how the BASIC code works, just for fun.

Eliza in BASIC, an analysis

We'll take the program one gist at a time.

Initialization

One of the biggest problems with implementing a program, such as Eliza, in BASIC is that you are limited by the types of data structures that BASIC provides. We need to have arrays of strings (or some other data structure that points to the strings). In the original I got from GitHub, line 90 was a series of DIM initializations. I don't know why it did this, since, these variables are not used as dimensions later in the program. It could be that this was required for some other version of BASIC, so on the C64 I just commented that line out.

What we really need are the dimensions s, r and n, each able to hold 36 numbers. These values are used to represent the starting index of a set of replies (s for start), the current index for the next reply (r for reply), and the total number of replies in the set (n for number of replies.)

Next, there are 3 nX values; n1=36, n2=12 and n3=112. These are indexes into a large number of data statements, that mark the boundary of where a set of data items starts. There are four sets of data items: keywords, conjugation pairs, replies, and data that match up keywords to replies. The data statements come in that order. So, the keywords start at index 0. I.e., the keyword data statements are the first data statements in the program (which all come at the end.) n1 is 36, because there are exactly 36 keywords (0 to 35), thus, 36 is where the conjugation pairs data start. n2 is 12 because there are 6 conjugation pairs for a total of 12 data items. n3 is 112 because there are 112 replies. And then the data for matching keywords to replies follows that.

If you want to find the data for replies, you start at index 0 (which is accomplished with the "restore" BASIC command), then scan through the data statements n1 + n2 times. That skips over the 36 keywords and the 6 conjugation pairs (12 items) and boom, now the internal data pointer is at the start of the replies. It took me a while to figure out that this is what it was doing. But, you'll see this pattern in several places in the program.

In fact, we first see this on line 120. It's just a for-next loop that increments x from 1 to n1 + n2 + n3, and calls "read z$" once for each interation. Now we know what this is doing. It's moving the data pointer to the start of the last set of data.

Lines 130 to 150 are doing something tricky again. They loop n1 times, or 36 times. That's one interation for every keyword. But, the read statement fetches 2 data items on each iteration. In total, this loop will fetch 72 (36 + 36) data items, or 36 pairs of numbers, one pair for each keyword. The first number fetched goes into the "s" dimension at x. So the first number in each pair is an index into the set of replies for keyword x. The second number it pulls as "l", which presumably stands for length, for the length of the number of replies for keyword x.

This already gives us a basic idea of how this thing works. There are 36 keywords and 112 replies. But, not every keyword gets the same number of replies. There are groups of replies, some keywords get 3 replies, some get 4 replies, some get 5 or 6 or more replies. Additionally, sometimes different keywords resolve to the same set of replies. We'll see more of this when we get to the data statements themselves.

While reading in these data pairs that associate replies to keywords, s(x) takes the start index of a group of replies, r(x) gets initialized to the same value as s(x) and n(x) gets set to the last index of the set. We'll see later how this works, but we can sort of already get the idea. When a keyword is found, we take the keyword's index as x, and use it to look up a reply index from r(x). But THEN, we'll increment r(x), so the next time the same keyword is found we'll get the next reply in the set for that keyword. When r(x) exceeds n(x), it will get set back to s(x) (the start index), so the set of replies for a given keyword gets continually looped over each time that keyword appears.

Initialization is complete, and it spits out the standard "Hi! I'm Eliza..." message.

Input

User input is fairly basic. But, it has a little easter egg, and a simple trick to help keep the conversation flowing. You give your input, and it starts by padding it with spaces at the start and end. Next, it does a loop and some string manipulation to scan through the string and remove any apostrophes. This helps with the keyword matching. "i'm" and "im", "can't" and "cant" suddenly become the same things.

It also checks for the presense of the word "shut". If you use that word, it prints "shut up..." and the program abruptly ends. This has got to be an easter egg. In fact, when I was testing Eliza for BASIC on the C128 (which works great, btw), I encountered the easter egg in a most hilarious way (I totally forgot about its existence.)

Eliza tells me to shut up.
Eliza tells me to shut up.

Eliza for BASIC never needs to output your input. Because, your input appears on the screen as you're typing it. And after that it just gets scrolled up the screen by the standard screen editor. It is, therefore, free to strip away the apostrophes and never needs to worry about directly printing your input variable after that. It does, however, check to see if your input is equal to p$. If it's equal, it says, don't repeat yourself. We'll see later in the program that it copies i$ to p$ before fetching your next input. This is just a little trick to prevent you from saying, "I know you are, but what am I?" over and over again like a petulant child.

Keyword Search

Now that you've submitted some input and that input has been slightly formatted, it needs to search your input string for a keyword.

Some standard but slightly complicated string routines ensue in BASIC. S is initialized to 0, then it for-loops on k from 1 to n1, which is 36, the number of keywords. It begins with a restore which moves the data pointer back to the first data statement. Because the keywords are in the first data statements there is no need to skip over any.

On each iteration it reads a keyword into k$. (k is the numeric index, k$ is the keyword at that index.) If s is > 0 it means a keyword has already been found, so it just skips down to the next k, so once a keyword has been found it continues to loop and read through to the end of the keyword data statements. But, until a keyword is found, at lines 320 to 350 it loops through the length of the string checking for the presence of the current keyword. Higher level languages, like C, have a function for this, like strstr() or strpos(), but in BASIC it has to be done manually with a loop and mid$() and len() and manual checks.

As soon as a keyword match is found s is set to the keyword index k, and t gets set to the index within the string immediately following the keyword. The keywords loop continues, but, and this is important, once the first keyword has been found anywhere in the input the rest of the keywords that may be in your input don't matter.

Lastly, it is possible that no keyword was found. If s > 0, k is set to s. S was a temporary variable holding the index of the first found keyword while k was the keyword index throughout the keyword loop, but at the end, k changes its role to be the keyword index for the keyword that was found. l and t also switch up, I'm not sure why this was necessary, but l becomes the index in the input string just after the keyword, and it goes to the next section: conjugation.

If, however, there was no keyword found in the input, s is 0. In this case, k gets set to 36, which is the last keyword index, which has the special meaning of no-keyword-found, and it skips over the conjugation section straight to the reply section.

Conjugation

This section also took me a little bit to figure out. I won't step through how the code works line-by-line, but I will tell you what it does.

The conjugation data statements have 12 data items, or 6 pairs of words. We'll see them later, but they are, for example, "are" v. "am", "were" v. "was", "you" v. "I". It loops n2 / 2 times, so it only loops 6 times, but it reads s$ and r$ on each interation.

Then it has two phases that look almost identical (from 470 to 495 and from 510 to 535). The first phase searches for the first word and replaces it with the second word. The second phase searches for the second word and replaces it with the first word. These have to be done in pairs as you move through the string, otherwise, the first loop through the string would change all instances of "are" to "you", but then the second loop through the string would change all instances of "you"... including the ones that were just switched from "are"... back to "are"!!

After doing the conjugation word-pair swap, it falls through to the next section: reply.

One note: I don't believe that line 420, above, is necessary. It does a restore, which restores the data pointer, then it loops from 1 to n1, reading through the keywords, so as to position the data pointer at the start of the conjugation data statements. However, the whole point of continuing the keyword loop until the end of the keywords, even after it had found a matching keyword, was to move the data pointer to the start of the conjugation data. Therefore, I believe line 420 could be removed or commented out and the program would run faster. Something to try!

Reply

Generating and outputting the reply is the final step, and you can see that, this program is actually incredibly short.

We see that pattern again mentioned earlier, scanning through the data items by starting with restore, and a for-next look from 1 to n1 + n2. This skips the keywords and the conjugation pairs and puts the data pointer at the start of the replies data.

Next, we have to find the reply to use for this keyword. This is accomplished on line 600, it for-next loops from 1 (i.e., reply 1) to r(k), which is the current response index for keyword k. As it loops, it's reading data items. It doesn't need those data items that are being read, it's just moving the data pointer through the data items until it gets to the one it does need. You can see why BASIC, in this instance, is so brutally slow. There just doesn't seem to be a faster way to do this.

Line 610 implements what we surmised about how it works. After fetching the current reply for the keyword found, it updates what is the next reply to be used for the next time this keyword is used. r(k) = r(k) + 1, that increments the response index. Then it checks to see if r(k) is greater than n(k), has it exceed the total number of replies in this reply set? If so, it sets r(k) back to s(k), the start index for this reply set. Nifty.

The next thing it does is also interesting. Some replies end with an asterisk. Those are the dynamic ones. If the reply ends with an asterisk, then it outputs the reply upto but not including the asterisk, followed by your input from just past the first instance of the keyword found, but which has already had the conjugation swap applied to it! It's so simple, but it's really cool!

Finally, it copies i$ to p$, so that p$ recalls your last input to prevent you from repeating yourself. Then it loops back to line 170 which is the user input section again. And that's it. That's the whole program.

  • Initialize the dimensions
  • Get user input
  • Search for a keyword
  • Conjugation swap the input
  • Pull the next reply for the keyword
  • Output the reply, either dynamic or static
  • Loop back to get more user input

Incidentally, the only way to quit this program and return to the READY prompt (short of pressing STOP+RESTORE) is to trigger the easter egg. Use the keyword shut, and it reads the "end" basic command.

The Data Statements

The only thing left are the data statements. I will leave it as an exercise for the curious to see how the data are cleverly designed to make Eliza seemingly able to converse. Compared to the original, the only change I made is to breakout the final section into separate lines. Each line has a comment indicating which keyword uses this set of replies. And the set of replies is indicated by the pair: start index, number of replies in the set.

For example, for the keyword "can i" the replies match data are 4,2. This means in the replies data, replies numbers 4 and 5 handle the situation in which the user said "can i". These include, "perhaps you don't want to*" and "do you want to be able to*". Pretty straightforward, right?

Next follows a lot of data statements, but this blog post isn't over, below that I'll give a brief explanation of how I improved things in the Eliza for C64 OS.


Eliza for C64 OS

C64 OS is literally perfect for implementing a program like Eliza, and the boost in what it can do is astronomical at very little cost of development time.

What does that really mean? In concrete terms, I spent almost precisely 2 weeks, working for 2 to 3 hours each night. So call that 2.5 * 14 or 35 hours of work. I don't know how long it would take (given you understand how to implement Eliza and have the data statements as reference, both of which I had by looking at the original) to implement the above BASIC version. It would probably have taken less then 35 hours. But, what you get as an end product after a mere 35 hours of work on the Eliza for C64 OS Application you wouldn't be able to come anywhere even close to producing without C64 OS.

Let's just briefly touch on some ways in which Eliza for C64 OS is much much much more modern than a simple BASIC program. First of all, you have a mouse pointer for interaction. There is a menu bar from which you can access commands and explore what options are available. The conversation happens in a scrollable text display area. This means that as you chat, your conversation doesn't just scroll off the top of the screen and become lost, but you can use the mouse and grab the proportional scroll bar and scroll back up to see how it all went.

The conversation itself is structured using the MText data format, so formatting codes are embedded in the data buffer. This allows for things Eliza says to be in one color and left aligned, while things that you say are in a different color and right aligned. This all came virtually for free, as the TKText class already knows how to color and format the text by interpreting the MText formatting codes. The codes are also semantic, not absolute, which means that Eliza has automatic support for adapting to your preferred C64 OS theme. So, you can theme the colors of Eliza, and it required zero development effort. This brings us to the fact that, within C64 OS you can use Utilities along side Eliza. You can open the Themes Utility and change the color theme, you can open the Today Utility and check or set notes on dates, all while Eliza is running. You can even copy and paste text between the chat input field and other fields, such as the notes of the Today Utility.

Better yet, and again, totally for free from a development standpoint, you can open the SID Preview Utility, and play a SID tune (you pick the .PSID file to listen to in File Manager, and copy and paste the file reference to SID Preview) while chatting with Eliza.

Now pulling all those pieces together, you can use your mouse, pull down a menu, notice that there is a "Save As..." option. This opens the Save Utility, which you can use the navigate the entire file system of all your storage devices, entire a filename, and save your chat progress to a file with the ".ech" extension added automatically. But, the format of the chat is just a plain MText file (a kind of extended PETSCII). From File Manager, you can select the .ech file, and choose File → TextView and you can read your conversation... AND it's still formatted with the different colors and alternating left/right alignment, and you can do that even when you're not in the Eliza Application. All this, and haven't even mentioned, opening previously existing conversations, changing personalities in realtime, personality logo on screen, custom border color, flexible UI by hiding either the logo, the menu bar, the status bar, or any combination of these, fast app switching, full editing of the input text, and so on.

That is why I say, Eliza is perfect for C64 OS. You get a veritable avalanche of nice little features that no one in their right mind would ever spend the time or effort to build from scratch just for something so simple as an Eliza program. But in C64 OS, adding these features is a snap.

Speaking of which, if you want to support my development efforts, and you love the sound of everything above, Eliza is avalable for purchase from Itch.io for just $1.

A few neat implementational details

I don't have time to give a complete description of everything that went into writing Eliza for C64 OS, or for how it was implemented in 6502. But I will give a few details on things I think are particularly interesting.

First is that it has in fact been completely rewritten in 6502 assembly code. And what this means is that we are no longer required to use the limited data storage mechanisms provided by BASIC.

I realized that the sets of replies and the keywords that go with those replies are conceptually tied to each other, so, why shouldn't they be physically close to each other? I put the keywords and the replies into a plain text file (PETSCII encoded of course), where the relationship between the keywords and their replies is implied by their physical relationship. It makes the keywords and the replies much easier to see and edit. Now, it happens that sometimes more than one keyword applies to the same set of replies, so I made the keywords line a delimited list of keywords. The delimiter is a colon (:), which, again via another feature of C64 OS, I configured the text input field to have a data input filter that prevents you from typing a colon. Thus, guaranteeing that this can be safely used as a delimiter.

The keywords line comes first, followed immediately by the set of replies for those keywords, one reply per line. A single blank line ends the block of replies, and the following line is another keywords line. The text file ends up like this:

can you
*don't you believe that i can
*perhaps you would like to be able to
*you want me to be able to

can i
*perhaps you don't want to
*do you want to be able to

you are:youre
*what makes you think i am
*does it please you to believe i am
*perhaps you would like to be
*do you sometimes wish you were

And so on. For reasons, I moved the asterisk indicating a dynamic reply from the end of the string to the start. But, look how clean that is! The blank lines make it super easy for human eyes to see the groups. You can easy see which keywords correspond with with replies. You can even add other keywords to the same group of replies by simply adding another keyword with the colon delimiter to an existing block. You can also easily rearrange blocks to change the priority of keywords. For example, if you say, "I know you are clever, but can you sing?" Although "you are" comes before "can you" in the input string, that doesn't matter. Because "can you" comes first in the list of keywords, it will find that first and act on it. But, you can easily play with this, especially if you introduce whole new blocks of replies, by simply rearranging the blocks in the text file.

Parsing the personality data

Once the data for keywords and replies was in a text file, it became trivially easy to make more than one text file, changing the replies, and have this represent different "personalities."

Loading a personality consists of deallocating the memory structures that were used for the previously loaded personality (if any), and then loading in and parsing one of the text files into a new set of data structures.

So what are these data structures? Eliza defines two data structures: keyword and reply.

Eliza Keyword
-------------
ekword      .word 0 (2 bytes, keyword string pointer)
ekwrsp      .word 0 (2 bytes, reply structure pointer)
eknext      .word 0 (2 bytes, next keyword structure pointer)

Eliza Reply
-----------
erstr       .word 0 (2 bytes, reply string pointer)
ernext      .word 0 (2 bytes, next reply structure pointer)

Let's take a look at how these two data structures get linked together. I do a lot design work on paper. I like the freedom afforded by a pencil on grid paper. Grid paper is like my favorite programming design tool.

Eliza data structures.
Eliza data structures

At a technical level, C64 OS also provides features that make development easier. For example, opening a personality text file that's saved in the Application's resource bundle, is very easy. Allocating memory to find where to put the next data structure, is very easy. And although just coming up with your own memory usage scheme isn't too hard, using the official memory allocation routines (pgalloc, pgfree, malloc, and free) means that your program can peacefully co-exist with the OS itself, different drivers and libraries that the user may have loaded in to customize the system, a Utility that the user might want to load at the same time as your Application, plus the libraries and data that the Utility needs to load in.

Eliza Keyword Structure

The keyword structures form a singly-linked-list from the first node, to the next, to the next, to the next, until the final node's eknext pointer is null, which indicates the end of the list of nodes. Each keyword node has a pointer directly to a single keyword string. Although the keywords can come colon-delimited in the personality text file, once loaded into memory the colons get converted to NULL-byte string terminators. So the first keyword structure's ekword pointer points to the null-terminated string in memory "can you".

Each keyword structure as a single pointer to just one reply structure. When the program is searching for a keyword, then, it starts with the first keyword structure, uses the ekword pointer to find the keyword to look for, and scans to see if that keyword is in the input. If it isn't found, the keyword structure's eknext pointer is followed to find the next keyword structure, and the process is repeated for its keyword string, until either a keyword is found, or, the eknext pointer is null, which means no keywords were found.

Eliza Reply Structure

Let's suppose that a keyword was found. We hang on to a pointer to the current keyword structure because we're going to need it once more before this is up. But we use its ekwrsp pointer to find the one reply structure it points to.

That reply structure has an erstr pointer, which points to just one of the null-terminated replies that followed the keyword in the source text file. The carriage return at the end of the source text file's line, by the way, gets converted to a null-terminator, so every reply string is now a properly null-terminated string. That reply string can then be used to generate the output. I'm not going to describe that process here though.

Additionally, each reply structure has an ernext pointer. The ernext pointer points to the next reply structure for the next reply, and then that points to the next reply struct, and that to the next reply struct, and so on. Except, unlike how the keyword linked list was a single chain that ended with a null next pointer, the final reply struct's next pointer points back to the first reply struct (for this keyword, of course.) The result is that the set of reply structs for a given keyword for a looped-singly-linked-list. In other words, you don't have to worry about indexes anymore. You can just follow a reply struct's next pointer, and it always points to the next reply that ought to be used for this keyword, and the exactly where you are at any given moment in the loop of replies doesn't really matter.

But how is your current position in the reply loop maintained? It's super easy. The current keyword struct points you to a reply struct. The very first thing you do is copy the current reply struct's next reply pointer to the current keyword struct's reply struct pointer. Then you carry on using the reply string of the current reply struct. The next time this keyword struct is a match, it will automatically get you to the next reply struct, which will then update the keyword struct to point to the next reply struct. And so round and round it goes, where she stops nobody knows.


Final Thoughts

I hope you found this interesting. I hope it inspires someone to try to write their own C64 OS Application. Remember, it's an open marketplace. I'm selling Eliza for C64 OS on Itch.io (rather than just including it for free in the next C64 OS system update) not because I'm hoping to make a killing, but because I'm using it as a model.

Eliza is a model for how other developers can also write an Application for C64 OS, take advantage of all the wonderful programming APIs; Toolkit classes, menu system, and suite of built-in libraries and Utilities, to make some compelling with a minimal investment of coding time. And then, package it up as a .CAR install file (that's how Eliza is packaged) for easy installation, and then sell it for whatever price you think people will buy it for, on some market platform, such as Itch.io.

When you buy Eliza for C64 OS from Itch.io, you actually get a .zip file. You can unzip that, and you get some graphic logo files as .PNGs in high resolution, plus you get a read me and a eula text file, and then you also find a .CAR file. You transfer just the .CAR archive to your C64. Boot up C64 OS and double-click eliza.car in File Manager. This opens the Installer Utility. Click install, and it creates a new Application bundle in the Applications directory, and puts a bunch of files (the Application's executables and data resource files) into that bundle directory. Plus, it writes a file into the //os/settings/assigns/ directory to assign SEQ-type files with a .ech extension to open in the Eliza Application.

Installation is therefore, incredibly easy for your end-users.

And of course, I hope you enjoy chatting with Eliza too! Trying out the different personalities, discovering the easter eggs, and sharing funny moments on social media.

You can checkout the new OpCoders Inc Itch.io page here: https://opcoders.itch.io

  1. Dude, you're using ChatGPT 3.5! ChatGPT 4 is where it's at. I know, but the point is, a child does not have the entire internet as its corpus, and yet already doesn't make these kinds of nonsense results. Humans make different kinds of nonsense! :)

Do you like what you see?

You've just read one of my high-quality, long-form, weblog posts, for free! First, thank you for your interest, it makes producing this content feel worthwhile. I love to hear your input and feedback in the forums below. And I do my best to answer every question.

I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider purchasing one of the items I am currently offering or making a small donation, to help me continue to bring you updates, in-depth technical discussions and programming reference. Your generous support is greatly appreciated.

Greg Naçu — C64OS.com

Want to support my hard work? Here's how!