News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_redbox

Keyboard Buffer and Menus

Started by redbox, 11:52, 10 May 13

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

redbox

I am writing a simple menu system with up/down/fire select controls.

It's running at 50hz so if I just scan the keyboard and change the menu selection it's far to quick and jumpy.

A simple solution appeared to be to insert a simple counter to only scan the keyboard/change selection every 2 or 3 frames.  But this is still a bit jumpy or laggy, no matter how much I try to refine it.

I thought about flushing the keyboard buffer, but if the user holds down the key it's still way to fast at 50hz... so, is there a better way of doing it?

arnoldemu

#1
Quote from: redbox on 11:52, 10 May 13
I am writing a simple menu system with up/down/fire select controls.

It's running at 50hz so if I just scan the keyboard and change the menu selection it's far to quick and jumpy.

A simple solution appeared to be to insert a simple counter to only scan the keyboard/change selection every 2 or 3 frames.  But this is still a bit jumpy or laggy, no matter how much I try to refine it.

I thought about flushing the keyboard buffer, but if the user holds down the key it's still way to fast at 50hz... so, is there a better way of doing it?
keep track of current and previous state of keyboard.
20 bytes total.

Then trigger movement/select on either key up (was pressed before, but not now), or key down (not pressed before, but pressed now).

That works great for any frame rate but there is no repeat.

So for repeat, check for key down. First time trigger a movement, then reset timer. when timer has done, do another movement, and reset timer.

repeat until key is up.
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

redbox

Thanks for the algorithms, will give them a try!

redbox

Quote from: arnoldemu on 12:44, 10 May 13
keep track of current and previous state of keyboard.
Then trigger movement/select on either key up (was pressed before, but not now), or key down (not pressed before, but pressed now).

I implemented this so thought I'd share the code.  I am using a mini-buffer of 1 byte as I only want to use 3 keys (Cursor Up/Down and Space) for the menu system.

Buffer byte is arranged as %01010100. Bits 7/5/3 are previous state of Cursor Up/Down/Space.  Bits 6/4/2 are current state of Cursor Up/Down/Space.

So basically I am looking for a %01 on 7/6, 5/4 or 3/2 to trigger an action.  If you hold down the key, the buffer will show %11 (so no action), and eventually for one frame %10 and again, no action is taken here.

However, from what you said above, I should also do the action on %10 ("key up - was pressed before, but not now") - but wouldn't this result in a double action...?

Anyway, the following code appears to be working for me:


menu_check_keys:    ld    a,(menu_keyb_buffer)        ; get state of keyboard buffer
            rla                    ; rotate left so current state becomes previous
            ld    (menu_keyb_buffer),a        ; store it back

            ld     a,&40                ; bit line to scan on keyboard
            call     scan_keyb            ; scan keyboard
            bit    0,a
            jp    z,menu_check_keys_up        ; up pressed, check buffer
            bit    2,a   
            jp    z,menu_check_keys_down        ; etc
            ld    a,&45
            call    scan_keyb
            bit    7,a
            jp    z,menu_check_keys_space

            ld    a,(menu_keyb_buffer)        ; nothing pressed, so set state in buffer
            res    6,a                ; reset bit 6 (cursor up current)   
            res    4,a                ; reset bit 4 (cursor down current)
            res    2,a                ; reset bit 2 (space current)
            ld    (menu_keyb_buffer),a        ; and store it back

            ret

menu_check_keys_up:    ld    a,(menu_keyb_buffer)       
            set    6,a                ; set bit 6 (cursor up current)
            ld    (menu_keyb_buffer),a
            cp    %01000000            ; is this first time it's been pressed?
            jp    z,menu_cursor_up        ; yes, then do action
            ret                    ; else RETurn and do nothing

menu_check_keys_down:    ld    a,(menu_keyb_buffer)
            set    4,a                ; set bit 4 (cursor down current)
            ld    (menu_keyb_buffer),a
            cp    %00010000
            jp    z,menu_cursor_down
            ret

menu_check_keys_space:    ld    a,(menu_keyb_buffer)
            set    2,a                ; set bit 2 (space current)
            ld    (menu_keyb_buffer),a
            cp    %00000100
            jp    z,menu_space
            ret


