1
// This file is part of hnefatafl-copenhagen.
2
//
3
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU Affero General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU Affero General Public License for more details.
12
//
13
// You should have received a copy of the GNU Affero General Public License
14
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
//
16
// SPDX-License-Identifier: AGPL-3.0-or-later
17
// SPDX-FileCopyrightText: 2025 David Campbell <david@hnefatafl.org>
18

            
19
#[cfg(any(target_family = "unix", target_family = "windows"))]
20
use std::process::Command;
21
use std::{env, fs::DirBuilder, path::PathBuf, process::ExitStatus, time::Duration};
22

            
23
use directories::ProjectDirs;
24
use env_logger::Builder;
25
use log::LevelFilter;
26

            
27
use crate::ai::{AI, AiBanal, AiBasic, AiMonteCarlo};
28

            
29
/// # Errors
30
///
31
/// If you don't choose banal, basic, or monte-carlo.
32
pub fn choose_ai(
33
    ai: &str,
34
    seconds: Option<u64>,
35
    depth: Option<u8>,
36
    sequential: bool,
37
) -> anyhow::Result<Box<dyn AI>> {
38
    match ai {
39
        "banal" => Ok(Box::new(AiBanal)),
40
        "basic" => {
41
            let depth = depth.unwrap_or(4);
42

            
43
            Ok(Box::new(AiBasic::new(depth, sequential)))
44
        }
45
        "monte-carlo" => {
46
            let seconds = seconds.unwrap_or(10);
47
            let depth = depth.unwrap_or(20);
48

            
49
            Ok(Box::new(AiMonteCarlo::new(
50
                Duration::from_secs(seconds),
51
                depth,
52
            )))
53
        }
54
        _ => Err(anyhow::Error::msg(
55
            "you must pass banal, basic, or monte-carlo to --ai",
56
        )),
57
    }
58
}
59

            
60
#[allow(clippy::missing_errors_doc)]
61
pub fn clear_screen() -> anyhow::Result<ExitStatus> {
62
    #[cfg(not(any(target_family = "unix", target_family = "windows")))]
63
    return Ok(ExitStatus::default());
64

            
65
    #[cfg(target_family = "unix")]
66
    let exit_status = Command::new("clear").status()?;
67

            
68
    #[cfg(target_family = "windows")]
69
    let exit_status = Command::new("cls").status()?;
70

            
71
    #[cfg(any(target_family = "unix", target_family = "windows"))]
72
    Ok(exit_status)
73
}
74

            
75
/// # Errors
76
///
77
/// If it fails to create the directory or the directory does not already exist.
78
pub fn create_data_folder() -> anyhow::Result<()> {
79
    let project_dir = ProjectDirs::from("org", "Hnefatafl Org", "hnefatafl-copenhagen");
80

            
81
    if let Some(project_dir) = project_dir {
82
        DirBuilder::new()
83
            .recursive(true)
84
            .create(project_dir.data_local_dir())?;
85
    }
86

            
87
    Ok(())
88
}
89

            
90
#[must_use]
91
pub fn data_file(file: &str) -> PathBuf {
92
    let project_dir = ProjectDirs::from("org", "Hnefatafl Org", "hnefatafl-copenhagen");
93

            
94
    let mut project_dir = if let Some(project_dir) = project_dir {
95
        project_dir.data_local_dir().to_path_buf()
96
    } else {
97
        PathBuf::new()
98
    };
99

            
100
    project_dir.push(file);
101
    project_dir
102
}
103

            
104
pub fn init_logger(module: &str, debug: bool, systemd: bool) {
105
    let mut builder = Builder::new();
106

            
107
    if systemd {
108
        builder.format_timestamp(None);
109
        builder.format_target(false);
110
    }
111

            
112
    if let Ok(var) = env::var("RUST_LOG") {
113
        builder.parse_filters(&var);
114
    } else if debug {
115
        builder.filter(Some(module), LevelFilter::Debug);
116
    } else {
117
        // If no RUST_LOG provided, default to logging at the Info level.
118
        #[cfg(not(feature = "debug"))]
119
        builder.filter(Some(module), LevelFilter::Info);
120
        #[cfg(feature = "debug")]
121
        builder.filter(Some(module), LevelFilter::Debug);
122
    }
123

            
124
    builder.init();
125
}
126

            
127
#[must_use]
128
pub fn split_whitespace_password(string: &str) -> (String, bool) {
129
    let mut ends_with_whitespace = false;
130

            
131
    if string.ends_with(|ch: char| ch.is_whitespace()) {
132
        ends_with_whitespace = true;
133
    }
134

            
135
    let mut string: String = string.split_whitespace().collect();
136

            
137
    if string.is_empty() {
138
        ends_with_whitespace = false;
139
    }
140

            
141
    if ends_with_whitespace {
142
        string.push(' ');
143
    }
144

            
145
    (string, ends_with_whitespace)
146
}