Hunt The Wumpus in Ruby – My First Ruby Application

Years ago I knew Gregory Yob, creator of the game Hunt the Wumpus.  In honor of his memory, I recreated the game as my first Ruby application!  Here’s the code (sorry, no pretty printing):

cave.rb

class Cave
  attr_reader :roomNumber
  attr_accessor :vertices
  attr_accessor :hasBats
  attr_accessor :hasPit

  def initialize(roomNumber)
    @roomNumber = roomNumber
    @vertices = []
  end
end

cave_system.rb

# Creates a cave system based on a dodecahedron, a 12 sided figure with 20 vertices, each vertex connects to 3 other vertices.
# The cave system therefore consists of 20 caves, each connecting to 3 other unique caves.
require './cave.rb'

class CaveSystem
  attr_reader :caves

  def initialize
    initializeCaveMatrix
    shuffleCaveMatrix
    initializeCaves
    initializeCaveVertices
    initializeCaveFeatures(1, 1)
  end

  # Create the array of caves
  def initializeCaves
    @caves = Array.new(20) {|n| Cave.new(n+1)}
    # or
    # @caves = Array.new
    # (1..20).each {|n| @caves.push(Cave.new(n))}
  end

  # Create an initial matrix of vertex numbers for a dodecahedron.
  def initializeCaveMatrix
    @caveMatrix = []
    @caveMatrix <<
        [ 1,  5,  4] << [ 0,  7,  2] << [ 1,  9,  3] << [ 2, 11,  4] <<
        [ 3, 13,  0] << [ 0, 14,  6] << [ 5, 16,  7] << [ 1,  6,  8] <<
        [ 7,  9, 17] << [ 2,  8, 10] << [ 9, 11, 18] << [10,  3, 12] <<
        [19, 11, 13] << [14, 12,  4] << [13,  5, 15] << [14, 19, 16] <<
        [ 6, 15, 17] << [16,  8, 18] << [10, 17, 19] << [12, 15, 18]
  end

  # shuffle the cave numbers, preserving vertices, so we get a random cave room numbers each time, but
  # still in the pattern of a dodecahedron.
  # stolen from here: stackoverflow.com/questions/4447270/java-array-with-vertices-of-dodecahedron
  def shuffleCaveMatrix
    shuffle = (0..19).to_a.sample(20)
    z = Array.new(20) {[0, 0, 0]}

    for i in 0..19 do
      vy = @caveMatrix[i]
      vz = [0, 0, 0]

      for j in 0..2 do
        vz[j] = shuffle[vy[j]]
      end

      z[shuffle[i]] = vz
    end

    @caveMatrix = z
  end

  # We want to work with objects instead of indices, so create the vertex Cave instances for each cave.
  def initializeCaveVertices
    @caveMatrix.each_with_index {|vertices, n| vertices.each {|vn| @caves[n].vertices.push(@caves[vn]) }}
  end

  # Initialize the cave system with superbats, pits, and the wumpus
  # Pits, bats, and the wumpus can all occupy the same room
  def initializeCaveFeatures(numBats, numPits)
    caves.sample(numBats).each {|cave| cave.hasBats = true}
    caves.sample(numPits).each {|cave| cave.hasPit = true}
  end
end

main.rb

require './cave.rb'
require './cave_system.rb'

# Hunt the Wumpus
# http://en.wikipedia.org/wiki/Hunt_the_Wumpus

def main
  puts "Welcome to Hunt the Wumpus, my first Ruby project, dedicated to Hara Ra, aka Gregory Yob, the creator of this game, first published in People's Computer Company journal Vol. 2 No. 1 in 1973."
  caveSystem = CaveSystem.new()
  # for debugging:
  caveSystem.caves.each {|cave| printf("Cave # %d connects to %d, %d, and %d\n", cave.roomNumber, cave.vertices[0].roomNumber, cave.vertices[1].roomNumber, cave.vertices[2].roomNumber)}

  wumpusHunterIsAlive = true
  wumpusIsAlive = true
  wumpusInCave = caveSystem.caves.sample

  # This will typically deposit the hunter in cave #1.  Would be nice to be more random.
  hunterInCave = caveSystem.caves.find {|cave| !cave.hasBats && !cave.hasPit && cave != wumpusInCave}

    while (wumpusHunterIsAlive && wumpusIsAlive) do
    whereAmI(hunterInCave)
    connectsTo(hunterInCave)
    whatsNearMe(hunterInCave, wumpusInCave)
    prompt = moveOrShoot
    hunterInCave, wumpusInCave, wumpusIsAlive, wumpusHunterIsAlive = processUserAction(prompt, hunterInCave, wumpusInCave, caveSystem)

    # bats snatch hunter from a bottomless pit, but may drop him into a bottomless pit!
    hunterInCave = checkForBats(hunterInCave, caveSystem)
    wumpusHunterIsAlive = checkForPit(hunterInCave)

    # if the poor guy/gal is still alive, check if he/she bumped into a Wumpus!
    if wumpusHunterIsAlive then
      wumpusInCave, wumpusHunterIsAlive = checkHunterAndWumpus(hunterInCave, wumpusInCave)
    end

  end

  if !wumpusIsAlive then printf("You got the Wumpus!\n") end
