Exercises Answers

52. We're tasked with creating a function named getGameObjects that receives a function that performs a test on an entity. If the entity passes the test then true is returned and that entity is added to a list that will be returned by getGameObjects. This exercise is basically asking if you have a good understanding of what passing functions around to other functions as arguments can achieve.

The first thing we wanna do in getGameObjects is go over all game objects of the Area:

 

Now, for each game object, we want to apply the filter function that is received. Since the user will always define that function as receiving an entity as argument, we can always pass game_object to it:

 

filter will return true in case the game object passes the test and false otherwise, which means that writing it like that in a conditional makes sense. Now, inside the conditional, we want to add the game_object that passed the test to a table, and then we want to return that table to the caller:

 

And so in this way we will get all game objects from an Area that pass the test.

53. This exercise is asking if you understand how the and and or operators work and how they can be used to check for certain conditions.

For the variable a the result is 2. Whenever two variables are true in an and operation, the second variable will always be the returned result. This is useful in a number of situations, like in the previous exercise the line if e.hp and e.hp >= 50 then is asking if e.hp exists, and then if it does, checking if that value is over 50. If e.hp doesn't exist, then that will be nil and the next check won't even be run (and if it was run it would result in an an error, since it would be asking if nil >= 50 which can't be done). In this way we can check to see if a certain attribute exists and then use that attribute in some way all in the same line instead of doing something like this:

 

For the variable b the result is nil. In Lua only nil and false are false values, so there's no way for this to be anything else. Like I just said, if the first element of an and operation is false, then the second one is not even checked, so in this case the value 2 is irrelevant.

For the variable c the result is 3. Whenever two variables are true in an or operation, the first variable will always be the returned result. This is useful in a number of situations, like whenever we do stuff like opts = opts or {}. If the opts that's passed in is nil, then the or will return the empty table. But if the opts value is defined then it will just return opts itself. Using or like this we can check to see if some value that was passed in was actually passed in or not instead of doing something like this:

 

For the variable d the result is 4. If one of the elements in an or operation is false then it will just return the one that's true. For the variable e the result is 4 as well and the same logic can be used to explain it.

For the variable f the result is 1. First, (4 > 3) is true, so the whole thing parses out to true and 1 or 2. and takes precedence over or, so first we parse true and 1 which returns 1. Now we have 1 or 2 left, which parses out to 1 again.

For the variable g the result is 2. First, (3 > 4) is false, so the whole thing parses out to false and 1 or 2. Then we parse false and 1 which is false, and finally we're left with false or 2, which is 2.

54. This question is asking for your understanding of the ... construct in Lua. Whenever you define the arguments of a function as ..., it means that the function can receive an unknown amount of arguments and that you'll deal with separating them inside. So, our function could look like this:

 

So we just capture all the arguments passed in inside the table args and then we simply go over that table and print each value.

55. We can use the same logic for this function, but instead of printing each value we just concatenate them together using ..:

 

The difference here is that we have to define an empty string str and then when going over the args table we concatenate all values to that string.

56. The collectgarbage function can be used to do a number of things related to the garbage collector. As the link states, calling it like collectgarbage("collect") will perform a full garbage collection cycle.

57. From the previous link we can see that calling collectgarbage("count") will show the total amount of memory used by the Lua program in KBs.

58. We can use the error function to do this. I personally rarely use this function in game code, but if you're building a library and you want to lock off certain paths of execution it's useful to halt execution when those paths are activated and tell the user how they used your library inappropriately. It's a way better thing to do than just failing silently.

59. First we can create a default Rectangle class:

 

Then the question asks us to create 10 instances of the class at random positions of the screen with random size. We already took care of the size inside the class itself, so now for the rest:

 

Then, whenever the d key is pressed a random instance should be deleted:

 

Because every object in the Area is an instance of the Rectangle class we can just remove a random element from the area.game_objects table directly instead of doing anything more complicated.

The next thing that's asked is that when the number of instances reaches 0, then 10 new instances should be created again like initially:

 

Here we can just check the size of the game_objects table and if it is 0 we just copy the code we used to spawn the Rectangle instances initially.

60. First we create the default Circle class:

 

Now we need to create 10 instances of this class, but with an interval of 0.25 between the creation of each instance:

 

Using the timer:after call inside the for loop we can make it so that one instance will get spawned every 0.25 seconds by multiplying i by 0.25 and using that as the argument for the after call.

Now it says that after all the instances are created we need to delete a random one every [0.5, 1] second:

 

So, after the initial 2.5 seconds of creating instances, we create an every timer that will run with an interval of between 0.5 and 1 second and will remove an instance each time it is run.

Now the last thing the exercise asks for is to restart the process again once the number of instances left reaches 0. Similarly to the previous exercise we can just check the number of entities left in game_objects:

 

Now the question is what do we put in place of --restart? The exercise asks for this process to repeat forever, so the first instinct would be to put this all into a function, and then call this function from within itself whenever it should be repeated. That would look something like this:

 

The only problem with this approach is that the first time it is called it will create one every timer, the second time it is called it will create another, and so on. In the end we will have multiple every timers operating at the same time and that will lead to bugs. One easy way to fix it is to label the every timer so that whenever it's called again, the previous one is cancelled.

 

And another thing we need to do is to also cancel the previous every timer whenever the process restarts again, or instances will be removed until the next every is called, which is something we don't want:

 

This exercise is very very tricky and there are many ways to get lost. But this is also the kind of thing that you have to do all the time in games. Understanding and controlling things so that they happen in appropriate orders and at appropriate times is very important, so make sure you understand everything that's happening!

62. The first thing we need to do is to go over all game objects in an Area and check to see if they are of the target classes we want:

 

Here I make use of the .class attribute, which holds the name of the class this object belongs to. We didn't define this previously but it can be easily added to every object that is inside the Area in the addGameObject function:

 

This is mostly something convenient so we can use the any function. We can achieve all this without this function and instead just looping over the object_types list and using classic's is function instead. Either way works but the first one feels cleaner.

Now that we know if the game object is or isn't of the type we're looking for, we can do the actual check to see if it's inside our determined radius or not. To do this we'll use a small function called distance, which computes the distance between two points:

 

And so with that we can do this:

 

Here we simply check the distance between the center of the radius and the center of the object. If that distance is lower than the radius then we'll add that object to a list, which we will then return once the loop is over:

 

62. This one is very similar to the previous exercise, except that instead of returning all objects inside the circle, we just return the one that's closest to the target point. To start with, we can use the function we defined in the previous exercise to get all objects that are actually inside the circle:

 

Now what we need to do is to somehow sort this table so that the first objects in it are closer and the last ones are further away. An easy way to do that is using table.sort:

 

And so with this, we will place objects that have a smaller distance from the target first in the list. Since we know that closer objects are first, to get the closest one all we need to do is return the first object:

 

63. We can check if a method or attribute exists by just using a conditional. For instance, if we want to check if self has the attribute damage then we can do if self.damage then. If we want to check if it has the attribute damage and if that damage is higher than 10 then we can do if self.damage and self.damage > 10 then. The use of the and operator like this was explained in a previous exercise.

64. Suppose we have table a and table b and we want to copy table a to table b. To achieve that we'd do the following:

 

Using the pairs function we can go over all keys and indexes of the source table and then use those to directly set the appropriate values on the target table.