17

The ZX BASIC Manual gives the following little program to demonstrate how INKEY$ works:

10 IF INKEY$ <> "" THEN GO TO 10 
20 IF INKEY$ = "" THEN GO TO 20 
30 PRINT INKEY$; 
40 GO TO 10

While I have ascertained that this works, and that if you remove line 10 (as the manual goes on to suggest), you get several letters displayed per keypress, I just don't understand the logic of it. I mean, IF INKEY$ <> "" and IF INKEY$ ="" would seem to cover all logical possibilities. How does the program ever get to line 30?

So my question is: What exactly is the logic of INKEY$ in ZX BASIC, and why do these two conditions not produce an inescapable loop?

  • 6
    Ugh, a thousand memories have just returned of when not being able to receive multiple keypresses was the thing that destroyed many an attempted game in BASIC. – Tommy Feb 20 '18 at 14:50
  • 1
    @HarryJohnston Well spotted: I didn't have the benefit of the Sinclair keyboard there :-D – harlandski Feb 21 '18 at 8:49
  • Get one, they still show up on flea markets and online sites for low money - today often with a realy impressive collection of additional hardware and books. You need to feel the real thing. – Raffzahn Feb 22 '18 at 11:48
  • 1
    @Raffzahn I do have a real working rubber-keyed ZX Spectrum and a ZX81 (not to mention a German-keyboard Epson HX-20 which I bought on a flea market in Berlin), but my original typo came from re-typing the Speccy code on my modern keyboard, so without the benefit of BASIC keyboard tokens :-) – harlandski Feb 23 '18 at 0:57
  • 1
    @Raffzahn I agree there's nothing like the real thing! – harlandski Feb 23 '18 at 1:07
23

Some comprehensive BASIC to start with

There are BASICly three (*1) kinds of statements to handle single keystrokes in various BASICs (*2):

  1. Waiting for a single keyvalue to arrive and returning it.

  2. Checking if a key has been pressed, if yes, it's read and returned, otherwise an empty value is returned

  3. Delivering the actual state of the (decoded) keyboard without any waiting or checking.

Most classic BASICs follow version #1 or #2, as they work with keystroke buffers like when there is a terminal connection. Many homecomputer BASICs, especially of the Microsoft kind follow #2, while mainframe BASICs often follow #1. Usually, but not always, it is called GET (*3). #3 is common among 'creative' BASICs that support game programming, including Sinclair BASIC.

All three have their reasoning:

Version #1 stops the program run until a keypress is received, thus waiting for it does not need to do any further handling. Convenient on more text and business style application - also, in a mainframe environment, input is sometimes only available in blocking versions

Version #2 is about as low level as any terminal based system can get. It only peeks into the input buffer, but if there is a character, it gets extracted and delivered. This allows more interactivity, but still can only report 'no keypress' or 'this key has been pressed and released' (*4).

Version #3 now is something that can only be accomplished if the keyboard state is in direct access to the BASIC interpreter. It delivers this state each time called. So handling input is much like on Assembly programs. There is no filter about pressing or releasing. This has to be done by the BASIC program. As a result, each and every key (*5) behaves much like a joystick switch. Not only pressing, but also exact timing of press and release can be detected - thus including duration of press.

In practice this translates to three different logics:

Version #1 is the most relaxed, as it always returns with a key pressed.

Version #2 also returns when no key is pressed, so if waiting is required, it needs to be looped around. Of course, other things can be done during that loop before checking the keyboard again.

Version #3 does need edge discrimination (*6) when used, differentiating key press and key release to form a keystroke if this is the goal.

Now for your question

What exactly is the logic of INKEY$ in ZX BASIC,

Sinclair BASIC's INKEY$ follows Version #3 and delivers just the actual state. Perfect for timing relevant game programming, but a bit more work for the programmer of more mundane tasks.

In your example it works like this:

10 IF INKEY$ <> "" THEN GO TO 10

This loop waits until no key is pressed by reading the matrix over and over again

20 IF INKEY$ = "" THEN GO TO 20 

This loop waits until a key is pressed by reading the matrix over and over again

30 PRINT INKEY$;

Now the matrix gets read one more time and the result is displayed.

and why do these two conditions not produce an inescapable loop?

Because it's always checking the actual state, not some buffer. The logic is the one of an edge discriminator (*6), not so much of an OS call as modern languages offer.

But yes, there is a chance to screw it, if you're able to press and release faster than thee lines are executed may result, depending on your timing, in lost keystrokes, missed keystrokes or wrong returned. Then again, We are talking about a human operation the membrane here. Fat chance :))

A truly secure routine would be a bit slower, a bit more complex and look like this:

10 A$ = INKEY$
20 IF A$ = "" THEN GO TO 10
30 IF INKEY$ <> "" THEN GO TO 30
40 PRINT A$
50 GO TO 10

Now we read the keyvalue only once and store it for later processing. This will always record the first key pressed, no matter if later on others got pressed (before releasing all).


*1 - Make that 6, as some BASICs define INKEY as numeric, while others (like Sinclair) made it string based.

