Multikey key release

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
sancho3
Posts: 358
Joined: Sep 30, 2017 3:22

Multikey key release

Post by sancho3 »

[EDIT} So I searched and found a few similar and good alternatives to what I show here. I thought I would link them at the top instead of at the end since they were here before me:
vdecampo
redcrab
Paul Doe
freebasic wiki - screenevent

Multikey is the go to keyword to use when you need key input handling in your program that is a little more intelligent than inkey/getkey.
The wiki has good examples of how to use it.
Multikey polls the keyboard and internally stores a snapshot of the state of the keyboard.
This is done very fast and a single press of a key can lead to a long list of repeated key is down events (for lack of a better term)(They are not events).
In the following code pressing <left> or <right> in an ordinary fashion will print text at least 3 times (and it is even more when the sleep in the loop is shortened).

Code: Select all

#include "fbgfx.bi"
#if __FB_LANG__ = "fb"
Using FB '' Scan code constants are stored in the FB namespace in lang FB
#endif


Do
    If MultiKey(SC_LEFT ) Then Print "left "
    If MultiKey(SC_RIGHT) Then Print "right"
    
    Sleep 15, 1
    
    ' Run loop until user presses Escape
Loop Until MultiKey(SC_ESCAPE)
Sometimes you need just to know if a key has been pressed.
In this tip I built a TMultiKey object that encapsulates the Multikey key input methodology.
It provides a property to determine if the key is released. While a key check with multikey will report -1 (key down)
as long as it is held and for as many times as it is checked in that loop, TMultiKey will report only once (per press) that the key has
been released.

Here is a description of the code and following it, the code itself:

At the start we include fbgfx.bi so that we have access to the keyboard scan codes.
The enum TKeyState defines three states ksUP = 0, ksDOWN = 1, ksRELEASE = 2
ksUP is the idle state of a key ie it has not been pressed since last polled.

The constant RELEASE_TIMEOUT is used to determine how long to retain the released state after the key is pressed.
If the key state is ksRELEASE, the key will revert back to ksUP after approximately 1 second or after its status has been checked.
I use a time check to revert the state of a key back to ksUP. Another option would be to leave the key in the ksRELEASE state
revert it only after it has been checked (maybe for another version).
Without this, the key would retain its ksRELEASED state until the next time it is pressed (not so useful imo).

The type TKey is an object representation of a single key. Its fields and properties are self explanatory. I will note that state_time is used to
store the time at which the key state changed from ksDOWN to ksRELEASE.

The type TMultiKey is the multikey handling encapsulation object.

Keys is an array of TKey used to store the state of each key.

The properties are self explanatory. I will note that the property is_key_release() will set the key state to ksUP.

The sub poll is to be used in the main loop where you would ordinarily check multikey ex. 'if multikey(sc_left) = -1'. Instead use
'[TMultiKeyInsttance].poll()'. This statement sets the state of each keyboard key.
There are 100 predefined keyboard keys on the standard keyboard. To be more accurate, there are just under 100 predefined scan code enums
in fbgfx.

The sub poll() loops through each scan code and sets the state of each key. This could easily be refined to suit your specific needs
and loop through only the keys you are going to use in your program. I did it this way so that the demo could use any key. Its still
fast. Just know that each time poll() is called a 1 to 100 for/next loop will run. This is not designed to be used in a tight loop where
every fraction of a millisecond counts.

The subs _set_keydown_state() and _set_keyup_state() execute the following logic (for each key):
if multikey returns anything other than -1(-1 is key down):
if the key state = ksUP: no action
orelse if the key state = ksDOWN: state_time is set to timer, key state is set to ksRELEASE
orelse if the key state = ksRELEASE:
if this state has existed for longer than RELEASE_TIMEOUT then set the key state to ksUP
if multikey returns -1:
the key state is set to ksDOWN

Here is the code with a simple code test at the end (press <left> or <right> arrow key or <esc> to exit):

