



/*
 * control.c
 *
 * Functions that alter the flow of control.
 *
 */

#include "ztypes.h"

const char *v1_lookup_table[3] = {
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    " 0123456789.,!?_#'\"/\\<-:()"
};

const char *v3_lookup_table[3] = {
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    " \n0123456789.,!?_#'\"/\\-:()"
};

/*
 * check_argument
 *
 * Jump if argument is present.
 *
 */

#ifdef __STDC__
void check_argument (zword_t argc, z_globals *gl)
#else
void check_argument (argc, gl)
zword_t argc;
z_globals *gl;
#endif
{

    conditional_jump (argc <= (zword_t) (gl->stack[(gl->fp) + 1] & ARGS_MASK), gl);

}/* check_argument */

/*
 * call
 *
 * Call a subroutine. Save PC and FP then load new PC and initialise stack based
 * local arguments.
 *
 */

#ifdef __STDC__
int call (int argc, zword_t *argv, int type, z_globals *gl)
#else
int call (argc, argv, type, gl)
int argc;
zword_t *argv;
int type;
z_globals *gl;
#endif
{
    zword_t arg;
    int i = 1, args, status = 0;

    /* Convert calls to 0 as returning FALSE */

    if (argv[0] == 0) {
        if (type == FUNCTION)
            store_operand (FALSE, gl);
        return (0);
    }

    /* Save current PC, FP and argument count on stack */

    gl->stack[--(gl->sp)] = (zword_t) ((gl->pc) / PAGE_SIZE);
    gl->stack[--(gl->sp)] = (zword_t) ((gl->pc) % PAGE_SIZE);
    gl->stack[--(gl->sp)] = (gl->fp);
    gl->stack[--(gl->sp)] = (argc - 1) | type;

    /* Create FP for new subroutine and load new PC */

    (gl->fp) = (gl->sp) - 1;
    (gl->pc) = (unsigned long) argv[0] * gl->story_scaler;

    /* Read argument count and initialise local variables */

    args = (unsigned int) read_code_byte (gl);
    while (--args >= 0) {
        arg = (gl->h_type > V4) ? 0 : read_code_word (gl);
        gl->stack[--(gl->sp)] = (--argc > 0) ? argv[i++] : arg;
    }

    /* If the call is asynchronous then call the interpreter directly.
       We will return back here when the corresponding return frame is
       encountered in the ret call. */

    if (type == ASYNC) {
        status = interpret (gl);
        (gl->interpreter_state) = RUN;
        (gl->interpreter_status) = 1;
    }

    return (status);

}/* call */

/*
 * ret
 *
 * Return from subroutine. Restore FP and PC from stack.
 *
 */

#ifdef __STDC__
void ret (zword_t value, z_globals *gl)
#else
void ret (value, gl)
zword_t value;
z_globals *gl;
#endif
{
    zword_t argc;

    /* Clean stack */

    (gl->sp) = (gl->fp) + 1;

    /* Restore argument count, FP and PC */

    argc = gl->stack[(gl->sp)++];
    (gl->fp) = gl->stack[(gl->sp)++];
    (gl->pc) = gl->stack[(gl->sp)++];
    (gl->pc) += (unsigned long) gl->stack[(gl->sp)++] * PAGE_SIZE;

    /* If this was an async call then stop the interpreter and return
       the value from the async routine. This is slightly hacky using
       a global state variable, but ret can be called with conditional_jump
       which in turn can be called from all over the place, sigh. A
       better design would have all opcodes returning the status RUN, but
       this is too much work and makes the interpreter loop look ugly */

    if ((argc & TYPE_MASK) == ASYNC) {

        (gl->interpreter_state) = STOP;
        (gl->interpreter_status) = (int) value;

    } else {

        /* Return subroutine value for function call only */

        if ((argc & TYPE_MASK) == FUNCTION)
            store_operand (value, gl);

    }

}/* ret */

/*
 * jump
 *
 * Unconditional jump. Jump is PC relative.
 *
 */

#ifdef __STDC__
void jump (zword_t offset, z_globals *gl)
#else
void jump (offset, gl)
zword_t offset;
z_globals *gl;
#endif
{

    (gl->pc) = (unsigned long) ((gl->pc) + (short) offset - 2);

}/* jump */

