Your retro text game, with no coding
Remember Issue #4
Here is issue #4 of ">REMEMBER"! Today's article is a tutorial, for the first time, but not the last!
My news
I was busy this month with the release of Light, a sci-fi visual novel for mobile, which I talked about a bit all the way back in Issue #2. I'm also writing another project for the same company; it's very nice to be able to make some money writing narrative games!
The translation of Tristam Island is still underway, and the text of the game is almost done; this had the beneficial side-effect of having me read the game's text line by line, which led me to find a couple of typos and mistakes. Release 3, along with the French translation, will probably come out in late April or early May (I still need to translate the parser, which is no small feat, and some accent-related stuff of course).
In the meantime, full reviews of Tristam Island are appearing in all sorts of magazines, and I couldn't be happier! The game has appeared, or is slated to appear, in "Zzap! 64", "Crash Magazine", "Juiced.GS", "Al's Spectrum Annual", "Amiga Future", and more! Overall, the reviews have been very positive, which I'm obviously quite proud of. Can't wait for the physical release, some time this year!
Last month's issue
The response to the article in Issue #3, on women's contributions to the text adventure genre, was bigger than ever! I heard from a lot of people, both replying to this newsletter and talking to me on Twitter. I'm quite pleased with it! If you're interested, here's some further reading on this: Gareth Pitchford listed a great number of women who contributed to the British 80s scene, and an account related to the Spanish-speaking IF scene had a whole Twitter thread highlighting women's contributions to that scene. Thank you all for the discussion, I learned a lot of things!
Community news
Quite a few things have happened this month:
- In the last couple of editions, I somehow forgot to tell you about Aaron A. Reed's newsletter, "50 years of text games", which is a very interesting read! Every week corresponds to a year, and Aaron picks a text-based game (including interactive fiction but also gamebooks, ARGs, and The Oregon Trail) published that year and writes an in-depth article about it. There is ton of research behind it, and even seasoned adventurers will discover something new on Adventure (1976) or Scott Adams games (1978). Highly recommended reading!
- Hot on the heels of their Christmas jam, Adventuron is organizing a new game jam, under the umbrella of the "Text Adventure Literacy Project". You have about 20 days to make a text adventure with a "VERB NOUN" parser that would be suitable for young ones; the idea is to help younger players learn how to play a text adventure and discover this great genre. There are several prizes, and Scott Adams himself is involved! Huge kudos to the folks at Adventuron for organizing this jam.
- Torbjörn Andersson has been taking a look at "Mini-Zork II", an unreleased Infocom game which aimed to give a taste of "Zork II" on a C64 cassette. The game was recovered when the so-called Infocom files were uploaded on Github, and Torbjörn fixed a few bugs. Work is underway on his Github.
- Remember ">REMEMBER" (ha) Issue #2, on text compression with Infocom's tools? Work on the problem of determining good abbreviations is still underway (it's really not easy to find the best algorithm!), and I really need to update my tool to enjoy these algorithmic advances. But this work had a very cool payoff this month! Thanks to Matthew Russotto's algorithm and Henrik Asman's help for code cleanup, Torbjörn was able to compress the size of "Mini-Zork II" enough that it can indeed be played on a C64 cassette on Fredrik Ramsberg and Johan Berntsson's Ozmoo interpreter! My understanding is that Torbjörn wants the eventual final version to fit on a C64 cassette, just like Infocom's game would have. Congratulations to all!
- Andrew Plotkin had a very interesting article on the topic of unwinnability in adventure games, challenging some conventional wisdom about Infocom games, and reflecting on the evolution of the design of adventure games through the ages.
Article of the month
In today's article, I'm showing you a really easy and visual way to make a text-based adventure (like a computerized "Choose your Own Adventure"-style book) for your favorite retro platform. Mind you, not a text adventure with a parser, like the ones we've been talking about; so this might be considered slightly out of scope for this newsletter, but I'm assume it'll still be interesting to you. After all, I'd wager you've all tried to make this kind of game when you were first discovering BASIC 🙂
The tutorial can be done under an hour, and is accessible enough that children could try their hand at it! It involves a really cool and visual new tool that you probably haven't heard about before, and Infocom's Z-Machine. The result is compact, fast, and portable; what more could you want? Dive in!
See you soon!
Thank you for your continued support! Please share the word if this newsletter has been interesting to you, and tell your friends to subscribe - past issues can be read in a browser, too! Thank you, and see you next month!
Issue #4 : Make your own retro text game with Moiki
We’re changing up the format of this week’s issue again, and here is a tutorial that’ll show you an amazing new tool that will be useful for making your own text-based game for an 8-bit or 16-bit platform. Full disclosure: not a parser-based text adventure like we’ve been talking about in the last few issues; this is far too large a topic to fit in one issue of this newsletter! Instead, I will show you a very easy way to make branching games, the kind that ask the player to type a number to pick one of the choices offered. (Basically computerized “Choose your Own Adventure” books!) This is a very simple interface, and I’m sure a lot of you have tried to write a game like that when you discovered BASIC programming. But it’s also very powerful: by building a complex structure and using variables, you’ll be able to make exciting games ranging from computer novels to text-based RPGs.
This tutorial is very easy: I especially recommend it if you don’t know programming, or if you want to create a game for your retro computer with a young child. It should take about an hour, all in all – by that I mean that in an hour, you’ll have a new game that can be played on most retro computers!
The tool we’ll be using this month, Moiki, is very streamlined, and its goal is to make creating games very accessible. It also exports code to Inform 6, which is the path (via the Z-Machine) to getting your game to run on a retro computer. Furthermore, if you know a bit of Inform 6, you’ll be able to tweak Moiki’s export to your liking, for instance if you need new functionalities, or a more powerful parser.
What is Moiki?
What is this amazing new tool? How come you have no idea what it is if it’s that amazing? Well, the answer is: because it’s French. (“Moiki” = “I’m the one who”, as in “I’m the one who decides”!) Clément came on the French Interactive Fiction community’s Discord server in 2020, with an idea for a tool which makes it even easier to author branching-narrative games. He put in a ton of work in his tool, and was very receptive to feedback; his goal was to make the experience as smooth as possible to entice new writers to start writing more text-based games. And it worked! We had quite a few newcomers that discovered our community thanks to Moiki, and a game written with this tool took second place in our annual competition. It’s only very recently that Clément started translating it to English; in fact, the English translation of the website has only been live for a few days!
One area where Moiki shines is that Clément worked hard to ensure the story could be exported to a variety of formats: HTML, JSON, PDF (choose-your-own adventure book style), Ink (a major player in the branching narrative genre), and... Inform 6, Graham Nelson’s language which can be compiled onto a Z-Machine game. The export is light enough that most games will fit in a z3 file (< 128kb), which means it’ll be playable on most disk-based retro platforms, from the TRS-80 to the Game Boy, the C64, the Beeb, and the Amiga. It could also run on tape: if it’s smaller than 53,000 bytes, roughly, it can be run from tape on Commodore 64 (using the Ozmoo interpreter), just like Mini-Zork (and, now, Mini-Zork II). (However, I don’t know if there is a ZX Spectrum interpreter for the Z-Machine that would run on tape; I know there is one for the Spectrum +3, but nobody has made one for tape, I believe.)
What you need
- Moiki.fr (it’s probably a good idea to create an account)
- the latest version of the Inform 6 compiler, which right now is Inform 6.34, and can be found at the very bottom of this page
http://www.ifarchive.org/indexes/if-archiveXinfocomXcompilersXinform6Xexecutables.html - a Z-Machine interpreter for your favorite retro platform
Note that if your favorite retro platform does not have a Z-Machine interpreter, you could of course still write that kind of game another way, by writing it in BASIC for instance; but what I like about Moiki is that it’s very visual and keeps your branching tidy, which really helps if your project starts to get big.
Making a text-based RPG
We’ll be starting with something small: a couple of quests, changing statistics, fighting a monster. It should be enough to get your creative juices flowing! Start by going on Moiki.fr, and create a new story; give it a name, and a description, and let’s go with “normal mode” – I’ll explain everything.
First of all, in the top menu, you see a few buttons: “run” to test your story, “save” to save it on your account on the website, “share” to get a shareable link, “export” to export it to different formats; as well as a list of sequences and a list of variables.
Your first sequence is already created: write some intro text, like “the king asked you to go forth and be a hero!”. Then, you can select the type of sequence it should be; let’s not do choices right away, so let’s choose to create a simple sequence. It should link somewhere, but as we don’t have another sequence yet, we need to click the “create a new sequence” button. A new node named “seq-1” appears, along with other options, but let’s not bother with these for now.
Click on the “seq-1” node to design the next step of your adventure. I’m going to go with the old well trope: do we want to look into it, or just keep walking? Write a little text corresponding to the situation, then select the type of the sequence to be one with choices. You are now prompted to type the first choice that should be given: let’s do “look inside the well”. A choice is created, and you see a “circle with a line across” icon below: this just means we haven’t said yet what sequence should the choice lead to. Let’s click on this, and the familiar popup appears; create a new sequence, seq-2. Now, add a new choice, “keep walking”, and create a new sequence, seq-3, as a result of that choice.
This simple decision about the well shouldn’t have too many consequences; at the end of the choices, we should probably go back to the main line of adventure. Let’s create it: go to “seq-3”, write something like “it probably doesn’t contain anything”, make that a straight sequence and create a new sequence, seq-4, that seq-3 leads to. You can now go to seq-2 and also make it a straight choice that goes to seq-4.
But wait! Shouldn’t we reward our player with something for looking into the well? How about a gold coin? First, create the gold coin: go to the “variables” button in the menu at the top, then in “object/hero” (currently empty) you should see a button to create one. Give it the name “gold coin”; you also need to give it an icon (which will appear on your graph in the right side of the screen, but also in the HTML – but not on our retro computer), by choosing one from the search engine. Now go back lower, to the seq-2 sequence (click on it in the graph to be taken to it). Below the sequence, you see an “action +” button; click it, then select “gain object” and select your gold coin.
We have a gold coin? Let’s have a beggar! We can give it a coin if we do, or apologize (or any other option you’d like). Again, select seq-4, write a text about the beggar, and add two choices, each leading to a new sequence (let’s say seq-5 to give the coin and seq-6 to just apologize). But wait! We can’t give a gold coin if we don’t have one! So we need to control the visibility of the “give the coin” choice: look at the bar containing the name of the choice, and on the right side you should see an eye. Click on it, and you’re invited to create a new condition: select “object”, then “has the object...”, and select the gold coin. The choice will now only appear if you have a gold coin in your inventory.
We should probably reward the player for giving a gold coin. Let’s create a very simple karma system (or reputation, etc.); this is done by creating a counter, i.e. a numerical variable. Go to the top menu, select “variables”; below your gold coin is a section on counters, which should be empty. Create a new variable, give it the name “karma”, ranging from 1 to 10 with a starting value of 5. Now go back to seq-5, the consequence of the “give the coin” choice; add an action, select “counter”, select “karma” in the dropdown menu, and say it should increase by 1. Finally, add another action, which is to lose the gold coin: “action +”, “object”, “lose an object”, “gold coin”. I’m sure you’re getting the hang of this already!
Don’t forget to tie seq-5 and seq-6 to a new sequence, let’s say seq-7. And now, let’s end this tutorial with a fight scene. I hear you sigh already: are we going to have to fight a rat with our bare hands? Yes, sorry... Let’s just create a very simple fight scene: your two choices are hit the rat, or run away. In seq-7 say “a rat is here! It attacks you!”; make this a simple passage leading to seq-8, the meat of the fight. Seq- 8 should contain something like “The rat is ready to pounce!”, then the two choices: “fight” or “run away”. The “run away” choice leads to, say, seq-9, a simple sequence which says “you run away” and leads to the rest of the adventure (say, seq-10).
As for the fight sequence, let’s have this very simple system: each attack takes 2 HP out of the rat, and it has 8 HP; when the rat’s HP is at 0, the fight ends. Let’s start by creating a new counter holding the rat’s health: go up to variables, then create a new counter, starting at 8, max 8, and min 0. Then, inside seq-8, create a new choice, called “Coup de grâce!”; click on the eye icon on the right, and set this choice to only display when the rat’s health is 0. As for the other two choices, click on their eye icon, and set them to only display when the rat’s health is different from 0.
Now we just need to tie the loose ends: the “Fight!” should lead to a simple sequence, seq-11, saying something like “You hit the rat!”, and leads back to our seq- 8 node after decreasing the rat’s health by 2; the “Coup de grâce!” choice should lead to a node, seq-12, in which you defeat the rat, maybe earn some karma, then go to the rest of the adventure (seq-10). Then, we can say that seq-10 is of type “final node”, and write something saying “more to come!”.
This is a very simple game, unlikely to capture anyone’s attention for too long; but you probably did it in half an hour, no more. And you have 12 sequence, when Moiki can support literally hundreds. Imagine what you can do with this system with more time!
Tweaking the Inform 6 output
It’s time to put our game on our computer. Click on the “Export” button in the top menu, and export your story as a JSON file. Save this file, then open the “Moiki exporter” app (in another window). Drop the “.zip” file you just got, and choose the “Inform 6” format. Tweak whichever settings you want (don’t forget to specify the language as “en” to get the default messages in English!). You get a “.inf” file, and a “.h” file, which can just be edited with any text editor. The generated code is rather simple, and very lightweight, as the usual parser is sidestepped. If you don’t know Inform 6, that’s completely fine! Here’s a couple of tricks you can do, though.
For now, Moiki doesn’t support randomness, though it might come later. However, it is very simple to add it in your code. Look for the piece of code updating the rat’s health; mine looks like
subCounter(_rat_health_2, 2);
Here we could add randomness, to make this a little less predictable: let’s say
subCounter(_rat_health_2, random(4));
Now each hit will deal damage between 0 and 3!
Another way to make the game better is to change the line “You hit the rat!” so it’s not always the same. You can replace
print “You hit the rat!”
with
print random(“You hit the rat!”, “You kick it, but it tries to fight back!”, “You step on its toes!”);
This makes your game change the line randomly, which is good for lines you see all the time.
Careful, though! You’re tweaking the output of Moiki; if you decide to go further on Moiki and then export the result, those changes will be lost! It’s probably better to save them for last, or copy-and-paste them in periodically. I’ll let you figure out how you want to organize this.
Once you’re done, you should compile your game. This is done through the command line (cmd.exe on Windows, a terminal on OSX or Unix); in theory, just typing
inform -v3 story.inf
should work. The resulting file is a “game.z3” file that only weighs a few kilobytes (mine is 4.5kb) – you can put it on a disk with a Z-Machine interpreter and run it! And there you go, you’ve authored a text RPG for retro systems!
The quick and easy way to generate a C64 game is to use Ozmoo Online:
http://microheaven.com/ozmooonline/
What I particularly like about Moiki’s export is how small it is, and it gets compiled to efficient Z-Machine code. Most Z-Machine interpreters on 8-bit machines only have z3 support, which means you cannot go further than 128kb, although there are some 8-bit computers that should support z5 (like the Apple II, the C64, and the BBC Micro); once you’re on 16-bit machines, you probably can find a z5 (256kb) or z8 (512kb) interpreter. Plus, thanks to the Z-Machine’s text encoding scheme (see Issue #2!), you can fit around 25% more text; and if you want to use abbreviations, they probably would free up more than 20k on a file that’s getting too close to the 128k limit! My back-of-the-envelope calculation is that a Moiki-generated game with up to 200,000 characters of text could fit in a z3 file – that’s 40,000 words of adventuring, which I’m fairly certain is the size of a book of the “Choose Your Own Adventure” line! If your 10-year-old self was wondering if a computerized adaptation of “Deathtrap Dungeon” would ever run on the old Acorn Electron, it might very well be possible...
I hope you have enjoyed this month’s article! I think the Moiki tool has a ton of potential to introduce people (you, your children, your grandchildren, and more!) to writing branching narratives, text RPGs, computer novels, and all of that, and I’d be delighted to hear about your creations. Feel free to reach out to me if you have any comments on the tool, I can pass it on to its creator if needed! I’ll see you next month!
The remember quest
moikinform.h
! This file contains the necessary core for the Moiki export to Inform6
! kaelhem (c) 2021
! kaelhem at gmail com
! Inform settings
! -------------------------------------------
Global location = DefaultRoomForStatusBar; ! Must be the first global to show location name
Global status_field_1 = 0; ! Must be the second global to show score or hours
Global status_field_2 = 0; ! Must be the third global to show turns or minutes
! Variables for game management
! -------------------------------------------
! Array path --> 10; ! allow 10 undo moves, but it's not implemented yet...
Global markForRedo = 0; ! used to restart game from beginning
Global markForShow = 0; ! used to re-display sequence text
Global gameOver = 0;
#IfV3;
Constant ARRAY_LEN_OFFSET = 2;
#IfNot;
Constant ARRAY_LEN_OFFSET = 3;
#EndIf;
! Choices management (used for visibility conditions of choices)
! -------------------------------------------
Constant MAX_CHOICES = 6;
Array userChoices --> (ARRAY_LEN_OFFSET + MAX_CHOICES);
[ clearChoices i;
for (i=1: i<=MAX_CHOICES: i++) {
userChoices-->i = 0;
}
return;
];
! Items management
! -------------------------------------------
Array userItems --> (ARRAY_LEN_OFFSET + COUNT_TOTAL_ITEMS);
[ clearItems i;
for (i=1: i<=COUNT_TOTAL_ITEMS: i++) {
userItems-->i = 0;
}
return;
];
[ addItem index;
if (userItems-->index == 0) {
toggleItem(index);
}
return;
];
[ subItem index;
if (userItems-->index == 1) {
toggleItem(index);
}
return;
];
[ hasItem index;
return userItems-->index == 1;
];
[ toggleItem index;
#IfV5; style bold; #EndIf;
if (userItems-->index == 0) {
userItems-->index = 1;
++status_field_1;
print (string) STR_OBJECT_WON;
} else {
userItems-->index = 0;
--status_field_1;
print (string) STR_OBJECT_LOST;
}
print (string) getItemDescription(index);
#IfV5; style roman; #EndIf;
wait();
return;
];
[ countItems i count;
count = 0;
for (i=1: i<=COUNT_TOTAL_ITEMS: i++) {
if (userItems-->i == 1) ++count;
}
return count;
];
! Counters management
! -------------------------------------------
Array userCounters --> (ARRAY_LEN_OFFSET + COUNT_TOTAL_COUNTERS);
[ clearCounters i;
for (i=1: i<=COUNT_TOTAL_COUNTERS: i++) {
userCounters-->i = defaultCounterValue(i);
}
return;
];
[ setCounter index value;
userCounters-->index = value;
if (isCounterGauge(index)) {
#IfV5; style bold; #EndIf;
print (string) getCounterName(index), (string) STR_COUNTER_SET, value;
#IfV5; style roman; #EndIf;
wait();
}
return;
];
[ addCounter index value;
userCounters-->index = userCounters-->index + value;
if (isCounterGauge(index)) {
#IfV5; style bold; #EndIf;
print (string) getCounterName(index), (string) STR_COUNTER_ADD, value, " ", (string) STR_AND, (string) STR_COUNTER_SET, userCounters-->index;
#IfV5; style roman; #EndIf;
wait();
}
return;
];
[ subCounter index value;
userCounters-->index = userCounters-->index - value;
if (isCounterGauge(index)) {
#IfV5; style bold; #EndIf;
print (string) getCounterName(index), (string) STR_COUNTER_SUB, value, " ", (string) STR_AND, (string) STR_COUNTER_SET, userCounters-->index;
#IfV5; style roman; #EndIf;
wait();
}
return;
];
! Manage user inputs
! -------------------------------------------
! fix: in z-code v3, input buffers are not formatted the same way...
#IfV3;
Constant arrayStartIndex 1;
[ length arr len;
len = 0;
while (arr->(len+1) ~= 0) ++len;
return len;
];
#Ifnot;
Constant arrayStartIndex 2;
[ length arr;
return arr->1;
];
#EndIf;
! read user inputs
[ KeyLine buffer;
buffer->0 = 10;
read buffer 0;
return buffer;
];
! convert a string into array
[ toArray str arr;
@output_stream 3 arr;
@print_paddr str;
@output_stream -3;
return arr;
];
! take a char and return the same in lower case
[ toLowerCase c;
if (c >= 'A' && c <= 'Z') return c + 32; else return c;
];
! return true if the given command as string match the current input buffer
[isCommand cmd aCmd i;
aCmd = toArray(cmd);
if (aCmd-->0 == length(key)) {
for (i=0: i<aCmd-->0: i++) {
if (key->(arrayStartIndex+i) ~= toLowerCase(aCmd->(2+i))) rfalse;
}
rtrue;
}
rfalse;
];
! store user input
Array key -> 13;
! read user choices / menu commands
[ getInputChoice numChoices len chNum commandUnknown done;
done = false;
do {
commandUnknown = false;
do {
print "> ";
} until(KeyLine(key)-->0);
len = length(key);
if (len == 1) {
chNum = key->arrayStartIndex - 48;
if (chNum > 0 && chNum <= numChoices) {
cls();
done = true;
} else if (chNum > 0 && chNum <= 10) {
print (string) STR_NOCHOICE_MATCH, "^";
} else {
commandUnknown = true;
}
} else if (isCommand(STR_CMD_HELP)) {
showHelp();
} else if (isCommand(STR_CMD_UNDO)) {
undo();
} else if (isCommand(STR_CMD_REDO)) {
if (redo()) return 0;
} else if (isCommand(STR_CMD_EXIT)) {
exit();
} else if (isCommand(STR_CMD_SHOW)) {
markForShow = 1;
return 0;
} else if (isCommand(STR_CMD_LIST)) {
inventory();
} else {
commandUnknown = true;
}
if (commandUnknown) {
print (string) STR_COMMAND_UNKNOWN_LEFT, (string) STR_CMD_HELP, (string) STR_COMMAND_UNKNOWN_RIGHT, "^";
}
} until(done);
return chNum;
];
[ confirm question ok done;
done = false;
ok = false;
do {
do {
if (question) {
print (string) question;
} else {
print (string) STR_DEFAULT_CONFIRM_MSG;
}
print " (", (string) STR_CMD_YES, "/", (string) STR_CMD_NO, ")^> ";
} until(KeyLine(key)-->0);
if (isCommand(STR_CMD_YES) || isCommand(STR_CMD_YES_SHORT)) {
ok = true;
done = true;
} else if (isCommand(STR_CMD_NO) || isCommand(STR_CMD_NO_SHORT)) {
done = true;
}
if (~~done) {
print (string) STR_PLEASE_ANSWER, (string) STR_CMD_YES, " ", (string) STR_OR, " ", (string) STR_CMD_NO,".^";
}
} until(done);
return ok;
];
[ cls;
#IfV3;
! in v3 it seems there is no way to clear the screen...
print (string) CLS_PATTERN, "^";
#Ifnot;
@erase_window -1; ! this opcode is not available in V3
#EndIf;
rtrue;
];
[ wait x;
#IfV3;
read key 0;
#Ifnot;
@read_char 1 x; ! this opcode is not available in V3
print "^";
#EndIf;
];
! Menu
! -------------------------------------------
[ showHelp;
#IfV5; style underline; #EndIf;
print (string) STR_LIST_OF_COMMANDS, "^";
#IfV5; style roman; #EndIf;
! print " - ", (string) STR_CMD_UNDO, (string) STR_COLON, " ", (string) STR_BACK_TO_PREVIOUS, "^";
print " - ", (string) STR_CMD_REDO, (string) STR_COLON, " ", (string) STR_RESTART_GAME, "^";
print " - ", (string) STR_CMD_SHOW, (string) STR_COLON, " ", (string) STR_RESHOW_TEXT, "^";
print " - ", (string) STR_CMD_EXIT, (string) STR_COLON, " ", (string) STR_QUIT, "^";
print " - ", (string) STR_CMD_LIST, (string) STR_COLON, " ", (string) STR_LIST_OBJECTS, "^";
rtrue;
];
[ exit;
print (string) STR_BYE_BYE, "^";
@quit;
];
[ undo;
print "Undo: not implemented yet !^";
rtrue;
];
[ redo;
if (confirm(STR_CONFIRM_RESTART)) {
markForRedo = 1;
rtrue;
}
rfalse;
];
[ inventory i;
if (countItems() == 0) {
print (string) STR_INVENTORY_EMPTY, "^";
} else {
#IfV5; style underline; #EndIf;
print (string) STR_INVENTORY_LIST, "^";
#IfV5; style roman; #EndIf;
for (i=1: i<=COUNT_TOTAL_ITEMS: i++) {
if (hasItem(i)) print "* ", (string) getItemDescription(i), "^";
}
}
rtrue;
];
! Presentation
! -------------------------------------------
[ startScreen;
#IfV5; style underline; #EndIf;
print (string) STR_HEADER, " ", (string) STORY_URL, "^^";
#IfV5; style roman; #EndIf;
print (string) STR_MOIKI_PRESENTS, "^";
#IfV5; style bold; #EndIf;
print (string) STORY_TITLE, "^^";
#IfV5; style roman; #EndIf;
print (string) STR_A_STORY_BY, " ", (string) STORY_AUTHOR, "^^", (string) STORY_DESCRIPTION, "^";
rtrue;
];
! Game loop
! -------------------------------------------
[ mainLoop firstSequence next res;
next = firstSequence;
do {
++status_field_2; ! increase turn counter
res = next();
if (markForShow == 1) {
markForShow = 0;
res = next;
}
if (markForRedo == 1) {
res = false;
}
next = res;
print "^";
} until(~~next);
if (gameOver > 0) {
#IfV5; style bold; #EndIf;
if (gameOver == 1) {
print (string) STR_WIN_GAME, "^^";
} else if (gameOver == 2) {
print (string) STR_LOSE_GAME, "^^";
}
#IfV5; style roman; #EndIf;
gameOver = 0;
}
];
[ startGame firstSequence replay;
startScreen();
wait();
do {
cls();
replay = false;
location = DefaultRoomForStatusBar; ! reset location (avoid compiler warning)
status_field_1 = 0; ! reset score
status_field_2 = 0; ! reset turns
clearItems();
clearCounters();
mainLoop(firstSequence);
if (markForRedo == 1) {
markForRedo = 0;
replay = true;
} else {
if (confirm(STR_ANOTHER_GAME)) {
replay = true;
} else {
exit();
}
}
} until(~~replay);
];
story.inf
!% -Cu
!% -~S
!% $OMIT_UNUSED_ROUTINES=1
Zcharacter table + '@{e2}';
! This story was created with Moiki, and converted with Moiki-Exporter
! More info: https://github.com/kaelhem/moiki-exporter
! Launch it with the Moiki player: https://moiki.fr/story/6046eaaab5ebb300392f02d4
! Exported on Monday, March 8, 2021, 8:49:50 PM
! author: Hugo Labrande
! title: The REMEMBER quest
Object DefaultRoomForStatusBar "The REMEMBER quest"; ! used to force name in status line
! Constants
! -------------------------------------------
Constant STORY_TITLE = "The REMEMBER quest";
Constant STORY_DESCRIPTION = "A short tutorial for ~>REMEMBER~";
Constant STORY_AUTHOR = "Hugo Labrande";
Constant STORY_URL = "https://moiki.fr/story/6046eaaab5ebb300392f02d4";
! Strings
Constant STR_HEADER = "Cette histoire a été exportée avec Moiki Exporter.^La version originelle est accessible ici :";
Constant STR_MOIKI_PRESENTS = "Moiki présente :";
Constant STR_A_STORY_BY = "Une histoire de";
Constant STR_COLON = " :";
Constant STR_CMD_HELP = "AIDE";
Constant STR_CMD_UNDO = "RETOUR";
Constant STR_CMD_REDO = "REFAIRE";
Constant STR_CMD_LIST = "LISTE";
Constant STR_CMD_SHOW = "REVOIR";
Constant STR_CMD_EXIT = "QUITTER";
Constant STR_CMD_YES = "oui";
Constant STR_CMD_YES_SHORT = "o";
Constant STR_CMD_NO = "non";
Constant STR_CMD_NO_SHORT = "n";
Constant STR_NOCHOICE_MATCH = "Cette saisie ne correspond à aucun choix !";
Constant STR_LIST_OF_COMMANDS = "Liste des commandes";
Constant STR_RESTART_GAME = "Recommencer depuis le début";
Constant STR_LIST_OBJECTS = "Lister les objets récupérés";
Constant STR_RESHOW_TEXT = "Afficher le texte de la dernière séquence";
Constant STR_QUIT = "Quitter";
Constant STR_BYE_BYE = "Bye-bye !";
Constant STR_CONFIRM_RESTART = "Recommencer depuis le début ?";
Constant STR_INVENTORY_LIST = "Liste des objets de l'inventaire :";
Constant STR_INVENTORY_EMPTY = "Votre inventaire est vide !";
Constant STR_OBJECT_WON = "Objet récupéré : ";
Constant STR_OBJECT_LOST = "Objet perdu : ";
Constant STR_COUNTER_SET = " vaut maintenant : ";
Constant STR_COUNTER_ADD = " augmente de ";
Constant STR_COUNTER_SUB = " diminue de ";
Constant STR_WIN_GAME = "Gagné !";
Constant STR_LOSE_GAME = "Perdu !";
Constant STR_COMMAND_UNKNOWN_LEFT = "Cette commande est inconnue ! Tapez ~";
Constant STR_COMMAND_UNKNOWN_RIGHT = "~ pour une liste des commandes disponibles.";
Constant STR_DEFAULT_CONFIRM_MSG = "Etes-vous sûr de vouloir faire cette action ?";
Constant STR_AND = "et";
Constant STR_OR = "ou";
Constant STR_PLEASE_ANSWER = "Veuillez répondre par ";
Constant STR_ANOTHER_GAME = "Lancer une autre partie ?";
! Config
Constant CLS_PATTERN = "----------------------------------------";
! Defines objects / heroes
!-------------------------------------------
Constant COUNT_TOTAL_ITEMS = 1;
Constant _gold_coin_1 = 1;
[ getItemDescription index;
switch (index) {
_gold_coin_1: return "Gold coin - ~A gold coin~";
default: return "";
}
];
! Defines counters
!-------------------------------------------
Constant COUNT_TOTAL_COUNTERS = 2;
Constant _karma_1 = 1;
Constant _rat_health_2 = 2;
[ getCounterName index;
switch (index) {
_karma_1: return "Karma";
_rat_health_2: return "Rat health";
default: return "Undefined counter";
}
];
[ defaultCounterValue index;
switch (index) {
_karma_1: return 5;
_rat_health_2: return 8;
default: return 0;
}
];
[ isCounterGauge index;
switch (index) {
default: return false;
}
];
! Include MoikInform library
! ------------------------------------------
Include "moikinform";
! App entry point
! ------------------------------------------
[ Main;
startGame(story_intro);
];
! Story sequences
! ------------------------------------------
[ story_intro;
print "the king asked you to go forth and be a hero!";
wait();
return story_seq_1;
];
[ story_seq_1 choice;
print "There is a well here, what do you do?^^";
print "- 1. Look inside^";
print "- 2. Keep walking^";
choice = getInputChoice(2);
if (choice == 1) {
return story_seq_2;
}
if (choice == 2) {
return story_seq_3;
}
];
[ story_seq_2;
print "You look inside and find a gold coin!^^";
addItem(_gold_coin_1);
return story_seq_4;
];
[ story_seq_3;
print "It's probably nothing.";
wait();
return story_seq_4;
];
[ story_seq_4 choice numVisibleChoices;
numVisibleChoices = 0;
print "A beggar begs: ~Do you have gold?~^^";
numVisibleChoices = numVisibleChoices + 1;
userChoices-->1 = numVisibleChoices;
print "- (", numVisibleChoices, "). ~Sorry, I don't.~^";
if (hasItem(_gold_coin_1)) {
numVisibleChoices = numVisibleChoices + 1;
userChoices-->2 = numVisibleChoices;
print "- (", numVisibleChoices, "). ~I do! Here it is!~^";
}
choice = getInputChoice(numVisibleChoices);
if (choice == userChoices-->1) {
return story_seq_5;
}
if (choice == userChoices-->2) {
return story_seq_6;
}
];
[ story_seq_5;
print "He shrugs.";
wait();
return story_seq_7;
];
[ story_seq_6;
print "~Thank you! Now I can eat!~^^";
subItem(_gold_coin_1);
addCounter(_karma_1, 1);
return story_seq_7;
];
[ story_seq_7;
print "~A rat is here! It attacks you!~^";
wait();
return story_seq_8;
];
[ story_seq_8 choice numVisibleChoices;
numVisibleChoices = 0;
print "The rat is ready to pounce!^^";
if (_rat_health_2 ~= 0) {
numVisibleChoices = numVisibleChoices + 1;
userChoices-->1 = numVisibleChoices;
print "- (", numVisibleChoices, "). Fight^";
}
if (_rat_health_2 ~= 0) {
numVisibleChoices = numVisibleChoices + 1;
userChoices-->2 = numVisibleChoices;
print "- (", numVisibleChoices, "). Run away^";
}
if (_rat_health_2 == 0) {
numVisibleChoices = numVisibleChoices + 1;
userChoices-->3 = numVisibleChoices;
print "- (", numVisibleChoices, "). Coup de grâce!^";
}
choice = getInputChoice(numVisibleChoices);
if (choice == userChoices-->1) {
return story_seq_11;
}
if (choice == userChoices-->2) {
return story_seq_9;
}
if (choice == userChoices-->3) {
return story_seq_12;
}
];
[ story_seq_9;
print "You run away.";
wait();
return story_seq_10;
];
[ story_seq_10;
print "The end, but more to come!^";
wait();
gameOver = 1;
return nothing;
];
[ story_seq_11;
print "You hit the rat!^^";
subCounter(_rat_health_2, 2);
return story_seq_8;
];
[ story_seq_12;
print "Another rat bites the dust! You gain karma.^^";
addCounter(_karma_1, 1);
return story_seq_10;
];