use crate::battle::rng::BattleRng;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EncounterMode {
Walking,
Surfing,
Fishing {
rod_power: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EncounterStep<S> {
None,
Encounter {
species_level: (S, u8),
},
}
pub trait EncounterProvider {
type Species: Copy + Eq + std::fmt::Debug;
fn roll_encounter(
&self,
map_id: u32,
x: i32,
y: i32,
mode: EncounterMode,
rng: &mut dyn BattleRng,
) -> Option<(Self::Species, u8)>;
fn is_encounter_tile(&self, map_id: u32, x: i32, y: i32) -> bool;
}
pub struct EncounterEngine;
impl EncounterEngine {
pub fn on_step<E: EncounterProvider>(
provider: &E,
map_id: u32,
x: i32,
y: i32,
mode: EncounterMode,
rng: &mut dyn BattleRng,
) -> EncounterStep<E::Species> {
if !provider.is_encounter_tile(map_id, x, y) {
return EncounterStep::None;
}
match provider.roll_encounter(map_id, x, y, mode, rng) {
Some(species_level) => EncounterStep::Encounter { species_level },
None => EncounterStep::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::battle::rng::ScriptedRng;
const THRESHOLDS: [u8; 4] = [99, 199, 254, 255];
const SPECIES: [u8; 4] = [10, 20, 30, 40];
const LEVELS: [u8; 4] = [3, 5, 7, 9];
struct MockProvider {
rate: u8,
eligible_tile: (i32, i32),
}
impl MockProvider {
fn new(rate: u8) -> Self {
Self {
rate,
eligible_tile: (1, 1),
}
}
fn select_slot(roll: u8) -> usize {
for (i, &t) in THRESHOLDS.iter().enumerate() {
if roll <= t {
return i;
}
}
3
}
}
impl EncounterProvider for MockProvider {
type Species = u8;
fn is_encounter_tile(&self, map_id: u32, x: i32, y: i32) -> bool {
map_id == 0 && (x, y) == self.eligible_tile
}
fn roll_encounter(
&self,
_map_id: u32,
_x: i32,
_y: i32,
_mode: EncounterMode,
rng: &mut dyn BattleRng,
) -> Option<(u8, u8)> {
let rate_roll = rng.next_u8();
if rate_roll >= self.rate {
return None;
}
let slot = Self::select_slot(rng.next_u8());
Some((SPECIES[slot], LEVELS[slot]))
}
}
#[test]
fn non_encounter_tile_returns_none_and_consumes_no_rng() {
let provider = MockProvider::new(255);
let mut rng = ScriptedRng::new(vec![0, 0, 0]);
let step = EncounterEngine::on_step(&provider, 0, 5, 5, EncounterMode::Walking, &mut rng);
assert_eq!(step, EncounterStep::None);
assert_eq!(rng.consumed(), 0);
}
#[test]
fn roll_at_or_above_rate_returns_none() {
let provider = MockProvider::new(100);
let mut rng = ScriptedRng::new(vec![100, 0]);
let step = EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng);
assert_eq!(step, EncounterStep::None);
let mut rng = ScriptedRng::new(vec![200, 0]);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng),
EncounterStep::None
);
}
#[test]
fn roll_below_rate_returns_expected_species_level() {
let provider = MockProvider::new(100);
let mut rng = ScriptedRng::new(vec![0, 0]);
let step = EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng);
assert_eq!(
step,
EncounterStep::Encounter {
species_level: (10, 3)
}
);
}
#[test]
fn slot_selection_picks_the_right_table_entry() {
let provider = MockProvider::new(255);
let mut rng = ScriptedRng::new(vec![0, 99]);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng),
EncounterStep::Encounter {
species_level: (10, 3)
}
);
let mut rng = ScriptedRng::new(vec![0, 100]);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng),
EncounterStep::Encounter {
species_level: (20, 5)
}
);
let mut rng = ScriptedRng::new(vec![0, 255]);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 1, 1, EncounterMode::Walking, &mut rng),
EncounterStep::Encounter {
species_level: (40, 9)
}
);
}
#[test]
fn mode_is_passed_through_to_provider() {
struct SurfOnly;
impl EncounterProvider for SurfOnly {
type Species = u8;
fn is_encounter_tile(&self, _m: u32, _x: i32, _y: i32) -> bool {
true
}
fn roll_encounter(
&self,
_m: u32,
_x: i32,
_y: i32,
mode: EncounterMode,
_rng: &mut dyn BattleRng,
) -> Option<(u8, u8)> {
matches!(mode, EncounterMode::Surfing).then_some((7, 12))
}
}
let provider = SurfOnly;
let mut rng = ScriptedRng::new(vec![0]);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 0, 0, EncounterMode::Walking, &mut rng),
EncounterStep::None
);
assert_eq!(
EncounterEngine::on_step(&provider, 0, 0, 0, EncounterMode::Surfing, &mut rng),
EncounterStep::Encounter {
species_level: (7, 12)
}
);
assert_eq!(
EncounterEngine::on_step(
&provider,
0,
0,
0,
EncounterMode::Fishing { rod_power: 2 },
&mut rng
),
EncounterStep::None
);
}
}