Alright, I'll try the other approach. Here is my much longer code (with several compiler errors cause I'm kind of a mess right now ). I would stop and do some exercises in borrowing and lifetimes and all that stuff, but I'm kind of on a deadline .
use chrono::prelude::*;
use chrono::Duration;
use std::boxed::Box;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct State {
date: Date<Utc>,
miles: u64,
food: u64,
health: u64,
hunt_days: i64,
}
enum Action<T>
where
T: FnMut(State) -> State,
{
Hunt,
Travel(Duration, u64),
Rest(Duration),
Status(T),
}
/// The main function that uses an Action to get a new State
///
/// # Examples
/// ```
/// root_reducer(
/// State {
/// date: Utc.ymd(2020, 3, 1),
/// miles: 2000,
/// food: 500,
/// health: 5,
/// hunt_days: 2,
/// rest_days: 2,
/// travel_days: 3,
/// travel_distance: 30,
/// },
/// Action::Travel
/// )
/// ```
fn root_reducer<T>(state: State, action: Action<T>) -> State
where
T: FnMut(State) -> State,
{
match action {
Action::Travel(days, distance) => State {
date: state.date + days,
miles: state.miles - distance,
..state
},
Action::Rest(days) => State {
date: state.date + days,
// No more than 5 health
health: if state.health < 5 {
state.health + 1
} else {
state.health
},
..state
},
Action::Hunt => State {
date: state.date + Duration::days(state.hunt_days),
food: state.food + 100,
..state
},
Action::Status(mut status_function) => status_function(state),
}
}
fn main() {
println!("Hello World!");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_travel() {
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let travel_result_state = State {
miles: 1970,
date: Utc.ymd(2020, 3, 4),
..default_state
};
let travel_result_state_with_more_days: State = State {
date: Utc.ymd(2020, 3, 5),
..travel_result_state
};
let travel_result_state_with_more_miles: State = State {
miles: 1960,
..travel_result_state
};
let default_duration = Duration::days(3);
let longer_duration = Duration::days(4);
let default_distance = 30;
let longer_distance = 40;
let default_action = Action::Travel(default_duration, default_distance);
assert_eq!(
root_reducer(default_state, default_action),
travel_result_state
);
assert_eq!(
root_reducer(
default_state,
Action::Travel(longer_duration, default_distance)
),
travel_result_state_with_more_days
);
assert_eq!(
root_reducer(
default_state,
Action::Travel(default_duration, longer_distance)
),
travel_result_state_with_more_miles
);
}
#[test]
fn test_rest() {
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let rest_default_state = State {
health: 4,
..default_state
};
let default_duration = Duration::days(2);
assert_eq!(
root_reducer(rest_default_state, Action::Rest(default_duration)),
default_state
);
assert_eq!(
root_reducer(default_state, Action::Rest(default_duration)),
default_state
);
}
#[test]
fn test_hunt() {
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let hunt_state_with_more_days: State = State {
hunt_days: 3,
..default_state
};
let hunt_result_state: State = State {
food: 600,
..default_state
};
let hunt_result_state_with_more_days = State {
date: Utc.ymd(2020, 3, 4),
..hunt_result_state
};
assert_eq!(root_reducer(default_state, Action::Hunt), hunt_result_state);
assert_eq!(
root_reducer(hunt_state_with_more_days, Action::Hunt),
hunt_result_state_with_more_days
);
}
#[test]
fn test_status() {
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 1970,
food: 500,
health: 5,
hunt_days: 2,
};
let mut status_mock_called = false;
let status_mock = |state: State| -> State {
status_mock_called = true;
return state;
};
assert_eq!(
root_reducer(default_state, Action::Status(status_mock)),
default_state
);
assert!(status_mock_called);
}
}