/*
 * restart
 *
 * Restart game by initialising environment and reloading start PC.
 *
 */

#ifdef __STDC__
void restart (z_globals *gl)
#else
void restart (gl)
z_globals *gl;
#endif
{
    unsigned int i, j, restart_size, scripting_flag;

    /* Reset output buffer */

    flush_buffer (TRUE, gl);

    /* Reset text control flags */

    (gl->formatting) = ON;
    (gl->outputting) = ON;
    (gl->redirecting) = OFF;
    (gl->scripting_disable) = OFF;

    /* Randomise */

    srand ((unsigned int) time (NULL));

    /* Remember scripting state */

    scripting_flag = get_word (H_FLAGS) & SCRIPTING_FLAG;

    /* Load restart size and reload writeable data area */

    restart_size = (gl->h_restart_size / PAGE_SIZE) + 1;
    for (i = 0; i < restart_size; i++)
        read_page (i, &(gl->datap)[i * PAGE_SIZE], gl);

    /* Restart the screen */

    set_status_size (0, gl);
    set_attribute (NORMAL);
    erase_window (SCREEN, gl);

    restart_screen (gl);

    /* Reset the interpreter state */

    if (scripting_flag)
        set_word (H_FLAGS, (get_word (H_FLAGS) | SCRIPTING_FLAG));

    set_byte (H_INTERPRETER, gl->h_interpreter);
    set_byte (H_INTERPRETER_VERSION, gl->h_interpreter_version);
    set_byte (H_SCREEN_ROWS, (gl->screen_rows)); /* Screen dimension in characters */
    set_byte (H_SCREEN_COLUMNS, (gl->screen_cols));

    set_byte (H_SCREEN_LEFT, 0); /* Screen dimension in smallest addressable units, ie. pixels */
    set_byte (H_SCREEN_RIGHT, (gl->screen_cols));
    set_byte (H_SCREEN_TOP, 0);
    set_byte (H_SCREEN_BOTTOM, (gl->screen_rows));

    set_byte (H_MAX_CHAR_WIDTH, 1); /* Size of a character in screen units */
    set_byte (H_MAX_CHAR_HEIGHT, 1);

    /* Initialise status region */

    if (gl->h_type < V4) {
        set_status_size (0, gl);
        blank_status_line (gl);
    }

    /* Initialise the character translation lookup tables */

    for (i = 0; i < 3; i++) {
        for (j = 0; j < 26; j++) {
            if (gl->h_alternate_alphabet_offset) {
                (gl->lookup_table)[i][j] = get_byte (gl->h_alternate_alphabet_offset + (i * 26) + j);
            } else {
                if (gl->h_type == V1)
                    (gl->lookup_table)[i][j] = v1_lookup_table[i][j];
                else
                    (gl->lookup_table)[i][j] = v3_lookup_table[i][j];
            }
        }
    }

    /* Load start PC, SP and FP */

    (gl->pc) = gl->h_start_pc;
    (gl->sp) = STACK_SIZE;
    (gl->fp) = STACK_SIZE - 1;

}/* restart */

/*
 * get_fp
 *
 * Return the value of the frame pointer (FP) for later use with unwind.
 * Before V5 games this was a simple pop.
 *
 */

#ifdef __STDC__
void get_fp (z_globals *gl)
#else
void get_fp (gl)
z_globals *gl;
#endif
{

    if (gl->h_type > V4)
        store_operand ((gl->fp), gl);
    else
        (gl->sp)++;

}/* get_fp */

/*
 * unwind
 *
 * Remove one or more stack frames and return. Works like longjmp, see get_fp.
 *
 */

#ifdef __STDC__
void unwind (zword_t value, zword_t new_fp, z_globals *gl)
#else
void unwind (value, new_fp, gl)
zword_t value;
zword_t new_fp;
z_globals *gl;
#endif
{

    if (new_fp > (gl->fp))
        fatal ("Bad frame for unwind", gl);

    (gl->fp) = new_fp;
    ret (value, gl);

}/* unwind */
