5GBG7TFPFZOIPQFG4WHK5JHQRUUTYVOUXJJ6GDHJ5TMBXLQJWXNAC DYPVZ7XKRKWI7NYG33PZBPWWXL2LOX3UQE52J3CJQRTAV7WBRNPQC O6YZGEK72ODTQU7JHSMJLXZ2SWMU6TEHEG2FG42QMHX6Z25EJTAAC N5UNE37IO6R4VJ2F3O462XM7OEAIFE6KOMDA5ZFP2PQ4TZ43FIEQC ZPHVX63UFFOWD6LYGSC56FOKL25Z6EJ5JEJU4OROS6FYRQQZORSAC OCUJVMFIDSQWCJU73EWTUQL3OMPUQTZTESY3O3IG7MRZ2HKUO6GQC 5QDSYIEJCF7PJL3AIDWTG4AW7BM73KCVD6CAEWGZKUONHZ2TUGLAC NBS5RQNTK22TQBTR26VNIBM4SFIXFRHA6ZVBH2PFEDOWAOYD2S5QC EDPSYYMBK7AAKEN7Q5JZV7V3PFGMM777O6T37RJZRYWDENSXZ4JAC KZVN5HJ7ZXM7EKQCD6YP3IZIGLEU3SXFRF6JTPALLYZ7VCMML43AC 66UPZMNHJTLMUK45BLSVDVGMR3T2SJCYC6LI2QCNRGRG5GTRTUSQC MNFJIJBGIVL6XGB3TS6DUK6N7BEMDTCDHAMPTT4BXQ4NVBWN2AJAC V75YR4XB4TYZTGPOKHO4M2RXISV4HYSVEH7UICEZ2T77UTBK7VDAC AIJS2HBCJU5C2E45AJA6UCSRJEJJGKDVTSA24PPA3QXUZGQQE6UAC footer.message = "<p>Copyright © 2026 <a href=https://dlc.name>David Lawrence Campbell</a></p> <script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support DLC on Ko-fi', '#72a4f2', 'L4L61R1PR0');kofiwidget2.draw();</script>"
footer.message = "<p>Copyright © 2026 <a href=https://dlc.name>David Lawrence Campbell</a></p> <script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support DLC on Ko-fi', '#72a4f2', 'dcampbell24');kofiwidget2.draw();</script>"
// This file is part of hnefatafl-copenhagen.//// hnefatafl-copenhagen is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// hnefatafl-copenhagen is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program. If not, see <https://www.gnu.org/licenses/>.use std::{net::IpAddr, sync::mpsc::Sender};#[derive(Debug)]pub(crate) struct RemoveConnection {pub address: IpAddr,pub tx: Sender<(String, Option<Sender<String>>)>,}impl Drop for RemoveConnection {fn drop(&mut self) {let _ok = self.tx.send((format!("0 server connection_remove {}", self.address), None));}}//// SPDX-License-Identifier: AGPL-3.0-or-later// SPDX-FileCopyrightText: 2025 David Campbell <david@hnefatafl.org>
if args.secure {let (tx_close, rx_close) = mpsc::channel();tx.send((format!("0 server connection_add {peer_address}"),Some(tx_close),))?;
if let Some(groups) = round {for (i, mut group) in groups.into_iter().enumerate() {for combination in group.records.iter().map(|record| record.0).combinations(2) {if let (Some(first), Some(second)) = (combination.first(), combination.get(1)) {ids.push_back((self.new_tournament_game(first, second), i));ids.push_back((self.new_tournament_game(second, first), i));group.total_games += 2;}
for (i, mut group) in groups.into_iter().enumerate() {for combination in group.records.iter().map(|record| record.0).combinations(2) {if let (Some(first), Some(second)) = (combination.first(), combination.get(1)) {ids.push_back((self.new_tournament_game(first, second), i));ids.push_back((self.new_tournament_game(second, first), i));group.total_games += 2;
"connection_add" => {if let Some(address) = the_rest.first()&& let Some(tx) = option_tx{if let Some(connections) = self.connections.get(*address)&& *connections > 2_000{tx.send("true".to_string()).ok()?;} else {tx.send("false".to_string()).ok()?;let entry = self.connections.entry(address.to_string());entry.and_modify(|value| *value += 1).or_insert(1);}}debug!("connections: {:?}", self.connections);None}"connection_remove" => {if let Some(connection) = the_rest.first() {let entry = self.connections.entry(connection.to_string());entry.and_modify(|value| *value = value.saturating_sub(1));if let Some(value) = self.connections.get(*connection)&& *value == 0{self.connections.remove(*connection);}}debug!("connections: {:?}", self.connections);None}
let text = format!("= text {timestamp} {username}: {the_rest}");
info!("{index_supplied} {timestamp} {username} text {text}");let text = censor(&text);if text.is_empty() {return None;}let text = format!("= text {timestamp} {username}: {text}");
if self.admins_tournament.contains(username)&& let Some(tournament) = &mut self.tournament{tournament.groups = None;tournament.tournament_games = HashMap::new();
if self.admins_tournament.contains(username) {self.tournament.groups = None;self.tournament.tournament_games = HashMap::new();
&& let Some(tournament) = &mut self.tournament&& tournament.groups.is_none()&& Timestamp::now() >= tournament.date
&& self.tournament.groups.is_none()&& let Some(date) = self.tournament.date&& Timestamp::now() >= date
}}// Fixme: Censor::from_str removes the dots ä, but not using censor This allows for ͬ ͣ p (crap)fn censor(text: &str) -> String {if text.len() > MESSAGE_LENGTH {return String::new();}let censored_first = badwords_rs::censor(text, MODERATE);let (censored_second, analysis) = Censor::from_str(&censored_first).with_censor_threshold(Type::PROFANE | Type::SEXUAL).with_censor_first_character_threshold(Type::ANY).censor_and_analyze();if analysis == Type::NONE {censored_first} else {censored_second
let round_title = if tournament.tournament_games.is_empty() && i + 1 == round.len(){let winner = t!("Winner");let row_1 = text(winner.to_string());let row_2 = text!("{}", "-".repeat(winner.len())).font(Font::MONOSPACE);
let round_title =if self.tournament.tournament_games.is_empty() && i + 1 == round.len() {let winner = t!("Winner");let row_1 = text(winner.to_string());let row_2 = text!("{}", "-".repeat(winner.len())).font(Font::MONOSPACE);
column![row_1, row_2]} else {let round = t!("Round");let row_1 = text!("{} {}", round.to_string(), i + 1);let row_2 = text!("{}-{}","-".repeat(round.len()),"-".repeat((i + 1).to_string().len())).font(Font::MONOSPACE);
column![row_1, row_2]} else {let round = t!("Round");let row_1 = text!("{} {}", round.to_string(), i + 1);let row_2 = text!("{}-{}","-".repeat(round.len()),"-".repeat((i + 1).to_string().len())).font(Font::MONOSPACE);
if let Some(user) = self.users.get(&self.attacker) {attacker_rating = user.rating.to_string_rounded();
if self.admin {if let Some(account) = self.accounts.0.get(&self.attacker) {attacker_rating = account.rating.to_string_rounded();}if let Some(account) = self.accounts.0.get(&self.defender) {defender_rating = account.rating.to_string_rounded();}} else {if let Some(user) = self.users.get(&self.attacker) {attacker_rating = user.rating.to_string_rounded();}if let Some(user) = self.users.get(&self.defender) {defender_rating = user.rating.to_string_rounded();}
let Some(tournament) = &self.tournament else {column = column.push(text(t!("There is no tournament.")));column = column.push(button(text!("{} (Esc)", t!("Quit"))).on_press(Message::Leave));return scrollable(column).spacing(SPACING);};
if let Some(tournament) = &self.tournament&& tournament.groups.is_some(){delete_button = delete_button.on_press(Message::TournamentTreeDelete);
if self.tournament.groups.is_some() {delete_button_2 = delete_button_2.on_press(Message::TournamentTreeDelete);
if let Some(tournament) = &self.tournament {if tournament.groups.is_none() {start_tournament = start_tournament.on_press(Message::TournamentStart);}} else {
if self.tournament.groups.is_none()&& !self.tournament.players.is_empty()&& let Some(date) = self.tournament.date&& date < Timestamp::now(){
impl PartialEq for Account {fn eq(&self, other: &Self) -> bool {self.email == other.email&& self.email_sent == other.email_sent&& self.logged_in == other.logged_in&& self.draws == other.draws&& self.wins == other.wins&& self.losses == other.losses&& self.rating == other.rating&& self.send_emails == other.send_emails&& self.creation_date == other.creation_date&& self.last_logged_in == other.last_logged_in}}
<url type="contribute">https://github.com/dcampbell24/hnefatafl/blob/main/CONTRIBUTING.md</url><url type="vcs-browser">https://github.com/dcampbell24/hnefatafl</url>
<url type="contribute">https://codeberg.org/dcampbell/hnefatafl/src/branch/main/CONTRIBUTING.md</url><url type="vcs-browser">https://codeberg.org/dcampbell/hnefatafl</url>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><!-- Created using Krita: https://krita.org --><svg xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"xmlns:krita="http://krita.org/namespaces/svg/krita"xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"width="184.32pt"height="184.32pt"viewBox="0 0 184.32 184.32"><defs/><rect id="shape0" transform="matrix(0.72 0 0 0.72 20.5416 20.0736)" fill="#002b36" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" width="199.62" height="199.62" rx="13.14" ry="13.14"/><path id="shape1" transform="matrix(0.72 0 0 0.72 19.1087976206245 18.6264)" fill="#34cac0" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M188.46 4C191.412 4.00263 194.245 5.17626 196.334 7.2616C198.423 9.34695 199.602 12.1782 199.61 15.13L199.61 188.48C199.607 191.435 198.431 194.272 196.341 196.361C194.252 198.451 191.415 199.627 188.46 199.63L15.13 199.63C12.1765 199.625 9.34265 198.447 7.25515 196.358C5.16765 194.268 3.99265 191.433 3.99 188.48L3.99 15.13C3.99793 12.18 5.17526 9.35019 7.2622 7.26513C9.34913 5.18007 12.18 4.00528 15.13 4L188.46 4M188.46 0L15.13 0C11.1188 -3.55271e-15 7.26781 1.59514 4.43148 4.43147C1.59514 7.26781 3.30469e-06 11.1188 3.30469e-06 15.13L3.30469e-06 188.48C-0.00175268 191.137 0.696317 193.748 2.0237 196.049C3.35109 198.351 5.26155 200.262 7.56212 201.591C9.86268 202.92 12.4732 203.62 15.13 203.62L188.46 203.62C191.117 203.62 193.727 202.92 196.028 201.591C198.328 200.262 200.239 198.351 201.566 196.049C202.894 193.748 203.592 191.137 203.59 188.48L203.59 15.13C203.587 11.1205 201.991 7.27212 199.155 4.43794C196.319 1.60376 192.469 0.00999912 188.46 0.01Z" sodipodi:nodetypes="ccccccccccccccccccccccccccccc"/><path id="shape2" transform="matrix(1.13975337126883 0 0 1.17160146534129 75.6399760441674 46.08)" fill="#37c9be" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M16 78.6616L0 68.0316C6.48 48.7616 4.32 27.9516 5.94 7.44156C6.45 0.951561 8.44 -0.258439 17.1 0.0415609C23.42 0.271561 24.77 1.85156 24.87 8.13156C25.12 22.3316 25.12 36.5616 26.12 50.7116C26.53 56.3816 29.33 61.9016 31.19 68.0016Z" sodipodi:nodetypes="cccccccc"/><path id="shape3" transform="matrix(1.13975337126883 0 0 1.17160146534129 109.763970975147 51.5883556233582)" fill="#37c9be" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M0.0201939 0C23.4402 3.06 46.5102 30.82 42.9102 50.82C35.1502 50.96 34.7702 50.58 34.2402 43.55C33.7402 36.91 32.8702 30.36 27.1002 24.62L30.2102 46.7C20.1502 44.52 11.3402 42.76 2.65019 40.55C1.45019 40.25 0.160194 37.77 0.130194 36.28C-0.0698061 24.32 0.0201939 12.38 0.0201939 0Z" sodipodi:nodetypes="cccccccc"/><path id="shape4" transform="matrix(1.13975337126883 0 0 1.17160146534129 29.2761399373758 51.5883556233582)" fill="#37c9be" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M40.2188 0L40.2188 38.58L0.00883217 45.91C-0.441168 31.59 16.3488 4.27 40.2188 0Z" sodipodi:nodetypes="cccc"/><path id="shape5" transform="matrix(1.13975337126883 0 0 1.17160146534129 112.340565090752 104.591605915398)" fill="#37c9be" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M39.8795 10.8L39.8795 21.9C33.8195 19.59 28.3495 17.3 22.7295 15.42C17.9995 13.83 13.1795 12.08 8.24953 11.53C1.35953 10.75 -0.290465 7.13 0.0395346 0Z" sodipodi:nodetypes="cccccc"/><path id="shape6" transform="matrix(1.13975337126883 0 0 1.17160146534129 25.2238862559241 104.650185988665)" fill="#37c9be" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M1.41421 15.25C-1.25579 8.62 -0.46579 6.98 6.56421 5.65C16.0709 3.83667 25.5942 2.1 35.1342 0.44C37.2781 0.177282 39.4345 0.0304019 41.5942 0C42.0742 8 41.5942 8 34.5342 9.15C23.6042 10.85 12.7742 13.12 1.41421 15.25Z" sodipodi:nodetypes="cccccc"/></svg>
client = ["dep:iced", "dep:iced_aw", "dep:image", "dep:rodio", "dep:smol_str", "socket", "dep:sys-locale", "dep:webbrowser"]
client = ["dep:iced","dep:iced_aw","dep:image","dep:rodio","dep:smol_str","socket","dep:sys-locale","dep:webbrowser",]
][[package]]name = "rustrict"version = "0.7.38"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "60e8725b8fa49d34ef769db698a148815fffd4ed8d0c140b2b2325d0710ee976"dependencies = ["arrayvec","bitflags 1.3.2","doc-comment","finl_unicode","itertools 0.10.5","lazy_static","rustc-hash 1.1.0","strsim 0.10.0","unicode-normalization",
## [5.6.1] - 2026-04-08### Added- hnefatafl-server-full: allow blocking IP addresses.- hnefatafl-server-full: message filtering and set OARS for flathub.### Breaking- Fix tournaments so that you can have one pre-start when another is beingplayed or finished and remove the players from players when starting a game.### Removed- hnefatafl-server-full: `--secure` flag.### Fixed- hnefatafl-server-full: actually delete unused accounts.- hnefatafl-client: show the rating of players in games for admin.