extern crate nalgebra as na;
extern crate serde_json;
use std::collections::HashMap;
use std::f64::consts::PI;
use std::fs;
use std::path::PathBuf;
use std::fs::File;
use std::process;
pub mod colors {
pub struct Color {
value: &'static str,
}
impl Color {
pub fn value(&self) -> &str {
self.value
}
}
impl Default for Color {
fn default() -> Color {
GROUND
}
}
pub const GROUND: Color = Color { value: "#FDD9B5" }; pub const YELLOW: Color = Color { value: "#FDEE00" }; pub const GREEN: Color = Color { value: "#00A550" }; pub const RUSSET: Color = Color { value: "#CD7F32" }; pub const GREY: Color = Color { value: "#ACACAC" }; pub const BROWN: Color = Color { value: "#7B3F00" }; pub const RED: Color = Color { value: "#DC143C" }; pub const BLUE: Color = Color { value: "#007FFF" }; pub const BARRIER: Color = Color { value: "#660000" }; pub const WHITE: Color = Color { value: "#FFFFFF" };
pub fn name_to_color(name: &String) -> Color {
match name.to_lowercase().as_str() {
"ground" => GROUND,
"yellow" => YELLOW,
"green" => GREEN,
"russet" => RUSSET,
"grey" => GREY,
"brown" => BROWN,
"red" => RED,
"blue" => BLUE,
"barrier" => BARRIER,
"white" => WHITE,
_ => Color { value: "#000000" },
}
}
}
fn edge_to_coordinate(edge: &str) -> na::Vector3<f64> {
match edge {
"N" => na::Vector3::new( 0.0, 0.5, 0.5),
"NE" => na::Vector3::new( 0.5, 0.5, 0.0),
"SE" => na::Vector3::new( 0.5, 0.0, -0.5),
"S" => na::Vector3::new( 0.0, -0.5, -0.5),
"SW" => na::Vector3::new(-0.5, -0.5, 0.0),
"NW" => na::Vector3::new(-0.5, 0.0, 0.5),
"C" => na::Vector3::new( 0.0, 0.0, 0.0),
c => panic!("Invalid edge code {}", c),
}
}
pub fn direction_to_angle(direction: &str) -> f64 {
match direction {
"N" => 0.0,
"NW" => -PI / 3.0,
"SW" => -PI * 2.0 / 3.0,
"S" => PI,
"SE" => PI * 2.0 / 3.0,
"NE" => PI / 3.0,
c => panic!("Invalid direction {}", c),
}
}
#[derive(Clone, Deserialize, Debug)]
#[serde(untagged)]
pub enum Coordinate {
Named(String),
HexSpace((f64, f64, f64)),
}
impl Coordinate {
pub fn as_vector(&self) -> na::Vector3<f64> {
match self {
&Coordinate::Named(ref name) => edge_to_coordinate(name.as_ref()),
&Coordinate::HexSpace(ref pos) =>
na::Vector3::new(pos.0, pos.1, pos.2),
}
}
}
pub trait TileSpec {
fn color(&self) -> colors::Color;
fn set_name(&mut self, name: String);
fn name(&self) -> &str;
fn paths(&self) -> Vec<Path>;
fn cities(&self) -> Vec<City>;
fn stops(&self) -> Vec<Stop>;
fn is_lawson(&self) -> bool;
fn arrows(&self) -> Vec<Coordinate> { vec![] }
fn revenue_track(&self) -> Option<RevenueTrack> { None }
fn terrain(&self) -> Option<Terrain> { None }
fn get_text<'a>(&'a self, &'a str) -> &'a str;
fn text_position(&self, usize) -> Option<na::Vector3<f64>>;
fn text_spec(&self) -> Vec<Text>;
fn orientation(&self) -> f64 { 0.0 }
}
#[derive(Deserialize)]
pub struct Tile {
base_tile: String,
color: String,
text: HashMap<String, String>,
#[serde(skip)]
definition: Option<TileDefinition>,
}
impl Tile {
pub fn set_definition(&mut self, definition: &TileDefinition) {
self.definition = Some(definition.clone());
}
pub fn base_tile(&self) -> String {
self.base_tile.clone()
}
}
impl Default for Tile {
fn default() -> Tile {
Tile {
base_tile: String::new(),
color: String::new(),
text: HashMap::new(),
definition: None,
}
}
}
impl TileSpec for Tile {
fn color(&self) -> colors::Color {
colors::name_to_color(&self.color)
}
fn name(&self) -> &str {
self.text.get("number").unwrap()
}
fn set_name(&mut self, name: String) {
self.text.insert("number".to_string(), name);
}
fn paths(&self) -> Vec<Path> {
self.definition.as_ref()
.expect("You must call set_definition() before using paths()")
.paths()
}
fn cities(&self) -> Vec<City> {
self.definition.as_ref()
.expect("You must call set_definition() before using cities()")
.cities()
}
fn stops(&self) -> Vec<Stop> {
self.definition.as_ref()
.expect("You must call set_definition() before using stops()")
.stops()
}
fn is_lawson(&self) -> bool {
self.definition.as_ref()
.expect("You must call set_definition() before using is_lawson()")
.is_lawson()
}
fn get_text(&self, id: &str) -> &str {
match self.text.get(id) {
Some(s) => s,
None => "",
}
}
fn text_position(&self, id: usize) -> Option<na::Vector3<f64>> {
self.definition.as_ref()
.expect("You must call set_definition() before using \
text_position()")
.text_position(id)
}
fn text_spec(&self) -> Vec<Text> {
self.definition.as_ref()
.expect("You must call set_definition() before using \
text_spec()")
.text_spec()
}
}
#[derive(Clone, Deserialize, Debug)]
#[serde(default)]
pub struct TileDefinition {
name: String,
paths: Vec<Path>,
cities: Vec<City>,
stops: Vec<Stop>,
is_lawson: bool,
text: Vec<Text>,
}
impl Default for TileDefinition {
fn default() -> TileDefinition {
TileDefinition {
name: "NoName".to_string(),
paths: vec![],
cities: vec![],
stops: vec![],
is_lawson: false,
text: vec![],
}
}
}
impl TileSpec for TileDefinition {
fn paths(&self) -> Vec<Path> { self.paths.clone() }
fn cities(&self) -> Vec<City> { self.cities.clone() }
fn stops(&self) -> Vec<Stop> { self.stops.clone() }
fn is_lawson(&self) -> bool { self.is_lawson }
fn color(&self) -> colors::Color { colors::GROUND }
fn set_name(&mut self, name: String) { self.name = name; }
fn name(&self) -> &str { self.name.as_str() }
fn get_text<'a>(&'a self, id: &'a str) -> &'a str {
match id {
"number" => self.name(),
x => x,
}
}
fn text_position(&self, id: usize) -> Option<na::Vector3<f64>> {
Some(self.text[id].position())
}
fn text_spec(&self) -> Vec<Text> {
let tile_number = Text {
id: "number".to_string(),
position: Coordinate::HexSpace((0.0, 0.0, -0.9)),
anchor: TextAnchor::End,
size: None,
weight: None,
};
let mut text = self.text.clone();
text.insert(0, tile_number);
text
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Path {
start: Coordinate,
end: Coordinate,
pub start_control: Option<Coordinate>,
pub end_control: Option<Coordinate>,
#[serde(default)]
is_bridge: bool,
}
impl Path {
pub fn start(&self) -> na::Vector3<f64> {
self.start.as_vector()
}
pub fn end(&self) -> na::Vector3<f64> {
self.end.as_vector()
}
pub fn is_bridge(&self) -> bool {
self.is_bridge
}
pub fn radius(&self) -> f64 {
let gentle_curve = 2.0_f64.sqrt() / 2.0;
if let (&Coordinate::Named(ref start), &Coordinate::Named(ref end))
= (&self.start, &self.end) {
if start.len() == 2 && end.len() == 2 &&
start.chars().nth(0) == end.chars().nth(0) {
return gentle_curve
} else if ((start.len() == 2 && end.len() == 1) ||
(start.len() == 1 && end.len() == 2)) &&
start.chars().nth(0) != end.chars().nth(0) {
return gentle_curve
}
}
1.0
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct City {
pub circles: u32,
pub text_id: String,
pub revenue_position: Coordinate,
position: Coordinate,
}
impl City {
pub fn position(&self) -> na::Vector3<f64> {
self.position.as_vector()
}
pub fn revenue_position(&self) -> na::Vector3<f64>{
self.revenue_position.as_vector()
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct Stop {
position: Coordinate,
pub text_id: String,
pub revenue_angle: i32,
}
impl Stop {
pub fn position(&self) -> na::Vector3<f64> {
self.position.as_vector()
}
}
#[derive(Deserialize, Debug, Clone)]
pub enum TextAnchor {
Start,
Middle,
End,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Text {
pub id: String,
position: Coordinate,
size: Option<String>,
pub weight: Option<u32>,
pub anchor: TextAnchor,
}
impl Text {
pub fn position(&self) -> na::Vector3<f64> {
self.position.as_vector()
}
pub fn size(&self) -> Option<&str> {
match self.size {
None => None,
Some(ref s) => Some(&s),
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct RevenueTrack {
position: Coordinate,
pub yellow: String,
pub green: Option<String>,
pub russet: Option<String>,
pub grey: Option<String>,
}
impl RevenueTrack {
pub fn position(&self) -> na::Vector3<f64> {
self.position.as_vector()
}
}
#[derive(Clone, Deserialize)]
pub struct Terrain {
position: Coordinate,
#[serde(rename="type")]
pub terrain_type: TerrainType,
pub cost: String,
}
impl Terrain {
pub fn position(&self) -> na::Vector3<f64> {
self.position.as_vector()
}
}
#[derive(Clone, Deserialize)]
#[serde(rename_all="lowercase")]
pub enum TerrainType {
Rough,
Hill,
Mountain,
River,
Marsh,
}
pub fn definitions(options: &super::Options)
-> HashMap<String, TileDefinition> {
println!("Reading tile definitions from file...");
let def_files: Vec<PathBuf> = match fs::read_dir("tiledefs") {
Err(err) => {
eprintln!("Couldn't open tile definitions directory: {:?}",
err.kind());
process::exit(1);
}
Ok(paths) => {
paths.map(|path| path.unwrap().path()).collect()
},
};
let mut definitions = HashMap::new();
for def in &def_files {
if def.extension().unwrap() != "json" {
continue;
}
if options.verbose {
println!("Parsing definition {}",
def.file_stem().unwrap().to_string_lossy());
}
let file = File::open(def).unwrap_or_else(|err| {
eprintln!("Couldn't open {}: {:?}", def.to_string_lossy(),
err.kind());
process::exit(1);
});
let mut tile: TileDefinition = serde_json::from_reader(file)
.unwrap_or_else(|err| {
eprintln!("Error parsing {}: {}", def.to_string_lossy(), err);
process::exit(1);
});
tile.set_name(String::from(def.file_stem()
.unwrap().to_string_lossy()));
definitions.insert(String::from(tile.name()), tile);
}
definitions
}