use super::*;
pub(crate) fn fill_bot_actions(
board: &Board,
controllers: &Controllers,
actions: &mut [PlayerAction; MAX_PLAYERS],
) {
debug_assert_eq!(
actions.len(),
controllers.0.len(),
"a seat with no controller"
);
for (seat, action) in actions.iter_mut().enumerate() {
if let Some(level) = controllers.ai(seat)
&& matches!(action, PlayerAction::None)
{
*action = bot_action(board, seat as u8, level);
}
}
}
fn fill_online_bots(
board: &Board,
controllers: &Controllers,
abandoned: &[(u8, u32)],
level: BotLevel,
frame: u32,
actions: &mut [PlayerAction; MAX_PLAYERS],
) {
for (seat, action) in actions.iter_mut().enumerate() {
if let Some(level) = ai_holding(controllers, abandoned, level, seat, frame)
&& matches!(action, PlayerAction::None)
{
*action = bot_action(board, seat as u8, level);
}
}
}
pub(super) fn ai_holding(
controllers: &Controllers,
abandoned: &[(u8, u32)],
level: BotLevel,
seat: usize,
frame: u32,
) -> Option<BotLevel> {
match abandoned
.iter()
.find(|(gone, _)| usize::from(*gone) == seat)
{
Some((_, at)) => (frame >= *at).then_some(level),
None => controllers.ai(seat),
}
}
pub(in crate::app) fn advance_sim(
play: Play,
mut online: ResMut<net::Online>,
mut recorder: ResMut<Recorder>,
mut playback: ResMut<Playback>,
speed: Res<replays::PlaybackSpeed>,
mut drivers: bot_seats::Drivers,
mut tally: ResMut<awards::RoundTally>,
) {
let Play {
mut sim,
mut pending,
paused,
} = play;
if paused.0 {
return;
}
if let Some((replay, idx)) = &mut playback.0 {
for _ in 0..speed.0.max(1) {
let Some(actions) = replay.inputs.get(*idx).copied() else {
break;
};
*idx += 1;
let before = awards::Reading::of(&sim.0);
sim.0.tick(&actions);
tally.observe(before, &sim.0, &actions);
}
return;
}
if let Some(session) = &mut online.0 {
let local = session.session.seat().map(usize::from);
let call = session.stands.pending_call;
let controllers = &*drivers.controllers;
let bots = &mut *drivers.bots;
let frame = session.session.next_commit();
let driven = match session.session.paused() {
true => Vec::new(),
false => bots.commit_online(&sim.0),
};
let own = local.and_then(|seat| driven.iter().find(|(s, ..)| usize::from(*s) == seat));
let action = match (call, own) {
(Some(event), _) => PlayerAction::CallEvent(event),
(None, Some(&(_, action, _))) => action,
(None, None) => local.map_or(PlayerAction::None, |seat| pending.0[seat]),
};
let extra: Vec<(u8, PlayerAction)> = driven
.iter()
.filter(|(seat, ..)| Some(usize::from(*seat)) != local)
.map(|&(seat, action, _)| (seat, action))
.collect();
let sim_board = &mut sim.bypass_change_detection().0;
let recording = &mut recorder.bypass_change_detection().0;
let tally = &mut *tally;
let level = {
use crate::app::cycle::Cycle;
BotLevel::from_index(usize::from(session.terms.bot_level))
};
let mut advanced = false;
let committed = session.pump_with(action, &extra, |net| {
if let Some(mut frame_actions) = net.session.advance() {
let at = net.session.frame().saturating_sub(1);
fill_online_bots(
sim_board,
controllers,
&net.abandoned,
level,
at,
&mut frame_actions,
);
bots.before_frame(at, sim_board);
let before = awards::Reading::of(sim_board);
sim_board.tick(&frame_actions);
tally.observe(before, sim_board, &frame_actions);
bots.after_frame(at, sim_board);
debug_assert!(
sim_board.round_over() || sim_board.ticks() == u64::from(net.session.frame()),
"board at tick {} for frame {}",
sim_board.ticks(),
net.session.frame()
);
if let Some(replay) = recording {
replay.record(frame_actions);
debug_assert!(
sim_board.round_over() || replay.inputs.len() as u64 == sim_board.ticks(),
"a recording out of step with its round"
);
}
net.after_frame(sim_board.state_hash());
advanced = true;
}
});
if !driven.is_empty() {
if committed {
for &(seat, action, act) in &driven {
if call.is_none() || Some(usize::from(seat)) != local {
bots.committed(frame, seat, act, action);
}
}
}
bots.send_online(
&sim.bypass_change_detection().0,
recorder.bypass_change_detection().0.as_ref(),
);
}
session.send_catch_ups(&sim.bypass_change_detection().0);
if advanced {
sim.set_changed();
recorder.set_changed();
}
if call.is_some() {
if committed {
session.stands.pending_call = None;
}
} else if let Some(seat) = local
&& (committed || session.session.paused())
{
pending.0[seat] = PlayerAction::None;
}
return;
}
let mut actions = std::mem::take(&mut pending.0);
fill_bot_actions(&sim.0, &drivers.controllers, &mut actions);
let committed = drivers.commit(&sim.0, &mut actions);
let before = awards::Reading::of(&sim.0);
sim.0.tick(&actions);
tally.observe(before, &sim.0, &actions);
if let Some(replay) = &mut recorder.0 {
replay.record(actions);
debug_assert!(
sim.0.round_over() || replay.inputs.len() as u64 == sim.0.ticks(),
"a recording out of step with its round"
);
}
drivers.after(&sim.0, committed, recorder.0.as_ref());
}