arnoldemu

#4
Quote from: redbox on 11:38, 14 May 13
However, from what you said above, I should also do the action on %10 ("key up - was pressed before, but not now") - but wouldn't this result in a double action...?
Do the action on one or the other. I normally do it on key up.

I normally do something like this this:


ld hl,current_buffer
ld de,old_buffer
ld bc,10
ldir
;; update current
call scan_keyboard

ld e,1 ;; key mask
ld ix,old_buffer
ld a,(ix+0)  ;; new state (key line 0)
ld c,a
xor (ix+10) ;; xor with old state of key line 0 -> 1 here means key changed
and e ;; mask to isolate 1 key
;; 0 = no change, not zero = change
ret z
;; get new state
ld a,c
and e ;; the state, 0 means released, 1 means pressed.
ret




current_buffer:
defs 10
old_buffer:
defs 10
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

redbox

Ah I see, one OR the other - I misread what you said.

Thanks for the example code, we're doing the same thing but the other way round ;)

db6128

Philosophical or, not logical OR. For that, you would need XOR. ;)
Quote from: Devilmarkus on 13:04, 27 February 12
Quote from: ukmarkh on 11:38, 27 February 12[The owner of one of the few existing cartridges of Chase HQ 2] mentioned to me that unless someone could find a way to guarantee the code wouldn't be duplicated to anyone else, he wouldn't be interested.
Did he also say things like "My treasureeeeee" and is he a little grey guy?

redbox

Quote from: db6128 on 12:01, 20 May 13
Philosophical or, not logical OR. For that, you would need XOR. ;)

Usually in my case it's XOR A.

Set Z flag and try again.   ;)

db6128

Actually, I was thinking of XORing pastState against presentState and looking for a 1, but that would induce the response on both key-down and key-up . . . probably not what you want here, although maybe a good idea to hack into games like Supertest. :D

But yeah, good old XOR A! I usually use that for clearing A and/or setting Z, OR A for checking whether A is 0, and AND A for clearing C. Some of those side-effects overlap, but using certain instructions for certain effects helps me to remember what I was intending at the time of writing. I like the ability of XOR A to conjure up a zero quickly. I remember using something like XOR A:LD H,A:LD L,A:LD (HL),A:LD E,A:LDIR somewhere!

I was reading a manual for a different CPU the other day, and it had built-in commands for setting each of its registers to 0 instantly. Posers! ;)
Quote from: Devilmarkus on 13:04, 27 February 12
Quote from: ukmarkh on 11:38, 27 February 12[The owner of one of the few existing cartridges of Chase HQ 2] mentioned to me that unless someone could find a way to guarantee the code wouldn't be duplicated to anyone else, he wouldn't be interested.
Did he also say things like "My treasureeeeee" and is he a little grey guy?

ralferoo

My favourite instruction is "SBC A" which replicates the carry bit to all bits of A, this can be used for all sorts of nice tricks, e.g. "SBC A:AND option2-option1:ADD option1" to set A to option1 for no carry, and option2 for carry.

redbox

