pub mod arena;
pub mod cli;
pub mod connstr;
pub mod cup;
pub mod cursor;
pub mod diff;
pub mod draw;
pub mod game;
pub mod listener;
pub mod lookahead;
pub mod observe;
pub mod protocol;
pub mod seat;
#[cfg(test)]
mod tests;
use std::ffi::OsString;
pub fn subcommand(args: &[OsString]) -> Option<std::process::ExitCode> {
let (command, rest) = args.split_first()?;
let command = command.to_str()?;
let outcome = match command {
"arena" => text(rest).and_then(arena::run),
"cup" => text(rest).and_then(cup::run),
"watch" => watch(rest),
"help" | "--help" | "-h" => {
print!("{USAGE}");
Ok(())
}
_ => return None,
};
Some(match outcome {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(e) => {
eprintln!("pinch-points {command}: {e}");
std::process::ExitCode::FAILURE
}
})
}
fn text(args: &[OsString]) -> Result<Vec<String>, String> {
args.iter()
.map(|arg| {
arg.to_str()
.map(str::to_string)
.ok_or_else(|| format!("{} is not UTF-8 text", arg.to_string_lossy()))
})
.collect()
}
const USAGE: &str = "\
pinch-points open the game
pinch-points arena ... play bots against the game's AI or each other
pinch-points cup serve ... run a competition between bots
pinch-points watch FILE watch a replay in the game's window
`pinch-points <command> --help` says more. Writing a bot: docs/bot-protocol.md
";
fn watch(args: &[OsString]) -> Result<(), String> {
let [path] = args else {
return Err("watch takes one replay file".into());
};
let replay = read_replay(std::path::Path::new(path))?;
crate::app::watch_file(replay);
Ok(())
}
fn read_replay(path: &std::path::Path) -> Result<crate::sim::Replay, String> {
let shown = path.display();
let text = std::fs::read_to_string(path).map_err(|e| format!("{shown}: {e}"))?;
crate::sim::Replay::parse(&text).map_err(|e| format!("{shown}: {e}"))
}
#[cfg(all(test, unix))]
mod args_tests {
use super::*;
use std::os::unix::ffi::OsStringExt;
fn not_utf8(prefix: &str) -> OsString {
let mut bytes = prefix.as_bytes().to_vec();
bytes.extend_from_slice(b"\xff\xfe");
OsString::from_vec(bytes)
}
#[test]
fn arguments_that_are_not_utf8_are_taken_as_given() {
assert!(subcommand(&[not_utf8("x")]).is_none());
assert!(text(&[OsString::from("--games"), not_utf8("3")]).is_err());
assert_eq!(
text(&[OsString::from("--games"), OsString::from("3")]),
Ok(vec!["--games".to_string(), "3".to_string()])
);
}
#[test]
fn a_replay_with_a_name_that_is_not_utf8_is_read() {
let level = crate::sim::Level::from_board("Arena", 3, crate::sim::classic_arena(false, 2));
let replay = crate::sim::Replay::new(level);
let mut name = std::env::temp_dir().into_os_string();
name.push("/");
name.push(not_utf8(&format!("pinch-watch-{}-", std::process::id())));
let path = std::path::PathBuf::from(name);
std::fs::write(&path, replay.to_text()).expect("write");
let read = read_replay(&path);
let _ = std::fs::remove_file(&path);
read.expect("a replay");
}
}