Task: Hunt the Wumpus




Go: package main

import (
    "bufio"
    "fmt"
    "log"
    "math/rand"
    "os"
    "strconv"
    "strings"
    "time"
)

var cave = map[int][3]int{
    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},
}

var player, wumpus, bat1, bat2, pit1, pit2 int

var arrows = 5

func isEmpty(r int) bool {
    if r != player && r != wumpus && r != bat1 && r != bat2 && r != pit1 && r != pit2 {
        return true
    }
    return false
}

func sense(adj [3]int) {
    bat := false
    pit := false
    for _, ar := range adj {
        if ar == wumpus {
            fmt.Println("You smell something terrible nearby.")
        }
        switch ar {
        case bat1, bat2:
            if !bat {
                fmt.Println("You hear a rustling.")
                bat = true
            }
        case pit1, pit2:
            if !pit {
                fmt.Println("You feel a cold wind blowing from a nearby cavern.")
                pit = true
            }
        }
    }
    fmt.Println()
}

func check(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func plural(n int) string {
    if n != 1 {
        return "s"
    }
    return ""
}

func main() {
    rand.Seed(time.Now().UnixNano())
    player = 1
    wumpus = rand.Intn(19) + 2 
    bat1 = rand.Intn(19) + 2
    for {
        bat2 = rand.Intn(19) + 2
        if bat2 != bat1 {
            break
        }
    }
    for {
        pit1 = rand.Intn(19) + 2
        if pit1 != bat1 && pit1 != bat2 {
            break
        }
    }
    for {
        pit2 = rand.Intn(19) + 2
        if pit2 != bat1 && pit2 != bat2 && pit2 != pit1 {
            break
        }
    }
    scanner := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("\nYou are in room %d with %d arrow%s left\n", player, arrows, plural(arrows))
        adj := cave[player]
        fmt.Printf("The adjacent rooms are %v\n", adj)
        sense(adj)
        var room int
        for {
            fmt.Print("Choose an adjacent room : ")
            scanner.Scan()
            room, _ = strconv.Atoi(scanner.Text())
            if room != adj[0] && room != adj[1] && room != adj[2] {
                fmt.Println("Invalid response, try again")
            } else {
                break
            }
        }
        check(scanner.Err())
        var action byte
        for {
            fmt.Print("Walk or shoot w/s : ")
            scanner.Scan()
            reply := strings.ToLower(scanner.Text())
            if len(reply) != 1 || (len(reply) == 1 && reply[0] != 'w' && reply[0] != 's') {
                fmt.Println("Invalid response, try again")
            } else {
                action = reply[0]
                break
            }
        }
        check(scanner.Err())
        if action == 'w' {
            player = room
            switch player {
            case wumpus:
                fmt.Println("You have been eaten by the Wumpus and lost the game!")
                return
            case pit1, pit2:
                fmt.Println("You have fallen down a bottomless pit and lost the game!")
                return
            case bat1, bat2:
                for {
                    room = rand.Intn(19) + 2
                    if isEmpty(room) {
                        fmt.Println("A bat has transported you to a random empty room")
                        player = room
                        break
                    }
                }
            }
        } else {
            if room == wumpus {
                fmt.Println("You have killed the Wumpus and won the game!!")
                return
            } else {
                chance := rand.Intn(4) 
                if chance > 0 {        
                    wumpus = cave[wumpus][rand.Intn(3)]
                    if player == wumpus {
                        fmt.Println("You have been eaten by the Wumpus and lost the game!")
                        return
                    }
                }
            }
            arrows--
            if arrows == 0 {
                fmt.Println("You have run out of arrows and lost the game!")
                return
            }
        }
    }
}




Python: import random

class WumpusGame(object):


	def __init__(self, edges=[]):
		
		
		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

		
		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		
											
		self.player_pos = -1


	


	def get_safe_rooms(self):
		
		return list(set(self.cave.keys()).difference(self.threats.keys()))


	def populate_cave(self):
		
		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):
		
		
		graph = self.cave
		depth = 0

		def search(stack, visited, target, depth):
			if stack == []:					
				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:			
				return False, depth
			else:							
				return search(stack, visited, target, depth)

		return search([source], [], target, depth)


	


	def print_warning(self, threat):
		
		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):
		
		while 1:								

			inpt = input("Shoot or move (S-M)? ")
			try:								
				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':							
			return 'q', 0

		while 1:								

			inpt = input("Where to? ")
			try:								
				target = int(inpt)
			except ValueError:
				print("This is not even a real number.")
				continue						

			if mode == 'm':
				try:							
					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:							
					bfs = self.breadth_first_search(self.player_pos, target)
					assert bfs[0] == True
					break
				except AssertionError:
					if bfs[1] == -1: 			
						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:				
						print("Arrows aren't that croocked.")

		return mode, target


	


	def enter_room(self, room_number):
			
		print("Entering room {}...".format(room_number))
		
		if self.threats.get(room_number) == 'bat':
			
			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

		
		for i in self.cave[room_number]:
			self.print_warning(self.threats.get(i))

		
		return room_number


	def shoot_room(self, room_number):
		
		print("Shooting an arrow into room {}...".format(room_number))
		
		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 self.arrows < 1:		
			print("Your quiver is empty.")
			return -1

		
		if random.random() < 0.75:
			
			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: 
				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("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()		
			print()								
			if inpt[0] == 'm':					
				target = inpt[1] 
				self.player_pos = self.enter_room(target)
			elif inpt[0] == 's':				
				target = inpt[1]
				self.player_pos = self.shoot_room(target)
			elif inpt[0] == 'q':				
				self.player_pos = -1

			if self.player_pos == -1:			
				break							
												

		print()
		print("Game over!")	
		

if __name__ == '__main__':						
	
	
	
	

	

	WG = WumpusGame()
	WG.gameloop()