1use itertools::Itertools;
2use serde::{Deserialize, Serialize};
3use std::{
4 array,
5 cmp::{min, Ordering},
6 collections::HashMap,
7 iter,
8};
9
10use crate::armor_and_skills::{Armor, Gender, Skill};
11
12pub type Jewels = [Option<Skill>; 3];
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Build {
16 pub helmet: Option<(Armor, Jewels)>,
17 pub chest: Option<(Armor, Jewels)>,
18 pub arm: Option<(Armor, Jewels)>,
19 pub waist: Option<(Armor, Jewels)>,
20 pub leg: Option<(Armor, Jewels)>,
21 pub talisman: Option<(Armor, Jewels)>,
22 pub weapon_jewels: Jewels,
23}
24
25impl Build {
26 pub fn get_all_skills_and_amounts(&self) -> HashMap<Skill, u8> {
27 let mut hm = HashMap::with_capacity(5);
28 for opt in array::IntoIter::new([
29 &self.helmet,
30 &self.chest,
31 &self.arm,
32 &self.waist,
33 &self.leg,
34 &self.talisman,
35 ])
36 .flatten()
37 {
38 let (armor, jewels) = opt;
39 for (skill, amount) in armor.skills.iter() {
40 if !hm.contains_key(skill) {
41 hm.insert(*skill, 0);
42 }
43 *hm.get_mut(skill).unwrap() += amount
44 }
45 for jewel in jewels.iter().flatten() {
46 if !hm.contains_key(jewel) {
47 hm.insert(*jewel, 0);
48 }
49 *hm.get_mut(jewel).unwrap() += 1
50 }
51 }
52 for jewel in self.weapon_jewels.iter().flatten() {
53 if !hm.contains_key(jewel) {
54 hm.insert(*jewel, 0);
55 }
56 *hm.get_mut(jewel).unwrap() += 1
57 }
58 hm
59 }
60}
61
62fn optionify_slice_and_add_none<T>(slice: &[T]) -> Vec<Option<&T>> {
68 slice
69 .iter()
70 .map(Some)
71 .chain(iter::once(None::<&T>))
72 .collect()
73}
74
75fn brute_force_search_builds(
85 wishes: &[(Skill, u8)],
86 all_armor_slices: AllArmorSlices,
87 weapon_slots: [u8; 3],
88) -> Vec<Build> {
89 let AllArmorSlices {
90 helmets,
91 chests,
92 arms,
93 waists,
94 legs,
95 talismans,
96 } = all_armor_slices;
97 let mut builds: Vec<Build> = Vec::with_capacity(500);
98
99 for v in array::IntoIter::new([helmets, chests, arms, waists, legs, talismans])
100 .map(optionify_slice_and_add_none)
101 .multi_cartesian_product()
102 {
103 let (helmet, chest, arm, waist, leg, talisman) = (v[0], v[1], v[2], v[3], v[4], v[5]);
104
105 let mut delta_wishes: Vec<(Skill, u8)> = wishes.iter().copied().collect();
108 for &option in &[helmet, chest, arm, waist, leg, talisman] {
109 if let Some(armor) = option {
110 for &(skill, amount) in &armor.skills {
111 for (w_skill, w_amount) in delta_wishes.iter_mut() {
112 if skill == *w_skill {
113 if amount > *w_amount {
114 *w_amount = 0;
115 } else {
116 *w_amount -= amount;
117 }
118 }
119 }
120 }
121 }
122 }
123 delta_wishes.sort_unstable_by(|(skill_a, _), (skill_b, _)| {
127 let jewel_size_a = skill_a.get_jewel_size();
128 let jewel_size_b = skill_b.get_jewel_size();
129 match (jewel_size_a, jewel_size_b) {
130 (None, None) => Ordering::Equal,
131 (None, Some(_)) => Ordering::Greater,
132 (Some(_), None) => Ordering::Less,
133 (Some(a), Some(b)) => b.cmp(&a),
134 }
135 });
136
137 const NB_PARTS: usize = 7;
138
139 let mut possible_jewels_for_each_part = [Jewels::default(); NB_PARTS];
140 let mut jewel_indices = [0; NB_PARTS];
141 let mut empty_armor_slots = [
142 extract_slots_copy(&helmet),
143 extract_slots_copy(&chest),
144 extract_slots_copy(&arm),
145 extract_slots_copy(&waist),
146 extract_slots_copy(&leg),
147 extract_slots_copy(&talisman),
148 weapon_slots,
149 ];
150
151 'wish_loop: for (skill, amount) in delta_wishes.iter_mut() {
152 for (jewel_slots, (jewels, index)) in empty_armor_slots.iter_mut().zip(
153 possible_jewels_for_each_part
154 .iter_mut()
155 .zip(jewel_indices.iter_mut()),
156 ) {
157 if *amount > 0 {
158 for slot in jewel_slots.iter_mut() {
159 if let Some(jewel_size) = skill.get_jewel_size() {
160 if *slot >= jewel_size {
161 *slot = 0;
162 *amount -= 1;
163 jewels[*index] = Some(*skill);
164 *index += 1;
165 if *amount == 0 {
166 continue 'wish_loop;
169 }
170 }
171 } else {
172 break 'wish_loop;
175 }
176 }
177 }
178 }
179 }
180
181 if delta_wishes.iter().all(|(_, amount)| *amount == 0) {
182 let build = Build {
183 helmet: helmet.map(|armor| (armor.clone(), possible_jewels_for_each_part[0])),
184 chest: chest.map(|armor| (armor.clone(), possible_jewels_for_each_part[1])),
185 arm: arm.map(|armor| (armor.clone(), possible_jewels_for_each_part[2])),
186 waist: waist.map(|armor| (armor.clone(), possible_jewels_for_each_part[3])),
187 leg: leg.map(|armor| (armor.clone(), possible_jewels_for_each_part[4])),
188 talisman: talisman.map(|armor| (armor.clone(), possible_jewels_for_each_part[5])),
189 weapon_jewels: possible_jewels_for_each_part[6],
190 };
191
192 let mut push_it = true;
200 let mut replacement: Option<&mut Build> = None;
201 let mut to_remove = Vec::with_capacity(20);
203
204 for (key, old_build) in builds.iter_mut().enumerate() {
205 let mut old_has_better_none = false;
206 let mut new_has_better_none = false;
207 for couple in array::IntoIter::new([
211 &build.helmet,
212 &build.chest,
213 &build.arm,
214 &build.waist,
215 &build.leg,
216 &build.talisman,
217 ])
218 .zip([
219 &old_build.helmet,
220 &old_build.chest,
221 &old_build.arm,
222 &old_build.waist,
223 &old_build.leg,
224 &old_build.talisman,
225 ]) {
226 match couple {
227 (None, Some(_)) => {
228 new_has_better_none = true;
229 if old_has_better_none {
230 break;
232 }
233 }
234 (Some(_), None) => {
235 old_has_better_none = true;
236 if new_has_better_none {
237 break;
238 }
239 }
240 (Some(part), Some(old_part)) if part.0 != old_part.0 => {
241 old_has_better_none = new_has_better_none;
243 break;
244 }
245 _ => {}
246 };
247 }
248
249 if old_has_better_none != new_has_better_none {
250 push_it = false;
251 if old_has_better_none {
252 break;
253 }
254 if replacement.is_none() {
258 replacement = Some(old_build);
259 } else {
260 to_remove.push(key);
263 }
264 }
265 }
266 if let Some(place) = replacement {
267 *place = build;
268 } else if push_it {
269 builds.push(build);
270 }
271 to_remove.sort_unstable();
272
273 for index in to_remove.drain(..).rev() {
275 builds.swap_remove(index);
276 }
277 }
278 }
279
280 builds
281}
282
283pub struct AllArmorSlices<'a> {
284 pub helmets: &'a [Armor],
285 pub chests: &'a [Armor],
286 pub arms: &'a [Armor],
287 pub waists: &'a [Armor],
288 pub legs: &'a [Armor],
289 pub talismans: &'a [Armor],
290}
291
292fn extract_slots_copy(helmet: &Option<&Armor>) -> [u8; 3] {
294 match helmet {
295 Some(armor) => {
296 let mut slots = [0; 3];
297 for (key, &slot) in armor.slots.iter().enumerate() {
298 slots[key] = slot;
299 }
300 slots
301 }
302 None => [0; 3],
303 }
304}
305
306pub fn pre_selection_then_brute_force_search(
309 wishes: &[(Skill, u8)],
310 all_armor_slices: AllArmorSlices,
311 gender: Gender,
312 weapon_slots: [u8; 3],
313) -> Vec<Build> {
314 let AllArmorSlices {
315 helmets,
316 chests,
317 arms,
318 waists,
319 legs,
320 talismans,
321 } = all_armor_slices;
322 brute_force_search_builds(
323 wishes,
324 AllArmorSlices {
325 helmets: &search_best_candidates(wishes, helmets, gender),
326 chests: &search_best_candidates(wishes, chests, gender),
327 arms: &search_best_candidates(wishes, arms, gender),
328 waists: &search_best_candidates(wishes, waists, gender),
329 legs: &search_best_candidates(wishes, legs, gender),
330 talismans: &search_best_candidates(wishes, talismans, gender),
331 },
332 weapon_slots,
333 )
334}
335
336#[derive(Eq, PartialEq, Debug)]
350enum OddComparison {
351 Better,
352 Worse,
353 Undefined,
354}
355
356fn compare_armors(wishes: &[(Skill, u8)], a: &Armor, b: &Armor) -> OddComparison {
366 let (delta_skills_a, delta_skills_b) = generate_deltas_skills(&a.skills, &b.skills);
369 let (priority_a, virtual_slots_a) = generate_virtual_slots(wishes, &delta_skills_a);
370 let (priority_b, virtual_slots_b) = generate_virtual_slots(wishes, &delta_skills_b);
371
372 let mut slots_a_copy = Vec::with_capacity(3);
373 let mut slots_b_copy = Vec::with_capacity(3);
374
375 if a.slots.len() + virtual_slots_a.len() <= b.slots.len() {
376 for &s in &[a.slots.as_slice(), virtual_slots_a.as_slice()].concat() {
377 slots_a_copy.push(s);
378 }
379 for &s in &b.slots {
380 slots_b_copy.push(s);
381 }
382 slots_a_copy.sort_unstable();
383 slots_b_copy.sort_unstable();
384 if !priority_a
392 && ((slots_a_copy == slots_b_copy && a.slots.len() < b.slots.len())
393 || compare_slots(&slots_a_copy, &slots_b_copy) == OddComparison::Worse)
394 {
395 return OddComparison::Worse;
396 }
397 } else if b.slots.len() + virtual_slots_b.len() <= a.slots.len() {
398 for &s in &a.slots {
399 slots_a_copy.push(s);
400 }
401 for &s in &[b.slots.as_slice(), virtual_slots_b.as_slice()].concat() {
402 slots_b_copy.push(s);
403 }
404 slots_a_copy.sort_unstable();
405 slots_b_copy.sort_unstable();
406 if !priority_b
407 && ((slots_a_copy == slots_b_copy && b.slots.len() < a.slots.len())
408 || compare_slots(&slots_a_copy, &slots_b_copy) == OddComparison::Better)
409 {
410 return OddComparison::Better;
411 }
412 }
413
414 OddComparison::Undefined
415}
416
417fn search_best_candidates(wishes: &[(Skill, u8)], armors: &[Armor], gender: Gender) -> Vec<Armor> {
423 let armors: Vec<&Armor> = armors
425 .iter()
426 .filter(|armor| {
427 if let Some(armor_gender) = armor.gender {
428 gender == armor_gender
429 } else {
430 true
431 }
432 })
433 .filter(|armor| {
434 for (skill, _) in wishes {
435 for &slot in &armor.slots {
437 if let Some(size) = skill.get_jewel_size() {
438 if slot >= size {
439 return true;
440 }
441 }
442 }
443 for (armor_skill, _) in &armor.skills {
445 if armor_skill == skill {
446 return true;
447 }
448 }
449 }
450 false
451 })
452 .collect();
453
454 let mut armors_copy = Vec::with_capacity(armors.len());
455 for &w in &armors {
456 armors_copy.push(w.clone());
457 }
458 armors_copy.retain(|a| {
460 for &b in &armors {
461 if compare_armors(wishes, a, b) == OddComparison::Worse {
462 return false;
463 }
464 }
465 true
466 });
467
468 armors_copy
469}
470
471fn generate_virtual_slots(wishes: &[(Skill, u8)], skills: &[(Skill, u8)]) -> (bool, Vec<u8>) {
477 let mut priority = false;
478 let mut virtual_slots = Vec::with_capacity(5);
479 for (wished_skill, _) in wishes {
480 for (skill, amount) in skills {
481 if skill == wished_skill {
482 if let Some(size) = skill.get_jewel_size() {
483 for _ in 0..*amount {
484 virtual_slots.push(size);
485 }
486 } else if *amount > 0 {
487 priority = true;
489 }
490 }
491 }
492 }
493 (priority, virtual_slots)
494}
495
496fn compare_slots(slots0: &[u8], slots1: &[u8]) -> OddComparison {
502 if slots0.is_empty() {
503 if slots1.is_empty() {
504 return OddComparison::Undefined;
505 }
506 return OddComparison::Worse;
507 }
508 if slots1.is_empty() {
509 return OddComparison::Better;
510 }
511 let mut slot_cmp: Vec<i8> = vec![0; 3];
512 for (key, &value) in slots0.iter().enumerate() {
513 slot_cmp[key + 3 - slots0.len()] = value as i8;
516 }
517 for (key, &value) in slots1.iter().enumerate() {
518 slot_cmp[key + 3 - slots1.len()] -= value as i8;
519 }
520 let mut sign = 0;
521 for i in slot_cmp {
522 if i != 0 {
523 if sign == 0 {
524 sign = i;
525 } else if sign.signum() != i.signum() {
526 return OddComparison::Undefined;
534 }
535 }
536 }
537 if sign == 0 {
538 OddComparison::Undefined
539 } else if sign.signum() == -1 {
540 OddComparison::Worse
541 } else {
542 OddComparison::Better
543 }
544}
545
546fn generate_deltas_skills(skills0: &[(Skill, u8)], skills1: &[(Skill, u8)]) -> DeltasSkills {
558 let mut delta0 = Vec::with_capacity(skills0.len());
559 let mut delta1 = Vec::with_capacity(skills1.len());
560
561 for &s in skills0 {
562 delta0.push(s);
563 }
564 for &s in skills1 {
565 delta1.push(s);
566 }
567
568 let mut to_do0 = Vec::with_capacity(3);
569 let mut to_do1 = Vec::with_capacity(3);
570
571 for (key0, &(skill0, amount0)) in delta0.iter().enumerate() {
572 for (key1, &(skill1, amount1)) in delta1.iter().enumerate() {
573 if skill0 == skill1 {
574 let delta = min(amount0, amount1);
575 to_do0.push((key0, (skill0, amount0 - delta)));
577 to_do1.push((key1, (skill0, amount1 - delta)));
578 }
579 }
580 }
581
582 for &(key, couple) in &to_do0 {
583 delta0[key] = couple;
584 }
585 for &(key, couple) in &to_do1 {
586 delta1[key] = couple;
587 }
588
589 (delta0, delta1)
590}
591
592type DeltasSkills = (Vec<(Skill, u8)>, Vec<(Skill, u8)>);