Hunt The Wumpus/Rust: Difference between revisions
Refactoring
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::...") |
Pistacchio (talk | contribs) (Refactoring) |
||
Line 4:
==Code==
<lang rust>
//! *Hunt the Wumpus* reimplementation in Rust.
use rand;
use rand::prelude::SliceRandom;
use std::io;
use std::io::Write;
use std::process::exit;
/// Help message.
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. (The tunnels form a dodecahedron:
http://en.wikipedia.org/wiki/dodecahedron)
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.
Arrows: You have
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
Pit
";
/// The maze is an dodecahedron.
const MAZE_ROOMS: usize = 20;
const
/// Number of bats.
const BATS: usize = 2;
/// Number of pits.
const PITS: usize = 2;
/// Initial number of arrows.
const ARROWS: usize = 5;
/// Fractional chance of waking the Wumpus on entry to its room.
const WAKE_WUMPUS_PROB: f32 = 0.75;
type RoomNum = usize;
/// Description of the current player state.
struct Player {
/// Player location.
room: RoomNum,
/// Remaining number of arrows.
arrows: usize,
}
impl Player {
/// Make a new player starting in the given room.
fn new(room: RoomNum) -> Self {
Player {
arrows: ARROWS,
Line 77 ⟶ 92:
}
/// Dangerous things that can be in a room.
#[derive(PartialEq)]
enum Danger {
Wumpus,
Line 88 ⟶ 100:
}
/// Room description.
#[derive(Default)]
struct Room {
id:
/// The indices of neighboring rooms.
neighbours: [Option<RoomNum>; ROOM_NEIGHBORS],
/// Possible danger in the room.
dangers: Vec<Danger>,
}
impl Room {
fn new(id:
let default_room = Room::default();
Room { id, ..default_room }
}
fn neighbour_ids(&self) -> Vec<
self.neighbours.iter().cloned().filter_map(|n| n).collect()
}
}
///
struct Maze {
/// Room list.
rooms: Vec<Room>,
}
impl Maze {
// List of adjacencies used to wire up the dodecahedron.
// https://stackoverflow.com/a/44096541/364875
const ADJS: [[usize; 3]; 20] = [
[0, 2, 9],
[1, 3, 11],
[2, 4, 13],
[0, 3, 5],
[4, 6, 14],
[5, 7, 16],
[0, 6, 8],
[7, 9, 17],
[1, 8, 10],
[9, 11, 18],
[2, 10, 12],
[11, 13, 19],
[3, 12, 14],
[5, 13, 15],
[14, 16, 19],
[6, 15, 17],
[8, 16, 18],
[10, 17, 19],
[12, 15, 18],
];
// Builds a vector of rooms comprising a dodecahedron.
fn new() -> Self {
let mut rooms: Vec<Room> = (0..MAZE_ROOMS)
.map(|idx| Room::new(idx as RoomNum))
.collect();
for (i, room) in rooms.iter_mut().enumerate() {
for
}
}
let mut maze = Maze { rooms };
// place the wumpus, pits and bats in empty rooms
Line 185:
}
/// Return a randomly-selected empty room.
fn rnd_empty_room(&mut self) -> RoomNum {
let empty_rooms: Vec<_> = self.rooms.iter().filter(|n| n.dangers.is_empty()).collect();
empty_rooms.choose(&mut rand::thread_rng()).unwrap().id
}
/// Retrun the id of a random empty neighbour if any
fn rnd_empty_neighbour(&mut self, room: RoomNum) -> Option<RoomNum> {
let neighbour_ids = self.rooms[room].neighbour_ids();
let empty_neighbours: Vec<_> = neighbour_ids
.iter()
.filter(|&n| self.rooms[*n].dangers.is_empty())
.collect();
Line 207 ⟶ 205:
}
let empty_neighbour = empty_neighbours.choose(&mut rand::thread_rng()).unwrap();
Some(**empty_neighbour)
}
fn describe_room(&self, room: RoomNum) -> String {
let mut description = format!("You are in room #{}", room);
Line 227 ⟶ 224:
}
description.push_str(&format!(
"\nExits go to: {}",
.map(|n|
.collect::<Vec<String>>()
.join(", ")
));
description
}
/// Adjacent room contains a non-wumpus danger.
fn is_danger_nearby(&self, room: RoomNum, danger: Danger) -> bool {
.any(|n| self.rooms[n.unwrap()].dangers.contains(&danger))
}
/// Index of neighboring room given by user `destination`, else an error message.
fn parse_room(&self, destination: &str, current_room: RoomNum) -> Result<RoomNum, ()> {
let destination: Result<RoomNum, _> = destination.parse();
// check that the given destination is both a number an the number of a linked room
Line 258 ⟶ 260:
}
/// Current game state.
enum Status {
Normal,
Line 270 ⟶ 269:
fn main() {
let
let mut player = Player::new(maze.rnd_empty_room());
let mut status = Status::Normal;
Line 291 ⟶ 289:
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 {
println!("
}
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) {
Line 326 ⟶ 324:
describe(&maze, &player);
} else {
println!(
"There are no tunnels from here to that room. Where do you wanto do go?"
);
}
}
Line 336:
} else {
// 75% chances of waking up the wumpus that would go into another room
if
let wumpus_room = maze
.rooms
.iter()
.find(|r| r.dangers.contains(&Danger::Wumpus))
.unwrap()
Line 348 ⟶ 350:
}
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.");
Line 355 ⟶ 359:
player.arrows -= 1;
if
println!("You ran out of arrows.\nGAME OVER");
exit(1);
Line 363 ⟶ 367:
}
} else {
println!(
"There are no tunnels from here to that room. Where do you wanto do shoot?"
);
}
}
_ => match input {
println!("
status =
}
println!("Where?");
status = Status::Moving;
}
"s" => {
println!("Where?");
status = Status::Shooting;
}
_ => println!("That doesn't make any sense"),
},
}
prompt();
}
}
#[test]
fn test_maze_connected() {
use std::collections::HashSet;
let maze = Maze::new();
let n = maze.rooms.len();
fn exists_path(i: RoomNum, j: RoomNum, vis: &mut HashSet<RoomNum>, maze: &Maze) -> bool {
if i == j {
return true;
}
vis.insert(i);
maze.rooms[i].neighbours.iter().any(|neighbour| {
// Check that all rooms have three neighbors.
let k = neighbour.unwrap();
!vis.contains(&k) && exists_path(k, j, vis, maze)
})
}
for i in 0..n {
for j in 0..n {
assert!(exists_path(i, j, &mut HashSet::new(), &maze));
}
}
}
|