Code: Select all

#Include Once "fbgfx.bi" 
Using fb 
Enum TKeyState
	ksUP 
	ksDOWN
	ksRELEASE
End Enum
Const As Double RELEASE_TIMEOUT = 1
'------------------------------------------------------------------------------------------------
Type TKey
	As Long scan_code
	As TKeyState state
	As Double state_time
	Declare Property is_down() 	As Boolean
	Declare Property is_up()		As Boolean
	Declare Property is_release() As Boolean 

End Type
Property TKey.is_down() As Boolean 
	'
	Return cbool(this.state = ksDOWN) 
End Property
Property TKey.is_up() As Boolean 
	'
	return cbool(this.state = ksUP) 
End Property
Property TKey.is_release() As Boolean 
	'
	Return cbool(this.state = ksRELEASE) 
End Property
'------------------------------------------------------------------------------------------------
Type TMultiKey 
	As TKey keys(1 To 100) 
	Declare Sub poll() 
	Declare Property is_down(Byval key_code As long) As Boolean 
	Declare Property is_up(Byval key_code As long) As Boolean 
	Declare Property is_release(Byval key_code As long) As Boolean 
	Private: 
		Declare Sub _set_keydown_state(Byval key_code As Long)
		Declare Sub _set_keyup_state(Byval key_code As long) 
End Type
sub TMultiKey.poll() 
	'
	With This 
		For i as Integer = 1 To 100 
			If Multikey(i) = -1 Then 
				._set_keydown_state(i)
			Else 
				._set_keyup_state(i)
			Endif 
			
		Next
	end With 
End Sub
Property TMultiKey.is_down(Byval key_code As long) As Boolean 
	'
	Return this.keys(key_code).is_down
End Property
Property TMultiKey.is_up(Byval key_code As long) As Boolean 
	'
	Return this.keys(key_code).is_up
End Property
Property TMultiKey.is_release(Byval key_code As long) As Boolean 
	'
	Property = this.keys(key_code).is_release	
	If this.keys(key_code).is_release = TRUE Then 
		this.keys(key_code).state = ksUP
	EndIf  
End Property

Sub TMultiKey._set_keydown_state(Byval key_code As long) 
	'
	With This 
		Dim As TKey Ptr pKey = @.keys(key_code) 
		pKey->state = ksDOWN
	End With
	
End Sub
Sub TMultiKey._set_keyup_state(Byval key_code As long) 
	'
	with This 
		Dim As TKey Ptr pKey = @.keys(key_code) 
		If pKey->state = ksDOWN Then 
			pKey->state_time = timer 
			pKey->state = ksRELEASE 
		Elseif pKey->state = ksRELEASE Then 
			If Timer - pKey->state_time >= RELEASE_TIMEOUT Then 
				pKey->state = ksUP 
			End If
		End If
	End With
End Sub
'-------------------------------
'-------------------------------
Dim As TMultiKey mk
Do 
	mk.poll()
	If mk.is_release(sc_left) = TRUE Then Print "<left>"
	If mk.is_release(sc_right) = TRUE Then Print "<right>"  
	 
Loop While mk.is_down(sc_escape) = FALSE 

It is pretty simple stuff and I would be surprised if it hasn't already been a tip (I didn't look, but I will now).
I will probably monkey with this a bit more and post if I add anything that I like.
Last edited by sancho3 on Nov 12, 2018 6:06, edited 1 time in total.
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

sancho3 wrote:...It is pretty simple stuff and I would be surprised if it hasn't already been a tip (I didn't look, but I will now).
I will probably monkey with this a bit more and post if I add anything that I like.
Yes, there are several implementations floating around here, but yours is nice and always adds =D

Here's one of mine (I don't remember the thread for which I coded it, sorry):

Code: Select all

'' This file contains the constants used with multiKey()
#include once "fbgfx.bi"

