Compare commits

...

2 Commits

Author SHA1 Message Date
Elijah C. Voigt 53922bcd91 its a total hack but this is better! 1 year ago
Elijah C. Voigt db09fe8cce AI is almost fully working! 1 year ago

@ -288,4 +288,7 @@ fast_light_speed = 1.0
# 0: Disabled # 0: Disabled
# -1: No limit # -1: No limit
# N: Some value in-between # N: Some value in-between
undo_limit = -1 undo_limit = -1
[ai]
lag_secs = 2.0

@ -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();
}
} }

@ -85,13 +85,18 @@ impl Plugin for Display3dPlugin {
.run_if(resource_exists::<tweak::GameTweaks>), .run_if(resource_exists::<tweak::GameTweaks>),
switch_sides switch_sides
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(state_changed::<game::TurnState>), .run_if(state_changed::<game::TurnState>)
.run_if(in_state(ai::PlayState::Human)),
vantage_point vantage_point
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(not(state_changed::<game::TurnState>)) .run_if(in_state(PlayState::AiBogo))
.run_if(any_component_added::<Selected>() .run_if(in_state(TurnState(Side::B)))
.or_else(any_component_removed::<Selected>()) .run_if(any_component_added::<Selected>().or_else(any_component_removed::<Selected>())),
), vantage_point
.run_if(in_state(GameState::Play))
.run_if(in_state(PlayState::Human))
.run_if(not(state_changed::<TurnState>))
.run_if(any_component_added::<Selected>().or_else(any_component_removed::<Selected>())),
update_pieces update_pieces
.run_if(resource_exists::<tweak::GameTweaks>) .run_if(resource_exists::<tweak::GameTweaks>)
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
@ -570,7 +575,7 @@ fn set_models(
let scene = tweak.get::<String>(key).unwrap(); let scene = tweak.get::<String>(key).unwrap();
let new_handle = gltf.named_scenes.get(scene.as_str()).expect("Game board model"); let new_handle = gltf.named_scenes.get(scene.as_str()).expect("Game board model");
if *new_handle != *handle { if *new_handle != *handle {
info!("Updating piece for {:?}", entity); debug!("Updating piece for {:?}", entity);
*handle = new_handle.clone(); *handle = new_handle.clone();
} }
} }

@ -22,9 +22,11 @@ impl Plugin for MenuPlugin {
handle_button_press::<GameState>, handle_button_press::<GameState>,
handle_button_press::<MenuState>, handle_button_press::<MenuState>,
handle_button_press::<TutorialState>, handle_button_press::<TutorialState>,
handle_button_press::<PlayState>,
) )
.run_if(any_component_changed::<Interaction>()), .run_if(any_component_changed::<Interaction>()),
) )
.add_systems(Update, manage_ai_button.run_if(state_changed::<ai::PlayState>))
.add_systems(Update, handle_escape.run_if(just_pressed(KeyCode::Escape))); .add_systems(Update, handle_escape.run_if(just_pressed(KeyCode::Escape)));
} }
} }
@ -201,6 +203,44 @@ fn init_play_menu(
}); });
}); });
// Opponent button
parent
.spawn((
ButtonAction(ai::PlayState::AiBogo),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Opponent: AI".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Credits button // Credits button
parent parent
.spawn(( .spawn((
@ -345,3 +385,28 @@ fn handle_button_press<S: States + Clone + Component>(
next_state.set(ba.clone()) next_state.set(ba.clone())
}); });
} }
fn manage_ai_button(
mut curr: Res<State<ai::PlayState>>,
mut query: Query<(&mut ButtonAction<PlayState>, &Children)>,
mut texts: Query<&mut Text>,
) {
query.iter_mut().for_each(|(mut ba, children)| {
ba.0 = match curr.get() {
PlayState::AiBogo => PlayState::Human,
PlayState::Human => PlayState::AiBogo,
};
children.iter().for_each(|c| {
texts.get_mut(*c).iter_mut().for_each(|t| {
t.sections.iter_mut().for_each(|s| {
s.value = match ba.0 {
PlayState::AiBogo => "Opponent: AI".into(),
PlayState::Human => "Opponent: Human".into()
}
});
});
});
});
}

Loading…
Cancel
Save