#![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, q: Query<&mut TileTextureIndex, With>| { for mut lem in q { if lem.0 == 1 { return; } if lem.0 != 12 { lem.0 = 11; } } }) .add_observer( |_: On, q: Query<&mut TileTextureIndex, (Without, With)>| { for mut lem in q { lem.0 = 13; } }, ) .add_observer( |_: On, q: Query, With)>, mut c: Commands| { // println!("{q:?}"); for lem in q { c.entity(lem).remove::(); } }, ) .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() -> impl Fn(On, Query<(&TilePos, &mut TileTextureIndex)>, Res, 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>, Query<(&TilePos, &mut TileTextureIndex)>, Res, 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::(); } (!(sp.0 != 0)) as u32 } _ => return, }; } } fn reload( mut commands: Commands, mut gr: ResMut, asset_server: Res, q: Query>, ) { gr.0 = ge(); for lem in q { commands.entity(lem).despawn(); } startup(commands, gr.into(), asset_server); } fn startup(mut commands: Commands, ge: Res, asset_server: Res) { // 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 = 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::()).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;