iner_calc/
lib.rs

1use factorial::Factorial;
2use indicatif::{ParallelProgressIterator, ProgressStyle};
3use rayon::iter::{ParallelBridge, ParallelIterator};
4use std::sync::{Arc, Mutex, RwLock};
5use std::thread;
6use std::time::Duration;
7use std::{cell::RefCell, collections::HashMap, vec};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Iner {
11    /// 火焰伊纳
12    Nesre,
13    /// 火焰伊纳 I
14    NesreI,
15    /// 火焰伊纳 II
16    NesreII,
17    // 火焰伊纳 III
18    NesreIII,
19    /// 火焰伊纳 IV
20    NesreIV,
21    /// 天空伊纳
22    Pet,
23    /// 天空伊纳 I
24    PetI,
25    /// 天空伊纳 II
26    PetII,
27    /// 天空伊纳 III
28    PetIII,
29    /// 草叶伊纳
30    Gabe,
31    /// 草叶伊纳 I
32    GabeI,
33    /// 草叶伊纳 II
34    GabeII,
35    /// 草叶伊纳 III
36    GabeIII,
37    /// 沙伊纳
38    Shay,
39}
40
41impl Iner {
42    /// 伊纳基础分数
43    pub const fn base_score(&self) -> usize {
44        match self {
45            Iner::Nesre => 1,
46            Iner::NesreI => 2,
47            Iner::NesreII => 10,
48            Iner::NesreIII => 35,
49            Iner::NesreIV => 85,
50            Iner::Pet => 1,
51            Iner::PetI => 3,
52            Iner::PetII => 22,
53            Iner::PetIII => 105,
54            Iner::Gabe => 1,
55            Iner::GabeI => 5,
56            Iner::GabeII => 50,
57            Iner::GabeIII => 500,
58            Iner::Shay => 1,
59        }
60    }
61}
62
63#[derive(Debug, Copy, Clone)]
64pub enum Technique {
65    // 萃雕
66    ExtractI,
67    ExtractII,
68    ExtractIII,
69    ExtractIV,
70    // 滤纯
71    FilterI,
72    FilterII,
73    FilterIII,
74    // 交糅
75    MingleI,
76    MingleII,
77    MingleIII,
78    // 落晶
79    CrystalI,
80    CrystalII,
81    CrystalIII,
82}
83
84/// 天空系列宝石额外加分
85pub fn add_pet_iner_score(game: &mut GameData, score: usize) {
86    *game.iner_score_mut(Iner::Pet) += score;
87    *game.iner_score_mut(Iner::PetI) += score;
88    *game.iner_score_mut(Iner::PetII) += score;
89    *game.iner_score_mut(Iner::PetIII) += score;
90}
91
92impl Technique {
93    /// 在操作台上的操作
94    pub fn operate_on_board(&self, data: &mut GameData) {
95        match self {
96            // 将一份 <火焰伊纳> 刻印为 2 份<火焰伊纳 I>
97            Technique::ExtractI => {
98                let input = data.iner(Iner::Nesre);
99                *data.iner_mut(Iner::Nesre) = 0.0;
100                *data.iner_mut(Iner::NesreI) += input * 2.0;
101            }
102            // 将一份 <火焰伊纳 I> 刻印为 2 份<火焰伊纳 II>
103            Technique::ExtractII => {
104                let input = data.iner(Iner::NesreI);
105                *data.iner_mut(Iner::NesreI) = 0.0;
106                *data.iner_mut(Iner::NesreII) += input * 2.0;
107            }
108            // 将一份 <火焰伊纳 II> 刻印为 2.4 份<火焰伊纳 III>
109            // 向上取整
110            Technique::ExtractIII => {
111                let input = data.iner(Iner::NesreII);
112                *data.iner_mut(Iner::NesreII) = 0.0;
113                *data.iner_mut(Iner::NesreIII) += input * 2.4;
114            }
115            // 将一份 <火焰伊纳 III> 刻印为 1 份<火焰伊纳 IV>
116            // 每有一个空置操作台,获得5000额外评价分数
117            Technique::ExtractIV => {
118                let input = data.iner(Iner::NesreIII);
119                *data.iner_mut(Iner::NesreIII) = 0.0;
120                *data.iner_mut(Iner::NesreIV) += input;
121                data.extra_score += 5000 * data.empty_boards;
122            }
123            // 将 <草叶伊纳> 全部刻印为 <草叶伊纳 I>
124            // 额外产出 <沙伊纳>
125            Technique::FilterI => {
126                let input = data.iner(Iner::Gabe);
127                *data.iner_mut(Iner::Gabe) = 0.0;
128                *data.iner_mut(Iner::GabeI) += input;
129                *data.iner_mut(Iner::Shay) += input;
130            }
131            // 将 <草叶伊纳 I> 按 4:1 刻印为 <草叶伊纳 II> 和 <沙伊纳>
132            // 额外产出 <沙伊纳>
133            Technique::FilterII => {
134                let input = data.iner(Iner::GabeI);
135                *data.iner_mut(Iner::GabeI) = 0.0;
136                *data.iner_mut(Iner::GabeII) += input * 0.8;
137                // = input + input * 0.2
138                *data.iner_mut(Iner::Shay) += input * 1.2;
139            }
140            // 将 <草叶伊纳 II> 按 7:3 刻印为 <草叶伊纳 III> 和 <沙伊纳>
141            // 额外产出 <沙伊纳>
142            Technique::FilterIII => {
143                let input = data.iner(Iner::GabeII);
144                *data.iner_mut(Iner::GabeII) = 0.0;
145                *data.iner_mut(Iner::GabeIII) += input * 0.7;
146                *data.iner_mut(Iner::Shay) += input * 1.3;
147            }
148            // 将 1 份 <天空伊纳> 和 1 份 <沙伊纳> 刻印为 1 份 <天空伊纳 I>
149            // 本工艺生效前,将已有 <天空伊纳> 和 <沙伊纳> 数量均分
150            // <天空伊纳> 系列宝石单个评价分数增加 5 分
151            Technique::MingleI => {
152                let pet = data.iner(Iner::Pet);
153                let shay = data.iner(Iner::Shay);
154                if pet != 0.0 && shay != 0.0 {
155                    // 数量均分,故最终会完全消耗,不会有剩余部分
156                    let sum = pet + shay;
157                    *data.iner_mut(Iner::Pet) = 0.0;
158                    *data.iner_mut(Iner::Shay) = 0.0;
159                    *data.iner_mut(Iner::PetI) += sum / 2.0;
160                }
161                add_pet_iner_score(data, 5);
162            }
163            // 将 1 份 <天空伊纳 I> 和 1 份 <草叶伊纳 I> 刻印为 1 份 <天空伊纳 II>
164            // 本工艺生效前,将已有 <天空伊纳 I> 和 <草叶伊纳 I> 数量均分
165            // <天空伊纳> 系列宝石单个评价分数增加 15 分
166            Technique::MingleII => {
167                let pet = data.iner(Iner::PetI);
168                let gabe = data.iner(Iner::GabeI);
169                if pet != 0.0 && gabe != 0.0 {
170                    // 数量均分,故最终会完全消耗,不会有剩余部分
171                    let sum = pet + gabe;
172                    *data.iner_mut(Iner::PetI) = 0.0;
173                    *data.iner_mut(Iner::GabeI) = 0.0;
174                    *data.iner_mut(Iner::PetII) += sum / 2.0;
175                }
176                add_pet_iner_score(data, 15)
177            }
178            // 将 1 份 <天空伊纳 II> 和 1 份 <火焰伊纳 III> 刻印为 1 份 <天空伊纳 III>
179            // 本工艺生效前,将已有 <天空伊纳 II> 和 <火焰伊纳 III> 数量均分
180            // 若刻印产出只有1种宝石,额外获得 <天空伊纳 III> 数量 100 倍的评价分数
181            Technique::MingleIII => {
182                let nesre = data.iner(Iner::NesreIII);
183                let pet = data.iner(Iner::PetII);
184                if pet != 0.0 && nesre != 0.0 {
185                    // 数量均分,故最终会完全消耗,不会有剩余部分
186                    let sum = nesre + pet;
187                    *data.iner_mut(Iner::PetII) = 0.0;
188                    *data.iner_mut(Iner::NesreIII) = 0.0;
189                    *data.iner_mut(Iner::PetIII) += sum / 2.0;
190                    data.extra_score += 100 * data.iner(Iner::PetIII).ceil() as usize;
191                }
192            }
193            // 将 1 份 <沙伊纳> 刻印为 5 份 <沙伊纳>
194            Technique::CrystalI => {
195                *data.iner_mut(Iner::Shay) *= 5.0;
196            }
197            // 将 1 份 <沙伊纳> 刻印为 8 份 <沙伊纳>
198            Technique::CrystalII => {
199                *data.iner_mut(Iner::Shay) *= 8.0;
200            }
201            // 将 1 份 <沙伊纳> 刻印为 9 份 <沙伊纳>
202            // <沙伊纳> 单个评价分数增加 1 分
203            Technique::CrystalIII => {
204                *data.iner_mut(Iner::Shay) *= 9.0;
205                *data.iner_score_mut(Iner::Shay) += 1;
206            }
207        }
208    }
209
210    /// 在工艺区的操作
211    /// 只有萃雕 I II III 可以在工艺区生效
212    pub fn operate_in_technique_area(&self, data: &mut GameData) {
213        match self {
214            Technique::ExtractI | Technique::ExtractII | Technique::ExtractIII => {
215                self.operate_on_board(data);
216            }
217            _ => (),
218        }
219    }
220}
221
222#[derive(Debug, Clone)]
223pub struct InerInfo {
224    pub amount: f64,
225    pub score: usize,
226}
227
228impl InerInfo {
229    pub fn new(iner: Iner, amount: f64) -> Self {
230        Self {
231            amount,
232            score: iner.base_score(),
233        }
234    }
235}
236
237#[derive(Debug, Clone)]
238pub struct GameData {
239    pub extra_score: usize,
240    pub iner: HashMap<Iner, InerInfo>,
241    pub empty_boards: usize,
242}
243
244impl GameData {
245    pub fn new(nesre: usize, pet: usize, gabe: usize, shay: usize, empty_boards: usize) -> Self {
246        Self {
247            extra_score: 0,
248            iner: [
249                (Iner::Nesre, nesre),
250                (Iner::Pet, pet),
251                (Iner::Gabe, gabe),
252                (Iner::Shay, shay),
253            ]
254            .into_iter()
255            .map(|(iner, amount)| (iner, InerInfo::new(iner, amount as f64)))
256            .collect(),
257            empty_boards,
258        }
259    }
260
261    pub fn iner_mut(&mut self, iner: Iner) -> &mut f64 {
262        &mut self
263            .iner
264            .entry(iner)
265            .or_insert(InerInfo::new(iner, 0.0))
266            .amount
267    }
268
269    pub fn iner(&self, iner: Iner) -> f64 {
270        if let Some(info) = self.iner.get(&iner) {
271            info.amount
272        } else {
273            0.0
274        }
275    }
276
277    pub fn iner_score_mut(&mut self, iner: Iner) -> &mut usize {
278        &mut self
279            .iner
280            .entry(iner)
281            .or_insert(InerInfo::new(iner, 0.0))
282            .score
283    }
284}
285
286#[derive(Debug, Clone)]
287pub struct Game {
288    // 数据
289    pub data: RefCell<GameData>,
290    // 操作台
291    pub board: [Option<Technique>; 6],
292    // 工艺区
293    pub technique: Vec<Technique>,
294}
295
296impl Game {
297    pub fn run(self) -> (usize, GameData) {
298        for tech in &self.technique {
299            tech.operate_in_technique_area(&mut self.data.borrow_mut());
300        }
301        for tech in &self.board {
302            if let Some(tech) = tech {
303                tech.operate_on_board(&mut self.data.borrow_mut());
304            }
305        }
306        let mut score: f64 = 0.0;
307        for (
308            _,
309            &InerInfo {
310                amount: a,
311                score: s,
312            },
313        ) in self.data.borrow().iner.iter()
314        {
315            score += a.ceil() * (s as f64);
316        }
317        score += self.data.borrow().extra_score as f64;
318        (score as usize, self.data.into_inner())
319    }
320}
321
322pub struct Simulation {
323    pub available_techniques: Vec<Technique>,
324    pub board_size: usize,
325    pub initial_nesre: usize,
326    pub initial_pet: usize,
327    pub initial_gabe: usize,
328    pub initial_shay: usize,
329}
330
331impl Simulation {
332    #[deprecated]
333    pub fn find_best_v1(&self) -> Game {
334        use itertools::*;
335        let tech_amount = self.available_techniques.len();
336        let best_game: Mutex<Option<Game>> = Mutex::new(None);
337        let best_score: Mutex<Option<usize>> = Mutex::new(None);
338        let total_iterations: u128 = (tech_amount as u128).factorial();
339        let iters = Arc::new(RwLock::new(0u128));
340        let iters1 = Arc::clone(&iters);
341        let handle = thread::spawn(move || {
342            let mut old = 0;
343            loop {
344                let iters = iters1.read().unwrap().to_owned();
345                println!(
346                    "[{:.2}%]: {} / {}, {}M iterations per sec",
347                    iters as f64 / total_iterations as f64 * 100.0,
348                    iters,
349                    total_iterations,
350                    (iters - old) as f64 / 1_000_000.0
351                );
352                thread::sleep(Duration::from_secs(1));
353                if iters >= total_iterations {
354                    break;
355                }
356                old = iters;
357            }
358        });
359        self.available_techniques
360            .iter()
361            .permutations(tech_amount)
362            .par_bridge()
363            .for_each(|permutation| {
364                let s = usize::min(tech_amount, self.board_size);
365                for i in 0..=s {
366                    let mut board: [Option<Technique>; 6] = [None; 6];
367                    board.iter_mut().enumerate().for_each(|(k, v)| {
368                        if k < i {
369                            *v = Some(*permutation[k]);
370                        }
371                    });
372                    let technique = permutation[i..].iter().map(|&&t| t).collect_vec();
373                    let data = GameData::new(
374                        self.initial_nesre,
375                        self.initial_pet,
376                        self.initial_gabe,
377                        self.initial_shay,
378                        self.board_size - i,
379                    );
380                    let game = Game {
381                        data: RefCell::new(data),
382                        board,
383                        technique,
384                    };
385                    let rem = game.clone();
386                    let (score, _) = game.run();
387                    if score > best_score.lock().unwrap().unwrap_or(0) {
388                        *best_game.lock().unwrap() = Some(rem);
389                        *best_score.lock().unwrap() = Some(score);
390                    }
391                }
392                *iters.write().unwrap() += s as u128;
393            });
394        handle.join().unwrap();
395        let game = best_game.lock().unwrap().clone();
396        game.unwrap()
397    }
398
399    pub fn find_best_v2(&self) -> Game {
400        let tech_amount = self.available_techniques.len();
401        let best_game: Mutex<Option<Game>> = Mutex::new(None);
402        let best_score: Mutex<Option<usize>> = Mutex::new(None);
403        self.find_best_inner(tech_amount, best_score, &best_game);
404        let game = best_game.lock().unwrap().clone();
405        game.unwrap()
406    }
407
408    pub fn find_best_v2_scilenced(&self) -> Game {
409        let tech_amount = self.available_techniques.len();
410        let best_game: Mutex<Option<Game>> = Mutex::new(None);
411        let best_score: Mutex<Option<usize>> = Mutex::new(None);
412        self.find_best_inner(tech_amount, best_score, &best_game);
413        let game = best_game.lock().unwrap().clone();
414        game.unwrap()
415    }
416
417    fn find_best_inner(
418        &self,
419        tech_amount: usize,
420        best_score: Mutex<Option<usize>>,
421        best_game: &Mutex<Option<Game>>,
422    ) {
423        use itertools::*;
424        self.available_techniques
425            .iter()
426            .permutations(tech_amount)
427            .par_bridge()
428            .progress_count(3628800)
429            .with_style(
430                ProgressStyle::with_template(
431                    "[{elapsed_precise}] [{bar:40}] {pos:>7}/{len:7} {msg}",
432                )
433                .unwrap().progress_chars("=> "),
434            )
435            .for_each(|permutation| {
436                let s = usize::min(tech_amount, self.board_size);
437                for i in 0..=s {
438                    let mut board: [Option<Technique>; 6] = [None; 6];
439                    board.iter_mut().enumerate().for_each(|(k, v)| {
440                        if k < i {
441                            *v = Some(*permutation[k]);
442                        }
443                    });
444                    let mut technique = vec![
445                        Technique::ExtractI,
446                        Technique::ExtractII,
447                        Technique::ExtractIII,
448                    ];
449                    technique.append(&mut permutation[i..].iter().map(|&&t| t).collect_vec());
450                    let data = GameData::new(
451                        self.initial_nesre,
452                        self.initial_pet,
453                        self.initial_gabe,
454                        self.initial_shay,
455                        self.board_size - i,
456                    );
457                    let game = Game {
458                        data: RefCell::new(data),
459                        board,
460                        technique,
461                    };
462                    let rem = game.clone();
463                    let (score, _) = game.run();
464                    if score > best_score.lock().unwrap().unwrap_or(0) {
465                        *best_game.lock().unwrap() = Some(rem);
466                        *best_score.lock().unwrap() = Some(score);
467                    }
468                }
469            });
470    }
471}
472
473pub fn printer(
474    progress: Arc<RwLock<(u128, u128)>>,
475    total_iterations: u128,
476) -> thread::JoinHandle<()> {
477    let handle = thread::spawn(move || {
478        let mut old_iters = 0;
479        loop {
480            let (progress, iters) = progress.read().unwrap().to_owned();
481            println!(
482                "[{:06.2}%]: {:07} of {:07}, {:.2}k iter/s",
483                progress as f64 / total_iterations as f64 * 100.0,
484                progress,
485                total_iterations,
486                (iters - old_iters) as f64 / 1_000.0
487            );
488            old_iters = iters;
489            if progress >= total_iterations {
490                break;
491            }
492            thread::sleep(Duration::from_secs(1));
493        }
494    });
495    handle
496}