Skip to main content

asterion_core/
minotaur.rs

1use crate::{entity::Entity, Direction, Hero, IntoDirection, Maze, PlayerId, Position};
2use itertools::Itertools;
3use rand::{seq::IteratorRandom, RngExt};
4use std::time::Instant;
5
6#[derive(Debug)]
7pub struct Minotaur {
8    id: PlayerId,
9    name: String,
10    chasing: Option<PlayerId>,
11    maze_id: usize,
12    position: Position,
13    direction: Direction,
14    speed: u64,
15    vision: usize,
16    aggression: f64,
17    // pub view: View,
18    last_update_time: Instant,
19    pub kills: usize,
20}
21
22impl Minotaur {
23    fn find_closest_hero(&mut self, visible_heros: &Vec<&Hero>) -> Option<PlayerId> {
24        let closest_hero = visible_heros
25            .iter()
26            .min_by_key(|hero| self.position.distance_squared(hero.position()));
27
28        closest_hero.map(|hero| hero.id())
29    }
30
31    pub fn new(
32        name: String,
33        maze_id: usize,
34        position: Position,
35        speed: u64,
36        vision: usize,
37        aggression: f64,
38    ) -> Self {
39        Self {
40            id: PlayerId::new_v4(),
41            name,
42            chasing: None,
43            maze_id,
44            position,
45            direction: Direction::North,
46            speed,
47            vision,
48            aggression,
49            last_update_time: Instant::now(),
50            kills: 0,
51        }
52    }
53
54    pub fn update(&mut self, maze: &mut Maze, visible_heros: Vec<&Hero>) {
55        if let Some(hero_id) = self.chasing {
56            if !visible_heros.iter().map(|h| h.id()).any(|id| id == hero_id) {
57                self.chasing = None;
58            }
59        } else if let Some(hero_id) = self.find_closest_hero(&visible_heros) {
60            self.chasing = Some(hero_id);
61        }
62
63        if self.last_update_time.elapsed() < self.movement_recovery_duration() {
64            return;
65        }
66
67        let rng = &mut rand::rng();
68        let (x, y) = self.position;
69
70        let mut available_directions = [
71            Direction::North,
72            Direction::East,
73            Direction::South,
74            Direction::West,
75        ]
76        .iter()
77        .filter(|d| {
78            let new_position = (
79                (x as isize + d.as_offset().0).max(0) as usize,
80                (y as isize + d.as_offset().1).max(0) as usize,
81            );
82            maze.is_valid_minotaur_position(new_position)
83        })
84        .collect_vec();
85
86        if let Some(hero_id) = self.chasing {
87            // Move toward chased hero
88            if let Some(hero) = visible_heros.iter().find(|h| h.id() == hero_id) {
89                let current_distance = self.position.distance(hero.position());
90                available_directions = available_directions
91                    .iter()
92                    .filter(|d| {
93                        let new_position = (
94                            (x as isize + d.as_offset().0).max(0) as usize,
95                            (y as isize + d.as_offset().1).max(0) as usize,
96                        );
97                        new_position.distance(hero.position()) < current_distance
98                    }).copied()
99                    .collect_vec();
100            }
101        }
102
103        if !available_directions.is_empty() && rng.random_bool(self.aggression) {
104            // Pick a random available direction
105            let direction = available_directions.iter().choose(rng).unwrap();
106            let new_position = (
107                (x as isize + direction.as_offset().0).max(0) as usize,
108                (y as isize + direction.as_offset().1).max(0) as usize,
109            );
110            self.position = new_position;
111            self.last_update_time = Instant::now();
112        }
113    }
114
115    pub fn is_chasing(&self, player_id: PlayerId) -> bool {
116        self.chasing == Some(player_id)
117    }
118
119    pub fn is_chasing_someone(&self) -> bool {
120        self.chasing.is_some()
121    }
122}
123
124impl Entity for Minotaur {
125    fn id(&self) -> PlayerId {
126        self.id
127    }
128
129    fn name(&self) -> &str {
130        &self.name
131    }
132
133    fn vision(&self) -> usize {
134        self.vision
135    }
136
137    fn speed(&self) -> u64 {
138        self.speed
139    }
140
141    fn position(&self) -> super::Position {
142        self.position
143    }
144
145    fn direction(&self) -> Direction {
146        self.direction
147    }
148
149    fn maze_id(&self) -> usize {
150        self.maze_id
151    }
152}