Multiple object list

Monkey Forums/Monkey Beginners/Multiple object list

Razmonger(Posted 2014) [#1]
Hi team,

I have a list full of various object types and I'd like to filter out certain objects only for a collision check, but I can't understand why the code below doesn't work and only checks against one Orange.

I've tried:
If item <> Apple Then ...
which works but is only good if I want to check for collision with any object.

however this:
If item = Orange Then ...
only seems to check against one of my oranges (bottom right) in the list the others are ignored.

Thanks in advance for any help anyone can give.

Strict

Import mojo

Global FruitList:List<TFruit> = New List<TFruit>
Global Apple:TApple
Global Banana:TBanana
Global Orange:TOrange

Function Main:Int()
	New TGame()
	return 0
End Function

Class TGame extends App

	Method OnCreate:Int()
		SetUpdateRate (60)
		For Local i:Int = 1 To 3 
			Banana = New TBanana
		Next
		For Local i:Int = 1 To 3 
			Orange = New TOrange
		Next
		Apple = New TApple

		Return 0
	End Method
	
	Method OnUpdate:Int()
		TFruit.UpdateAll()
		Return 0
	End Method
	
	Method OnRender:Int()
		Cls 
		TFruit.DrawAll()
		Return 0
	End Method	
End Class

Class TFruit 

	Field x:Float
	Field y:Float
	Field r:Int

	Function DrawAll:Void()
		Local item:TFruit
		For item = Eachin FruitList
			item.draw()
		Next
	End Function
	
	Function UpdateAll:Void()
		Local item:TFruit
		For item = Eachin FruitList
			item.update()
		Next
	End Function
	
	Method New()
		FruitList.AddLast(Self)	
	End Method
	
	Method draw:Void() Abstract
	
	Method update:Void() Abstract
	
End Class

Class TApple Extends TFruit

	Field collision:Bool
	
	Method New()
		x = Rnd(640)
		y = Rnd(480)
		r = 16
	End Method
	
	Method draw:Void()
		SetColor(0,255,0)
		DrawCircle(x,y,r)
		SetColor(255,255,255)
		If collision = True Then
			DrawText("collision",10,10)
		endif
	End Method
	
	Method update:Void()

		If KeyDown (KEY_LEFT) Then x = x - 5
		If KeyDown (KEY_RIGHT) Then x = x + 5
		If KeyDown (KEY_UP) Then y = y - 5
		If KeyDown (KEY_DOWN) Then y = y + 5
		
		Local item:TFruit
		collision = False
		For item = Eachin FruitList
			If item = Orange Then   '<--------------- why can't I just say I want only to check against oranges? 
				If x - item.x < 2*r And y - item.y < 2*r And item.x - x < 2*r  And item.y - y < 2*r Then
					collision = True
				Endif	
			Endif
		Next

	End Method
	
End Class

Class TBanana Extends TFruit

	Method New()
		x = Rnd(640)
		y = Rnd(480)
		r = 16
	End Method
	
	Method draw:Void()
		SetColor(255,255,0)
		DrawCircle(x,y,r)
		SetColor(255,255,255)
	End Method
	
	Method update:Void()
	End Method
	
End Class

Class TOrange Extends TFruit

	Method New()
		x = Rnd(640)
		y = Rnd(480)
		r = 16
	End Method
	
	Method draw:Void()
		SetColor(255,128,0)
		DrawCircle(x,y,r)
		SetColor(255,255,255)
	End Method
	
	Method update:Void()
	End Method
	
End Class



Raph(Posted 2014) [#2]
Those globals are each holding a single instance.

What you want to do is check the type. You can cast it to Orange and see if it works:

if Orange(item)  ' it's an orange
else 'it's not
endif



Razmonger(Posted 2014) [#3]
Thanks Raph,
Your explanation of the problem makes it clear why it wasn't working, but I don't understand your solution. After a bit of thinking and understanding the problem, I've added another field to the base class and given each sub class an id which I can check against. Not sure if this is good practice but it works.

If anyone knows of a better way to do this please give suggestions.

New working code:



Raph(Posted 2014) [#4]
I had a typo. But here's an explanation...

Your list is made of TFruit. You have subclasses that are TApple, TOrange, whatever.

When you pull an item from the list, say into a local variable fruitinstance, it's a TFruit. That's what you defined fruitinstance as, that's what the list is.

But you can attempt to cast it to TApple or TOrange -- if the cast succeeds, you know it is that type. If it does not, you know it isn't. So, if you have a fruitinstance that is a TFruit, it could be any of the fruit. If it's actually TApple and try

if TApple(fruitinstance)
  return true
else
  return false
endif


as a TApple, this will return true. If it were a TOrange it would return false because the cast would fail.

Casting in Monkey is basically like this:

fuji:TApple = TApple(fruitinstance)



Raph(Posted 2014) [#5]
PS, an id works, and I have used it myself lots. But unless you have other reasons to have an id (I use it for tool display, that sort of thing) it's a little bit of memory overhead.


Razmonger(Posted 2014) [#6]
Thanks Raph,
I managed to get your explanation to work. I'm still very new to programming and have never heard of casting, but it does seem a cleaner way to achieve what I wanted instead of creating a new field.
Cheers,
George


Razmonger(Posted 2014) [#7]
final code:



Raph(Posted 2014) [#8]
You can think of casting as "changing something of one type into another." You can't cast an object into a type that is actually different, though, so it's really more like "adjusting the label on things." :)


Jesse(Posted 2014) [#9]
I personally don't like an automatic all inclusive list in a class. It limits the things you can do with the class objects. so I don't use lists that way at all.

also objects make it easier to figure out collision:




Razmonger(Posted 2014) [#10]
Thanks Jesse,

I don't have the experience yet to fully see why or why not one way is better than the other, but thanks for showing me an alternative. I'll have a play around with your code and do some hard thinking! There are various little tweaks in there that are very interesting.

Either which way, I have two solutions now so thank you both for your time.
Cheers,
George