Browse Source

code import, working engine with automated users for testing

master
Ondřej Hruška 4 years ago
parent
commit
0ff10f5c45
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      .gitignore
  2. 4
      Cargo.lock
  3. 7
      Cargo.toml
  4. 15
      animals.iml
  5. 183
      src/animals.rs
  6. 221
      src/main.rs
  7. 254
      src/slot.rs

3
.gitignore vendored

@ -0,0 +1,3 @@
/target
**/*.rs.bk
.idea

4
Cargo.lock generated

@ -0,0 +1,4 @@
[[package]]
name = "animals"
version = "0.1.0"

7
Cargo.toml

@ -0,0 +1,7 @@
[package]
name = "animals"
version = "0.1.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018"
[dependencies]

15
animals.iml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RUST_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

183
src/animals.rs

@ -0,0 +1,183 @@
use std::cell::Cell;
use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use crate::slot::Slot;
use std::rc::Rc;
#[derive(Debug)]
enum NodeType {
Question,
Animal,
}
#[derive(Debug)]
struct NodeStruct {
kind: NodeType,
text: String,
branch_true: Slot<NodeStruct>,
branch_false: Slot<NodeStruct>,
}
pub struct AnimalDB {
root: Slot<NodeStruct>,
enable_debug: bool,
}
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, other: &str) -> (String, bool);
}
impl AnimalDB {
pub fn new() -> AnimalDB {
AnimalDB {
root: Slot::new(),
enable_debug: false,
}
}
pub fn enable_debug(&mut self, yes: bool) {
self.enable_debug = yes;
}
fn debug(&self, s: &str) {
if self.enable_debug {
println!("{}", s);
}
}
fn insert_new_animal(
&self,
user: &dyn UserAPI,
following: Rc<NodeStruct>,
updated_branch: &Slot<NodeStruct>,
) {
let new_name = user.what_is_it();
let (question, answer_for_new) = user.how_to_tell_apart(&following.text);
self.debug(&format!(
"Updating DB with: ({}, y:{}, n:{})",
question,
if answer_for_new {
&new_name
} else {
&following.text
},
if answer_for_new {
&following.text
} else {
&new_name
}
));
// 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 mut 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),
};
user.notify_new_animal(&new_name);
let mut new_animal = NodeStruct {
kind: NodeType::Animal,
text: new_name,
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);
}
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.debug(&format!("Initializing empty DB with: ({})", name));
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();
}
}

221
src/main.rs

@ -0,0 +1,221 @@
use std::collections::HashMap;
mod animals;
mod slot;
struct AnswerMachine<'a> {
answer: String,
yes_no: HashMap<&'a str, bool>,
distinguish: HashMap<&'a str, (&'a str, bool)>,
}
impl<'a> AnswerMachine<'a> {
fn abort(&self, msg: String) -> ! {
println!("{}", msg);
std::process::exit(1);
}
}
impl<'a> animals::UserAPI for AnswerMachine<'a> {
fn notify_new_game(&self) {
println!("----- NEW GAME -----");
println!("Secret animal is: {}", self.answer);
}
fn notify_game_ended(&self) {
println!("Game ended.\n");
}
fn notify_new_animal(&self, animal: &str) {
println!("Learned a new animal: {}", animal);
}
fn notify_victory(&self) {
println!("Yay, found an answer!");
}
fn answer_yes_no(&self, question: &str) -> bool {
println!("{}", question);
let answer = match self.yes_no.get(question) {
Some(a) => *a,
None => {
self.abort(format!("* Missing answer for {}! *", &self.answer));
}
};
println!("> {}", match answer { true => "yes", false => "no" });
answer
}
fn is_it_a(&self, animal: &str) -> bool {
println!("Is it a {}?", animal);
let answer = animal == &self.answer;
println!("> {}", match answer { true => "yes", false => "no" });
answer
}
fn what_is_it(&self) -> String {
println!("What animal is it?");
println!("> {}", self.answer);
self.answer.clone()
}
fn how_to_tell_apart(&self, other: &str) -> (String, bool) {
println!("How to tell apart {} and {}?", self.answer, other);
let (s, b) = self.distinguish.get(other).unwrap_or_else(|| {
self.abort(format!("* I don't know how to tell apart {} and {}! *", self.answer, other));
});
println!("> {}", s);
println!("What is the answer for {}?", self.answer);
println!(
"> {}",
match *b { true => "yes", false => "no" }
);
(s.to_string(), *b)
}
}
// a little hack for map initializer literals
macro_rules! map(
{ $($key:expr => $value:expr),* } => {
{
let mut m = std::collections::HashMap::new();
$( m.insert($key, $value); )*
m
}
};
);
fn main() {
let mut db = animals::AnimalDB::new();
let duck = AnswerMachine {
answer: "duck".to_string(),
yes_no: map! {
"Does it quack?" => true
},
distinguish: map! {
"cow" => ("Does it moo?", false)
},
};
db.start_game(&duck);
let dog = AnswerMachine {
answer: "dog".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Does it woof?" => true,
"Is it wild?" => false
},
distinguish: map! {
"duck" => ("Does it quack?", false),
"wolf" => ("Is it wild?", false)
},
};
db.start_game(&dog);
let wolf = AnswerMachine {
answer: "wolf".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => true,
"Does it fly?" => false,
"Does it eat acorns?" => false
},
distinguish: map! {
"dog" => ("Is it wild?", true)
},
};
db.start_game(&wolf);
let cow = AnswerMachine {
answer: "cow".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => false,
"Does it woof?" => false,
"Does it give milk?" => true,
"Is it grown for meat?" => true
},
distinguish: map! {
"dog" => ("Does it woof?", false)
},
};
db.start_game(&cow);
let pig = AnswerMachine {
answer: "pig".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => false,
"Does it woof?" => false,
"Does it give milk?" => false
},
distinguish: map! {
"cow" => ("Does it give milk?", false)
},
};
db.start_game(&pig);
let goat = AnswerMachine {
answer: "goat".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => false,
"Does it woof?" => false,
"Does it give milk?" => true,
"Is it grown for meat?" => false
},
distinguish: map! {
"cow" => ("Is it grown for meat?", false)
},
};
db.start_game(&goat);
let raven = AnswerMachine {
answer: "raven".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => true,
"Does it woof?" => false,
"Does it fly?" => true
},
distinguish: map! {
"wolf" => ("Does it fly?", true)
},
};
db.start_game(&raven);
let boar = AnswerMachine {
answer: "boar".to_string(),
yes_no: map! {
"Does it quack?" => false,
"Is it wild?" => true,
"Does it woof?" => false,
"Does it fly?" => false
},
distinguish: map! {
"wolf" => ("Does it eat acorns?", true)
},
};
db.start_game(&boar);
// try how the DB works with the learned animals
println!("===== Testing known animals =====");
db.start_game(&cow);
db.start_game(&dog);
db.start_game(&wolf);
db.start_game(&duck);
db.start_game(&pig);
db.start_game(&goat);
db.start_game(&raven);
}

254
src/slot.rs

@ -0,0 +1,254 @@
use std::cell::{Ref, RefCell};
use std::fmt;
use std::rc::Rc;
/// Smart cell that wraps an optional reference-counted value,
/// which can be leased, temporarily borrowed, removed, or replaced.
pub struct Slot<T> {
v: RefCell<Option<Rc<T>>>,
}
impl<T> Slot<T> {
/// Create a slot
///
/// # Examples
///
/// ```rust
/// use slot::Slot;
/// let s : Slot<String> = Slot::new();
/// assert_eq!(true, s.is_empty());
/// ```
pub fn new() -> Slot<T> {
Slot {
v: RefCell::new(None),
}
}
/// Create a slot with an initial value
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// let x = String::from("lorem");
/// let s : Slot<String> = Slot::new_with(x);
///
/// assert_eq!("lorem", s.lease().as_str());
/// assert_eq!("lorem", s.take().as_str());
/// ```
pub fn new_with(value: T) -> Slot<T> {
Slot {
v: RefCell::new(Some(Rc::new(value))),
}
}
/// Take a hold of the inner value via an `Rc`.
///
/// # Panics
///
/// Panics if empty
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// let s : Slot<String> = Slot::new_with(String::from("lorem"));
///
/// let lease1 = s.lease();
/// let lease2 = s.lease();
///
/// assert_eq!("lorem", lease1.as_str());
/// assert_eq!("lorem", lease2.as_str());
///
/// s.put(String::from("ipsum")); // here we replace the value, but the old Rc's remain valid
///
/// assert_eq!("lorem", lease1.as_str());
/// assert_eq!("lorem", lease2.as_str());
///
/// let lease3 = s.lease();
/// assert_eq!("ipsum", lease3.as_str());
/// ```
pub fn lease(&self) -> Rc<T> {
Rc::clone(self.v.borrow().as_ref().unwrap())
}
/// Check if the container is empty
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// let s : Slot<String> = Slot::new();
/// assert!(s.is_empty());
///
/// let s = Slot::new_with(132);
/// assert!(! s.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.v.borrow().is_none()
}
/// Put a value into the slot. Returns the old value, if any.
///
/// # Panics
///
/// Panics if the value is currently borrowed.
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// use std::rc::Rc;
///
/// let s : Slot<i32> = Slot::new();
/// s.put(123);
///
/// assert!(! s.is_empty());
/// assert_eq!(123, Rc::try_unwrap(s.take()).unwrap());
/// ```
pub fn put(&self, value: T) -> Option<Rc<T>> {
self.v.replace(Some(Rc::new(value)))
}
/// Take a value out of the slot, setting it to `None`.
/// If there are any leases, they remain valid, as they point
/// to clones of the same `Rc`.
///
/// # Panics
///
/// Panics if empty
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// use std::rc::Rc;
///
/// let s : Slot<i32> = Slot::new();
/// s.put(789);
///
/// assert!(! s.is_empty());
/// let r = s.take();
/// assert!(s.is_empty());
///
/// assert_eq!(789, Rc::try_unwrap(r).unwrap());
/// ```
pub fn take(&self) -> Rc<T> {
let x = self.v.replace(None);
x.unwrap()
}
/// Empty the slot. This is equivalent to `take()`, except the old value is discarded, if any.
///
/// # Examples
///
/// ```
/// use slot::Slot;
///
/// let s : Slot<i32> = Slot::new();
/// s.put(123);
///
/// assert!(! s.is_empty());
/// s.clear();
/// assert!(s.is_empty());
///
/// s.clear(); // this is allowed - no-op if empty
/// ```
pub fn clear(&self) {
self.v.replace(None);
}
/// Borrow. you must call `.as_ref().unwrap()` to get a reference to the inner value,
/// but this reference lives only as long as the `Ref` it came from, so it can't be
/// done inside the function.
///
/// It is possible to get `Ref(None)` if the container is currently empty.
///
/// # Examples
///
/// ```
/// use slot::Slot;
/// use std::rc::Rc;
///
/// let s : Slot<i32> = Slot::new();
/// s.borrow(); // no panic
///
/// let s = Slot::new_with("aaa".to_string());
///
/// assert_eq!("aaa", format!("{}", s.borrow().as_ref().unwrap()));
/// ```
pub fn borrow(&self) -> Ref<Option<Rc<T>>> {
self.v.borrow()
}
}
impl<T> fmt::Display for Slot<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.is_empty() {
true => write!(f, "nil"),
false => write!(f, "{}", self.borrow().as_ref().unwrap()),
}
}
}
impl<T> fmt::Debug for Slot<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let b = self.borrow();
match b.is_none() {
true => write!(f, "Slot(None)"),
false => {
let unwrapped = b.as_ref().unwrap();
if f.alternate() {
write!(
f,
"Slot(Rc({}+{}) -> {:#?})",
Rc::strong_count(unwrapped),
Rc::weak_count(unwrapped),
unwrapped
)
} else {
write!(
f,
"Slot(Rc({}+{}) -> {:?})",
Rc::strong_count(unwrapped),
Rc::weak_count(unwrapped),
unwrapped
)
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::slot::Slot;
#[test]
#[should_panic]
fn take_from_empty_panics() {
let s: Slot<i32> = Slot::new();
let _s = s.take();
}
#[test]
#[should_panic]
fn take_from_borrowed_panics() {
let s: Slot<i32> = Slot::new();
let _a = s.borrow();
let _s = s.take();
}
#[test]
#[should_panic]
fn lease_from_empty_panics() {
let s: Slot<i32> = Slot::new();
let _a = s.lease();
}
}
Loading…
Cancel
Save