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
#![deny(clippy::expect_used)]
20
#![deny(clippy::indexing_slicing)]
21
#![deny(clippy::panic)]
22
#![deny(clippy::unwrap_used)]
23

            
24
use std::{
25
    io::{BufReader, Write},
26
    net::{Shutdown, TcpListener, TcpStream},
27
    thread,
28
};
29

            
30
use clap::{self, CommandFactory, Parser};
31

            
32
use hnefatafl_copenhagen::{
33
    COPYRIGHT, SERVER_PORT, game::Game, read_response, status::Status, utils::clear_screen,
34
    write_command,
35
};
36

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

            
48
    /// Build the manpage
49
    #[arg(long)]
50
    man: bool,
51
}
52

            
53
fn main() -> anyhow::Result<()> {
54
    let args = Args::parse();
55

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

            
61
        man.render(&mut buffer)?;
62
        write!(buffer, "{COPYRIGHT}")?;
63

            
64
        std::fs::write("hnefatafl-server.1", buffer)?;
65
        return Ok(());
66
    }
67

            
68
    let mut address = args.host;
69
    address.push_str(SERVER_PORT);
70
    start(&address)
71
}
72

            
73
struct Htp {
74
    attacker_connection: TcpStream,
75
    defender_connection: TcpStream,
76
}
77

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

            
83
        let mut game = Game::default();
84

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

            
91
            clear_screen()?;
92
            println!("{game}");
93

            
94
            if game.status != Status::Ongoing {
95
                break;
96
            }
97

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

            
103
            clear_screen()?;
104
            println!("{game}");
105

            
106
            if game.status != Status::Ongoing {
107
                break;
108
            }
109
        }
110

            
111
        self.attacker_connection.shutdown(Shutdown::Both)?;
112
        self.defender_connection.shutdown(Shutdown::Both)?;
113

            
114
        Ok(())
115
    }
116
}
117

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

            
122
    let mut players = Vec::new();
123

            
124
    for stream in listener.incoming() {
125
        let stream_1 = stream?;
126

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

            
133
            thread::spawn(move || game.start());
134
        } else {
135
            players.push(stream_1);
136
        }
137
    }
138

            
139
    Ok(())
140
}