5. Input#
5.1 Joystick input#
Analog joysticks (or paddles) on the Apple //e are just variable resistors. The resistor on a paddle
creates an RC circuit with a capacitor which can be discharged by accessing the PTRIG location. Once
that is done, the capacitor starts charging through the resistor. The lower the resistor value, the
faster the charge.
At the start, each PADDL value has its high bit set to one.
When the voltage on the capacitor reaches 2/3 of the supply voltage, the
corresponding PADDL switch will have its high bit set to zero. So, we just need to watch the PADDL
value until it is non-negative, counting the amount of time it takes for that to happen.
In the READ_PADDLES routine, we trigger the paddles and then alternately read PADDL0 and PADDL1
until one of them indicates the threshold was reached. If the PADDL value hasn't yet triggered,
we increment the corresponding PADDLE_VALUE location.
Once a PADDL triggers, we stop incrementing the corresponding PADDLE_VALUE.
Once both PADDL have been triggered, we end the routine.
PADDLE0_VALUE EQU $65 PADDLE1_VALUE EQU $66 PADDL0 EQU $C064 PADDL1 EQU $C065 PTRIG EQU $C070
ORG $8746 READ_PADDLES: SUBROUTINE LDA #$00 STA PADDLE0_VALUE STA PADDLE1_VALUE ; Zero out values LDA PTRIG .loop: LDX #$01 ; Start with paddle 1 .check_paddle: LDA PADDL0,X BPL .threshold_reached INC PADDLE0_VALUE,X .check_next_paddle DEX BPL .check_paddle ; Checked both paddles LDA PADDL0 ORA PADDL1 BPL .end ; Both paddles triggered, then end. LDA PADDLE0_VALUE ORA PADDLE1_VALUE BPL .loop ; Unconditional .threshold_reached: NOP BPL .check_next_paddle ; Unconditional .end: RTS
The INPUT_MODE location tells whether the player is using keyboard or joystick input.
The CHECK_JOYSTICK_OR_DELAY routine, if we are in joystick mode, reads the paddle values
and checks to see if any value is below 0x12 or above 0x3A, and if so, declares that
a paddle has a large enough input by setting the carry flag and returning.
If neither paddle has a large enough input, we also check the paddle buttons, and if either one is triggered, we set the carry and return.
Otherwise, if no paddle input was detected, or we're in keyboard mode, we clear the carry and return.
INPUT_MODE EQU $95 ; 0xCA = Joystick mode (J), 0xCB = Keyboard mode (K) ; initially set to 0xCA JOYSTICK_MODE EQU #$CA KEYBOARD_MODE EQU #$CB BUTN0 EQU $C061 ; Or open apple BUTN1 EQU $C062 ; Or solid apple
ORG $876D CHECK_JOYSTICK_OR_DELAY: SUBROUTINE LDA INPUT_MODE CMP #KEYBOARD_MODE BEQ .delay_and_return ; Keyboard mode, so just delay and return JSR READ_PADDLES LDA PADDLE0_VALUE CMP #$12 BCC .have_joystick_input ; PADDLE0_VALUE < 0x12 CMP #$3B BCS .have_joystick_input ; PADDLE0_VALUE >= 0x3B LDA PADDLE1_VALUE CMP #$12 BCC .have_joystick_input CMP #$3B BCS .have_joystick_input LDA BUTN1 BMI .have_joystick_input LDA BUTN0 BMI .have_joystick_input CLC RTS .have_joystick_input: SEC RTS .delay_and_return: LDX #$02 .loop: DEY BNE .loop DEX BNE .loop CLC RTS
CHECK_JOYSTICK_OR_DELAYIf, after a timeout, a button hasn't been pressed, just assume keyboard mode.
ORG $87A2 DETECT_LACK_OF_JOYSTICK: SUBROUTINE LDA PTRIG LDX #$10 .loop: LDA PADDL0 ORA PADDL1 BPL .return DEY BNE .loop DEX BNE .loop LDA #KEYBOARD_MODE STA INPUT_MODE .return: RTS
5.2 Keyboard routines#
The WAIT_KEY routine accesses the keyboard strobe softswitch KBDSTRB, which clears the keyboard
strobe in readiness to get a key. When a key is pressed after the keyboard strobe is cleared,
the key (with the high bit set) is accessible through KBD
The WAIT_KEY_QUEUED routine does not clear the keyboard strobe first, so if a key had
been pressed before entering the routine, the routine will immediately return.
ORG $86A8 WAIT_KEY_QUEUED: SUBROUTINE LDA KBD BPL WAIT_KEY_QUEUED STA KBDSTRB RTS
ORG $8745 CURSOR_SPRITE: HEX 06
ORG $85F3 WAIT_FOR_KEY: SUBROUTINE ; Enter routine with A set to cursor sprite. If zero, sprite 10 (all white) ; will be used. STA CURSOR_SPRITE .loop: LDA #$68 STA SCRATCH_A1 LDA CURSOR_SPRITE BNE .draw_sprite LDA #SPRITE_ALLWHITE .draw_sprite: JSR DRAW_SPRITE_PAGE2 .loop2: LDA KBD BMI .end ; on keypress, end JSR CHECK_JOYSTICK_OR_DELAY DEC SCRATCH_A1 BNE .loop2 ; Draw a blank LDA #$00 JSR DRAW_SPRITE_PAGE2 LDA #$68 STA SCRATCH_A1 .loop3: LDA KBD BMI .end JSR CHECK_JOYSTICK_OR_DELAY DEC SCRATCH_A1 BNE .loop3 JMP .loop .end: PHA LDA CURSOR_SPRITE JSR DRAW_SPRITE_PAGE2 PLA RTS
WAIT_FOR_KEYORG $8700 WAIT_FOR_KEY_WITH_CURSOR_PAGE_1: SUBROUTINE ; Enter routine with A set to cursor sprite. If zero, sprite 10 (all white) ; will be used. STA CURSOR_SPRITE .loop: LDA #$68 STA SCRATCH_A1 LDA #$00 LDX CURSOR_SPRITE BNE .draw_sprite LDA #SPRITE_ALLWHITE .draw_sprite: JSR DRAW_SPRITE_PAGE1 .loop2: LDA KBD BMI .end ; on keypress, end JSR CHECK_JOYSTICK_OR_DELAY BCS .end DEC SCRATCH_A1 BNE .loop2 LDA CURSOR_SPRITE JSR DRAW_SPRITE_PAGE1 LDA #$68 STA SCRATCH_A1 .loop3: LDA KBD BMI .end JSR CHECK_JOYSTICK_OR_DELAY BCS .end DEC SCRATCH_A1 BNE .loop3 JMP .loop .end: PHA LDA CURSOR_SPRITE JSR DRAW_SPRITE_PAGE1 PLA RTS
WAIT_FOR_KEY_WITH_CURSOR_PAGE_1This routine is used by the level editor whenever we need to wait for a key. If the key isn't the escape key, we can immediately exit, and the caller interprets the key. However, on escape, we abort whatever editor command we were in the middle of, and just go back to the main editor command loop, asking for an editor command.
ORG $823D EDITOR_WAIT_FOR_KEY: SUBROUTINE LDA #$00 JSR WAIT_FOR_KEY_WITH_CURSOR_PAGE_1 STA KBDSTRB CMP #$9B ; ESC BNE .return JMP EDITOR_COMMAND_LOOP .return RTS
EDITOR_WAIT_FOR_KEYORG $80D8 HIT_KEY_TO_CONTINUE: SUBROUTINE ; "\r" ; "\r" ; "HIT A KEY TO CONTINUE " JSR PUT_STRING HEX 8D 8D C8 C9 D4 A0 C1 A0 CB C5 D9 A0 D4 CF A0 C3 HEX CF CE D4 C9 CE D5 C5 A0 00 JSR BEEP STA TXTPAGE2 LDA #$00 JSR WAIT_FOR_KEY STA KBDSTRB STA TXTPAGE1 RETURN_FROM_SUBROUTINE: RTS
HIT_KEY_TO_CONTINUE, RETURN_FROM_SUBROUTINEThe GET_LEVEL_FROM_KEYBOARD is used by the level editor to ask the user for
a 3-digit level number. The current level number, given by DISK_LEVEL_LOC, is
put on the screen. Note that DISK_LEVEL_LOC is 0-based, while the levels the
user enters are 1-based, so there's an increment at the beginning and a decrement
at the end.
The routine handles forward and backward arrows. Hitting the escape key aborts
the editor action and dumps the user back into the editor command loop. Hitting
the return key accepts the user's input, and the level is stored in DISK_LEVEL_LOC
and LEVELNUM.
ORG $824E SAVED_GAME_COLNUM: HEX 85
ORG $817B GET_LEVEL_FROM_KEYBOARD: SUBROUTINE LDY DISK_LEVEL_LOC INY TYA JSR TO_DECIMAL3 ; make 1-based LDA GAME_COLNUM STA SAVED_GAME_COLNUM LDY #$00 ; Print current level .loop: LDA HUNDREDS,Y STY KBD_ENTRY_INDEX ; save Y JSR PUT_DIGIT LDY KBD_ENTRY_INDEX ; restore Y INY CPY #$03 BCC .loop LDA SAVED_GAME_COLNUM STA GAME_COLNUM LDY #$00 STY KBD_ENTRY_INDEX .loop2 LDX KBD_ENTRY_INDEX LDA HUNDREDS,X CLC ADC #$3B ; sprite = '0' + X JSR WAIT_FOR_KEY_WITH_CURSOR_PAGE_1 STA KBDSTRB CMP #$8D ; return BEQ .return_pressed CMP #$88 ; backspace BNE .check_for_fwd_arrow LDX KBD_ENTRY_INDEX BEQ .beep ; can't backspace past the beginning DEC KBD_ENTRY_INDEX DEC GAME_COLNUM JMP .loop2 .check_for_fwd_arrow: CMP #$95 ; fwd arrow BNE .check_for_escape LDX KBD_ENTRY_INDEX CPX #$02 BEQ .beep ; can't fwd past the end INC GAME_COLNUM INC KBD_ENTRY_INDEX JMP .loop2 .check_for_escape: CMP #$9B ; ESC BNE .check_for_digit JMP EDITOR_COMMAND_LOOP .check_for_digit: CMP #$B0 ; '0' BCC .beep ; less than '0' not allowed CMP #$BA ; '9'+1 BCS .beep ; greater than '9' not allowed SEC SBC #$B0 ; char - '0' LDY KBD_ENTRY_INDEX STA HUNDREDS,Y JSR PUT_DIGIT INC KBD_ENTRY_INDEX LDA KBD_ENTRY_INDEX CMP #$03 BCC .loop2 ; Don't allow a fourth digit DEC KBD_ENTRY_INDEX DEC GAME_COLNUM JMP .loop2 .beep: JSR BEEP JMP .loop2 .return_pressed: LDA SAVED_GAME_COLNUM CLC ADC #$03 STA GAME_COLNUM LDA #$00 LDX HUNDREDS BEQ .add_tens CLC .loop_hundreds: ADC #100 BCS .end DEX BNE .loop_hundreds .add_tens: LDX TENS BEQ .add_units CLC .loop_tens: ADC #10 BCS .end DEX BNE .loop_tens .add_units: CLC ADC UNITS BCS .end STA LEVELNUM TAY DEY STY DISK_LEVEL_LOC CPY #$96 .end: RTS
GET_LEVEL_FROM_KEYBOARD