01 Building Automatically

Building a Wall Automatically - Part 1

For a computer scientist, code runs the world but this is true for the everyday person on planet Earth even if they do not realize it. Code is in your refrigerator, microwave, car, TV, you name it code runs the device. We live in a digital age.

One thing that is not apparent to most people is that when you have the source code to your device or game you can do almost anything that you can imagine. Think about your favorite game. Wouldn’t it be cool if you could make your character move faster, have more health, or deal more damage? Games are one thing but as a Computer Scientist for the Government, having the source code makes it possible to verify the system performance and understand the weakness or strength of an algorithm.

Fortunately, open source software exists and is thriving today making computer programming accessible to everyone. For this exercise we are going to use a simple ‘for loop’ to build a wall by pushing the b key. We created this YouTube video about this chapter 01 Building Automatically.

To get started with this programming exercise, first copy 00_flat_world_TVR.py code to a new file 01_building_automatically_TVR.py but replace TVR with your initials using the following command:

cp 00_flat_world_TVR.py 01_building_automatically_TVR.py

The first thing we have to do is tell the program that the b key is worth listening to; so, jump down to line 742 and make the adjustments as shown below:

    def on_key_press(self, symbol, modifiers):
        """ Called when the player presses a key. See pyglet docs for key
        mappings.

        Parameters
        ----------
        symbol : int
            Number representing the key that was pressed.
        modifiers : int
            Number representing any modifying keys that were pressed.

        """
        if symbol == key.W:
            self.strafe[0] -= 1
        elif symbol == key.S:
            self.strafe[0] += 1
        elif symbol == key.A:
            self.strafe[1] -= 1
        elif symbol == key.D:
            self.strafe[1] += 1
        elif symbol == key.SPACE:
            if self.dy == 0:
                self.dy = JUMP_SPEED
        elif symbol == key.ESCAPE:
            self.set_exclusive_mouse(False)
        elif symbol == key.Y:
            self.position = (0, 0, 0)
            dx, dy, dz = self.get_motion_vector()
            self.dy = 0
        elif symbol == key.TAB:
            self.flying = not self.flying
        elif symbol in self.num_keys:
            index = (symbol - self.num_keys[0]) % len(self.inventory)
            self.block = self.inventory[index]
        elif symbol == key.B:
            self.build_wall()

Take note that the method we are working on is called “on_key_press” which is a name that makes a lot of sense because this code will be executed when a key is pressed. Lots of keys exist on the keyboard and for this game; however, we only care about the keys listed so we use an if-else statement to execute lines of code when that particular key is pressed. The code is obvious when symbol is equal to the constant key b then execute the method build_wall(). So next would be a good time to create the build_wall method.

    def build_wall(self):
        """ Builds a simple wall

        """
        for x in range(-10, 10):
            self.model.add_block((x, -1, -10), STONE)
            self.model.add_block((x, 0, -10), STONE)
            self.model.add_block((x, 1, -10), STONE)

The build_wall method is created at the end of the Window class on line 857, be sure to tab this method over two tabs or Python will be unhappy. Python uses whitespace (in our case the tab character) to delimit program blocks, which is a genius feature of Python because you don’t have to count braces like in many other languages. One of the key advantages of using IDLE is if the tabs are not correct it will highlight the error with a red underline as shown below:

_images/IDLE_expected_indentation.png

A “for loop” is a critical structure in computer science and is often used by programmers to repeat instructions over and over again and increment (or decrement) a variable. In this case the variable is x and it will run over the range starting at -10 and ending before 10. Notice that for each iteration of the for loop a block is added at a different elevation -1, 0, or 1. We will explain 3-dimensional coordinate systems in the next chapter.

Building a Great Wall Automatically - Part 2

By changing a single variable in the for loop, the wall can be turned into a great wall. We created this YouTube video about this chapter 01 Building Automatically - part 2.. Simply change the build_wall method as follows:

    def build_wall(self):
        """ Builds a simple wall

        """
        for x in range(-100, 100):
            self.model.add_block((x, -1, -10), STONE)
            self.model.add_block((x, 0, -10), STONE)
            self.model.add_block((x, 1, -10), STONE)

Notice that the x variable in the for loop now goes from -100 to 100, and the wall goes past the boundaries of the world. Below is a picture of what the wall should look like in the great wall form.

_images/building_automatically_great_wall.png

Building a Pyramid Automatically - Part 3

Pyramids can be a challenging structure to build in a block based game because of all the repetition. Pyramids are essentially smaller and smaller squares laid on-top of each other. We created this YouTube video about this chapter 01 Building Automatically - part 3..

