Simple CYOA

version 1 by Mark Tilford

  • Home page
  • Complete text



  • 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.