'' A simple class to help us deal with the keyboard
type keyboard
	public:
		declare function hold( byval as long ) as boolean
		declare function pressed( byval as long ) as boolean
		declare function released( byval as long ) as boolean
		
	private:
		'' These will store the states of the keys
		m_oldKey( 0 to 127 )	as boolean
		m_newKey( 0 to 127 )	as boolean
end type

function keyboard.hold( byval index as long ) as boolean
	'' Returns whether a key is being held
	return( cbool( multiKey( index ) ) )
end function

function keyboard.pressed( byval index as long ) as boolean
	'' Returns whether a key was pressed
	m_oldKey( index ) = m_newKey( index )
	m_newKey( index ) = cbool( multiKey( index ) )
	
	return( m_oldKey( index ) = false andAlso m_newKey( index ) = true )
end function

function keyboard.released( byval index as long ) as boolean
	'' Returns whether a key was released
	m_oldKey( index ) = m_newKey( index )
	m_newKey( index ) = cbool( multiKey( index ) )
	
	return( m_oldKey( index ) = true andAlso m_newKey( index ) = false )
end function

dim as integer screenWidth = 800, screenHeight = 600
screenRes( screenWidth, screenHeight, 32 )

'' The coordinates of the 'sprite' (it is just a white ball)
dim as integer x = 400, y = 300
'' The radius of the sprite
dim as integer radius = 10
'' The speed of the sprite
dim as integer speed = 1

dim as keyboard key

'' Main loop
do
	'' Get some keypresses
	if( key.released( fb.sc_up ) = true ) then
		'' Move the sprite if it's still on bounds
		y -= speed * 10.0
		
		if( y < 0 ) then
			y = 0
		end if
	end if
		
	if( key.hold( fb.sc_down ) = true ) then
		'' Move the sprite if it's still on bounds
		y += speed
		
		if( y > screenHeight ) then
			y = screenHeight
		end if	
	end if

	if( key.pressed( fb.sc_left ) = true ) then
		'' Move the sprite if it's still on bounds
		x -= speed * 10.0
		
		if( x < 0 ) then
			x = 0
		end if	
	end if

	if( key.hold( fb.sc_right ) = true ) then
		'' Move the sprite if it's still on bounds
		x += speed
		
		if( x > screenWidth ) then
			x = screenWidth
		end if		
	end if

	'' Renders the sprite
	screenLock()
		cls()
		
		circle( x, y ), radius, rgb( 255, 255, 255 ), , , , f
	screenUnlock()
	
	sleep( 1, 1 )
loop until( key.pressed( fb.sc_escape ) = true ) '' Loops until escape key is pressed
The ball moves up when the key is released, left when it is pressed, and right and down when it is held.
sancho3
Posts: 358
Joined: Sep 30, 2017 3:22

Re: Multikey key release

Post by sancho3 »

Hey Paul:
I played with yours a bit and I think I even remember seeing this before.
I see that you have left out key.hold for up and left in the demo.
You should add those in before someone reports a bug in the code (like I was going to, lol)
In any case I found the thread and I will edit my first post and add the link.
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

Oh, but it was done like that on purpose ;)
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Multikey key release

Post by badidea »

This is very useful for the game code am am working on. I had something similar, but this is better. I'll assimilate it and use it as my own :-)
The (performance) advantage of paul doe's version is that it only checks keys of interest?
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

@badidea: by all means, use it if you want. I learned that technique from rel (relsoft). I think he used it in Pyromax Dax. Adding a customizable 'autofire' function should be straightforward ;D

Indeed, it is used pretty much like the raw 'multiKey()' call, it only performs some extra work behind the scenes to provide the additional functionality.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Multikey key release

Post by badidea »

Assimilation complete:

Code: Select all

#include once "fbgfx.bi"

