Something strange with lists.

xooll

Chieftain
Joined
Jun 3, 2011
Messages
77
I'm working on a mod that will simulate the spread of a contagious plague. It's starting to work pretty well, but I'm having a strange problem with the list that contains all the infected plots and the level of infection.

See, it's supposed to increase the level of infection when the (already infected) plot gets re-infected by a neighboring plot, and decrease it if it happens to get better on its own. Each of these is controlled by a random number and works well.

The problem arises when the plot's infection level (stored in the second element of the list representing that plot, which is, in turn, an element in the nested list InfectedPlots) drops to zero or lower. After checking for changes in the levels of existing infections and adding new ones, the code runs a loop to remove any plots that have dropped to zero or lower from the list.

The list element representing the no-longer-infected plot is successfully removed, but then the strange thing happens: a neighboring plot gets duplicated on the list instead. So I will have the same plot appearing twice on the list (which isn't supposed to be possible--the code checks before adding to make sure it's not already there).

To remove the no-longer-infected, I am using the list.pop(i) function. I would use "remove," but it refers to the contents of the list element rather than the index, so it's less useful to me.

So, here's my code. I'm starting to get stumped here, so if anybody sees my error, please point it out.
Code:
from CvPythonExtensions import *
from Popup import PyPopup
import random

gc = CyGlobalContext ()
cyGame = CyGame()
cyMap = CyMap()
cyInterface = CyInterface()

iSpreadByPlotChance = 5
iInfectUnitChance = 40
iSpreadByUnitChance = 40
iSpreadUnitToUnitChance = 30
iGetBetterChance = 15

InfectedPlots = []
InfectedUnits = []

#InfectedPlots is a list of smaller lists.

def PlagueListAdd(NewPlot):
	global InfectedPlots
	InfectedPlots.append([NewPlot,1])


def Plagueturn():
	global InfectedPlots
	global InfectedUnits	
	NewInfectedPlots = []
	NewInfectedUnits = []
	if InfectedPlots:
		###Begin test thing###
		bloop = ''
		for blarg in InfectedPlots:
			warg = blarg[0]
			yikes = blarg[1]
			bloop = bloop + str(warg.getX())+', '+str(warg.getY())+', level: '+str(yikes)+'\n'
		tpop = PyPopup()
		tpop.setHeaderString('Infected Plot List:')
		tpop.setBodyString(bloop)
		tpop.launch()
		###end test thing###
		for PlotSet in InfectedPlots:	#For each of the infected plots...
			ThisPlot = PlotSet[0]
			Level = PlotSet[1]
			Neighbors = []
			x = ThisPlot.getX()
			y = ThisPlot.getY()
			x_range = range(x-1, x+2)
			y_range = range(y-1, y+2)
			for i in x_range:			#Create a list of all its neighbors
				for j in y_range:
					if i != x or j != y:
						Neighbors.append(cyMap.plot(i,j))
			for ThisNeighbor in Neighbors:	#For each neighbor...
				if random.randint(1,100) <= iSpreadByPlotChance + ThisNeighbor.isRoute() * 5:	#See if infection is spread
					if InfectedPlotFind(ThisNeighbor,InfectedPlots)==-1:			#If it's not already infected,
						if InfectedPlotFind(ThisNeighbor,NewInfectedPlots)==-1:		#And it's not already on the list about to be infected,
							NewInfectedPlots.append([ThisNeighbor,1])	#add it to the list about to be infected.
						else:														#If it's already about to be infected,
							NewInfectedPlots[InfectedPlotFind(ThisNeighbor,NewInfectedPlots)][1] += 1	#The new infection gets worse
					else:											#If it is already infected,
						InfectedPlots[InfectedPlotFind(ThisNeighbor, InfectedPlots)][1] += 1	#The infection gets worse.
					if ThisNeighbor.isCity() and InfectedPlotFind(ThisNeighbor,InfectedPlots)==-1:	#Notify the first time a city is infected
						ThisCity = ThisNeighbor.getPlotCity()
						Owner = gc.getPlayer(ThisCity.getOwner())
						tCity = (Owner.getCivilizationAdjectiveKey(), ThisCity.getName())
						cyInterface.addImmediateMessage(CyTranslator().getText('Plague has spread to the %s1 city of %s2.', tCity), '')
			iUnits = ThisPlot.getNumUnits()
			UnitsHereIndex = range(1, iUnits)	#Create a list of all units on this plot
			for unit_iter in UnitsHereIndex:
				ThisUnit = ThisPlot.getUnit(unit_iter)
				if random.randint(1,100)<=iInfectUnitChance:	#See if the units are infected
					NewInfectedUnits.append(ThisUnit)			#If infected, add to a new list to be appended later.
					bpop = PyPopup()
					bpop.setHeaderString('Infected Unit')
					bpop.setBodyString(ThisUnit.getName())
					bpop.launch()
			if random.randint(1,100)<= iGetBetterChance:
				PlotSet[1] = PlotSet[1]-1
				bpop = PyPopup()
				bpop.setHeaderString('Slight Improvement')
				bpop.setBodyString('It is not so bad in '+str(PlotSet[0].getX())+', '+str(PlotSet[0].getY()))
				bpop.launch()
		trashlist = []
		for PlotSet in InfectedPlots:
			PlotSet[0] = ThisPlot
			if PlotSet[1] >=3 and ThisPlot.isCity():
				ThisCity = ThisNeighbor.getPlotCity()
				Owner = gc.getPlayer(ThisCity.getOwner())
				ThisCity.changePopulation(-1)
				PlotSet[1] = PlotSet[1]-2
				tCity = (Owner.getCivilizationAdjectiveKey(), ThisCity.getName())
				cyInterface.addImmediateMessage(CyTranslator().getText('The plague is causing widespread deaths in the %s1 city of %s2.', tCity), '')		
			if PlotSet[1] <= 0:
				trashlist.append(InfectedPlotFind(PlotSet[0],InfectedPlots))
		for index in trashlist:
			InfectedPlots.pop(index)
		if len(InfectedPlots) ==0:
			cyInterface.addImmediateMessage('Physicians believe the plague may have been eradicated.','')
				
	for Infected in InfectedUnits:		#For each unit presently infected
		x = Infected.getX()
		y = Infected.getY()
		Here = cyMap.plot(x,y)
		if random.randint(1,100)<= iSpreadByUnitChance: #If infection is spread to the unit's new location...
			NewInfectedPlots.append(Here)	#Add it to the new infections list
		iUnits = Here.getNumUnits()
		UnitsHereIndex = range(1, iUnits)	#Create a list of all units on this plot
		for unit_iter in UnitsHereIndex:	#For each unit on the same plot...
			ThisUnit = Here.getUnit(unit_iter)
			if random.randint(1,100)<=iSpreadUnitToUnitChance:	#See if the units are infected
				NewInfectedUnits.append(ThisUnit)			#If infected, add to a new list to be appended later.
	
	#Now that we're done iterating through infected things, append the lists with the new infections.
	###Begin test thing###
	bloop = ''
	for blarg in NewInfectedPlots:
		warg = blarg[0]
		yikes = blarg[1]
		bloop = bloop + str(warg.getX())+', '+str(warg.getY())+', level: '+str(yikes)+'\n'
	tpop = PyPopup()
	tpop.setHeaderString('Newly Infected Plot List:')
	tpop.setBodyString(bloop)
	if NewInfectedPlots:
		tpop.launch()
	###end test thing###

	InfectedPlots.extend(NewInfectedPlots)
	InfectedUnits.extend(NewInfectedUnits)
	
