use super::*;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Dial {
Map,
Gulls,
Round,
Bots,
Teams,
Series,
}
impl Dial {
pub const ALL: [Dial; 6] = [
Dial::Map,
Dial::Gulls,
Dial::Round,
Dial::Bots,
Dial::Teams,
Dial::Series,
];
pub fn label(
self,
tr: &crate::app::i18n::Tr,
config: &MatchConfig,
teams: crate::app::teams::TeamMode,
beaches: &crate::app::match_setup::CustomBeaches,
) -> (&'static str, String) {
use crate::app::cycle::Cycle;
match self {
Dial::Map => (
tr.match_map,
crate::app::match_setup::map_label(config, tr, beaches),
),
Dial::Gulls => (
tr.match_gulls,
tr.gull_names[config.gulls.index()].to_string(),
),
Dial::Round => (
tr.match_round,
tr.round_names[config.round.index()].to_string(),
),
Dial::Bots => (tr.match_ai, config.bots.to_string()),
Dial::Teams => (tr.set_versus_mode, tr.team_modes[teams.index()].to_string()),
Dial::Series => (
tr.match_mode,
tr.mode_names[usize::from(config.series)].to_string(),
),
}
}
pub fn turn(
self,
right: bool,
config: &mut MatchConfig,
teams: &mut crate::app::teams::TeamMode,
humans: u8,
beaches: &crate::app::match_setup::CustomBeaches,
) {
use crate::app::cycle::Cycle;
let room = (MAX_PLAYERS as u8).saturating_sub(humans);
match self {
Dial::Map => crate::app::match_setup::cycle_map(config, right, beaches),
Dial::Gulls => config.gulls = config.gulls.cycled(right),
Dial::Round => config.round = config.round.cycled(right),
Dial::Bots => {
let step = i32::from(config.bots) + if right { 1 } else { -1 };
config.bots = step.clamp(0, i32::from(room)) as u8;
}
Dial::Teams => *teams = teams.cycled(right),
Dial::Series => config.series = !config.series,
}
}
}
pub fn map_for(config: &MatchConfig, seats: u8) -> crate::app::match_setup::MapChoice {
use crate::app::match_setup::{CLASSIC_SEATS, MapChoice, WIDE_ENOUGH};
match seats > CLASSIC_SEATS && config.map.size().0 < WIDE_ENOUGH {
true => MapChoice::GenXl,
false => config.map,
}
}
pub(super) fn turn_the_dials(
keys: &ButtonInput<KeyCode>,
settings: &mut GameSettings,
config: &mut MatchConfig,
state: &mut LobbyState,
beaches: &crate::app::match_setup::CustomBeaches,
) {
if state.standing() == Standing::Hosting {
if keys.just_pressed(KeyCode::ArrowUp) {
state.dial = (state.dial + Dial::ALL.len() - 1) % Dial::ALL.len();
}
if keys.just_pressed(KeyCode::ArrowDown) {
state.dial = (state.dial + 1) % Dial::ALL.len();
}
let turn = match (
keys.just_pressed(KeyCode::ArrowRight),
keys.just_pressed(KeyCode::ArrowLeft),
) {
(true, false) => Some(true),
(false, true) => Some(false),
_ => None,
};
if let Some(right) = turn
&& let Some(dial) = Dial::ALL.get(state.dial).copied()
{
let humans = 1 + state.players_aboard() as u8;
let mut teams = settings.team_mode;
dial.turn(right, config, &mut teams, humans, beaches);
if teams != settings.team_mode {
settings.team_mode = teams;
settings.save();
}
}
}
}
#[cfg(test)]
mod dial_tests {
use super::*;
use crate::app::match_setup::{CLASSIC_SEATS, MapChoice};
use crate::app::teams::TeamMode;
#[test]
fn every_dial_reads_as_a_row() {
let config = MatchConfig::default();
for lang in crate::app::i18n::ALL_LANGS {
for dial in Dial::ALL {
let (name, value) =
dial.label(lang.tr(), &config, TeamMode::Solo, &Default::default());
assert!(!name.is_empty(), "{dial:?} in {lang:?} has no name");
assert!(!value.is_empty(), "{dial:?} in {lang:?} has no value");
}
}
}
#[test]
fn every_dial_turns_and_only_itself() {
for dial in Dial::ALL {
for right in [true, false] {
let mut config = MatchConfig {
bots: 1,
..MatchConfig::default()
};
let mut teams = TeamMode::Solo;
let before = (
config.map,
config.gulls,
config.round,
config.bots,
teams,
config.series,
);
dial.turn(right, &mut config, &mut teams, 2, &Default::default());
let after = (
config.map,
config.gulls,
config.round,
config.bots,
teams,
config.series,
);
assert_ne!(before, after, "{dial:?} turned {right} and did nothing");
let moved = [
before.0 != after.0,
before.1 != after.1,
before.2 != after.2,
before.3 != after.3,
before.4 != after.4,
before.5 != after.5,
];
assert_eq!(
moved.iter().filter(|m| **m).count(),
1,
"{dial:?} moved more than itself"
);
}
}
}
#[test]
fn the_ai_takes_only_the_chairs_the_players_leave() {
let mut config = MatchConfig::default();
let mut teams = TeamMode::Solo;
for _ in 0..10 {
Dial::Bots.turn(true, &mut config, &mut teams, 4, &Default::default());
}
assert_eq!(config.bots, MAX_PLAYERS as u8 - 4);
for _ in 0..10 {
Dial::Bots.turn(false, &mut config, &mut teams, 4, &Default::default());
}
assert_eq!(config.bots, 0);
Dial::Bots.turn(
true,
&mut config,
&mut teams,
MAX_PLAYERS as u8,
&Default::default(),
);
assert_eq!(config.bots, 0, "six people is six people");
}
#[test]
fn a_small_beach_gives_way_to_the_people_who_turned_up() {
let mut config = MatchConfig {
map: MapChoice::Classic,
..MatchConfig::default()
};
assert!(config.map.size().0 < crate::app::match_setup::WIDE_ENOUGH);
for seats in 2..=CLASSIC_SEATS {
assert_eq!(map_for(&config, seats), MapChoice::Classic, "{seats} seats");
}
for seats in CLASSIC_SEATS + 1..=MAX_PLAYERS as u8 {
let map = map_for(&config, seats);
assert!(
map.size().0 >= crate::app::match_setup::WIDE_ENOUGH,
"{seats} seats landed on {map:?}, which cannot hold them"
);
}
config.map = MapChoice::GenLarge;
for seats in 2..=MAX_PLAYERS as u8 {
assert_eq!(map_for(&config, seats), MapChoice::GenLarge);
}
}
#[test]
fn what_the_host_turns_is_what_travels() {
let mut config = MatchConfig::default();
let mut teams = TeamMode::Solo;
for dial in Dial::ALL {
dial.turn(true, &mut config, &mut teams, 2, &Default::default());
}
let terms = crate::app::match_setup::terms(&config, teams, 7);
assert_eq!(terms.map, config.map.index() as u8);
assert_eq!(terms.gulls, config.gulls.index() as u8);
assert_eq!(terms.round, config.round.index() as u8);
assert_eq!(terms.bots, config.bots);
assert_eq!(terms.teams, teams.index() as u8);
assert_eq!(terms.series, u8::from(config.series));
assert_eq!(terms.seed, 7);
}
}