use super::*;
pub(crate) fn greeting(bot: Option<&str>, watching: bool, name: &str) -> NetMsg {
match (bot, watching) {
(Some(seat), _) => NetMsg::hello_bot(seat),
(None, true) => NetMsg::watch(name),
(None, false) => NetMsg::hello(name),
}
}
pub(crate) fn speaking_as<'a>(bot: Option<&'a str>, me: &'a str) -> &'a str {
bot.unwrap_or(me)
}
#[cfg(test)]
pub(super) fn hosting_lobby() -> Standing {
Standing::hosting(
Announcer::new(0xB0A7).expect("announcer"),
UdpTransport::host(0).expect("game socket"),
)
}
pub enum Standing {
Choosing { watching: bool },
Joining(Joined),
Hosting(Hosted),
}
impl Default for Standing {
fn default() -> Self {
Standing::Choosing { watching: false }
}
}
impl Standing {
pub fn at_a_beach(&self) -> bool {
matches!(self, Standing::Joining(_) | Standing::Hosting(_))
}
pub fn hosting(announcer: Announcer, transport: UdpTransport) -> Standing {
Standing::Hosting(Hosted {
announcer,
transport,
peers: PeerBook::default(),
announce_in: 0.0,
kicking: None,
bot_string: None,
tail: RoundTail::default(),
rounds_dealt: 0,
picking: None,
partnering: false,
arranged: None,
})
}
}
pub struct Hosted {
pub announcer: Announcer,
pub transport: UdpTransport,
pub peers: PeerBook,
pub(super) announce_in: f32,
pub kicking: Option<usize>,
pub bot_string: Option<String>,
pub(super) tail: RoundTail,
pub(super) rounds_dealt: u32,
pub picking: Option<Picking>,
pub partnering: bool,
pub arranged: Option<crate::app::net::order::Arranged>,
}
const TAIL_KEPT: f32 = 10.0;
#[derive(Default)]
pub struct RoundTail {
played: Option<Box<crate::sim::Lockstep>>,
round: u8,
left: f32,
}
impl RoundTail {
pub(super) fn kept(played: Box<crate::sim::Lockstep>, rounds_dealt: u32) -> RoundTail {
RoundTail {
played: Some(played),
round: crate::transport::round_tag(rounds_dealt),
left: TAIL_KEPT,
}
}
pub(super) fn repeat(
&mut self,
transport: &UdpTransport,
delta: f32,
announced: bool,
asked: &[(usize, crate::transport::Batch)],
) {
self.left -= delta;
if self.left <= 0.0 {
self.played = None;
}
let Some(played) = &self.played else {
return;
};
let round = self.round;
if announced {
let tail = crate::transport::Batch {
round,
inputs: played.played_tail(),
..Default::default()
};
transport.send_batch(&tail, |_| true);
}
for (peer, batch) in asked {
if batch.round != round {
continue;
}
let wants = batch
.wants
.filter(|wants| wants.iter().all(|&frame| played.within_horizon(frame)));
let owed = crate::transport::Batch {
round,
inputs: played.owed(wants, None, played.frame()),
..Default::default()
};
transport.send_batch(&owed, |to| to == *peer);
}
}
}
impl Hosted {
pub(super) fn forget_peer(&mut self, peer: usize) {
self.transport.forget(peer);
self.peers.forget(peer);
}
pub(super) fn turn_away(&mut self, peer: usize) {
self.transport.turn_away(peer);
self.peers.forget(peer);
}
pub fn players_aboard(&self) -> usize {
self.peers.at_the_table().count()
}
pub(super) fn watchers_aboard(&self) -> usize {
self.peers.iter().filter(|peer| peer.watch).count()
}
}
pub struct Joined {
pub transport: UdpTransport,
pub watching: bool,
pub queued: Option<u8>,
pub no_chair: bool,
pub host_picking: bool,
pub(super) told_full: bool,
pub(super) hello_in: f32,
pub(super) host_silence: f32,
pub(super) host_answered: bool,
pub(super) host_goodbye: crate::app::net::Goodbye,
played_seed: Option<u64>,
pub host_terms: Option<HostTerms>,
pub(super) catching_up: crate::app::net::catch_up::Assembly,
pub bot: Option<String>,
pub beach: Option<u64>,
}
pub struct HostTerms {
pub terms: MatchTerms,
}
impl Joined {
pub fn dialled(transport: UdpTransport, watching: bool) -> Joined {
Joined {
transport,
watching,
queued: None,
no_chair: false,
host_picking: false,
told_full: false,
hello_in: ANNOUNCE_EVERY,
host_silence: 0.0,
host_answered: false,
host_goodbye: Default::default(),
played_seed: None,
host_terms: None,
catching_up: Default::default(),
bot: None,
beach: None,
}
}
pub fn returned(
transport: UdpTransport,
watching: bool,
played_seed: u64,
bot: Option<String>,
) -> Joined {
Joined {
transport,
watching,
queued: None,
no_chair: false,
host_picking: false,
told_full: false,
hello_in: 0.0,
host_silence: 0.0,
host_answered: true,
host_goodbye: Default::default(),
played_seed: Some(played_seed),
host_terms: None,
catching_up: Default::default(),
bot,
beach: None,
}
}
pub fn greeting(&self, name: &str) -> NetMsg {
greeting(self.bot.as_deref(), self.watching, name)
}
pub fn played_seed(&self) -> Option<u64> {
self.played_seed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_spectator_greets_by_name_like_everybody_else() {
assert_eq!(
greeting(None, true, "Dee"),
NetMsg::Watch {
name: crate::transport::wire_name("Dee")
},
);
assert_eq!(
greeting(None, false, "Bo"),
NetMsg::Hello {
name: crate::transport::wire_name("Bo"),
bot: false,
},
);
}
#[test]
fn a_bot_chats_under_the_name_it_greeted_with() {
let socket = UdpTransport::host(0).expect("socket");
let joined = Joined::returned(socket, false, 0, Some("Greedy (Ana)".into()));
let NetMsg::Hello { name, bot: true } = joined.greeting("Bo") else {
panic!("a bot greets as one");
};
let mut peers = PeerBook::default();
peers.row(0).name = crate::transport::name_from_wire(&name);
let vouched = peers.vouch(0, crate::transport::wire_name("Bo"));
assert_eq!(
crate::transport::name_from_wire(&vouched),
crate::transport::name_as_sent(speaking_as(joined.bot.as_deref(), "Bo")),
"the echo and the table read one name"
);
}
}