Опубликован: 12.07.2013 | Доступ: свободный | Студентов: 735 / 20 | Длительность: 37:41:00
Лекция 13:

Sonar Treasure Hunt

< Лекция 12 || Лекция 13: 12345 || Лекция 14 >

How the Code Works: Lines 94 to 162

The last few functions we need are to let the player enter their move on the game board, ask the player if he wants to play again (this will be called at the end of the game), and print the instructions for the game on the screen (this will be called at the beginning of the game).

Getting the Player's Move

94.  def enterPlayerMove():
95.      # Let the player type in her move. Return a two-item list of int xy coordinates.
96.      print('Where do you want to drop the next sonar device? (0-59 0-14) (or type quit)')
97.      while True:
98.          move = input ()
99.           if move.lower() == 'quit':
100.               print('Thanks for playing!')
101.               sys.exit()

This function collects the XY coordinates of the player's next move. It has a while loop so that it will keep asking the player for her next move. The player can also type in quit in order to quit the game. In that case, we call the sys.exit() function which immediately terminates the program.

103.          move = move.split()
104.           if len(move) == 2 and move[0] .isdigit() and move [1].isdigit() and isValidMove(int(move[0]), int(move
[1]) ) : 
105.                                return [int(move[0]) , int(move[1])]
106.                       print('Enter a number from 0 to 59, a space, then
a number from 0 to 14.')

Assuming the player has not typed in 'quit', we call the split() method on move and set the list it returns as the new value of move. What we expect move to be is a list of two numbers. These numbers will be strings, because the split() method returns a list of strings. But we can convert these to integers with the int() function.

If the player typed in something like '1 2 3', then the list returned by split() would be ['1', '2', '3']. In that case, the expression len(move) == 2 would be False and the entire expression immediately evaluates to False (because of expression short-circuiting.)

If the list returned by split() does have a length of 2, then it will have a move[0] and move[1]. We call the string method isdigit() on those strings. isdigit() will return True if the string consists solely of numbers. Otherwise it returns False. Try typing the following into the interactive shell:

>>> '42'.isdigit()
>>> 'forty'.isdigit()
>>> ''.isdigit()
>>> 'hello'.isdigit()
>>> x = '10'
>>> x.isdigit()

As you can see, both move[0].isdigit() and move[1].isdigit() must be True. The final part of this expression calls our move[1] function to check if the XY coordinates exist on the board. If all these expressions are True, then this function returns a two-integer list of the XY coordinates. Otherwise, the player will be asked to enter coordinates again.

Asking the Player to Play Again

109. def playAgain() :
110.     # This function returns True if the player wants to play again, otherwise it returns False.
111.     print('Do you want to play again? (yes or no)')
112.     return input().lower().startswith('y')

The playAgain() function will ask the player if they want to play again, and will keep asking until the player types in a string that begins with 'y'. This function returns a Boolean value.

Printing the Game Instructions for the Player

115. def showInstructions():
116.     print('''Instructions:
117. You are the captain of the Simon, a treasure-hunting ship. Your current mission
118.  is to find the three sunken treasure chests that are lurking in the part of the
119. ocean you are in and collect them. 
121. To play, enter the coordinates of the point in the ocean
you wish to drop a
122.  sonar device. The sonar can find out how far away the closest chest is to it.
123.  For example, the d below marks where the device was dropped, and the 2's
124.  represent distances of 2 away from the device. The 4's represent
125.  distances of 4 away from the device.
127.     444444444
128.     4       4
129.     4 22222 4 13 0 .     4 2   2 4
131.     4 2 d 2 4
132.     4 2   2 4
133.     4 22222 4
134.     4       4
135.     444444444
136. Press enter to continue...''') 
137.     input()

The showInstructions() is just a couple of print() calls that print multi-line strings. The input() function just gives the player a chance to press Enter before printing the next string. This is because the screen can only show 25 lines of text at a time.

139.      print('''For example, here is a treasure chest (the c) located a distance of 2 away
140.  from the sonar device (the d):
142.             22222
143.             c   2
144.             2 d 2
145.             2   2
146.             22222
148. The point where the device was dropped will be marked with a 2 . 
150.  The treasure chests don't move around. Sonar devices can detect treasure
151.  chests up to a distance of 9. If all chests are out of range, the point
152.  will be marked with O 153 .
154.  If a device is directly dropped on a treasure chest, you have discovered
155.  the location of the chest, and it will be collected. The sonar device will
156.  remain there. 
158. When you collect a chest, all sonar devices will update to locate the next
159.  closest sunken treasure chest.
160.  Press enter to continue...''')
161.      input() 
162 .     print()

This is the rest of the instructions in one multi-line string. After the player presses Enter, the function returns. These are all of the functions we will define for our game. The rest of the program is the main part of our game.

How the Code Works: Lines 165 to 217

Now that we are done writing all of the functions our game will need, let's start the main part of the program.

The Start of the Game

165.  print('S O N A R !')
166.  print()
167.  print('Would you like to view the instructions? (yes/no)') 
168.    if input () .lower() .startswith('y') :
169.     showInstructions()

The expression input().lower().startswith('y') asks the player if they wan to see the instructions, and evaluates to True if the player typed in a string that began with 'y' or 'Y'. If so, showInstructions() is called.

171.  while True:
172.      # game setup
173 .             sonarDevices = 16
174.      theBoard = getNewBoard()
175.      theChests = getRandomChests(3)
176.      drawBoard(theBoard)
177.      previousMoves = []

This while loop is the main game loop. Here are what the variables are for:

Таблица 13-2. Variables used in the main game loop.
Variable Description
sonarDevices The number of sonar devices (and turns) the player has left.
theBoard The board data structure we will use for this game. getNewBoard () will set us up with a fresh board.
theChests The list of chest data structures. getRandomChests() will return a list of three treasure chests at random places on the board.
previousMoves A list of all the XY moves that the player has made in the game.

Displaying the Game Status for the Player

179.     while sonarDevices > 0:
180.         # Start of a turn:
182.         # show sonar device/chest status
183.          if sonarDevices > 1: extraSsonar = 's'
184.         else: extraSsonar = ''
185.          if len(theChests) > 1: extraSchest = 's'
186.         else: extraSchest = ''
187.         print('You have %s sonar device%s left. %s treasure chest%s remaining.' % 
 (sonarDevices, extraSsonar, len(theChests), extraSchest))

This while loop executes as long as the player has sonar devices remaining. We want to print a message telling the user how many sonar devices and treasure chests are left. But there is a problem. If there are two or more sonar devices left, we want to print '2 sonar devices'. But if there is only one sonar device left, we want to print '1 sonar device' left. We only want the plural form of devices if there are multiple sonar devices. The same goes for '2 treasure chests' and '1 treasure chest'.

Notice on lines 183 through 186 that we have code after the if and else statements' colon. This is perfectly valid Python. Instead of having a block of code after the statement, instead you can just use the rest of the same line to make your code more concise. (Of course, this means you can only have one line of code for the if-block and else-block.) This applies to any statement that uses colons, including while and for loops.

So we have two string variables named extraSsonar and extraSchest, which are set to ' ' (space) if there are multiple sonar devices or treasures chests. Otherwise, they are blank. We use them in the while statement on line 187.

Getting the Player's Move

189.         x, y = enterPlayerMove()
190.         previousMoves.append([x, y]) # we must track all moves so that sonar devices can be updated.
192.         moveResult = makeMove(theBoard, theChests, x, y)
193.          if moveResult == False:
194.             continue

Line 189 uses the multiple assignment trick. enterPlayerMove() returns a two-item list. The first item will be stored in the x variable and the second will be stored in the y variable. We then put these two variables into another two-item list, which we store in the previousMoves list with the append() method. This means previousMoves is a list of XY coordinates of each move the player makes in this game.

The x and y variables, along with theBoard and theChests (which represent the current state of the game board) are all sent to the makeMove() function. As we have already seen, this function will make the necessary modifications to the game board. If makeMove() returns the value False, then there was a problem with the x and y values we passed it. The continue statement will send the execution back to the start of the while loop that began on line 179 to ask the player for XY coordinates again.

Finding a Sunken Treasure Chest

195.         else:
196.              if moveResult == 'You have found a sunken treasure chest!' :
197.                 # update all the sonar devices currently on the map.
198.                  for x, y in previousMoves:
199.                     makeMove(theBoard, theChests, x, y)
200.             drawBoard(theBoard)
201.             print(moveResult)

If makeMove() did not return the value False, it would have returned a string that tells us what were the results of that move. If this string was 'You have found a sunken treasure chest!', then that means we should update all the sonar devices on the board so they detect the second closest treasure chest on the board. We have the XY coordinates of all the sonar devices currently on the board stored in previousMoves. So we can just pass all of these XY coordinates to the makeMove() function again to have it redraw the values on the board.

We don't have to worry about this call to makeMove() having errors, because we already know all the XY coordinates in previousMoves are valid. We also know that this call to makeMove() won't find any new treasure chests, because they would have already been removed from the board when that move was first made.

The for loop on line 198 also uses the same multiple assignment trick for x and y because the items in previousMoves list are themselves two-item lists. Because we don't print anything here, the player doesn't realize we are redoing all of the previous moves. It just appears that the board has been entirely updated.

Checking if the Player has Won

203.                     if len(theChests) == 0:
204.             print('You have found all the sunken treasure chests! Congratulations and good game!')
205.             break

Remember that the makeMove() function modifies the theChests list we send it. Because theChests is a list, any changes made to it inside the function will persist after execution returns from the function. makeMove() removes items from theChests when treasure chests are found, so eventually (if the player guesses correctly) all of the treasure chests will have been removed. (Remember, by "treasure chest" we mean the two-item lists of the XY coordinates inside the theChests list.)

When all the treasure chests have been found on the board and removed from theChests, the theChests list will have a length of 0. When that happens, we display a congratulations to the player, and then execute a break statement to break out of this while loop. Execution will then move down to line 209 (the first line after the while-block.)

Checking if the Player has Lost

207.        sonarDevices -= 1

This is the last line of the while loop that started on line 179. We decrement the sonarDevices variable because the player has used one. If the player keeps missing the treasure chests, eventually sonarDevices will be reduced to 0. After this line, execution jumps back up to line 179 so we can re-evaluate the while statement's condition (which is sonarDevices > 0). If sonarDevices is 0, then the condition will be False and execution will continue outside the while-block on line 209.

But until then, the condition will remain True and the player can keep making guesses.

209.      if sonarDevices == 0:
210.         print('We\'ve run out of sonar devices! Now we have to turn the ship around and head')
211.         print('for home with treasure chests still out there! Game over.')
212.         print('    The remaining chests were here:')
213.          for x, y in theChests:
214.             print('    %s, %s' % (x, y))

Line 209 is the first line outside the while loop. By this point the game is over. But how do we tell if the player won or not? The only two places where the program execution would have left the while loop is on line 179 if the condition failed. In that case, sonarDevices would be 0 and the player would have lost.

The second place is the break statement on line 205. That statement is executed if the player has found all the treasure chests before running out of sonar devices. In that case, sonarDevices would be some value greater than 0.

Lines 210 to 212 will tell the player they've lost. The for loop on line 213 will go through the treasure chests remaining in theChests and show their location to the player so that they know where the treasure chests had been lurking.

Asking the Player to Play Again, and the sys.exit() Function

216.      if not playAgain() :
217.          sys.exit()

Win or lose, we call the playAgain() function to let the player type in whether they want to keep playing or not. If not, then playAgain() returns False. The not operator changes this to True, making the if statement's condition True and the sys.exit() function is executed. This will cause the program to terminate.

Otherwise, execution jumps back to the beginning of the while loop on line 171.

Summary: Review of our Sonar Game

Remember how our Tic Tac Toe game numbered the spaces on the Tic Tac Toe board 1 through 9? This sort of coordinate system might have been okay for a board with less than ten spaces. But the Sonar board has nine hundred spaces! The Cartesian coordinate system we learned in the last chapter really makes all these spaces manageable, especially when our game needs to find the distance between two points on the board.

Game boards in games that use a Cartesian coordinate system are often stored in a list of lists so that the first index is the x-coordinate and the second index is the y-coordinate. This make accessing a coordinates look like board[x][y].

These data structures (such as the ones used for the ocean and locations of the treasure chests) make it possible to have complicated concepts represented as data in our program, and our game programs become mostly about modifying these data structures.

In the next chapter, we will be representing letters as numbers using their ASCII numbers. (This is the same ASCII term we used in "ASCII art" previously.) By representing text as numbers, we can perform mathematically operations on them which will encrypt or decrypt secret messages.

< Лекция 12 || Лекция 13: 12345 || Лекция 14 >