Simple CYOA
version 1 by Mark Tilford
Simple CYOA by Mark Tilford begins here.
"An extension for creating simple menu-based games."
Use inline choices translates as (- Constant Inline_Choices 1; -).
Use mention all changes translates as (- Constant AlertChanges 1; -).
Game page is a kind of value.
Initial page is a game page that varies.
A game state is a kind of thing.
Initial state is a game state.
Table of Current Choices
| Result |
| a game page |
| with 4 blank rows |
The rowcount is a number which varies;
To reset the links:
repeat through the Table of Current Choices begin;
blank out the whole row;
end repeat;
change rowcount to 0.
To say link (which - a game page):
if rowcount is less than the number of rows in the Table of Current Choices begin;
increase rowcount by 1;
change result in row rowcount of Table of Current Choices to which;
if using the inline choices option begin; otherwise; say line break; end if;
say "[rowcount]) ";
otherwise;
say "Too many choices, including [which] (BUG).";
end if.
To continue to (next - a game page):
if current view mode is allow changes then try preparing page next;
[change current page to next;]
try displaying page next.
To say continue to (next - a game page): continue to next;
Backup state is a game state. Current state is a game state. Current page is a game page that varies. Backup page is a game page that varies.
View mode is a kind of value. The view modes are just looking and allow changes. [A view mode is usually allow changes.] Current view mode is a view mode that varies.
Include (-
Default UndoBufferLength 100;
Constant NAttributes 48;
Array UndoBuffer --> UndoBufferLength;
Default AlertChanges 0;
Global UndoStep = 0;
[ ShowBacktrace ptr block_ends x;
!for (ptr = (UndoStep + (UndoBufferLength - 1)) % UndoBufferLength: ptr ~= UndoStep:
! ptr = (ptr + (UndoBufferLength - 1)) % UndoBufferLength )
! print UndoBuffer-->ptr, ", ";
for (ptr = 0: ptr < UndoBufferLength: ptr ++ ) {
if (ptr % 5 == 0) print " ";
print UndoBuffer-->ptr;
if (ptr + 1 == UndoStep) print "*";
print ", ";
}
print "^";
ptr = UndoStep;
for (::) {
!x = x + 1; if (x >= 10) "overflow";
!ptr = (ptr + (UndoStep-1)) % UndoBufferLength;
ptr = (ptr + (UndoBufferLength - 1)) % UndoBufferLength;
if (ptr == UndoStep) "(end of buffer)";
block_ends = UndoBuffer-->ptr;
!print " {", ptr, ":", UndoBuffer-->ptr, "}";
if (block_ends == -1) "(overwritten)";
if (UndoBuffer-->block_ends == -2) "(start)";
if (UndoBuffer-->block_ends == -1) "---";
ptr = (ptr + (UndoBufferLength-1)) % UndoBufferLength;
if (ptr == UndoStep) "...b";
print "^Page == ", (UndoBuffer-->ptr), ": ";
while (ptr ~= block_ends) {
ptr = (ptr + (UndoBufferLength-1)) % UndoBufferLength;
!print "{", ptr, "|", UndoBuffer-->ptr, "}";
if (ptr == UndoStep) "...c";
print "(", (UndoBuffer-->ptr), ": ";
ptr = (ptr + (UndoBufferLength-1)) % UndoBufferLength;
!print "{", ptr, ";", UndoBuffer-->ptr, "}";
if (ptr == UndoStep) "...d";
print (UndoBuffer-->ptr), ") ";
}
}
];
! Copy all properties from src to dest. They must be of the exact same kind.
!
[ CopyObject src dest i j;
!print "Copy Object -- ^";
for (i = 0: i < NAttributes: i ++) {
if (src has i) give dest i;
else give dest ~i;
}
!print "Comparing object ", src, " to ", dest, "^";
for (i = 4: i < #identifiers_table-->0: i ++) {
if ((dest provides i) ~= (src provides i))
print "Error: property mismatch: ", i, "^";
if (dest provides i) {
if (dest.#i ~= src.#i)
print "Error: property size mismatch: ", (dest.#i), " / ", (src.#i), "^";
else if (dest.#i == 1)
dest.i = src.i;
else
for (j = 0: j < dest.#i: j ++)
dest.&i->j = src.&i->j;
}
}
];
! PushGameState: Copies all differences between the 'current state /
! current page' and 'backup state / backup page'
! onto the undo stack, and then copies the current
! values into the backup values.
!
[ PushGameState current prev i old_undostep;
!print "Pushing game state...^";
current = (+ current state +);
prev = (+ backup state +);
old_undostep = UndoStep;
for (i = 0: i < NAttributes: i ++)
if ((current has i) ~= (prev has i)) {
if (AlertChanges)
print "(Detected change of attribute ", i, " to ", (current has i), ")^";
PushUndoValue((prev has i));
PushUndoValue(i);
if (current has i) give prev i;
else give prev ~i;
}
for (i = 4: i < #identifiers_table-->0: i ++) {
if (~~(current provides i)) continue;
if (~~(prev provides i)) continue;
if (current.#i > 2) {
continue; ! TODO
}
if (current.i ~= prev.i) {
if (AlertChanges)
print "(Detected change of prop ", (property) i, " to ", (current.i), ")^";
PushUndoValue (prev.i);
PushUndoValue (NAttributes + i);
prev.i = current.i;
}
}
PushUndoValue ( (+ Backup page +) );
(+ backup page +) = (+ current page +);
PushUndoValue (old_undostep);
];
! PopGameState: Reverses a previous call to PushGameState
! Sets all values in current state, backup state,
! current page, backup page to before that call.
!
[ PopGameState current prev prop val undo_to;
current = (+ current state +);
prev = (+ backup state +);
val = PeekUndoBuffer();
if (val == -1)
"(Sorry, you can't undo any further.)";
val = UndoBuffer-->val;
if (val == -2)
"(You're at the beginning of the game!)";
if (val == -1)
"(Sorry, you can't undo any further.)";
undo_to = PopUndoValue();
(+ current page +) = PopUndoValue();
while (UndoStep ~= undo_to) {
if (UndoStep == 0) return;
prop = PopUndoValue();
val = PopUndoValue();
if (prop < NAttributes) {
if (AlertChanges)
print "(Reverting change of attribute ", prop, " to ", val, ")^";
if (val) {
give current prop;
give prev prop;
} else {
give current ~prop;
give prev ~prop;
}
} else {
prop = prop - NAttributes;
if (AlertChanges)
print "(Reverting change of property ", prop, " to ", val, ")^";
(+ current state +).prop = val;
(+ backup state +).prop = val;
}
}
(+ backup page +) = (+ current page +);
return 0;
];
[ ClearUndoBuffer i;
for (i = 0; i < UndoBufferLength; i ++)
UndoBuffer-->i = -1;
UndoStep = 0;
PushUndoValue(-2);
PushUndoValue(0);
! Special values to indicate that the undo stack is at the very start of the game.
!(+ initial page +) = (+ current page +);
!CopyObject ( (+ current state +), (+ initial state +) );
];
[ PushUndoValue i;
!print "[[Pushing ", i, " onto stack: ", UndoStep, "]]^";
UndoBuffer-->UndoStep = i;
UndoStep = (UndoStep + 1) % UndoBufferLength;
];
[ PopUndoValue tmp;
if (UndoStep <= 0) UndoStep = UndoStep + UndoBufferLength;
UndoStep = UndoStep - 1;
tmp = UndoBuffer-->UndoStep;
UndoBuffer-->UndoStep = -1;
!print "[[Popping ", tmp, " from stack: ", UndoStep, "]]^";
return tmp;
];
[ PeekUndoBuffer tmp;
tmp = PopUndoValue();
!print "[[Peeking: ", tmp, ": ", UndoStep, "]]^";
PushUndoValue(tmp);
return tmp;
];
[ ReadChoice i j wd;
for (::) {
!ShowBacktrace();
if ( (+ rowcount +) == 0)
print "restart / restore / undo / quit> ";
else
print "> ";
read buffer parse;
!print "parse->1 == ", parse->1, "^";
if (parse->1 ~= 1) continue;
wd = parse-->1;
if (wd == 'undo') {
if (PopGameState ( (+ current page +), (+ backup page +) ) == 0)
return -1;
continue;
}
if (wd == 'save' && (+ rowcount +) > 0) {
#IFV5;
@save -> j;
switch (j) {
0: L__M(##Save,1);
1: L__M(##Save,2);
2: L__M(##Restore,2);
}
#IFNOT;
save Smaybe;
L__M(##Save,1);
continue;
.SMaybe; L__M(##Save,2);
#ENDIF;
continue;
}
if (wd == 'restore') {
restore Rmaybe;
L__M(##Restore,1);
continue;
.RMaybe; L__M(##Restore,2);
continue;
}
if (wd == 'look' && (+ rowcount +) > 0)
return -1;
if (wd == 'restart') {
ClearUndoBuffer();
CopyObject ( (+ initial state +), (+ current state +) );
CopyObject ( (+ initial state +), (+ backup state +) );
!print "Changing page from ", (+ current page +), " to ", (+ initial page +), "^";
(+ current page +) = (+ initial page +);
(+ backup page +) = (+ initial page +);
return -1;
}
if (parse-->1 == 'quit') {
print "Bye^";
@read_char i;
quit;
}
i = TryNumber(1);
if (i > 0 && i <= (+ rowcount +)) return i;
!j = j + 1;
!if (j >= 10) return 2;
}
];
[ EndTheGame x; print "Press a key to exit^"; @read_char x; print "Bye!^"; quit; ];
-);
To abort the game: (- EndTheGame(); -).
To back up the game state: (- PushGameState(); -).
To undo a game state: (- PopGameState(); -).
To decide which number is page to view next: (- ReadChoice() -).
To clear the undo buffer: (- ClearUndoBuffer(); -).
To copy object (SRC - thing) onto (DEST - thing): (- CopyObject ( {SRC}, {DEST} ); -).
To initialize the gamebook: copy object initial state onto current state; copy object initial state onto backup state; clear the undo buffer; change current page to initial page.
Displaying page is an action applying to a game page. Carry out displaying page: say "Fallthrough: there is no support for saying page on [game page understood](BUG).".
Preparing page is an action applying to a game page. Carry out preparing page: do nothing.
To say page for (which - a game page): try displaying page which.
The game base is a room. "You should never see this (BUG).".
Interturn rules is a rulebook.
when play begins:
say banner text;
say list of extension credits;
change rowcount to 1;
reset the links;
change current view mode to allow changes;
try preparing page current page;
try displaying page current page;
initialize the gamebook;
while 1 is 1 begin;
let nextrow be page to view next;
if nextrow is -1 begin;
reset the links;
change current view mode to just looking;
try displaying page current page;
otherwise;
change current page to result in row nextrow of the Table of Current Choices;
reset the links;
change current view mode to allow changes;
try preparing page current page;
try displaying page current page;
follow the interturn rules;
back up the game state;
end if;
end while.
[abort the game.]
Simple CYOA ends here.