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
#![deny(clippy::expect_used)]
17
#![deny(clippy::indexing_slicing)]
18
#![deny(clippy::panic)]
19
#![deny(clippy::unwrap_used)]
20

            
21
use std::{
22
    io::{BufReader, Write},
23
    net::{Shutdown, TcpListener, TcpStream},
24
    thread,
25
};
26

            
27
use clap::{self, CommandFactory, Parser};
28

            
29
use hnefatafl_copenhagen::{
30
    COPYRIGHT, SERVER_PORT, game::Game, read_response, status::Status, utils::clear_screen,
31
    write_command,
32
};
33

            
34
/// A Hnefatafl Copenhagen Server
35
///
36
/// This is a TCP server that listens for HTP engines
37
/// to connect and then plays them against each other.
38
#[derive(Parser, Debug)]
39
#[command(version, about)]
40
struct Args {
41
    /// Listen for HTP drivers on host and port
42
    #[arg(default_value = "0.0.0.0", index = 1, value_name = "host")]
43
    host: String,
44

            
45
    /// Build the manpage
46
    #[arg(long)]
47
    man: bool,
48
}
49

            
50
fn main() -> anyhow::Result<()> {
51
    let args = Args::parse();
52

            
53
    if args.man {
54
        let mut buffer: Vec<u8> = Vec::default();
55
        let cmd = Args::command().name("hnefatafl-server").long_version(None);
56
        let man = clap_mangen::Man::new(cmd).date("2025-11-21");
57

            
58
        man.render(&mut buffer)?;
59
        write!(buffer, "{COPYRIGHT}")?;
60

            
61
        std::fs::write("hnefatafl-server.1", buffer)?;
62
        return Ok(());
63
    }
64

            
65
    let mut address = args.host;
66
    address.push_str(SERVER_PORT);
67
    start(&address)
68
}
69

            
70
struct Htp {
71
    attacker_connection: TcpStream,
72
    defender_connection: TcpStream,
73
}
74

            
75
impl Htp {
76
    fn start(&mut self) -> anyhow::Result<()> {
77
        let mut attacker_reader = BufReader::new(self.attacker_connection.try_clone()?);
78
        let mut defender_reader = BufReader::new(self.defender_connection.try_clone()?);
79

            
80
        let mut game = Game::default();
81

            
82
        loop {
83
            write_command("generate_move attacker\n", &mut self.attacker_connection)?;
84
            let attacker_move = read_response(&mut attacker_reader)?;
85
            game.read_line(&attacker_move)?;
86
            write_command(&attacker_move, &mut self.defender_connection)?;
87

            
88
            clear_screen()?;
89
            println!("{game}");
90

            
91
            if game.status != Status::Ongoing {
92
                break;
93
            }
94

            
95
            write_command("generate_move defender\n", &mut self.defender_connection)?;
96
            let defender_move = read_response(&mut defender_reader)?;
97
            game.read_line(&defender_move)?;
98
            write_command(&defender_move, &mut self.attacker_connection)?;
99

            
100
            clear_screen()?;
101
            println!("{game}");
102

            
103
            if game.status != Status::Ongoing {
104
                break;
105
            }
106
        }
107

            
108
        self.attacker_connection.shutdown(Shutdown::Both)?;
109
        self.defender_connection.shutdown(Shutdown::Both)?;
110

            
111
        Ok(())
112
    }
113
}
114

            
115
fn start(address: &str) -> anyhow::Result<()> {
116
    let listener = TcpListener::bind(address)?;
117
    println!("listening on {address} ...");
118

            
119
    let mut players = Vec::new();
120

            
121
    for stream in listener.incoming() {
122
        let stream_1 = stream?;
123

            
124
        if let Some(stream_2) = players.pop() {
125
            let mut game = Htp {
126
                attacker_connection: stream_1,
127
                defender_connection: stream_2,
128
            };
129

            
130
            thread::spawn(move || game.start());
131
        } else {
132
            players.push(stream_1);
133
        }
134
    }
135

            
136
    Ok(())
137
}