Hello World! Function Junction

0
422

By Jeff Kazmierski, Copy Editor

Hello World! Function Junction

Jeff Kazmierski

Copy Editor

Sunburn and heatstroke and dehydration, oh my! Sunburn and heatstroke and dehydration, oh my! With temperatures soaring above 100, and no relief in sight, that’s what you have to look forward to if you’re brave or crazy enough to venture forth from your air-conditioned hovel this summer. I’m back with another installment of Hello World!, designed especially to keep you safe and healthy. Put away the suntan lotion; what you really need is to open up a well-chilled bottle of your favorite libation, fire up the development environment of your choice, crank up the AC and start coding. I hope you didn’t lose any fingers during your Independence Day revelries, ’cause you’re going to need them!

Last week we expanded our Monster Hunt program a little, by limiting the player’s movements and giving the monster the ability to move. This week is all about preparation. Our program is going to get a lot more complicated, as we’ll see in the future columns, so we’re going to get ready for that by encoding all of its major functions in, well, functions.

Recall from Spring that in Python, functions are identified by the keyword ‘def,’ the function name, and a colon. All the code indented after the function definition is part of the function. In C-type languages like Java, Perl and C++, functions or methods are encased in curly braces ( {} ). Python, on the other hand, relies on formatting and indentation. This makes the code much easier to read.

But you already know that. Moving along…

To convert our program from its linear format to a function-based one requires we take a look at what it’s doing and think about where it can be broken apart. If we look at last week’s version, we can see easily that there are six basic functions:

1. it tells the player where he can move

2. it tells the player if the monster is near

3. it tells the player if the exit is near

4. it gets the player’s move

5. it gets the monster’s moves, then moves the monster

6. it determines if the game is over

We won’t spend a lot of time going over the code line by line and explaining it. Instead, we’ll look closely at a few specific sections and examine how they can be converted to modular code. For example, if we look at last week’s program, specifically the code that enables the player and monster to move, we see they’re pretty much the same:

# show available player moves

player_moves = []

for a in range(-2, 3):

next_room = player_loc + a

if next_room < 1 or next_room > 20:

continue

else:

player_moves.append(next_room)

# get the monster’s moves…

monster_moves = []

for a in range(-2, 3):

next_room = monster_loc + a

if next_room < 1 or next_room > 20:

continue

else:

monster_moves.append(next_room)

# move him…

monster_loc = monster_moves[random.randint(0, len(monster_moves)-1)]

# but make sure he doesn’t cover the exit…

if monster_loc == exit_loc:

monster_loc = monster_moves[random.randint(0, len(monster_moves)-1)]

The only real difference is who’s being moved. One goal of modularizing code is to reduce redundancy, and this is a prime candidate. So in our improved version we’ll probably turn this into one function called moves(). The other functions can be defined on their own. We’ll call them player_turn(), monster_turn(), monster_near(), exit_near() and game_over().

Next, we’ve already decided on some variables to be used throughout the program, player_loc, monster_loc, and exit_loc. These are also called global variables.

Java and C programmers will already be familiar with the concept of global variables; these are variables declared at compilation time that are available from any function in the program. If you have a variable named “spam” declared outside every function, any time the program calls that variable name, it uses the value stored there. In Java, at least. In Python, it’s a little different.

In Python, variable named inside functions are considered local unless identified by the keyword global. Then, the variable must have been previously declared. Traditionally, this is done at the beginning of the program to reduce confusion. For example, here’s what the first lines of our new version looks like:

# hunt20.py

# A dungeon crawl program in the spirit of Wumpus, Adventure, Dungeon and

# many other popular text-based games from the 1970’s.

# Version 1.2 adds extended proximity alarm

# adds limit to player movement options

# and makes the monster move after each turn

# Version 2.0 the program is getting complex so now we build functions

# imports

import random

# global variables – these will be used in a variety of functions so

# they need to be declared early in the code

player_loc = 1

exit_loc = random.randint(10,20)

monster_loc = exit_loc

while monster_loc == exit_loc:

monster_loc = random.randint(3, 20)

We can name the variables before or after the import is declared, but usually it’s done after.

The global variables are then called in each function:

# player turn

def player_turn():

global player_loc, monster_loc, exit_loc

The keyword global tells Python to use the global variable values instead of creating new local variables every time the function player_turn() is called. The rest of player_turn() looks like this:

