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}