You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
martian-chess/src/ai.rs

168 lines
3.8 KiB
Rust

use crate::prelude::*;
pub(crate) struct AiPlugin;
impl Plugin for AiPlugin {
fn build(&self, app: &mut App) {
app.init_state::<PlayState>()
.init_resource::<AiMove>()
.init_state::<AiDrama>()
// Bogo AI Systems
.add_systems(
Update,
(
bogo_ai_thinking
.run_if(in_state(AiDrama::Thinking)),
bogo_ai_holding
.run_if(in_state(AiDrama::Holding)),
)
.run_if(in_state(PlayState::AiBogo))
.run_if(in_state(GameState::Play))
.run_if(in_state(TurnState(Side::A)))
);
}
}
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone, Component)]
pub(crate) enum PlayState {
Human,
#[default]
AiBogo,
}
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone)]
enum AiDrama {
// Before AI picks up a piece
#[default]
Thinking,
// When AI picks up piece
Holding,
}
/// The move the AI will commit
#[derive(Debug, Resource, Default)]
struct AiMove(Move);
// Bogo AI logic
fn bogo_ai_thinking(
board: Res<Board>,
query: Query<(Entity, &BoardIndex), With<Piece>>,
time: Res<Time>,
mut next_drama: ResMut<NextState<AiDrama>>,
mut timer: Local<Timer>,
mut commands: Commands,
mut selected: ResMut<AiMove>,
tweaks: Res<Assets<Tweaks>>,
tweaks_file: Res<tweak::GameTweaks>,
) {
let tweak = tweaks
.get(tweaks_file.handle.clone())
.expect("Load tweakfile");
let lag = tweak.get::<f32>("ai_lag_secs").unwrap();
let timer_duration = Duration::from_secs_f32(lag);
// Initialize timer
if timer.duration() != timer_duration {
timer.set_duration(Duration::from_secs_f32(1.0));
timer.set_mode(TimerMode::Once);
}
// Timer before committing move
if !timer.finished() {
timer.tick(time.delta());
} else {
info!("AI finished thinking");
info!("Timer: {:?}", timer);
// TODO: Assuming AI is Side::A
let ai_side = Side::A;
// Determine which moves to do
let (from, to) = {
let on = board.on(ai_side);
let set: Vec<(&BoardIndex, HashSet<BoardIndex>)> = on
.iter()
.filter_map(|(_, idx)| {
let moves = board.valid_moves(*idx);
(!moves.is_empty()).then_some((idx, moves))
})
.collect();
let r = time.elapsed_seconds() as usize;
let m_i = r % set.len();
let m = set.get(m_i).unwrap();
let m_bi = m.0;
let n_i = r % m.1.len();
let n_bi = m.1.iter().nth(n_i).unwrap();
(m_bi.clone(), n_bi.clone())
};
// Pick up selected piece
query
.iter()
.find_map(|(e, bi)| {
(*bi == from).then_some(e)
})
.iter()
.for_each(|e| {
commands.entity(*e).insert(Selected);
});
*selected = AiMove(Move { from, to: Some(to), ..default() });
// Pass off to next state in the "drama"
next_drama.set(AiDrama::Holding);
// Reset timer for next time
timer.reset();
}
}
fn bogo_ai_holding(
selected: Res<AiMove>,
time: Res<Time>,
mut timer: Local<Timer>,
mut board: ResMut<Board>,
mut move_events: EventWriter<Move>,
mut next_drama: ResMut<NextState<AiDrama>>,
tweaks: Res<Assets<Tweaks>>,
tweaks_file: Res<tweak::GameTweaks>,
) {
let tweak = tweaks
.get(tweaks_file.handle.clone())
.expect("Load tweakfile");
let lag = tweak.get::<f32>("ai_lag_secs").unwrap();
let timer_duration = Duration::from_secs_f32(lag);
// Initialize timer
if timer.duration() != timer_duration {
timer.set_duration(Duration::from_secs_f32(1.0));
timer.set_mode(TimerMode::Once);
}
// Timer before committing move
if !timer.finished() {
timer.tick(time.delta());
} else {
info!("AI Holding");
info!("Timer: {:?}", timer);
// Apply selected moves
board
.move_piece(selected.0.from, selected.0.to.unwrap())
.unwrap()
.iter()
.for_each(|ai_move| {
// De-select the piece
move_events.send(ai_move.clone());
});
// Pass off to next state in the "drama"
next_drama.set(AiDrama::Thinking);
timer.reset();
}
}