use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::client::LichessClient;
use crate::config::Host;
use crate::error::Result;
use crate::http;
#[derive(Debug)]
pub struct TablebaseApi<'a> {
client: &'a LichessClient,
}
impl<'a> TablebaseApi<'a> {
pub(crate) fn new(client: &'a LichessClient) -> Self {
Self { client }
}
pub async fn standard(&self, fen: &str, dtc: Option<&str>) -> Result<LichessTablebasePosition> {
self.lookup("/standard", fen, dtc).await
}
pub async fn atomic(&self, fen: &str) -> Result<LichessTablebasePosition> {
self.lookup("/atomic", fen, None).await
}
pub async fn antichess(&self, fen: &str) -> Result<LichessTablebasePosition> {
self.lookup("/antichess", fen, None).await
}
async fn lookup(
&self,
path: &str,
fen: &str,
dtc: Option<&str>,
) -> Result<LichessTablebasePosition> {
let request = self
.client
.request(Method::GET, Host::Tablebase, path)
.query(&[("dtc", dtc)])
.query(&[("fen", fen)]);
http::json(request, "LichessTablebasePosition").await
}
}
impl LichessClient {
#[must_use]
pub fn tablebase(&self) -> TablebaseApi<'_> {
TablebaseApi::new(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum LichessTablebaseCategory {
Win,
Unknown,
SyzygyWin,
MaybeWin,
CursedWin,
Draw,
BlessedLoss,
MaybeLoss,
SyzygyLoss,
Loss,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessTablebaseMove {
pub uci: String,
pub san: String,
pub category: LichessTablebaseCategory,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub zeroing: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtz: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub precise_dtz: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtc: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtm: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtw: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkmate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stalemate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_win: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_loss: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub insufficient_material: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessTablebasePosition {
pub category: LichessTablebaseCategory,
pub moves: Vec<LichessTablebaseMove>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtz: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub precise_dtz: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtc: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtm: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dtw: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkmate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stalemate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_win: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant_loss: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub insufficient_material: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_position_with_kebab_case_category() {
let json = r#"{"category":"cursed-win","dtz":1,"dtm":17,"checkmate":false,
"moves":[{"uci":"h7h8q","san":"h8=Q+","category":"loss","zeroing":true}]}"#;
let position: LichessTablebasePosition = serde_json::from_str(json).unwrap();
assert_eq!(position.category, LichessTablebaseCategory::CursedWin);
assert_eq!(position.dtz, Some(1));
assert_eq!(position.moves[0].category, LichessTablebaseCategory::Loss);
assert_eq!(position.moves[0].zeroing, Some(true));
}
}