Before we build a pyramid, let’s make it easier to construct solid shapes by creating a method that takes two x, y, z points as parameters, then adds blocks in between those two points. Lets call this new method add_blocks(), and place it in the model class on line 255.

    def add_blocks(self, position1, position2, texture, immediate=True):
        """ Add a volume of blocks with the given `texture` from position 1 to
        position  to the world.

        Parameters
        ----------
        position1 : tuple of len 3
            The (x, y, z) position of the block to add.
        position2 : tuple of len 3
            The (x, y, z) position of the block to add.
        texture : list of len 3
            The coordinates of the texture squares. Use `tex_coords()` to
            generate.
        immediate : bool
            Whether or not to draw the block immediately.

        """
        # this code below finds the lowest x, y, z
        if position1[0] < position2[0]:
            x_start = position1[0]
            x_end = position2[0] + 1
        else:
            x_start = position2[0]
            x_end = position1[0] + 1
        if position1[1] < position2[1]:
            y_start = position1[1]
            y_end = position2[1] + 1
        else:
            y_start = position2[1]
            y_end = position1[1] + 1
        if position1[2] < position2[2]:
            z_start = position1[2]
            z_end = position2[2] + 1
        else:
            z_start = position2[2]
            z_end = position1[2] + 1

        for x in range(x_start, x_end):
            for y in range(y_start, y_end):
                for z in range(z_start, z_end):
                    self.add_block((x, y, z), texture, immediate)

The add_blocks() method is complicated, so don’t feel bad if you don’t understand all of it. Python ‘for loops’ uses the range function which doesn’t include the last number in the range so we have to add a +1 to the end of every range. To make sure the ranges increment in the proper direction, we check the arguments to determine the start of each of the ranges for x, y, and z. Finally, we use three for loops to fill in all the blocks similar to what we have done in previous chapters. It is reasonable not to care how the add_blocks() method works and just use the method when you need to use it.

We changed the key from b to p in the on_key_press method to build the pyramid automatically as listed in the code below:

    def on_key_press(self, symbol, modifiers):
        """ Called when the player presses a key. See pyglet docs for key
        mappings.

        Parameters
        ----------
        symbol : int
            Number representing the key that was pressed.
        modifiers : int
            Number representing any modifying keys that were pressed.

        """
        if symbol == key.W:
            self.strafe[0] -= 1
        elif symbol == key.S:
            self.strafe[0] += 1
        elif symbol == key.A:
            self.strafe[1] -= 1
        elif symbol == key.D:
            self.strafe[1] += 1
        elif symbol == key.SPACE:
            if self.dy == 0:
                self.dy = JUMP_SPEED
        elif symbol == key.ESCAPE:
            self.set_exclusive_mouse(False)
        elif symbol == key.Y:
            self.position = (0, 0, 0)
            dx, dy, dz = self.get_motion_vector()
            self.dy = 0
        elif symbol == key.TAB:
            self.flying = not self.flying
        elif symbol in self.num_keys:
            index = (symbol - self.num_keys[0]) % len(self.inventory)
            self.block = self.inventory[index]
        elif symbol == key.P:
            self.build_pyramid()

Next, we removed the method build_wall() and then created a new method called build_pyramid() on line 899. The build_pyramid() methods is shown below:

    def build_pyramid(self):
        """ Builds a pyramid

        """
        posx = 0
        posy = 0
        posz = -10
        width = 8
        # May sure width is odd number so pyramid ends
        # with a single block
        if width % 2 == 0:
            width = width + 1
        height = (width + 1) / 2
        halfsize = int(math.floor(width/2))
        print("Create solid base")
        self.model.add_blocks((posx-halfsize-2, posy-2, posz-halfsize-2), (posx+halfsize+2, posy-2, posz+halfsize+2), GRASS)
        self.model.add_blocks((posx-halfsize-2, posy-1,posz-halfsize-2), (posx+halfsize+2, posy-1, posz+halfsize+2), SAND)

        # Create solid Pyramid
        print("Create Pyramid")
        for y in range(int(posy), int(posy+height)):
            self.model.add_blocks((posx-halfsize, y, posz-halfsize), (posx+halfsize, y, posz+halfsize), SAND)
            halfsize = halfsize - 1

Basically, the build_pyramid() method lays a square that has a width of -1 for every layer it creates. The add_blocks() method is employed to make the build_pyramid() method simpler. Now, when the user presses the p key, the following result should be shown:

_images/building_automatically_pyramid.png

Note, the pyramid will not be placed where the reticle on your screen is pointing. It will be constructed at the x, y, and z coordinates placed under the build_pyramid function as defined at the top of the function by the variables posx, posy, and posz.