Keyboard and joystick

Get commands from key events. Get movement from the held key and the joystick. Find button presses. All of these read the standard //e inputs. The joystick needs the audio driver for its timer.

Two types of keyboard input

The //e keyboard gives one character at a time, and it tells if a key is down. It gives no release events and no key matrix. libtini gives two types of keyboard input:

Move while a key is held

A held key moves the player in each update. A short push of a key can start and end between two reads of the held key. Thus STARFALL also takes the direction from key events, and keeps it for one frame. The arrow keys give these control codes: left 8, right 21, up 11, down 10.

#define UP 1
#define DOWN 2
#define LEFT 4
#define RIGHT 8
static const char move_key[8] = {'W', 11, 'S', 10, 'A', 8, 'D', 21};
static const uint8_t move_dir[8] = {UP, UP, DOWN, DOWN, LEFT, LEFT, RIGHT, RIGHT};
static uint8_t direction, tapped;

static uint8_t direction_of(uint8_t k)
{
    uint8_t i;
    if (k >= 'a' && k <= 'z') k -= 32;
    for (i = 0; i < sizeof(move_key); ++i) if (k == move_key[i]) return move_dir[i];
    return 0;
}

/* One time for each frame, after ti_input_poll(). */
static void read_keys(void)
{
    uint8_t key, d;
    while (ti_input_key(&key) == TI_OK) {
        if ((d = direction_of(key)) != 0) { direction = d; tapped = 1; }
        else handle_command(key);        /* pause, quit, bomb ... */
    }
    key = ti_input_held();
    if (key) { if ((d = direction_of(key)) != 0) direction = d; }
    else if (!tapped) direction = 0;    /* key released, and no short push in this frame */
    tapped = 0;
}

The joystick

ti_stick_read returns TI_STICK_UP, DOWN, LEFT and RIGHT bits, with diagonals. It measures them from the rest position of the stick. Apple joysticks rarely rest at 128, and their travel is often different in each direction. Thus the reader learns the rest position and the travel:

The starter and STARFALL use the stick and the keys together. A deflected stick has priority. A stick at its rest position lets the keys control the movement. Thus the stick and the keyboard can stay connected together:

uint8_t stick = stick_enabled ? ti_stick_read(0) : TI_STICK_ABSENT;
uint8_t input;
read_keys();
if (stick != TI_STICK_ABSENT && stick) input = stick;   /* deflected */
else input = direction;                                  /* keys */

Give the players a key that sets the stick off. STARFALL uses J. Without this key, a stick with a bad drift always has priority over the keys.

Buttons

ti_input_buttons returns the three button inputs as bits 0-2. The two joystick buttons are the Open Apple and Solid Apple inputs, bits 0 and 1. To find a new push, compare the value with the last value:

static uint8_t last_buttons;
uint8_t now = ti_input_buttons() & 3;
uint8_t pressed = now && !last_buttons;
last_buttons = now;
if (pressed) drop_bomb();

A player can hold a button when the state of the game changes, for example when the ship is lost. Ignore pushes for a short time after such a change. STARFALL accepts a retry only one second after the loss of the ship.

Raw readings

ti_joystick_read returns the two paddles as values 0-255, and the buttons. Use it for analog control, or for a screen that shows the stick values. The STARFALL pause panel shows JOY x y, so that a player can examine a stick.

A paddle that has not finished after 4 ms reads 255, and the call returns TI_E_TIMING. If interrupts disturb a read, the call returns TI_E_BUSY and keeps the old values. Then try again in the next frame.

ti_joystick j;
if (ti_joystick_read(&j) != TI_E_BUSY) show_stick(j.x, j.y);

Why paddle reads wait

On the //e, each access to $C070-$C07F starts the paddle timers. This includes the RamWorks bank selector at $C073. A 558 timer that has not finished ignores a new start. A read that starts too soon after such an access measures from the previous start, and its value is too short. On a stick, the right and down readings then jump back.

Thus libtini does not write the selector for bank 0 transfers, which include all screen publications. Each read also waits a maximum of 4 ms for running timers to finish. Do not access $C070-$C07F in your code between reads.