what would this be equivilent to in assembler?

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
777
Manic Miner
Posts: 512
Joined: Fri Jun 26, 2020 11:23 am
Location: sw uk

what would this be equivilent to in assembler?

Post by 777 »

if inkeys$="m" then x=x+1

i understand the keyboard matrix is read differently in assembler/m code
i started programming the spectrum when i was 8 :-

1 plot rnd*255,rnd*175
2 goto 1
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: what would this be equivilent to in assembler?

Post by Seven.FFF »

There's lots of ways depending on whether X is in a register or memory location, whether it can go larger than 255, whether you want it to wrap or stop at a certain max value, whether you want to do something with X after increasing it, etc. But something like this is a good start:

Code: Select all

Key.SpSsMNB             equ $7ffe                       ; Keyboard half row for Space, Sybmbol Shift, M, N, and B
Xcoordinate:            db 0                            ; Location to store X (0..255)

; ...

IncreaseX:              ld bc, Key.SpSsMNB              ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, .notM                    ; A will only be zero if X key is pressed
                        inc a                           ; X = X + 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
.notM:                                                  ; X is now in A register so we can do something with it next
It's worth pointing out that here, the and sets the flags, including the zero flag. Then the ld loads a new value into a, which doesn't change the flags. Then the jr nz is doing the conditional jump based on the flags from the old value of a.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

I separate my keyboard reading from the game logic since I may not always want to move right when right is pressed, there may be a wall in the way etc.

I set BC as follows (controls here are WASD but comment in the function shows which port to read and what bit gets cleared when pressed) so it's not hard to change the controls. Note I only read the port once to handle all of ASD since they are on the same half-row.

Code: Select all

