use super::*;
use crate::app::match_setup::{CustomBeaches, MapPress, MatchMenu};
use crate::bots::listener::BotId;
pub struct Picking {
pub people: u8,
pub bots: Vec<BotId>,
before: MatchConfig,
fresh: bool,
pub chosen: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum Gone {
Player,
Bot,
}
fn freeze(peers: &mut PeerBook, door: &[(BotId, String)]) -> (u8, Vec<BotId>) {
let plan = peers.seat_plan(peers.len());
peers.freeze(&plan);
let people = 1 + plan.iter().flatten().count() as u8;
let bots = door
.iter()
.take(crate::app::net::chairs_for_bots(people))
.map(|(id, _)| *id)
.collect();
(people, bots)
}
pub(super) fn gone_from_the_table(
picking: &Picking,
peers: &PeerBook,
door: &[(BotId, String)],
) -> Option<Gone> {
let still = peers
.iter()
.filter(|row| row.frozen && !row.watches())
.count();
if still + 1 < usize::from(picking.people) {
return Some(Gone::Player);
}
let here = |bot: &BotId| door.iter().any(|(id, _)| id == bot);
match picking.bots.iter().all(here) {
true => None,
false => Some(Gone::Bot),
}
}
pub(super) fn frozen_plan(peers: &PeerBook) -> Vec<Option<u8>> {
peers.frozen_plan(peers.len())
}
pub(super) fn open_the_pick(
state: &mut LobbyState,
config: &mut MatchConfig,
menu: &mut MatchMenu,
beaches: &CustomBeaches,
door: &[(BotId, String)],
) {
let Some(hosted) = state.hosted_mut() else {
return;
};
let (people, bots) = freeze(&mut hosted.peers, door);
let seats = dealt(config, people + bots.len() as u8).seats;
let before = config.clone();
crate::app::match_setup::reseat(config, seats, beaches);
hosted.picking = Some(Picking {
people,
bots,
before,
fresh: true,
chosen: false,
});
menu.online = true;
menu.step = crate::app::match_setup::Step::Map;
menu.seed = crate::app::clock::fresh_seed();
menu.scroll = 0.0;
crate::app::match_setup::put_the_cursor_on_the_pick(menu, config, beaches);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum Close {
Keep,
PutBack,
}
pub(super) fn close_the_pick(
picking: Picking,
close: Close,
config: &mut MatchConfig,
menu: &mut MatchMenu,
beaches: &CustomBeaches,
) {
match close {
Close::PutBack => *config = picking.before,
Close::Keep => crate::app::match_setup::reseat(config, picking.before.seats, beaches),
}
menu.online = false;
menu.step = crate::app::match_setup::Step::Game;
}
pub(super) fn pick_the_card_here(
state: &mut LobbyState,
config: &mut MatchConfig,
menu: &MatchMenu,
beaches: &CustomBeaches,
) {
let Some(picking) = state
.hosted_mut()
.and_then(|hosted| hosted.picking.as_mut())
else {
return;
};
if let Some(card) = crate::app::match_setup::card_to_pick(menu, config, beaches) {
crate::app::match_setup::pick_card(config, card, beaches);
picking.chosen = true;
}
}
pub(super) fn pick_keys(
keys: &ButtonInput<KeyCode>,
state: &mut LobbyState,
config: &mut MatchConfig,
menu: &mut MatchMenu,
beaches: &CustomBeaches,
) -> bool {
let Some(picking) = state
.hosted_mut()
.and_then(|hosted| hosted.picking.as_mut())
else {
return false;
};
if std::mem::take(&mut picking.fresh) || picking.chosen {
return true;
}
match crate::app::match_setup::map_page_keys(keys, menu, config, beaches) {
MapPress::Nothing => {}
MapPress::Chosen(_) => picking.chosen = true,
MapPress::Back => {
if let Some(picking) = state.hosted_mut().and_then(|hosted| hosted.picking.take()) {
close_the_pick(picking, Close::PutBack, config, menu, beaches);
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
fn book(rows: usize, watching: &[usize]) -> PeerBook {
let mut peers = PeerBook::default();
peers.reach(rows);
for &at in watching {
peers.row(at).watch = true;
}
peers
}
#[test]
fn the_plan_seats_who_was_frozen_and_nobody_else() {
let mut peers = book(3, &[1]);
let (people, bots) = freeze(&mut peers, &[]);
assert_eq!((people, bots.len()), (3, 0));
peers.row(1).watch = false;
peers.reach(4);
assert_eq!(frozen_plan(&peers), [Some(1), None, Some(2), None]);
}
#[test]
fn a_frozen_player_leaving_is_seen_past_a_newcomer() {
let mut peers = book(2, &[]);
let (people, bots) = freeze(&mut peers, &[]);
let picking = Picking {
people,
bots,
before: MatchConfig::default(),
fresh: false,
chosen: false,
};
peers.reach(3);
assert_eq!(gone_from_the_table(&picking, &peers, &[]), None);
peers.forget(0);
assert_eq!(peers.len(), 2, "the newcomer is in the count again");
assert_eq!(
gone_from_the_table(&picking, &peers, &[]),
Some(Gone::Player)
);
assert_eq!(frozen_plan(&peers), [Some(1), None]);
}
fn picking(peers: &mut PeerBook, door: &[(BotId, String)]) -> Picking {
let (people, bots) = freeze(peers, door);
Picking {
people,
bots,
before: MatchConfig::default(),
fresh: false,
chosen: false,
}
}
#[test]
fn a_newcomer_never_calls_the_start_off() {
let door: Vec<(BotId, String)> = (1..=3).map(|id| (id, format!("Bot {id}"))).collect();
let mut peers = book(2, &[]);
let frozen = picking(&mut peers, &door);
assert_eq!((frozen.people, frozen.bots.len()), (3, 3));
peers.reach(3);
let mut more = door.clone();
more.push((4, "Bot 4".into()));
assert_eq!(gone_from_the_table(&frozen, &peers, &more), None);
assert_eq!(frozen_plan(&peers), [Some(1), Some(2), None]);
}
#[test]
fn a_frozen_bot_dropping_is_a_bot_gone() {
let door: Vec<(BotId, String)> = (1..=2).map(|id| (id, format!("Bot {id}"))).collect();
let mut peers = book(1, &[]);
let frozen = picking(&mut peers, &door);
assert_eq!(
gone_from_the_table(&frozen, &peers, &door[..1]),
Some(Gone::Bot)
);
peers.forget(0);
assert_eq!(
gone_from_the_table(&frozen, &peers, &door[..1]),
Some(Gone::Player)
);
}
}