def InfectedPlotFind(MyPlot,PlotList):
	count = 0
	for Entry in PlotList:
		ThisPlot = Entry[0]
		if ThisPlot.getX() == MyPlot.getX() and ThisPlot.getY() == MyPlot.getY():
			return count
		count += 1
	return -1

Note: this code won't run on its own; it needs some changes to xml and native python files to do anything. However, if you want to test it, you can change the initialization of InfectedPlots to some random coordinates, and it should run okay (I haven't tried this, but I don't see why it wouldn't).
 
Not sure where exactly the problem is, but I see another one:
You collect the trash indexes in one list. So e.g. 2, 5, 8, etc.
Then you go through the base list, and pop them out.
You pop out 2, you have 1, 3, 4, 5, 6, etc.
Now you pop the 5th element, which doesn't refer anymore to 5, but now to 6, which mixes up the whole thing.
-> revert the thrash list before you delete it's elements out of the base list. If you go backwards through the list, it will prevent that indexes get shifted.
 
I was hoping that was it, but sadly it's still there. Now I've observed the bug occurring without the infection level going down first, so it isn't related to that like I suspected. Time to add more debugging popups...
 
You go through the infected plots at some point, and you add the neighbouring plots to the list, correct?
But you don't check if a plot is already in there. Because what happens if you have neighbouring plots in the infectedPlotList? Some of their neighbours will be added twice, thrice or more to the list.
 
I do have some code checking to see if it's already on the list:
Code:
for ThisNeighbor in Neighbors:	#For each neighbor...
	if random.randint(1,100) <= iSpreadByPlotChance + ThisNeighbor.isRoute() * 5:	#See if infection is spread
		if InfectedPlotFind(ThisNeighbor,InfectedPlots)==-1:			#If it's not already infected,
			if InfectedPlotFind(ThisNeighbor,NewInfectedPlots)==-1:		#And it's not already on the list about to be infected,
				NewInfectedPlots.append([ThisNeighbor,1])	#add it to the list about to be infected.
			else:														#If it's already about to be infected,
				NewInfectedPlots[InfectedPlotFind(ThisNeighbor,NewInfectedPlots)][1] += 1	#The new infection gets worse
		else:											#If it is already infected,
			InfectedPlots[InfectedPlotFind(ThisNeighbor, InfectedPlots)][1] += 1	#The infection gets worse.
Which uses the function:
Code:
def InfectedPlotFind(MyPlot,PlotList):
	#Returns the index of MyPlot in PlotList. If MyPlot does not appear in PlotList, returns -1
	count = 0
	for Entry in PlotList:
		ThisPlot = Entry[0]
		if ThisPlot.getX() == MyPlot.getX() and ThisPlot.getY() == MyPlot.getY():
			return count
		count += 1
	return -1

It's possible that InfectedPlotFind doesn't work properly, but I have been seeing some plots with infection level 2 or more, so I think it's working.
 
But here:
PHP:
			for i in x_range:			#Create a list of all its neighbors
				for j in y_range:
					if i != x or j != y:
						Neighbors.append(cyMap.plot(i,j))

you don't check for doubles. And if you have neighbouring infected plots, then it will lead to doubles in it...or...not?
(not sure :blush:)
 
Hmm. Well, each time a neighbor gets infected, it checks to see if it's on the list already or not before adding it or moving on to the next neighbor, so it shouldn't make doubles... but clearly it does make doubles somehow, so I dunno.
 
Ok, my new debugging popups tell me that the list is changing somewhere in the trash loop. I'm gonna have to give that thing a closer look.
 
I found the bug!
When it says
Code:
PlotSet[0] = ThisPlot
It's supposed to say
Code:
ThisPlot = PlotSet[0]

Ahhh, programming.
 
Back
Top Bottom