#!/usr/bin/python # $Id$ # # wumpus.py --- a faithful translation of the classic "Hunt The Wumpus" game. # Translator: Lance Finn Helsten # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # #-------------------------------------------------------------------- # The BASIC source is that posted by Magnus Olsson in USENET article # <9207071854.AA21847@thep.lu.se>: he wrote # >Below is the source code for _one_ (rather simple) Wumpus version, # >which I found in the PC-BLUE collection on SIMTEL20. I believe this is # >pretty much the same version that was published in David Ahl's "101 # >Basic Computer Games" (or, possibly, in the sequel). # # Eric Raymond: # >I have staunchly resisted the temptation to "improve" this game. It # >is functionally identical to the BASIC version (source for which # >appears in the comments). I fixed some typos in the help text. # > # >Language hackers may be interested to know that he most difficult thing # >about the translation was tracking the details required to translate from # >1-origin to 0-origin array indexing. # > # >The only enhancement is a an -s command-line switch for setting the # >random number seed. # > # >So, pretend for a little while that your workstation is an ASR-33 and # >limber up your fingers for a trip to nostalgia-land... # # I took the BASIC code from Eric Raymond's "comments" in his Wumpus.c source. # The major difference here is that I add a dummy array element at location # 0 so all the indexes remain the same. # # I translated from the BASIC and not the C source. I did not improve the game, # but I did change how it was coded to take advantage of the facilities in # Python. # # 5 REM *** HUNT THE WUMPUS *** import sys from random import random DEBUG = False STATUS_ALIVE = 0 STATUS_WIN = 1 STATUS_LOSE = 2 # 225 REM *** SET NO. OF ARROWS *** # 230 A=5 ARROWS = 5 LOCATION = -1 WUMPUS = -1 PITS = [-1,-1] BATS = [-1,-1] SETUP_SAVE = [] # 80 REM *** SET UP CAVE (DODECAHEDRAL NODE LIST) *** # 85 DIM S(20,3) # 90 FOR J=1 TO 20 # 95 FOR K=1 TO 3 # 100 READ S(J,K) # 105 NEXT K # 110 NEXT J # 115 DATA 2,5,8,1,3,10,2,4,12,3,5,14,1,4,6 # 120 DATA 5,7,15,6,8,17,1,7,9,8,10,18,2,9,11 # 125 DATA 10,12,19,3,11,13,12,14,20,4,13,15,6,14,16 # 130 DATA 15,17,20,7,16,18,9,17,19,11,18,20,13,16,19 CAVE = ((0,0,0), (2,5,8), (1,3,10), (2,4,12), (3,5,14), (1,4,6), (5,7,15), (6,8,17), (1,7,9), (8,10,18), (2,9,11), (10,12,19), (3,11,13), (12,14,20), (4,13,15), (6,14,16), (15,17,20), (7,16,18), (9,17,19), (11,18,20), (13,16,19)) def FNA(x=0): # 135 DEF FNA(X)=INT(20*RND(1))+1 return int(random() * 20) + 1 def FNB(x=1): # 140 DEF FNB(X)=INT(3*RND(1))+1 return int(random() * 3) def FNC(x=0): # 145 DEF FNC(X)=INT(4*RND(1))+1 return int(random() * 4) def _readInt(query, min, max): """Read an integer in the given range from sys.stdin""" ret = 0 while not (min <= ret and ret <= max): print query print "?", try: ret = int(sys.stdin.readline()) except ValueError: ret = -1 return ret def _readChar(query, valid): """Read a character and return when in valid set""" ret = ' ' while ret not in valid: print query print "?", ret = sys.stdin.readline()[0] return ret def _initGame(): global LOCATION global WUMPUS global PITS global BATS global SETUP_SAVE rebuild = True while rebuild: # 235 L=L(1) # 150 REM *** LOCATE L ARRAY ITEMS *** # 155 REM *** 1-YOU, 2-WUMPUS, 3&4-PITS, 5&6-BATS *** # 160 DIM L(6) # 165 DIM M(6) # 170 FOR J=1 TO 6 # 175 L(J)=FNA(0) # 180 M(J)=L(J) # 185 NEXT J LOCATION = FNA() WUMPUS = FNA() PITS = [FNA(), FNA()] BATS = [FNA(), FNA()] SETUP_SAVE = [LOCATION, WUMPUS, PITS, BATS] # 190 REM *** CHECK FOR CROSSOVERS (IE L(1)=L(2), ETC) *** # 195 FOR J=1 TO 6 # 200 FOR K=1 TO 6 # 205 IF J=K THEN 215 # 210 IF L(J)=L(K) THEN 170 # 215 NEXT K # 220 NEXT J rebuild = (LOCATION == WUMPUS or LOCATION in PITS or LOCATION in BATS or WUMPUS in PITS or WUMPUS in BATS or PITS[0] == PITS[1] or PITS[0] in BATS or PITS[1] in BATS or BATS[0] == BATS[1]) def runGame(): # 240 REM *** RUN THE GAME *** # 245 PRINT "HUNT THE WUMPUS" print "HUNT THE WUMPUS" jtable = {'S': shootArrows, 'M': move} status = STATUS_ALIVE while status == STATUS_ALIVE: # 250 REM *** HAZARD WARNING AND LOCATION *** # 255 GOSUB 585 printLocHazards() # 260 REM *** MOVE OR SHOOT *** # 265 GOSUB 670 # 270 ON O GOTO 280,300 # 275 REM *** SHOOT *** # 280 GOSUB 715 # 285 IF F=0 THEN 255 # 290 GOTO 310 # 295 REM *** MOVE *** # 300 GOSUB 975 # 305 IF F=0 THEN 255 # 670 REM *** CHOOSE OPTION *** # 675 PRINT "SHOOT OR MOVE (S-M)"; # 680 INPUT I$ # 685 IF I$<>"S" THEN 700 # 690 O=1 # 695 RETURN # 700 IF I$<>"M" THEN 675 # 705 O=2 # 710 RETURN status = jtable[_readChar("SHOOT OR MOVE (S-M)", 'SM')]() # 310 IF F>0 THEN 335 if status == STATUS_LOSE: # 315 REM *** LOSE *** # 320 PRINT "HA HA HA - YOU LOSE!" # 325 GOTO 340 print "HA HA HA - YOU LOSE!" elif status == STATUS_WIN: # 330 REM *** WIN *** # 335 PRINT "HEE HEE HEE - THE WUMPUS'LL GET YOU NEXT TIME!!" print "HEE HEE HEE - THE WUMPUS'LL GET YOU NEXT TIME!!" def instructions(): # 375 REM *** INSTRUCTIONS *** # 380 PRINT "WELCOME TO 'HUNT THE WUMPUS'" # 385 PRINT " THE WUMPUS LIVES IN A CAVE OF 20 ROOMS. EACH ROOM" # 390 PRINT "HAS 3 TUNNELS LEADING TO OTHER ROOMS. (LOOK AT A" # 395 PRINT "DODECAHEDRON TO SEE HOW THIS WORKS-IF YOU DON'T KNOW" # 400 PRINT "WHAT A DODECAHEDRON IS, ASK SOMEONE)" # 405 PRINT # 410 PRINT " HAZARDS:" # 415 PRINT " BOTTOMLESS PITS - TWO ROOMS HAVE BOTTOMLESS PITS IN THEM # 420 PRINT " IF YOU GO THERE, YOU FALL INTO THE PIT (& LOSE!)" # 425 PRINT " SUPER BATS - TWO OTHER ROOMS HAVE SUPER BATS. IF YOU" # 430 PRINT " GO THERE, A BAT GRABS YOU AND TAKES YOU TO SOME OTHER" # 435 PRINT " ROOM AT RANDOM. (WHICH MAY BE TROUBLESOME)" # 440 INPUT "TYPE AN E THEN RETURN ";W9 print """WELCOME TO 'HUNT THE WUMPUS' THE WUMPUS LIVES IN A CAVE OF 20 ROOMS. EACH ROOM HAS 3 TUNNELS LEADING TO OTHER ROOMS. (LOOK AT A DODECAHEDRON TO SEE HOW THIS WORKS-IF YOU DON'T KNOW WHAT A DODECAHEDRON IS, ASK SOMEONE) HAZARDS: BOTTOMLESS PITS - TWO ROOMS HAVE BOTTOMLESS PITS IN THEM IF YOU GO THERE, YOU FALL INTO THE PIT (& LOSE!) SUPER BATS - TWO OTHER ROOMS HAVE SUPER BATS. IF YOU GO THERE, A BAT GRABS YOU AND TAKES YOU TO SOME OTHER ROOM AT RANDOM. (WHICH MAY BE TROUBLESOME) TYPE AN E THEN RETURN""" sys.stdin.readline() # 445 PRINT " WUMPUS:" # 450 PRINT " THE WUMPUS IS NOT BOTHERED BY HAZARDS (HE HAS SUCKER" # 455 PRINT " FEET AND IS TOO BIG FOR A BAT TO LIFT). USUALLY" # 460 PRINT " HE IS ASLEEP. TWO THINGS WAKE HIM UP: YOU SHOOTING AN" # 465 PRINT "ARROW OR YOU ENTERING HIS ROOM." # 470 PRINT " IF THE WUMPUS WAKES HE MOVES (P=.75) ONE ROOM" # 475 PRINT " OR STAYS STILL (P=.25). AFTER THAT, IF HE IS WHERE YOU" # 480 PRINT " ARE, HE EATS YOU UP AND YOU LOSE!" # 485 PRINT # 490 PRINT " YOU:" # 495 PRINT " EACH TURN YOU MAY MOVE OR SHOOT A CROOKED ARROW" # 500 PRINT " MOVING: YOU CAN MOVE ONE ROOM (THRU ONE TUNNEL)" # 505 PRINT " ARROWS: YOU HAVE 5 ARROWS. YOU LOSE WHEN YOU RUN OUT # 510 PRINT " EACH ARROW CAN GO FROM 1 TO 5 ROOMS. YOU AIM BY TELLING # 515 PRINT " THE COMPUTER THE ROOM#S YOU WANT THE ARROW TO GO TO." # 520 PRINT " IF THE ARROW CAN'T GO THAT WAY (IF NO TUNNEL) IT MOVES" # 525 PRINT " AT RANDOM TO THE NEXT ROOM." # 530 PRINT " IF THE ARROW HITS THE WUMPUS, YOU WIN." # 535 PRINT " IF THE ARROW HITS YOU, YOU LOSE." # 540 INPUT "TYPE AN E THEN RETURN ";W9 print """WUMPUS: THE WUMPUS IS NOT BOTHERED BY HAZARDS (HE HAS SUCKER FEET AND IS TOO BIG FOR A BAT TO LIFT). USUALLY HE IS ASLEEP. TWO THINGS WAKE HIM UP: YOU SHOOTING AN ARROW OR YOU ENTERING HIS ROOM. IF THE WUMPUS WAKES HE MOVES (P=.75) ONE ROOM OR STAYS STILL (P=.25). AFTER THAT, IF HE IS WHERE YOU ARE, HE EATS YOU UP AND YOU LOSE! YOU:" EACH TURN YOU MAY MOVE OR SHOOT A CROOKED ARROW MOVING: YOU CAN MOVE ONE ROOM (THRU ONE TUNNEL) ARROWS: YOU HAVE 5 ARROWS. YOU LOSE WHEN YOU RUN OUT EACH ARROW CAN GO FROM 1 TO 5 ROOMS. YOU AIM BY TELLING THE COMPUTER THE ROOM#S YOU WANT THE ARROW TO GO TO. IF THE ARROW CAN'T GO THAT WAY (IF NO TUNNEL) IT MOVES AT RANDOM TO THE NEXT ROOM. IF THE ARROW HITS THE WUMPUS, YOU WIN. IF THE ARROW HITS YOU, YOU LOSE. TYPE AN E THEN RETURN""", sys.stdin.readline() # 545 PRINT " WARNINGS:" # 550 PRINT " WHEN YOU ARE ONE ROOM AWAY FROM A WUMPUS OR HAZARD," # 555 PRINT " THE COMPUTER SAYS:" # 560 PRINT " WUMPUS: 'I SMELL A WUMPUS'" # 565 PRINT " BAT : 'BATS NEARBY'" # 570 PRINT " PIT : 'I FEEL A DRAFT'" # 575 PRINT # 580 RETURN print """WARNINGS: WHEN YOU ARE ONE ROOM AWAY FROM A WUMPUS OR HAZARD, THE COMPUTER SAYS: WUMPUS: 'I SMELL A WUMPUS' BAT : 'BATS NEARBY' PIT : 'I FEEL A DRAFT' """ def printLocHazards(): # 585 REM *** PRINT LOCATION & HAZARD WARNINGS *** # 590 PRINT # 595 FOR J=2 TO 6 # 600 FOR K=1 TO 3 # 605 IF S(L(1),K)<>L(J) THEN 640 # 610 ON J-1 GOTO 615,625,625,635,635 # 615 PRINT "I SMELL A WUMPUS!" # 620 GOTO 640 # 625 PRINT "I FEEL A DRAFT" # 630 GOTO 640 # 635 PRINT "BATS NEARBY!" # 640 NEXT K # 645 NEXT J # 650 PRINT "YOU ARE IN ROOM "L(1) # 655 PRINT "TUNNELS LEAD TO "S(L,1);S(L,2);S(L,3) # 660 PRINT # 665 RETURN for c in CAVE[LOCATION]: if c == WUMPUS: print "I SMELL A WUMPUS!" if c in PITS: print "I FEEL A DRAFT" if c in BATS: print "BATS NEARBY!" print "YOU ARE IN ROOM", LOCATION if DEBUG: print "Parameters", WUMPUS, PITS, BATS print "TUNNELS LEAD TO %s %s %s" % CAVE[LOCATION] def shootArrows(): global ARROWS status = STATUS_ALIVE # 10 DIM P(5) path = [-1, -1, -1, -1, -1] # 715 REM *** ARROW ROUTINE *** # 720 F=0 # 725 REM *** PATH OF ARROW *** # 735 PRINT "NO. OF ROOMS (1-5)"; # 740 INPUT J9 # 745 IF J9<1 THEN 735 # 750 IF J9>5 THEN 735 # 755 FOR K=1 TO J9 # 760 PRINT "ROOM #"; # 765 INPUT P(K) # 770 IF K<=2 THEN 790 # 775 IF P(K)<>P(K-2) THEN 790 # 780 PRINT "ARROWS AREN'T THAT CROOKED - TRY ANOTHER ROOM" # 785 GOTO 760 # 790 NEXT K j9 = _readInt("NO. OF ROOMS (1-5)", 1, 5) for k in range(0,j9): path[k] = _readInt("ROOM #", 1, 20) while k > 2 and path[k] == path[k-2]: print "ARROWS AREN'T THAT CROOKED - TRY ANOTHER ROOM" path[k] = _readInt("ROOM #", 1, 20) # 795 REM *** SHOOT ARROW *** # 800 L=L(1) # 805 FOR K=1 TO J9 # 810 FOR K1=1 TO 3 # 815 IF S(L,K1)=P(K) THEN 895 # 820 NEXT K1 for k in range(1,j9): # 825 REM *** NO TUNNEL FOR ARROW *** # 830 L=S(L,FNB(1)) # 835 GOTO 900 randomCave = CAVE[LOCATION][FNB(1)] if path[k] == LOCATION: # 920 IF L<>L(1) THEN 840 # 925 PRINT "OUCH! ARROW GOT YOU!" # 930 GOTO 880 print "OUCH! ARROW GOT YOU!" return STATUS_LOSE elif path[k] in CAVE[LOCATION] or randomCave in CAVE[LOCATION]: # 890 REM *** SEE IF ARROW IS AT L(1) OR AT L(2) # 895 L=P(K) # 900 IF L<>L(2) THEN 920 # 905 PRINT "AHA! YOU GOT THE WUMPUS!" # 910 F=1 # 915 RETURN if path[k] == WUMPUS: print "AHA! YOU GOT THE WUMPUS!" return STATUS_WIN # 840 NEXT K # 845 PRINT "MISSED" # 850 L=L(1) print "MISSED" # 855 REM *** MOVE WUMPUS *** # 860 GOSUB 935 if status == STATUS_ALIVE: status = moveWumpus() # 865 REM *** AMMO CHECK *** # 870 A=A-1 # 875 IF A>0 THEN 885 # 880 F=-1 # 885 RETURN if status == STATUS_ALIVE: ARROWS = ARROWS - 1 if ARROWS <= 0: status = STATUS_LOSE return status def moveWumpus(): # 935 REM *** MOVE WUMPUS ROUTINE *** # 940 K=FNC(0) # 945 IF K=4 THEN 955 # 950 L(2)=S(L(2),K) # 955 IF L(2)<>L THEN 970 # 960 PRINT "TSK TSK TSK - WUMPUS GOT YOU!" # 965 F=-1 # 970 RETURN global WUMPUS k = FNC(0) if k != 3: WUMPUS = CAVE[WUMPUS][k] if WUMPUS == LOCATION: print "TSK TSK TSK - WUMPUS GOT YOU!" return STATUS_LOSE return STATUS_ALIVE def move(): global LOCATION status = STATUS_ALIVE # 1005 FOR K=1 TO 3 # 1010 REM *** CHECK IF LEGAL MOVE *** # 1015 IF S(L(1),K)=L THEN 1045 # 1020 NEXT K l = -1 while l not in CAVE[LOCATION]: # 975 REM *** MOVE ROUTINE *** # 980 F=0 # 985 PRINT "WHERE TO"; # 990 INPUT L # 995 IF L<1 THEN 985 # 1000 IF L>20 THEN 985 l = _readInt("WHERE TO", 1, 20) # 1025 IF L=L(1) THEN 1045 # 1030 PRINT "NOT POSSIBLE -"; # 1035 GOTO 985 if l != LOCATION: break print "NOT POSSIBLE -" # 1040 REM *** CHECK FOR HAZARDS *** # 1045 L(1)=L LOCATION = l if status == STATUS_ALIVE: status = checkWumpus() if status == STATUS_ALIVE: status = checkPit() if status == STATUS_ALIVE: status = checkBats() # 1145 RETURN return status def checkWumpus(): # 1050 REM *** WUMPUS *** # 1055 IF L<>L(2) THEN 1090 # 1060 PRINT "... OOPS! BUMPED A WUMPUS!" if LOCATION == WUMPUS: print "... OOPS! BUMPED A WUMPUS!" # 1065 REM *** MOVE WUMPUS *** # 1070 GOSUB 940 # 1075 IF F=0 THEN 1090 # 1080 RETURN return moveWumpus() return STATUS_ALIVE def checkPit(): # 1085 REM *** PIT *** # 1090 IF L=L(3) THEN 1100 # 1095 IF L<>L(4) THEN 1120 # 1100 PRINT "YYYYIIIIEEEE . . . FELL IN PIT" # 1105 F=-1 # 1110 RETURN if LOCATION in PITS: print "YYYYIIIIEEEE . . . FELL IN PIT" return STATUS_LOSE return STATUS_ALIVE def checkBats(): # 1115 REM *** BATS *** # 1120 IF L=L(5) THEN 1130 # 1125 IF L<>L(6) THEN 1145 # 1130 PRINT "ZAP--SUPER BAT SNATCH! ELSEWHEREVILLE FOR YOU!" # 1135 L=FNA(1) # 1140 GOTO 1045 global LOCATION status = STATUS_ALIVE while status == STATUS_ALIVE and LOCATION in BATS: print "ZAP--SUPER BAT SNATCH! ELSEWHEREVILLE FOR YOU!" LOCATION = FNA(1) status = checkWumpus() if status == STATUS_ALIVE: status = checkPit() return STATUS_ALIVE if __name__ == '__main__': # 15 PRINT "INSTRUCTIONS (Y-N)"; # 20 INPUT I$ # 25 IF I$="N" THEN 35 # 30 GOSUB 375 # 35 GOTO 80 if _readChar("INSTRUCTIONS (Y-N)", "YN") == 'Y': instructions() _initGame() try: quit = False while not quit: runGame() # 340 FOR J=1 TO 6 # 345 L(J)=M(J) # 350 NEXT J LOCATION, WUMPUS, PITS, BATS = SETUP_SAVE # 355 PRINT "SAME SETUP (Y-N)"; # 360 INPUT I$ # 365 IF I$<>"Y"THEN 170 # 370 GOTO 230 quit = (_readChar("SAME SETUP (Y-N)", "YN") == "N") except KeyboardInterrupt: pass # 1150 END