Jump to content
  • entries
    28
  • comments
    28
  • views
    21,056
Message added by OLD CS1

See the first comment below for enhancement to this routine.

TI BASIC: Search for a fast and easy input routine for games


OLD CS1

602 views

drawer_ti99.png.1ea4241f9a4961815e952048ed810098.pngTI BASIC built into the TI-99/4A console is slow.  Really slow.  This lack of speed hampers a lot of good game development for console BASIC.  Not impossible, mind you.  Many game makers working in TI BASIC have found ways to work with the lack of speed while still maintaining a functional game.  As well, players generally get used to knowing they have to hold a key down or joystick in the direction until the magical moment the program is able to run the detection.

 

For my purposes, I need to detect several things as quickly as possible and ideally in the same subroutine.  I need to detect joystick (cardinal directions,) "arrow keys," fire button, function (FCTN) key presses, maybe other keys.  This is a lot with which to deal: a joystick, a special key unit for the arrow keys, and another key unit for other key presses.

 

Thus: the UserInput subroutine which is present in my two BASIC games released on AtariAge in the TI-99 subforum, though more refined here.  The routine will return values in J={0..5} for nothing pressed, left, right, down, up, and fire, and optionally {6..} for FCTN keys.

 

UserInput is not a general-purpose input routine, but is specifically tailored to return cardinal directional presses of the joysticks, fire buttons, equivalent key presses, and optional FCTN keys.  It is presented here in TIdBiT, a pre-processor by Matthew Hagerty @matthew180 for writing more structured programs in TI BASIC and TI Extended BASIC.  The source at the bottom of this post can be used in your own programs and translated at https://tidbit99.com.

 

UserInput requires three entrance variables.  First, P for player number, negative if only scanning the joystick.  In my games I use P to track player number so this tracks itself.  However, if wanting to do something fancy which the routine will allow, your program could use another variable to track the current player and then put the appropriate value of {1, 2} or {-1, -2}.


What happens with these values of P?

P={1,2}: scan split keyboard and joysticks, fall-through to scan unit 3
P={-1,-2}: scan joysticks, fall-through to scan unit 3

Effectively, a negative value for P indicates to only scan the joystick for direction, ignoring split-keyboard mode presses (units 1 and 2) including the fire button (so be careful with this.)

 

