#![deny(clippy::expect_used)]
#![deny(clippy::indexing_slicing)]
#![deny(clippy::panic)]
#![deny(clippy::unwrap_used)]
use std::{
io::{BufReader, Write},
net::{Shutdown, TcpListener, TcpStream},
thread,
};
use clap::{self, CommandFactory, Parser};
use hnefatafl_copenhagen::{
COPYRIGHT, SERVER_PORT, game::Game, read_response, status::Status, utils::clear_screen,
write_command,
};
#[derive(Parser, Debug)]
#[command(version, about = "Copenhagen Hnefatafl Server Light")]
struct Args {
#[arg(default_value = "0.0.0.0", index = 1, value_name = "host")]
host: String,
#[arg(long)]
man: bool,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
if args.man {
let mut buffer: Vec<u8> = Vec::default();
let cmd = Args::command().name("hnefatafl-server").long_version(None);
let man = clap_mangen::Man::new(cmd).date("2025-11-21");
man.render(&mut buffer)?;
write!(buffer, "{COPYRIGHT}")?;
std::fs::write("hnefatafl-server.1", buffer)?;
return Ok(());
}
let mut address = args.host;
address.push_str(SERVER_PORT);
start(&address)
}
struct Htp {
attacker_connection: TcpStream,
defender_connection: TcpStream,
}
impl Htp {
fn start(&mut self) -> anyhow::Result<()> {
let mut attacker_reader = BufReader::new(self.attacker_connection.try_clone()?);
let mut defender_reader = BufReader::new(self.defender_connection.try_clone()?);
let mut game = Game::default();
loop {
write_command("generate_move attacker\n", &mut self.attacker_connection)?;
let attacker_move = read_response(&mut attacker_reader)?;
game.read_line(&attacker_move)?;
write_command(&attacker_move, &mut self.defender_connection)?;
clear_screen()?;
println!("{game}");
if game.status != Status::Ongoing {
break;
}
write_command("generate_move defender\n", &mut self.defender_connection)?;
let defender_move = read_response(&mut defender_reader)?;
game.read_line(&defender_move)?;
write_command(&defender_move, &mut self.attacker_connection)?;
clear_screen()?;
println!("{game}");
if game.status != Status::Ongoing {
break;
}
}
self.attacker_connection.shutdown(Shutdown::Both)?;
self.defender_connection.shutdown(Shutdown::Both)?;
Ok(())
}
}
fn start(address: &str) -> anyhow::Result<()> {
let listener = TcpListener::bind(address)?;
println!("listening on {address} ...");
let mut players = Vec::new();
for stream in listener.incoming() {
let stream_1 = stream?;
if let Some(stream_2) = players.pop() {
let mut game = Htp {
attacker_connection: stream_1,
defender_connection: stream_2,
};
thread::spawn(move || game.start());
} else {
players.push(stream_1);
}
}
Ok(())
}