Hac-Man Retrochallenge: Getting InputPosted: July 12, 2012
An important part—one of the important parts of a video game is the input processing. Whether the player controls his or her persona, character or avatar with a mouse, joystick, keyboard or game controller, there has to be a way for our game code to take feedback from the outside world and use it to affect the world in the game. It also helps if there’s some way to stop the game if the player’s about to miss the bus, or take a refreshment break.
RSTS/E is normally configured for line-based, line-delimited, input, such as one would see in a command shell like Bash, Windows CMD or Powershell. It’s not normally able to read characters on the fly.
Many early microcomputer implementations of BASIC did eventually include some means of character-based inputs; some of you may be familiar with the INKEY$ variable that was part of most versions of Tandy BASIC across their product line.
RSTS/E turns out to have such a capability, but it’s not real easy to use. RSTS/E needed to have character-based input to support certain system software, notably ODT, an all-purpose machine-language debugger and binary editor.
To access this capability from within a BASIC program, one must open the local keyboard as a file, specifying some special modes. I’ve written a short test program that reads input as required by the game, which I’ll explain presently, line by line:
100 EXTEND 490 ES$ = CHR$(27) ! Escape character 500 LA$ = ES$+"[D" ! VT100 Left arrow 502 RA$ = ES$+"[C" ! VT100 Right arrow 504 UA$ = ES$+"[A" ! VT100 Up arrow 506 DA$ = ES$+"[B" ! VT100 Down arrow 508 ! Quit, Help and Restart keys 518 ! Combine arrow keys in one search string. & ! Note the filler characters at the start of the string & ! All arrow key sequences are three characters long, including the escape char. 520 ARKY$ = CHR$(0)+CHR$(0)+CHR$(0)+LA$+RA$+DA$+UA$ 528 ! Game command keys are also combined in one search string. 530 CMKY$ = "QHR "
First, the preliminaries; we have to define the characters we are checking for. The player is on a VT-100 terminal, so we must define its arrow key sequences in lines 500-506. For brevity, we reserve a string variable ES$ for the escape character.
There are also other keys we need to define. “Q” is obvious, it quits the game. “R” restarts. The space bar is important. In a real game with a joystick, the player can freeze his or her ship or character on the screen by releasing the controller; with a keyboard, it’s a bit different—the player hits an arrow key to move, and the player sprite keeps moving, until he or she hits the space bar.
There is another key “H” for a high score display but that’s not relevant at the moment.
There are two character search strings, which I will explain as we go along.
Next, we open the keyboard for input:
540 ! Open the keyboard for input 1000 OPEN "KB:" FOR INPUT AS FILE #1 1010 X% = SPEC%(7,0,1,2%) ! Disable type-ahead 1020 X% = SPEC%(0,0,1,2%) ! Cancel Ctrl-O 1030 X% = SPEC%(3,0,1,2%) ! Disable echo 1050 FIELD #1%, 128% AS B$ 1060 LSET B$ = "" ! Flush buffer 1100 ON ERROR GOTO 2000 1110 PRINT "Waiting for input..."
With this idiomatic sequence of code, we open the keyboard for input on channel #1. Microsoft BASIC virtually copied much of the syntax and semantics of file access from DEC BASIC, so if you’ve used a Tandy, you might have seen some code like this.
Lines 1010 through 1030 set up some special conditions we need to take input. We disable type-ahead, the well known phenomenon we see in online web forms, Facebook and Twitter where people type whole messages to a blank form box, only to see their message pop up already sent with misspellings, usually including phrases like “StopQuitABORTControl-CDammit$$$$!%” When we start the game we want no stray input. Line 1030 is self-explanatory; it keeps the system from echoing arrow keys and other input, which would destroy the display. Line 1020 disables a certain control character that could also disrupt the game.
RSTS/E requires us to process our input with record-based input in lines 1050 and 1060. To make a long story short, our text is going to go into the buffer B$. Line 1060 flushes the buffer before we begin. Line 1100 sets up error handling for the next section of the code.
1150 ! Start character input loop 1155 X%=SPEC%(4,0,1,2%) ! Set ODT Mode 1160 GET #1%, RECORD 8192%
This is where we actually start reading characters. Line 1155 sets the special character input mode. Line 1160 gets characters from the terminal. RECORD 8192% tells RSTS/E not to wait for input. If there is no input, an error is thrown, which we handle later on.
1170 I$ = LEFT(B$,RECOUNT) 1175 IF I$<>ES$ THEN GOTO 1400 ! If it's not an escape sequence, we are done getting chars. 1180 X% = SPEC%(4%,0%,1%,2%) ! Set ODT mode to get remaining escape sequence 1185 GET #1%, RECORD 8192% ! And get the rest of the sequence 1190 I$ = I$+LEFT(B$,RECOUNT) 1200 I2% = INSTR(1%,ARKY$,I$)/3 1210 PRINT "I2%=";I2%;", I$=";I$;", Count=";RECOUNT
We have our input, but we’re not done. Some of our characters are escape sequences, and others are normal characters. Line 1170 retrieves the character from the buffer. If it’s an escape sequence, we need to get the rest of the characters in the sequence. This is done in lines 1185-1190. Line 1200 detects the arrow key characters and returns a value in I2%. RECOUNT refers to the number of characters left in the buffer.
1220 IF I2%<>0% THEN ON I2% GOSUB 1320, 1330, 1340, 1350 1250 LSET B$ = "" 1290 GOTO 1150 1320 PRINT "** Left arrow" \ RETURN 1330 PRINT "** Right arrow" \ RETURN 1340 PRINT "** Up arrow" \ RETURN 1350 PRINT "** Down arrow" \ RETURN
In line 1220, if I2% is not zero, meaning we have valid input, we do an ON…GOSUB on that index. This familiar BASIC construct is somewhat like a CASE statement, though it really should be called an indexed function dispatch. Each of the lines in 1320-1350 print a message at the terminal indicating the arrow key that was pressed. In a game, this will be replaced by specific handling code. Line 1250 clears the buffer and line 1290 goes back to the beginning of the input loop.
Just a few more lines left:
1399 ! 1 char codes 1400 I1% = INSTR(1%,CMKY$,CVT$$(I$,32%)) ! Convert input to uppercase before checking 1405 PRINT "I1%=";I1% 1410 IF I1%<>0% THEN ON I1% GOSUB 1430,1440,1450,1460 1415 LSET B$ = "" 1420 GOTO 1150 1430 PRINT "** Quit key" \ RETURN 1440 PRINT "** High Score" \ RETURN 1450 PRINT "** Restart" \ RETURN 1460 PRINT "** (space)" \ RETURN 1470 RETURN 2000 IF ERR=13 AND ERL=1160 THEN RESUME 1150 2010 PRINT "ERR=",ERR,"ERL",ERL 2030 X% = SPEC%(2%,0%,1%,2%) ! Reenable echo 2040 STOP 9999 END
Lines 1400 through 1470 perform the same processing for one-character non-escape inputs. The CVT$$ function in line 1400 converts the incoming characters to upper case, so that a player’s input to quit the game will work whether he or she enters “q” or “Q”. Note line 2000. The GET #1, RECORD 8192% statement that performs the input will error out if there is no input, which will certainly happen through the course of the game as the game loop is (or should be) faster than the player. Line 2000 checks for the specific error (error 13, equivalent to a end-of-file error on most platforms) at the specific line. If it comes from a normal no input condition, it resumes at the top of the input loop. Otherwise, it prints an error message, re-enables echo on the terminal and ends the program.
This is all we need for input. Next post will cover more graphics, including how I handled object detection. Soon after, I can actually start moving sprites on the screen—I have no more excuses, no more exposition!