*2 - It's INKEY on Sinclair, GET on Microsoft, and maybe other names on lesser known dialects. I just use INKEY for the remaining answer to simplify wording. I'm well aware the Sinclair might have chosen INKEY instead of GET to make clear that here a different behaviour is to be expected.

*3 - None of these single key reading operations where part of the original Dartmouth BASIC (as FORTRAN didn't offer anything about keystrokes), thus names are made up by each implementation in their own way.

*4 - Usually it's reported the time it is pressed, but both ways have been used.

*5 - Depending on the keyboard structure and scanning, multiple key combinations may or may not work.

*6 - An edge discriminator in electronics is a circuit to detect when a signal changes, like detecting a rising or falling flank (aka edge), for example in interrupt handling or alinke. Here it describes a procedural way to detect when a condition changes - from not pressed to pressend, and back.

  • Don't want to nitpick too much but I don't remember seeing an INKEY$ that did option 1. GET usually paused and INKEY returned immediately (BBC Micro follows this rule) MS Basics usually used INKEY$ (certainly any that used GET for file IO did). The only exceptions would be commodore basics that didn't wait. Atari basic seems even weirder – PeterI Feb 20 '18 at 13:54
  • @PeterI Applesoft (a MS-BASIC) did use GET and did not wait. The whole behaviour of GET/INKEY/whatsoeveritsnamed is machine/BASIC specific, as it is a leter addition. Same for the name. There is no well defined rules what name will cause what behaviour. For #1, there is for example a /370 BASIC close based on the original Dartmouth BASIC but offering a waiting GET (for reading terminal function keys) – Raffzahn Feb 20 '18 at 14:05
  • @Raffzahn GET on AppleSoft did indeed wait, but only for one keypress. The Apple II did not have a function like INKEY$ (return character if pressed or empty if none), but could be simulated in AppleSoft by testing PEEK(-16384)>127. If true, then a key was pending. Retrieve it either by a GET command or use that PEEK value and then POKE a value into -16353 to clear the keyboard strobe. Obviously the GET would be a touch easier :-) – bjb Feb 20 '18 at 17:56
  • 1
    @Raffzahn I was referring to your comment to Peterl where you state "Applesoft did use GET and did not wait", not the general answer. Unless one of us misunderstood the other? ;-) – bjb Feb 20 '18 at 18:53
  • 1
    @Raffzahn Viel besser als mein geschriebenes Deutsch :-D – harlandski Feb 21 '18 at 12:18
23

I suspect your confusion is because INKEY$ returns the current state of the keyboard, not a buffered stream of up / down key presses (On serial terminal based systems such as CP/M you'd get the character code which might have buffer depending on the hardware).

Basically the code could be written:

10 Wait for all keys to be up.

20 Wait for a key to be pressed

30 Print pressed key

40 back to start again.

  • 4
    A buffered up/down stream is something very PC specific. Before the PC, there where only character buffers. – Raffzahn Feb 20 '18 at 14:49
  • 1
    Your very right, buffered up down seems to be a PC invention (and you just sent me off on a wild goose chase when I remembered the QL 8049) – PeterI Feb 20 '18 at 23:27
  • @PeterI not an invention but necessity due to way PC (screwy) keyboard is interconnected to PC ... PC does not have access to the matrix it only receives key up/down messages ... and only for keys stored in keyboard buffer which was usually i8048 with 128Byte RAM for everything (including registers and stack) – Spektre Feb 21 '18 at 13:10
  • Same is true for the QL (well mostly you can scan rows individually) and lots of other machines Apple // and Atari 400/800 spring to mind. All of these tend to provide a single keystroke rather than access to the keyboard matrix scan. QL uses an 8049, Apple a keyboard chip (signetics?) and the Atari is the Pokey – PeterI Feb 21 '18 at 18:13
10

Lines 10 and 20 do cover all possibilities, but they don't cover them at the same time.

Line 10 is saying, "do not proceed to the next line as long as there's still a key being pressed from before."

Line 20 is saying, "now that no key is being pressed, stay here until one is being pressed."

You will reach line 30 only when you first press a key, which it will print, then it will go back to line 10 to wait for you to release that key, then on to line 20 to wait for you to press a new one.

0

Raffzahn's answer is excellent and deserves to be the accepted answer but I would like to add another viewpoint.

It only appears to be impossible for the program to get to line 30 because of an implicit expectation that INKEY$ will return the same value on successive calls. However, if this was the case then one of line 10 or 20 by itself would block. Understanding why that is not the case requires a realisation that successive calls to INKEY$ might return different values. Once this is understood, the apparent impossibility should evaporate.

In some languages, a declaration of a function allows the specification of "deterministic" or "non-deterministic". When I first saw this, my reaction was: "Of course it is deterministic, it's a computer program". However, both cases are possible. A sensibly implemented sqrt or log function will be deterministic. You would not expect multiple calls to sqrt(2) to return different values. However, you should not be surprised if time() or rand() return different values. INKEY$ is of the latter type: non-deterministic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.