commit 94c5952a0c72f81eed8ee17259e125382f78705a Author: Adrian Malacoda Date: Sat Feb 18 23:34:41 2017 -0600 initial commit diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2317416 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name="pvn" +version="0.0.1" +authors=["Adrian Malacoda "] + +[dependencies] +log = "0.3.6" +env_logger = "0.4.0" +lru-cache = "0.1.0" +select = "0.3.0" +hyper = "0.10.4" diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdcc9ef --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Pirate vs Ninja Client Library +This is a client for [Pirate vs Ninja](http://www.majcher.com/pvn/) written in Rust. It can be used standalone or as a library. + +## Usage +### Command Line +`cargo run ` + +#### Example + malacoda@kraken:~/Projects/pvn$ cargo run Pie "Windows 98" + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs + Running `target/debug/pvn Pie Windows\ 98` + Pirate { name: "Captain Pie", swashbuckling: 19, drunkenness: 9, booty: 15, weapons: ["Cannon", "Broken Bottle", "Belaying Pin"] } => 46 + Ninja { name: "Ninja Master Windows 98", sneakiness: 9, pajamas: 15, pointy_things: 19, weapons: ["Poison Dart", "Thousand Blossom Finger", "Death Touch"] } => 46 + It's a tie! + +### Library +The library provides the `Pirates` and `Ninjas` types, which each keep a cache of `Pirate` and `Ninja` objects. Given a `Pirates` object, you can call `pirates.get(&str)` to obtain a reference to the `Pirate` with the given name. Or, you can create a `Pirate` or `Ninja` by passing in HTML to `Pirate::from_html(&str)`. + +#### Example +See `src/main.rs` diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0d65ac7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,36 @@ +extern crate lru_cache; +extern crate hyper; +extern crate select; + +pub mod pirates; +pub mod ninjas; + +#[derive(Debug)] +pub enum Error { + Http(hyper::error::Error), + Io(std::io::Error) +} + +impl From for Error { + fn from (err: hyper::error::Error) -> Error { + Error::Http(err) + } +} + +impl From for Error { + fn from (err: std::io::Error) -> Error { + Error::Io(err) + } +} + +pub trait Fighter { + fn power (&self) -> u8; +} + +pub fn parse_weapons (weapons: &str) -> Vec { + if weapons == "None" { + return vec![]; + } + + weapons.split(", ").into_iter().map(String::from).collect() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..19183be --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +extern crate pvn; + +use pvn::Fighter; +use pvn::pirates::Pirates; +use pvn::ninjas::Ninjas; +use std::env; + +fn main () { + let mut pirates = Pirates::new(); + let mut ninjas = Ninjas::new(); + + let pirate_name = &env::args().nth(1).expect("Expected pirate name as first argument")[..]; + let pirate = pirates.get(pirate_name).unwrap(); + println!("{:?} => {}", pirate, pirate.power()); + + let ninja_name = &env::args().nth(2).expect("Expected ninja name as second argument")[..]; + let ninja = ninjas.get(ninja_name).unwrap(); + println!("{:?} => {}", ninja, ninja.power()); + + if ninja.power() == pirate.power() { + println!("It's a tie!", ) + } else if ninja.power() > pirate.power() { + println!("Winner: {}!", ninja.name); + } else { + println!("Winner: {}!", pirate.name); + } +} diff --git a/src/ninjas/mod.rs b/src/ninjas/mod.rs new file mode 100644 index 0000000..8cfb47d --- /dev/null +++ b/src/ninjas/mod.rs @@ -0,0 +1,84 @@ +use lru_cache::LruCache; +use hyper::client::Client; +use select::document::Document; +use select::predicate::Class; + +use std::io::Read; + +use {Error, Fighter, parse_weapons}; + +pub struct Ninjas { + cache: LruCache, + client: Client +} + +impl Ninjas { + pub fn new () -> Ninjas { + Ninjas::with_cache_size(30) + } + + pub fn with_cache_size (cache_size: usize) -> Ninjas { + Ninjas { + cache: LruCache::new(cache_size), + client: Client::new() + } + } + + pub fn get (&mut self, name: &str) -> Result<&Ninja, Error> { + if self.cache.contains_key(name) { + return Result::Ok(self.cache.get_mut(name).unwrap()) + } + + let ninja = self.get_by_name(name)?; + self.cache.insert(String::from(name), ninja); + Result::Ok(self.cache.get_mut(name).unwrap()) + } + + fn get_by_name (&self, name: &str) -> Result { + let mut contents = String::new(); + self.client.get(&format!("http://www.majcher.com/pvn/pvn.cgi?a=n&n1={}", name)).send()?.read_to_string(&mut contents)?; + Ninja::from_html(&contents[..]) + } +} + +#[derive(Debug)] +pub struct Ninja { + pub name: String, + pub sneakiness: u8, + pub pajamas: u8, + pub pointy_things: u8, + pub weapons: Vec +} + +impl Ninja { + pub fn from_html (contents: &str) -> Result { + let document = Document::from(contents); + let mut ninja = Ninja { + name: String::from(document.find(Class("ninja-name")).first().unwrap().text()), + sneakiness: 0, + pajamas: 0, + pointy_things: 0, + weapons: vec![] + }; + + for node in document.find(Class("ninja-skill")).iter() { + let skill = node.text(); + let score = node.parent().unwrap().find(Class("ninja-score")).first().unwrap().text(); + match &skill[..] { + "Sneakiness:" => { ninja.sneakiness = score.parse::().unwrap() } + "Pajamas:" => { ninja.pajamas = score.parse::().unwrap() }, + "Pointy Things:" => { ninja.pointy_things = score.parse::().unwrap() }, + "Weapons:" => { ninja.weapons = parse_weapons(&score[..]) } + _ => { } + } + } + + Result::Ok(ninja) + } +} + +impl Fighter for Ninja { + fn power (&self) -> u8 { + self.sneakiness + self.pajamas + self.pointy_things + self.weapons.len() as u8 + } +} diff --git a/src/pirates/mod.rs b/src/pirates/mod.rs new file mode 100644 index 0000000..f789990 --- /dev/null +++ b/src/pirates/mod.rs @@ -0,0 +1,84 @@ +use lru_cache::LruCache; +use hyper::client::Client; +use select::document::Document; +use select::predicate::Class; + +use std::io::Read; + +use {Error, Fighter, parse_weapons}; + +pub struct Pirates { + cache: LruCache, + client: Client +} + +impl Pirates { + pub fn new () -> Pirates { + Pirates::with_cache_size(30) + } + + pub fn with_cache_size (cache_size: usize) -> Pirates { + Pirates { + cache: LruCache::new(cache_size), + client: Client::new() + } + } + + pub fn get (&mut self, name: &str) -> Result<&Pirate, Error> { + if self.cache.contains_key(name) { + return Result::Ok(self.cache.get_mut(name).unwrap()) + } + + let pirate = self.get_by_name(name)?; + self.cache.insert(String::from(name), pirate); + Result::Ok(self.cache.get_mut(name).unwrap()) + } + + fn get_by_name (&self, name: &str) -> Result { + let mut contents = String::new(); + self.client.get(&format!("http://www.majcher.com/pvn/pvn.cgi?a=p&n1={}", name)).send()?.read_to_string(&mut contents)?; + Pirate::from_html(&contents[..]) + } +} + +#[derive(Debug)] +pub struct Pirate { + pub name: String, + pub swashbuckling: u8, + pub drunkenness: u8, + pub booty: u8, + pub weapons: Vec +} + +impl Pirate { + pub fn from_html (contents: &str) -> Result { + let document = Document::from(contents); + let mut pirate = Pirate { + name: String::from(document.find(Class("pirate-name")).first().unwrap().text()), + swashbuckling: 0, + drunkenness: 0, + booty: 0, + weapons: vec![] + }; + + for node in document.find(Class("pirate-skill")).iter() { + let skill = node.text(); + let score = node.parent().unwrap().find(Class("pirate-score")).first().unwrap().text(); + match &skill[..] { + "Swashbuckling:" => { pirate.swashbuckling = score.parse::().unwrap() } + "Drunkenness:" => { pirate.drunkenness = score.parse::().unwrap() }, + "Booty:" => { pirate.booty = score.parse::().unwrap() }, + "Weapons:" => { pirate.weapons = parse_weapons(&score[..]) } + _ => { } + } + } + + Result::Ok(pirate) + } +} + +impl Fighter for Pirate { + fn power (&self) -> u8 { + self.swashbuckling + self.drunkenness + self.booty + self.weapons.len() as u8 + } +}