Chapter 9 - Sonar

Source Code

sonar.py
  1. # Sonar
  2. import random
  3. import sys
  4. def drawBoard(board):
  5.     hline = '    ' # initial space for the numbers down the left side of the board
  6.     for i in range(1, 6):
  7.         hline += (' ' * 9) + str(i)
  8.     print hline
  9.     print '   ' + ('0123456789' * 6)
  10.     print
  11.     for i in range(15):
  12.         if i < 10:
  13.             extraSpace = ' '
  14.         else:
  15.             extraSpace = ''
  16.         print '%s%s %s %s' % (extraSpace, i, getRow(board, i), i)
  17.     print
  18.     print '   ' + ('0123456789' * 6)
  19.     print hline
  20. def getRow(board, row):
  21.     boardRow = ''
  22.     for i in range(60):
  23.         boardRow += board[i][row]
  24.     return boardRow
  25. def getNewBoard():
  26.     board = []
  27.     for x in range(60):
  28.         board.append([])
  29.         for y in range(15):
  30.             # use different characters for the ocean to make it more readable.
  31.             if random.randint(0, 1) == 0:
  32.                 ocean = '~'
  33.             else:
  34.                 ocean = '`'
  35.             board[x].append(ocean)
  36.     return board
  37. def getRandomSubs(numSubs):
  38.     subs = []
  39.     for i in range(numSubs):
  40.         subs.append([random.randint(0, 59), random.randint(0, 14)])
  41.     return subs
  42. def isValidMove(x, y):
  43.     return x >= 0 and x <= 59 and y >= 0 and y <= 14
  44. def makeMove(board, subs, x, y):
  45.     if not isValidMove(x, y):
  46.         return False
  47.     smallestDistance = 100
  48.     for s in subs:
  49.         sx = s[0]
  50.         sy = s[1]
  51.         if abs(sx - x) < abs(sy - y):
  52.             distance = abs(sy - y)
  53.         else:
  54.             distance = abs(sx - x)
  55.         if distance < smallestDistance:
  56.             smallestDistance = distance
  57.     if smallestDistance == 0:
  58.         # xy is directly on a sub!
  59.         subs.remove([x, y])
  60.         return 'You have found and destroyed a sub!'
  61.     else:
  62.         if smallestDistance < 10:
  63.             board[x][y] = str(smallestDistance)
  64.             return 'Sub detected at a distance of %s from the sonar device.' % (smallestDistance)
  65.         else:
  66.             board[x][y] = 'O'
  67.             return 'Sonar did not detect anything. All subs out of range.'
  68.             
  69. def enterPlayerMove():
  70.     print 'Where do you want to drop the next sonar device? (0-59 0-14) (or type quit)'
  71.     while True:
  72.         move = raw_input()
  73.         if move.lower() == 'quit':
  74.             print 'Thanks for playing!'
  75.             sys.exit()
  76.         move = move.split()
  77.         if len(move) == 2 and move[0].isdigit() and move[1].isdigit() and isValidMove(int(move[0]), int(move[1])):
  78.             return [int(move[0]), int(move[1])]
  79.         print 'Enter a number from 0 to 59, a space, then a number from 0 to 14.'
  80. def playAgain():
  81.     # This function returns True if the player wants to play again, otherwise it returns False.
  82.     print 'Do you want to play again? (yes or no)'
  83.     return raw_input().lower().startswith('y')
  84. def showInstructions():
  85.     print '''Instructions:
  86. You are the captain of a sub-hunting warship, USS Simon. Your current mission
  87. is to find the three subs that are lurking in the part of the ocean you are in
  88. and destroy them.
  89. To play, enter the coordinates of the point in the ocean you wish to drop a
  90. sonar device. The sonar can find out how far away the closest sub is to it.
  91. For example, the d below marks where the device was dropped, and the 2's
  92. represent distances of 2 away from the device. The 4's represent
  93. distances of 4 away from the device.
  94.     444444444
  95.     4       4
  96.     4 22222 4
  97.     4 2   2 4
  98.     4 2 d 2 4
  99.     4 2   2 4
  100.     4 22222 4
  101.     4       4
  102.     444444444
  103. Press enter to continue...'''
  104.     raw_input()
  105.     print '''For example, here is a sub (the s) located a distance of 2 away from the
  106. sonar device (the d):
  107.     22222
  108.     s   2
  109.     2 d 2
  110.     2   2
  111.     22222
  112. The point where the device was dropped will be marked with a 2.
  113. The subs don't move around. They have shut off their engines and are "running
  114. dark" to hide from you. Sonar devices can detect subs up to a distance of 9.
  115. In that case, the point will be marked with O
  116. If a device is directly dropped on a sub, you have discovered the location of
  117. the sub, and it will be destroyed with depth charges. The sonar device will
  118. remain there.
  119. When you destroy a sub, all sonar devices will update to locate the next
  120. closest sub.
  121. Press enter to continue...'''
  122.     raw_input()
  123.     print
  124. print 'S O N A R !'
  125. print
  126. print 'Would you like to view the instructions? (yes/no)'
  127. if raw_input().lower().startswith('y'):
  128.     showInstructions()
  129. while True:
  130.     sonarDevices = 16
  131.     theBoard = getNewBoard()
  132.     theSubs = getRandomSubs(3)
  133.     drawBoard(theBoard)
  134.     previousMoves = []
  135.     while sonarDevices > 0:
  136.         if sonarDevices > 1: extraSsonar = 's'
  137.         else: extraSsonar = ''
  138.         if len(theSubs) > 1: extraSsub = 's'
  139.         else: extraSsub = ''
  140.         print 'You have %s sonar device%s left. %s sub%s remaining.' % (sonarDevices, extraSsonar, len(theSubs), extraSsub)
  141.         x, y = enterPlayerMove()
  142.         previousMoves.append([x, y])
  143.         moveResult = makeMove(theBoard, theSubs, x, y)
  144.         if moveResult == False:
  145.             continue
  146.         else:
  147.             if moveResult == 'You have found and destroyed a sub!':
  148.                 # update all the sonar devices currently on the map.
  149.                 for x, y in previousMoves:
  150.                     makeMove(theBoard, theSubs, x, y)
  151.             drawBoard(theBoard)
  152.             print moveResult
  153.         if len(theSubs) == 0:
  154.             print 'You have destroyed all the subs! Congratulations and good game!'
  155.             break
  156.         sonarDevices -= 1
  157.     if sonarDevices == 0:
  158.         print 'We\'ve run out of sonar devices! Now we have to turn the ship around and head'
  159.         print 'for home with the subs still out there! Game over.'
  160.         print '    The remaining subs were here:'
  161.         for s in theSubs:
  162.             print '    %s, %s' % (s[0], s[1])
  163.     if not playAgain():
  164.         break

