#![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;