prop_check_rs/machine.rs
1use crate::state::State;
2
3/// Input for the candy machine.
4#[derive(Debug, Clone, Copy)]
5enum Input {
6 /// Insert a coin into the machine.
7 Coin,
8 /// Turn the knob to get a candy.
9 Turn,
10}
11
12/// Represents a candy machine state.
13#[derive(Debug, Clone, Copy, Default)]
14struct Machine {
15 /// Whether the machine is locked.
16 locked: bool,
17 /// Number of candies in the machine.
18 candies: i32,
19 /// Number of coins in the machine.
20 coins: i32,
21}
22
23impl Machine {
24 /// Simulates the candy machine with a sequence of inputs.
25 ///
26 /// # Arguments
27 /// - `inputs` - A vector of inputs to the machine.
28 ///
29 /// # Returns
30 /// - `State<Machine, (i32, i32)>` - A state monad that returns a tuple of coins and candies.
31 fn simulate_machine(inputs: Vec<Input>) -> State<Machine, (i32, i32)> {
32 let xs = inputs
33 .into_iter()
34 .map(move |i| {
35 let uf: Box<dyn Fn(Input) -> Box<dyn Fn(Machine) -> Machine>> = Self::update();
36 let r: Box<dyn Fn(Machine) -> Machine> = uf(i);
37 State::<Machine, ()>::modify(move |m: Machine| r(m))
38 })
39 .collect::<Vec<_>>();
40
41 let result = State::sequence(xs);
42 result.flat_map(|_| State::<Machine, Machine>::get().map(|s: Machine| (s.coins, s.candies)))
43 }
44
45 /// Creates a function that updates the machine state based on input.
46 ///
47 /// # Returns
48 /// - `Box<dyn Fn(Input) -> Box<dyn Fn(Machine) -> Machine>>` - A function that takes an input and returns a function that updates the machine state.
49 fn update() -> Box<dyn Fn(Input) -> Box<dyn Fn(Machine) -> Machine>> {
50 Box::new(move |i: Input| {
51 Box::new(move |s: Machine| {
52 match (i, s) {
53 // Inserting a coin into an unlocked machine does nothing
54 // Inserting a coin into an unlocked machine does nothing
55 // (Coin, Machine { locked: false, .. }) => s.clone(),
56
57 // Turning the knob on a locked machine does nothing
58 // Turning the knob on a locked machine does nothing
59 // (Turn, Machine { locked: true, .. }) => s.clone(),
60
61 // Inserting a coin into a locked machine unlocks it if there are candies
62 // Inserting a coin into a locked machine unlocks it if there are candies
63 (
64 Input::Coin,
65 Machine {
66 locked: true,
67 candies: candy,
68 coins: coin,
69 },
70 ) => Machine {
71 locked: false,
72 candies: candy,
73 coins: coin + 1,
74 },
75
76 // Turning the knob on an unlocked machine dispenses a candy and locks the machine
77 // Turning the knob on an unlocked machine dispenses a candy and locks the machine
78 (
79 Input::Turn,
80 Machine {
81 locked: false,
82 candies: candy,
83 coins: coin,
84 },
85 ) => Machine {
86 locked: true,
87 candies: candy - 1,
88 coins: coin,
89 },
90
91 // Any other action does nothing
92 // Any other action does nothing
93 (_, Machine { .. }) => s.clone(),
94 }
95 })
96 })
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use crate::machine::{Input, Machine};
103 use std::env;
104
105 #[ctor::ctor]
106 fn init() {
107 env::set_var("RUST_LOG", "info");
108 let _ = env_logger::builder().is_test(true).try_init();
109 }
110
111 #[test]
112 fn candy() {
113 let state = Machine::simulate_machine(vec![Input::Coin, Input::Turn]);
114 let result = state.run(Machine {
115 locked: true,
116 candies: 1,
117 coins: 1,
118 });
119 println!("{:?}", result);
120 }
121}