Code Explanation

  1. # Sonar
  2. import random
  3. import sys
  1. def drawBoard(board):
  2.     hline = '    ' # initial space for the numbers down the left side of the board
  3.     for i in range(1, 6):
  4.         hline += (' ' * 9) + str(i)
  1.     print hline
  2.     print '   ' + ('0123456789' * 6)
  3.     print
  1.     for i in range(15):
  2.         if i < 10:
  3.             extraSpace = ' '
  4.         else:
  5.             extraSpace = ''
  6.         print '%s%s %s %s' % (extraSpace, i, getRow(board, i), i)
  1.     print
  2.     print '   ' + ('0123456789' * 6)
  3.     print hline
  1. def getRow(board, row):
  2.     boardRow = ''
  3.     for i in range(60):
  4.         boardRow += board[i][row]
  5.     return boardRow
  1. def getNewBoard():
  2.     board = []
  3.     for x in range(60):
  4.         board.append([])
  1.         for y in range(15):
  2.             # use different characters for the ocean to make it more readable.
  3.             if random.randint(0, 1) == 0:
  4.                 ocean = '~'
  5.             else:
  6.                 ocean = '`'
  7.             board[x].append(ocean)
  8.     return board
  1. def getRandomSubs(numSubs):
  2.     subs = []
  3.     for i in range(numSubs):
  4.         subs.append([random.randint(0, 59), random.randint(0, 14)])
  5.     return subs
  1. def isValidMove(x, y):
  2.     return x >= 0 and x <= 59 and y >= 0 and y <= 14
  1. def makeMove(board, subs, x, y):
  2.     if not isValidMove(x, y):
  3.         return False
  4.     smallestDistance = 100
  1.     for s in subs:
  2.         sx = s[0]
  3.         sy = s[1]
  4.         if abs(sx - x) < abs(sy - y):
  5.             distance = abs(sy - y)
  6.         else:
  7.             distance = abs(sx - x)
  8.         if distance < smallestDistance:
  9.             smallestDistance = distance
  1.     if smallestDistance == 0:
  2.         # xy is directly on a sub!
  3.         subs.remove([x, y])
  4.         return 'You have found and destroyed a sub!'
  1.     else:
  2.         if smallestDistance < 10:
  3.             board[x][y] = str(smallestDistance)
  4.             return 'Sub detected at a distance of %s from the sonar device.' % (smallestDistance)
  1.         else:
  2.             board[x][y] = 'O'
  3.             return 'Sonar did not detect anything. All subs out of range.'
  4.             
  1. def enterPlayerMove():
  2.     print 'Where do you want to drop the next sonar device? (0-59 0-14) (or type quit)'
  3.     while True:
  4.         move = raw_input()
  5.         if move.lower() == 'quit':
  6.             print 'Thanks for playing!'
  7.             sys.exit()
  1.         move = move.split()
  2.         if len(move) == 2 and move[0].isdigit() and move[1].isdigit() and isValidMove(int(move[0]), int(move[1])):
  3.             return [int(move[0]), int(move[1])]
  4.         print 'Enter a number from 0 to 59, a space, then a number from 0 to 14.'
  1. def playAgain():
  2.     # This function returns True if the player wants to play again, otherwise it returns False.
  3.     print 'Do you want to play again? (yes or no)'
  4.     return raw_input().lower().startswith('y')
  1. def showInstructions():
  2.     print '''Instructions:
  3. You are the captain of a sub-hunting warship, USS Simon. Your current mission
  4. is to find the three subs that are lurking in the part of the ocean you are in
  5. and destroy them.
  6. To play, enter the coordinates of the point in the ocean you wish to drop a
  7. sonar device. The sonar can find out how far away the closest sub is to it.
  8. For example, the d below marks where the device was dropped, and the 2's
  9. represent distances of 2 away from the device. The 4's represent
  10. distances of 4 away from the device.
  11.     444444444
  12.     4       4
  13.     4 22222 4
  14.     4 2   2 4
  15.     4 2 d 2 4
  16.     4 2   2 4
  17.     4 22222 4
  18.     4       4
  19.     444444444
  20. Press enter to continue...'''
  21.     raw_input()
  1.     print '''For example, here is a sub (the s) located a distance of 2 away from the
  2. sonar device (the d):
  3.     22222
  4.     s   2
  5.     2 d 2
  6.     2   2
  7.     22222
  8. The point where the device was dropped will be marked with a 2.
  9. The subs don't move around. They have shut off their engines and are "running
  10. dark" to hide from you. Sonar devices can detect subs up to a distance of 9.
  11. In that case, the point will be marked with O
  12. If a device is directly dropped on a sub, you have discovered the location of
  13. the sub, and it will be destroyed with depth charges. The sonar device will
  14. remain there.
  15. When you destroy a sub, all sonar devices will update to locate the next
  16. closest sub.
  17. Press enter to continue...'''
  18.     raw_input()
  19.     print
  1. print 'S O N A R !'
  2. print
  3. print 'Would you like to view the instructions? (yes/no)'
  4. if raw_input().lower().startswith('y'):
  5.     showInstructions()
  1. while True:
  2.     sonarDevices = 16
  3.     theBoard = getNewBoard()
  4.     theSubs = getRandomSubs(3)
  5.     drawBoard(theBoard)
  6.     previousMoves = []
  1.     while sonarDevices > 0:
  2.         if sonarDevices > 1: extraSsonar = 's'
  3.         else: extraSsonar = ''
  4.         if len(theSubs) > 1: extraSsub = 's'
  5.         else: extraSsub = ''
  6.         print 'You have %s sonar device%s left. %s sub%s remaining.' % (sonarDevices, extraSsonar, len(theSubs), extraSsub)
  1.         x, y = enterPlayerMove()
  2.         previousMoves.append([x, y])
  1.         moveResult = makeMove(theBoard, theSubs, x, y)
  2.         if moveResult == False:
  3.             continue
  1.         else:
  2.             if moveResult == 'You have found and destroyed a sub!':
  3.                 # update all the sonar devices currently on the map.
  4.                 for x, y in previousMoves:
  5.                     makeMove(theBoard, theSubs, x, y)
  6.             drawBoard(theBoard)
  7.             print moveResult
  1.         if len(theSubs) == 0:
  2.             print 'You have destroyed all the subs! Congratulations and good game!'
  3.             break
  1.         sonarDevices -= 1
  1.     if sonarDevices == 0:
  2.         print 'We\'ve run out of sonar devices! Now we have to turn the ship around and head'
  3.         print 'for home with the subs still out there! Game over.'
  4.         print '    The remaining subs were here:'
  5.         for s in theSubs:
  6.             print '    %s, %s' % (s[0], s[1])
  1.     if not playAgain():
  2.         break

Things Covered In This Chapter: