use std::rc::Rc; use std::io; use crate::slot::Slot; pub trait UserAPI { fn notify_new_game(&self); fn notify_game_ended(&self); fn notify_new_animal(&self, animal: &str); fn notify_victory(&self); fn answer_yes_no(&self, question: &str) -> bool; fn is_it_a(&self, animal: &str) -> bool; fn what_is_it(&self) -> String; fn how_to_tell_apart(&self, secret: &str, other: &str) -> (String, bool); } // --- storage --- mod storage { use std::io; use std::io::Read; use std::io::Write; use std::collections::HashMap; use std::fs::File; use super::NodeType; #[derive(Debug)] pub struct AnimalStorageEntry { pub kind: NodeType, pub text: String, pub yes_index: i32, pub no_index: i32, } impl AnimalStorageEntry { pub fn new(kind: NodeType, text: String, yes_index: i32, no_index: i32) -> Self { AnimalStorageEntry { kind, text, yes_index, no_index, } } } #[derive(Debug)] pub struct AnimalStorage { entries: HashMap, next_free: i32, } impl AnimalStorage { pub fn new() -> Self { AnimalStorage { entries: HashMap::new(), next_free: 0, } } pub fn reserve(&mut self) -> i32 { let index = self.next_free; self.next_free += 1; index } pub fn put(&mut self, pos: i32, entry: AnimalStorageEntry) { assert_eq!(false, self.entries.contains_key(&pos)); self.entries.insert(pos, entry); } pub fn get(&self, pos: i32) -> Option<&AnimalStorageEntry> { return self.entries.get(&pos); } pub fn to_file(&self, path: &str) -> io::Result<()> { let mut buf = String::new(); let mut sorted: Vec<_> = self.entries.iter().collect(); sorted.sort_by(|a, b| a.0.cmp(b.0)); for (n, e) in sorted { buf.push_str(&format!( "{},{},{},{}\n", n, e.yes_index, e.no_index, e.text )); } let mut file = File::create(path)?; file.write_all(buf.as_bytes())?; Ok(()) } pub fn from_file(path: &str) -> io::Result { let mut store = Self::new(); let mut s = String::new(); File::open(path)?.read_to_string(&mut s)?; let lines = s.lines() .map(|x| x.trim()) .filter(|x| !x.is_empty()); for line in lines { let pieces: Vec<&str> = line.splitn(4, ',').collect(); if let [num, yes, no, text] = pieces[..] { let num: i32 = num.parse().unwrap(); let yes: i32 = yes.parse().unwrap(); let no: i32 = no.parse().unwrap(); let kind = match (yes, no) { (0, 0) => NodeType::Animal, (_x, _y) if (_x == 0 || _y == 0) => panic!(format!("Structural error in file: {}", line)), _ => NodeType::Question, }; store.put(num, AnimalStorageEntry::new(kind, text.to_string(), yes, no)); } else { panic!(format!("Syntax error in file: {}", line)); } } Ok(store) } } pub trait StorageLoadStore { fn store(&self, store: &mut AnimalStorage, pos: i32); fn load(&mut self, store: &mut AnimalStorage, pos: i32); } impl StorageLoadStore for super::NodeStruct { fn store(&self, store: &mut AnimalStorage, pos: i32) { let yes_i = if self.branch_true.is_empty() { 0 } else { store.reserve() }; let no_i = if self.branch_false.is_empty() { 0 } else { store.reserve() }; store.put( pos, AnimalStorageEntry::new(self.kind, self.text.clone(), yes_i, no_i), ); println!("Write: {:?}, {} -> y {}, n {}", self.kind, self.text, yes_i, no_i); if yes_i != 0 { self.branch_true.lease().store(store, yes_i); } if no_i != 0 { self.branch_false.lease().store(store, no_i); } } fn load(&mut self, store: &mut AnimalStorage, pos: i32) { let entry = store.get(pos).unwrap(); self.text = entry.text.clone(); self.kind = entry.kind; let yes_index = entry.yes_index; let no_index = entry.no_index; if self.kind == NodeType::Question { let mut true_node = super::NodeStruct::new(); true_node.load(store, yes_index); self.branch_true.put(true_node); let mut false_node = super::NodeStruct::new(); false_node.load(store, no_index); self.branch_false.put(false_node); } } } } use self::storage::StorageLoadStore; // take the trait into scope // --- node structure --- #[derive(Debug, Copy, Clone, PartialEq)] pub enum NodeType { Question, Animal, } #[derive(Debug)] struct NodeStruct { kind: NodeType, text: String, branch_true: Slot, branch_false: Slot, } impl NodeStruct { pub fn new() -> NodeStruct { NodeStruct { kind: NodeType::Question, text: "".to_string(), branch_true: Slot::new(), branch_false: Slot::new() } } } // --- animal DB --- #[derive(Debug)] pub struct AnimalDB { root: Slot, } impl AnimalDB { pub fn new() -> AnimalDB { AnimalDB { root: Slot::new(), } } pub fn save(&self, path: &str) -> io::Result<()> { let mut store = storage::AnimalStorage::new(); let n = store.reserve(); self.root.lease().store(&mut store, n); if !path.is_empty() { store.to_file(path) } else { println!("{:#?}", store); Ok(()) } } pub fn load(&self, path: &str) -> io::Result<()> { let mut store = storage::AnimalStorage::from_file(path)?; let mut root_node = NodeStruct::new(); root_node.load(&mut store, 0); self.root.put(root_node); Ok(()) } fn insert_new_animal( &self, user: &dyn UserAPI, following: Rc, updated_branch: &Slot, ) { let new_name = user.what_is_it(); let (question, answer_for_new) = user.how_to_tell_apart(&new_name, &following.text); // we have to insert a new Question node between the parent node and the // Animal node we just found drop(following); // must drop this lease, or .take() will panic let old_animal = Rc::try_unwrap(updated_branch.take()).unwrap(); let new_q = NodeStruct { kind: NodeType::Question, text: question, branch_true: Slot::new(), branch_false: Slot::new(), }; let (new_animal_branch, old_animal_branch) = match answer_for_new { true => (&new_q.branch_true, &new_q.branch_false), false => (&new_q.branch_false, &new_q.branch_true), }; let new_animal = NodeStruct { kind: NodeType::Animal, text: new_name.clone(), branch_true: Slot::new(), branch_false: Slot::new(), }; new_animal_branch.put(new_animal); old_animal_branch.put(old_animal); assert!(updated_branch.is_empty()); updated_branch.put(new_q); user.notify_new_animal(&new_name); } fn play_game(&self, user: &dyn UserAPI) { // question node for the upcoming iteration of the loop let mut next = self.root.lease(); loop { let user_answer; let updated_branch; let following; if let NodeType::Animal = &next.kind { // this is a leaf node (happens only when a leaf is in root). // root is used as a parent reference for inserting a new // in-between question if needed. updated_branch = &self.root; // move from next instead of another lease // we can do this here, because updated_branch is borrowed // from root directly, so there would be no orphaned reference // like in the 'else' branch following = next; } else { user_answer = user.answer_yes_no(&next.text); // find which branch will be updated if this is a new animal updated_branch = match user_answer { true => &next.branch_true, false => &next.branch_false, }; // cannot overwrite next and use it in the rest of the function, // because updated_branch is borrowed from it and the references // would become invalid following = updated_branch.lease(); } if let NodeType::Animal = &following.kind { if user.is_it_a(&following.text) { user.notify_victory(); } else { self.insert_new_animal(user, following, updated_branch); } break; } else { // we found another Question node, proceed with questions. next = following; // move lease } } } //noinspection RsBorrowChecker pub fn start_game(&self, user: &dyn UserAPI) { user.notify_new_game(); if self.root.is_empty() { let name = user.what_is_it(); self.root.put(NodeStruct { kind: NodeType::Animal, text: name, branch_true: Slot::new(), branch_false: Slot::new(), }); } else { self.play_game(user); } user.notify_game_ended(); } }