@db6128 - yes but always remember OR A destroys A (that's caught me out more than once!)

@ralferoo - that's really cool, will use that as my screen address routine sets carry if a pre shift is required and my routine is less than eloquent at present...!!!

ralferoo

Quote from: redbox on 17:12, 20 May 13
@db6128 - yes but always remember OR A destroys A (that's caught me out more than once!)
No it doesn't! XOR A sets A to 0. OR A / AND A both preserve A. Both instructions are effectively equivalent and only useful for modifying flags (and mostly only Z or S are useful, although clearing C is a nice side effect).

redbox

Hmmm you're right.

I remember fixing something with CP 0 instead, must have been a flag issue.

I'm sure seeing one optimisation that does destroy A though, but that obviously wasn't it. Z80 is starting to fry my brain ;)

db6128

Quote from: ralferoo on 17:03, 20 May 13My favourite instruction is "SBC A" which replicates the carry bit to all bits of A, this can be used for all sorts of nice tricks, e.g. "SBC A:AND option2-option1:ADD option1" to set A to option1 for no carry, and option2 for carry.
Now this looks very clever! :D Thanks for the tip! How did you work that out?

It reminds me of some other, albeit less impressive, snippets that I picked up in the past, although I think they was based upon A interacting with other registers rather than itself. The latter makes for much more interesting tricks! On first glance, the self-referential operations can seem pointless, so it is always great to learn of uses for them. Especially if you love every chance for optimisation like I do!

Quote from: redbox on 17:12, 20 May 13@db6128 - yes but always remember OR A destroys A (that's caught me out more than once!)
I was going to respond to this but lost my connection and gave up, so ralferoo beat me to it. ;)

Quote from: ralferoo on 17:31, 20 May 13No it doesn't! XOR A sets A to 0. OR A / AND A both preserve A. Both instructions are effectively equivalent and only useful for modifying flags (and mostly only Z or S are useful, although clearing C is a nice side effect).
Yes, especially since we got a command to set C but not clear it. . .with the misleadingly named CCF actually meaning complement carry flag. :|

Anyway, they are both identical as you said, and I tend to use this otherwise useless property so that it is easier to remember what I was doing in ASM. . .although I confess that I forget which I used in which circumstances and might not always have been consistent!

Anyway, I always loved it when I got to a stretch of repeated code where I could only save 4 NOPs if C was already clear, and lo and behold, there was coincidentally an OR or an AND just a few bytes earlier.  :laugh:
Quote from: Devilmarkus on 13:04, 27 February 12
Quote from: ukmarkh on 11:38, 27 February 12[The owner of one of the few existing cartridges of Chase HQ 2] mentioned to me that unless someone could find a way to guarantee the code wouldn't be duplicated to anyone else, he wouldn't be interested.
Did he also say things like "My treasureeeeee" and is he a little grey guy?

ralferoo

Quote from: db6128 on 11:12, 21 May 13
Now this looks very clever! :D Thanks for the tip! How did you work that out?
The "SBC A: AND x: OR/ADD/XOR y" trick I've started doing relatively recently, but it was a fairly obvious extension once you know about "SBC A".

I can't remember when I first started using "SBC A" on Z80. I might have known about it when I was younger, but I suspect it's something I picked up when programming for the PS3 - there, every instruction is 1 cycle long but a branch will cause a 12 cycle penalty. So, you have instructions like "set-if-condition" that sets the register to 0 or all 1s depending if the condition is met and a "select" instruction where "selb rt,ra,rb,rm" is "rt=(ra and rm) or (rb and not rm)". So, you calculate both branches and just merge the results at the end using "Scc:SELB".

On the Z80 where there's no instruction pipline, the branches make a lot less of an impact, so it's usually better to branch except if you're doing exactly cycle counted code when it's more useful to have consistency.

The 68000 has a similar set-if-condition instruction, but less opportunities to use it, if I recall. I definitely remember doing things like explicit SEQ:AND:NOT:AND:OR on 68000, although I don't ever recall optimising this to SEQ:AND:XOR.

It's interesting that the same trick can be done on the 8086 too: "SBB AL,AL" is the equivalent, but there's also an "undocumented" "SALC" instruction that does the same but without affecting the flags, which suggests that the subtract-with-carry trick wasn't widely known at Intel when the 8086 was designed.

MaV

#15
ralferoo = Programming God :D

Anyone who programs the Z80, 68000 and the Cell chip and know a heavy dose of x86 is one in my book!

Need I mention ralf does FPGAs as well?
Black Mesa Transit Announcement System:
"Work safe, work smart. Your future depends on it."

redbox

Quote from: ralferoo on 17:31, 20 May 13
No it doesn't! XOR A sets A to 0. OR A / AND A both preserve A. Both instructions are effectively equivalent and only useful for modifying flags (and mostly only Z or S are useful, although clearing C is a nice side effect).

Remembered the one I was thinking of - instead of CP 1 use DEC A, but this does modify A.

:)

Gryzor

Quote from: MaV
Need I mention ralf does FPGAs as well?

Maybe we should collectively ask him to do a CPC for the MiST board? :D

Powered by SMFPacks Menu Editor Mod