Python is one of the most widely used dynamic programming languages. It supports a rich set of packages, GUI libraries, and web frameworks that enable you to build efficient cross-platform applications. It is an ideal language for rapid application development. Such fast-paced development often comes with its own baggage that could bring down the overall quality, performance, and extensibility of the code. This book will show you ways to handle such situations and help you develop better Python applications. The key concepts will be explained with the help of command-line applications, which will be progressively improved in subsequent chapters.
This chapter will be an introductory one. It will serve as a refresher to Python programming. That being said, it is expected you have some knowledge of Python language, as well as object-oriented programming (OOP) concepts.
Here is how this chapter is organized:
We will start with installation prerequisites and set up a proper environment for Python development.
To set the tone right for the rest of the book, the next section will be a brief introduction to the high fantasy theme of the book.
What follows next is our first program. It is a simple text-based fantasy game, presented as a Python script.
We will add some complexity to this game and develop an incremental version of the game using simple functions.
Moving ahead, we will add more features to the game and redesign the code by applying OOP concepts.
The code explanation will be a bit verbose. More experienced readers can breeze past the examples and go to the next chapter, but be sure to understand the theme of the book and review the code in the
ch01_ex03.py file. In the next few chapters, you will learn techniques to progressively improve this code.
Before diving into the rest of the chapter, let's get some housekeeping out of the way. If you haven't already, you should read the Preface, which documents most of the following things:
Every chapter will have its own set of Python source files. Although we will talk through most of the code, you should keep the relevant files at hand.
The source code can be downloaded from the Packt Publishing website. Follow the instructions mentioned in the Preface.
The code illustrated in this book is compatible with Python version 3.5.1. The supporting code bundles also provide files compatible with version 2.7.9.
As noted before, it is assumed that you are familiar with basics of the Python language and know OOP concepts.
The book uses a fun, text-based game theme as a vehicle to explain various application development aspects. However, the book itself is not about developing game applications!
The solutions to the exercises (if any) are generally not provided.
The book provides several external links (URLs) for further reading. Over time, some of these links might end up being broken. If that ever happens, try searching the web with appropriate search terms.
Let's make sure that we have installed the prerequisites. Here is a table that summarizes the basic tools we need for this chapter and beyond; more verbose installation instructions follow in the next section:
The code illustrated in this book is compatible with version 3.5. See the next table for available Python distributions. Supporting code bundles also provide 2.7.9 compatible files.
pip (package manager for Python)
The pip is already available in the official distribution for versions 3.5 and 2.7.9.
Optional installation. IPython is an enhanced Python interpreter.
Integrated development environment (IDE)
Use the Python editor or any IDE of your choice. Some good IDEs are listed in a table later in this chapter.
In subsequent chapters, we will need to install some additional dependencies. The Python package manager (pip) will makes this a trivial task.
Have you already set up the required Python environment or know how to do it? Just skip the setup instructions that follow and move on to the The theme of the book section, where the real action begins!
For Linux or Mac users, Python is probably already installed on your system. If not, you can install it using the package manager of your operating system. Windows OS users can install Python 3.5 by downloading the Python installer from the official Python website:
During the installation process, just make sure to select the option that adds Python 3.5 to the system environment variable,
PATH, as shown in the preceding screenshot. You can also visit the official Python website, https://www.python.org/downloads, to get the platform-specific distribution.
Alternatively, there are several freely available Python distributions that bundle together useful Python packages, including pip and IPython. The following table summarizes some of the most popular Python distributions, including the official one:
Windows, Linux, Mac
Let's briefly talk about the path where Python is installed, and how to make sure
python is available as a command in your terminal window. Of course, things will widely vary, depending on where you install it and which Python distribution you choose.
The official Python documentation page has comprehensive information on setting up the Python environment on different platforms. Here is a link, in case you need further help beyond what we have covered: https://docs.python.org/3/using/index.html.
On a Unix-like operating system such as Linux, the default location is typically
If you used your operating system's package manager to install Python, the command
python3 should be available in the terminal window. If it isn't, you need to update the
PATH system environment variable to include the directory path to the Python executable. For example, if you have a
Bash shell, add the following to the
.bashrc file in your user home directory:
Specify the actual path to your Python installation in place of
On Windows OS, the default Python installation path is typically the following directory:
name with your Windows username. Depending on your installer and system, the Python directory can also be
Python35-64. As mentioned earlier, at the time of installation, you should select the option Add Python 3.5 to PATH to make sure
python.exe are automatically recognized as commands. Alternatively, you can rerun the installer with just this option checked.
Open a terminal window (or command prompt on Windows OS) and type the following command to verify the Python version. This command will work if Python is installed and is available as a command in the terminal window. Otherwise, specify the full path to the Python executable. For instance, on Linux you can specify it as
/usr/bin/python, if Python is installed in
$ python -V
Note that the
$ sign in the previous command line belongs to the terminal window and is not part of the command itself! Put another way, the actual command is just
python -V. The
% sign in the terminal window is a prompt for a normal user on Linux. For a root (admin) user, the sign is
#. Likewise, on Windows OS, the corresponding symbol is
>. You will type the actual command after this symbol.
The following is just a sample output, if we run the preceding command:
[[email protected] ~]$ python -V Python 3.5.1 :: Anaconda 4.0.0 (64-bit)
The pip is a software package manager that makes it trivial to install Python packages from the official third party software repository, PyPI. The pip is already installed for Python-2 version 2.7.9 or higher and Python-3 version 3.4 or higher. If you are using a different Python version, check out https://pip.pypa.io/en/stable/installing for the installation instructions.
On Linux OS, the default location for the pip is same as that of the Python executable. For example, if you have
/usr/bin/python, then pip should be available as
/usr/bin/pip. On Windows OS, the default
pip.exe is typically the following:
C:\Users\name\AppData\Local\Programs\Python\Python35-32\Scripts\pip.exe. As mentioned earlier, replace
name with your Windows username. Depending on your installer and the system, the Python directory can also be
$ pip install ipython
After the installation, just type
ipython in the terminal to start the IPython interactive shell. Here is a screenshot of the IPython shell using the Anaconda Python 3.5 distribution:
It is often very convenient to use the Jupyter Notebook to write and share interactive programs. It is a web application that enables an interactive environment for writing Python code alongside rich text, images, plots, and so on. For further details, check out the project homepage at http://jupyter.org/. The Jupyter Notebook can be installed with:
$ pip install "ipython[notebook]"
Using an IDE for development is a matter of personal preference. Simply put, an IDE is a tool intended to accelerate application development. It enables developers to write efficient code quickly by integrating the most common tools they need. The Python installation comes with a program called IDLE. It is a basic IDE for Python, which should get you started. For advanced development, you can choose from a number of freely or commercially available tools. Any good Python IDE has the following minimum features:
A source code editor with code completion and syntax highlighting features
A code browser to browse through files, projects, functions, and classes
A debugger to interactively identify problems
A version control system integration such as Git
You can get started by trying out one of the freely available IDEs. Here is a partial list of popular IDEs. If you are just interested in a simple source code editor, you can check out https://wiki.python.org/moin/PythonEditors, for a list of available choices.
PyCharm Community Edition
Wing IDE 101
Sublime Text 2 or Sublime Text 3 (beta)
Have you read high fantasy novels, such as The Lord of the Rings or The Hobbit by J. R. R. Tolkien? Or watched the films based on these novels? Well, here is a high fantasy, "Tolkienesque" themed book on Python application development.
To find out more about J.R.R. Tolkien's work, see https://en.wikipedia.org/wiki/J._R._R._Tolkien. The term high fantasy is often used to represent a fantasy theme set in an alternate fictional world. Check out https://en.wikipedia.org/wiki/High_fantasy for more information.
This book takes you to an imaginary world where you will develop a text game based on the aforementioned theme. Yes, you can continue being a developer even in this imaginary world! During the course of the book, you will be accompanied by many fictional characters. While you learn different aspects of Python development, these characters will talk to you, ask questions, request new features, and even fight with the enemy.
It should be noted that this book is not about developing game applications. It uses a simple text-based game just as a medium to learn various development aspects.
Off topic, if you are interested in playing a high fantasy theme game, there are quite a few to choose from. Among the open source ones, Battle for Wesnoth is one of the most highly rated, free, turn-based strategy games with a high fantasy theme. Check out https://www.wesnoth.org, for more details.
Let's meet the imaginary characters who will accompany you in various chapters:
An Elf is a supernatural mythical being. The Elf is mounted on an elvish horse. He is portrayed as a friendly. You will meet Mr. Elf in Chapter 6, Design Patterns.
An intelligent fairy with an inherent capability for magic. She will use her magic just once while finding her enchanted locket in Chapter 7, Performance Identifying Bottlenecks, (See O(log n)). You will first meet her in Chapter 6, Design Patterns.
A Dwarf is a small human-like mythical being. He is portrayed as "The Great Dwarf" of the Foo mountains. He asks lots of questions. You will see him in the second half of the book, starting with Chapter 6, Design Patterns.
With this fun theme as a vehicle, let's start our journey with a simple command-line application. It will be a text-based game. The complexities added in subsequent chapters will challenge you with interesting problems. The book will show you how to gracefully handle such situations.
We have the required tools and the environment set up. It is now time to write our first Python program. It will be a simple game of chance, developed as a command-line application. As we advance further, we will add more complexity to the game and learn new techniques to develop efficient applications. So, get ready for action!
The war between humans and their arch enemies, the Orcs, was in the offing. A huge army of Orcs was heading toward the human establishments. They were virtually destroying everything in their way. The great kings of the human race joined hands to defeat their worst enemy for the great battle of their time. Men were summoned to join the rest of the army. Sir Foo, one of the brave knights guarding the southern plains, began a long journey toward the east, through an unknown dense forest. For two days and two nights, he moved cautiously through the thick woods. On his way, he spotted a small isolated settlement. Tired and hoping to replenish his food stock, he decided to take a detour. As he approached the village, he saw five huts. There was no one to be seen around. Hesitantly, he decided to enter a hut...
You are designing a simple game in which the player is required to choose a hut for Sir Foo. The huts are randomly occupied either by a friend or an enemy. It is also possible that some huts remain unoccupied. If the chosen one turns out to be an enemy hut, the player loses. In the other two scenarios, the player wins.
While the user wishes to keep playing the game:
Print the game mission
'unoccupied'in 5 huts
Prompt the player to select a hut number
if enemy: print
As you will notice, the key piece of the code is to randomly occupy the five huts with either enemy or friend and keep the remaining ones unoccupied. How do we do this? Let's quickly work this out using the Python interpreter. If you have installed IPython, start the IPython interpreter. Otherwise, just use the default Python interpreter by typing the command
python in a terminal window. First, we need a Python list to hold all the occupant types. Next, we will use the built-in
random module and call
random.choice to pick one element randomly from this list. This code is shown in the following screen capture:
Now, we just need to write the surrounding code. Let's review it next.
Download the source code,
ch01_ex01.py, from the supplementary code bundle provided for this chapter. The file extension,
.py, indicates that it is a Python file. Open it in a Python editor or an IDE of your choice. It is recommended that you keep this file handy while reading the following discussion. It is often easier to glance at the full code to understand it better. Observe the following code snippet. It is just a small portion of the code inside the
if __name__ == '__main__' condition block in the aforementioned file.
If you have Python 2.7.9 installed, there is a separate Python 2.7.9 compatible source provided in the supporting code bundle.
The first two lines import two built-in modules to gain access to the functionality provided within these modules. The
textwrapmodule essentially provides features to nicely format the messages printed on the command line.
if __name__ == '__main__', is invoked only when the file is run as a standalone script. In other words, the code inside this condition block won't be executed if you import this file in some other file.
Now, let's look at the code in this condition block. First, we will initialize a few variables. As demonstrated earlier, the list
occupantsstores the potential occupant types for the hut.
The last few lines are just to format the text printed in the terminal window. The
dotted_lineis a string that will show a 72-character long line with hyphen symbols.
The ASCII escape sequence is used to print the text in bold. The sequence
"\033[1m"is to make bold text, and
"\033[0m"is to go back to normal printing style.
The next few lines essentially print further information about the game in the console:
msgis a very long string. This is where the
textwrapmodule is used.
textwrap.fillfunction wraps the message in such a way that each line is 72 characters long, as specified by the
widthin our code.
Now, let's review the following
For Python 2.7.9, the only change required in the first example is to replace all the calls to the built-in function
# For Python 2.7 user_choice = raw_input(msg)
random.choice, we randomly pick an occupant from the list of
occupantsand add it to the
hutslist. This was illustrated earlier.
inputfunction accepts a hut number of the user's choice as an integer. The
idxvariable stores a number.
Next, it reveals the occupants by printing related information. Finally, it determines the winner by checking the list item corresponding to the hut number. Note that the
huts list index starts at 0. Therefore, to retrieve the list element for a given hut number,
idx, we need to check the list index at
$ python ch01_ex01.py
That's all! Just play the game and try to save Sir Foo by choosing the right hut! The following snapshot of a Linux terminal window shows our game in action:
In the last section, you wrote a quick set of instructions to create a nice little command-line game. You asked your friends to try it out and they kind of liked it (perhaps they were just trying to be nice!). You received the first feature request for the game.
"I think this game has good potential to grow. How about including combat in the next version of the game? When Sir Foo encounters an enemy, he should not just give up that easily. Fight with the enemy! Let the combat decide the winner. "-your friend
You liked the idea and decided to add this capability to the code in the next version. Additionally, you also want to make it more interactive.
The script you wrote for the first program was small. However, as we go on adding new features, it will soon become a maintenance headache. As a step further, we will wrap the existing code into small functions so that the code is easier to manage. In functional programming, the focus is typically on function arrangement and their composition. For example, you can build complicated logic using a simple set of reusable functions.
Before adding any new features, let's revisit the script that you wrote in the previous version (version 0.0.1). We will identify the blocks of code that can be wrapped into functions. Such code chunks are marked in the two code snippets that follow:
We will wrap most of the highlighted code into individual functions, as follows:
1: show_theme_message 2: show_game_mission 3: occupy_huts 4: process_user_choice 5: reveal_occupants 6: enter_hut
In addition to these six blocks of code, we can also create a few top-level functions to handle all this logic. In Python, the function is created using the
def keyword, followed by the function name and arguments in parentheses. For example, the
reveal_occupants function requires the information about the
huts list. We also need to optionally pass the
dotted_line string if we do not want to recreate it in the function. So, we will pass the hut number
huts list, and the
dotted_line string as function arguments. This function can be written as follows:
After this initial work, the original script can be rewritten as:
This is much easier to read now. What we just did is also referred to as refactoring; more on various refactoring techniques in a later chapter. It makes it easier to do changes to the individual methods. For example, if you want to customize the mission statement or scenario description, you do not need to open the main function,
occupy_huts can be expanded further without any clutter in the main code.
In the previous section, we wrapped the game logic into individual functions. This not only improved the code readability, but also made it easier to maintain. Let's move on and include the new
attack() function in the game. The following steps show the logic of the game with the attack feature included.
While the user wishes to keep playing the game:
Print game mission
'unoccupied'in 5 huts
Prompt the player to select a hut number
ifthe hut has an enemy, do the following:
whilethe user wishes to continue the attack, use the
attack()method on the enemy
After each attack, update and show the health of Sir Foo, and of the enemy too;
if enemy health <= 0:print
if Sir Foo health <= 0:print
else(hut has a friend or is unoccupied) print
Initially, Sir Foo and the Orc will have full health. To quantify health, let's assign hit points to each of these characters (or the game units). So, when we say the character has full health, it means it has the maximum possible hit points. Depending on the character, the default number of hit points will vary. The following image shows Sir Foo and the Orc with the default number of hit points, indicated by the Health label:
The bar above the Health label in the image represents a health meter. Essentially, it keeps track of the hit points. In the discussion that follows, we will use the terms hit points and health meter interchangeably. During the combat, either the player or the enemy will get injured. For now, neglect the third possibility where both escape unhurt. An injury will reduce the number of available hit points for the injured unit. In the game, we will assume that in a single attack turn only one of the characters is hit. The following image will help you imagine one such attack turn:
Here, Sir Foo's health meter is shown as the maximum and the Orc has sustained injuries!
Hmm, the Orc thinks he can defeat Sir Foo! This is interesting. Let's develop the game first and then see who has a better chance of winning!
Download the source file,
ch01_ex02.py, from the chapter's code bundle and skim through the code. The key logic will be in the
attack() function. We will also need a data structure to keep the health record of Sir Foo and the enemy. Let's start by introducing the following utility functions that take care of some print business:
Now, look at the main function,
run_application, and the supporting function,
reset_health_meter. In addition to introducing the dictionary
health_meter, we have also encapsulated the game logic in
At the start of a new game, the values of the
health_meter dictionary are set back to the initial ones by calling
Next, let's review the
play_game function. If the hut has the enemy, the player will be asked if the attack should be continued (the start of the
while loop). Based on the user input, the code calls the
attack function or exits the current game:
The enemy is attacked repetitively using the interactive
while loop, which accepts user input. Execution of the
attack function may result in injury to Sir Foo, or the enemy, or both. It is also possible that no one gets hurt. For simplicity, we will only consider two possibilities: a single attack that will injure either the enemy or Sir Foo. In the previous section, we used the built-in random number generator to randomly determine the occupants of the huts. We can use the same technique to determine who gets hurt:
injured_unit = random.choice(['player', 'enemy'])
But hold on a minute. Sir Foo has something to say:
We should take into account the chance of an injury to the player and to the enemy. In the
attack function shown next, we will assume that for about 60% of the time, the enemy will get hit and for the remaining 40%, it is Sir Foo who is on the receiving end.
The simplest way is to create a list with 10 elements. This list should have six entries of
'enemy' and four entries of
'player'. Then, let
random.choice select an element from this list. You can always introduce a difficulty level in the game and change this distribution:
injured_unit is selected randomly, the
injury is determined by picking a random number between
15, inclusive. Here, we use the
random.randint function. The final important step is to update the
health_meter dictionary for the injured unit by reducing its number of hit points.
The attack feature that you added in the previous game has made it a lot more interesting. You can see some friends coming back again and again to play the game. The new feature requests have started pouring in.
Here is a partial list of the requested features:
New mission to acquire all the huts and defeat all the enemies. This also means the hut occupants should be revealed right at the beginning of the game.
Ability to get healed in a friendly or unoccupied hut.
Ability to abandon combat (or run away from the enemy). This is a strategic move to run away, get healed in a friendly hut, and resume combat.
Introduce one or more horse riders to assist Sir Foo. They can take turns to acquire huts. Ideally, a user-configurable option.
Ability to configure the maximum hit points for each enemy unit and each of the horse riders.
Configurable total number of huts; for example, increase it to 10.
Each hut can have either some gold or a weapon inside that Sir Foo and his friends can pick up.
This is quite a long list. You are preparing a plan. Here is a partial list of things you will need to add to the existing code to implement some of these features:
Keeping track of the hit points of multiple enemy units occupying various huts
Maintaining the health record of Sir Foo and all accompanying horse riders
Monitoring how many huts are acquired by Sir Foo's army
Another dictionary or list to keep track of the gold in each hut, and another one for weapons; additionally, what if someone wants to put armor in the hut?
Not to forget, yet another list of dictionary for each unit that accepts any of these goodies
Ah! So they want an elf rider with its own traits and abilities...nice...thanks for the additional trouble!
That is already a long list. While you could still continue to use the functional programming approach, in such scenarios it will get tougher as the game evolves and new features get added.
Thankfully, object-oriented programming comes to the rescue. How about making Sir Foo an instance of a
class? With this, it should be easy to manage parameters relevant to Sir Foo. For example, an attribute,
hitpoints, can be used to keep track of Sir Foo's health instead of using the
health_meter dictionary in the earlier example. Similarly, the other attributes in the class can keep track of the amount of gold or weapons collected while acquiring the huts (another requested feature).
There is a lot more beyond this bookkeeping. The various methods of the class would enable a specific implementation of behaviors, such as attack, run, heal, and so on. The horse riders accompanying Sir Foo can also be instances of the class
Knight. Alternatively, you can create a new class called
HorseRider for all these units that accept commands from Sir Foo.
As you wish, Sir Foo...we will only add the new heal feature in this version.
It is now time to clearly define the targets for this release. You are not just adding new features to your application, but also making some fundamental changes to the code to accommodate future requests.
In this version, the mission is to acquire all of the five huts. Here, you will implement a new
heal feature to regain all the hit points for Sir Foo. You will also implement some strategic controls, such as running away from combat, getting healed in a friendly hut, and then returning rejuvenated to defeat the enemy.
What other classes can be carved out? How about having the enemy as an object? The enemy could occupy multiple huts. Remember that we need to defeat all the enemies. Imagine the following scenario: Sir Foo injures an enemy in hut number 2, thereby reducing its hit points. Then, he moves on to another hut occupied by another enemy. Now, we need to maintain two separate hit point counters for each of these enemy units.
In a future version, you can expect users to ask for different enemy types with the ability to attack or heal, just like how we have it for Sir Foo. So, at this point, it makes sense to have a separate class, instances of which represent the enemy units. We will name this class
OrcRider. It will have similar attributes to the
Knight class. However, for simplicity, we will not give the enemy capabilities such as healing, changing huts, and so on.
Sir Foo says he is delighted to read that the enemy has been denied some important capabilities. (But you can't see his happy face behind the helm.)
There is something else we should consider. So far,
huts was just a simple Python
list object holding information about the occupant types as strings.
Looking at the requested features list, we also need bookkeeping for the amount of gold and armor in the hut and to update its occupant, depending on the result of the fight. In a future version, you may also want to show some statistics, such as a historic record of the occupants, changes in the amount of gold, and so on. For all this and more, we will create a class,
Take a pen and paper and write down the important attributes we need for each class discussed so far. At this point, do not worry about classifying whether it is an instance variable or a class method that encapsulates instructions to perform specific tasks. Just write down what you think belongs to each class.
The following schematic shows a list of potential attributes for the
OrcRider classes. The attribute names in strikethrough text indicate the potential attributes that won't be implemented in this illustration. But, it is always good to think ahead and keep it at the back of your mind during the design phase of the application:
This is not a complete specification, but we have a good starting point now. When Sir Foo enters an enemy hut, we have a choice to call the
attack method of the
Knight class. As before, the
attack method will randomly pick who gets injured and deduct the hit points for that character. In the
Knight class, it is convenient to have a new attribute,
enemy, that will represent the active opponent. In this example,
enemy will be an instance of the
Let's develop this design further. Did you notice that the
OrcRider classes have several things in common? We will use the inheritance principle to create a superclass for these classes, and call it
GameUnit. We will move the common code to the superclass, and let the subclasses override the things they want to implement differently. In the next section, we will represent these classes with a Unified Modeling Language (UML)-like diagram.
The preceding diagram is similar to a UML representation. It helps create a visual representation of a software design. In this book, we will loosely follow the UML representations. Let's call the diagrams used here pseudo UML diagrams (or UML-like diagrams).
An explanation is in order for the UML-like convention used here. We will represent each class in the schematics as a rounded rectangle. It shows the class name followed by its attributes. The plus sign (+) before the attribute indicates that it is public. A protected or private method is generally represented with a negative sign (-). All the attributes shown in this diagram are public attributes. So, optionally, you could add a plus sign next to each attribute. In later chapters, we will follow this convention. For ease of illustration, only a few relevant public attributes will be listed. Observe that we are using different types of connectors in this diagram:
The arrowhead with an empty triangle symbol represents inheritance; for example, the
Knightclass inherits from the
Now, let's talk about the individual components of the diagram presented earlier.
OrcRider classes inherit from
Knight class, in this case, will override default methods, such as
OrcRider class will not have such overridden methods, as we will not give these capabilities to the enemy.
Hut class will have an occupant. The occupant can either be an instance of the
Knight or the
OrcRider, or the
None type if the hut is unoccupied. The filled diamond connector in the diagram indicates composition.
It is an important OOP principle. It implies a has-a relationship. In this case,
Hut contains, or is composed of, some other object that is to be used to perform specific tasks. Just say it out loud; a
OrcRider, and so on.
In addition to the four classes discussed, we will introduce another one to encapsulate the top-level code. Let's call it
AttackOfTheOrcs. As there are five huts, a class method in
AttackOfTheOrcs creates that number of
Hut instances. This is object aggregation, shown by the empty diamond shaped arrow in the preceding diagram.
Have you noticed another has-a relationship in
player attribute in this class is an instance of the
Knight class, but in the future, this could change. This relationship is indicated by the filled diamond-head connector joining the
With this high-level understanding, let's begin developing the code. Download the Python source file,
ch01_ex03.py. We will review only a few important methods in the code. Refer to this source file for the complete code.
The code for this example,
ch01_ex03.py, is all squished inside a single file. Is it good practice? Certainly not! As we go along, you will learn about best practices. Later in the book, we will discuss some important building blocks of application development, namely refactoring, coding standards, and design patterns. As an exercise, try to split the code into smaller modules and add code documentation.
The main execution code is shown here, along with some details of the
AttackOfTheOrcs class. In the
__init__ method, we will initialize some instance variables and later update the values they hold. For example,
self.player represents the instance of the
Knight class when the game begins:
Just as a refresher, the
__init__ method is somewhat similar to a constructor in languages such as C++; however, keep in mind some differences. For example, you cannot overload
__init__ as you might do in these languages. Instead, you can easily accomplish this using optional arguments or the
classmethod decorator. We will cover some aspects later in the book.
self.player is an instance of the
Knight class. We will call the
acquire_hut method of this instance where most of the high-level action happens. After this, the program simply looks for the health parameters of the player and the enemy. It also queries the
Hut instance to see if it is acquired.
Moving ahead, in the
_occupy_hut method, the objects of
Hut are created and appended to the
self.huts list. This method is shown in the following figure:
Public, protected, and private in Python
You will notice that some methods of the
AttackOfTheOrcs class start with an underscore, for example,
_process_user_choice(). That is a way to say that this method is not meant for public use. It is intended to be used from within the class. Languages such as C++ define class access specifiers, namely,
public. These are used to put restrictions on the access of class attributes.
There is no such thing in Python. It allows outside access to the attributes with a single underscore as
game._process_user_choice(). If the attribute name starts with double underscores, you can't call it directly. For example, you can't directly call
game.__process_user_choice(). That being said, there is another way to access such attributes from outside. But let's not talk about it. Although Python allows you to access such attributes, it is is not good practice to do so!
acquire_hut method of the Knight class:
Let's talk through this method next:
First, we need to check whether the hut's occupant is a friend or an enemy. This is determined by the variable
is_enemy, as shown in the preceding figure.
The hut's occupant can be of the following types: an instance of the
Knightclass, an instance of the
OrcRiderclass, or set to
GameUnitclass, and its subclasses
OrcRider, define a
unit_typeattribute. This is just a string that is set as either
Thus, to determine whether there is an enemy hiding in the hut, we will first check whether the
hut.occupantis an instance of the superclass
GameUnit. If true, we will know it has a
unit_typeparameter. So, we will check whether
hut.occupant.unit_typeis equal to
'enemy'. For the
unit_typeis set to
The rest of the logic is simple. If the occupant is an enemy, it asks the user what to do next: attack or run away.
Knight.attackmethod is similar to the one discussed earlier. One change here is that we can access the
health_meterattribute of the injured unit and update it.
hut.occupanthappens to be
None, it calls
acquire method simply updates the
occupant attribute with the object passed as an argument to this method.
It's play time! We have reviewed the most important methods of the new classes. You can review the rest of the code from the
ch01_ex03.py file, or better try to write these methods on your own. Run the application from the command line, like we did earlier. The following screenshot shows the game in action:
In the previous section, we redesigned the code using the OOP approach. We also demonstrated the use of inheritance by defining a superclass
GameUnit, and inheriting from it to create the
OrcRider subclasses. As the last topic in this chapter, let's talk about using abstract base classes in Python.
This section is intended to provide a basic understanding of ABCs in Python. The discussion here is far from being comprehensive but will be just enough to implement an ABC in our application code. For further reading, check out the Python documentation at https://docs.python.org/3/library/abc.html.
If you are familiar with OOP languages such as Java or C++, you probably already know the concept of an ABC.
A base class is a parent class from which other classes can be derived. Similarly, you can have an abstract base class and create other classes that inherit this class. So, where is the difference? One of the major differences is that an ABC can't be instantiated. But that is not the only difference. An ABC forces the derived classes to implement specific methods defined within that class. This much knowledge about an ABC should be good enough to work through the examples in this book. For more details, see the aforementioned Python documentation link. Let's review a simple example that shows how to implement an abstract base class in Python and how it differs from an ordinary base class. The
abc module provides the necessary infrastructure. The following code snippet compares the implementation of an ABC to an ordinary base class:
The class on the left,
GameUnit class on the right is an ordinary base class. The three differences in the ABC implementation are marked with numbers, as shown in the preceding screenshot.
metaclass=ABCMetais used to define
AbstractGameUnitas an ABC.
ABCMetais a metaclass to define the abstract base class. It is a broad discussion topic, but the simplified meaning of a metaclass is as follows: to create an object, we use a class. Likewise, imagine a metaclass as one used to create a class.
A Python decorator provides a simple way to dynamically alter the functionality of a method, a class, or a function. This is a special Python syntax that starts with an @ symbol followed by the decorator name. A decorator is placed directly above the method definition.
@abstractmethodis a decorator that makes the method defined on the next line an abstract method.
The abstract method is the one that the ABC requires all the subclasses to implement. In this case,
Knightsubclass to implement the
info()method. If the subclass does not implement this method, Python simply doesn't instantiate that subclass and will throw
TypeError. You can try this by removing the
Knight.infomethod and running the code.
There is no such restriction if the
Knightclass inherits from an ordinary base class, such as
The code illustrated here is for Python version 3.5. For version 2.7, the syntax is different. Refer to the
ch01_ex03_AbstractBaseClass.py file in the Python2 directory of the supporting material for an equivalent example.
ch01_ex03.py file, you will see some comments. These are intentionally kept to give you an opportunity to improve portions of the code. There is plenty of room for improvement in this code. See if you can rewrite portions of the code to make it more robust. If you prefer a well-defined problem, here is one:
OrcRider classes inherit from the
GameUnit superclass. This exercise is about converting
AbstractGameUnit, an abstract base class. Here is a cheat sheet for you; the skeleton code shown in the following figure is with the Python 3.5 syntax.
Refer to the
In this chapter, we touched upon some introductory concepts in Python to develop a simple command-line application. We first equipped ourselves by setting up a Python development environment.
The first program we wrote was a simple Python script. We soon realized that a simple script would be hard to maintain if more features are added. As a next step, we did a bit of refactoring and wrapped the code inside functions. This improved the code readability and also made it easier to manage. The proposed introduction of more features to the application made us rethink the design. We learned how to transform the code into an object-oriented design and implemented a few of these new features.
And how can we forget Sir Foo! He will accompany us throughout this book.
Is the code developed free from bugs? You might have already noticed some problems while playing the game! In the next chapter, we will see how to make the application more robust by handling exceptions.
The code illustrations that you see in this book are actually image files or code snapshots.
The rendering quality of these images will vary depending on your PDF reader's page display resolution and the zoom level.
If you have trouble clearly reading this code, you may try the following in your PDF or e-book reader:
Set the zoom level to 100%
Use the page display resolution of 96 pixels/inch or similar
If the problem still persists, you can try with a different resolution.
How do you set this resolution? It will depend on your e-book reader. For example, if you are using Adobe Reader, go to Edit | Preferences and then select Page Display from the left panel. You will see Resolution as an option in the right panel. Select 96 pixels/inch or similar and see if that helps render the images better.