xiangqi_tui 0.1.0

Chinese chess (Xiangqi) TUI client with UCI/UCCI engine and opening book support
use crate::engine::EngineProtocol;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingsFieldKind {
    Text,
    Bool,
    Cycle,
    Number,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingsField {
    EnginePath,
    EngineProtocol,
    EngineThreads,
    EngineHashMb,
    EngineSkill,
    EngineMultiPv,
    EngineSearchLimit,
    EngineMovetimeMs,
    EngineSearchDepth,
    EngineSearchNodes,
    BookLocalPath,
    BookLocalEnabled,
    BookCloudEnabled,
    BookPickMode,
    BookMaxHalfmoves,
}

impl SettingsField {
    /// 开局库区第一行(其上方渲染分隔线)。
    pub const FIRST_BOOK: Self = Self::BookLocalPath;

    pub const ALL: [SettingsField; 15] = [
        Self::EnginePath,
        Self::EngineProtocol,
        Self::EngineThreads,
        Self::EngineHashMb,
        Self::EngineSkill,
        Self::EngineMultiPv,
        Self::EngineSearchLimit,
        Self::EngineMovetimeMs,
        Self::EngineSearchDepth,
        Self::EngineSearchNodes,
        Self::BookLocalPath,
        Self::BookLocalEnabled,
        Self::BookCloudEnabled,
        Self::BookPickMode,
        Self::BookMaxHalfmoves,
    ];

    pub fn label(self) -> &'static str {
        match self {
            Self::EnginePath => "引擎路径",
            Self::EngineProtocol => "协议",
            Self::EngineThreads => "线程数",
            Self::EngineHashMb => "Hash(MB)",
            Self::EngineSkill => "棋力 Skill",
            Self::EngineMultiPv => "MultiPV",
            Self::EngineSearchLimit => "电脑走子",
            Self::EngineMovetimeMs => "时限(ms)",
            Self::EngineSearchDepth => "深度",
            Self::EngineSearchNodes => "节点",
            Self::BookLocalPath => "本地库路径",
            Self::BookLocalEnabled => "启用本地库",
            Self::BookCloudEnabled => "启用云库",
            Self::BookPickMode => "库招选取",
            Self::BookMaxHalfmoves => "开局库最多步",
        }
    }

    pub fn kind(self) -> SettingsFieldKind {
        match self {
            Self::EnginePath | Self::BookLocalPath => SettingsFieldKind::Text,
            Self::BookLocalEnabled | Self::BookCloudEnabled => SettingsFieldKind::Bool,
            Self::EngineProtocol | Self::BookPickMode => SettingsFieldKind::Cycle,
            Self::EngineSearchLimit => SettingsFieldKind::Cycle,
            Self::EngineThreads
            | Self::EngineHashMb
            | Self::EngineSkill
            | Self::EngineMultiPv
            | Self::EngineMovetimeMs
            | Self::EngineSearchDepth
            | Self::EngineSearchNodes
            | Self::BookMaxHalfmoves => SettingsFieldKind::Number,
        }
    }

    pub fn hint(self) -> &'static str {
        match self {
            Self::EnginePath => "Enter 在 C 区输入路径,Esc 返回。",
            Self::BookLocalPath => "Enter 在 C 区输入库路径,Esc 返回。",
            Self::EngineProtocol => "←/→ 或 Enter 在 C 区输入 uci/ucci。",
            Self::BookPickMode => "←/→ 切换;Enter 输入 optimal/positive_random。",
            Self::BookLocalEnabled | Self::BookCloudEnabled => "空格切换;Enter 输入 0/1。",
            Self::EngineThreads => "←/→ 微调;Enter 在 C 区输入数字(1~64)。",
            Self::EngineHashMb => "←/→ 微调;Enter 在 C 区输入 MB(64~8192)。",
            Self::EngineSkill => "←/→ 微调;Enter 在 C 区输入(0~20)。",
            Self::EngineMultiPv => "←/→ 微调;Enter 在 C 区输入(1~5)。",
            Self::EngineSearchLimit => {
                "←/→ 切换固定时间/深度/节点;Enter 输入 movetime/depth/nodes。"
            }
            Self::EngineMovetimeMs => "固定时间模式用;←/→ 微调;Enter 输入毫秒。",
            Self::EngineSearchDepth => "固定深度模式用;←/→ 微调;Enter 输入层数。",
            Self::EngineSearchNodes => "固定节点模式用;←/→ 微调;Enter 输入节点数。",
            Self::BookMaxHalfmoves => "←/→ 微调;Enter 在 C 区输入步数(0=不用库)。",
        }
    }

    pub fn next(self) -> Self {
        let index = Self::ALL.iter().position(|f| *f == self).unwrap_or(0);
        Self::ALL[(index + 1) % Self::ALL.len()]
    }

    pub fn prev(self) -> Self {
        let index = Self::ALL.iter().position(|f| *f == self).unwrap_or(0);
        Self::ALL[(index + Self::ALL.len() - 1) % Self::ALL.len()]
    }
}

pub fn pick_mode_label(mode: &str) -> &'static str {
    if mode == "positive_random" {
        "正向随机"
    } else {
        "最优"
    }
}

pub fn cycle_pick_mode(mode: &str, delta: isize) -> String {
    let modes = ["optimal", "positive_random"];
    let index = modes.iter().position(|m| *m == mode).unwrap_or(0) as isize;
    let next = (index + delta).rem_euclid(modes.len() as isize) as usize;
    modes[next].to_string()
}

pub fn cycle_protocol(protocol: EngineProtocol, delta: isize) -> EngineProtocol {
    if delta.rem_euclid(2) == 0 {
        return protocol;
    }
    match protocol {
        EngineProtocol::Uci => EngineProtocol::Ucci,
        EngineProtocol::Ucci => EngineProtocol::Uci,
    }
}

pub fn clamp_threads(v: i32) -> u8 {
    v.clamp(1, 64) as u8
}

pub fn bump_hash_mb(current: u32, delta: isize) -> u32 {
    let steps = [64_u32, 128, 256, 512, 1024, 2048, 4096, 8192];
    let index = steps.iter().position(|s| *s == current).unwrap_or(3);
    let next = (index as isize + delta).clamp(0, steps.len() as isize - 1) as usize;
    steps[next]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn cycle_pick_mode_roundtrip() {
        assert_eq!(cycle_pick_mode("optimal", 1), "positive_random");
        assert_eq!(cycle_pick_mode("positive_random", 1), "optimal");
    }
}