; at exit, B contains 1 if we pressed down and -1 if we pressed up
; at exit, C contains 1 if we pressed right and -1 if we pressed left
read_keyboard:
	; Read these ports to scan keyboard
	; bit N (0-based) is clear if the key is being pressed
	; #FE - SHIFT, Z, X, C, & V
	; #FD - A, S, D, F, & G
	; #FB - Q, W, E, R, & T
	; #F7 - 1, 2, 3, 4, & 5
	; #EF - 0, 9, 8, 7, & 6
	; #DF - P, O, I, U, & Y
	; #BF - ENTER, L, K, J, & H
	; #7F - SPACE, FULL-STOP, M, N, & B
	; ld a, port
	; in a, (#FE)
	; to do the read of the port

	ld bc, 0
	ld d, b
	ld e, b

	; are we pressing W?
	ld a, #FB
	in a, (#FE)
	bit 1, a
	jr nz, .notpressingW
	dec b
.notpressingW
	; are we pressing A, S or D?
	ld a, #FD
	in a, (#FE)
	bit 1, a
	jr nz, .notpressingS
	inc b
.notpressingS
	; are we pressing A?
	bit 0, a
	jr nz, .notpressingA
	dec c
.notpressingA
	; are we pressing D?
	bit 2, a
	jr nz, .notpressingD
	inc c
.notpressingD
	ret
Also makes it easier to redefine keys, use a joystick port read instead, etc.

EDIT: That's for arcade style control anyway. I also have a routine which scans all keys pressed into an 8 byte buffer which is easier to use for text input (high score entry, text parsing etc.).

Anyway point is you are better separating your keyboard logic from game logic. Also means you only need to read the keyboard once per frame in one place and not scattered throughout the game loop.

EDIT2: My routine also does the sensible thing if you are pressing left and right at the same time (sets C to 0). The non-sensible thing would be to have 1 key taking priority over another e.g. pressing right and left means right wins (not really a good way of handling that).

EDIT3: I also clear D and E because I also read IJKL which is same as WASD but sets DE instead of BC. But that is not shown.


EDIT4: And the to increment a variable if the "right" key is pressed I just call read_keyboard (once per frame) then process what is returned straight after.

Code: Select all

call read_keyboard
ld a, c
cp 1 ; right is pressed
jr nz,.rightisnotpressed
;increment some variable here
User avatar
777
Manic Miner
Posts: 512
Joined: Fri Jun 26, 2020 11:23 am
Location: sw uk

Re: what would this be equivilent to in assembler?

Post by 777 »

Seven.FFF wrote: Sun Mar 17, 2024 3:20 pm There's lots of ways depending on whether X is in a register or memory location, whether it can go larger than 255, whether you want it to wrap or stop at a certain max value, whether you want to do something with X after increasing it, etc. But something like this is a good start:

Code: Select all

Key.SpSsMNB             equ $7ffe                       ; Keyboard half row for Space, Sybmbol Shift, M, N, and B
Xcoordinate:            db 0                            ; Location to store X (0..255)

; ...

IncreaseX:              ld bc, Key.SpSsMNB              ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, .notM                    ; A will only be zero if X key is pressed
                        inc a                           ; X = X + 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
.notM:                                                  ; X is now in A register so we can do something with it next
It's worth pointing out that here, the and sets the flags, including the zero flag. Then the ld loads a new value into a, which doesn't change the flags. Then the jr nz is doing the conditional jump based on the flags from the old value of a.
cool, got that working. so how do i tell the program not to not let 'a' register go passed say 31?
i started programming the spectrum when i was 8 :-

1 plot rnd*255,rnd*175
2 goto 1
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

If your x position is 31, and you press right, ignore it (easier again if you separate keyboard reading from logic). If you can move more than 1 x value at a time, add some amount to the value stored in the x coordinate, see if it is 32 or over (cp 31 ; clears carry flag if A >= 32), if it is set it to 31.

So you calculate what the next position would be and if it is not valid you either ignore it (do not write the value back), or write back to the x coord variable what it should be.

To wrap around 0-31 you can use AND 31.

So to use that code as an example

Code: Select all

IncreaseX:              ld bc, Key.SpSsMNB              ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, .notM                    ; A will only be zero if X key is pressed
                        inc a                           ; X = X + 1
                        cp 32 ; A < 32?
                        jr nc, .notM ; A >= 32 : do not write back new value of Xcoordinate
                        ld (Xcoordinate), a             ; Save new value of X back for next time
.notM
cp 32 sets the C flag (and the Z/S flags). C flag is the chibi flag (chibi = smaller) so since you want to do something when you are not chibi (greater or equal) compared to 32, so you check the NC flag.

It's not really called the chibi flag of course but that is a good way of remembering it ;)
Last edited by ParadigmShifter on Sun Mar 17, 2024 5:51 pm, edited 3 times in total.
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: what would this be equivilent to in assembler?

Post by Seven.FFF »

777 wrote: Sun Mar 17, 2024 5:30 pm cool, got that working. so how do i tell the program not to not let 'a' register go passed say 31?
If you wanted to wrap X around so it goes 0,1,..,30,31,0,1 you can use a neat trick with and 31, because 32 is a power of two:

Code: Select all

Key.SpSsMNB             equ $7ffe                       ; Half row for Space, Sybmbol Shift, M, N, and B
Xcoordinate:            db 0                            ; Location to store X (0..255)

                        ld bc, Key.SpSsMNB              ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, .notM                    ; A will only be zero if X key is pressed
                        inc a                           ; X = X + 1
                        and 31                          ; Constrain between 0..31 so X wraps round
                        ld (Xcoordinate), a             ; Save new value of X back for next time
.notM:                                                  ; X is now in A register so we can do something with it next
If you wanted to make X stick at max 31 then you can do this:

Code: Select all

Key.SpSsMNB             equ $7ffe                       ; Half row for Space, Sybmbol Shift, M, N, and B
Xcoordinate:            db 0                            ; Location to store X (0..255)

                        ld bc, Key.SpSsMNB              ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, .notM                    ; A will only be zero if X key is pressed
                        cp 31                           ; 31 is the max value X can have                     
                        jr nc, .skipXincrease           ; If X >= 31 then we're in range, so skip increasing X
                        inc a                           ; X = X + 1
.skipXincrease:         ld (Xcoordinate), a             ; Save new value of X back for next time
.notM:                                                  ; X is now in A register so we can do something with it next
You can see the rules for < and >= comparisons summarized handily here:

Image
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Reason for me using 32 and poster above using 31 is because I check the chibi flag after the inc a. Seven.FFF is also writing back the value regardless of whether it changes, that's not really necessary can just skip to the .notM case and not bother writing the value back (since it has not changed).

That code might be a little confusing for beginners though since you have to remember that ld a, (Xcoordinate) does not set any flags so the following jr nz is testing the result of the AND with the keycode mask, it is not checking whether the Xcoordinate is non-zero.

I still think you would be better separating the keyboard read from the logic though.
sn3j
Manic Miner
Posts: 500
Joined: Mon Oct 31, 2022 12:29 am
Location: Germany

Re: what would this be equivilent to in assembler?

Post by sn3j »

Some more options without bit operations:
  • read out LASTK by:
    ld hl LASTK
    ld a (hl)
    ld (hl) 0
    LASTK is at 23560
  • LASTK is also the same as iy-50, so you might use:
    ld a (iy-50)
    ld (iy-50) 0
  • call $2BF yourself to get the key code in A
POKE 23614,10: STOP      1..0 hold, SS/m/n colors, b/spc toggle
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

LASTK has the same problems that INKEY$ has though (you can only press one key at a time and it cares about shift keys as well I think?). You also need to use the standard interrupt routine (probably not a problem for beginners of course) or call the scan routine/default interrupt service routine from your own interrupt service routine.

Here is some simple code which sets 40 bytes (1 for each key) with 1 for "pressed" and 0 for "not pressed". In my proper code I pack that into 8 bytes not 40 though, but this is useful for detecting multiple keys from BASIC. The key pressed/not pressed table is address 65000 - 65039 with that ORG address. If you were doing it all in ASM you can put the table anywhere you like and just call read_keys to fill it (which you would do once per frame).

Code: Select all

        ORG 65000
kbdbuff BLOCK 40

read_keys:
	ld hl, kbdbuff
	ld bc, #fefe

.nexthalfline
	in a, (c)
	cpl
	and #1f
	ld e, a
	ld d, 5

.loop
	xor a ; clear carry
	rr e ; shift lowest bit from half row out into the carry
	adc a ; set A to 1 if the key was pressed, 0 otherwise
	ld (hl), a
	inc hl

	dec d
	jr nz, .loop

	rlc b
	jr c, .nexthalfline
	ret
You can easily turn that into a redefine keys routine or extend it so it prints the keys pressed etc.

If you want to map the entry in the table to the actual key so you can print it, this will be useful

Code: Select all

CR EQU #0D ; enter key code

key_tbl db 0, "zxcvasdfgqwert1234509876poiuy", CR, "lkjh ", 0, "mnb"
Both shift keys map to 0 in that table though. You have to test multiple values if you want to support pressing shift giving different tokens.
Last edited by ParadigmShifter on Sun Mar 17, 2024 6:42 pm, edited 1 time in total.
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: what would this be equivilent to in assembler?

Post by Seven.FFF »

The ROM routines (that set LASTK) are great for quick routines when you're typing words and sentences on one key at a time. In most games you're moving up/down and left/right at the same time, or left/right and jump and the same time, or moving and firing at the same time, or rotating and thrusting and firing at the same time. Users don't want to be pecking those keys one at a time to suit the code reading the keys, so it makes more sense to read simultaneous keyspresses directly with IN $xxFE.

You can press and detect up to three keys simultaneously, the way the matrix keyboard is implemented in hardware. Any more and you start getting ghost keys or phantom keypresses. But simultaneous presses is great for the scenarios above.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Seven.FFF wrote: Sun Mar 17, 2024 6:42 pm The ROM routines (that set LASTK) are great for quick routines when you're typing words and sentences on one key at a time. In most games you're moving up/down and left/right at the same time, or left/right and jump and the same time, or moving and firing at the same time. Users don't want to be pecking those keys one at a time, so it makes more sense to read simultaneous keyspresses directly with IN $xxFE.

You can press and detect up to three keys sumultaneously, the way the matrix keyboard is implemented in hardware. Any more and you start getting ghost keys or phantom keypresses. But simultaneous presses is great for the scenarios above.
I'm definitely able to detect at least 4 keys at once (it also depends on the PC keyboard when emulating though I guess, some keyboards have limits to which keys can be pressed simultaneously). I had a robotron style thing working ages ago which used WASD to move and IJKL to fire... you were able to move and shoot in 8 independent directions no problems.

The ROM LASTK routine bails out if more than 3 keys are pressed IIRC which is why it does not work for LASTK. It also only returns 1 key of course (I think it takes into account shifts though). I think it also uses the key repeat rate and stuff? Or maybe that is another SYSVAR, I don't use the ROM for keyboard handling so I'm not very familiar with it.

My routine which I use instead of LASTK only decodes a keypress if you are pressing max 1 key and 1 shift key at the same time, it's a lot more complicated than the other routine I posted above though. I use that for text entry only.
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: what would this be equivilent to in assembler?

Post by Seven.FFF »

ParadigmShifter wrote: Sun Mar 17, 2024 6:47 pm I'm definitely able to detect at least 4 keys at once (it also depends on the PC keyboard when emulating though I guess, some keyboards have limits to which keys can be pressed simultaneously).
Using emulators to derive the hardware characteristics of real spectrums is a bit... counterproductive. It's true that most emulator authors don't bother emulating the Spectrum keyboard accurately, but this does lead to people using them as the primary reference source, to write games that won't work properly on any real 80s models of Spectrum.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Can someone try my routine on a real speccy then and tell us the results? Since my Spectrum broke when I was 17, 35 years ago. I use Spin and occasionally additional testing on Fuse. EDIT: 35 years ago not 3 lol :)

I would like to know if it is possible to detect more than 3 keys on real hardware.

I had a basic listing which printed out the keyboard array (and poked the values for the ASM in to memory at address 65040) but it was a postimage image and it seems to not be working now :/

Basic listings were here

viewtopic.php?t=10794

but I just see postimage not available :(

I see 1 image (the poke the routine from BASIC) but not the others, I guess postimage is just having a busy server brainfart or something.
User avatar
Seven.FFF
Manic Miner
Posts: 744
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: what would this be equivilent to in assembler?

Post by Seven.FFF »

You can detect multiple keys, but you can’t detect that other keys weren’t pressed, because of the way this kind of matrix shorts out a row to a column. And the more extra keys are pressed, the more phantom keypresses register.

With carefully crafted fixed defined keys you can get away with a certain amount, but that’s no good if you allow all your keys to be user defined. Sometimes it’s good enough for your needs though.

I always recommend having real hardware to test projects on. Emulation, even 25+ years into the era of accurate emulators. is a little bit of a circle jerk where people will often make their emulators work the same as other emulators and use that as the proof of accuracy.

You can read more about phantom keys in the Chris Smith book, to save buying a Spectrum just to test this.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

I found this which suggests ASZ pressed produces CAPS SHIFT but I haven't got time to think about it all yet ;) Also looks a bit "hardware"-y for me as well lol.

https://retrocomputing.stackexchange.co ... y-ghosting

That suggests to me pressing WAS (= Q) at the same time or WSD (= E) at the same time may go wrong, but that involves pressing up and down at the same time I guess.

I do allow users to redefine the controls so that would enable them to pick something else if there are problems anyway, so I think that is fine and not really a (serious) problem?

SJOE has 8 keys and W is flip not move up though... you aren't likely to be flipping and dropping pieces at the same time. IJKL by default is rotate/shuffle but you can't shuffle clockwise and anticlockwise at the same time so you not likely to press I and K at the same time I guess.

There's also a pause key for 9 keys total.

EDIT: I'm not going to buy a Spectrum no, but I would appreciate people testing SJOE on real hardware of course, which I can't do. Especially the much improved new version I have planned (still in progress developing the MULLET physics engine).
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Ok I think I will add a key test to my games then if there is enough space left in the frontend (which flashes keys pressed) so you would be able to test whether combinations of redefinable keys were suitable in the frontend before or after redefining keys.

I think WASD was used for FPS games on PC to reduce the amount of ghost keypresses anyway so having those as default on the speccy seems to make sense (you don't press up and down at same time usually).

I will see if Spin does ghost the keys in a bit as well I think. It doesn't in BASIC cos it only supports 1 keypress at a time for input so pressing WAS at the same time does nothing... can modify my basic test code of my 40 keypress array routine though to check.

EDIT: That WoS thread has good info as well, cheers for posting that. So any 3 keys corner of a rectangle, so that would be WSIK keys affected with my default keyboard layout problems can occur if you press WS same time as any other key, or IK same time as any other key I think... probably not a serious issue. Of course you can redefine the keys to be able to cause more issues if you want...
_dw
Dizzy
Posts: 90
Joined: Thu Dec 07, 2023 1:52 am

Re: what would this be equivilent to in assembler?

Post by _dw »

777 wrote: Sun Mar 17, 2024 2:22 pm if inkeys$="m" then x=x+1

i understand the keyboard matrix is read differently in assembler/m code
If I wrote it in M4 Forth it would look like
Spoiler

Code: Select all

dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'PUSH(__TESTKEY_M) TESTKEY IF _1ADD THEN'
    ld    A, 0x7F       ; 2:7       0x7F04 testkey if   ( -- )  if press "M"
    in    A,[0xFE]      ; 2:11      0x7F04 testkey if
    and  0x04           ; 2:7       0x7F04 testkey if
    jp   nz, else101    ; 3:10      0x7F04 testkey if
    inc  HL             ; 1:6       1+
else101  EQU $          ;           then  = endif
endif101:               ;           then
; seconds: 0           ;[10:41]
or when x is in memory
Spoiler

Code: Select all

dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'VARIABLE(x) PUSH(__TESTKEY_M) TESTKEY IF PUSH(1) PUSH(x) ADDSTORE THEN'
    ld    A, 0x7F       ; 2:7       0x7F04 testkey if   ( -- )  if press "M"
    in    A,[0xFE]      ; 2:11      0x7F04 testkey if
    and  0x04           ; 2:7       0x7F04 testkey if
    jp   nz, else101    ; 3:10      0x7F04 testkey if
                        ;[9:46]     1 x +!   ( -- )  [x] += 1
    ld   BC,[x]         ; 4:20      1 x +!
    inc  BC             ; 1:6       1 x +!
    ld  [x], BC         ; 4:20      1 x +!
else101  EQU $          ;           then  = endif
endif101:               ;           then

VARIABLE_SECTION:

x:                      ;           variable x
    dw 0                ;           variable x

;# ============================================================================
  if ($<0x0100)
    .error Overflow 64k! over 0..255 bytes
  endif
  if ($<0x0200)
    .error Overflow 64k! over 256..511 bytes
  endif
  if ($<0x0400)
    .error Overflow 64k! over 512..1023 bytes
  endif
  if ($<0x0800)
    .error Overflow 64k! over 1024..2047 bytes
  endif
  if ($<0x1000)
    .error Overflow 64k! over 2048..4095 bytes
  endif
  if ($<0x2000)
    .error Overflow 64k! over 4096..8191 bytes
  endif
  if ($<0x3000)
    .error Overflow 64k! over 8192..12287 bytes
  endif
  if ($<0x4000)
    .error Overflow 64k! over 12288..16383 bytes
  endif
  if ($>0xFF00)
    .warning Data ends at 0xFF00+ address!
  endif
; seconds: 1           ;[18:81]
with the fact that the M key (lower case "m") is tested immediately.

If I wanted to use reading from the buffer:
Spoiler

Code: Select all

dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'KEY PUSH(m) CEQ IF _1ADD THEN'
    call READKEY        ; 3:17      key
                        ;[8:35]     m c= if   ( char1 -- )  bool: lo(char1) = lo(m)
    ld    A, L          ; 1:4       m c= if
    xor  low m          ; 2:7       m c= if   L ^ lo(m)
    ex   DE, HL         ; 1:4       m c= if
    pop  DE             ; 1:10      m c= if
    jp   nz, else101    ; 3:10      m c= if   variant: variable
    inc  HL             ; 1:6       1+
else101  EQU $          ;           then  = endif
endif101:               ;           then
;#==============================================================================
;# Read key from keyboard
;# In:
;# Out: push stack, TOP = HL = key
READKEY:
    ex   DE, HL         ; 1:4       readkey   ( ret . old _DE old_HL -- old_DE ret . old_HL key )
    ex  [SP],HL         ; 1:19      readkey
    push HL             ; 1:11      readkey

    ld    A,[0x5C08]    ; 3:13      readkey   read new value of LAST K
    or    A             ; 1:4       readkey   is it still zero?
    jr    z, $-4        ; 2:7/12    readkey
    ld    L, A          ; 1:4       readkey
    ld    H, 0x00       ; 2:7       readkey
;#   ...fall down to clearbuff{}dnl

;#==============================================================================
;# Clear key buffer
;# In:
;# Out: {(LAST_K)} = 0
CLEARBUFF:
    push AF             ; 1:11      clearbuff
    xor   A             ; 1:4       clearbuff   ZX Spectrum LAST K system variable
    ld  [0x5C08],A      ; 3:13      clearbuff
    pop  AF             ; 1:10      clearbuff
    ret                 ; 1:10      clearbuff
; seconds: 1           ;[31:175]
or when x is in memory
Spoiler

Code: Select all

dworkin@dw-A15:~/Programovani/ZX/Forth/M4$ ../check_word.sh 'VARIABLE(x) KEY PUSH(m) CEQ IF PUSH(1) PUSH(x) ADDSTORE THEN'
    call READKEY        ; 3:17      key
                        ;[8:35]     m c= if   ( char1 -- )  bool: lo(char1) = lo(m)
    ld    A, L          ; 1:4       m c= if
    xor  low m          ; 2:7       m c= if   L ^ lo(m)
    ex   DE, HL         ; 1:4       m c= if
    pop  DE             ; 1:10      m c= if
    jp   nz, else101    ; 3:10      m c= if   variant: variable
                        ;[9:46]     1 x +!   ( -- )  [x] += 1
    ld   BC,[x]         ; 4:20      1 x +!
    inc  BC             ; 1:6       1 x +!
    ld  [x], BC         ; 4:20      1 x +!
else101  EQU $          ;           then  = endif
endif101:               ;           then
;#==============================================================================
;# Read key from keyboard
;# In:
;# Out: push stack, TOP = HL = key
READKEY:
    ex   DE, HL         ; 1:4       readkey   ( ret . old _DE old_HL -- old_DE ret . old_HL key )
    ex  [SP],HL         ; 1:19      readkey
    push HL             ; 1:11      readkey

    ld    A,[0x5C08]    ; 3:13      readkey   read new value of LAST K
    or    A             ; 1:4       readkey   is it still zero?
    jr    z, $-4        ; 2:7/12    readkey
    ld    L, A          ; 1:4       readkey
    ld    H, 0x00       ; 2:7       readkey
;#   ...fall down to clearbuff{}dnl

;#==============================================================================
;# Clear key buffer
;# In:
;# Out: {(LAST_K)} = 0
CLEARBUFF:
    push AF             ; 1:11      clearbuff
    xor   A             ; 1:4       clearbuff   ZX Spectrum LAST K system variable
    ld  [0x5C08],A      ; 3:13      clearbuff
    pop  AF             ; 1:10      clearbuff
    ret                 ; 1:10      clearbuff

VARIABLE_SECTION:

x:                      ;           variable x
    dw 0                ;           variable x

;# ============================================================================
  if ($<0x0100)
    .error Overflow 64k! over 0..255 bytes
  endif
  if ($<0x0200)
    .error Overflow 64k! over 256..511 bytes
  endif
  if ($<0x0400)
    .error Overflow 64k! over 512..1023 bytes
  endif
  if ($<0x0800)
    .error Overflow 64k! over 1024..2047 bytes
  endif
  if ($<0x1000)
    .error Overflow 64k! over 2048..4095 bytes
  endif
  if ($<0x2000)
    .error Overflow 64k! over 4096..8191 bytes
  endif
  if ($<0x3000)
    .error Overflow 64k! over 8192..12287 bytes
  endif
  if ($<0x4000)
    .error Overflow 64k! over 12288..16383 bytes
  endif
  if ($>0xFF00)
    .warning Data ends at 0xFF00+ address!
  endif
; seconds: 1           ;[39:215]
this way you can very quickly convert any part of the program from a higher language into assembler. It could also be changed to unsigned, 8 bit, etc.

Another way is to look at the compiler explorer for C and Z88dk. How the parameter must be written "+zx -zorg=32768".
https://godbolt.org/

but in this case it doesn't help much because it shows that it calls the _getchar built-in function
Spoiler

Code: Select all

void main(){
    int x;

    if ( getchar() == 'm') x++;
}

._main
        push    bc
        call    _getchar
        ld      de,109
        and     a
        sbc     hl,de
        jp      nz,i_2      ;
        add     hl,sp
        inc     (hl)
        ld      a,(hl)
        inc     hl
        jr      nz,ASMPC+3
        inc     (hl)
        ld      h,(hl)
        ld      l,a
        dec     hl
.i_2
        pop     bc
        ret
and it can still surprise you, because it increases a 16-bit value in memory in an 8-bit way with the help of a jump. And write the result to the registry as the program output. So you have to watch what you want after it, where you haven't initialized it with a variable, etc.

It would be possible to compile the source with the switch -S to look for files in /snap/z88dk/current/share/z88dk/libsrc/_DEVELOPMENT/stdio/ or use the dissasembler for the binary. But in this particular case it would be very difficult to cut through many layers.

If I use the BorielBasic compiler

Code: Select all

dim x as integer
if inkey$="m" then x=x+1
./zxbc.py -A -S 32768 --explicit -o test_m_debug.asm test_m.bas
This way we get all human-readable code in one file. We can easily find it there
Spoiler

Code: Select all

	    push namespace core

INKEY:
	    PROC
	    LOCAL __EMPTY_INKEY
	    LOCAL KEY_SCAN
	    LOCAL KEY_TEST
	    LOCAL KEY_CODE

	    ld bc, 3	; 1 char length string
	    call __MEM_ALLOC

	    ld a, h
	    or l
	    ret z	; Return if NULL (No memory)

	    push hl ; Saves memory pointer

	    call KEY_SCAN
	    jp nz, __EMPTY_INKEY

	    call KEY_TEST
	    jp nc, __EMPTY_INKEY

	    dec d	; D is expected to be FLAGS so set bit 3 $FF
	    ; 'L' Mode so no keywords.
	    ld e, a	; main key to A
	    ; C is MODE 0 'KLC' from above still.
	    call KEY_CODE ; routine K-DECODE
	    pop hl

	    ld (hl), 1
	    inc hl
	    ld (hl), 0
	    inc hl
	    ld (hl), a
	    dec hl
	    dec hl	; HL Points to string result
	    ret

__EMPTY_INKEY:
	    pop hl
	    xor a
	    ld (hl), a
	    inc hl
	    ld (hl), a
	    dec hl
	    ret

	KEY_SCAN	EQU 028Eh
	KEY_TEST	EQU 031Eh
	KEY_CODE	EQU 0333h

	    ENDP

	    pop namespace
So the code uses ZX ROM basic routines.
028E: THE 'KEYBOARD SCANNING' SUBROUTINE https://skoolkid.github.io/rom/asm/028E.html
031E: THE 'K-TEST' SUBROUTINE https://skoolkid.github.io/rom/asm/031E.html
0333: THE 'KEYBOARD DECODING' SUBROUTINE https://skoolkid.github.io/rom/asm/0333.html

It is relatively easy to obtain asm code from a higher language.
Z80 Forth compiler (ZX Spectrum 48kb): https://codeberg.org/DW0RKiN/M4_FORTH
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

_dw wrote: Sun Mar 17, 2024 11:49 pm It is relatively easy to obtain asm code from a higher language.
As long as it is compiled... interpreted BASIC is a lot harder and more pointless to reverse engineer like that.
_dw
Dizzy
Posts: 90
Joined: Thu Dec 07, 2023 1:52 am

Re: what would this be equivilent to in assembler?

Post by _dw »

ParadigmShifter wrote: Mon Mar 18, 2024 12:01 am As long as it is compiled... interpreted BASIC is a lot harder and more pointless to reverse engineer like that.
I apologize if I missed the point or if it got lost in the automatic translation.

My point I wanted to make was:

The code that comes from BorielBasic is relatively easy to read and understand for humans. All the code is in one file.

"Relatively" means that it is not as good as the code that comes from M4 FORTH. You can easily translate very small pieces of code there, and you don't have to deal with things around.

"Relatively" means that it is much better than what comes from C. To readability and finding where the code is hidden. GETCHAR is only an apparently simple function, but thanks to the fact that it depends on the platform, and Z88DK is multi-platform and has alternative libraries for each, so one can quickly get lost in it.

From what I understand, you write that if the basic was interpreted, it is more complicated. This doesn't make much sense to me considering what the BorielBasic compiler does.

I think I still answered within the scope of the original assignment.

Yes, the easiest way is to get an answer from someone who knows how to program in assembler. I showed other ways.
Z80 Forth compiler (ZX Spectrum 48kb): https://codeberg.org/DW0RKiN/M4_FORTH
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

I just meant it's harder to work out what the BASIC interpreter is doing because it has to do all the interpreting stuff inbetween the actual code which does what you asked it to do in the first place. Compiled code obviously just does the machine code translation of what you ask for and doesn't have to parse tokens, numbers, etc. Obviously (in ZX Basic when interpreted) it all boils down to ROM calls in the end but working out what those are is not easy unless you already know machine code and can read the ROM disassembly of what the interpreter does when it actually executes your basic instructions.
_dw
Dizzy
Posts: 90
Joined: Thu Dec 07, 2023 1:52 am

Re: what would this be equivilent to in assembler?

Post by _dw »

ParadigmShifter wrote: Mon Mar 18, 2024 12:42 am I just meant it's harder to work out what the BASIC interpreter is doing because it has to do all the interpreting stuff inbetween the actual code which does what you asked it to do in the first place. Compiled code obviously just does the machine code translation of what you ask for and doesn't have to parse tokens, numbers, etc. Obviously (in ZX Basic when interpreted) it all boils down to ROM calls in the end but working out what those are is not easy unless you already know machine code and can read the ROM disassembly of what the interpreter does when it actually executes your basic instructions.
Ah, well, as I understood the question and out of all the answers, no one asked about how the Basic interpreter works. On the one hand, it can be more complex than the C compiler, on the other hand, it is easier, because you don't have to solve any optimizations, but it divides the code into small parts. Both want to know some other things. If I could choose, I would rather write a Basic interpreter than a C compiler, because it would be easier for me. Increased requirements are placed on the interpreter, regarding the fact that it will not be a cross compiler and it must be able to do as many things as possible with the fewest requirements in order to get into the ROM. The C translation is expected to take the code from a human, wonder what it is doing and write new, efficient code suitable for the processor. He is expected to throw out unnecessary parts, etc. All difficult or, in principle, unsolvable tasks under all circumstances. On the one hand, we enter "GO TO THE STORE" on the other hand, we expect "AND CLIMB THIS FENCE (BECAUSE IT'S A FASTER WAY)" but we have no word for "CLIMB" nor can we see the "FENCE" even though we know it's there. So then we try to give the command "LOCK THE DOOR AND THEN GO TO THE SHOP" and hope that instead of unlocking it, he will climb over the fence. The compiler won't be too happy about it either.

But I'm way off topic.
Z80 Forth compiler (ZX Spectrum 48kb): https://codeberg.org/DW0RKiN/M4_FORTH
User avatar
777
Manic Miner
Posts: 512
Joined: Fri Jun 26, 2020 11:23 am
Location: sw uk

Re: what would this be equivilent to in assembler?

Post by 777 »

well u know what im going to ask u now dont u?

how do i get it to move the character the other way?

heres what i have so far but it doesnt work. i tried to adapt it.

im using the x key

i did try swapping the key and key2 labels around and reads the key fine...

Code: Select all

;
; ROM routine addresses
;
ROM_CLS                 EQU  0x0DAF             ; Clears the screen and opens channel 2
ROM_OPEN_CHANNEL        EQU  0x1601             ; Open a channel
ROM_PRINT               EQU  0x203C             ; Print a string 
;
; PRINT control codes - work with ROM_PRINT and RST 0x10
;
INK                     EQU 0x10
PAPER                   EQU 0x11
FLASH                   EQU 0x12
BRIGHT                  EQU 0x13
INVERSE                 EQU 0x14
OVER                    EQU 0x15
AT1                     EQU 0x16
TAB                     EQU 0x17
CR                      EQU 0x0C

;
; Or this...
;
org 30000
                     
Key	                equ $7ffe                       ; Keyboard half row for Space, Sybmbol Shift, M, N, and B
Key2		        equ $fefe                       ; Keyboard half row
Xcoordinate             db 0                            ; Location to store X (0..255)
loop


IncreaseX               ld bc, Key	                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except X
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        inc a 
			cp 31 ; A < 32?
                        jr nc, notM ; A >= 32 : do not write back new value of Xcoordinate                          ; X = X + 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time



DecreaseX          	ld bc, Key2		                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row 
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        dec a                           ; X = X - 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
notM                                                    ; X is now in A register so we can do something with it next

ld (30050),a

; You can either do this...
;
                        CALL ROM_CLS            ; Clear screen and open Channel 2 (Screen

; Print a single character at screen position (15, 1)
;
                        LD A, AT1                ; AT control character
                        RST 0x10
                        LD A, 10                 ; Y
                        RST 0x10
                        LD A, 15                ; X
                        RST 0x10
                        LD A, INK               ; Ink colour
                        RST 0x10
                        LD A, 0                 ; Red
                        RST 0x10
                        LD A, 65                ; Character to print
                        RST 0x10
                     

	                                              
jp loop
i started programming the spectrum when i was 8 :-

1 plot rnd*255,rnd*175
2 goto 1
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Easiest way is to check the Xcoordinate before you decrement it and if it is 0 skip writing it back.

You can use

ld a, (Xcoordinate)
or a ; same as cp 0 but faster and smaller
jr z, .skipmoveleft
dec a
ld (Xcoordinate), a
.skipmoveleft

for that

EDIT: So with your code

Code: Select all

DecreaseX          	ld bc, Key2		                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row 
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        or a
                        jr z, notM
                        dec a                           ; X = X - 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
But you've done the "wrong thing" there in that checking for moving right trumps moving left... consider my first post as an alternative... that makes you not move if you are pressing right and left at the same time.
User avatar
777
Manic Miner
Posts: 512
Joined: Fri Jun 26, 2020 11:23 am
Location: sw uk

Re: what would this be equivilent to in assembler?

Post by 777 »

ParadigmShifter wrote: Mon Mar 18, 2024 2:56 am Easiest way is to check the Xcoordinate before you decrement it and if it is 0 skip writing it back.

You can use

ld a, (Xcoordinate)
or a ; same as cp 0 but faster and smaller
jr z, .skipmoveleft
dec a
ld (Xcoordinate), a
.skipmoveleft

for that

EDIT: So with your code

Code: Select all

DecreaseX          	ld bc, Key2		                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row 
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        or a
                        jr z, notM
                        dec a                           ; X = X - 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
But you've done the "wrong thing" there in that checking for moving right trumps moving left... consider my first post as an alternative... that makes you not move if you are pressing right and left at the same time.
thank you but now its not running at all...

Code: Select all

;
; ROM routine addresses
;
ROM_CLS                 EQU  0x0DAF             ; Clears the screen and opens channel 2
ROM_OPEN_CHANNEL        EQU  0x1601             ; Open a channel
ROM_PRINT               EQU  0x203C             ; Print a string 
;
; PRINT control codes - work with ROM_PRINT and RST 0x10
;
INK                     EQU 0x10
PAPER                   EQU 0x11
FLASH                   EQU 0x12
BRIGHT                  EQU 0x13
INVERSE                 EQU 0x14
OVER                    EQU 0x15
AT1                     EQU 0x16
TAB                     EQU 0x17
CR                      EQU 0x0C

;
; Or this...
;
org 30000
                     
Key	                equ $7ffe                       ; Keyboard half row for Space, Sybmbol Shift, M, N, and B
Key2		        equ $fefe                       ; Keyboard half row
Xcoordinate             db 0                            ; Location to store X (0..255)
loop


IncreaseX               ld bc, Key	                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row with Space, Sybmbol Shift, M, N, and B
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                     ; A will only be zero if X key is pressed
                        inc a 
			cp 31 ; A < 32?
                        jr nc, notM ; A >= 32 : do not write back new value of Xcoordinate                          ; X = X + 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time

DecreaseX          	ld bc, Key2		                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row 
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        or a
                        jr z, notM
                        dec a                           ; X = X - 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time

notM                                                    ; X is now in A register so we can do something with it next

ld (30069),a

; You can either do this...
;
                        CALL ROM_CLS            ; Clear screen and open Channel 2 (Screen

; Print a single character at screen position (15, 1)
;DecreaseX          	ld bc, Key2		                ; Bit 0 = Space, bit 4 = B
                        in a, (c)                       ; Read kb half row 
                        and %00100                      ; Clear all bits except for M
                        ld a, (Xcoordinate)             ; Read existing value of X
                        jr nz, notM                    ; A will only be zero if X key is pressed
                        or a
                        jr z, notM
                        dec a                           ; X = X - 1
                        ld (Xcoordinate), a             ; Save new value of X back for next time
                        LD A, AT1                ; AT control character
                        RST 0x10
                        LD A, 10                 ; Y
                        RST 0x10
                        LD A, 15                ; X
                        RST 0x10
                        LD A, INK               ; Ink colour
                        RST 0x10
                        LD A, 0                 ; Red
                        RST 0x10
                        LD A, 65                ; Character to print
                        RST 0x10
                     

	                                              
jp loop
i started programming the spectrum when i was 8 :-

1 plot rnd*255,rnd*175
2 goto 1
User avatar
ParadigmShifter
Manic Miner
Posts: 670
Joined: Sat Sep 09, 2023 4:55 am

Re: what would this be equivilent to in assembler?

Post by ParadigmShifter »

Not running isn't very helpful ;) Say how it does not work. Did it compile?

If you are using an emulator use the debugger to put breakpoints on the code paths and step through.

My code looks ok to me but it is way past beer o'clock so disclaimers apply.

EDIT: This looks a bit dodgy

ld (30069),a

where did you get 30069 from? Use a label instead of a hardcoded address.
Post Reply