Next is K$(0), which contains the key codes for left, right, down, up, and fire.  For the default sense, this is the values 2, 3, 0, 5, and 18, for the keys "SDXEQ" for scan unit 1 and "JKMIY" for scan unit 2.  (See User's Reference Guide page II-88 or page 204 of the TI Extended BASIC manual.)
 

K$(0)=CHR$(2)&CHR$(3)&CHR$(0)&CHR$(5)&CHR$(18)

 

The last required variable is K$(1), which holds the alternate fire button key (required, generally the space bar,) and any optional function keys per keyboard scan unit 3, in the range {1..15}.  (See User's Reference Guide page II-89 or page 199 of the TI Extended BASIC manual.)  In my games I use this to define FCTN keys, like AID, REDO, and BACK, where these FCTN keys will return 6, 7, and 8, respectively.

 

As mentioned above, this is a little more of a trick hidden here.  The -P for joystick-only scanning is a bit of a fib.  If a joystick scan does not return on of the four cardinal directions, the standard keyboard is still read.  This makes the routine still useful for detecting FCTN keys.  Since keyboard scan unit 3 is in use here, only upper-case and FCTN keys are available.  The scan unit can be changed to 4 (Pascal) or 5 (BASIC) if desired for whatever purpose, and K$(1) set accordingly including those which would conflict with the split-keyboard codes.  Character codes in K$(1) will return values of {5..x}, where x is base of 4 plus the length of the variable.  This, however, goes well beyond the purpose of the routine and would be more complicated than necessary.

 

(The listings below includes simple modification to make K$(1) not require the alternate fire key and make the return {6..x}, where 6 is the first character of K$(1).)

 

K$(1)=" "&CHR$(1)&CHR$(6)&CHR$(15)


With variables set as required, your program will call the subroutine with
 

GOSUB UserInput


The flow of the TI BASIC version is below to demonstrate operation.  It starts with quick house-cleaning and check the detection type wanted:  Clear J, the return variable, and see if the player number is negative which indicates to only scan the joysticks.
 

UserInput:
 J=0
 IF P<0 THEN JoyStickInput


If the keyboard is to be scanned, scan the unit number of the current player.  Jump to scanning the joystick if no key is pressed.  If a key is pressed, then decode for a valid value from K$(0).  If valid, return to the calling program, otherwise we know a key not joystick-related is pressed so it needs to be checked against standard keyboard scan unit 3 and K$(1) (the jump to KeyScan.)  This introduces a bit of a race condition here, as it is possible the user can press a key and get caught by the split-keyboard scan but release it before the standard scan.
 

 CALL KEY(P,K,S)
 IF S THEN UnitKeyPress ELSE JoyStickInput
UnitKeyPress:
 J=POS(K$(0),CHR$(K),1)
 IF J=0 THEN KeyScan
RETURN


At this point since no split keyboard unit has been pressed, or P is negative, then the joysticks are scanned.  CALL JOYST (User's Reference Guide page II-90, TI Extended BASIC manual page 109,) returns two values in the set {-4,0,4}.  This check is logically (X OR Y), meaning if the joystick is pressed in any direction.  As this routine is only concerned about cardinal directions, the mathematical result of X+Y also has the logical benefit of ignoring two of the four ordinal directions to be filtered in the next check.
 

JoyStickInput:
 CALL JOYST(ABS(P),X,Y)
 IF X+Y THEN JoyPress


If no joystick press is detected, fall through to CALL KEY keyboard scan unit 3, validated against K$(1).
 

KeyScan:
 CALL KEY(3,K,S)
 IF S THEN KeyPress ELSE ExitInput
KeyPress:
 J=4+POS(K$(1),CHR$(K),1) // Change to J=5+... to skip a fire-button alternative key,
                          // but note Q and Y will still be detected as fire buttons

 

Next is the main exit and return for the input routine.  At this point J should contain a valid key-press validated against K$().  Anything else is rejected.
 

DecodeJoySt:
 J=J*-(J>4) // Change to (J>5) to skip alternate fire key-press (Q,Y still detected as fire)
ExitInput: RETURN


The last bit of decoding eliminates the remaining ordinal joystick directions using X*Y (X AND Y in Extended Basic) which were not filtered previously by X+Y.  Logically, (X*Y) is (X AND Y), which means this detection is rejected if a the joystick is pressed in both an X and Y direction.  The second part now turns X{-4,4} or Y{-4,4} into the values {0..4} corresponding to none (which should never make it here to be detected,) left, right, down, and up, by way of a somewhat convoluted formula (likely the cause of the slower response to joystick than the keyboard) which produces the below value table for the valid values of X and Y.  The routine then returns from here.

 X  Y  J
-- -- --
-4  0  1
 0 -4  3
 0  0  0
 0  4  4
 4  0  2

 

JoyPress:
 IF X*Y THEN DecodeJoySt
 J=ABS(((SGN(X)+3)/2)*SGN(X))+(ABS(((SGN(Y)+3)/2)*SGN(Y))-2*(Y<>0))
RETURN

 

Notes on UserInput subroutine variable usage:

  • Requires: P, K$(0), K$(1) as described above
  • Uses: X, Y, K, S
  • Returns: J

Obviously these variables can be changed as seen fit.  For additionally sorcery, K will contain the key code of the last key detection.

 

Label usage by UserInput subroutine:

  • UnitKeyPress
  • JoyStick
  • KeyScan (not in TI Extended BASIC version)
  • DecodeJoySt
  • JoyPress

 

While working on this post I had a few ideas

  • I should elaborate on the X,Y to J conversion formula.  Perhaps in a later post.
  • There is a possible opportunity for some change in flow which may or may not help with additional optimization.  As it is, the routine is pretty fast though there is a noticeable but negligible slower response to joystick than keyboard.
  • Using K$(0..1) is not memory efficient as, by default, an array is allocated with 10 elements.  Thus, program memory footprint and variable stack usage may be reduced by using something like K0$ and K1$, or maybe more by using appropriately-named single-character string variables (perhaps [$ and ]$ for fun.)
  • In the TI Extended BASIC version, several changes were made to logic and combination of lines into multi-statement lines.
  • The TI Extended BASIC version contains some logic changes made to favor execution speed.  For instance, the statement IF S THEN (next line) ELSE RETURN is faster than IF S=0 THEN RETURN by a difference of 15 seconds over 10,000 iterations.

 


TIdBiT source of the full routine for TI BASIC

UserInput:
 J=0
 IF P<0 THEN JoyStickInput
 CALL KEY(P,K,S)
 IF S THEN UnitKeyPress ELSE JoyStickInput
UnitKeyPress:
 J=POS(K$(0),CHR$(K),1)
 IF J=0 THEN KeyScan
RETURN
JoyStickInput:
 CALL JOYST(ABS(P),X,Y)
 IF X+Y THEN JoyPress
KeyScan:
 CALL KEY(3,K,S)
 IF S THEN KeyPress ELSE ExitInput
KeyPress:
 J=4+POS(K$(1),CHR$(K),1)
DecodeJoySt:
 J=J*-(J>4)
ExitInput: RETURN
JoyPress:
 IF X*Y THEN DecodeJoySt
 J=ABS(((SGN(X)+3)/2)*SGN(X))+(ABS(((SGN(Y)+3)/2)*SGN(Y))-2*(Y<>0))
RETURN

 

TIdBiT source of the full routine for TI Extended BASIC

(Translates to seven lines of Extended BASIC code.)

UserInput:
 J=0 ::
 IF P<0 THEN JoyStick ELSE
 CALL KEY(P,K,S) ::
 IF S THEN UnitKeyPress ELSE JoyStick
UnitKeyPress:
 J=POS(K$(0),CHR$(K),1) ::
 IF J THEN RETURN ELSE KeyScan
JoyStick:
 CALL JOYST(ABS(P),X,Y) ::
 IF X+Y THEN JoyPress
KeyScan:
 CALL KEY(3,K,S) ::
 IF S THEN J=4+POS(K$(1),CHR$(K),1) ELSE RETURN
DecodeJoySt:
 J=J*-(J>4) :: 
 RETURN
JoyPress:
 IF X AND Y THEN DecodeJoySt ELSE
 J=ABS(((SGN(X)+3)/2)*SGN(X))+(ABS(((SGN(Y)+3)/2)*SGN(Y))-2*(Y<>0)) ::
RETURN

 

1 Comment


Recommended Comments

Putting some thought into the conversions of X and Y from {-4,0,4} in each to {0..4}, the original formula was developed a long time ago and I just left it since it works. Nonetheless, the duplicate calls to SGN() in each conversion has nagged me. I have been sick the past couple of days and last night, in a bout of physical exhaustion and boredom, I had a go at it.

ABS(((SGN(X)+3)/2)*SGN(X))+(ABS(((SGN(Y)+3)/2)*SGN(Y))-2*(Y<>0))

can be optimized as

(ABS(X)/2+(X<0))+(ABS(Y)+(Y<0))

Each conversion is 6 seconds faster per 500 iterations in TI BASIC, and 12 seconds for both together over the same period, roughly a considerable 2.4 seconds faster per 100 iterations.  This winds up being noticeable and makes the joystick feel almost as responsive as the keyboard.

 

Breaking down each direction, the original X conversion calls three functions, two of which are duplicate, three maths, in three priorities.  The new conversion calls one function, two maths, one comparison in two priorities.  The original Y conversion calls one additional math over X, while the new conversion calls one less math than the new X.  I do not have a list on-hand of the speed of each function or mathematical operation in TI BASIC so I have no literals to provide, just the anecdotal execution results.

  • Like 1
Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...