Lines
0 %
Functions
Branches
100 %
// 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::{
collections::{HashMap, VecDeque},
fmt,
str::FromStr,
sync::mpsc::Sender,
};
use rust_i18n::t;
use serde::{Deserialize, Serialize};
use crate::{
Id,
board::{Board, BoardSize},
game::Game,
glicko::Rating,
play::{PlayRecordTimed, Plays},
rating::Rated,
role::Role,
status::Status,
time::{Time, TimeSettings},
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ArchivedGame {
pub id: Id,
pub attacker: String,
pub attacker_rating: Rating,
pub defender: String,
pub defender_rating: Rating,
pub rated: Rated,
pub plays: Plays,
pub status: Status,
pub texts: VecDeque<String>,
pub board_size: BoardSize,
}
impl ArchivedGame {
#[must_use]
pub fn new(game: ServerGame, attacker_rating: Rating, defender_rating: Rating) -> Self {
Self {
id: game.id,
attacker: game.attacker,
attacker_rating,
defender: game.defender,
defender_rating,
rated: game.rated,
plays: game.game.plays,
status: game.game.status,
texts: game.texts,
board_size: game.game.board.size(),
impl fmt::Display for ArchivedGame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"{}: {}, {}: {} {}, {}: {} {}, {}: {}",
t!("ID"),
self.id,
t!("Attacker"),
self.attacker,
self.attacker_rating.to_string_rounded(),
t!("Defender"),
self.defender,
self.defender_rating.to_string_rounded(),
t!("Size"),
self.board_size,
)
impl PartialEq for ArchivedGame {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
impl Eq for ArchivedGame {}
#[derive(Clone, Debug)]
pub struct Messenger(Option<Sender<String>>);
impl Messenger {
pub fn new(sender: Sender<String>) -> Self {
Self(Some(sender))
pub fn send(&self, string: String) {
if let Some(sender) = &self.0 {
let _ok = sender.send(string);
pub struct ServerGame {
pub attacker_tx: Messenger,
pub defender_tx: Messenger,
pub elapsed_time: i64,
pub game: Game,
impl From<ServerGameSerialized> for ServerGame {
fn from(game: ServerGameSerialized) -> Self {
attacker_tx: Messenger(None),
defender_tx: Messenger(None),
elapsed_time: 0,
game: game.game,
impl ServerGame {
pub fn protocol(&self) -> String {
format!(
"game {} {} {} {}",
self.id, self.attacker, self.defender, self.rated
#[allow(clippy::missing_panics_doc)]
pub fn new(
attacker_tx: Option<Sender<String>>,
defender_tx: Option<Sender<String>>,
game: ServerGameLight,
) -> Self {
let (Some(attacker), Some(defender)) = (game.attacker, game.defender) else {
unreachable!();
let plays = match game.timed {
TimeSettings::Timed(time) => Plays::PlayRecordsTimed(vec![PlayRecordTimed {
play: None,
attacker_time: time.into(),
defender_time: time.into(),
}]),
TimeSettings::UnTimed => Plays::PlayRecords(vec![None]),
let board = Board::new(game.board_size);
attacker,
attacker_tx: Messenger(attacker_tx),
defender,
defender_tx: Messenger(defender_tx),
game: Game {
attacker_time: game.timed.clone(),
defender_time: game.timed,
board,
plays,
..Game::default()
},
texts: VecDeque::new(),
impl fmt::Display for ServerGame {
write!(
"{}: {}, {}, {} ",
pub struct ServerGameSerialized {
pub timed: TimeSettings,
impl From<&ServerGame> for ServerGameSerialized {
fn from(game: &ServerGame) -> Self {
attacker: game.attacker.clone(),
defender: game.defender.clone(),
game: game.game.clone(),
texts: game.texts.clone(),
timed: TimeSettings::default(),
#[derive(Clone, Debug, Default)]
pub struct ServerGames(pub HashMap<Id, ServerGame>);
#[derive(Clone, Default, Eq, PartialEq)]
pub struct Challenger(pub Option<String>);
impl fmt::Debug for Challenger {
if let Some(challenger) = &self.0 {
write!(f, "{challenger}")?;
} else {
write!(f, "_")?;
Ok(())
impl fmt::Display for Challenger {
write!(f, "challenger: ")?;
write!(f, "none")?;
#[derive(Clone, Eq, PartialEq)]
pub struct ServerGameLight {
pub attacker: Option<String>,
pub defender: Option<String>,
pub challenger: Challenger,
pub attacker_channel: Option<usize>,
pub defender_channel: Option<usize>,
pub spectators: HashMap<String, usize>,
pub challenge_accepted: bool,
pub game_over: bool,
impl ServerGameLight {
game_id: Id,
username: String,
rated: Rated,
timed: TimeSettings,
board_size: BoardSize,
index_supplied: usize,
role: Role,
if role == Role::Attacker {
id: game_id,
attacker: Some(username),
defender: None,
challenger: Challenger::default(),
rated,
timed,
board_size,
attacker_channel: Some(index_supplied),
defender_channel: None,
spectators: HashMap::new(),
challenge_accepted: false,
game_over: false,
attacker: None,
defender: Some(username),
attacker_channel: None,
defender_channel: Some(index_supplied),
impl From<&ServerGameSerialized> for ServerGameLight {
fn from(game: &ServerGameSerialized) -> Self {
attacker: Some(game.attacker.clone()),
defender: Some(game.defender.clone()),
timed: game.timed.clone(),
challenge_accepted: true,
impl fmt::Debug for ServerGameLight {
let attacker = if let Some(name) = &self.attacker {
name
"_"
let defender = if let Some(name) = &self.defender {
let Ok(spectators) = ron::ser::to_string(&self.spectators) else {
"game {} {attacker} {defender} {} {:?} {} {:?} {} {spectators}",
self.rated,
self.timed,
self.challenger,
self.challenge_accepted,
impl fmt::Display for ServerGameLight {
let attacker = t!("attacker");
let defender = t!("defender");
let rated = t!(self.rated.to_string());
let none = t!("none");
&format!("{attacker}: {name}")
&format!("{attacker}: {none}")
&format!("{defender}: {name}")
&format!("{defender}: {none}")
"# {}\n{attacker}, {defender}, {rated}\n{}: {}, {}: {}",
t!("time"),
t!("board size"),
impl TryFrom<&[&str]> for ServerGameLight {
type Error = anyhow::Error;
fn try_from(vector: &[&str]) -> anyhow::Result<Self> {
if vector.len() < 12 {
return Err(anyhow::Error::msg("ServerGameLight has too few words."));
let id = vector[1];
let attacker = vector[2];
let defender = vector[3];
let rated = vector[4];
let timed = vector[5];
let minutes = vector[6];
let add_seconds = vector[7];
let board_size = vector[8];
let challenger = vector[9];
let challenge_accepted = vector[10];
let spectators = vector[11];
let id = id.parse::<Id>()?;
let attacker = if attacker == "_" {
None
Some(attacker.to_string())
let defender = if defender == "_" {
Some(defender.to_string())
let timed = match timed {
"fischer" => TimeSettings::Timed(Time {
add_seconds: add_seconds.parse::<i64>()?,
milliseconds_left: minutes.parse::<i64>()?,
}),
// "un-timed"
_ => TimeSettings::UnTimed,
let board_size = BoardSize::from_str(board_size)?;
let Ok(challenge_accepted) = <bool as FromStr>::from_str(challenge_accepted) else {
return Err(anyhow::Error::msg("challenge_accepted is not a bool."));
let spectators = ron::from_str(spectators)?;
let mut game = Self {
id,
rated: Rated::from_str(rated)?,
spectators,
challenge_accepted,
if challenger != "_" {
game.challenger.0 = Some(challenger.to_string());
Ok(game)
pub struct ServerGamesLight(pub HashMap<Id, ServerGameLight>);
impl fmt::Debug for ServerGamesLight {
for game in self.0.values().filter(|game| !game.game_over) {
write!(f, "{game:?} ")?;