#![allow(dead_code)]
use chrono::prelude::*;
use chrono::Duration;
use redux_rs::{Store, Subscription};
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,
{
Help(T),
Hunt,
Quit(T),
Rest(Duration),
Status(T),
Travel(Duration, u64),
}
enum SimpleAction {
Hunt,
Travel(Duration, u64),
Rest(Duration),
}
impl From<SimpleAction> for Action<Box<dyn Fn(State) -> State>> {
fn from(action: SimpleAction) -> Self {
use SimpleAction::*;
match action {
Hunt => Action::<Box<dyn Fn(State) -> State>>::Hunt,
Travel(d, i) => Action::<Box<dyn Fn(State) -> State>>::Travel(d, i),
Rest(d) => Action::<Box<dyn Fn(State) -> State>>::Rest(d),
}
}
}
/// 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: &mut Action<T>) -> State
where
T: Fn(State) -> State,
{
match action {
// Travel: Move the player forward by distance and move the date forward by days
Action::Travel(days, distance) => State {
date: state.date + *days,
miles: state.miles - *distance,
..*state
},
// Rest: Regenerate one health (up to 5) by stopping for rest_days
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
},
// Hunt: Add one hundred pounds of food by stopping for hunt_days
Action::Hunt => State {
date: state.date + Duration::days(state.hunt_days),
food: state.food + 100,
..*state
},
// Print the status of the game
Action::Status(mut status_function) => status_function(*state),
// Print commands and what they do
Action::Help(mut help_function) => help_function(*state),
// End the game
Action::Quit(mut quit_mock) => quit_mock(*state),
}
}
fn main() {
let inital_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let mut store = Store::new(root_reducer, inital_state);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_travel() {
let initial_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let result_state = State {
miles: 1970,
date: Utc.ymd(2020, 3, 4),
..initial_state
};
let result_state_with_more_days: State = State {
date: Utc.ymd(2020, 3, 5),
..result_state
};
let result_state_with_more_miles: State = State {
miles: 1960,
..result_state
};
let duration = Duration::days(3);
let longer_duration = Duration::days(4);
let distance = 30;
let longer_distance = 40;
let default_action = SimpleAction::Travel(duration, distance).into();
assert_eq!(root_reducer(&initial_state, default_action), result_state);
assert_eq!(
root_reducer(
&initial_state,
SimpleAction::Travel(longer_duration, distance).into()
),
result_state_with_more_days
);
assert_eq!(
root_reducer(
&initial_state,
SimpleAction::Travel(duration, longer_distance).into()
),
result_state_with_more_miles
);
}
#[test]
fn test_rest() {
let initial_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 4,
hunt_days: 2,
};
let duration = Duration::days(2);
assert_eq!(
root_reducer(&initial_state, SimpleAction::Rest(duration).into()),
State {
date: Utc.ymd(2020, 3, 3),
..initial_state
}
);
assert_eq!(
root_reducer(&initial_state, SimpleAction::Rest(duration).into()),
State {
date: Utc.ymd(2020, 3, 3),
..initial_state
}
);
}
#[test]
fn test_hunt() {
let initial_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 2000,
food: 500,
health: 5,
hunt_days: 2,
};
let state_with_more_days: State = State {
hunt_days: 3,
..initial_state
};
let result_state: State = State {
date: Utc.ymd(2020, 3, 3),
food: 600,
..initial_state
};
let result_state_with_more_days = State {
date: Utc.ymd(2020, 3, 4),
hunt_days: 3,
..result_state
};
assert_eq!(
root_reducer(&initial_state, SimpleAction::Hunt.into()),
result_state
);
assert_eq!(
root_reducer(&state_with_more_days, SimpleAction::Hunt.into()),
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, &mut Action::Status(status_mock)),
default_state
);
assert!(status_mock_called);
}
#[test]
fn test_help() {
let mut help_mock_called = false;
let mut help_mock = |state: State| -> State {
help_mock_called = true;
return state;
};
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 1970,
food: 500,
health: 5,
hunt_days: 2,
};
root_reducer(&default_state, &mut Action::Help(help_mock));
assert!(help_mock_called);
}
fn test_quit() {
let mut quit_mock_called = false;
let quit_mock = |state: State| -> State {
quit_mock_called = true;
return state;
};
let default_state = State {
date: Utc.ymd(2020, 3, 1),
miles: 1970,
food: 500,
health: 5,
hunt_days: 2,
};
root_reducer(&default_state, &mut Action::Quit(quit_mock));
assert!(quit_mock_called);
}
}
Outputs the error on line 115:
mismatched types
types differ in mutability
note: expected fn pointer `for<'r, 's> fn(&'r _, &'s _) -> _`
found fn item `for<'r, 's> fn(&'r State, &'s mut Action<_>) -> State {root_reducer::<_>}`
And on all the tests where I need to pass in a closure to one of the fat enums:
expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`