'Class for extended multikey functionality
type multikey_type
	private:
		m_oldKey(127) as boolean
		m_newKey(127) as boolean
	public:
		declare function down(byval as long) as boolean
		declare function pressed(byval as long) as boolean
		declare function released(byval as long) as boolean
end type

'Returns whether a key is being held
function multikey_type.down(byval index as long) as boolean
	return cbool(multiKey(index))
end function

'Returns whether a key was pressed
function multikey_type.pressed(byval index as long) as boolean
	m_oldKey(index) = m_newKey(index)
	m_newKey(index) = cbool(multiKey(index))
	return (m_oldKey(index) = false) andalso (m_newKey(index) = true)
end function

'Returns whether a key was released
function multikey_type.released(byval index as long) as boolean
	m_oldKey(index) = m_newKey(index)
	m_newKey(index) = cbool(multiKey(index))
	return (m_oldKey(index) = true) andalso (m_newKey(index) = false)
end function

'-------------------------------------------------------------------------------

dim as multikey_type mkey

do
	if mkey.released(FB.SC_UP) then _
		print "mkey.released(FB.SC_UP)"
	if mkey.down(FB.SC_DOWN) then _
		print "mkey.down(FB.SC_DOWN)"
	if mkey.pressed(FB.SC_LEFT) then _
		print "mkey.pressed(FB.SC_LEFT)"
	if mkey.down(FB.SC_RIGHT) then _
		print "mkey.down(FB.SC_RIGHT)"
	sleep(1, 1)
loop until mkey.pressed(FB.SC_ESCAPE)
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Multikey key release

Post by dodicat »

I can use my statevariables for this.

Code: Select all



Namespace state

Type variable  'string or double
    As Integer initflag
    As String present_string,last_string,original_string
    As Double present_double,last_double,original_double
    As Integer Sstart,Fstart
    Declare Function ischanged()  As boolean
    Declare Sub set Overload(As Double=0,As String="")
    Declare Sub set Overload(As String="",As Double=0)
    Declare Function  _string() Byref As String
    Declare Function  _float()  Byref As Double
    Declare Constructor(As Double)
    Declare Constructor(As String)
    Declare Constructor(As String,As Double)
    Declare Constructor(As Double,As String)
    Declare Constructor
    As boolean is_changed
    Declare Operator Let(As String)
    Declare Operator Let(As Double)
    Declare Operator Cast() As String
    Declare Operator Cast() As Double
    Declare Sub restoredoubles 'for demo
End Type

Sub variable.set Overload(d As Double,s As String)
    This=d:This=s
End Sub

Sub variable.set Overload(s As String,d As Double)
    This=d:This=s
End Sub

Constructor variable(d As Double)
This=d
End Constructor

Constructor variable(s As String)
This=s
End Constructor

Constructor variable(s As String,d As Double)
This=d:This=s
End Constructor

Constructor variable(d As Double,s As String)
This=d:This=s
End Constructor

Constructor variable
'stupid state
End Constructor

Function variable._string() Byref As String
    Function=present_string
    last_string=present_string
End Function

Function variable._float() Byref As Double
    Function=present_double
    last_double=present_double
End Function

Operator variable.let(s As String)
If initflag=0 Then original_string=s
initflag=2
_string()=s
If Sstart<2 Then Sstart+=1 
is_changed=ischanged
End Operator

Operator variable.let(s As Double)
If initflag=0 Then original_double=s
initflag=1
_float()=s
If Fstart<2 Then Fstart+=1
is_changed=ischanged
End Operator

Function variable.ischanged()  As boolean
    Dim As Long sc = Iif(Sstart>1,last_string<>present_string,0)
    Dim As Long dc = Iif(Fstart>1,last_double<>present_double,0)
    Function = sc Or dc
    If initflag=0 Then Function= 0
End Function

Operator variable.cast() As String
Operator="(" +present_string + " , " + Str(present_double) +")"
End Operator

Operator variable.Cast() As Double
Operator= present_double
End Operator

