1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use crate::drop::{Drop, DropSim, Item};

/// Represents a single speed run, in which barters are made and blazes are fought.
/// The results of bartering and fighting are stored as a list of drops that can be interrogated
/// to see exactly how lucky or unlucky the run was.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Run {
    pub barters: Vec<Drop>,
    pub fights: Vec<Drop>,
}

impl Run {
    /// Create a run from the results of bartering with piglins and fighting blazes.
    /// ```
    /// # use mc_sim::drop::*;
    /// # use mc_sim::run::*;
    /// let barters = vec![
    ///     Drop { item: Item::Gravel, roll: 0, count: 1 },
    ///     Drop { item: Item::Gravel, roll: 0, count: 1 },
    ///     Drop { item: Item::EnderPearl, roll: 0, count: 1 },
    ///     Drop { item: Item::Gravel, roll: 0, count: 1 },
    ///     Drop { item: Item::EnderPearl, roll: 0, count: 3 },
    /// ];
    ///
    /// let fights = vec![
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 0 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 0 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 1 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 0 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 0 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 1 },
    ///     Drop { item: Item::BlazeRod, roll: 0, count: 1 },
    /// ];
    ///
    /// let run = Run::new(barters, fights);
    /// assert_eq!(run.total_barters(), 5);
    /// assert_eq!(run.total_pearls(), 4);
    /// assert_eq!(run.total_fights(), 7);
    /// assert_eq!(run.total_rods(), 3);
    /// ```
    pub fn new(barters: Vec<Drop>, fights: Vec<Drop>) -> Self {
        Self { barters, fights }
    }

    /// The total number of barters that were made in the run.
    pub fn total_barters(&self) -> u32 {
        self.barters.len() as u32
    }

    pub fn successful_barters(&self) -> u32 {
        self.barters
            .iter()
            .filter(|drop| drop.item == Item::EnderPearl)
            .count() as u32
    }

    /// The total number of pearls that were obtained during the run.
    pub fn total_pearls(&self) -> u32 {
        self.barters
            .iter()
            .filter(|drop| drop.item == Item::EnderPearl)
            .map(|drop| drop.count)
            .sum()
    }

    /// The total number of blazes that were killed in the run.
    pub fn total_fights(&self) -> u32 {
        self.fights.len() as u32
    }

    pub fn successful_fights(&self) -> u32 {
        self.barters
            .iter()
            .filter(|drop| drop.item == Item::BlazeRod)
            .count() as u32
    }

    /// The total number of blaze rods that were obtained during the run.
    pub fn total_rods(&self) -> u32 {
        self.fights
            .iter()
            .filter(|drop| drop.item == Item::BlazeRod)
            .map(|drop| drop.count)
            .sum()
    }
}

/// The goals of a run simulation.
/// This represents the minimum resources a runner is looking for out of this run before moving on.
/// E.G. total_pearls is the number of ender pearls the runner wants before they stop trading with piglins.
///
/// This does not take into account ideas like "batches" of trades, where a runner might choose to leave
/// before reaching their goal because the run won't pb if they have to trade any more and they just hope
/// that they get good portal luck.
///
/// Ideas like this are not in scope for this simulation and can be accounted for in the analysis of the data.
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct RunGoals {
    pub target_pearls: u32,
    pub target_rods: u32,
}

/// A Minecraft speed run simulation.
#[derive(Debug)]
pub struct RunSim<'a, 'b> {
    barter_drop_sim: &'a mut DropSim,
    blaze_drop_sim: &'b mut DropSim,
    pearl_target: u32,
    rods_target: u32,
}

impl<'a, 'b> RunSim<'a, 'b> {
    /// Creates a minecraft speed run simulator.
    /// ```
    /// # use mc_sim::drop::*;
    /// # use mc_sim::drop_list;
    /// # use mc_sim::run::*;
    /// let mut barter_drop_sim = DropSim::new(drop_list::barter_drop_list(10, 10).list_clone());
    /// let mut blaze_drop_sim = DropSim::new(drop_list::blaze_drop_list(7).list_clone());
    ///
    /// let mut run_sim = RunSim::new(&mut barter_drop_sim, &mut blaze_drop_sim, 10, 7);
    /// let run = run_sim.run();
    /// assert!(run.total_pearls() >= 10);
    /// assert!(run.total_rods() >= 7);
    /// ```
    pub fn new(
        barter_drop_sim: &'a mut DropSim,
        blaze_drop_sim: &'b mut DropSim,
        pearl_target: u32,
        rods_target: u32,
    ) -> Self {
        Self {
            barter_drop_sim,
            blaze_drop_sim,
            pearl_target,
            rods_target,
        }
    }

    /// Simulate a run.
    pub fn run(&mut self) -> Run {
        Run::new(self.barter_for_pearls(), self.fight_for_rods())
    }

    /// Barter for pearls until the pearl target is reached.
    pub fn barter_for_pearls(&mut self) -> Vec<Drop> {
        RunSim::farm_for_item(
            &mut self.barter_drop_sim,
            Item::EnderPearl,
            self.pearl_target,
        )
    }

    /// Fight blazes until the rod target is reached.
    pub fn fight_for_rods(&mut self) -> Vec<Drop> {
        RunSim::farm_for_item(&mut self.blaze_drop_sim, Item::BlazeRod, self.rods_target)
    }

    /// Farm for an item from a drop simulator with a minimum target before we're done.
    pub fn farm_for_item(drop_sim: &mut DropSim, item: Item, minimum: u32) -> Vec<Drop> {
        let mut drops = Vec::new();
        let mut count = 0;

        while count < minimum {
            let drop = drop_sim.get_drop();

            if drop.item == item {
                count += drop.count;
            }

            drops.push(drop);
        }

        drops
    }
}