end

def whereAmI(cave)
  printf("You are in room # %d\n", cave.roomNumber)
end

def connectsTo(cave)
  printf("Tunnels lead to rooms %d, %d, and %d\n", cave.vertices[0].roomNumber, cave.vertices[1].roomNumber, cave.vertices[2].roomNumber)
end

def whatsNearMe(cave, wumpus)
  cave.vertices.each do |cv|
    if cv.hasBats then printf("I hear the bats!\n") end
    if cv.hasPit then printf("I feel a draft!\n") end
    if cv == wumpus then printf("I smell a Wumpus!\n") end
    end
end

def moveOrShoot
  printf("(M)ove or (S)hoot ? ")
  gets
end

def processUserAction(prompt, cave, wumpus, caveSystem)
  newCave = cave
  newWumpus = wumpus
  wumpusIsDead = false
  hunterIsDead = false

  case prompt
    when "M\n"
      newCave = getValidConnectingCave(cave)
    when "S\n"
      shootAtCave = getCaveForArrow(caveSystem)
      # Minimal logic here for now
      if shootAtCave == wumpus then
        wumpusIsDead = true
      else
        printf("Miss!\n")
        newWumpus = moveWumpus(wumpus)
        newWumpus, hunterIsAlive = checkHunterAndWumpus(cave, newWumpus)
        # sort of dumb
        hunterIsDead = !hunterIsAlive
      end
    else
      printf("Please enter M for Move or S for Shoot\n")
  end

  return newCave, newWumpus, !wumpusIsDead, !hunterIsDead
end

def checkHunterAndWumpus(cave, newWumpus)
  hunterIsAlive = true

  if newWumpus == cave then
    printf("EEEK!  You bumped into a Wumpus!\n")
    hunterIsAlive = (0..1).to_a.sample == 1

    if !hunterIsAlive
      printf("Yum, munch, munch, the Wumpus has eaten you!\n")
    else
      # move the wumpus again
      newWumpus = moveWumpus(newWumpus)
    end
  end

  return newWumpus, hunterIsAlive
end

def getValidConnectingCave(cave)
  valid = false

  while !valid do
    printf("Enter the room number to move to: ")
    roomNum = Integer(gets)
    newCave = cave.vertices.find {|cv| cv.roomNumber == roomNum}

    if !newCave then
      printf("You cannot go directly to that room.\n")
    else
      valid = true
    end
  end

  newCave
end

def getCaveForArrow(caveSystem)
  printf("Enter the room number to shoot into: ")
  roomNum = Integer(gets)
  caveSystem.caves[roomNum-1]
end

# wumpus moves randomly to another adjoining cave
def moveWumpus(wumpus)
   wumpus.vertices.sample
end

# return a random cave
def superBatZap(caveSystem)
   caveSystem.caves.sample
end

def checkForBats(hunterInCave, caveSystem)
  while hunterInCave.hasBats do
    printf("Super Bat Snatch!\n")
    hunterInCave = superBatZap(caveSystem)
  end
  hunterInCave
end

def checkForPit(hunterInCave)
  alive = true
  if hunterInCave.hasPit then
    printf("YYYAAAAHHHHHH....You fell into a bottomless pit.\n")
    alive = false
  end
  alive
end

main

A sample run (otherwise known as committing suicide):

You are in room # 1
Tunnels lead to rooms 2, 16, and 14
I feel a draft!
(M)ove or (S)hoot ? M
Enter the room number to move to: 2
You are in room # 2
Tunnels lead to rooms 15, 4, and 1
(M)ove or (S)hoot ? M
Enter the room number to move to: 1
You are in room # 1
Tunnels lead to rooms 2, 16, and 14
I feel a draft!
(M)ove or (S)hoot ? M
Enter the room number to move to: 16
You are in room # 16
Tunnels lead to rooms 1, 12, and 3
I hear the bats!
(M)ove or (S)hoot ? M
Enter the room number to move to: 1
You are in room # 1
Tunnels lead to rooms 2, 16, and 14
I feel a draft!
(M)ove or (S)hoot ? M
Enter the room number to move to: 14
YYYAAAAHHHHHH….You fell into a bottomless pit.

Advertisements

2 thoughts on “Hunt The Wumpus in Ruby – My First Ruby Application

    1. Yes it does. Create the three files and run it. In Ubuntu, with the RubyMine IDE, you can just run main.rb. In Windows, with Interactive Ruby installed, double-click on main.rb

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s