Sub variable.restoredoubles
    This-=Sgn(this.present_double-this.original_double)
End Sub

End Namespace

'===============================  example ===============
Screen 19,32
Dim As state.variable x,key
Dim As Long n
Do
    
    key=Inkey
    
    For n =1 To 86
        If Multikey(n) Then Exit For
    Next
    
    x=n
    
    If x.is_changed Then
        Cls
        Circle(400,300),200,Rgb(Rnd*255,Rnd*255,Rnd*255),,,,f
        Locate 5,20
        Print key.present_string
    End If
    Sleep 5,1
Loop Until key.present_string=Chr(27)
 
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Multikey key release

Post by badidea »

dodicat wrote:I can use my statevariables for this...
That looks very complicated. I do not understand.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Multikey key release

Post by dodicat »

It detects a change in a value.
A state variable can hold a double a string or both.
As soon as anything changes, BINGO
I copied and pasted the state variable set up, and used it for any key press change.
For this scenario, for myself, there was no sense in re- inventing anything.
I don't expect anybody to actually use it of course, folk like to do their own routines.
But I might use it sometime.
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

badidea wrote:Assimilation complete:
Nice. You can use a keyboard snooper to look for specific scancodes on your keyboard, too:

Code: Select all

/'
  Keyboard snooper
  
  This little utility will show you the scancode returned by
  multiKey(), so you can see which SC corresponds to which
  one. Useful if you don't want to include fbgfx.bi and want
  to define your own constants.
'/
sub pollKeys( _
  keys() as boolean )
  
  for i as integer = 0 to ubound( keys )
    keys( i ) = cbool( multikey( i ) )
  next
end sub

sub showKeys( _
  keys() as boolean )
  
  dim as integer columns = 20
  dim as integer rows = 16
  dim as ulong scanCode, textColor
  
  for y as integer = 0 to rows - 1
    for x as integer = 0 to columns - 1
      scanCode = columns * y + x
      
      if( scanCode <= ubound( keys ) ) then
        textColor = iif( keys( scanCode ) = true, _
          14, 7 )
          
        draw string _
          ( x * 48, y * 48 ), _
          str( scanCode ), textColor
        draw string _
          ( x * 48, y * 48 + 16 ), _
          str( keys( scanCode ) ), textColor
      end if
    next
  next
end sub

'' Define our own key constant (in this case, the ESC key)
const as long MKEY_ESCAPE = 1

dim as integer windowWidth = 970
dim as integer windowHeight = 330

screenRes( windowWidth, windowHeight )
width windowWidth \ 8, windowHeight \ 16
windowTitle( "Keyboard Snooper" )

'' Most keyboards don't have more than 111 keys
dim as boolean keys( 0 to 127 )

do
  pollKeys( keys() )
  
  screenLock()
    cls()
    
    showKeys( keys() )
  screenUnlock()
  
  sleep( 1, 1 )
loop until( multiKey( MKEY_ESCAPE ) ) 
sancho3
Posts: 358
Joined: Sep 30, 2017 3:22

Re: Multikey key release

Post by sancho3 »

@Dodicat:
I didn't see your state variable before. It looks interesting and the demo works great.
I dd a state variable as well. Mine was rudimentary compared to this. Just a variable field (integer) and a boolean set to true when the variable was first set to a value.
I had the notion that I would like to have FreeBASIC have the initial state of a variable be NULL (as in no value, not just zero).
I going to study yours a bit to see what exactly is going on.

@Badidea:
Yes, that loop is the major drawback of my version. I have and updated version wherein I overloaded the poll() sub in two new ways.
One of the new methods takes an array of scan codes as its parameter and loops through only those scan codes.
The other new method takes only a single scan code value and determines its state.
This is similar to the method Paul Doe used.

