[no description]
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..8644542
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,239 @@
+#![feature(try_blocks)]
+use std::time::Duration;
+
+use bevy::{
+ DefaultPlugins,
+ app::App,
+ camera::ScalingMode,
+ ecs::{event::Trigger, observer::ObservedBy, resource::IsResource},
+ input::{common_conditions::input_just_pressed, keyboard::KeyboardInput},
+ prelude::*,
+ window::{Window, WindowPlugin},
+};
+use bevy_ecs_tilemap::{helpers::square_grid::neighbors::Neighbors, prelude::*};
+use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
+use rand::{rng, seq::IteratorRandom};
+
+use crate::picking::TilemapPickingPlugin;
+
+const W: usize = 20;
+const H: usize = 10;
+const BOMB_COUNT: usize = 30;
+fn main() -> AppExit {
+ App::new()
+ .add_plugins(
+ DefaultPlugins
+ .set(WindowPlugin {
+ primary_window: Some(Window {
+ title: "Minesweeper!".into(),
+ ..default()
+ }),
+ ..default()
+ })
+ .set(ImagePlugin::default_linear()),
+ )
+ .insert_resource(Grid(ge()))
+ .add_plugins(TilemapPlugin)
+ // .add_plugins(EguiPlugin::default())
+ // .add_plugins(WorldInspectorPlugin::new())
+ .add_plugins(TilemapPickingPlugin)
+ .add_systems(Startup, startup)
+ // .add_systems(Update, movement)
+ .add_observer(|_: On<End>, q: Query<&mut TileTextureIndex, With<Bomb>>| {
+ for mut lem in q {
+ if lem.0 == 1 {
+ return;
+ }
+ if lem.0 != 12 {
+ lem.0 = 11;
+ }
+ }
+ })
+ .add_observer(
+ |_: On<End>, q: Query<&mut TileTextureIndex, (Without<Bomb>, With<Flag>)>| {
+ for mut lem in q {
+ lem.0 = 13;
+ }
+ },
+ )
+ .add_observer(
+ |_: On<End>, q: Query<Entity, (With<TilePos>, With<ObservedBy>)>, mut c: Commands| {
+ // println!("{q:?}");
+ for lem in q {
+ c.entity(lem).remove::<ObservedBy>();
+ }
+ },
+ )
+ .add_systems(Update, reload.run_if(input_just_pressed(KeyCode::KeyR)))
+ .run()
+}
+#[derive(Resource)]
+struct Grid([[u8; W]; W]);
+#[derive(EntityEvent, Debug, Clone, Reflect)]
+struct Zero(Entity);
+#[derive(Event, Debug, Clone, Reflect)]
+struct End;
+#[derive(Component)]
+struct Bomb;
+#[derive(Component)]
+struct Flag;
+#[derive(Component)]
+struct Murderable;
+
+macro_rules! all {
+ () => {
+ (0..W).flat_map(|x| (0..H).map(move |y| (x, y)))
+ };
+}
+
+fn reveal<E: EntityEvent>()
+-> impl Fn(On<E>, Query<(&TilePos, &mut TileTextureIndex)>, Res<Grid>, Query<&TileStorage>, Commands)
+{
+ move |ev, mut tile, ge, store, mut cmds| {
+ let Ok((&tp @ TilePos { x, y }, mut sp)) = tile.get_mut(ev.event_target()) else {
+ return;
+ };
+ *sp = TileTextureIndex(match ge.0[y as usize][x as usize] {
+ 0 if (sp.0 != 10 && sp.0 == 0) => {
+ let map_size = TilemapSize {
+ x: W as u32,
+ y: H as u32,
+ };
+ for lem in Neighbors::get_square_neighboring_positions(&tp, &map_size, false)
+ .entities(store.single().unwrap())
+ .iter()
+ {
+ cmds.trigger(Zero(*lem));
+ }
+ 10
+ }
+ n @ 1..=8 => 1 + n as u32,
+ _ => return,
+ });
+ }
+}
+fn clicked()
+-> impl Fn(On<Pointer<Press>>, Query<(&TilePos, &mut TileTextureIndex)>, Res<Grid>, Commands) {
+ move |ev, mut tile, ge, mut cmds| {
+ let Ok((&TilePos { x, y }, mut sp)) = tile.get_mut(ev.event_target()) else {
+ return;
+ };
+ let real = ge.0[y as usize][x as usize];
+ sp.0 = match ev.event().button {
+ PointerButton::Primary if sp.0 == 0 => match real {
+ 255 => {
+ cmds.trigger(End);
+ 12
+ }
+ 0 => {
+ cmds.trigger(Zero(ev.event_target()));
+ return;
+ }
+ n @ 1..=8 => 1 + n as u32,
+ _ => 10,
+ },
+ PointerButton::Secondary if sp.0 < 2 => {
+ if sp.0 == 0 {
+ cmds.entity(ev.event_target()).insert(Flag);
+ } else {
+ cmds.entity(ev.event_target()).remove::<Flag>();
+ }
+ (!(sp.0 != 0)) as u32
+ }
+ _ => return,
+ };
+ }
+}
+fn reload(
+ mut commands: Commands,
+ mut gr: ResMut<Grid>,
+ asset_server: Res<AssetServer>,
+ q: Query<Entity, With<Murderable>>,
+) {
+ gr.0 = ge();
+ for lem in q {
+ commands.entity(lem).despawn();
+ }
+ startup(commands, gr.into(), asset_server);
+}
+fn startup(mut commands: Commands, ge: Res<Grid>, asset_server: Res<AssetServer>) {
+ // commands.spawn(Camera2d);
+ commands.spawn((
+ Camera2d,
+ Projection::Orthographic(OrthographicProjection {
+ scaling_mode: ScalingMode::AutoMin {
+ min_width: (W as f32 + 4.) * 256.,
+ min_height: (H as f32 + 4.) * 256.,
+ },
+ ..OrthographicProjection::default_2d()
+ }),
+ Murderable,
+ ));
+ let texture_handle: Handle<Image> = asset_server.load("tiles.png");
+
+ let map_size = TilemapSize {
+ x: W as u32,
+ y: H as u32,
+ };
+
+ let mut tile_storage = TileStorage::empty(map_size);
+ let tilemap_entity = commands.spawn_empty().insert(Murderable).id();
+ let tilemap_id = TilemapId(tilemap_entity);
+
+ for (x, y) in all!() {
+ let position = TilePos {
+ x: x as u32,
+ y: y as u32,
+ };
+ let mut e = commands.spawn((
+ TileBundle {
+ texture_index: TileTextureIndex(0),
+ position,
+ tilemap_id,
+ ..default()
+ },
+ Murderable,
+ ));
+ if ge.0[y][x] == 255 {
+ e.insert(Bomb);
+ }
+ let tile_entity = e.observe(clicked()).observe(reveal::<Zero>()).id();
+ tile_storage.set(&position, tile_entity);
+ }
+
+ let tile_size = TilemapTileSize { x: 256.0, y: 256.0 };
+ let grid_size = tile_size.into();
+
+ commands.entity(tilemap_entity).insert((
+ TilemapBundle {
+ grid_size,
+ size: map_size,
+ storage: tile_storage,
+ texture: TilemapTexture::Single(texture_handle),
+ tile_size,
+ map_type: TilemapType::Square,
+ anchor: TilemapAnchor::Center,
+
+ ..Default::default()
+ },
+ Murderable,
+ ));
+}
+
+fn ge() -> [[u8; W]; W] {
+ let mut g = [[0u8; W]; W];
+
+ for (x, y) in all!().sample(&mut rng(), BOMB_COUNT) {
+ g[y][x] = 255;
+ let lems = aoc::util::nb((x, y));
+ lems.into_iter().for_each(|(x, y)| {
+ try {
+ let lem = g.get_mut(y)?.get_mut(x)?;
+ *lem = lem.saturating_add(1);
+ };
+ });
+ }
+
+ g
+}
+mod picking;