use crate::{SgfError, SgfErrorKind};
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Color {
Black,
White,
}
#[derive(Debug, PartialEq, Clone)]
pub enum SgfToken {
Add { color: Color, coordinate: (u8, u8) },
Move { color: Color, coordinate: (u8, u8) },
Time { color: Color, time: u32 },
PlayerName { color: Color, name: String },
PlayerRank { color: Color, rank: String },
Komi(f32),
Event(String),
Copyright(String),
GameName(String),
Place(String),
Date(String),
Size(u32),
TimeLimit(u32),
Comment(String),
Unknown((String, String)),
Invalid((String, String)),
Square { coordinate: (u8, u8) },
Triangle { coordinate: (u8, u8) },
Label { label: String, coordinate: (u8, u8) },
}
impl SgfToken {
pub fn from_pair(base_ident: &str, value: &str) -> SgfToken {
let ident = base_ident
.chars()
.filter(|c| c.is_uppercase())
.collect::<String>();
let token: Option<SgfToken> = match ident.as_ref() {
"LB" => {
split_label_text(value).and_then(|(coord, label)| {
str_to_coordinates(coord).ok().map(|coordinate| {
SgfToken::Label{
label: label[1..].to_string(),
coordinate,
}
})
})
},
"SQ" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Square{
coordinate,
}
})
},
"TR" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Triangle{
coordinate,
}
})
},
"AB" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Add{
color: Color::Black,
coordinate,
}
})
},
"B" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Move{
color: Color::Black,
coordinate,
}
})
},
"BL" => {
value.parse().ok().map(|time| {
SgfToken::Time {
color: Color::Black,
time,
}
})
},
"PB" => Some(SgfToken::PlayerName {
color: Color::Black,
name: value.to_string(),
}),
"BR" => Some(SgfToken::PlayerRank {
color: Color::Black,
rank: value.to_string(),
}),
"AW" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Add{
color: Color::White,
coordinate,
}
})
}
"W" => {
str_to_coordinates(value).ok().map(|coordinate| {
SgfToken::Move{
color: Color::White,
coordinate,
}
})
}
"WL" => {
value.parse().ok().map(|time| {
SgfToken::Time {
color: Color::White,
time,
}
})
}
"PW" => Some(SgfToken::PlayerName {
color: Color::White,
name: value.to_string(),
}),
"WR" => Some(SgfToken::PlayerRank {
color: Color::White,
rank: value.to_string(),
}),
"KM" => {
value.parse().ok().map(|komi| {
SgfToken::Komi(komi)
})
}
"SZ" => {
value.parse().ok().map(|size| {
SgfToken::Size(size)
})
}
"TM" => {
value.parse().ok().map(|time| {
SgfToken::TimeLimit(time)
})
}
"EV" => Some(SgfToken::Event(value.to_string())),
"C" => Some(SgfToken::Comment(value.to_string())),
"GN" => Some(SgfToken::GameName(value.to_string())),
"CR" => Some(SgfToken::Copyright(value.to_string())),
"DT" => Some(SgfToken::Date(value.to_string())),
"PC" => Some(SgfToken::Place(value.to_string())),
_ => Some(SgfToken::Unknown((base_ident.to_string(), value.to_string()))),
};
match token {
Some(token) => token,
_ => SgfToken::Invalid((base_ident.to_string(), value.to_string()))
}
}
}
impl Into<String> for &SgfToken {
fn into(self) -> String {
match self {
SgfToken::Label{label, coordinate} => {
let value = coordinate_to_str(*coordinate);
format!("LB[{}:{}]", value, label)
},
SgfToken::Square{coordinate} => {
let value = coordinate_to_str(*coordinate);
format!("SQ[{}]", value)
}
SgfToken::Triangle{coordinate} => {
let value = coordinate_to_str(*coordinate);
format!("TR[{}]", value)
}
SgfToken::Add{color, coordinate} => {
let token = match color {
Color::Black => "AB",
Color::White => "AW"
};
let value = coordinate_to_str(*coordinate);
format!("{}[{}]", token, value)
},
SgfToken::Move{color, coordinate} => {
let token = match color {
Color::Black => "B",
Color::White => "W"
};
let value = coordinate_to_str(*coordinate);
format!("{}[{}]", token, value)
},
SgfToken::Time{color, time} => {
let token = match color {
Color::Black => "BL",
Color::White => "WL"
};
format!("{}[{}]", token, time)
},
SgfToken::PlayerName{color, name} => {
let token = match color {
Color::Black => "PB",
Color::White => "PW"
};
format!("{}[{}]", token, name)
},
SgfToken::PlayerRank{color, rank} => {
let token = match color {
Color::Black => "BR",
Color::White => "WR"
};
format!("{}[{}]", token, rank)
},
SgfToken::Komi(komi) => format!("KM[{}]", komi),
SgfToken::Size(size) => format!("SZ[{}]", size),
SgfToken::TimeLimit(time) => format!("TM[{}]", time),
SgfToken::Event(value) => format!("EV[{}]", value),
SgfToken::Comment(value) => format!("C[{}]", value),
SgfToken::GameName(value) => format!("GN[{}]", value),
SgfToken::Copyright(value) => format!("CR[{}]", value),
SgfToken::Date(value) => format!("DT[{}]", value),
SgfToken::Place(value) => format!("PC[{}]", value),
_ => panic!()
}
}
}
impl Into<String> for SgfToken {
fn into(self) -> String {
(&self).into()
}
}
fn coordinate_to_str(coordinate: (u8, u8)) -> String {
let conv = |n| {
n + if n >= 9 {
97
} else {
96
}
};
let x = conv(coordinate.0) as char;
let y = conv(coordinate.1) as char;
[x, y].iter().collect()
}
fn split_label_text(input: &str) -> Option<(&str, &str)> {
if input.len() >= 4 {
Some(input.split_at(2))
} else {
None
}
}
fn str_to_coordinates(input: &str) -> Result<(u8, u8), SgfError> {
if input.len() != 2 {
return Err(SgfErrorKind::ParseError.into());
}
let coords = input
.to_lowercase()
.as_bytes()
.iter()
.map(|&c| convert_u8_to_coordinate(c))
.take(2)
.collect::<Vec<_>>();
Ok((coords[0], coords[1]))
}
fn convert_u8_to_coordinate(c: u8) -> u8 {
let n = c - 96;
if n >= 9 {
n - 1
} else {
n
}
}