How to resolve the algorithm Hunt the Wumpus step by step in the Python programming language
How to resolve the algorithm Hunt the Wumpus step by step in the Python programming language
Table of Contents
Problem Statement
Create a simple implementation of the classic textual game Hunt the Wumpus. The rules are: The game is set in a cave that consists of a 20 room labyrinth. Each room is connected to 3 other rooms (the cave is modeled after the vertices of a dodecahedron). The objective of the player is to find and kill the horrendous beast Wumpus that lurks in the cave. The player has 5 arrows. If they run out of arrows before killing the Wumpus, the player loses the game. In the cave there are: If the player enters a room with the Wumpus, he is eaten by it and the game is lost. If the player enters a room with a bottomless pit, he falls into it and the game is lost. If the player enters a room with a giant bat, the bat takes him and transports him into a random empty room. Each turn the player can either walk into an adjacent room or shoot into an adjacent room. Whenever the player enters a room, he "senses" what happens in adjacent rooms. The messages are: When the player shoots, he wins the game if he is shooting in the room with the Wumpus. If he shoots into another room, the Wumpus has a 75% of chance of waking up and moving into an adjacent room: if this is the room with the player, he eats him up and the game is lost.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Hunt the Wumpus step by step in the Python programming language
The provided code implements the Wumpus game in Python. The game is a text-based adventure game where the player explores a cave system and tries to hunt a Wumpus monster. The cave is represented as a graph, where each room is a node and the tunnels connecting the rooms are the edges.
The WumpusGame
class is the main class of the game. It contains the game state, such as the current position of the player, the location of the Wumpus and other threats, and the number of arrows the player has.
The class has several methods:
__init__
: Initializes the game with the specified edges. If no edges are specified, the default cave is used.get_safe_rooms
: Returns a list of all rooms that do not contain any threats.populate_cave
: Randomly places the player and threats in the cave.breadth_first_search
: Performs a breadth-first search on the cave graph to find out whether a target room can be reached from a source room using a given amount of tunnels.print_warning
: Called when entering a new room. Shows threats in adjacent rooms.get_players_input
: Queries the player for input until valid input is given.enter_room
: Controls the process of entering a new room.shoot_room
: Controls the process of shooting in a room.gameloop
: The main game loop.
The gameloop
method is the entry point of the game. It starts by printing the game introduction and initializing the game state. Then, it enters the main game loop, where the player can explore the cave, shoot arrows, and interact with the threats. The game loop continues until the player dies or kills the Wumpus.
The Wumpus game is a classic text-based adventure game that has been enjoyed by people for decades. The provided Python implementation is a faithful recreation of the original game, and it provides a fun and challenging experience for players of all ages.
Source code in the python programming language
import random
class WumpusGame(object):
def __init__(self, edges=[]):
# Create arbitrary caves from a list of edges (see the end of the script for example).
if edges:
cave = {}
N = max([edges[i][0] for i in range(len(edges))])
for i in range(N):
exits = [edge[1] for edge in edges if edge[0] == i]
cave[i] = exits
# If no edges are specified, play in the standard cave: a dodecahedron.
else:
cave = {1: [2,3,4], 2: [1,5,6], 3: [1,7,8], 4: [1,9,10], 5:[2,9,11],
6: [2,7,12], 7: [3,6,13], 8: [3,10,14], 9: [4,5,15], 10: [4,8,16],
11: [5,12,17], 12: [6,11,18], 13: [7,14,18], 14: [8,13,19],
15: [9,16,17], 16: [10,15,19], 17: [11,20,15], 18: [12,13,20],
19: [14,16,20], 20: [17,18,19]}
self.cave = cave
self.threats = {}
self.arrows = 5
self.arrow_travel_distance = 5 # As in the original game. I don't like this choice:
# a bow should not cover a whole cave.
self.player_pos = -1
"""
HELPER: These methods wrap processes that are useful or called often.
"""
def get_safe_rooms(self):
""" Returns a list containing all numbers of rooms that
do not contain any threats
"""
return list(set(self.cave.keys()).difference(self.threats.keys()))
def populate_cave(self):
""" Drop player and threats into random rooms in the cave.
"""
for threat in ['bat', 'bat', 'pit', 'pit', 'wumpus']:
pos = random.choice(self.get_safe_rooms())
self.threats[pos] = threat
self.player_pos = random.choice(self.get_safe_rooms())
def breadth_first_search(self, source, target, max_depth=5):
""" The game board (whether custom or standard dodecahedron) is an undirected graph.
The rooms are the vertices and the tunnels are the edges of this graph. To find
out whether a target room can be reached from a source room using a given amount
of tunnels, one can do a breadth first search on the underlying undirected graph.
BFS works like this: start with the source vertex, maybe it is already the target?
If not, then go a level deeper and find out, if one of the children (also called
successors) of the source vertex is the wanted target. If not, then for each child,
go a level deeper and find out if one of the grand-children is the wanted target.
If not, then for each grand-child go a level deeper and so on.
The following is a recursive implementation of BFS. You will not find any loops
(for, while). Instead you manage two lists. The first one ('stack') contains all
the vertices of the current depth-level (e.g. all grand children). The second
('visited') contains all vertices that you already checked. Now there are three
possibilites: Either stack is empty, then all vertices have been checked unsuccessfully;
or the target vertex is a member of the stack, then you are happy; or the target is
not a member of the stack, but there are still some vertices that you did not visit,
then you append to the stack, all successors of the members of the stack and the old
stack now belongs to the visited vertices.
"""
# Set up some initial values.
graph = self.cave
depth = 0
def search(stack, visited, target, depth):
if stack == []: # The whole graph was searched, but target was not found.
return False, -1
if target in stack:
return True, depth
visited = visited + stack
stack = list(set([graph[v][i] for v in stack for i in range(len(graph[v]))]).difference(visited))
depth += 1
if depth > max_depth: # Target is too far away from the source.
return False, depth
else: # Visit all successors of vertices in the stack.
return search(stack, visited, target, depth)
return search([source], [], target, depth)
"""
INPUT / OUTPUT: The player interacts with the game.
"""
def print_warning(self, threat):
""" Called when entering a new room. Shows threats in adjacent rooms.
"""
if threat == 'bat':
print("You hear a rustling.")
elif threat == 'pit':
print("You feel a cold wind blowing from a nearby cavern.")
elif threat == 'wumpus':
print("You smell something terrible nearby.")
def get_players_input(self):
""" Queries input until valid input is given.
"""
while 1: # Query the action.
inpt = input("Shoot or move (S-M)? ")
try: # Ensure that the player choses a valid action (shoot or move)
mode = str(inpt).lower()
assert mode in ['s', 'm', 'q']
break
except (ValueError, AssertionError):
print("This is not a valid action: pick 'S' to shoot and 'M' to move.")
if mode == 'q': # I added a 'quit-button' for convenience.
return 'q', 0
while 1: # Query the target of the action.
inpt = input("Where to? ")
try: # Ensure that the chosen target is convertable to an integer.
target = int(inpt)
except ValueError:
print("This is not even a real number.")
continue # Restart the while loop, to get a valid integer as target.
if mode == 'm':
try: # When walking, the target must be adjacent to the current room.
assert target in self.cave[self.player_pos]
break
except AssertionError:
print("You cannot walk that far. Please use one of the tunnels.")
elif mode == 's':
try: # When shooting, the target must be reachable within 5 tunnels.
bfs = self.breadth_first_search(self.player_pos, target)
assert bfs[0] == True
break
except AssertionError:
if bfs[1] == -1: # The target is outside cave.
print("There is no room with this number in the cave. Your arrow travels randomly.")
target = random.choice(self.cave.keys())
if bfs[1] > self.arrow_travel_distance: # The target is too far.
print("Arrows aren't that croocked.")
return mode, target
"""
CORE / GAME LOGIC
"""
def enter_room(self, room_number):
""" Controls the process of entering a new room.
"""
print("Entering room {}...".format(room_number))
# Maybe a threat waits in the new room.
if self.threats.get(room_number) == 'bat':
# The bat teleports the player to random empty room
print("You encounter a bat, it transports you to a random empty room.")
new_pos = random.choice(self.get_safe_rooms())
return self.enter_room(new_pos)
elif self.threats.get(room_number) == 'wumpus':
print("Wumpus eats you.")
return -1
elif self.threats.get(room_number) == 'pit':
print("You fall into a pit.")
return -1
# The room is safe; collect information about adjacent rooms.
for i in self.cave[room_number]:
self.print_warning(self.threats.get(i))
# Only if nothing else happens, the player enters the room of his choice.
return room_number
def shoot_room(self, room_number):
""" Controls the process of shooting in a room.
"""
print("Shooting an arrow into room {}...".format(room_number))
# Fire an arrow and see if something is hit by it.
self.arrows -= 1
threat = self.threats.get(room_number)
if threat in ['bat', 'wumpus']:
del self.threats[room_number]
if threat == 'wumpus':
print("Hurra, you killed the wumpus!")
return -1
elif threat == 'bat':
print("You killed a bat.")
elif threat in ['pit', None]:
print("This arrow is lost.")
# If this was your last arrow and it did not hit the wumpus...
if self.arrows < 1: # This (or the updating of self.arrows) seems to be broken...
print("Your quiver is empty.")
return -1
# If you shoot into another room, the Wumpus has a 75% of chance of waking up and moving into an adjacent room.
if random.random() < 0.75:
#print("DEBUG: Wumpus moved.")
for room_number, threat in self.threats.items():
if threat == 'wumpus':
wumpus_pos = room_number
new_pos = random.choice(list(set(self.cave[wumpus_pos]).difference(self.threats.keys())))
del self.threats[room_number]
self.threats[new_pos] = 'wumpus'
if new_pos == self.player_pos: # Wumpus entered players room.
print("Wumpus enters your room and eats you!")
return -1
return self.player_pos
def gameloop(self):
print("HUNT THE WUMPUS")
print("===============")
print()
self.populate_cave()
self.enter_room(self.player_pos)
while 1:
#print("DEBUG: Your quiver holds {} arrows.".format(self.arrows))
#print("DEBUG: Rooms with no threats are: {}.".format(self.get_safe_rooms()))
#print("DEBUG: Threats are located in the following rooms: {}".format(self.threats))
print("You are in room {}.".format(self.player_pos), end=" ")
print("Tunnels lead to: {0} {1} {2}".format(*self.cave[self.player_pos]))
inpt = self.get_players_input() # Player choses move or shoot.
print() # Visual separation of rounds.
if inpt[0] == 'm': # Move.
target = inpt[1]
self.player_pos = self.enter_room(target)
elif inpt[0] == 's': # Shoot.
target = inpt[1]
self.player_pos = self.shoot_room(target)
elif inpt[0] == 'q': # Quit.
self.player_pos = -1
if self.player_pos == -1: # E.g. Deadly threat, quiver empty, etc.
break # If any of the game loosing conditions are True,
# then player_pos will be -1.
print()
print("Game over!")
if __name__ == '__main__':
# Only executed if you start this script as the main script,
# i.e. you enter 'python path/to/wumpus.py' in a terminal.
# Assuming you saved the script in the directory 'path/to'
# and named it 'wumpus.py'.
# TODO: In the original game you can replay a dungeon (same positions of you and the threats)
WG = WumpusGame()
WG.gameloop()
D:\Workspace\rosettacode>python wumpus.py
HUNT THE WUMPUS
===============
Entering room 7...
You smell something terrible nearby.
You are in room 7. Tunnels lead to: 3 6 13
Shoot or move (S-M)? m
Where to? 3
Entering room 3...
Wumpus eats you.
Game over!
>>> import wumpus
>>> WG = wumpus.WumpusGame()
>>> edges = [[i, WG.cave[i][j]] for i in WG.cave.keys() for j in range(len(WG.cave[i]))]
>>> print edges
[[1,2], [1,3], [1,4], [2,1], [2,5], [2,6], [3,1], ...]
You may also check:How to resolve the algorithm Modular inverse step by step in the ObjectIcon programming language
You may also check:How to resolve the algorithm Ordered partitions step by step in the GAP programming language
You may also check:How to resolve the algorithm Terminal control/Preserve screen step by step in the Go programming language
You may also check:How to resolve the algorithm Kolakoski sequence step by step in the C++ programming language
You may also check:How to resolve the algorithm Index finite lists of positive integers step by step in the FreeBASIC programming language