Sonar Treasure Hunt
How the Code Works: Lines 64 to 91
Placing a Move on the Board
64. def makeMove(board, chests, x, y) : 65. # Change the board data structure with a sonar device character. Remove treasure chests 66. # from the chests list as they are found. Return False if this is an invalid move. 67. # Otherwise, return the string of the result of this move. 68. if not isValidMove(x, y): 69. return False
In our Sonar game, the game board is updated to display a number for each sonar device dropped. The number shows how far away the closest treasure chest is. So when the player makes a move by giving the program an X and Y coordinate, we will change the board based on the positions of the treasure chests. This is why our makeMove() function takes four parameters: the game board data structure, the treasure chests data structures, and the X and Y coordinates.
This function will return the False Boolean value if the X and Y coordinates if was passed do not exist on the game board. If isValidMove() returns False, then makeMove() will return False.
If the coordinates land directly on the treasure, makeMove() will return the string 'You have found a sunken treasure chest!'. If the XY coordinates are within a distance of 9 or less of a treasure chest, we return the string 'Treasure detected at a distance of %s from the sonar device.' (where %s is the distance). Otherwise, makeMove() will return the string 'Sonar did not detect anything. All treasure chests out of range.'.
71. smallestDistance = 100 # any chest will be closer than 100. 72. for cx, cy in chests: 73. if abs(cx - x) > abs(cy - y): 74. distance = abs(cx - x) 75. else: 76. distance = abs(cy - y) 77. 78. if distance < smallestDistance: # we want the closest treasure chest. 79. smallestDistance = distance
Given the XY coordinates of where the player wants to drop the sonar device, and a list of XY coordinates for the treasure chests (in the chests list of lists), how do we find out which treasure chest is closest?
An Algorithm for Finding the Closest Treasure Chest
While the x and y variables are just integers (say, 5 and 0), together they represent the location on the game board (which is a Cartesian coordinate system) where the player guessed. The chests variable may have a value such as [[5, 0], [0, 2], [4, 2]], that value represents the locations of three treasure chests. Even though these variables are a bunch of numbers, we can visualize it like this:
We figure out the distance from the sonar device located at 0, 2 with "rings" and the distances around it:
But how do we translate this into code for our game? We need a way to represent distance as an expression. Notice that the distance from an XY coordinate is always the larger of two values: the absolute value of the difference of the two X coordinates and the absolute value of the difference of the two Y coordinates.
That means we should subtract the sonar device's X coordinate and a treasure chest's X coordinate, and then take the absolute value of this number. We do the same for the sonar device's Y coordinate and a treasure chest's Y coordinate. The larger of these two values is the distance. Let's look at our example board with rings above to see if this algorithm is correct.
The sonar's X and Y coordinates are 3 and 2. The first treasure chest's X and Y coordinates (first in the list [[5, 0], [0, 2], [4, 2]] that is) are 5 and 0.
For the X coordinates, 3 - 5 evaluates to -2, and the absolute value of -2 is 2.
For the Y coordinates, 2 - 1 evaluates to 1, and the absolute value of 1 is 1.
Comparing the two absolute values 2 and 1, 2 is the larger value and should be the distance from the sonar device and the treasure chest at coordinates 5, 1. We can look at the board and see that this algorithm works, because the treasure chest at 5,1 is in the sonar device's 2nd ring. Let's quickly compare the other two chests to see if his distances work out correctly also.
Let's find the distance from the sonar device at 3,2 and the treasure chest at 0,2. abs(3 - 0) evaluates to 3. The abs() function returns the absolute value of the number we pass to it. abs(2 - 2) evaluates to 0. 3 is larger than 0, so the distance from the sonar device at 3,2 and the treasure chest at 0,2 is 3. We look at the board and see this is true.
Let's find the distance from the sonar device at 3,2 and the last treasure chest at 4,2. abs (3 - 4) evaluates to 1. abs(2 - 2) evaluates to 0. 1 is larger than 0, so the distance from the sonar device at 3,2 and the treasure chest at 4,2 is 1. We look at the board and see this is true also.
Because all three distances worked out correctly, our algorithm works. The distances from the sonar device to the three sunken treasure chests are 2, 3, and 1. On each guess, we want to know the distance from the sonar device to the closest of the three treasure chest distances. To do this we use a variable called smallestDistance. Let's look at the code again:
71. smallestDistance = 100 # any chest will be closer than 100. 72. for cx, cy in chests: 73. if abs(cx - x) > abs(cy - y): 74. distance = abs(cx - x) 75. else: 76. distance = abs(cy - y) 77. 78. if distance < smallestDistance: # we want the closest treasure chest. 79. smallestDistance = distance
You can also use multiple assignment in for loops. For example, the assignment statement a, b = [5, 10] will assign 5 to a and 10 to b. Also, the for loop for i in [0, 1, 2, 3, 4] will assign the i variable the values 0 and 1 and so on for each iteration.
The for loop for cx, cy in chests: combines both of these principles. Because chests is a list where each item in the list is itself a list of two integers, the first of these integers is assigned to cx and the second integer is assigned to cy. So if chests has the value [[5, 0], [0, 2], [4, 2]], on the first iteration through the loop, cx will have the value 5 and cy will have the value 0.
Line 73 determines which is larger: the absolute value of the difference of the X coordinates, or the absolute value of the difference of the Y coordinates. (abs(cx - x) < abs(cy - y) seems like much easier way to say that, doesn't it?). The if-else statement assigns the larger of the values to the distance variable.
So on each iteration of the for loop, the distance variable holds the distance of a treasure chest's distance from the sonar device. But we want the shortest (that is, smallest) distance of all the treasure chests. This is where the smallestDistance variable comes in. Whenever the distance variable is smaller than smallestDistance, then the value in distance becomes the new value of smallestDistance.
We give smallestDistance the impossibly high value of 100 at the beginning of the loop so that at least one of the treasure chests we find will be put into smallestDistance. By the time the for loop has finished, we know that smallestDistance holds the shortest distance between the sonar device and all of the treasure chests in the game.
81. if smallestDistance == 0: 82. # xy is directly on a treasure chest! 83. chests.remove([x, y]) 84. return 'You have found a sunken treasure chest!'
The only time that smallestDistance is equal to 0 is when the sonar device's XY coordinates are the same as a treasure chest's XY coordinates. This means the player has correctly guessed the location of a treasure chest. We should remove this chest's two-integer list from the chests data structure with the remove() list method.
The remove() List Method
The remove() list method will remove the first occurrence of the value passed as a parameter from the list. For example, try typing the following into the interactive shell:
>>> x = [42, 5, 10, 42, 15, 42] >>> x.remove(10) >>> x [42, 5, 42, 15, 42]
You can see that the 10 value has been removed from the x list. The remove() method removes the first occurrence of the value you pass it, and only the first. For example, type the following into the shell:
>>> x = [42, 5, 10, 42, 15, 42] >>> x.remove(42) >>> x [5, 10, 42, 15, 42]
Notice that only the first 42 value was removed, but the second and third ones are still there. The remove() method will cause an error if you try to remove a value that is not in the list:
>>> x = [5, 42] >>> x.remove(10) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list >>>
After removing the found treasure chest from the chests list, we return the string 'You have found a sunken treasure chest!' to tell the caller that the guess was correct. Remember that any changes made to the list in a function will exist outside the function as well.
85. else: 86. if smallestDistance < 10: 87. board[x] [y] = str(smallestDistance) 88. return 'Treasure detected at a distance of %s from the sonar device.' % (smallestDistance) 89. else: 90. board[x] [y] = 'O' 91. return 'Sonar did not detect anything. All treasure chests out of range.'
The else block executes if smallestDistance was not 0, which means the player did not guess an exact location of a treasure chest. We return two different strings, depending on if the sonar device was placed within range of any of the treasure chests. If it was, we mark the board with the string version of smallestDistance. If not, we mark the board with a '0' .