diff --git a/crates/protocol/src/quests/graph.rs b/crates/protocol/src/quests/graph.rs index c87c0ac..b502676 100644 --- a/crates/protocol/src/quests/graph.rs +++ b/crates/protocol/src/quests/graph.rs @@ -106,23 +106,31 @@ fn build_graph_from_quest_definition(quest: &Quest) -> Dag { let Some(definition) = &quest.definition else { return dag; }; - for Connection { - ref step_from, - ref step_to, - } in &definition.connections - { - // Validate if steps are in defined in the quest - if quest.contains_step(step_from) && quest.contains_step(step_to) { - if let Some(node_from) = nodes.get(step_from) { - let (_, node_to) = - dag.add_child(*node_from, node_from.index() as u32, step_to.clone()); - nodes.insert(step_to.clone(), node_to); - } else { - let node_from = dag.add_node(step_from.clone()); - nodes.insert(step_from.clone(), node_from); - let (_, node_to) = - dag.add_child(node_from, node_from.index() as u32, step_to.clone()); - nodes.insert(step_to.clone(), node_to); + + if definition.connections.is_empty() { + for step in &definition.steps { + let node = dag.add_node(step.id.clone()); + nodes.insert(step.id.clone(), node); + } + } else { + for Connection { + ref step_from, + ref step_to, + } in &definition.connections + { + // Validate if steps are in defined in the quest + if quest.contains_step(step_from) && quest.contains_step(step_to) { + if let Some(node_from) = nodes.get(step_from) { + let (_, node_to) = + dag.add_child(*node_from, node_from.index() as u32, step_to.clone()); + nodes.insert(step_to.clone(), node_to); + } else { + let node_from = dag.add_node(step_from.clone()); + nodes.insert(step_from.clone(), node_from); + let (_, node_to) = + dag.add_child(node_from, node_from.index() as u32, step_to.clone()); + nodes.insert(step_to.clone(), node_to); + } } } } @@ -636,4 +644,33 @@ mod tests { let result = matches_action(&Action::emote(Coordinates::new(1, 2), "ID"), &other_action); assert!(result); } + + #[test] + fn quest_with_single_step_works_properly() { + let quest = Quest { + id: "1e9a8bbf-2223-4f51-b7e5-660d35cedef4".to_string(), + name: "CUSTOM_QUEST".to_string(), + description: "".to_string(), + creator_address: "0xB".to_string(), + definition: Some(QuestDefinition { + connections: vec![], + steps: vec![Step { + id: "A".to_string(), + description: "".to_string(), + tasks: vec![Task { + id: "A_1".to_string(), + description: "".to_string(), + action_items: vec![], + }], + }], + }), + ..Default::default() + }; + + let graph: QuestGraph = (&quest).into(); + + let next = graph.next(START_STEP_ID).unwrap(); + assert_eq!(next.len(), 1); + assert_eq!(next[0], "A"); + } } diff --git a/crates/protocol/src/quests/mod.rs b/crates/protocol/src/quests/mod.rs index 1e956d8..aac6b33 100644 --- a/crates/protocol/src/quests/mod.rs +++ b/crates/protocol/src/quests/mod.rs @@ -455,6 +455,8 @@ mod tests { .build(); assert!(quest.is_valid().is_ok()); + assert!(quest.get_steps_without_from().contains("A")); + assert!(quest.get_steps_without_to().contains("A")) } #[test] diff --git a/crates/protocol/src/quests/state.rs b/crates/protocol/src/quests/state.rs index 31e88ad..08ae0d0 100644 --- a/crates/protocol/src/quests/state.rs +++ b/crates/protocol/src/quests/state.rs @@ -704,4 +704,117 @@ mod tests { assert_eq!(state.current_steps.len(), 0); assert!(state.is_completed()) } + + #[test] + fn quest_graph_single_step_apply_event_works() { + let quest = Quest { + id: "".to_string(), + name: "CUSTOM_QUEST".to_string(), + description: "".to_string(), + creator_address: "0xB".to_string(), + definition: Some(QuestDefinition { + connections: vec![], + steps: vec![Step { + id: "A1".to_string(), + description: "".to_string(), + tasks: vec![Task { + id: "A1_1".to_string(), + description: "".to_string(), + action_items: vec![Action::custom("A1_1_ID")], + }], + }], + }), + ..Default::default() + }; + let quest_graph = QuestGraph::from(&quest); + let mut events = vec![Event { + // A1_1 + id: uuid::Uuid::new_v4().to_string(), + address: "0xA".to_string(), + action: Some(Action::custom("A1_1_ID")), + }]; + + let mut state = QuestState::from(&quest_graph); + assert!(state.current_steps.contains_key("A1")); // branch 1 + assert_eq!(state.current_steps.len(), 1); + assert!(state.steps_completed.is_empty()); + assert_eq!(state.steps_left, 1); + assert!(state.required_steps.contains(&"A1".to_string())); + + state = state.apply_event(&quest_graph, &events.remove(0)); + assert!(state.current_steps.is_empty()); + assert!(state.steps_completed.contains(&"A1".to_string())); + assert_eq!(state.steps_left, 0); + assert!(state.is_completed()); + } + + #[test] + fn quest_graph_multiple_starting_points_as_single_steps_apply_event_works() { + let quest = Quest { + id: "".to_string(), + name: "CUSTOM_QUEST".to_string(), + description: "".to_string(), + creator_address: "0xB".to_string(), + definition: Some(QuestDefinition { + connections: vec![], + steps: vec![ + Step { + id: "A1".to_string(), + description: "".to_string(), + tasks: vec![Task { + id: "A1_1".to_string(), + description: "".to_string(), + action_items: vec![Action::custom("A1_1_ID")], + }], + }, + Step { + id: "B1".to_string(), + description: "".to_string(), + tasks: vec![Task { + id: "B1_1".to_string(), + description: "".to_string(), + action_items: vec![Action::custom("B1_1_ID")], + }], + }, + ], + }), + ..Default::default() + }; + let quest_graph = QuestGraph::from(&quest); + let mut events = vec![ + Event { + // A1_1 + id: uuid::Uuid::new_v4().to_string(), + address: "0xA".to_string(), + action: Some(Action::custom("A1_1_ID")), + }, + Event { + // A1_1 + id: uuid::Uuid::new_v4().to_string(), + address: "0xA".to_string(), + action: Some(Action::custom("B1_1_ID")), + }, + ]; + + let mut state = QuestState::from(&quest_graph); + assert!(state.current_steps.contains_key("A1")); // branch 1 + assert!(state.current_steps.contains_key("B1")); // branch 2 + assert_eq!(state.current_steps.len(), 2); + assert!(state.steps_completed.is_empty()); + assert_eq!(state.steps_left, 2); + assert!(state.required_steps.contains(&"A1".to_string())); + assert!(state.required_steps.contains(&"B1".to_string())); + + state = state.apply_event(&quest_graph, &events.remove(0)); + assert_eq!(state.current_steps.len(), 1); + assert!(state.current_steps.contains_key("B1")); + assert!(state.steps_completed.contains(&"A1".to_string())); + assert_eq!(state.steps_left, 1); + + state = state.apply_event(&quest_graph, &events.remove(0)); + assert_eq!(state.current_steps.len(), 0); + assert!(state.steps_completed.contains(&"A1".to_string())); + assert!(state.steps_completed.contains(&"B1".to_string())); + assert!(state.is_completed()) + } }