# player turn

def player_turn():

global player_loc, monster_loc, exit_loc

 

print(“You are in room %d” % player_loc)

player_moves = moves(player_loc)

print(“You can move to rooms “, end = ” “)

print(player_moves, “n”)

# get the player’s next move

monster_near()

exit_near()

move = int(input(“Where would you like to go? “))

if move not in player_moves:

move = int(input(“Where would you like to go? “))

return move

Mostly what we’ve done is taken our already-written code and placed it under the control of a function. But that’s not all we’ll do. Functions are also used in programming to reduce code redundancy. In other words, if you have two routines that do pretty much the same thing, but with slightly different variables, you can code them as one function and just send the variable values you need to that function to get the job done.

For example, looking again at last week’s code for the player’s move and monster’s move, we can see they’re pretty much the same. So in our new program we’ll call the function move(). It will receive one argument (variable), which will be either player_loc or monster_loc, depending on which entity is being moved, and return a list of valid moves. It looks like this:

def moves(loc):

moves = []

for a in range(-2, 3):

next_room = loc + a

if next_room < 1 or next_room > 20:

continue

else:

moves.append(next_room)

return moves

The variable moves is a list that will store the valid move values available. The argument variable loc is either player_loc or monster_loc, because we’ll be calling this function to move either the player or the monster.

Ok, I hear you say, but how and when will moves() be used? Take another look at player_turn(), especially this line:

player_moves = moves(player_loc)

In this line we’re telling the program to create a local variable called player_moves, the value of which will be the list returned by the function moves(), to which we pass the global variable player_loc. Inside moves(), player_loc becomes loc, and is used to determine which rooms the player or monster can move to. The function then returns the local variable moves to the calling function, in this case player_moves(), and assigns its values to player_moves. It doesn’t matter that we have a variable with the same name as its function; Python is smart enough to know the difference. If it makes you feel better, go ahead and give them different names.

For most of the rest of the program, translating it into modular (function-based) code from sequential code is fairly straightforward. Define the function, drop the code inside it, and move on. When you’re done, add a main() function to execute the program, and do some testing. So rather than spend a lot of time tediously going over each function in detail, I’ll concentrate on just the ones where major changes happen.

By the way, you don’t have to call your executor function main(). You can call it start(), go(), or even quit() if you want, so long as you can keep it straight. Traditionally, though, programmers usually use main().

Let’s take a look at the function game_over().

def game_over():

global player_loc, monster_loc, exit_loc

 

monster = exit = False

if player_loc == monster_loc:

print(“You were eaten by a monster!”)

monster = True

if player_loc == exit_loc:

print(“You escaped!”)

exit = True

return (monster ^ exit)

At first glance, it looks pretty much like our previous code, but with a few lines added. The variables monster and exit are Booleans, meaning they contain values of either True or False. The function executes pretty much the same as before, but if the player gets eaten monster becomes True, and if he finds the exit, exit becomes True.

The last line is particularly interesting. The little ‘^’ mark is the Boolean operator XOR. What’s that? Glad you asked.

XOR compares two variables, usually Booleans, and returns True if either, but not both, condition is True, and False if both are either True or False. In other words, if monster is True and exit is False, the player gets eaten and the game is over. Likewise, if monster is False and exit is True, the player escapes. On the other hand, if both are false, the game keeps going because the player is still stuck in the cave and he hasn’t met the monster. We’ve already set up the monster’s moves so he can’t be in the same room as the exit, so the final condition (monster = True and exit = True) should be impossible, but if it were, the condition would result in a False return. Confused? Don’t be; it’s quite simple. Here’s a handy chart:

monster exit monster XOR exit

False False False; game continues

False True True; player escapes

True False True; player is eaten

True True False; impossible to achieve

We’ve already seen other Boolean operators like and and or. I could probably have done the same thing using those, but using XOR both simplified the code and gave me an excuse to introduce something new. That’s how I roll.

We’ll also put our introduction and instructions in its own function. That’s easy; just move and format the print statements.

Finally, our main() function goes at the end of the program. Remember, Python code executes sequentially, so all other functions must be loaded into memory before main() can call them, followed by a main() function call at the end:

# main function

def main():

intro()

global player_loc, monster_loc, exit_loc

while not game_over():

