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
use rand::prelude::*;
use rayon::prelude::*;

use crate::character::{Character, RawCaracsValue};
use crate::dofapi::{CaracKind, Element, Equipement};
use crate::rls::rls;

const STEPS: u32 = 100_000;
const ASSIGNABLE_CARACS: &[CaracKind] = &[
    CaracKind::Vitality,
    CaracKind::Wisdom,
    CaracKind::Stats(Element::Air),
    CaracKind::Stats(Element::Earth),
    CaracKind::Stats(Element::Fire),
    CaracKind::Stats(Element::Water),
];

fn walk_character<'i>(
    init: &Character<'i>,
    rng: &mut impl rand::Rng,
    db_slot_pool: &[Vec<&'i Equipement>],
) -> Character<'i> {
    let mut new = init.clone();

    if rng.gen_bool(0.5) {
        // Swap some items
        let slot_i = rng.gen_range(0, db_slot_pool.len());
        let item = db_slot_pool[slot_i]
            .choose(rng)
            .expect("No available item for slot");
        new.item_slots[slot_i].equip(item);
        new
    } else {
        // Swap some statistics
        let kind = ASSIGNABLE_CARACS.iter().choose(rng).unwrap();
        let from = ASSIGNABLE_CARACS.iter().choose(rng).unwrap();

        if new
            .carac_spend_or_seek(kind, *[1, 5, 10].choose(rng).unwrap(), from)
            .is_err()
        {
            let _ = new.carac_spend_or_seek(kind, 1, from);
        }
        new
    }
}

pub fn eval_character(
    character: &Character<'_>,
    target: &[(RawCaracsValue, f64)],
) -> f64 {
    let target_min = |target: f64, width: f64, x: f64| -> f64 {
        1. / (1. + (-4. * (x - target) / width).exp())
    };

    let target_zero = |width: f64, x: f64| -> f64 {
        let nx = 2. * x / width; // normalize x
        (1. - (nx.exp() - (-nx).exp()) / (nx.exp() + (-nx).exp())).powi(2)
    };

    let caracs = character.get_caracs();
    let targets_weight: f64 = target
        .iter()
        .map(|(target_type, target_val)| {
            if let Ok(smithmage_weight) = target_type.approx_smithmage_weight()
            {
                let val = caracs.eval(target_type);
                let width = 100. / smithmage_weight;
                let invert =
                    if target_type.is_decreasing() { -1. } else { 1. };
                target_min(*target_val * invert, width, val * invert)
            } else {
                1.
            }
        })
        .product();

    let count_item_conflicts = character.count_item_conflicts();
    let conflicts_weight = 0.05f64.powi(count_item_conflicts.into());

    let conditions_weight = target_zero(
        200.,
        character.condition_overflow(&character.all_conditions()),
    );

    targets_weight * conflicts_weight * conditions_weight
}

pub fn optimize_character<'i>(
    init: Character<'i>,
    count: u64,
    target: &[(RawCaracsValue, f64)],
    db_equipements: &'i [Equipement],
) -> Vec<Character<'i>> {
    // Reorder set into pools assigned to each slot
    let slot_pool: Vec<_> = init
        .item_slots
        .iter()
        .map(|slot| {
            db_equipements
                .iter()
                .filter(|item| slot.get_allowed().contains(&item.item_type))
                .collect::<Vec<_>>()
        })
        .collect();

    (0..count)
        .into_par_iter()
        .map_init(rand::thread_rng, |mut rng, _| {
            rls(
                init.clone(),
                STEPS,
                &mut rng,
                |character| eval_character(character, target),
                |character, rng| walk_character(&character, rng, &slot_pool),
            )
        })
        .collect()
}