libroller/src/roll.rs
2017-11-02 11:44:41 -05:00

141 lines
4.0 KiB
Rust

//! A struct in which our data is held for rolling. By ensuring that
//! only `usize`s are used we prevent the need for additional type
//! checking at time of use. */
extern crate rand;
use rand::Rng;
use std::fmt;
use std::num::ParseIntError;
use super::std;
#[derive(Debug, Clone, PartialEq)]
pub struct Roll {
die: usize,
number: usize,
}
impl Roll {
/// A fn to create new `Roll` structs, private to prevent
/// being used outside the library.
pub fn new(number: usize, die: usize) -> Roll {
Roll {
die: die,
number: number,
}
}
///Returns the value stored in the number field of a roll.
///Allows the fields of `Roll` to be private (and unchangable)
///while still allowing access to act on their internal value
/// ```
/// let x = Roll::new(&2, &6);
/// assert!(x.number() == 2);
/// ```
pub fn number(&self) -> usize {
self.number
}
/// Returns the value stored in the die field of a roll.
/// Allows the fields of `Roll` to be private (and unchangable)
/// while still allowing access to act on their internal value
pub fn die(&self) -> usize {
self.die
}
/// Creates a Vector to hold each of the values rolled. Making
/// a Vector ensures the ability to work on exactly the same
/// die rolls easily and accurately, since calling the `Roll`
/// iterator again will produce new results.
pub fn rolls(self) -> Vec<usize> {
self.collect()
}
/// Used on a `Roll` to return only the final value, ignoring
/// the values used to add up to that result. Allows `.total()`
/// syntax when used
pub fn total(self) -> usize {
self.sum()
}
/// Takes arguments and turns them into a `Roll` if possible,
/// failing that it returns an error to be handled.
/// Keeps taking args until less than 2 remain so it returns a
/// `Vec<Roll>`
pub fn from_args(args: &mut std::env::Args) -> Result<Vec<Roll>, ParseIntError> {
//first arg is the path to the executable so skip it
args.next();
let mut total: Vec<Roll> = vec![];
// loop that checks that there's enough elements in args remaining
// to make a `Roll` from.
while args.len() >= 2 {
// Collect 2 args into a `Vec<String>` and turn them into
// a `Roll`
let roll: Vec<String> = args.take(2).collect();
total.push(Roll::new(
roll[0].parse::<usize>()?,
roll[1].parse::<usize>()?,
));
}
Ok(total)
}
}
/// Turns `Roll` into an `Iterator`, causing it to create a series of random
///numbers between 1 and the chosen die size. It counts down the number field
///of the `Roll` and returns `None` to end the iteration. It's only capable of
///returning usize values, which should never be a problem.
impl Iterator for Roll {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.number != 0 {
self.number -= 1;
Some(rand::thread_rng().gen_range(1, self.die + 1))
} else {
None
}
}
}
/// Defines a `Display` format for the `Roll` type. Makes it much easier
///to use the `Roll` type as you should be able to just use
///```
/// ```
impl fmt::Display for Roll {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}d{}", self.number, self.die)
}
}
#[cfg(test)]
#[test]
fn rollnew() {
let x = Roll::new(2, 6);
let y = Roll { number: 2, die: 6 };
assert_eq!(x, y);
}
#[test]
fn displays() {
let x = Roll::new(2, 6);
assert_eq!(2, x.number());
assert_eq!(6, x.die());
}
#[test]
fn rolls() {
let x = Roll::new(20, 6);
for y in x.clone().rolls() {
assert!(y <= x.die());
}
}
#[test]
fn totaling() {
for _ in 0..21 {
let x = Roll::new(2, 6);
let y = x.total();
assert!(y <= 12 && y >= 2);
let z = Roll::new(3, 20);
let y = z.total();
assert!(y <= 60_usize && y >= 3_usize);
}
}