Hunt The Wumpus/Rust

From Rosetta Code
Revision as of 19:10, 3 September 2019 by Pistacchio (talk | contribs) (Created page with "{{collection|Hunt_The_Wumpus}} Rust version of Hunt The Wumpus . ==Code== <lang rust> use std::cell::{Cell, RefCell}; use std::io; use std::...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Hunt The Wumpus/Rust is part of Hunt_The_Wumpus. You may find other members of Hunt_The_Wumpus at Category:Hunt_The_Wumpus.

Rust version of Hunt The Wumpus .

Code

<lang rust> use std::cell::{Cell, RefCell}; use std::io; use std::io::Write; use std::process::exit; use std::rc::Rc; use std::ops::{DerefMut, Deref}; use rand::prelude::ThreadRng; use rand::prelude::*;


/////////////// // CONSTANTS // ///////////////

const HELP: &str = "\ Welcome to \"Hunt the Wumpus\" The wumpus lives in a cave of 20 rooms. Each room has 3 tunnels to other rooms. (Look at a dodecahedron to see how this works. If you dont 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).

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: your shooting an arrow, or your entering his room.
  If the wumpus wakes, he moves one room or stays still.
  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 (through one tunnel).
  Arrows:  You have 5 arrows.  You lose when you run out.
     You can only shoot to nearby rooms.
     If the arrow hits the wumpus, you win.

Warnings:

  When you are one room away from a wumpus or hazard, the computer
  says:
  Wumpus:  \"You smell something terrible nearby.\"
  Bat   :  \"You hear a rustling.\"
  Pit   :  \"You feel a cold wind blowing from a nearby cavern.\"

";

const MAZE_ROOMS: usize = 20; const ROOM_NEIGHBOURS: usize = 3;

const BATS: usize = 2; const PITS: usize = 2; const ARROWS: usize = 5;

const WAKE_WUMPUS_PROB: f32 = 0.75;

//////////// // PLAYER // ////////////

  1. [derive(Debug)]

struct Player {

   room: usize,
   arrows: usize,

}

impl Player {

   fn new(room: usize) -> Self {
       Player {
           arrows: ARROWS,
           room,
       }
   }

}

////////// // ROOM // //////////

  1. [derive(Debug, PartialEq)]

enum Danger {

   Wumpus,
   Bat,
   Pit,

}

  1. [derive(Default, Debug)]

struct Room {

   id: usize,
   neighbours: [Cell<Option<usize>>; ROOM_NEIGHBOURS],
   dangers: Vec<Danger>,

}

impl Room {

   fn new(id: usize) -> Self {
       let default_room = Room::default();
       Room {
           id,
           ..default_room
       }
   }
   fn missing_neighbours(&self) -> usize {
       self.neighbours.iter().filter(|n| n.get().is_none()).count()
   }
   fn neighbour_ids(&self) -> Vec<usize> {
       self.neighbours.iter()
           .filter(|n| n.get().is_some())
           .map(|n| n.get().unwrap())
           .collect()
   }

}

////////// // MAZE // //////////

  1. [derive(Debug)]

struct Maze {

   rooms: Vec<Room>,
   rng: Rc<RefCell<ThreadRng>>,

}

impl Maze {

   // Builds a vector of interconnected rooms by looping each room and finding three other rooms
   // to link to it
   fn new(rng: Rc<RefCell<ThreadRng>>) -> Self {
       let rooms: Vec<Room> = (0..MAZE_ROOMS)
           .map(|idx| Room::new(idx as usize))
           .collect();
       // for each room, see how many missing neighbours there are and them
       for room in rooms.iter() {
           for idx in 0..room.neighbours.len() {
               if room.neighbours[idx].get().is_none() {
                   // a suitable neighbour room is a room that is not the current one, not yet
                   // linked to the current one and that still has available neighbours
                   let neighbour = rooms.iter()
                       .find(|n| true &&
                           n.id != room.id
                           && n.missing_neighbours() != 0
                           && !room.neighbour_ids().contains(&n.id)
                       )
                       .expect("Cannot find a suitable neighbour");
                   room.neighbours[idx].set(Some(neighbour.id));
                   // link back the current room to the neighbour so that Room #0 has a link to
                   // Room #1 and Room #1 has a link to Room #0
                   for i in 0..ROOM_NEIGHBOURS {
                       if neighbour.neighbours[i].get().is_none() {
                           neighbour.neighbours[i].set(Some(room.id));
                           break;
                       }
                   }
               }
           }
       }
       let mut maze = Maze {
           rooms,
           rng,
       };
       // place the wumpus, pits and bats in empty rooms
       let empty_room = maze.rnd_empty_room();
       maze.rooms[empty_room].dangers.push(Danger::Wumpus);
       for _ in 0..PITS {
           let empty_room = maze.rnd_empty_room();
           maze.rooms[empty_room].dangers.push(Danger::Pit);
       }
       for _ in 0..BATS {
           let empty_room = maze.rnd_empty_room();
           maze.rooms[empty_room].dangers.push(Danger::Bat);
       }
       maze
   }
   fn rnd_empty_room(&mut self) -> usize {
       let empty_rooms: Vec<_> = self.rooms.iter()
           .filter(|n| n.dangers.is_empty())
           .collect();
       empty_rooms
           .choose(RefCell::borrow_mut(&self.rng).deref_mut())
           .unwrap()
           .id
   }
   fn rnd_empty_neighbour(&mut self, room: usize) -> Option<usize> {
       let neighbour_ids = self.rooms[room].neighbour_ids();
       let empty_neighbours: Vec<_> = neighbour_ids.iter()
           .filter(|&n| self.rooms[*n].dangers.is_empty())
           .collect();
       if empty_neighbours.is_empty() {
           return None;
       }
       let empty_neighbour = empty_neighbours
           .choose(RefCell::borrow_mut(&self.rng).deref_mut())
           .unwrap();
       Some(**empty_neighbour)
   }
   fn describe_room(&self, room: usize) -> String {
       let mut description = format!("You are in room #{}", room);
       if self.is_danger_nearby(room, Danger::Pit) {
           description.push_str("\nYou feel a cold wind blowing from a nearby cavern.");
       }
       if self.is_danger_nearby(room, Danger::Bat) {
           description.push_str("\nYou hear a rustling.");
       }
       if self.is_danger_nearby(room, Danger::Wumpus) {
           description.push_str("\nYou smell something terrible nearby.");
       }
       description.push_str(&format!("\nExits go to: {}",
                                     self.rooms[room].neighbours
                                         .iter()
                                         .map(|n| n.get().unwrap().to_string())
                                         .collect::<Vec<String>>()
                                         .join(", ")));
       description
   }
   fn is_danger_nearby(&self, room: usize, danger: Danger) -> bool {
       self.rooms[room].neighbours.iter().find(|n| {
           self.rooms[n.get().unwrap()]
               .dangers.contains(&danger)
       }).is_some()
   }
   fn parse_room(&self, destination: &str, current_room: usize) -> Result<usize, ()> {
       let destination: Result<usize, _> = destination.parse();
       // check that the given destination is both a number an the number of a linked room
       if let Ok(room) = destination {
           if self.rooms[current_room].neighbour_ids().contains(&room) {
               return Ok(room);
           }
       }
       Err(())
   }

}

/////////////// // MAIN LOOP // ///////////////

enum Status {

   Normal,
   Quitting,
   Moving,
   Shooting,

}

fn main() {

   let rng = Rc::new(RefCell::new(rand::thread_rng()));
   let mut maze = Maze::new(rng.clone());
   let mut player = Player::new(maze.rnd_empty_room());
   let mut status = Status::Normal;
   let describe = |maze: &Maze, player: &Player| {
       println!("{}", maze.describe_room(player.room));
       println!("What do you want to do? (m)ove or (s)hoot?");
   };
   let prompt = || {
       print!("> ");
       io::stdout().flush().expect("Error flushing");
   };
   describe(&maze, &player);
   prompt();
   // main loop
   loop {
       let mut input = String::new();
       io::stdin().read_line(&mut input).expect("Cannot read from stdin");
       let input: &str = &input.trim().to_lowercase();
       match status {
           Status::Quitting => {
               match input {
                   "y" => {
                       println!("Goodbye, braveheart!");
                       exit(0);
                   }
                   "n" => {
                       println!("Good. the Wumpus is looking for you!");
                       status = Status::Normal;
                   }
                   _ => println!("That doesn't make any sense")
               }
           }
           Status::Moving => {
               if let Ok(room) = maze.parse_room(input, player.room) {
                   if maze.rooms[room].dangers.contains(&Danger::Wumpus) {
                       println!("The wumpus ate you up!\nGAME OVER");
                       exit(0);
                   } else if maze.rooms[room].dangers.contains(&Danger::Pit) {
                       println!("You fall into a bottomless pit!\nGAME OVER");
                       exit(0);
                   } else if maze.rooms[room].dangers.contains(&Danger::Bat) {
                       println!("The bats whisk you away!");
                       player.room = maze.rnd_empty_room();
                   } else {
                       player.room = room;
                   }
                   status = Status::Normal;
                   describe(&maze, &player);
               } else {
                   println!("There are no tunnels from here to that room. Where do you wanto do go?");
               }
           }
           Status::Shooting => {
               if let Ok(room) = maze.parse_room(input, player.room) {
                   if maze.rooms[room].dangers.contains(&Danger::Wumpus) {
                       println!("YOU KILLED THE WUMPUS! GOOD JOB, BUDDY!!!");
                       exit(0);
                   } else {
                       // 75% chances of waking up the wumpus that would go into another room
                       if RefCell::borrow_mut(rng.deref()).gen::<f32>() < WAKE_WUMPUS_PROB {
                           let wumpus_room = maze.rooms.iter()
                               .find(|r| r.dangers.contains(&Danger::Wumpus))
                               .unwrap()
                               .id;
                           if let Some(new_wumpus_room) = maze.rnd_empty_neighbour(wumpus_room) {
                               if new_wumpus_room == player.room {
                                   println!("You woke up the wumpus and he ate you!\nGAME OVER");
                                   exit(1);
                               }
                               maze.rooms[wumpus_room].dangers.retain(|d| d != &Danger::Wumpus);
                               maze.rooms[new_wumpus_room].dangers.push(Danger::Wumpus);
                               println!("You heard a rumbling in a nearby cavern.");
                           }
                       }
                       player.arrows -= 1;
                       if  player.arrows == 0 {
                           println!("You ran out of arrows.\nGAME OVER");
                           exit(1);
                       }
                       status = Status::Normal;
                   }
               } else {
                   println!("There are no tunnels from here to that room. Where do you wanto do shoot?");
               }
           }
           _ => {
               match input {
                   "h" => println!("{}", HELP),
                   "q" => {
                       println!("Are you so easily scared? [y/n]");
                       status = Status::Quitting;
                   }
                   "m" => {
                       println!("Where?");
                       status = Status::Moving;
                   }
                   "s" => {
                       println!("Where?");
                       status = Status::Shooting;
                   }
                   _ => println!("That doesn't make any sense")
               }
           }
       }
       prompt();
   }

} </lang>