initial commit

This commit is contained in:
Adrian Malacoda 2017-02-18 23:34:41 -06:00
commit 94c5952a0c
6 changed files with 262 additions and 0 deletions

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name="pvn"
version="0.0.1"
authors=["Adrian Malacoda <adrian.malacoda@monarch-pass.net>"]
[dependencies]
log = "0.3.6"
env_logger = "0.4.0"
lru-cache = "0.1.0"
select = "0.3.0"
hyper = "0.10.4"

20
README.md Normal file
View File

@ -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 <pirate_name> <ninja_name>`
#### 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`

36
src/lib.rs Normal file
View File

@ -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<hyper::error::Error> for Error {
fn from (err: hyper::error::Error) -> Error {
Error::Http(err)
}
}
impl From<std::io::Error> 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<String> {
if weapons == "None" {
return vec![];
}
weapons.split(", ").into_iter().map(String::from).collect()
}

27
src/main.rs Normal file
View File

@ -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);
}
}

84
src/ninjas/mod.rs Normal file
View File

@ -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<String, Ninja>,
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<Ninja, Error> {
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<String>
}
impl Ninja {
pub fn from_html (contents: &str) -> Result<Ninja, Error> {
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::<u8>().unwrap() }
"Pajamas:" => { ninja.pajamas = score.parse::<u8>().unwrap() },
"Pointy Things:" => { ninja.pointy_things = score.parse::<u8>().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
}
}

84
src/pirates/mod.rs Normal file
View File

@ -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<String, Pirate>,
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<Pirate, Error> {
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<String>
}
impl Pirate {
pub fn from_html (contents: &str) -> Result<Pirate, Error> {
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::<u8>().unwrap() }
"Drunkenness:" => { pirate.drunkenness = score.parse::<u8>().unwrap() },
"Booty:" => { pirate.booty = score.parse::<u8>().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
}
}