If - Else ========= In this tutorial we will: - learn about the Python ``if else`` statements - learn how to load a volume of blocks into the world from a file - and how to save a volume of blocks to a file - learn how to place buttons or levers in the world and have them call Python code when activated Logical Operators and Boolean ----------------------------- Here we will introduce a new type called a Boolean. A Boolean is a value that can only be either True or False. I can create boolean variables like this:: b1 = True b2 = False Python supports the logical operators that you are familiar with from maths. These operators make comparisons between numbers and return True or False (so return a Boolean). e.g. try this in iPython:: In [6]: a = 4 In [7]: b = 6 In [8]: a > b Out[8]: False Common logical operators are: - a > b (a is greater than b) - a < b (a is less than b) - a == b (a is equal to b) - a != b (a is not equal to b) - a >= b (a is greater than or equal to b) - a <= b (a is less than or equal to b) You can also assign the result of a logical operator to a variable. Try this in iPython (reusing values of a and b you assigned a moment ago):: In [9]: b1 = a < b In [10]: b1 Out[10]: True Above we compared a and b and assigned the result to b1. The value of b1 is True because a is less than b. It is important to remember the difference between ``=`` and ``==``. The ``=`` operator is used to assign a value to a variable. The ``==`` operator is used to compare two values. In written maths we do not have this distinction, but it helps Python to be clear about the meaning of a statement. Code Branching -------------- Up to this point we have always executed all the lines of code we have written in the order they appear in the program. The ``for`` loop does allow us to repeat sections of code. It is also very useful to have more than one branch of code to execute. To achieve this is we use ``if else``. This is reasonably easy to understand as it reads like an English sentence. Consider this simple function and see if you can work out what it does: .. code-block:: python def age_test(my_birth_year, your_birth_year): if my_birth_year > your_birth_year: print("You are older than me") else: print("I am older than you") Lets try this with my birth date is 1964 and yours is 2012: .. code-block:: python In [12]: age_test(1964, 2012) I am older than you There is a bug in our code at the moment. What if the birth years are the same? Then the program can't tell which one is older without knowing the month and day of each birthday. We could write the function like this: .. code-block:: python def age_test(my_birth_year, your_birth_year): if my_birth_year == your_birth_year: print("We have the same birth year!") elif my_birth_year > your_birth_year: print("You are older than me") else: print("I am older than you") In this function we first check for the same year. Then we use ``elif``. This is short for "else if". So if the years are not the same then it does the second check for "if my_birth_year > your_birth_year". Gate with Working Portcullis ---------------------------- Now lets use what we have learnt to make a working gate for our village. The following video is a demo of what we will make in this section: .. raw:: html .. centered:: *Gateway with Portcullis Demo* The shape of the gate itself is going to be loaded in from a file that I will provide. You will be free to edit the gate to look how you would like it and then save it back over the original file (see `saving`). The following commands need to be executed in a bash shell. They will create a folder called blocks and download my sample ``gate.json`` file into that folder .. code-block:: bash cd $HOME/my_world mkdir blocks cd blocks wget https://raw.githubusercontent.com/gilesknap/mciwb/main/blocks/gate.json For the moment we don't need to look inside the gate.json file. Just know that you can save and load a volume of blocks in the world to and from a file. (Later when we learn about Python Lists we will look inside these files). Let's jump right in and make the gate and then go back to explain what we have done. Create a new module in your ``buildings`` package and name it ``gate.py``. Here is a reminder of how to do that using your bash prompt: .. code-block:: bash cd $HOME/my_world code buildings/gate.py Paste this code into gate.py and save it. .. literalinclude :: ../../src/demo/gate.py :language: python Note that the ``build_gate`` function has a default value for position. I chose this as a likely entrance point to the village (we will add a drawbridge later!). If you want to change this you can pass a different position to the make_gate function. For help in choosing position coordinates see `../how-to/coordinates`. To make the gate type this in iPython: .. code-block:: python from buildings.gate import make_gate make_gate() This should place the gate in the world. Try out the lever and watch the portcullis open and close. How it Works ------------ Make_gate +++++++++ The first thing that the ``make_gate`` function does is define another function called ``open_close``. This function will be called when the lever is activated. By defining ``open_close`` *inside* of ``make_gate`` we are able to use the variable ``position`` which is needed when moving the portcullis. (If you want to understand more about this see `../explanations/scope`) This code always creates a gate facing north. ``position`` represents the bottom WEST corner of the portcullis. Because we want the stone gate to surround the portcullis we define the position of the South West corner of the gate as a couple of steps to the WEST (left if you are looking North) and one step SOUTH (back if you are looking North). Next we load in the file we downloaded earlier. This uses the world function ``load``. In order to get the world object inside of a module we use the get_world(). Because we need world only once I did not bother to assign it to a variable, see the equivalent approaches below. .. code-block:: python # using a variable to do world.load() w = get_world() w.load("gate.json") # A shortcut to do the same thing get_world().load("gate.json") When loading blocks into the world with ``world.load`` the position always specifies the bottom left SOUTH WEST corner of the blocks. Thus if you face North and choose a block in front of you the loaded object will extend away from you and to your right. The final step in ``make_gate`` is to create a Switch object, we pass it the position and the Item type (we use a lever here). The final parameter is the function to call when the lever is activated. Open_close ++++++++++ The ``open_close`` function is the callback function that the Switch object will call when it detects a change in the state of the lever. Switch callback functions are always passed the Switch object that triggered them. The Switch object's most important property is ``powered`` which is a boolean that is **True** if the lever is currently on and **False** if it is off. Therefore this callback will always call the portcullis function and pass it the original ``position`` value that we gave to ``make_gate`` plus a boolean to say if the lever is on or off. Portcullis ++++++++++ The ``portcullis`` function is called when the lever is activated. It takes a ``position`` and a boolean to say if the lever is on or off. It also takes a width and height for the portcullis, but we use the default values for these as they match the size of our stone gate. The first thing we do is use ``if else`` to change the function's behavior based on the boolean called ``close``. When ``open_close`` calls ``portcullis`` it passes the ``powered`` state of the lever in as the ``close`` parameter. Thus if the lever is powered then we close the portcullis and if the lever is not powered then we open the portcullis. How does the opening and closing get represented in the world? We have a ``for loop`` that sets one row of blocks of the portcullis at each iteration, after each iteration it pauses for half a second. The ``sleep`` function from the built-in module called ``time`` is used to provide the pause, note the import of this function at the top of the file. The ``if`` statement chooses some values for variables used by the ``for loop`` as follows: - Closing: - we set the rows of blocks to ``Item.ACACIA_FENCE`` starting at the top and working down. - Opening: - we set the rows of blocks to ``Item.AIR`` starting at the bottom and working up. .. note:: The ``range`` function we have used so far always counts from zero upwards. When closing the portcullis we want to start at the top and work down. To do this we add additional parameters to the ``range`` function. See `this description `_ for details of the parameters for ``range``. The code ``range(height, 0, -1)`` creates a range that starts at ``height`` and goes in steps of -1 until before it reaches zero. .. _saving: Saving Blocks to a File ----------------------- The ``world`` object also has a save function and you can use it to save a volume of blocks to a file. Then you can reload those blocks in a new location at a later date. To demonstrate this lets build something in Minecraft and save it to a file, then reload it in a new location. The steps are as follows: - Build something you would like to Save, my example in the video is giant a table. - use the selection sign to select the volume of blocks you want to save: - first select one corner e.g. bottom south west - then select the opposite corner e.g. top north east - in iPython type: ``world.save("blocks/table.json")`` - Now select the block where you want to load a copy: - select where you want the SOUTH WEST corner to be - in iPython type: ``world.load("blocks/table.json")`` For a demo of this feature see the video below. .. raw:: html .. centered:: *Save and Load Blocks Volumes* By the way, files that you create with code can be inspected using the vscode explorer. In the image below you can see the ``table.json`` file that we created in the above steps. To see this you would need to click on the ``blocks`` folder to expand it. .. figure:: ../images/table_json.png :alt: file explorer :align: center :width: 900px Viewing files in the VSCode explorer