Category: Programming

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!

WuLine (Antialiased lines)

Posted by on August 2, 2010

This is a simple implementation of Xiaolin Wu’s line algorithm It doesn’t support colors or blending at all, but it should be a good starting point for anyone willing to implement proper AA lines in PB.

The code:

EnableExplicit
 
Procedure.f  trunc(X.f)
	ProcedureReturn Int(X)
EndProcedure
 
Procedure.f frac(X.f)
	ProcedureReturn (X - trunc(X))
EndProcedure
 
Procedure.f invfrac(X.f)
	ProcedureReturn (1.0 - frac(X))
EndProcedure
 
Procedure.i DrawPixel( x.i, y.i, color.i )
	Plot( x, y, RGB(color, color, color) ) ; you would need to perform bound checking in here, or use your own plotting routine...
EndProcedure
 
Procedure WuLine( x1.f, y1.f, x2.f, y2.f)
 
	Define.i MaxPixelValue = 255
	Define.f grad, xd, yd, length, xm, ym, xgap, ygap, xend, yend, xf, yf, brightness1, brightness2
	Define.i x, y, ix1, ix2, iy1, iy2
	Define.b c1, c2
 
	xd = (x2-x1)
	yd = (y2-y1)
 
	If Abs(xd) > Abs(yd) ;-------------------- Horizontal --------------------
 
		If x1 > x2
			Swap x1, x2
			Swap y1, y2
			xd = (x2-x1)
			yd = (y2-y1)
		EndIf
 
		grad = yd/xd
 
		xend = trunc(x1+0.5)
		yend = y1 + grad*(xend-x1)
		xgap = invfrac(x1+0.5)
		ix1  = Int(xend)
		iy1  = Int(yend)
 
		brightness1 = invfrac(yend) * xgap
		brightness2 =    frac(yend) * xgap
 
		c1 = (brightness1 * MaxPixelValue)
		c2 = (brightness2 * MaxPixelValue)
 
		DrawPixel(ix1,iy1, c1)
		DrawPixel(ix1,iy1+1, c2)
 
		yf = yend+grad
 
		xend = trunc(x2+0.5)
		yend = y2 + grad*(xend-x2)
 
		xgap = invfrac(x2-0.5)
 
		ix2  = Int(xend)
		iy2  = Int(yend)
 
		brightness1 = invfrac(yend) * xgap
		brightness2 =    frac(yend) * xgap
 
		c1 = (brightness1 * MaxPixelValue)
		c2 = (brightness2 * MaxPixelValue)
 
		DrawPixel(ix2,iy2, c1		)
		DrawPixel(ix2,iy2+1, c2)
 
		For x= (ix1+1) To (ix2-1)
 
			brightness1 = invfrac(yf)
			brightness2 =    frac(yf)
 
			c1 = (brightness1 * MaxPixelValue)
			c2 = (brightness2 * MaxPixelValue)
 
			DrawPixel(x,Int(yf), c1	)
			DrawPixel(x,Int(yf)+1, c2)
 
			yf = yf + grad
 
		Next
 
	Else ;-------------------- Vertical --------------------
 
		If y1 > y2
			Swap x1, x2
			Swap y1, y2
			xd = (x2-x1)
			yd = (y2-y1)
		EndIf
 
		grad = xd/yd
		yend = trunc(y1+0.5)
		xend = x1 + grad*(yend-y1)
		ygap = invfrac(y1+0.5)
 
		ix1  = Int(xend)
		iy1  = Int(yend)
 
		brightness1 = invfrac(xend) * ygap
		brightness2 =    frac(xend) * ygap
 
		c1 = (brightness1 * MaxPixelValue)
		c2 = (brightness2 * MaxPixelValue)
 
		DrawPixel(ix1,iy1, c1)
		DrawPixel(ix1,iy1+1, c2)
 
		xf = xend+grad
 
		yend = trunc(y2+0.5)
		xend = x2 + grad*(yend-y2)
		ygap = invfrac(y2-0.5)
		ix2  = Int(xend)
		iy2  = Int(yend)
 
		brightness1 = invfrac(xend) * ygap
		brightness2 =    frac(xend) * ygap
 
		c1 = (brightness1 * MaxPixelValue)
		c2 = (brightness2 * MaxPixelValue)
 
		DrawPixel(ix2,iy2, c1		)
		DrawPixel(ix2,iy2+1, c2)
 
		For y= (iy1+1) To (iy2-1)
 
			brightness1 = invfrac(xf)
			brightness2 =    frac(xf)
 
			c1 = (brightness1 * MaxPixelValue)
			c2 = (brightness2 * MaxPixelValue)
 
			DrawPixel(Int(xf),y, c1	)
			DrawPixel(Int(xf)+1,y, c2)
 
			xf = xf + grad
 
		Next
 
	EndIf
 
