|
|
|
@ -5,46 +5,164 @@ pub(crate) struct AiPlugin;
|
|
|
|
impl Plugin for AiPlugin {
|
|
|
|
impl Plugin for AiPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
app.init_state::<PlayState>()
|
|
|
|
app.init_state::<PlayState>()
|
|
|
|
|
|
|
|
.init_resource::<AiMove>()
|
|
|
|
|
|
|
|
.init_state::<AiDrama>()
|
|
|
|
// Bogo AI Systems
|
|
|
|
// Bogo AI Systems
|
|
|
|
.add_systems(Update,
|
|
|
|
.add_systems(
|
|
|
|
bogo_ai.run_if(in_state(PlayState::AiBogo))
|
|
|
|
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)]
|
|
|
|
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone, Component)]
|
|
|
|
enum PlayState {
|
|
|
|
pub(crate) enum PlayState {
|
|
|
|
#[default]
|
|
|
|
|
|
|
|
Human,
|
|
|
|
Human,
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
AiBogo,
|
|
|
|
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
|
|
|
|
// Bogo AI logic
|
|
|
|
fn bogo_ai(
|
|
|
|
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 board: ResMut<Board>,
|
|
|
|
mut move_events: EventWriter<Move>,
|
|
|
|
mut move_events: EventWriter<Move>,
|
|
|
|
time: Res<Time>,
|
|
|
|
mut next_drama: ResMut<NextState<AiDrama>>,
|
|
|
|
|
|
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
|
|
|
|
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
// TODO: Assuming AI is Side::A
|
|
|
|
let tweak = tweaks
|
|
|
|
let ai_side = Side::A;
|
|
|
|
.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);
|
|
|
|
|
|
|
|
|
|
|
|
let on = board.on(ai_side);
|
|
|
|
// Initialize timer
|
|
|
|
let set: Vec<(&BoardIndex, HashSet<BoardIndex>)> = on
|
|
|
|
if timer.duration() != timer_duration {
|
|
|
|
.iter()
|
|
|
|
timer.set_duration(Duration::from_secs_f32(1.0));
|
|
|
|
.filter_map(|(_, idx)| {
|
|
|
|
timer.set_mode(TimerMode::Once);
|
|
|
|
let moves = board.valid_moves(*idx);
|
|
|
|
}
|
|
|
|
(!moves.is_empty()).then_some((idx, moves))
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let r = time.elapsed_seconds() as usize;
|
|
|
|
// Timer before committing move
|
|
|
|
|
|
|
|
if !timer.finished() {
|
|
|
|
|
|
|
|
timer.tick(time.delta());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
info!("AI Holding");
|
|
|
|
|
|
|
|
info!("Timer: {:?}", timer);
|
|
|
|
|
|
|
|
|
|
|
|
let m_i = r % set.len();
|
|
|
|
// Apply selected moves
|
|
|
|
let m = set.get(m_i).unwrap();
|
|
|
|
board
|
|
|
|
let m_bi = m.0;
|
|
|
|
.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());
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let n_i = r % m.1.len();
|
|
|
|
// Pass off to next state in the "drama"
|
|
|
|
let n_bi = m.1.iter().nth(n_i).unwrap();
|
|
|
|
next_drama.set(AiDrama::Thinking);
|
|
|
|
|
|
|
|
|
|
|
|
move_events.send(Move { from: *m_bi, to: Some(*n_bi), ..default() });
|
|
|
|
timer.reset();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|