#![allow(dead_code)]
#![allow(clippy::needless_return)]
#![allow(clippy::implicit_return)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::items_after_statements)]
#![allow(clippy::unnecessary_cast)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::explicit_iter_loop)]
#![allow(clippy::format_in_format_args)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::std_instead_of_core)]
#![allow(clippy::similar_names)]
#![allow(clippy::duplicated_attributes)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::trivially_copy_pass_by_ref)]
#![allow(clippy::missing_inline_in_public_items)]
#![allow(clippy::useless_vec)]
#![allow(clippy::unnested_or_patterns)]
#![allow(clippy::else_if_without_else)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::redundant_else)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::struct_field_names)]
#![allow(dead_code)]
use tiles_tools::{
ecs::{World, Position, Health, Stats, Team, AI, Movable},
coordinates::{
Distance,
square::{Coordinate as SquareCoord, EightConnected},
},
field_of_view::{FieldOfView, FOVAlgorithm, LightSource, LightingCalculator},
pathfind::astar,
};
#[derive(Debug, Clone, Copy)]
struct Stealth {
stealth_level: u32,
state: StealthState,
noise_level: u32,
in_cover: bool,
}
impl Stealth {
pub fn new(level: u32) -> Self {
Self {
stealth_level: level,
state: StealthState::Hidden,
noise_level: 1,
in_cover: false,
}
}
pub fn start_moving(&mut self) {
self.state = StealthState::Moving;
self.noise_level = if self.in_cover { 2 } else { 4 };
}
pub fn hide(&mut self) {
self.state = StealthState::Hidden;
self.noise_level = 0;
}
pub fn set_cover(&mut self, in_cover: bool) {
self.in_cover = in_cover;
if in_cover {
self.noise_level = self.noise_level.saturating_sub(2);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum StealthState {
Hidden, Moving, Exposed, Distracted, }
#[derive(Debug, Clone)]
struct Vision {
base_range: u32,
fov_angle: u32,
facing_direction: u32,
detection_threshold: u32,
alert_level: u32,
}
impl Vision {
pub fn new(range: u32, angle: u32) -> Self {
Self {
base_range: range,
fov_angle: angle,
facing_direction: 0,
detection_threshold: 5,
alert_level: 0,
}
}
pub fn face_direction(&mut self, direction: u32) {
self.facing_direction = direction % 360;
}
pub fn increase_alert(&mut self, amount: u32) {
self.alert_level = (self.alert_level + amount).min(10);
}
pub fn get_effective_range(&self) -> u32 {
self.base_range + (self.alert_level / 2)
}
}
#[derive(Debug, Clone)]
struct PatrolRoute {
waypoints: Vec<SquareCoord<EightConnected>>,
current_waypoint: usize,
wait_time: u32,
current_wait: u32,
reverse_route: bool,
going_forward: bool,
}
impl PatrolRoute {
pub fn new(waypoints: Vec<SquareCoord<EightConnected>>, wait_time: u32) -> Self {
Self {
waypoints,
current_waypoint: 0,
wait_time,
current_wait: 0,
reverse_route: true,
going_forward: true,
}
}
pub fn get_current_target(&self) -> Option<SquareCoord<EightConnected>> {
self.waypoints.get(self.current_waypoint).copied()
}
pub fn advance_waypoint(&mut self) {
if self.current_wait > 0 {
self.current_wait = self.current_wait.saturating_sub(1);
return;
}
if self.reverse_route {
if self.going_forward {
if self.current_waypoint + 1 >= self.waypoints.len() {
self.going_forward = false;
if self.current_waypoint > 0 {
self.current_waypoint -= 1;
}
} else {
self.current_waypoint += 1;
}
} else {
if self.current_waypoint == 0 {
self.going_forward = true;
self.current_waypoint = 1;
} else {
self.current_waypoint -= 1;
}
}
} else {
self.current_waypoint = (self.current_waypoint + 1) % self.waypoints.len();
}
self.current_wait = self.wait_time;
}
}
struct StealthGame {
world: World,
player_entity: hecs::Entity,
guard_entities: Vec<hecs::Entity>,
fov_calculator: FieldOfView,
lighting_calculator: LightingCalculator<SquareCoord<EightConnected>>,
level_map: LevelMap,
turn_counter: u32,
game_state: GameState,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum GameState {
Stealth, Alert, Detected, Victory, GameOver, }
struct LevelMap {
width: i32,
height: i32,
walls: std::collections::HashSet<SquareCoord<EightConnected>>,
cover_spots: std::collections::HashSet<SquareCoord<EightConnected>>,
light_sources: Vec<SquareCoord<EightConnected>>,
objective: SquareCoord<EightConnected>,
}
impl LevelMap {
pub fn new(width: i32, height: i32) -> Self {
let mut map = Self {
width,
height,
walls: std::collections::HashSet::new(),
cover_spots: std::collections::HashSet::new(),
light_sources: Vec::new(),
objective: SquareCoord::<EightConnected>::new(width - 2, height - 2),
};
map.generate_level_layout();
map
}
fn generate_level_layout(&mut self) {
for x in 0..self.width {
self.walls.insert(SquareCoord::<EightConnected>::new(x, 0));
self.walls.insert(SquareCoord::<EightConnected>::new(x, self.height - 1));
}
for y in 0..self.height {
self.walls.insert(SquareCoord::<EightConnected>::new(0, y));
self.walls.insert(SquareCoord::<EightConnected>::new(self.width - 1, y));
}
let obstacles = [
(5, 3), (5, 4), (5, 5), (8, 7), (9, 7), (10, 7), (12, 4), (13, 4), ];
for &(x, y) in &obstacles {
if x < self.width && y < self.height {
self.walls.insert(SquareCoord::<EightConnected>::new(x, y));
}
}
let cover_locations = [
(3, 6), (7, 2), (11, 8), (6, 9), (14, 5)
];
for &(x, y) in &cover_locations {
if x < self.width && y < self.height && !self.walls.contains(&SquareCoord::new(x, y)) {
self.cover_spots.insert(SquareCoord::<EightConnected>::new(x, y));
}
}
self.light_sources = vec![
SquareCoord::<EightConnected>::new(4, 4),
SquareCoord::<EightConnected>::new(10, 6),
SquareCoord::<EightConnected>::new(7, 9),
];
}
pub fn is_wall(&self, coord: &SquareCoord<EightConnected>) -> bool {
self.walls.contains(coord)
}
pub fn has_cover(&self, coord: &SquareCoord<EightConnected>) -> bool {
self.cover_spots.contains(coord)
}
pub fn blocks_sight(&self, coord: &SquareCoord<EightConnected>) -> bool {
self.is_wall(coord)
}
pub fn is_passable(&self, coord: &SquareCoord<EightConnected>) -> bool {
!self.is_wall(coord) &&
coord.x >= 0 && coord.x < self.width &&
coord.y >= 0 && coord.y < self.height
}
}
impl StealthGame {
pub fn new() -> Self {
let mut world = World::new();
let level_map = LevelMap::new(20, 15);
let player_team = Team::new(0);
let player_entity = world.spawn((
Position::new(SquareCoord::<EightConnected>::new(2, 2)),
Health::new(100),
Stats::new(10, 8, 15, 1), player_team,
Movable::new(3),
Stealth::new(7), ));
let guard_team = Team::hostile(1);
let mut guard_entities = Vec::new();
let guard1 = world.spawn((
Position::new(SquareCoord::<EightConnected>::new(8, 3)),
Health::new(80),
Stats::new(12, 10, 8, 1),
guard_team,
Movable::new(2),
AI::new(1.0),
Vision::new(6, 120), PatrolRoute::new(vec![
SquareCoord::<EightConnected>::new(8, 3),
SquareCoord::<EightConnected>::new(12, 3),
SquareCoord::<EightConnected>::new(12, 6),
SquareCoord::<EightConnected>::new(8, 6),
], 2),
));
guard_entities.push(guard1);
let guard2 = world.spawn((
Position::new(SquareCoord::<EightConnected>::new(16, 11)),
Health::new(90),
Stats::new(14, 12, 6, 1),
guard_team,
AI::new(1.5),
Vision::new(8, 180), PatrolRoute::new(vec![
SquareCoord::<EightConnected>::new(16, 11),
SquareCoord::<EightConnected>::new(15, 13),
], 3),
));
guard_entities.push(guard2);
let mut lighting_calculator = LightingCalculator::new();
for torch_pos in &level_map.light_sources {
let torch_light = LightSource::new(torch_pos.clone(), 4, 0.7)
.with_color(1.0, 0.8, 0.3); lighting_calculator.add_light_source(torch_light);
}
Self {
world,
player_entity,
guard_entities,
fov_calculator: FieldOfView::with_algorithm(FOVAlgorithm::Shadowcasting),
lighting_calculator,
level_map,
turn_counter: 0,
game_state: GameState::Stealth,
}
}
pub fn process_turn(&mut self) {
self.turn_counter += 1;
match self.game_state {
GameState::Stealth => {
self.process_stealth_turn();
}
GameState::Alert => {
self.process_alert_turn();
}
GameState::Detected => {
self.process_detected_turn();
}
_ => {
return;
}
}
if let Ok(player_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(self.player_entity) {
if player_pos.coord.distance(&self.level_map.objective) <= 1 {
self.game_state = GameState::Victory;
}
}
}
fn process_stealth_turn(&mut self) {
let guard_entities = self.guard_entities.clone();
for guard in guard_entities {
self.update_guard_patrol(guard);
}
if self.check_player_detection() {
self.game_state = GameState::Alert;
println!("🚨 Alert! Guards are searching...");
}
self.simulate_player_movement();
}
fn process_alert_turn(&mut self) {
if self.check_player_detection() {
self.game_state = GameState::Detected;
println!("🎯 Player detected! Game over!");
}
if self.turn_counter % 5 == 0 {
let mut alert_decreased = true;
for &guard in &self.guard_entities {
if let Ok(mut vision) = self.world.get_mut::<Vision>(guard) {
if vision.alert_level > 0 {
vision.alert_level -= 1;
alert_decreased = false;
}
}
}
if alert_decreased {
self.game_state = GameState::Stealth;
println!("😌 Guards have stopped searching.");
}
}
}
fn process_detected_turn(&mut self) {
self.game_state = GameState::GameOver;
}
fn update_guard_patrol(&mut self, guard: hecs::Entity) {
let current_pos = {
if let Ok(pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
pos.coord
} else {
return;
}
};
let target_to_move = {
if let Ok(mut patrol) = self.world.get_mut::<PatrolRoute>(guard) {
if let Some(target) = patrol.get_current_target() {
if current_pos.distance(&target) <= 1 {
patrol.advance_waypoint();
None
} else {
Some(target)
}
} else {
return;
}
} else {
return;
}
};
if let Some(target) = target_to_move {
self.move_guard_toward(guard, &target);
}
}
fn move_guard_toward(&mut self, guard: hecs::Entity, target: &SquareCoord<EightConnected>) {
if let Ok(pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
if let Ok(movable) = self.world.get::<Movable>(guard) {
let path_result = astar(
&pos.coord,
target,
|coord| self.level_map.is_passable(coord),
|_| 1,
);
if let Some((path, _cost)) = path_result {
let move_distance = movable.range.min(path.len() as u32 - 1);
if move_distance > 0 {
let new_pos = path[move_distance as usize];
println!("🚶 Guard moving from ({}, {}) to ({}, {})",
pos.coord.x, pos.coord.y, new_pos.x, new_pos.y);
}
}
}
}
}
fn check_player_detection(&mut self) -> bool {
let (player_coord, player_stealth_state, player_light_level) = {
if let Ok(player_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(self.player_entity) {
if let Ok(player_stealth) = self.world.get::<Stealth>(self.player_entity) {
let lighting = self.lighting_calculator.calculate_lighting(|coord| {
self.level_map.blocks_sight(coord)
});
let player_light_level = lighting.get(&player_pos.coord).unwrap_or(&0.0);
(player_pos.coord, *player_stealth, *player_light_level)
} else {
return false;
}
} else {
return false;
}
};
let guard_entities = self.guard_entities.clone();
for guard in guard_entities {
let guard_coord = {
if let Ok(guard_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
guard_pos.coord
} else {
continue;
}
};
if let Ok(mut vision) = self.world.get_mut::<Vision>(guard) {
let distance = guard_coord.distance(&player_coord);
let effective_range = vision.get_effective_range();
if distance <= effective_range {
let has_los = self.fov_calculator.line_of_sight(
&guard_coord,
&player_coord,
|coord| self.level_map.blocks_sight(coord)
);
if has_los {
let base_detection = 10 - player_stealth_state.stealth_level;
let light_modifier = (player_light_level * 5.0) as u32;
let distance_modifier = (effective_range - distance) / 2;
let cover_modifier = if player_stealth_state.in_cover { 0 } else { 3 };
let noise_modifier = player_stealth_state.noise_level;
let detection_score = base_detection + light_modifier +
distance_modifier + cover_modifier + noise_modifier;
if detection_score >= vision.detection_threshold {
vision.increase_alert(3);
if vision.alert_level >= 7 {
return true; }
}
}
}
}
}
false
}
fn simulate_player_movement(&mut self) {
let (current_pos, objective) = {
if let Ok(player_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(self.player_entity) {
(player_pos.coord, self.level_map.objective)
} else {
return;
}
};
if let Some((path, _cost)) = astar(
¤t_pos,
&objective,
|coord| self.level_map.is_passable(coord),
|_| 1,
) {
if path.len() > 1 {
let next_pos = path[1];
if self.is_position_safe_for_player(&next_pos) {
println!("🚶 Player moving from ({}, {}) to ({}, {})",
current_pos.x, current_pos.y, next_pos.x, next_pos.y);
if let Ok(mut stealth) = self.world.get_mut::<Stealth>(self.player_entity) {
stealth.start_moving();
stealth.set_cover(self.level_map.has_cover(&next_pos));
}
} else {
if let Ok(mut stealth) = self.world.get_mut::<Stealth>(self.player_entity) {
stealth.hide();
}
println!("🕵️ Player waiting for guards to pass...");
}
}
}
}
fn is_position_safe_for_player(&self, pos: &SquareCoord<EightConnected>) -> bool {
for &guard in &self.guard_entities {
if let Ok(guard_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
if let Ok(vision) = self.world.get::<Vision>(guard) {
let distance = guard_pos.coord.distance(pos);
if distance <= vision.get_effective_range() {
let has_los = self.fov_calculator.line_of_sight(
&guard_pos.coord,
pos,
|coord| self.level_map.blocks_sight(coord)
);
if has_los {
return false; }
}
}
}
}
true
}
pub fn print_game_state(&self) {
println!("\n=== Turn {} ===", self.turn_counter);
println!("Game State: {:?}", self.game_state);
self.print_level_map();
if let Ok(player_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(self.player_entity) {
if let Ok(stealth) = self.world.get::<Stealth>(self.player_entity) {
println!("🕵️ Player at ({}, {}): {:?}, Noise: {}, Cover: {}",
player_pos.coord.x, player_pos.coord.y,
stealth.state, stealth.noise_level, stealth.in_cover);
}
}
for (i, &guard) in self.guard_entities.iter().enumerate() {
if let Ok(guard_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
if let Ok(vision) = self.world.get::<Vision>(guard) {
println!("👮 Guard {} at ({}, {}): Alert Level {}/10",
i + 1, guard_pos.coord.x, guard_pos.coord.y, vision.alert_level);
}
}
}
}
fn print_level_map(&self) {
let lighting = self.lighting_calculator.calculate_lighting(|coord| {
self.level_map.blocks_sight(coord)
});
println!("\n📍 Level Map:");
println!("🕵️ = Player, 👮 = Guard, 🏆 = Objective, ⬛ = Wall");
println!("🌿 = Cover, 🕯️ = Torch, ░ = Dark, ▓ = Dim, ▀ = Lit");
for y in 0..self.level_map.height {
for x in 0..self.level_map.width {
let coord = SquareCoord::<EightConnected>::new(x, y);
let mut symbol = None;
if let Ok(player_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(self.player_entity) {
if player_pos.coord == coord {
symbol = Some("🕵️");
}
}
if symbol.is_none() {
for &guard in &self.guard_entities {
if let Ok(guard_pos) = self.world.get::<Position<SquareCoord<EightConnected>>>(guard) {
if guard_pos.coord == coord {
symbol = Some("👮");
break;
}
}
}
}
if symbol.is_none() && coord == self.level_map.objective {
symbol = Some("🏆");
}
if symbol.is_none() && self.level_map.light_sources.contains(&coord) {
symbol = Some("🕯️");
}
if symbol.is_none() {
if self.level_map.is_wall(&coord) {
symbol = Some("⬛");
} else if self.level_map.has_cover(&coord) {
symbol = Some("🌿");
} else {
let light_level = lighting.get(&coord).unwrap_or(&0.0);
if *light_level >= 0.7 {
symbol = Some("▀"); } else if *light_level >= 0.3 {
symbol = Some("▓"); } else {
symbol = Some("░"); }
}
}
print!("{}", symbol.unwrap_or(" "));
}
println!();
}
}
pub fn run_simulation(&mut self) {
println!("🎯 Stealth Game Simulation");
println!("=========================");
println!("Objective: Reach the 🏆 without being detected!");
println!("Use cover (🌿) and darkness to avoid guards (👮)");
self.print_game_state();
for turn in 1..=30 {
self.process_turn();
self.print_game_state();
match self.game_state {
GameState::Victory => {
println!("🏆 VICTORY! You reached the objective undetected!");
break;
}
GameState::GameOver => {
println!("💀 GAME OVER! You were caught by the guards!");
break;
}
_ => {
if turn >= 30 {
println!("⏰ Simulation ended - mission continues...");
break;
}
}
}
std::thread::sleep(std::time::Duration::from_millis(1500));
}
}
}
fn main() {
let mut game = StealthGame::new();
game.run_simulation();
println!("\n✨ Stealth Game Demo Complete!");
println!("This example showcases:");
println!("• Field-of-view calculations for line-of-sight");
println!("• Multi-source dynamic lighting systems");
println!("• Stealth mechanics with detection algorithms");
println!("• Guard AI with patrol routes and alertness");
println!("• Environmental factors (cover, lighting, noise)");
}