Problem with removing items from TList

BlitzMax Forums/BlitzMax Programming/Problem with removing items from TList

anawiki(Posted 2006) [#1]
Hi
In our game - Maggie the Gardener - we store every plant in TList. When player press right mouse button to remove a plant very often soft removes plants that are far away from the mouse pointer. As we can not see bugs in our code we believe that there is something wrong with TLists mechanism. Here is the code example:

Method RemoveObjects(all=False)
If(all)
ClearList(objectsList)
Return
Else
If(deleteTimer._ticks<4) Return
For temp=EachIn objectsList
If(temp.CollideWithCursor())
ListRemove(objectsList,temp)
deleteTimer._ticks=0
soundEngine.PlaySfx("usowanie")
Return
EndIf
Next
EndIf
End Method

Method CollideWithCursor() ' 
If(MouseX()>x and MouseX()<x+ImageWidth(brush.bitmap))
If(MouseY()>y and MouseY()<y+ImageHeight(brush.bitmap))
ResetCollisions()
CollideImage(brush.bitmap,x,y,0,0,1)
If(CollideRect(MouseX(),MouseY(),1,1,1,0)) Return True
EndIf
EndIf
Return False
End Method



This piece of code should be enougth to say if we are wrong or not.


N(Posted 2006) [#2]
I'm sorry, but if you can't post decently formatted code I just can't help...


assari(Posted 2006) [#3]
Anawiki,
What datatype are you storing in the TList. From your code it looks like integers. TList and integers don't mix I'm afraid.


skidracer(Posted 2006) [#4]

TList and integers don't mix I'm afraid.



Unfortunately I think they do in non strict mode.


anawiki(Posted 2006) [#5]
Code is written in Strict mode. Object in list are of custom type.

Here is the code once again:

	Method RemoveObjects(all=False)
		If(all)
			ClearList(objectsList)
			Return
		Else
			If(deleteTimer._ticks<4) Return
			For temp=EachIn objectsList
				If(temp.CollideWithCursor())
					ListRemove(objectsList,temp)
					deleteTimer._ticks=0
					soundEngine.PlaySfx("usowanie")
					Return
				EndIf 
			Next
		EndIf
	End Method

	Method CollideWithCursor()				
		If(MouseX()>x And MouseX()<x+ImageWidth(brush.bitmap))
		If(MouseY()>y And MouseY()<y+ImageHeight(brush.bitmap))
			ResetCollisions()
			CollideImage(brush.bitmap,x,y,0,0,1)
			If(CollideRect(MouseX(),MouseY(),1,1,1,0)) Return True 
		EndIf
		EndIf
		Return False
	End Method




Tibit(Posted 2006) [#6]
Are you sure you are using Strict, I do not think that would compile, you have a variable called temp which is not declared (in the Foreach loop). If temp is an Int I do not think it will work like that. You can either make it an object or make it a string.

Let us say you have TPlants in that list, then I would do something like this to loop it: (using Strict)
For local Plant:TPlant = Eachin ObjectList
    If Plant.Collide() ObjectList.Remove( Plant )
Next 
Also I would recommend using ObjectsList.Remove( temp ) instead of ListRemove(objectsList,temp)
as it is easier to read, but that is merly personal preference :)

Hope that helps.


anawiki(Posted 2006) [#7]
temp is declared as field. More code, maybe this time you can point me in some direction. TObject is a type that represents plants or other stuff. TGarden represents garden :D In this type I left only RemoveObjects method as others are not necessary.

Type TObject			' Klasa reprezentuje obiekty narysowane w ogrodzie
	
	Field x,y,brush:TBrush
	
	Function create:TObject(tX,tY,bsh:TBrush)
		Local temp:TObject = New TObject
		temp.brush = bsh
		temp.x = tX - (ImageWidth(bsh.bitmap)/2)
		temp.y = tY - (ImageHeight(bsh.bitmap)/2)
		Return temp
	End Function	
		
	Method paintObject(drawShadow=False)
		SetBlend(ALPHABLEND)
		SetAlpha(1)
		If(Not drawShadow) If(brush.bitmap) DrawImage(brush.bitmap,x,y)
		If	  (drawShadow) If(brush.shadow) DrawImage(brush.shadow,x,y)
	End Method

	Method Compare(otherObject:Object)		' Metoda porownuje pionowe polozenie obiektow, uzywana jest przez funkcje wbudowana SortList, potrzebne jest to do rysowania obiektow w okreslonej kolejnosci (z gory na dol)
		Local temp:TObject = TObject(otherObject)
		Return (y+ImageHeight(brush.bitmap)) - (temp.y+ImageHeight(temp.brush.bitmap)) 
	End Method
	
	Method CollideWithCursor()				' Metoda sprawdza czy obiekt koliduje z kursorem myszy
		If(MouseX()>x And MouseX()<x+ImageWidth(brush.bitmap))
		If(MouseY()>y And MouseY()<y+ImageHeight(brush.bitmap))
			ResetCollisions()
			CollideImage(brush.bitmap,x,y,0,0,1)
			If(CollideRect(MouseX(),MouseY(),1,1,1,0)) Return True 
		EndIf
		EndIf
		Return False
	End Method
	
End Type



Type TGarden

	Field objectsList:TList, temp:TObject
	Field lockLeft, lockRight
	Field coursor:TImage
	Field deleteTimer:TTimer 

.....

	Method RemoveObjects(all=False)
		If(all)
			ClearList(objectsList)
			Return
		Else
			If(deleteTimer._ticks<4) Return
			For temp=EachIn objectsList
				If(temp.CollideWithCursor())
					ListRemove(objectsList,temp)
					deleteTimer._ticks=0
					soundEngine.PlaySfx("usowanie")
					Return
				EndIf 
			Next
		EndIf
	End Method

......

end type



TomToad(Posted 2006) [#8]
If the problem here is what I think it is, then it's a known bug. ListRemove uses the Compare function to know when it has the correct object in the list. The default Compare will return 0 when self = object. Since your overridden Compare will return 0 any time self.x = TObject.x, the ListRemove function will think it found the object when it hasn't.
You either need to find a way to never return 0 except for ListRemove, or write your own sort method, or don't use lists.


TomToad(Posted 2006) [#9]
Hold on, I just discovered something looking at the mod source. The TList.Sort() method takes a function as a parameter.
Method Sort( ascending=True,compareFunc( o1:Object,o2:Object )=CompareObjects )
That means you don't have to override the compare function, You can just write your own and pass it to TList.Sort() and leave the COmpare function alone for the TList.Remove function.
Here is a test code to show how it works.
Type MyGreatType
 Field name:String

 Function MyGreatSort(m1:Object,m2:Object)
  If MyGreatType(m1).name > MyGreatType(m2).name Then Return 1
  If MyGreatType(m1).name < MyGreatType(m2).name Then Return -1
  Return 0
 End Function
End Type

Local MyType:MyGreatType
Local MyTypeList:TList = CreateList()

For t = 0 To 20
 MyType = New MyGreatType
 MyType.name = Mid("abcd",Rand(1,4),1)+"MyType"+t
 ListAddLast(MyTypeList,MyType)
Next

For MyType = EachIn MyTypeList
 Print MyType.name
Next

Print "Sorting...."

MyTypeList.sort(True,MyGreatType.MyGreatSort)

Print "Sorted."
For MyType = EachIn MyTypeList
 Print MyType.name
Next

This is in version 1.20, don't know how it'll behave in older versions.


assari(Posted 2006) [#10]
TomToad, thanks a bunch. I was wondering how to use the new sort function.

So in essence, dont override the compare method, create a separate compare function instead and then call the sort function accordingly.


TomToad(Posted 2006) [#11]
Yeup, that's the way it seems to work. It's definately not in version 1.12 so I don't know exactly when it was added. Looking through the docs, I see that the SortList function was changed also.
Function SortList( list:TList,ascending=True,compareFunc( o1:Object,o2:Object )=CompareObjects )

Here it is:
***** 1.20 Release *****
+ (BRL.LinkedList) Added optional CompareFunc parameter to SortList


So it was added in the latest version.


Tibit(Posted 2006) [#12]
Nice fix, I did not know that myself :)