EndProcedure

You'll quickly notice there's no coloring and no blending, ie. lines that overlap each other are not blended; we would have to sample the frame buffer to do this and quite frankly, I'd rather use OpenGL if I needed to draw hundredths of AA lines.

There’s at least one issue with this code, sometimes the start/end points are drawn with an offset, etc. I don’t really have time to hunt down the bug though, but heres a quick and dirty example:

Macro RAD2DEG(_n_) 	: (_n_ * 57.295779513082323 ) : EndMacro
Macro DEG2RAD(_n_) 	: (_n_ * 0.0174532925199432 ) : EndMacro
 
Procedure.i render( img.i )
 
	Define.f scalar = 240
	Define.i ot, nt, theta, x, y
	Define.f t, time,frames
 
	Define.i mode = 1
 
	InitKeyboard()
 
	Repeat
 
		If StartDrawing(ImageOutput(img))
 
			Box(0,0,512,512, 0)
 
			If ExamineKeyboard() ; who would've guessed, this works fine without openscreen() if we're not in debug mode...
				If KeyboardReleased( #PB_Key_Space )
					mode = 1 - mode
				EndIf
			EndIf
 
			For theta=0 To 360 Step 8
 
				t = DEG2RAD(theta)+(frames*0.005)
				x = ( Cos(t) * scalar ) + 16
				y = ( Sin(t) * scalar ) + 16
 
				If mode
					WuLine( scalar+y*time*2, scalar+x*time*2, scalar+x, scalar+y )
				Else
					LineXY( scalar+y*time*2, scalar+x*time*2, scalar+x, scalar+y, $ffffff )
				EndIf
 
			Next
 
			time + (Cos(frames*0.01)*0.0045)
			frames + 1
 
			StopDrawing()
			SetGadgetState( 0, ImageID(img) )
 
		EndIf
 
		Delay(8)
	ForEver
 
EndProcedure
 
Define.i img
If OpenWindow(0, 0, 0, 512, 512, "WuLine 4.50 by GuShH", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
	img = CreateImage(#PB_Any, 512, 512)
	If img
		CreateThread(@render(), img)
	EndIf
	ImageGadget(0,  0, 0, 0, 0, ImageID(img))
	Repeat
	Until WaitWindowEvent(16) = #PB_Event_CloseWindow
EndIf

Either way, enjoy.

PS: You may find the complete source in here

Validate email address

Posted by on July 18, 2010

Once in a while you’ll be forced to validate an email address (or many!) — The usual no-brainer method is to perform a quick syntactical test to ensure the address is not malformed, however even a properly formed address may be invalid or, unreachable.

Clearly we must add a second validation step… — Hey I know, let’s check the MX records!

Procedure.s Email_GetHost( eMail.s )
	If eMail
		ProcedureReturn Right( eMail, Len(eMail) - FindString(eMail, "@", 1) )
	EndIf
EndProcedure
 
Procedure.i Email_ValidateAddress( eMail.s, CompleteValidation.i = #True )
 
	Define.s pattern = "^([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,4})$"
	Define.i is_valid = #False
 
	Define.i handle = CreateRegularExpression( #PB_Any, pattern )
	If IsRegularExpression( handle )
 
		If MatchRegularExpression( handle, eMail )
			If CompleteValidation
 
				Dim mx.s(99)
				If getmxrr( Email_GetHost(eMail), mx() ) ; see http://gushh.net/blog/2010/07/17/get-mx-records/
					is_valid = #True
				EndIf
 
			Else
				is_valid = #True
			EndIf
		Else
				is_valid = -1
		EndIf
 
		FreeRegularExpression( handle )
	EndIf
 
	ProcedureReturn is_valid
 
EndProcedure

For the getmxrr() function click here. Or click here for the full code + example usage

The full code also contains a few other helper routines, I left them in there in case someone finds them useful.

These are the functions included in the source:

Email_GetName()
Email_GetHost()
Email_ValidateAddress()
Email_ConvertToLiteral()

Obviously you’ll have to tweak a bit the code, specially the way it handles the arrays. But overall it should be pretty usable. I might come up with a cross-platform version in the future, but so far I haven’t found a need for it.

Please double-check the example code, make sure you understand the routine could return <= 0 if validation fails (two modes basically, syntax fault and mx fault).

Feel free to leave a comment or send me your own version of the code!

Cheers!

Get MX Records

Posted by on July 17, 2010

This routine will populate an array with the MX records of a given domain name. It’s similar in functionality to getmxrr(); from PHP.

Procedure.i getmxrr( HostName.s, Array MXHosts.s(1) )
 
	Define.s buffer			= ""
	Define.s find_str 		= "mail exchanger = "
	Define.i count			= 0
	Define.i mail_exchanger		= 0
	Define.i find_strlen		= Len(find_str)
	Define.i handle 		= RunProgram( "nslookup", "-type=mx " + HostName, "",  #PB_Program_Open | #PB_Program_Read | #PB_Program_Hide )
 
	If IsProgram( handle )
		While ProgramRunning( handle )
 
			buffer 		= ReadProgramString( handle )
			mail_exchanger 	= FindString( buffer, find_str, 1 )
 
			If mail_exchanger
 
				buffer = Mid( buffer, mail_exchanger + find_strlen, Len(buffer) )
 
				If buffer
					MXHosts(count) = buffer
					count + 1
				EndIf
 
			EndIf
 
		Wend
		CloseProgram( handle )
	EndIf
 
	ProcedureReturn count
 
EndProcedure

This version is Windows only since it’s implemented on top of nslookup. Quite frankly I did not have a need to support other platforms at the time. But hopefully it’ll be useful for someone. For instance if you’re looking to validate an email address, this is the second step after the syntactical validation. (Usually a regular expression is utilized for the first step)

Usage example:

Define.i i	; iteration in case we get a result
Dim mx.s(99)	; this array will become populated with mx records
Define.i result = getmxrr( "google.com", mx() )
 
If result
 
	For i=0 To result - 1
		Debug mx(i)
	Next
 
Else
	Debug "no mx records found"
EndIf

In my case I got the following results:

google.com.s9a2.psmtp.com
google.com.s9b1.psmtp.com
google.com.s9a1.psmtp.com
google.com.s9b2.psmtp.com

Cheers!

Spaceship Generator!

Posted by on June 10, 2010

I’ve been working on implementing a content generator and mutator, this particular version was based on the Pixel Spaceships work. This was indeed my first goal.

One Iteration based on a base pattern gives us many ships to choose from.

The next logical step is to start separating the various parts of the code and begin to implement various means of generating content, adding noise based on a map/pattern as well as being able to mix different patterns to change the outcome. Another step in the process is to allow the use of arbitrarily sized patterns.

With just a few modifications to the base pattern we can generate all kinds of content, we then choose the one we like the most and select it’s pattern as the new base design for future mutations. The possibilities are endless!

Generating procedural content is nothing new, but it sure is exciting! — And it’s always a challenge to optimize and evolve this type of code.

Hopefully I’ll come up with something usable in the near future.