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
use std::{
20
    collections::{HashMap, HashSet},
21
    sync::{Arc, Mutex},
22
};
23

            
24
use jiff::Timestamp;
25
use rand::seq::SliceRandom;
26
use serde::{Deserialize, Serialize};
27

            
28
use crate::{Id, accounts::Accounts, glicko::Rating, server_game::ServerGame, status::Status};
29

            
30
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
31
pub struct Tournament {
32
    pub id: u64,
33
    pub players: HashSet<String>,
34
    pub players_left: HashSet<String>,
35
    pub date: Timestamp,
36
    pub groups: Option<Vec<Vec<Arc<Mutex<Group>>>>>,
37
    pub tournament_games: HashMap<Id, Arc<Mutex<Group>>>,
38
}
39

            
40
impl Tournament {
41
    #[allow(clippy::float_cmp, clippy::too_many_lines)]
42
    #[must_use]
43
    pub fn game_over(&mut self, game: &ServerGame) -> bool {
44
        let mut next_round = false;
45

            
46
        if let Some(group) = self.tournament_games.get_mut(&game.id) {
47
            if let Ok(mut group) = group.lock() {
48
                match game.game.status {
49
                    Status::AttackerWins => {
50
                        if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
51
                            record.wins += 1;
52
                        }
53
                        if let Some(record) = group.records.get_mut(game.defender.as_str()) {
54
                            record.losses += 1;
55
                        }
56
                    }
57
                    Status::Draw => {
58
                        if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
59
                            record.draws += 1;
60
                        }
61
                        if let Some(record) = group.records.get_mut(game.defender.as_str()) {
62
                            record.draws += 1;
63
                        }
64
                    }
65
                    Status::Ongoing => {}
66
                    Status::DefenderWins => {
67
                        if let Some(record) = group.records.get_mut(game.attacker.as_str()) {
68
                            record.losses += 1;
69
                        }
70
                        if let Some(record) = group.records.get_mut(game.defender.as_str()) {
71
                            record.wins += 1;
72
                        }
73
                    }
74
                }
75

            
76
                let mut games_count = 0;
77
                for record in group.records.values() {
78
                    games_count += record.games_count();
79
                }
80

            
81
                // If group finished:
82
                if group.total_games == games_count / 2 {
83
                    let mut standings = Vec::new();
84
                    let mut players = Vec::new();
85
                    let mut previous_score = u64::MAX;
86

            
87
                    for (name, record) in &group.records {
88
                        players.push(name.clone());
89
                        let score = record.score();
90

            
91
                        if score != previous_score {
92
                            standings.push(Standing {
93
                                score,
94
                                players: players.clone(),
95
                            });
96
                        } else if let Some(standing) = standings.last_mut() {
97
                            standing.players.push(name.clone());
98
                        }
99

            
100
                        previous_score = score;
101
                    }
102

            
103
                    group.finishing_standings = standings;
104
                }
105
            }
106

            
107
            self.tournament_games.remove(&game.id);
108

            
109
            if let Some(round) = &self.groups
110
                && let Some(groups) = round.last()
111
            {
112
                let mut finished = true;
113
                for group in groups {
114
                    if let Ok(group) = group.lock() {
115
                        let mut games_count = 0;
116
                        for record in group.records.values() {
117
                            games_count += record.games_count();
118
                        }
119

            
120
                        // If group not finished:
121
                        if group.total_games != games_count / 2 {
122
                            finished = false;
123
                            break;
124
                        }
125
                    }
126
                }
127

            
128
                if finished {
129
                    let mut players_left = HashSet::new();
130

            
131
                    for group in groups {
132
                        if let Ok(group) = group.lock()
133
                            && let Some(top_score) = group.records.values().map(Record::score).max()
134
                        {
135
                            for (name, record) in &group.records {
136
                                if record.score() == top_score {
137
                                    players_left.insert(name.clone());
138
                                } else {
139
                                    next_round = true;
140
                                }
141
                            }
142
                        }
143
                    }
144

            
145
                    self.players_left = players_left;
146
                }
147
            }
148
        }
149

            
150
        next_round
151
    }
152

            
153
    #[allow(clippy::missing_panics_doc)]
154
    #[must_use]
