about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml3
-rw-r--r--finito-door/Cargo.toml7
-rw-r--r--finito-door/src/lib.rs144
3 files changed, 153 insertions, 1 deletions
diff --git a/Cargo.toml b/Cargo.toml
index bb5cb6b67094..5d62cea6ec2f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,5 @@
 [workspace]
 members = [
-  "finito-core"
+  "finito-core",
+  "finito-door"
 ]
diff --git a/finito-door/Cargo.toml b/finito-door/Cargo.toml
new file mode 100644
index 000000000000..3497a1c04cca
--- /dev/null
+++ b/finito-door/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "finito-door"
+version = "0.1.0"
+authors = ["Vincent Ambo <vincent@aprila.no>"]
+
+[dependencies.finito]
+path = "../finito-core"
diff --git a/finito-door/src/lib.rs b/finito-door/src/lib.rs
new file mode 100644
index 000000000000..a4aad8dcbbe3
--- /dev/null
+++ b/finito-door/src/lib.rs
@@ -0,0 +1,144 @@
+//! TODO: port the door docs
+
+extern crate finito;
+
+use finito::FSM;
+
+type Code = usize;
+type Attempts = usize;
+
+#[derive(Debug, PartialEq)]
+pub enum DoorState {
+    /// This state represents an open door.
+    Opened,
+
+    /// This state represents a closed door.
+    Closed,
+
+    /// This state represents a locked door on which a given code
+    /// is set. It also carries a number of remaining attempts
+    /// before the door is permanently disabled.
+    Locked { code: Code, attempts: Attempts },
+
+    /// This state represents a disabled door. The police will
+    /// need to unlock it manually!
+    Disabled,
+}
+
+#[derive(Debug)]
+pub enum DoorEvent {
+    Open,
+    Close,
+    Lock(Code),
+    Unlock(Code),
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DoorAction {
+    NotifyIRC(String),
+    CallThePolice,
+}
+
+impl FSM for DoorState {
+    type Event = DoorEvent;
+    type Action = DoorAction;
+
+    fn handle(self, event: DoorEvent) -> (Self, Vec<DoorAction>) {
+        match (self, event) {
+            (DoorState::Opened, DoorEvent::Close) => return (DoorState::Closed, vec![]),
+
+            (DoorState::Closed, DoorEvent::Open) => return (DoorState::Opened, vec![]),
+
+            (DoorState::Closed, DoorEvent::Lock(code)) => {
+                return (DoorState::Locked { code, attempts: 3 }, vec![])
+            }
+
+            (DoorState::Locked { code, attempts }, DoorEvent::Unlock(unlock_code)) => {
+                if code == unlock_code {
+                    return (DoorState::Closed, vec![]);
+                }
+
+                if attempts == 1 {
+                    return (DoorState::Disabled, vec![DoorAction::CallThePolice]);
+                }
+
+                return (
+                    DoorState::Locked {
+                        code,
+                        attempts: attempts - 1,
+                    },
+                    vec![DoorAction::NotifyIRC("invalid code entered".into())],
+                );
+            }
+
+            (current, _) => (current, vec![]),
+        }
+    }
+
+    fn enter(&self) -> Vec<DoorAction> {
+        let msg = match self {
+            DoorState::Opened => "door was opened",
+            DoorState::Closed => "door was closed",
+            DoorState::Locked { .. } => "door was locked",
+            DoorState::Disabled => "door was disabled",
+        };
+
+        vec![DoorAction::NotifyIRC(msg.into())]
+    }
+
+    fn act(action: DoorAction) -> Vec<DoorEvent> {
+        match action {
+            DoorAction::NotifyIRC(msg) => {
+                // TODO: write to file in example
+                println!("IRC: {}", msg);
+                vec![]
+            }
+
+            DoorAction::CallThePolice => {
+                // TODO: call the police
+                println!("The police was called! For real!");
+                vec![]
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use finito::advance;
+
+    fn test_fsm<S: FSM>(initial: S, events: Vec<S::Event>) -> (S, Vec<S::Action>) {
+        events.into_iter().fold((initial, vec![]), |(state, mut actions), event| {
+            let (new_state, mut new_actions) = advance(state, event);
+            actions.append(&mut new_actions);
+            (new_state, actions)
+        })
+    }
+
+    #[test]
+    fn test_door() {
+        let initial = DoorState::Opened;
+        let events = vec![
+            DoorEvent::Close,
+            DoorEvent::Open,
+            DoorEvent::Close,
+            DoorEvent::Lock(1234),
+            DoorEvent::Unlock(1234),
+            DoorEvent::Lock(4567),
+            DoorEvent::Unlock(1234),
+        ];
+        let (final_state, actions) = test_fsm(initial, events);
+
+        assert_eq!(final_state, DoorState::Locked { code: 4567, attempts: 2 });
+        assert_eq!(actions, vec![
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was opened".into()),
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was locked".into()),
+            DoorAction::NotifyIRC("door was closed".into()),
+            DoorAction::NotifyIRC("door was locked".into()),
+            DoorAction::NotifyIRC("invalid code entered".into()),
+        ]);
+    }
+}