Introduction to TYPE Variables & Linked Lists

BlitzMax Forums/BlitzMax Tutorials/Introduction to TYPE Variables & Linked Lists

dw817(Posted 2016) [#1]
This is really REALLY new for me, but now that I finally understand it, I must share it with the rest of you.





Basically it draws colored raindrops, but NOTICE that no arrays are used in this code to keep track of the raindrop positions OR their color NOR are we keeping track of where they've been in a separate array either.

No, this program is using a method called LINKED LISTS. In it, you can store multiple kinds of data from a TYPE variable.



A TYPE variable as near as I can tell is a definition class, much like you are defining a variable to be integer, float, string, timage, or pixmap.

But it is none of these. Instead a TYPE variable is a bit like a box and can contain multiple kinds of data that can be recalled instantly from a LINKED LIST. And yes, you can even have arrays inside Types for added power and complexity.



Think of a LINKED LIST as a large warehouse for these TYPE boxes. When you run the program, we are essentially taking the warehouse, raising it about 300 feet in the air, removing the roof, and any time you hit SPACE a box (raindrop) is created right at the top of it.

With gravity, it falls, but something interesting happens when it hits the ground.



I have coded it above so when it hits the bottom of the screen it is removed. Now read that carefully, it is REMOVED, not zeroed out or changed to a negative value nor are we decreasing a total - in fact if you check, there is no variable recording the total raindrops in the air.

And you do not have to keep track of the number of elements in them. Unlike other programs where you 'zero' out a condition, or when you may have to do zero and blank out multiple array values just to get the desired effect it has been deleted.

No, instead you can tell your program that the item that is there is no longer needed and you can actually REMOVE IT from the linked list with the REMOVE command. In one command alone.

There are many advantages to this over conventional arrays. You can essentially fire and forget - create boxes on the fly when you need them and remove them just as easily, and you don't have to keep track of the number of them either, that is handled automatically in the LINKED LIST and EACHIN.

What are some things you could use with LINKED LISTS ? Particles for one, anything that can spawn and die rapidly and varies in the number all the time.

Imagine having one ship attack you in a top-view shooter and then when one enemy ship hasn't been hit for a-while it SPAWNS a new enemy ship for you to fight.

That would be a nightmare to do with normal arrays, but with TYPES and linked lists, it would be quite simple.

So overall BlitzMAX is more powerful than you give it credit for. Let it do the hard work for you to keep track of all your loose variable items and arrays, conducted entirely in a TYPE variable and created, recalled, modified, and removed, from a LINKED LIST.

Hope This Helps !


Cocopino(Posted 2016) [#2]
Hi.
A Blitzmax type can hold more than just variables, it can also contain functions/methods and has a constructor "New". This is where you should add the code that creates the particle.

Also, you could add a method "Draw", this will draw the current particle at its location. To illustrate I've moved around some of your code:



The main purpose of structuring like this is manageability; you will certainly lose track of stuff like "where did I set the color of the particle again in my code?" otherwise.
Hope this helps.


Hotshot2005(Posted 2016) [#3]
I have added Functions in and Escape key....

This is more cleaner code in main loop(where repeat and until is!)


' Rainbow Rain - an experiment and tutorial in TYPE variables and LINKED LISTS
Strict

Const speed=10 ' change to 1 for maximum speed
Const depth=0 ' change to higher # for more raindrops per cycle

SeedRnd MilliSecs()
SetGraphicsDriver GLMax2DDriver(),0 ' zero forces front buffer
Graphics 768,576

Type particle ' My 1st attempt at using TYPE variables !

  Field x ' x-coordinates
  Field y ' y-coordinates
  Field color:Int ' TYPE variables can hold more than one type AND value !

  Method New()
	  Self.x = Rand(-576, 767) ' x-position of rain
	  Self.y = 0 ' unnecessary it seems as by saying NEW it is already zero
	  Self.color = Rand(0, 5)
  End Method
  
  Method Draw()
  	Select Self.color
  		Case 0
			SetColor 255, 0, 255
		Case 1
			SetColor 255, 0, 0
		Case 2
			SetColor 255, 128, 0
		Case 3
			SetColor 255, 255, 0
		Case 4
			SetColor 0, 255, 0
		Case 5
			SetColor 0, 0, 255
	End Select

	DrawLine Self.x, Self.y, Self.x + 7, Self.y + 14 ' simple streak
	Self.x:+1 ' move rain down by 2 and across by 1
	Self.y:+2
	
  End Method
  
End Type ' this type has 2-integers and one string for it

Global list:TList = CreateList() ' this is called a LINKED LIST
Global dot:particle,ilist:particle ' a type of array holding more than one value

Function RenderWorld()

        Cls ' clear display so no streaks are left on screen
	If KeyDown(32) ' hold down SPACE to add a raindrop
	  For Local i = 0 To depth
	  dot=New particle' we are DEFINING a special type of variable now
	  ListAddFirst list,dot ' add this data to the linked list
	  Next
	EndIf

	SetColor 255, 255, 255 ' white (for our rain count)
	DrawText "Rain: " + list.count(), 0, 0 ' show how many drops are in the air
	For ilist = EachIn list ' EACHIN will work with non-numeric values, and we're looping only as many raindrops as there are
		ilist.Draw()
		If ilist.y>=576 Or ilist.x>=768 Then list.remove ilist ' if out of boundaries, remove from list
	Next ' and YES this does affect the total count, it's not zeroed out, it's actually REMOVED !
	' powerful business linked lists are

	Delay speed ' decrease this number to slow down animation effect
	glFlush ' show our work
	
End Function

Repeat '                    (* MAIN *)
	  RenderWorld()
Until KeyDown(Key_Escape) ' (* END OF MAIN *)




Brucey(Posted 2016) [#4]
Some things to note...

"Self" is not required when referring to fields or methods in the Type.

I think you are playing with fire choosing to render directly to the front buffer. Each to their own of course, and YMMV.


TomToad(Posted 2016) [#5]
My take on it. Added a bit of gravity. Press Enter to create a gust of wind. :)



dw817(Posted 2016) [#6]
Hello Gang. Looks like everyone had a shot at my code. :) That's fine. I'm not familiar with the command METHOD yet. One step at a time. I'll try to look into that later today.

Brucey, are you saying rendering to the front buffer is dangerous ? Can you explain, please ?


H&K(Posted 2016) [#7]
@brucy,

Self however allows auto complete to kickin without you needing to remember the types methods or fields names. (not in the default ide maybe)


Hotshot2005(Posted 2016) [#8]
I been making Shoot em up based on Link lists and I got problem at the moment.

I will post video of it plus the code too :)


okee(Posted 2016) [#9]
Can paramaters be passed to a New() method ?
local t:Tmytype
t = New("stringvalue") TmyType

testing here and it doesn't seem to like it.


Derron(Posted 2016) [#10]
It is not possible.

For this you create a custom function/method.
Method init:mytype(params)
self.x = paramXY
Return self
End Method

Local s:mytype = new mytype.init(paramsToUse)


Bye
Ron


Brucey(Posted 2016) [#11]
No. New() just returns a new instance of a Type.

You will need to write some kind of constructor method/function. There are several typical ways to do it :

Type TMyType
  Field x:int
  Field y:int

  ' the create method - useful when extending types.
  Method Create:TMyType(x:int, y:int)
    Self.x = x
    Self.y = y
    Return Self
  End Method

  ' the create function
  Function CreateFunc:TMyType(x:int, y:int)
    Local this:TMyType = New TMyType
    this.x = x
    this.y = y
    Return this
  End Function

End Type

' a create function alternative - old style, procedural based.
Function CreateMyType(x:int, y:int)
  ' optionally call one of the above
  Return New TMyType.Create(x, y)

  ' or implement a version of CreateFunc() here...
End Function



okee(Posted 2016) [#12]
Yeah, i did have a constructor written that works nicely but had read in a few places about the New method but hadn't seen an example of it.
I really like the methods and functions within a type in Blitzmax
makes the code very clear and manageable, after mostly using blitzplus


Hotshot2005(Posted 2016) [#13]
There video of it for people to download it and somethings isn't right thought and here the code of it....

https://www.dropbox.com/s/ubrnhkrw9nme85e/IMG_1308.MOV?dl=0

Graphics 640, 480, 0

Global Background = LoadImage("Data\Back_3.png")
Global Centre_Player = LoadImage("Data\Player_1.png")
Global Left_Player = LoadImage("Data\Player_3.png")
Global Right_Player = LoadImage("Data\Player_2.png")

' Enemys Animations
Global Enemy_Anim_Part_1=LoadImage("Data\Ennemy_3_1.png")


Global Player_X = 250
Global Player_Y = 400
Global Frames = 0

Global Enemys_Animations=0
Global Y
Global X2,Y2
Global SPEED=5

Type Alien
	 Field X, Y, Shoot
	
	  Method New_E()
	         X2 = Rand(20, 630) ' x-position of Enemys
	         y2 = y2 + 1
     End Method

     Method Draw()
  			Select Enemys_Animations
  				Case 0
                     DrawImage Enemy_Anim_Part_1,X2,y2
            End Select
	End Method

End Type

Global list:TList = CreateList()
Global dot:Alien,ilist:Alien ' a type of array holding more than one value

Function RenderWorld()

        Cls ' clear display so no streaks are left on screen
        
        TileImage Background, 0, Y
	    Y=Y+1
	
	    Select Frames
		      Case 1
		           DrawImage Centre_Player, Player_X, Player_Y
		      Case 2
		           DrawImage Left_Player, Player_X, Player_Y
		      Case 3
		           DrawImage Right_Player, Player_X, Player_Y
	    End Select
	
	   ' Control the Player on the Keyboard !!1
	   If KeyDown(KEY_LEFT)
		  Frames = 2
		  Player_X = Player_X - 1
       ElseIf KeyDown(KEY_RIGHT)
	      Frames = 3
	      Player_X = Player_X + 1
	   Else
	      ' IF No Keys press then go back Static ship !
	      Frames = 1
       EndIf
	
	   For Local i = 0 To depth
	       dot=New Alien' we are DEFINING a special type of variable now
	       ListAddFirst list,dot ' add this data to the linked list
	   Next
	
	   For ilist = EachIn list ' EACHIN will work with non-numeric values, and we're looping only as many raindrops as there are
	       ilist.Draw()
	
	       ' IF Aliens reached bottom of the screen
		   If ilist.y>=480 
		       list.remove ilist ' if out of boundaries, remove from list
		   Else
		       ' IF had been Aliens Remove then Create more Aliens !
		       ilist.New_E()
		   EndIf		   
	   Next
	
	   Delay speed ' decrease this number to slow down animation effect

End Function


While Not KeyDown(KEY_ESCAPE)
    RenderWorld()	
	Flip
Wend



Derron(Posted 2016) [#14]
For ilist = EachIn list
-> For local ilist:alien = EachIn list
(saves to declare the variable before)


For Local i = 0 To depth
-> how large is "depth" (it is not used anywhere else, Strict/Superstrict helps here)


' IF had been Aliens Remove then Create more Aliens !
ilist.New_E()
-> what should that do? you adjust values of the current item/alien ...
-> so for each entry in the list: if the y is < 480, set x to random and increase y

I would adjust it a bit...


what did I change:
- splitted UpdateWorld() and RenderWorld()
- made it strict
- created some dummy gfx for others without media
- prepared some "oop" (Update() for each Alien)
- redone some logic (refill with new aliens if others died)

I left for you to create a class for the player, containing its coordinates, frame, speed ...

What did you do wrong: each of your units used the same "X2,Y2" and each time a unit was y < 480, you set new values, which made them "jump around".


bye
Ron


Hotshot2005(Posted 2016) [#15]
thank you for helping and also good for explain on what I did wrong...

I am going to study them and see what I have learn :)

Now I going Enemys Animations, Bullets and collisions next on my own and see how goes :-)


Derron(Posted 2016) [#16]
So instead of removing the aliens when Y > 480, you should set them to "dead" (or alive=false).

Then you remove all enemies wo are no longer alive (that "remove :+ [alien]"-portion).

This allows you to "kill" the aliens for various reasons: collision, out of screen, timer, ...

Instead of "alive = false" you could create multiple attributes: "dying" (eg. while still displaying an "dying" animation...).


Bullets should be custom types too (TBullet) so you can easily hold a list/array of them for each unit which is able to shoot (player, aliens?).


Good luck

bye
ron


Hotshot2005(Posted 2016) [#17]

So instead of removing the aliens when Y > 480, you should set them to "dead" (or alive=false).

Then you remove all enemies wo are no longer alive (that "remove :+ [alien]"-portion).

This allows you to "kill" the aliens for various reasons: collision, out of screen, timer, ...

Instead of "alive = false" you could create multiple attributes: "dying" (eg. while still displaying an "dying" animation...).


Bullets should be custom types too (TBullet) so you can easily hold a list/array of them for each unit which is able to shoot (player, aliens?).



I will take note of that but I going take step by step like sorting the Animations then take it from there.


I got the Animations working but I am worry about slowing the game down as I try slow the animations down

here the code on what I did

	 Method Update()
	       Enemys_Animations=Enemys_Animations+1
	       If Enemys_Animations>=6
	          Enemys_Animations=0
	          Delay 25
	       EndIf 
	
	 	   Move()
		'Attack()
		'...
	 End Method

	 Method Draw()
		   Select Enemys_Animations
			     Case 0
				       DrawImage Enemy_Anim_Part_1,X,Y
				 Case 1
				        DrawImage Enemy_Anim_Part_2,X,Y
				 Case 2
				        DrawImage Enemy_Anim_Part_3,X,Y
				 Case 3
				        DrawImage Enemy_Anim_Part_4,X,Y
                 Case 4
				        DrawImage Enemy_Anim_Part_5,X,Y
				 Case 5
				        DrawImage Enemy_Anim_Part_6,X,Y
		   End Select
	End Method



Derron(Posted 2016) [#18]
Yes... Delay is absolutely _wrong_ in that case.

So how could we increase the time between animations?

a) we could store a timer for each alien ... once it reaches 0, the next animation is set and the timer is reset to the "timer duration".
Timer-approach allows to run independently of FPS (speed of your computer)

Field lastUpdate:int
Field animTimer:int
Field animTime:int = 100 'every 100 millisecs?
Field animation:int = 0
...

Method Update()
  UpdateAnimation()
  'other stuff we did in Update()
  'Move()
  'Attack()

  lastUpdate = Millisecs() 'not error prone, might be negative after 28days uptime
End Method

Method UpdateAnimation()
  'subtract time gone since last call to this instances Update()
  animTimer :- (Millisecs - lastUpdate)

  if animTimer < 0
    animTimer = animTime
    animation :+ 1
    if animation > 5 then animation = 0
  endif
End Method




b) Instead of storing an timer for each unit, you could calculate the time since the last "UpdateWorld()" call (similar to a), but this time only store that time once). Now you have some kind of "time since last update call" - which allows for a similar approach to a), but this time all units get the same "time since last update" instead of an individual one


BTW: instead of delay we now have the information of how many milliseconds have passed since the last update. Now imagine you do no longer say "move +1 each Update", but you declare: my units should move 10 pixels per second.

Now you just have to do "y :+ speed * lastUpdate/1000.0".
Speed is eg. "10". If last Update was 1 second ago, then the values would be: "y :+ 10 * 1000.0/1000.0" which means, add 10. If only 100 ms passed, then 1.0 would get added. And so on and so forth.

BUT ... to enable this kind of of movement, your "Y" must be of type "float" as "int" values only allow discrete steps. (y :+ 0.1 will stay "y" as it is rounded down automatically). Once you set them to "float", it should work.

Finally you should be able to remove that "delay 5" from your main loop.
If you want to slow down alien-movement you either decrease their individual speeds - or add a general "global speedMod:Float = 1.0" to your alien-type. Then extend "Y :+ speed * lastUpdate" to something like "Y :+ speed * speedMod * lastUpdate".


bye
Ron


Hotshot2005(Posted 2016) [#19]
I don't think I have good job on Bullets link lists and I just don't know why Bullets isn't working!

I hope I havnt made more confusing!

Strict 

Graphics 640, 480, 0

Global Background:TImage = LoadImage("Data\Back_3.png")
Global Centre_Player:TImage = LoadImage("Data\Player_1.png")
Global Left_Player:TImage = LoadImage("Data\Player_3.png")
Global Right_Player:TImage = LoadImage("Data\Player_2.png")

' Enemys Animations
Global Enemy_Anim_Part_1:TImage=LoadImage("Data\Ennemy_3_1.png")
Global Enemy_Anim_Part_2:TImage=LoadImage("Data\Ennemy_3_2.png")
Global Enemy_Anim_Part_3:TImage=LoadImage("Data\Ennemy_3_3.png")
Global Enemy_Anim_Part_4:TImage=LoadImage("Data\Ennemy_3_4.png")
Global Enemy_Anim_Part_5:TImage=LoadImage("Data\Ennemy_3_5.png")
Global Enemy_Anim_Part_6:TImage=LoadImage("Data\Ennemy_3_6.png")

Global Player_Bullet_Front:TImage=LoadImage("Data\Bullet_1.Png")

Global Player_X:Int = 250
Global Player_Y:Int = 400
Global Frames:Int = 0

Global animations:Int=0
Global BackgroundY:Int
Global speed:Int=5

Global animTimer:Int

Type Positions
     Field X:Int, Y:Int 'each alien or Tbullets has its OWN position
     Field Shoot:Int
End Type


Type Alien Extends Positions

     ' Alien Animations
     Field lastUpdate:Int
     Field animTimer:Int
     Field animTime:Int = 100 'every 100 millisecs?
     Field animations:Int = 0
	 
     Global list:TList = CreateList()
     Global maxAliens:Int = 10
	
     Method New()
            'random position for new aliens
            X = Rand(0, 640 - Enemy_Anim_Part_1.Width) ' x-position of Enemys
	    y = - Enemy_Anim_Part_1.Height - Rand(50,70) 'start outside
		    speed = Rand(2,4) '2-4 
     End Method
	
     Method Move()
            y :+ speed
     End Method

     Method Update()
       	    UpdateAnimation()
	    Move()
	    'Attack()
	    '...
	    lastUpdate = MilliSecs() 
     End Method
	
     Method UpdateAnimation()
            'subtract time gone since last call to this instances Update()
            animTimer :- (MilliSecs() - lastUpdate)

            If animTimer < 0
               animTimer = animTime
               animations :+ 1
               If animations > 5 Then animations = 0
            EndIf
     End Method

     Method Draw()
	    Select animations
	           Case 0
		        DrawImage Enemy_Anim_Part_1,X,Y
		   Case 1
		        DrawImage Enemy_Anim_Part_2,X,Y
		   Case 2
		        DrawImage Enemy_Anim_Part_3,X,Y
		   Case 3
		        DrawImage Enemy_Anim_Part_4,X,Y
                   Case 4
		        DrawImage Enemy_Anim_Part_5,X,Y
		   Case 5
		        DrawImage Enemy_Anim_Part_6,X,Y
	   End Select
     End Method
	
End Type


Global TBullets:TList=New TList

Type TBullet Extends Positions
     Global Bullets:TList = CreateList()
     
     ' Position of Player Bullets
     Method New()
            ' position for new Bullets
 	    X =  Player_X ' x-position of Player
     End Method
	
     Method BMove()
 	    y :- speed
     End Method

     Method BUpdate()
	       	
	    BMove()
	    BDraw()
	
	    'Attack()
     End Method

     Method BDraw()

            ' Users to press fire the Bullets!
	    If KeyDown(Key_Space)
	       BMOVE()		 
	    EndIf 
	
	    DrawImage Player_Bullet_Front,Player_X,Y	
     End Method
End Type

Function UpdateWorld()
             
         ' move background
         backgroundY :+ 1

        ' Control the Player on the Keyboard !!1
	If KeyDown(KEY_LEFT)
	   Frames = 2
	   Player_X = Player_X - 1
        ElseIf KeyDown(KEY_RIGHT)
	       Frames = 3
	       Player_X = Player_X + 1
	Else
	    ' IF No Keys press then go back Static ship !
	       Frames = 1
        EndIf


        'add as many aliens as missing
	For Local i = 0 Until alien.maxAliens - alien.list.Count()
	    alien.list.AddFirst( New Alien )
	Next

	Local remove:Alien[] 'array holding "to remove entries"
	
	For Local entry:Alien = EachIn Alien.list
            entry.Update()

	    ' if out of boundaries, remove from list
	    If entry.y >= 480 
	       remove :+ [entry] 'add to removal
	    EndIf
	Next

	' using this "remove outside of iterate-over-list" method avoids
	'concurrent modification of the list (else: might run into bugs)
	
        For Local entry:Alien = EachIn remove
	    Alien.list.Remove(entry)
	Next
	
End Function

Function RenderWorld()

         Cls ' clear display so no streaks are left on screen
        
         TileImage Background, 0, BackgroundY

         For Local entry:Alien = EachIn Alien.list
	     entry.Draw()
	 Next
	
	 Select Frames
		   
                Case 1
	             DrawImage Centre_Player, Player_X, Player_Y
		Case 2
		     DrawImage Left_Player, Player_X, Player_Y
		Case 3
		     DrawImage Right_Player, Player_X, Player_Y
	    
         End Select
	
End Function


While Not KeyDown(KEY_ESCAPE)
      UpdateWorld()
      RenderWorld()	
      Flip
Wend



Midimaster(Posted 2016) [#20]
Where in the code do you fire a bullet? whre Do you add a bullet to the list BULLETS and where do you call TBUllet.Draw()?