Skip to content

Commit

Permalink
Disentangle system dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
David Peter committed Jul 11, 2024
1 parent e734396 commit 6ec00a0
Show file tree
Hide file tree
Showing 13 changed files with 830 additions and 237 deletions.
492 changes: 492 additions & 0 deletions doc/states_and_events.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 64 additions & 12 deletions src/gui/ai.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,80 @@
use bevy::prelude::*;
use bevy::tasks::futures_lite::future;
use bevy::tasks::{block_on, Task};
use bevy::tasks::{block_on, AsyncComputeTaskPool, Task};

use yinsh::Action;
use yinsh::{Action, GameState, TurnMode};

use super::state::PlayerActionEvent;
use super::graphics::ANIMATION_DURATION;
use super::state_update::{PlayerActionEvent, StateUpdateSet};
use super::PLAYER_AI;

#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct AiSet;

#[derive(Resource)]
pub struct AiPlayerStrength(pub usize);

#[derive(Event)]
pub enum AiComputationEvent {
Start(GameState),
Cancel,
}

#[derive(Resource)]
pub struct AiTask(Option<Task<Action>>);
struct AiTask(Option<Task<Action>>);

impl AiTask {
pub fn new() -> Self {
fn new() -> Self {
Self(None)
}

pub fn is_running(&self) -> bool {
fn is_running(&self) -> bool {
self.0.is_some()
}

pub fn start(&mut self, task: Task<Action>) {
fn start(&mut self, task: Task<Action>) {
self.0 = Some(task);
}

pub fn cancel(&mut self) {
fn cancel(&mut self) {
self.0 = None;
}

pub fn get_status(&mut self) -> Option<Action> {
fn get_status(&mut self) -> Option<Action> {
block_on(future::poll_once(self.0.as_mut().unwrap()))
}
}

#[derive(Resource)]
pub struct AiPlayerStrength(pub usize);
fn manage_ai_tasks(
mut task: ResMut<AiTask>,
mut events: EventReader<AiComputationEvent>,
strength: Res<AiPlayerStrength>,
) {
for event in events.read() {
match event {
AiComputationEvent::Start(ref game_state) => {
let task_pool = AsyncComputeTaskPool::get();

let game_state = game_state.clone();
let search_depth = strength.0;
task.start(task_pool.spawn(async move {
// TODO! This is a hack to make sure the AI takes at least as long as
// the animation.
if matches!(game_state.turn_mode, TurnMode::MarkerPlacement) {
std::thread::sleep(ANIMATION_DURATION);
}

yinsh::get_ai_player_action(search_depth, &game_state)
}));
}
AiComputationEvent::Cancel => {
task.cancel();
}
}
}
}

pub fn wait_for_ai_move(
fn perform_ai_actions(
mut task: ResMut<AiTask>,
mut player_action_events: EventWriter<PlayerActionEvent>,
) {
Expand All @@ -55,3 +94,16 @@ pub fn wait_for_ai_move(

player_action_events.send(PlayerActionEvent(PLAYER_AI, action));
}

pub fn plugin(app: &mut App) {
app.insert_resource(AiTask::new())
.insert_resource(AiPlayerStrength(11))
.add_event::<AiComputationEvent>()
.add_systems(
Update,
(manage_ai_tasks, perform_ai_actions)
.chain()
.in_set(AiSet)
.after(StateUpdateSet),
);
}
13 changes: 13 additions & 0 deletions src/gui/board_update_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use bevy::prelude::Event;

use yinsh::{Coord, Player};

#[derive(Event)]
pub enum BoardUpdateEvent {
AddRing(Coord, Player),
AddMarker(Coord, Player),
MoveRing(Coord, Coord),
RemoveRing(Coord),
RemoveRun(Vec<Coord>),
FlipMarkers(Vec<Coord>),
}
9 changes: 7 additions & 2 deletions src/gui/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use yinsh::{Coord, Player};

use super::{
board::{BoardElement, Marker, Ring},
grid::draw_grid,
PLAYER_HUMAN,
};

Expand Down Expand Up @@ -117,6 +118,8 @@ fn setup_graphics(
mut config_store: ResMut<GizmoConfigStore>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.insert_resource(ClearColor(COLOR_BACKGROUND));

// Render layer 1 is for the grid
commands.spawn((
Camera2dBundle {
Expand Down Expand Up @@ -157,6 +160,8 @@ fn setup_graphics(
config.render_layers = BACKGROUND_RENDER_LAYER;
}

pub fn graphics_plugin(app: &mut App) {
app.add_systems(PreStartup, setup_graphics);
pub fn plugin(app: &mut App) {
app.insert_resource(Msaa::Sample8)
.add_systems(PreStartup, setup_graphics)
.add_systems(Update, draw_grid);
}
24 changes: 13 additions & 11 deletions src/gui/io.rs → src/gui/history.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
use bevy::prelude::*;
use yinsh::Player;

use crate::gui::{
graphics::{spawn_marker, spawn_ring},
state::InteractionState,
};
use crate::gui::graphics::{spawn_marker, spawn_ring};

use super::{
ai::AiTask, board::BoardElement, graphics::PlayerColors, interaction::CursorElement,
state::GameState,
ai::AiComputationEvent, board::BoardElement, graphics::PlayerColors,
interaction::CursorElement, state_update::GameState,
};

#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct HistorySet;

pub fn save_and_load_game_state(
keyboard: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
player_colors: Res<PlayerColors>,
keyboard: Res<ButtonInput<KeyCode>>,
mut game_state: ResMut<GameState>,
mut interaction_state: ResMut<InteractionState>,
mut ai_task: ResMut<AiTask>,
mut ai_computation_events: EventWriter<AiComputationEvent>,
q_board_elements: Query<Entity, (With<BoardElement>, Without<CursorElement>)>,
) {
let filename = "gamestate.yml";
Expand All @@ -30,8 +29,7 @@ pub fn save_and_load_game_state(
println!("Loading game state from {}", filename);
*game_state.as_deref_mut() = yinsh::GameState::load_from(filename);

*interaction_state = InteractionState::from_turn_mode(&game_state);
ai_task.cancel();
ai_computation_events.send(AiComputationEvent::Cancel);

// Despawn all board elements
for entity in q_board_elements.iter() {
Expand All @@ -50,3 +48,7 @@ pub fn save_and_load_game_state(
}
}
}

pub fn plugin(app: &mut App) {
app.add_systems(Update, save_and_load_game_state.in_set(HistorySet));
}
12 changes: 6 additions & 6 deletions src/gui/information_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::{
ai::AiPlayerStrength,
graphics::BACKGROUND_RENDER_LAYER,
interaction::CursorCoord,
state::{GameState, InteractionState},
state_update::{GameState, InteractionState},
PLAYER_HUMAN,
};

Expand Down Expand Up @@ -48,11 +48,11 @@ fn update_information_display(
points_a=game_state.points_a,
points_b=game_state.points_b,
mode=match *interaction_state {
InteractionState::RingPlacement => "Place a ring on the board",
InteractionState::MarkerPlacement => "Place a marker in one of your rings",
InteractionState::RingPlacement(_) => "Place a ring on the board",
InteractionState::MarkerPlacement(_) => "Place a marker in one of your rings",
InteractionState::RingMovement(_, _) => "Move the selected ring",
InteractionState::RunRemoval { .. } => "Select a run of five markers to remove",
InteractionState::RingRemoval => "Select one of your rings to remove it",
InteractionState::RingRemoval(_) => "Select one of your rings to remove it",
InteractionState::AutoMove | InteractionState::WaitForAI => "AI is thinking...",
InteractionState::Winner(Player::A) => "Game over. You win!",
InteractionState::Winner(Player::B) => "Game over. Floyd wins!",
Expand All @@ -62,7 +62,7 @@ fn update_information_display(
);
}

pub fn information_display_plugin(app: &mut App) {
pub fn plugin(app: &mut App) {
app.add_systems(Startup, setup_information_display)
.add_systems(Update, update_information_display);
.add_systems(Update, update_information_display.ambiguous_with_all());
}
Loading

0 comments on commit 6ec00a0

Please sign in to comment.