Category: Programming

(PB) Loading Assets, the simple way.

Posted by on January 23, 2012

Intro

Assets are an essential part of most games, how you manage them determines whether you spend more time working on them than dealing with them.

For small to medium games, loading an entire directory and having it referenced to a Map is the ideal solution. It’s both flexible and simple.

The following code allows you to do just this:

Prototype.i LOAD_DIRECTORY_CALLBACK( Map Assets.i(), directory.s, extension.s, name.s, userDefined.i = #Null )
 
Procedure.i LoadDirectory( Map assets.i(), directory.s, extension.s, *callback.LOAD_DIRECTORY_CALLBACK, userDefined.i = #Null )
 
	Define.i dir = ExamineDirectory( #PB_Any, directory, "*" + extension )
	If IsDirectory( dir )
 
		Define.i count = 0
		Define.s name = ""
 
		While NextDirectoryEntry( dir )
			If DirectoryEntryType( dir ) = #PB_DirectoryEntry_File
				name = DirectoryEntryName( dir );
				assets( ReplaceString( name, extension, "" ) ) = *callback( assets(), directory, extension, name, userDefined );LoadSound( #PB_Any, directory + "\" + name )
				count + 1
			EndIf
		Wend
 
		FinishDirectory( dir )
	EndIf
	ProcedureReturn count
 
EndProcedure

To use this code you must define a procedure of your own, this procedure is going to be called on each file to be loaded; you’ll have to load and process the file in this function.

Example use:

Global NewMap sprite.i()
Procedure.i callback_loadsprites( Map Assets.i(), directory.s, extension.s, name.s, userDefined.i = #Null )
	ProcedureReturn LoadSprite( #PB_Any, directory + "\" + name, userDefined )
EndProcedure
 
LoadDirectory( sprite(), "sprites", ".png", @callback_loadsprites() )

The example will attempt to scan through the directory “sprites”, it will execute your callback on each png file it finds inside the aforementioned directory, furthermore it’ll reference the filename (without extension) to the sprite() Map, so when you need a handle for the sprite called “fire.png” you’d just use sprite(“fire”) to obtain it.

Since the files are referenced to a Map by their actual name, you could easily implement a scripting system or any other dynamic management solution for your assets without much hassle.

An interesting part of the code is the return value, it’s actually the number of assets found (not the ones loaded, since your callback could choose not to load a certain file, for instance).

There are several limitations to this simple implementation, which is why I mentioned “small games” – There is no directory recursion, no advanced filtering options, no way to parallel the process on a separate thread and there’s no error handling at the moment.

However, it’s still very useful and I highly recommend you try it out, while it’s not a novel aproach, it’s always been the method I used to load my assets and it just works.

Have fun!

(PB) Nearest Power of Two in Assembly

Posted by on August 4, 2011

On today’s masochism section… we find the nearest power of two in Assembly!

Macro PowerOfTwo( _num_ )
	!MOV     Eax, [v_#_num_]
	!SUB     Eax, 1
	!PowerOfTwo_Loop_#_num_:
	!MOV     Ecx, Eax
	!ADD     Eax, 1
	!AND     Ecx, Eax
	!JNZ     PowerOfTwo_Loop_#_num_ 
	!MOV [v_#_num_], Eax
EndMacro

Truth be told I was looking for some old libraries I wrote a while back and I stumbled upon that code. It made me chuckle so of course I had to share it. The reason I laughed was because, well… Who would really need to optimize such a routine?; in those days I was suffering from a severe case of premature-optimizationitis.

However, compared to the normal method which follows:

Procedure.i NearestPow2( Value.i )
	ProcedureReturn Pow( 2, Int((Log( Value ) / Log(2)) + 0.5) )
EndProcedure

The ASM routine is about 25 times faster (I benchmarked the code itself without the procedure to be fair, otherwise it would’ve been 55 times faster!)

So, while unnecessary it still proves a point — You can always optimize your code for speed. However, the cost is clear… readability!

I guess it’s worth stating the obvious in this case: Always benchmark your code and find a balance when it comes to optimization, only optimize your bottlenecks. Don’t waste time in small details unless the reward is worth the time.

Cheers!

PS: Since the PB pre-processor is case sensitive (who would’ve known!) Remember that you must pass the macro the same label your variable has, also because we define a label this means we can only call the routine in one place for one particular variable. That however was not an issue with my code, so it never bothered me.

(PB) String Between Characters

Posted by on April 26, 2011

This is a small purebasic function used to retrieve a string between two known characters, it also allows you to provide a starting position.

Procedure.s str_between_char( *Source.CHARACTER, First.C, Last.C, StartAt.i = 0 )
	Define.i dwLength
 
	*Source + (StartAt * SizeOf(CHARACTER))
	Repeat
		If *Source\c = First ; If this character matches the First character
			*Source + SizeOf(CHARACTER)
			dwLength = *Source
			Repeat ; Repeat until we find an occurrance with the Last character
				*Source + SizeOf(CHARACTER)
			Until *Source\c = Last
			dwLength = (*Source - dwLength)
			ProcedureReturn PeekS( *Source - dwLength, dwLength ) ; Peek the output string
			Break
		EndIf
		*Source + SizeOf(CHARACTER)
	Until *Source\c = #Null
 
EndProcedure

Example use:

Define.s str 		= "dd(hallo)xyz(a)"
Define.s result 	= str_between_char(@str, '(', ')', 2)
Debug result

If you’re working on a parser or similar project this will sure come in handy.
Enjoy!

PB Parallel Port Library

Posted by on January 12, 2011

What is it:

This is a minimalist  library based on the inpout32 DLL

Basically I wrapped the original library and added a whole bunch of useful constants to easily access the Control and Status registers while keeping things clean and simple.

This library is currently being used on a side project I’ve been working on with a friend, since he doesn’t have any μController experience we decided to use LPT for the time being.

The code:

 
; 	Minimalist Library for Parallel port access based on inpout32.dll 
;		by Gustavo J. Fiorenza AKA GuShH (info@gushh.net - info@gushh.com.ar)
;		Version 1.0 - 11/01/2011
 
EnableExplicit
 
; Default LPT Addresses.
#PARALLEL_PORT_LPT1						= $3BC
#PARALLEL_PORT_LPT2						= $378
#PARALLEL_PORT_LPT3						= $278
 
; Register offsets.
#PARALLEL_PORT_STATUS						= $01
#PARALLEL_PORT_CONTROL						= $02
 
; Shared with Data Register.
#PARALLEL_PORT_OFF						= $00
#PARALLEL_PORT_BIT0						= $01
#PARALLEL_PORT_BIT1						= $02
#PARALLEL_PORT_BIT2						= $04
#PARALLEL_PORT_BIT3						= $08
#PARALLEL_PORT_BIT4						= $10
#PARALLEL_PORT_BIT5						= $20
#PARALLEL_PORT_BIT6						= $40
#PARALLEL_PORT_BIT7						= $80
 
XIncludeFile "ParallelPort_Constants.pbi" 	; All of the helper, non-essential constants are defined here.
 
 
Structure PARALLEL_PORT
	Handle.i
	Port.i
	LastData.i
EndStructure
 
 
;- Instance construction and destruction
 
Procedure.i ParallelPort_Create( Port.i = #PARALLEL_PORT_LPT2 )
 
	Define.PARALLEL_PORT *this = AllocateMemory( SizeOf(PARALLEL_PORT) )
	If *this
 
		*this\Port 	= port
		*this\Handle 	= OpenLibrary( #PB_Any, "inpout32.dll" )
 
		If IsLibrary( *this\Handle )
			ProcedureReturn *this
		Else
			Debug "Couldn't load inpout32.dll"
			FreeMemory(*this)
			ProcedureReturn #Null
		EndIf
 
	EndIf
 
EndProcedure
 
Procedure.i ParallelPort_Destroy( *this.PARALLEL_PORT )
 
	If *this
 
		If IsLibrary( *this\Handle )
			CloseLibrary( *this\Handle )
		EndIf
 
		FreeMemory( *this )
		*this = #Null
		ProcedureReturn *this
 
	EndIf
 
EndProcedure
 
;- Communication Functions
 
Procedure.i ParallelPort_Out( *this.PARALLEL_PORT, Bits.w = $00 )
	If *this
		*this\LastData = Bits & $FF
		ProcedureReturn CallFunction( *this\handle, "Out32", *this\Port, Bits )
	EndIf
EndProcedure
 
Procedure.i ParallelPort_In( *this.PARALLEL_PORT, Type.i )
	If *this
		ProcedureReturn CallFunction( *this\handle, "Inp32", *this\Port + Type )
	EndIf
EndProcedure
 
;- Getter Functions
 
Procedure.i ParallelPort_GetHandle( *this.PARALLEL_PORT )
	If *this
		ProcedureReturn *this\Handle
	EndIf
EndProcedure
 
Procedure.i ParallelPort_GetPort( *this.PARALLEL_PORT )
	If *this
		ProcedureReturn *this\Port
	EndIf
EndProcedure
 
Procedure.i ParallelPort_GetLastData( *this.PARALLEL_PORT )
	If *this
		ProcedureReturn *this\LastData
	EndIf
EndProcedure
 
;- Helper Functions
 
Procedure.i ParallelPort_Clear( *this.PARALLEL_PORT )
	If *this
		ParallelPort_Out( *this ) ; The default data parameter is $00
	EndIf
EndProcedure

And the constants include:

 
; Read Only Status Register.
#PARALLEL_PORT_STATUS_IRQ		= #PARALLEL_PORT_BIT2
 
#PARALLEL_PORT_STATUS_ERROR		= #PARALLEL_PORT_BIT3
#PARALLEL_PORT_STATUS_SELECT		= #PARALLEL_PORT_BIT4
#PARALLEL_PORT_STATUS_PAPEROUT		= #PARALLEL_PORT_BIT5
#PARALLEL_PORT_STATUS_ACK		= #PARALLEL_PORT_BIT6
#PARALLEL_PORT_STATUS_BUSY		= #PARALLEL_PORT_BIT7
 
; Read / Write Control Register.
#PARALLEL_PORT_CONTROL_STROBE		= #PARALLEL_PORT_BIT0
#PARALLEL_PORT_CONTROL_LINEFEED		= #PARALLEL_PORT_BIT1
#PARALLEL_PORT_CONTROL_RESET		= #PARALLEL_PORT_BIT2 ; AKA Initialize Printer OR Init.
#PARALLEL_PORT_CONTROL_SELECT		= #PARALLEL_PORT_BIT3
 
; These constants are icluded for completeness. All pin numbers are relative to D-Type 25, Centronics pins are between parentheses.
 
#PARALLEL_PORT_D0	=	#PARALLEL_PORT_BIT0			; PIN2
#PARALLEL_PORT_D1	=	#PARALLEL_PORT_BIT1			; PIN3
#PARALLEL_PORT_D2	=	#PARALLEL_PORT_BIT2			; PIN4
#PARALLEL_PORT_D3	=	#PARALLEL_PORT_BIT3			; PIN5
#PARALLEL_PORT_D4	=	#PARALLEL_PORT_BIT4			; PIN6
#PARALLEL_PORT_D5	=	#PARALLEL_PORT_BIT5			; PIN7
#PARALLEL_PORT_D6	=	#PARALLEL_PORT_BIT6			; PIN8
#PARALLEL_PORT_D7	=	#PARALLEL_PORT_BIT7			; PIN9
 
#PARALLEL_PORT_C0	=	#PARALLEL_PORT_CONTROL_STROBE		; PIN 1
#PARALLEL_PORT_C1	=	#PARALLEL_PORT_CONTROL_LINEFEED		; PIN 14
#PARALLEL_PORT_C2	=	#PARALLEL_PORT_CONTROL_RESET		; PIN 16	(31)
#PARALLEL_PORT_C3	=	#PARALLEL_PORT_CONTROL_SELECT		; PIN 17	(36)
 
#PARALLEL_PORT_S3	=	#PARALLEL_PORT_STATUS_ERROR		; PIN 15	(32)
#PARALLEL_PORT_S4	=	#PARALLEL_PORT_STATUS_SELECT		; PIN 13
#PARALLEL_PORT_S5	=	#PARALLEL_PORT_STATUS_PAPEROUT		; PIN 12
#PARALLEL_PORT_S6	=	#PARALLEL_PORT_STATUS_ACK		; PIN 10
#PARALLEL_PORT_S7	=	#PARALLEL_PORT_STATUS_BUSY		; PIN 11
 
; PINS 18-25 (19-30) Are all tied to GND.

The hardware:

You don’t need any special hardware other than a Centronics or similar cable and a PC with a Parallel port (I’m sure some of you keep older PCs around for a good reason!) — Aside from this if you’re planning on running the examples you might want to get some LEDs and Switches.

Something to download:

The entire sources and support files including the examples can be found Here. Remember to exit the demo programs with the ESC key so the program gets a chance to reset the output bits.

Useful companion:

During development you may want to keep an eye on the status of each pin, however not everyone has a breakout board for this particular interface so you may want to use a software version of this concept instead, one of my favourite ones is LPT.exe — It works fairly well and there are several ports to different languages in case you’re interested in modifying it to suit your own needs.

Closing up:

That’s all for now, hopefully I’ll be able to finish the project and post some of the code here. Hint: it involves controlling unipolar steppers, computer vision and webcams!

Can you guess what it is?

Cheers.

AutoComplete Library

Posted by on August 14, 2010

This nifty library will allow you to incorporate auto-completion in your latest game/software project without much hassle. It supports the use of a dictionary with both load and save routines, it keeps track of hits and allows to filter the suggestion using a “headroom” parameter.

The “algorithm” used to populate the suggestion list is down to the bare basics, reason being I wanted to keep the library small and simple. In fact some “methods” were not implemented for this very same reason. But please feel free to write a helper lib and I’ll happily add a link towards it.

The 4.50 source:

Structure AUTOCOMPLETE_ENTRY	; This represents each word in the dictionary
	word.s			; The word
	length.i		; The length of the word (cached for speed)
	hits.i			; Amount of hits this word received (see GetSuggestions for more information on this)
EndStructure
 
Structure AUTOCOMPLETE
	DictionaryFileName.s			; Filename for the dictionary, optional.
	last_input.s				; Handy last input helper
	last_suggestion.s			; Likewise but it holds the last "best" suggestion, in case you don't want to go through the list.
	List dictionary.AUTOCOMPLETE_ENTRY()	; Holds complete words or sentences, it is our database.
	List suggestion.AUTOCOMPLETE_ENTRY()	; This list will be populated with results once GetSuggestion is called.
EndStructure
 
Declare.i AutoComplete_LoadDictionary( *this.AUTOCOMPLETE, DictionaryFile.s )
Declare.i AutoComplete_SaveDictionary( *this.AUTOCOMPLETE, DictionaryFile.s = "" )
 
Procedure.i AutoComplete_Create( DictionaryFile.s = "" )
	; Creates an instance of the AutoComplete library.
 
	Define.AUTOCOMPLETE *this = AllocateMemory( SizeOf( AUTOCOMPLETE ) )
	If *this
 
		InitializeStructure( *this, AUTOCOMPLETE )
 
		If DictionaryFile
			AutoComplete_LoadDictionary( *this, DictionaryFile )
		EndIf
 
		ProcedureReturn *this	
	EndIf
 
EndProcedure
 
Procedure.i AutoComplete_Destroy( *this.AUTOCOMPLETE, AutoSave.i = #False )
	; Destroys an instance of the AutoComplete library.
 
	If *this
 
		If AutoSave
			AutoComplete_SaveDictionary( *this )	
		EndIf
 
		ClearStructure( *this, AUTOCOMPLETE )
		FreeMemory( *this )
	EndIf
 
EndProcedure
 
Procedure.s AutoComplete_GetSuggestion( *this.AUTOCOMPLETE, PartialInput.s, Headroom.i = 4 )
	; Generates a suggestion list based on input string.
	; The Headroom variable limits how bigger a suggestion can be, compared to the input length.
 
	If *this
 
		Define.i PartialFindLength	= 99999
		Define.i PartialInputLength 	= Len( PartialInput )
 
		If PartialInputLength > 0
 
			ClearList( *this\suggestion() )
 
			ForEach *this\dictionary()
 
				If Left( *this\dictionary()\word, PartialInputLength ) = PartialInput
 
					If *this\dictionary()\word = PartialInput
						*this\dictionary()\hits + 1
					EndIf
 
					If *this\dictionary()\length < PartialFindLength + Headroom
 
						PartialFindLength = *this\dictionary()\length
 
						If AddElement(*this\suggestion())
							*this\suggestion() 	= *this\dictionary()
							*this\last_suggestion 	= *this\suggestion()\word
						EndIf
 
					EndIf
				EndIf
 
			Next
 
			SortStructuredList( *this\suggestion(),  #PB_Sort_Integer, OffsetOf( AUTOCOMPLETE_ENTRY\hits ), #PB_Sort_Descending )
			ProcedureReturn *this\last_suggestion
 
		EndIf
	EndIf
 
EndProcedure
 
Procedure.i AutoComplete_AddWord( *this.AUTOCOMPLETE, Word.s, Hits.i = 0 )
	; Adds a word to the dictionary.
 
	If *this
 
		If AddElement( *this\dictionary() )
			*this\dictionary()\word 	= Word
			*this\dictionary()\length 	= Len(Word)
			*this\dictionary()\hits 	= Hits
		EndIf
 
		ProcedureReturn #True
	EndIf
 
EndProcedure
 
Procedure.i AutoComplete_ClearHits( *this.AUTOCOMPLETE )
	; Clears all word hits from the dictionary.
 
	If *this
 
		ForEach *this\dictionary()
			*this\dictionary()\hits = 0
		Next
 
		ProcedureReturn #True
	EndIf
 
EndProcedure
 
Procedure.i AutoComplete_LoadDictionary( *this.AUTOCOMPLETE, DictionaryFile.s )
	; Loads a dictionary from a file.
 
	If *this
		If DictionaryFile
 
			Define.s sInput, sOutput
			Define.i Hits = 0
			Define.i fp = ReadFile( #PB_Any, DictionaryFile )
 
			If IsFile( fp )
 
				*this\DictionaryFileName = DictionaryFile
 
				While Not Eof( fp )
 
					sInput = ReadString( fp )
 
					If FindString( sInput, ",", 1 ) ; If there's a comma in the line, then it means we have to load the hits.
 
						sOutput = StringField( sInput, 1, "," )
						Hits 	= Val( StringField( sInput, 2, "," ) )
 
					Else ; Otherwise no hits are present in the dictionary file, assume 0.
 
						sOutput = sInput
						Hits	= 0
 
					EndIf
 
					AutoComplete_AddWord( *this, sOutput, Hits )
				Wend
 
				CloseFile( fp )	
			EndIf
 
		EndIf
	EndIf
 
EndProcedure
 
Procedure.i AutoComplete_SaveDictionary( *this.AUTOCOMPLETE, DictionaryFile.s = "" )
	; Saves the dictionary to a file.
 
	If *this
 
		If DictionaryFile = ""
			DictionaryFile = *this\DictionaryFileName	
		EndIf
 
		If DictionaryFile
 
			Define.s sInput, sOutput
			Define.i fp = CreateFile( #PB_Any, DictionaryFile )
 
			If IsFile( fp )
 
				ForEach *this\dictionary()
 
					sOutput = *this\dictionary()\word
					sOutput + "," + Str( *this\dictionary()\hits )
					WriteStringN( fp, sOutput )
 
				Next
 
				CloseFile(fp)	
			EndIf
 
		EndIf
	EndIf
 
EndProcedure

And a quick example:

Notice: The example requires a dictionary, you may download a sample one based on english words by clicking here.

Define.AUTOCOMPLETE *auto = AutoComplete_Create( "english.txt" ) ; load this dictionary for future auto completion assistance.
If *auto
	If OpenConsole()
		PrintN(" Type part of a word and press enter to view the suggestions" )
		Repeat
 
			Define.s in = Input()
			If in
 
				If AutoComplete_GetSuggestion( *auto, in, 2 )
					ForEach *auto\suggestion()
						PrintN( *auto\suggestion()\word + " - " + Str( *auto\suggestion()\hits ) )
					Next
					PrintN("")
				EndIf
 
			EndIf
 
			Delay(100)
		Until Inkey() = Chr(27) Or in = "exit"
 
		CloseConsole()
	EndIf
 
	AutoComplete_Destroy( *auto, #True ) ; Because we loaded a dictionary, if we pass True as the second parameter, the dictionary will be saved before the instance is destroyed.
EndIf

Another good point about this library is that it's 100% cross-platform, so you're not depending on the OS to auto-complete your fields and like I said before, you could easily implement it in your game, etc. A bad point is that it includes no error handling at the moment.

That's it for now, you can download the entire source here. And a sample dictionary here.

Cheers!