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:
- Key events for commands and menus.
ti_input_pollputs each new key into a queue of eight characters.ti_input_keytakes the keys from the queue in sequence. - The held key for movement.
ti_input_heldreturns the newest key while a key is down, or 0. If you hold A and push Space, it returns Space until all keys are up. Thus two held directions cannot give a diagonal. Use the joystick for diagonals.
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 first reading in the range 48-207 on the two axes becomes the rest position. Call
ti_stick_recalibratewhen the stick is not touched. For example, call it when a run starts, when a game continues after a pause, or when the player sets the stick on again. - A direction becomes active at more than one third of the largest travel on that side, with a minimum of 16 units. It becomes inactive at three quarters of that value. Thus a stick near the limit does not change between active and inactive.
- When no stick is connected, the reader returns
TI_STICK_ABSENT. After four such reads, it waits three seconds before it measures again. Thus an empty port costs little time.
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.