player_loc = player_turn()

monster_loc = monster_turn()

if game_over():

break

 

print(“Thank you for playing!”)

again = input(“Another game? ” )

if again[0].lower() == “y”:

main()

else:

exit()

main()

And we’re pretty much done with this week’s exercise. Converting a program from sequential code to modular code isn’t hard; it just takes practice, patience and lots of testing.

Take a close look at this week’s and last week’s programs, and try to discern the differences, similarities and how they both operate. As a “bonus,” this week’s program isn’t perfect. It has some bugs in it; see if you can find and fix them before the next column (that’s programming teacher-speak for “I meant to do that”). Good luck!

 

Monster Hunt 2.0

# hunt20.py

# A dungeon crawl program in the spirit of Wumpus, Adventure, Dungeon and

# many other popular text-based games from the 1970’s.

# Version 1.2 adds extended proximity alarm

# adds limit to player movement options

# and makes the monster move after each turn

# Version 2.0 the program is getting complex so now we build functions

# imports

import random

# global variables – these will be used in a variety of functions so

# they need to be declared early in the code

player_loc = 1

exit_loc = random.randint(10,20)

monster_loc = exit_loc

while monster_loc == exit_loc:

monster_loc = random.randint(3, 20)

# player turn

def player_turn():

global player_loc, monster_loc, exit_loc

 

print(“You are in room %d” % player_loc)

player_moves = moves(player_loc)

print(“You can move to rooms “, end = ” “)

print(player_moves, “n”)

# get the player’s next move

monster_near()

exit_near()

move = int(input(“Where would you like to go? “))

if move not in player_moves:

move = int(input(“Where would you like to go? “))

return move

 

# monster turn

def monster_turn():

global player_loc, monster_loc, exit_loc

 

monster_moves = moves(monster_loc)

monster_loc = monster_moves[random.randint(0, len(monster_moves)-1)]

# but make sure he doesn’t cover the exit…

while monster_loc == exit_loc:

monster_loc = monster_moves[random.randint(0, len(monster_moves)-1)]

return monster_loc

# find the available exits

# receives player_loc or monster_loc as argument and

# returns an array list of available exits

def moves(loc):

moves = []

for a in range(-2, 3):

next_room = loc + a

if next_room < 1 or next_room > 20:

continue

else:

moves.append(next_room)

return moves

 

# determine if the monster is nearby

def monster_near():

global player_loc, monster_loc, exit_loc

 

if abs(player_locmonster_loc) <= 2:

print(“You smell a monster!”)

# and it’s really close…

if abs(player_locmonster_loc) == 1:

print(“That’s one stinky monster!”)

return

# determine if the exit is nearby

def exit_near():

global player_loc, monster_loc, exit_loc

 

if abs(player_locexit_loc) <= 2:

print(“You feel a breeze!”)

# and it’s really close…

if abs(player_locexit_loc) == 1:

print(“You can see a light!”)

return

# did the player escape or was he eaten?

# this function demonstrates the boolean expression XOR (^)

# as a way of determining how the game ends

def game_over():

global player_loc, monster_loc, exit_loc

 

monster = exit = False

if player_loc == monster_loc:

print(“You were eaten by a monster!”)

monster = True

if player_loc == exit_loc:

print(“You escaped!”)

exit = True

return (monster ^ exit)

 

# introduction/instructions

def intro():

print(“tMonster Hunt 1.0″)

print(“You are exploring a cave made up of 20 rooms. Somewhere in the cave”)

print(“is a monster. One of the rooms is an exit.”)

print(“At each turn you can move to any of the rooms in the cave.”)

print(“nIf you get close to the monster, the game will warn you:”)

print(“t”You smell a monster!””)

print(“nIf you get close to the exit, the game will let you know:”)

print(“t”You feel a breeze!””)

print(“nIf you wander into the room with the monster, you’ll be eaten!”)

print(“t”You were eaten by a monster!””)

print(“nYou can move to any room up to two away from your current room.”)

# main function

def main():

intro()

global player_loc, monster_loc, exit_loc

while not game_over():

player_loc = player_turn()

monster_loc = monster_turn()

if game_over():

break

 

print(“Thank you for playing!”)

again = input(“Another game? ” )

if again[0].lower() == “y”:

main()

else:

exit()

main()

Comments

comments