I'll say that if you are trying to squeeze every drop of speed you can out of your main loop, then none of these methods are your best bet. In fact I suspect that multikey itself will be too slow in that case (not sure of this).
If I remember correctly, even a type that has a constructor (declared or implicit) will run slower than using separate variables.
And in another thread I was reading Dodicat stated that overloaded operators are also slow (relatively speaking).
For the most part, this level of speed critical design is only for the most graphic intensive applications.

Here is that updated version (the new additions are pretty straight forward and don't warrant explanations):

Code: Select all

#Include Once "fbgfx.bi" 
Using fb 
Enum TKeyState
	ksUP 
	ksDOWN
	ksRELEASE
End Enum
Const As Double RELEASE_TIMEOUT = 1
'------------------------------------------------------------------------------------------------
Type TKey
	As Long scan_code
	As TKeyState state
	As Double state_time
	Declare Property is_down() 	As Boolean
	Declare Property is_up()		As Boolean
	Declare Property is_release() As Boolean 
	Declare Property is_double() 	As boolean 

End Type
Property TKey.is_down() As Boolean 
	'
	Return cbool(this.state = ksDOWN) 
End Property
Property TKey.is_up() As Boolean 
	'
	return cbool(this.state = ksUP) 
End Property
Property TKey.is_release() As Boolean 
	'
	Return cbool(this.state = ksRELEASE) 
End Property
'------------------------------------------------------------------------------------------------
Type TMultiKey 
	As TKey keys(1 To 100) 
	Declare Sub poll(ByVal key_code As Long)
	Declare Sub poll(key_codes() As Long)  
	Declare Sub poll()
	 
	Declare Property is_down(Byval key_code As long) As Boolean 
	Declare Property is_up(Byval key_code As long) As Boolean 
	Declare Property is_release(Byval key_code As long) As Boolean 
	Private: 
		Declare Sub _set_keydown_state(Byval key_code As Long)
		Declare Sub _set_keyup_state(Byval key_code As long) 
End Type
Sub TMultikey.poll(ByVal key_code As Long) 
	'
	With This 
		If MultiKey(key_code) = -1 Then
			._set_keydown_state(key_code)
		Else 
			._set_keyup_state(key_code)
		EndIf
	End With
End Sub
sub TMultiKey.poll() 
	'
	With This 
		For i as Integer = 1 To 100 
			If Multikey(i) = -1 Then 
				._set_keydown_state(i)
			Else 
				._set_keyup_state(i)
			Endif 
			
		Next
	end With 
End Sub
Sub TMultiKey.poll(key_codes() As Long) 
	'
	With This 
		For i As Integer = LBound(key_codes) To UBound(key_codes) 
			If Multikey(key_codes(i)) = -1 Then 
				._set_keydown_state(key_codes(i))
			Else 
				._set_keyup_state(key_codes(i))
			Endif 
		Next
	End With 
End Sub
Property TMultiKey.is_down(Byval key_code As long) As Boolean 
	'
	Return this.keys(key_code).is_down
End Property
Property TMultiKey.is_up(Byval key_code As long) As Boolean 
	'
	Return this.keys(key_code).is_up
End Property
Property TMultiKey.is_release(Byval key_code As long) As Boolean 
	'
	Property = this.keys(key_code).is_release	
	If this.keys(key_code).is_release = TRUE Then 
		this.keys(key_code).state = ksUP
	EndIf  
End Property
Sub TMultiKey._set_keydown_state(Byval key_code As long) 
	'
	With This 
		Dim As TKey Ptr pKey = @.keys(key_code)
		If pKey->state_time <> -1 Then 
			pKey->state = ksDOWN
		EndIf
	End With
	
End Sub
Sub TMultiKey._set_keyup_state(Byval key_code As long) 
	'
	with This 
		Dim As TKey Ptr pKey = @.keys(key_code) 
		If pKey->state = ksDOWN Then 
			pKey->state_time = timer 
			pKey->state = ksRELEASE
		Elseif pKey->state = ksRELEASE Then
			' check if the key release has expired 
			If Timer - pKey->state_time >= RELEASE_TIMEOUT Then 
				pKey->state = ksUP 
			End If
		End If
	End With
End Sub
'-------------------------------
'-------------------------------
Dim As TMultiKey mk
? "Press <left> or <right> key or <esc> for next example"
Do
	mk.poll() 
	If mk.is_release(sc_left) = TRUE Then Print "<left>"
	If mk.is_release(sc_right) = TRUE Then Print "<right>"
	 
Loop While mk.is_release(sc_escape) = FALSE 

Cls
?"Press <up> or <down> or <esc> for next example"; mk.is_release(sc_escape)
Dim As Long keys(1 To 3) = {sc_up, sc_down, sc_escape}
Do
	mk.poll(keys())
	If mk.is_release(sc_up) = TRUE Then Print "up"
	If mk.is_release(sc_down) = TRUE Then Print "down"
	 
Loop While mk.is_release(sc_escape) = FALSE

Cls
?"Press <enter> or press <esc> to quit"
Do
	mk.poll(sc_escape) 
	mk.poll(sc_enter)
	If mk.is_release(sc_enter) = TRUE Then Print "enter"

Loop While mk.is_down(sc_escape) = FALSE
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

Here's a revised version of the above class (the other one is old =D). This one allows for finer control of keys, specifying times for both repeating and releasing of the key. Check the code to see how it works. Might be useful for the folks that are competing in Lachie's compo:

Code: Select all

'' This file contains the constants used with multiKey()
#include once "fbgfx.bi"

#ifndef max
  #define max( x, y ) iif( ( x ) > ( y ), ( x ), ( y ) )
#endif

#ifndef min
  #define min( x, y ) iif( ( x ) < ( y ), ( x ), ( y ) )
#endif
 
'' A simple class to help us deal with the keyboard
type Keyboard
  public:
    declare constructor()
    declare destructor()
    
    declare function hold( _
      byval as long, _
      byval as double = 0.0 ) as boolean
    declare function repeated( _
      byval as long, _
      byval as double = 0.0 ) as boolean
    declare function pressed( byval as long ) as boolean
    declare function released( byval as long ) as boolean
    
  private:
    '' These will store the states of the keys
    m_oldKey( 0 to 127 )	as boolean
    m_newKey( 0 to 127 )	as boolean
    m_startTime( 0 to 127 ) as double
end type

constructor Keyboard()
end constructor

destructor Keyboard()
end destructor

/'
  Returns whether or not a key is being held. If an interval is
  specified, after the specified interval in milliseconds has
  passed, it stops being detected.
'/
function Keyboard.hold( _
  byval index as long, _
  byval interval as double = 0.0 ) as boolean
  
  if( interval > 0.0 ) then
    m_oldKey( index ) = m_newKey( index )
    m_newKey( index ) = cbool( multiKey( index ) )
    
    dim as boolean keyPressed = _
      not m_oldKey( index ) andAlso m_newKey( index )
    
    if( keyPressed ) then
      m_startTime( index ) = timer()
    end if
    
    dim as double elapsed = _
      ( timer() - m_startTime( index ) )  * 1000.0
    
    if( elapsed < interval ) then
      return( m_newKey( index ) )
    else
      return( keyPressed )
    end if
  else
    return( cbool( multiKey( index ) ) )
  end if
end function

'' Returns whether a key was pressed
function Keyboard.pressed( byval index as long ) as boolean
  
  m_oldKey( index ) = m_newKey( index )
  m_newKey( index ) = cbool( multiKey( index ) )
  
  return( _
    not m_oldKey( index ) andAlso m_newKey( index ) )
end function

'' Returns whether a key was released
function Keyboard.released( byval index as long ) as boolean
  m_oldKey( index ) = m_newKey( index )
  m_newKey( index ) = cbool( multiKey( index ) )
  
  return( m_oldkey( index ) andAlso not m_newKey( index ) )
end function

/'
  Returns whether or not a key is being held, and repeats at a
  specified interval. If no interval in milliseconds is specified,
  it behaves just like hold().
'/
function Keyboard.repeated( _
  byval index as long, _
  byval interval as double = 0.0 ) as boolean
  
  m_oldKey( index ) = m_newKey( index )
  m_newKey( index ) = cbool( multiKey( index ) )
  
  if( interval > 0.0 ) then
    dim as boolean keyPressed = _
      not m_oldKey( index ) andAlso m_newKey( index )
    
    if( keyPressed ) then
      m_startTime( index ) = timer()
      return( keyPressed )
    else
      dim as boolean keyHeld = _
        m_oldKey( index ) andAlso m_newKey( index )
      
      dim as double elapsed = _
        ( timer() - m_startTime( index ) ) * 1000.0
      
      if( keyHeld andAlso cbool( elapsed > interval ) ) then
        m_startTime( index ) = timer()
        return( keyHeld )
      end if
    end if
  else
    return( cbool( multiKey( index ) ) )
  end if
end function

/'
  Test code
'/
dim as integer screenWidth = 800
dim as integer screenHeight = 600

screenRes( screenWidth, screenHeight, 32 )

dim as integer x = 400, y = 300
dim as integer radius = 10
dim as integer speed = 3

dim as Keyboard key

/'
  Test code
'/
do
  '' Get some keypresses
  if( key.repeated( fb.sc_up, 150.0 ) ) then
    '' Move the sprite if it's still on bounds
    y = max( 0, y - speed )
	end if
		
  if( key.pressed( fb.sc_down ) ) then
    '' Move the sprite if it's still on bounds
    y = min( screenHeight, y + speed ) 
  end if
  
  if( key.released( fb.sc_left ) ) then
    '' Move the sprite if it's still on bounds
    x = max( 0, x - speed )
	end if
  
  if( key.hold( fb.sc_right, 100.0 ) ) then
    '' Move the sprite if it's still on bounds
    x = min( screenWidth, x + speed )
  end if
  
  '' Renders the sprite
  screenLock()
    cls()
    
    circle _
      ( x, y ), _
      radius, rgb( 255, 255, 255 ), , , , f
  screenUnlock()
  
  sleep( 1, 1 )
loop until( _
  key.pressed( fb.sc_escape ) )
paul doe
Moderator
Posts: 1735
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Multikey key release

Post by paul doe »

For the folks interested in these abstractions, I coded a better, more robust and efficient variant. You may find it here.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Multikey key release

Post by badidea »

paul doe wrote:For the folks interested in these abstractions, I coded a better, more robust and efficient variant. You may find it here.
Only now, do I notice that my simplified version does not allow 'pressed' and 'released' of the same key.
Your version does not have this issue, but with a graphics screen, I might as well use 'screenevent'.
From the wiki, stripped version:

Code: Select all

#include "fbgfx.bi"
using fb

dim e as EVENT

screenres 640, 480
width 640 \ 8, 480 \ 16
do
	if (screenevent(@e)) then
		select case e.Type
		case EVENT_KEY_PRESS
			if (e.scancode = SC_ESCAPE) then end
			print "Pressed  : e.scancode : " & e.scancode
		case EVENT_KEY_RELEASE
			print "Released : e.scancode : " & e.scancode
		case EVENT_KEY_REPEAT
		case EVENT_MOUSE_MOVE
		case EVENT_MOUSE_BUTTON_PRESS
		case EVENT_MOUSE_ENTER
		case EVENT_MOUSE_EXIT
		case EVENT_WINDOW_GOT_FOCUS
		case EVENT_WINDOW_LOST_FOCUS
		case EVENT_WINDOW_CLOSE
			end
		case EVENT_MOUSE_HWHEEL
		end select
	end if

	sleep 1
loop
Post Reply