155
    pub fn generate_round(&mut self, accounts: &Accounts, mut group_size: usize) -> Vec<Group> {
156
        let mut players_vec = Vec::new();
157

            
158
        for player in &self.players_left {
159
            let mut rating = Rating::default();
160

            
161
            if let Some(account) = accounts.0.get(player.as_str()) {
162
                rating = account.rating.clone();
163
            }
164

            
165
            players_vec.push((player.clone(), rating));
166
        }
167

            
168
        let players_len = players_vec.len();
169
        let mut rng = rand::rng();
170
        players_vec.shuffle(&mut rng);
171
        players_vec.sort_unstable_by(|a, b| b.1.rating.total_cmp(&a.1.rating));
172

            
173
        let mut groups_number = players_len / group_size;
174
        let mut remainder = 0;
175

            
176
        if groups_number == 0 {
177
            groups_number = 1;
178
            group_size = players_len;
179
        } else {
180
            remainder = players_len % group_size;
181
        }
182

            
183
        let mut groups = Vec::new();
184

            
185
        for _ in 0..groups_number {
186
            let mut group = self.new_group();
187

            
188
            for _ in 0..group_size {
189
                let player = players_vec.pop().expect("There should be a player to pop.");
190

            
191
                group.records.insert(
192
                    player.0,
193
                    Record {
194
                        rating: player.1,
195
                        ..Record::default()
196
                    },
197
                );
198
            }
199

            
200
            groups.push(group);
201
        }
202

            
203
        if remainder != 0
204
            && let Some(mut group_1) = groups.pop()
205
        {
206
            for _ in 0..remainder {
207
                let player = players_vec.pop().expect("There should be a player to pop.");
208

            
209
                group_1.records.insert(
210
                    player.0,
211
                    Record {
212
                        rating: player.1,
213
                        ..Record::default()
214
                    },
215
                );
216
            }
217

            
218
            let len = group_1.records.len();
219
            let mut records_1: Vec<_> = group_1.records.into_iter().take(len).collect();
220

            
221
            records_1.shuffle(&mut rng);
222
            records_1.sort_unstable_by(|(_, record_1), (_, record_2)| {
223
                record_2.rating.rating.total_cmp(&record_1.rating.rating)
224
            });
225

            
226
            let records_2 = records_1.split_off(len / 2);
227

            
228
            let mut records_1a = HashMap::new();
229
            for (name, record) in records_1 {
230
                records_1a.insert(name, record);
231
            }
232
            group_1.records = records_1a;
233

            
234
            let mut group_2 = self.new_group();
235
            let mut records_2a = HashMap::new();
236
            for (name, record) in records_2 {
237
                records_2a.insert(name, record);
238
            }
239
            group_2.records = records_2a;
240

            
241
            groups.push(group_1);
242
            groups.push(group_2);
243
        }
244

            
245
        groups
246
    }
247

            
248
    pub fn remove_duplicate_ids(&mut self) {
249
        if let Some(groups) = &self.groups {
250
            for round in groups {
251
                for group_1 in round {
252
                    if let Ok(group_1a) = group_1.lock() {
253
                        for group_2 in self.tournament_games.values_mut() {
254
                            if let Ok(group_2a) = group_2.clone().lock()
255
                                && group_1a.id == group_2a.id
256
                            {
257
                                *group_2 = group_1.clone();
258
                            }
259
                        }
260
                    }
261
                }
262
            }
263
        }
264
    }
265

            
266
    pub fn new_group(&mut self) -> Group {
267
        let group = Group {
268
            id: self.id,
269
            ..Group::default()
270
        };
271

            
272
        self.id += 1;
273
        group
274
    }
275
}
276

            
277
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
278
pub struct Group {
279
    pub id: u64,
280
    pub total_games: u64,
281
    pub records: HashMap<String, Record>,
282
    pub finishing_standings: Vec<Standing>,
283
}
284

            
285
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
286
pub struct Standing {
287
    pub score: u64,
288
    pub players: Vec<String>,
289
}
290

            
291
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
292
pub struct Record {
293
    pub rating: Rating,
294
    pub wins: u64,
295
    pub losses: u64,
296
    pub draws: u64,
297
}
298

            
299
impl Record {
300
    #[must_use]
301
    pub fn games_count(&self) -> u64 {
302
        self.wins + self.losses + self.draws
303
    }
304

            
305
    pub fn reset(&mut self) {
306
        self.wins = 0;
307
        self.losses = 0;
308
        self.draws = 0;
309
    }
310

            
311
    #[must_use]
312
    pub fn score(&self) -> u64 {
313
        2 * self.wins + self.draws
314
    }
315
}