Hi, welcome to this new video. We're going to continue working on our ecosystem simulation, and at this point, we're going to be starting to talk about how do we remove herbivores from the system. What would be the conditions for them to die, right? We could create a condition in which they die out of the lifespan, reaching kind of a point where we think that's kind of the end of their life. But we could also start thinking of how we can connect ideas of feeding or like achieving a resource like our food system, and how starvation might happen if that resource is not around, right? So we really want to create kind of a symbiosis between our different classes at this point, right? So what would be our starvation or death from starvation function? As we have done before, we're going to create a variable that is going to be our patient value, that is only going to start counting once the animal is in sick food state or hungry, right? Maybe when it's in that state, it will start gradually building up a sense of maybe desperation for food. That's perhaps how resilient the animal would maintain the search for food. If it doesn't find any food, especially when there's a lot of competition, it might end up in a dead state, right? We're going to remove those entities from the system. So with this in mind, let's just jump into the code and see how to implement this. So let's recap where we're at. We're actually continuing from our last video. If you remember, in the last video, we have our herbivore, only a single herbivore in the screen, it's wandering around. You can see in the console, at some point it transitions to seeking food, and at that point transitions to resting. So what I would like to do is kind of bring back a few things. We're going to create a transition first from resting to wandering, right? We don't have that yet. And now that when we have that complete circularity, we're going to introduce the possibility of this herbivore to die of starvation, right? And we're also going to bring back many herbivores to the system. So let's start with the, I'm here in the herbivore class, which is again the class that we've been working through the weeks, through the different lessons. If we go down to the rest function, you see that right now we have a placeholder that says, just print line, I'm resting. And that's what we can see here in the console. Our herbivore is in fact only resting, right? But I'm going to need a few variables first. Let's create a few variables that we will need. We did have hunger_level, hunger_threshold. Let's create a few variables that would be useful to calculate the starvation and also the rest level, right? So imagine you're resting and you're kind of regenerating a sense of kind fo rest. So let's call first self.rest_level. And that's going to be, we're going to start with something like 0, I think. Let's just go with 0 for now. And we're also going to use self.rest_threshold. And we're going to put a number 50 here for now so that the sleeping time is not going to be too much. This is basically how long to sleep for. If you want that nap to be very long after eating, you will have to increase this number. But we're going to spend a bit of time in calibration after this video. And I will come back here where we're going to create the starvation level and the starvation threshold, which is going to be a similar technique to calculate the starvation function. But for now, let's do the rest. So resting, we have basically this function here, which is a placeholder. And we're going to say self.rest_level += 1, right? So we increase this as a counter. And if the self.rest_level is bigger than the self.rest_threshold, Right, what happens? That's not how you spell threshold. There we go, threshold. We are going to do a few things. We're going to change the state, right? So we're going to say self.isAwake = True, right? So we wake up. So we are rested enough, we wake up. We're also going to reset the rest_level. So now the rest_level goes back to 0. And finally, and perhaps the most important part, is that we reset the tree, right? What we do here in other functions, like whenever we want to reset the calculation of the decision tree, we go back and recalculate that the current node of the tree, the decision tree, will become a new calculation. So this would kind of cascade through the tree, check, is it awake? Now, it's true, it's going to transition back to a wandering state or a sick food state, depending on if it's hungry or not hungry. Most likely after eating and going to a rest, our herbivore will be, in fact, not hungry. So it would probably default back to a wandering state, right? So, but again, it's important to debug and make sure that your behavior is as you expected. Sometimes you might, depending on the order of things, you might be getting a different result. So we are in a wandering state. Let's just check that this is working. Seeking food. Now it's resting, going back to wandering, right? And now it's wandering around. It's not necessarily seeking that food. It's not hungry yet. Got hungry, went and eat, now wanders around. So the wander state is more like a curiosity, kind of hanging out kind of state. The seeking food is a state of chasing that food. So we have that circularity, right? It loops around three different states. Let's bring back in the World_Manager, remember that we actually created a single herbivore with this function as a placeholder. So I'm going to remove all that because now I actually would like to create herbivores over time. And we did that thing with this function here that we had commented out, right? So every 80 frames we create a new herbivore. Let's just see how that would work. So we have one here, and every time we create a new herbivore, this is a pretty high rate of population growth. And you can see all of them are kind of eating the food supply that is available close to them. At some point, they will run out of food because the food kind of grows at its own rate. But suddenly more and more herbivores are getting hungry and they're not being fed. And eventually, they're going to reach a point where they cannot find the food. They're kind of chasing, chasing, chasing, and they're not going to find their food. At that point, we should be killing them, or basically they should be dying from not really having enough food. So that's where our starvation function is going to kick in, right? So let's just make sure that we go all the way up here to the herbivore node, and let's create another variable that is going to be our starvation value. And it's going to start at 0, and starvation_threshold, It's going to say 100. So for 100 frames, if our herbivore looks for food for 100 frames and it doesn't find food, it will die, right? So now that we have those variables, let's just put them in action. We're going to be looking into the seek_food function. That's basically where we want the starvation to kick in. So sick food, why do we want to do it in the seeking food? Because we want to make sure that while we're looking for food, the particular kind of movement that is faster, right? We're exerting more energy for looking food. It's kind of a desperate mode. It's like we're competing against other herbivores for food at this point. At this point, we're going to kick in a sense of like, now you're a countdown of starvation, right? So we would do the same technique again of self.starvation _value += 1, right? So your starvation will start growing. And if that starvation_value, It's bigger than the starvation_threshold, which we, I think we said 100 frames, right? We're going to call a function like die, [LAUGH] right? And we don't have that function yet, so let's create it. So def die(), it's going to take self. And here how we have done this before, like food actually can remove itself. It could be eaten, so it could be removed. We need to make sure that we have a reference to, currently, the herbivore has a reference to the all_food variable, right? So it can understand where all the food elements are, but it doesn't understand its position in relation to a population of other herbivores, especially because those are part of a list as well. So we're going to do here all_herbivores, And so all_herbivores will be a passing of the list that contains all the herbivores, or death is going to be like, you're no longer computed as part of that collection. You're removed from that collection in a way. So let's also, when we did self.all_food, where is that all_food? There, here. So let's do the same thing. self.all_herbivores = all_herbivores, right? So if we do that, because we changed the construction of the class, we need to make sure that whenever we're creating the class here, we're constructing a herbivore, we're passing on all_food. We also have to pass all_herivores, which is the list, right? And the constructor demands a series of arguments, right? And now we're passing the list of herbivores to the herbivore class, right? So that's our node. So now that we have all the herbivores referenced, we can actually die or kind of kill the herbivore. This function here, as we have discussed, or the same way in which we have done it for the food, right, which is self.all_herbivores.remove(self), right? And with that function now written, we can do self.die within the seek_food, right? So if the starvation level reaches, we're going to find a dead state, removing yourself from the system. So you would imagine that the more herbivores you have in the system competing for food. Some of them will be maybe further away from food and will transition to look for another piece of food, and eventually some of them will die. So they will start creating kind of this dynamic equilibrium between the amount of food available and the amount of herbivores available, because the herbivores are not going to be able to sustain population growth that doesn't have any food supply, right? So let's just see if our simulation is running correctly. So we have herbivores running around, they're getting hungry, and they start eating the food. And eventually some of them, we currently don't have a very clear way. You see one of them, I think, disappeared here. You might want to create a state, not just remove them from the system, but you see that some of them just disappear, they die. Our death is kind of pretty quick and pretty brutally. You don't see them really hanging around in any way. They just disappear. So we remove them from the computation. What we could actually do, if you wanted to graphically see them stay around, you can actually reduce their speed, maybe as they get tired, or you could actually leave them in a dead state as part of a computation, but maybe without an emotion. So it could be visible that how many of your animals are actually not surviving, right? But we're going to spend a bit of time in the next video really calibrating and also visualizing. There's a lot of data going on here that it might be important to visualize so that we could actually visually debug if our system is performing the way we intended. We're going to leave this video here, and I'll see you in the next one. We're going to be doing that visualization. I'll see you then.