neleabels

Python version of "Hunt the Wumpus" (Gregory Yob, 1972)

Dec 31st, 2022 (edited)
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.55 KB | Gaming | 0 0
  1. # Hunt the Wumpus - 1972 by Gregory Yob
  2. # -------------------------------------
  3. # rerewritten half a century later by Nele Abels as a Python coding exercise
  4.  
  5. import random
  6.  
  7. # the game data is stored in this array
  8.  
  9. game = [0,0,0,0,0,0,0]
  10.  
  11. # the save array is used to restart a game with the same settings
  12.  
  13. save = game[:]
  14.  
  15. # the following variables are just use to make access to the game array more transparent
  16.  
  17. player = 0  # in which room is the player?
  18. wumpus = 1  # in which room is the wumpus?
  19. bat1 = 2    # in first bat room
  20. bat2 = 3    # in second bat room
  21. pit1 = 4    # in first pit room
  22. pit2 = 5    # in second pit room
  23. arrows = 6      # in number of arrows left
  24.  
  25. # The tunnels in the original Wumpus were hard coded. Yob's reasoning was that
  26. # this would encourage the player to draw maps and play strategically
  27.  
  28. rooms = [
  29.     [0,0,0],       # dummy room; Yob's game map started with room 1
  30.     [2,5,8],       # room 1    
  31.     [1,3,10],      #   "  2
  32.     [2,4,12],      #   "  3
  33.     [3,5,14],      #   "  4
  34.     [1,4,6],       #   "  5
  35.     [5,7,15],      #   "  6
  36.     [6,8,17],      #   "  7
  37.     [1,7,9],       #   "  8
  38.     [8,10,18],     #   "  9
  39.     [2,9,11],      #   "  10
  40.     [10,12,19],    #   "  11
  41.     [3,11,13],     #   "  12
  42.     [12,14,20],    #   "  13
  43.     [4,13,15],     #   "  14
  44.     [6,14,16],     #   "  15
  45.     [15,17,20],    #   "  16
  46.     [7,16,18],     #   "  17
  47.     [9,17,19],     #   "  18
  48.     [11,18,20],    #   "  19
  49.     [13,16,19]]    #   "  20
  50.  
  51. def instructions():
  52.     print("\n\nThe Wumpus lives in a cave of 20 rooms. Each room has three tunnels")
  53.     print("leading to other rooms. (Look at a dodecahedron to see how this works -")
  54.     print("if you don't know what a dodecahedron is, ask someone.")
  55.     print("[Or look at a twenty sided die, Nele]")
  56.     print()
  57.     print("Hazards:")
  58.     print()
  59.     print("Bottomless pits - two rooms have bottomless pits in them. If you go there,")
  60.     print("you fall into the pit (and lose!)")
  61.     print("Superbats - two other rooms have super bats. If you go there, a bat grabs")
  62.     print("you and takes you to some other room at random. (Which might be troublesome.)")
  63.     print("\nPress <Return>",end="")
  64.     input()
  65.     print("\nWumpus:")
  66.     print()
  67.     print("The Wumpus is not bothered by the Hazards (he has sucker feet and is too big")
  68.     print("for a bat to lift.) Usually he is asleep. Two things wake him up: your entering")
  69.     print("his room or your shooting an arrow.")
  70.     print("If the Wumpus wakes, he move with a 75% chance to a next room or he stays still")
  71.     print("(25% chance). After that, if he is where you are, he eats you up and you lose.")
  72.     print()
  73.     print("You:")
  74.     print()
  75.     print("Each turn you may move or shoot a crooked arrow.")
  76.     print("Moving: you can go one room through one of the tunnels.")
  77.     print("Arrows: you have five arrows. You lose when you run out.")
  78.     print("Each arrow can go from one to five rooms. You aim by telling the computer the")
  79.     print("rooms you want the arrow to go.")
  80.     print("If the arrow can't go that (i.e. no tunnel) it moves at random to the next room.")
  81.     print("   If the arrow hits the wumpus, you win.")
  82.     print("   If the arrow hits you, you lose.")
  83.     print("\nPress <Return>",end="")
  84.     input()
  85.     print("\nWarnings:")
  86.     print()
  87.     print("When you are one room away from the Wumpus or a hazard, the computer says:")
  88.     print("   Wumpus   - 'I smell a Wumpus'")
  89.     print("   Superbat - 'Bats nearbay'")
  90.     print("   pit      - 'I feel a draft'\n")
  91.     print("[Nele says: 'Good hunting!']")
  92.  
  93. # init_cave() first creates a temporary array with five random numbers between 1 and 20. Then
  94. # it compares each array entry with the other entries to see if numbers are identical, e.g. if
  95. # the location of the wumpus is set to the location of the player. If that is the case, the
  96. # randomization is repeated.
  97.  
  98. def init_cave():
  99.  
  100.     tmp = [0,0,0,0,0,0,0]
  101.     fertig = False
  102.     while (fertig == False):
  103.         for i in range(0,6):
  104.             tmp[i] = random.randint(1,20)
  105.         fertig = True
  106.         for i in range(0,6):
  107.             for j in range(0,6):
  108.                 if (i != j) and (tmp[i] == tmp[j]):  # one of the values is double, we are not finished
  109.                     fertig = False
  110.     tmp[6] = 5 # the number of arrows is always 5
  111.     return tmp
  112.  
  113. # describe_room() just prints out the room number, the exits and the warnings. It does not write to
  114. # any variables
  115.    
  116. def describe_room():
  117.  
  118.     # use of local variables to make the code more readable
  119.  
  120.     pl = game[player]
  121.     wm = game[wumpus]
  122.     b1 = game[bat1]
  123.     b2 = game[bat2]
  124.     p1 = game[pit1]
  125.     p2 = game[pit2]
  126.  
  127.     print("\nI am in cave no. {}.".format(game[player]))
  128.  
  129.     # I don't use elif because then you could conclude from the order of the warnings
  130.     # the room in which the danger lurks
  131.    
  132.     for i in range(0,3):
  133.         if rooms[pl][i] == wm:
  134.             print("I smell a Wumpus.")
  135.  
  136.     bat_found = False # used to prevent double warning of bats
  137.     for i in range(0,3):
  138.         if ((rooms[pl][i] == b1) or (rooms[pl][i] == b2)) and (bat_found == False):
  139.             print("Bats nearby.")
  140.             bat_found = True
  141.  
  142.     pit_found = False # used to prevent double warning of pits    
  143.     for i in range(0,3):
  144.         if (rooms[pl][i] == p1) or (rooms[pl][i] == p2) and (pit_found == False):
  145.             print("I feel a draft.")
  146.             pit_found = True
  147.  
  148.     print("Exits go to caves no. {}, {}, and {}.\n".format(rooms[pl][0],rooms[pl][1],rooms[pl][2]))
  149.            
  150. # input_number(min,max) asks for a numerical input between a minimum and a maximum. The loop
  151. # runs until a correct input is given.
  152.  
  153. def input_number(minimum, maximum):
  154.     fertig = False
  155.     while (fertig == False):
  156.         ein = input()
  157.         if ein.isnumeric():
  158.             number = int(ein)
  159.             if (number > minimum-1) and (number < maximum + 1):
  160.                 fertig = True
  161.     return number
  162.  
  163. # input_yesno() asks for "y" or "n" as an answer (lower case as well as upper case)
  164.  
  165. def input_yesno():
  166.     fertig = False
  167.     while (fertig == False):
  168.         ein = input().lower()
  169.         if (ein == "y") or (ein == "n"):
  170.             fertig = True
  171.     return ein
  172.  
  173. # input_moveshoot() asks for "m" for "move" or "s" for shoot and doesn't accept anything else.
  174.  
  175. def input_moveshoot():
  176.     fertig = False
  177.     while (fertig == False):
  178.         print("Move or shoot (m/s)? ",end="")
  179.         ein = input().lower()
  180.         if (ein == "m") or (ein == "s"):
  181.             fertig = True
  182.     return ein
  183.  
  184. # shoot_arrow() is the original shoot algorithm as devised by Yob. It is not very good.
  185. # the function returns:
  186. #   0   - missed, nothing happened
  187. #   1   - Wumpus was hit, game won
  188. #   -1  - either player shot himself or was eaten by Wumpus. Game lost.
  189.  
  190. def shoot_arrow():
  191.  
  192.     global game # the Wumpus may wander around and the game data must be changed
  193.    
  194.     # Step 1: player enters the path of the arrow
  195.  
  196.     game[arrows] = game[arrows]-1
  197.     target = []  # this list will containt the room numbers
  198.    
  199.     print("\nHow many rooms (1-5)? ",end="")
  200.     rng = input_number(1,5)
  201.  
  202.     # last_ein and ein are used to see if a target room is entered twice, i.e. the player
  203.     # wants the arrow to do a 180° turn. Yob's original code is (probably) buggy since
  204.     # it compares the target with the penultimate target, i.e. it is possible to shoot
  205.     # yourself deliberately.
  206.  
  207.     last_ein = game[player] # the arrow obviously starts at the player's position
  208.     print("\nIn which rooms should the arrow go (1-20)? ")
  209.     for i in range(1,rng+1):
  210.         fertig = False
  211.         while fertig == False:
  212.             fertig = True
  213.             print("#{}: ".format(i),end="")
  214.             ein = input_number(1,20)
  215.             if ein == last_ein:
  216.                 print("\nThe arrow isn't that crooked. It cannot turn around in midair.")
  217.                 fertig = False
  218.             else:
  219.                 target.append(ein)
  220.                 last_ein = ein # choice was ok. ein is entered as new last entry
  221.  
  222.     # Step 2: arrow flies through the cave
  223.  
  224.     a_pos = game[player]    # a_pos is used to determine if next target is possible
  225.  
  226.     for i in range(len(target)):
  227.  
  228.         # is the target reachable from the current position of the arrow?
  229.        
  230.         if (rooms[a_pos][0] == target[i]) or (rooms[a_pos][1] == target[i]) or (rooms[a_pos][2] == target[i]):
  231.             a_pos = target[i]
  232.         else:
  233.  
  234.             # no way to reach the target, the arrow goes into a random exit.
  235.             a_pos = rooms[a_pos][random.randint(0,2)]
  236.  
  237.         # arrow hit wumpus or player? -> game over
  238.  
  239.         if a_pos == game[wumpus]:
  240.             print("Aha! I got the Wumpus.")
  241.             return 1
  242.         elif a_pos == game[player]:
  243.             print("Ouch! Arrow got me.")
  244.             return -1
  245.            
  246.     # Step 3: Wumpus moves (and perhaps eats player) with 75% if the game is not over
  247.     # In Yob's algorithm, the Wumpus could move into rooms with bats and a pit.
  248.  
  249.     if random.randint(1,100) < 76:
  250.         game[wumpus] = rooms[game[wumpus]][random.randint(0,2)]
  251.        
  252.     # Step 4: Does the Wumpus stumble onto the player? Are the arrows used up?
  253.  
  254.     if (game[player] == game[wumpus]):
  255.         print("Tsk, tsk, tsk. The Wumpus got me.")
  256.         return -1
  257.     elif (game[arrows] == 0):
  258.         print("I have run out of arrows. I can go home, now.")
  259.         return -1
  260.  
  261.     return 0 # arrow has just landed somewhere and nothing special happened
  262.  
  263. # move_player() manages player movement. The player can die by falling into a pit or
  264. # stumble into the Wumpus and be eaten. The function returns:
  265. # 0  -  nothing happened, game goes on
  266. # -1 -  player died, game over
  267.  
  268. def move_player():
  269.  
  270.     global game # may have to be changed because the Wumpus moves
  271.  
  272.     # use of local variables to make the code more readable
  273.  
  274.     pl = game[player]
  275.     wm = game[wumpus]
  276.     b1 = game[bat1]
  277.     b2 = game[bat2]
  278.     p1 = game[pit1]
  279.     p2 = game[pit2]
  280.     fertig = True
  281.  
  282.     # Step 1: Enter the next movement and check if there is a way. If yes, move player
  283.  
  284.     fertig = False
  285.     while fertig == False:
  286.         print("\nWhere do you want to go (1-20)? ",end="")
  287.         next_room = input_number(1,20)
  288.         for i in range(0,3):
  289.             if rooms[pl][i] == next_room:
  290.                 fertig = True
  291.         if fertig == False:
  292.             print("There is no exit in that direction.")
  293.         else:
  294.             game[player] = next_room
  295.             pl = next_room
  296.  
  297.     # Step 2: Check if pits, bats or Wumpus are in the new room. I have put the whole
  298.     # part in a loop because Yob's algorithm explicitely allows the bats to drop the
  299.     # player into rooms with bats, pits, or the Wumpus.
  300.  
  301.     fertig = False
  302.     bat_transport = False   # player has been carried away by bats
  303.     while fertig == False:
  304.         if (pl == b1) or (pl ==b2):                     # Bats?
  305.             print("ZAPP! A Super Bat snatches me and carries me elsewhere.")
  306.             pl = random.randint(1,20)
  307.             game[player] = pl
  308.             bat_transport = True
  309.         elif (pl == p1) or (pl == p2):                  # Pits?
  310.             print("YYYYYIIIIEEEE... fell in pit.")
  311.             return -1                                   # death, game over
  312.         elif pl == wm:                                  # Wumpus?
  313.             print("Oops! I bumped into the Wumpus.")
  314.             wumpus_moves = random.randint(1,4)
  315.             if wumpus_moves in [0,1,2]:
  316.                 print("The Wumpus scurries off...")
  317.                 wm = rooms[wm][wumpus_moves]
  318.                 game[wumpus] = wm
  319.                 return 0                                # lucky fuck. Game goes on.
  320.             else:
  321.                 print("The Wumpus got me!")
  322.                 return -1                               # death, game over      
  323.         else:
  324.             if bat_transport == False:  # only print when player walked on their own feet
  325.                 print("I carefully move through the tunnel...")
  326.             return 0                    # nothing remarkable happened
  327.    
  328.  
  329. ##############################################################################
  330. # Main program
  331. ##############################################################################
  332.  
  333. print("\n\nHunt the Wumpus\n---------------")
  334. print("1972 by Gregory Yob, 2022 by Nele Abels\n")
  335. print("Do you want to read the instructions? ",end="")
  336. if input_yesno() == "y":
  337.     instructions()
  338.    
  339. game = init_cave()
  340. save = game[:]
  341. game_over = False
  342.  
  343. while game_over == False:
  344.  
  345.     # Main game loop until player or Wumpus is dead
  346.    
  347.     gamestate = 0  # 1 - game won, -1 - game lost
  348.    
  349.     while gamestate == 0:
  350.         describe_room()                # Normal room description
  351.         if input_moveshoot() == "m":
  352.             gamestate = move_player()
  353.         else:
  354.             gamestate = shoot_arrow()
  355.  
  356.     # Play again?
  357.  
  358.     print("\nGame over.\n")
  359.     print("Do you want to play again (y/n)? ",end="")
  360.     again = input_yesno()
  361.     if again == "n":
  362.         print("Bye...")
  363.         game_over = True
  364.     else:
  365.         print("Do you want to use the same game map (y/n)? ",end="")
  366.         newgame = input_yesno()
  367.         if newgame == "y":
  368.             print("Restoring game...")
  369.             game = save[:]
  370.             game_over = False
  371.         else:
  372.             print("Setting up new game...")
  373.             game = init_cave()      
  374.             save = game[:]
  375.             game_over = False
  376.            
  377.            
  378.    
  379.  
  380.  
  381.  
  382.    
  383.  
  384.  
  385.  
Add Comment
Please, Sign In to add comment