monument_cli/
calls.rs

1use bellframe::{method::LABEL_LEAD_END, PlaceNot, Stage};
2use itertools::Itertools;
3use monument::parameters::{
4    default_calling_positions, BaseCallType, CallId, DEFAULT_MISC_CALL_WEIGHT,
5};
6use serde::Deserialize;
7
8/// The values of the `base_calls` attribute
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum BaseCalls {
12    None,
13    Near,
14    Far,
15}
16
17impl BaseCalls {
18    pub fn as_monument_type(self) -> Option<BaseCallType> {
19        match self {
20            Self::Near => Some(BaseCallType::Near),
21            Self::Far => Some(BaseCallType::Far),
22            Self::None => None,
23        }
24    }
25}
26
27impl Default for BaseCalls {
28    fn default() -> Self {
29        Self::Near
30    }
31}
32
33/// The specification of a single call type used in a composition.
34#[derive(Debug, Clone, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct CustomCall {
37    place_notation: String,
38    symbol: String,
39    debug_symbol: Option<String>, // Deprecated in v0.13.0
40    #[serde(default = "lead_end")]
41    label: CallLabel,
42    /// Deprecated alias for `label`
43    lead_location: Option<CallLabel>,
44    // TODO: Make this only allow strings
45    calling_positions: Option<CallingPositions>,
46    #[serde(default = "default_misc_call_score")]
47    weight: f32,
48}
49
50#[derive(Debug, Clone, Deserialize)]
51#[serde(untagged)]
52pub enum CallLabel {
53    /// Call goes from/to the same label
54    Same(String),
55    /// Call goes from/to different labels (e.g. for cases like Leary's 23, which use 6ths place
56    /// calls in 8ths place methods)
57    Different { from: String, to: String },
58}
59
60impl CustomCall {
61    pub(super) fn as_monument_call(
62        &self,
63        id: CallId,
64        stage: Stage,
65    ) -> anyhow::Result<monument::parameters::Call> {
66        let place_notation = PlaceNot::parse(&self.place_notation, stage).map_err(|e| {
67            anyhow::Error::msg(format!(
68                "Can't parse place notation {:?} for call {:?}: {}",
69                self.place_notation, &self.symbol, e
70            ))
71        })?;
72        if self.lead_location.is_some() {
73            return Err(anyhow::Error::msg(
74                "`calls.lead_location` has been renamed to `label`",
75            ));
76        }
77        if self.debug_symbol.is_some() {
78            return Err(anyhow::Error::msg(
79                "`debug_symbol` is now calculated automatically.  Use `symbol = \"-\" for bobs.`",
80            ));
81        }
82        let (label_from, label_to) = match self.label.clone() {
83            CallLabel::Same(loc) => (loc.clone(), loc),
84            CallLabel::Different { from, to } => (from, to),
85        };
86        let calling_positions = match &self.calling_positions {
87            Some(c) => c.as_vec(),
88            None => default_calling_positions(&place_notation),
89        };
90
91        Ok(monument::parameters::Call {
92            id,
93            symbol: self.symbol.to_owned(),
94            calling_positions,
95            label_from,
96            label_to,
97            place_notation,
98            weight: self.weight,
99        })
100    }
101}
102
103/// The different ways the user can specify a set of calling positions
104#[derive(Debug, Clone, Deserialize)]
105#[serde(untagged, deny_unknown_fields)]
106enum CallingPositions {
107    /// The calling positions should be the `char`s in the given string
108    Str(String),
109    /// Each calling position is explicitly listed
110    List(Vec<String>),
111}
112
113impl CallingPositions {
114    /// Returns the same [`CallingPositions`] as `self`, but always expressed as a [`Vec`] of one
115    /// [`String`] per place.
116    fn as_vec(&self) -> Vec<String> {
117        match self {
118            CallingPositions::Str(s) => s.chars().map(|c| c.to_string()).collect_vec(),
119            CallingPositions::List(positions) => positions.clone(),
120        }
121    }
122}
123
124fn lead_end() -> CallLabel {
125    CallLabel::Same(LABEL_LEAD_END.to_owned())
126}
127
128fn default_misc_call_score() -> f32 {
129    DEFAULT_MISC_CALL_WEIGHT
130}