1use std::{cmp::Ordering, convert::TryFrom, fmt::Debug, marker::PhantomData, ops::ControlFlow};
2
3use hivetuilib::{
4 engine::{logging::EventLog, CloneError, Engine, EventListener},
5 GameData, RevEffect,
6};
7
8use crate::{
9 engine_stepper::EngineStepper,
10 rater::{DecisionType, Rater},
11 search_tree_state::SearchTreeState,
12 IndexType, Params, RatingType, Sliding, INTERNAL_ERROR,
13};
14
15type MoveRating<T> = (RatingType, Box<[usize]>, <T as GameData>::Context);
16
17pub trait RateAndMap<T: GameData> {
18 fn apply_type_mapping(&self, context: &T::Context) -> DecisionType;
19
20 fn rate_moves(
21 &self,
22 rater: &mut Rater,
23 context: &[T::Context],
24 data: &T,
25 old_context: &[(T::Context, usize)],
26 );
27
28 fn rate_game_state(
30 &self,
31 data: &T,
32 old_context: &[(T::Context, usize)],
33 player: usize,
34 ) -> RatingType;
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum InvalidEngineState {
42 PendingEffect,
43 FollowUp,
44 Finished,
45}
46
47impl From<CloneError> for InvalidEngineState {
48 fn from(e: CloneError) -> Self {
49 match e {
50 CloneError::PendingEffect => InvalidEngineState::PendingEffect,
51 CloneError::FollowUp => InvalidEngineState::FollowUp,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum MinMaxError {
58 PendingEffect,
59 FollowUp,
60 Finished,
61 Cancelled,
62}
63
64impl MinMaxError {
65 pub fn into_engine_state_error(self) -> Option<InvalidEngineState> {
66 match self {
67 MinMaxError::PendingEffect => Some(InvalidEngineState::PendingEffect),
68 MinMaxError::FollowUp => Some(InvalidEngineState::FollowUp),
69 MinMaxError::Finished => Some(InvalidEngineState::Finished),
70 MinMaxError::Cancelled => None,
71 }
72 }
73}
74
75impl From<CloneError> for MinMaxError {
76 fn from(e: CloneError) -> Self {
77 match e {
78 CloneError::PendingEffect => MinMaxError::PendingEffect,
79 CloneError::FollowUp => MinMaxError::FollowUp,
80 }
81 }
82}
83
84pub fn add_context_to_ratings<T, L>(
85 engine: &Engine<T, L>,
86 ratings: Vec<(RatingType, Box<[usize]>)>,
87) -> Result<Vec<MoveRating<T>>, InvalidEngineState>
88where
89 T: GameData + Clone + Debug,
90 L: EventListener<T>,
91 T::EffectType: RevEffect<T>,
92{
93 if engine.is_finished() {
94 return Err(InvalidEngineState::Finished);
95 }
96 let mut engine = engine.try_clone_with_listener(EventLog::new())?;
97 let mut stepper = EngineStepper::new(&mut engine);
98
99 let result = ratings
100 .into_iter()
101 .map(|(val, path)| {
102 stepper.forward_step(&path);
103 let (ctx, _) = stepper.backward_step();
104 (val, path, ctx)
105 })
106 .collect::<Vec<_>>();
107 Ok(result)
108}
109
110pub struct PruningInput {
111 pub total_depth: usize,
112 pub current_depth: usize,
113 pub num_branches: usize,
114}
115
116pub enum PruningKind {
117 KeepAll,
118 KeepN(usize),
119 KeepByDiff(RatingType),
120 WithinBounds(usize, usize, RatingType),
121}
122
123pub struct MinMaxAlgorithm<T: GameData + Debug, R: RateAndMap<T>>
124where
125 T::EffectType: RevEffect<T>,
126{
127 params: Params,
128 rate_and_map: R,
129 pruning_fn: Box<dyn Fn(PruningInput) -> PruningKind>,
130 _t: PhantomData<T>,
131}
132
133type RatingList = Vec<(RatingType, Box<[IndexType]>)>;
134
135impl<T: GameData + Debug, R: RateAndMap<T>> MinMaxAlgorithm<T, R>
136where
137 T::EffectType: RevEffect<T>,
138{
139 pub fn new(params: Params, rate_and_map: R) -> MinMaxAlgorithm<T, R> {
140 Self::with_pruning(params, rate_and_map, |_| PruningKind::KeepAll)
141 }
142
143 pub fn with_pruning<P>(params: Params, rate_and_map: R, pruning_fn: P) -> MinMaxAlgorithm<T, R>
144 where
145 P: Fn(PruningInput) -> PruningKind + 'static,
146 {
147 MinMaxAlgorithm {
148 params,
149 rate_and_map,
150 pruning_fn: Box::new(pruning_fn),
151 _t: PhantomData,
152 }
153 }
154
155 pub fn rate_and_map(&self) -> &R {
156 &self.rate_and_map
157 }
158
159 pub fn apply<L>(&self, engine: &mut Engine<T, L>)
160 where
161 T: Clone,
162 L: EventListener<T>,
163 {
164 let (_, index_list, _) = self.run(engine).expect("Invalid engine state!");
165 for &i in index_list.iter() {
166 match engine.pull() {
167 hivetuilib::engine::GameState::PendingDecision(dec) => dec.select_option(i),
168 _ => panic!("{}", INTERNAL_ERROR),
169 }
170 }
171 }
172
173 pub fn run<L>(&self, engine: &Engine<T, L>) -> Result<MoveRating<T>, InvalidEngineState>
174 where
175 T: Clone,
176 L: EventListener<T>,
177 {
178 self.run_with_cancellation(engine, || false)
179 .map_err(|e| e.into_engine_state_error().unwrap())
180 }
181
182 pub fn run_with_cancellation<L, F>(
183 &self,
184 engine: &Engine<T, L>,
185 should_cancel: F,
186 ) -> Result<MoveRating<T>, MinMaxError>
187 where
188 T: GameData + Clone,
189 L: EventListener<T>,
190 F: Fn() -> bool,
191 {
192 let ratings = self.run_all_ratings_with_cancellation(engine, should_cancel)?;
193 let result = ratings
194 .into_iter()
195 .max_by_key(|&(r, _, _)| r)
196 .expect(INTERNAL_ERROR);
197
198 Ok(result)
200 }
201
202 pub fn run_all_ratings<L>(
203 &self,
204 engine: &Engine<T, L>,
205 ) -> Result<Vec<MoveRating<T>>, InvalidEngineState>
206 where
207 T: GameData + Clone,
208 L: EventListener<T>,
209 {
210 self.run_all_ratings_with_cancellation(engine, || false)
211 .map_err(|e| e.into_engine_state_error().unwrap())
212 }
213
214 pub fn run_all_ratings_with_cancellation<L, F>(
215 &self,
216 engine: &Engine<T, L>,
217 should_cancel: F,
218 ) -> Result<Vec<MoveRating<T>>, MinMaxError>
219 where
220 T: GameData + Clone,
221 L: EventListener<T>,
222 F: Fn() -> bool,
223 {
224 if engine.is_finished() {
225 return Err(MinMaxError::Finished);
226 }
227 let mut engine = engine.try_clone_with_listener(EventLog::new())?;
229
230 self.params.integrity_check();
231 let mut stepper = EngineStepper::new(&mut engine);
232 let player = stepper.player();
233
234 let mut tree = SearchTreeState::new();
236 let num_runs = self
237 .params
238 .depth
239 .saturating_sub(self.params.first_cut_delay_depth)
240 + 1;
241 for _ in 0..num_runs {
242 self.prune(&mut tree);
243 self.extend_search_tree(&mut stepper, &mut tree, player, &should_cancel)?;
244 if tree.root_moves().count() == 1 {
245 break;
246 }
247 }
248 Ok(tree
249 .root_moves()
250 .map(|(val, path)| {
251 (
252 val,
253 path.iter().map(|&i| usize::try_from(i).unwrap()).collect(),
254 {
255 stepper.forward_step(path);
256 let (ctx, _) = stepper.backward_step();
257 ctx
258 },
259 )
260 })
261 .collect::<Vec<_>>())
262 }
263
264 fn extend_search_tree<F: Fn() -> bool>(
265 &self,
266 stepper: &mut EngineStepper<T>,
267 tree: &mut SearchTreeState,
268 player: usize,
269 should_cancel: F,
270 ) -> Result<(), MinMaxError> {
271 assert!(tree.depth() < 2 * self.params.depth);
272 let depth = {
273 let mut depth = self.params.first_cut_delay_depth;
274 if tree.depth() == 0 {
275 depth += self.params.first_move_added_delay_depth;
276 }
277 2 * usize::min(depth, self.params.depth)
278 };
279 let sliding = self.params.sliding.get(tree.depth()..);
280 tree.new_levels();
281 tree.for_each_leaf(stepper, |tree, stepper, t_index| {
282 assert!(
283 stepper.is_finished() || stepper.player() == player,
284 "Min-max algorithm requires alternating turns!"
285 );
286 let new_moves = self.collect_recursive(
287 depth,
288 stepper,
289 player,
290 self.params.first_cut_delay_depth,
291 sliding,
292 );
293 for (rating, index, children) in new_moves {
294 tree.push_child(t_index, rating, index, children);
295 }
296 if should_cancel() {
297 ControlFlow::Break(MinMaxError::Cancelled)
298 } else {
299 ControlFlow::Continue(())
300 }
301 })?;
302 tree.extend();
304 tree.update_ratings();
305 Ok(())
306 }
307
308 fn collect_recursive(
309 &self,
310 depth: usize,
311 stepper: &mut EngineStepper<T>,
312 player: usize,
313 delay_depth: usize,
314 sliding: Sliding,
315 ) -> Vec<(RatingType, Box<[IndexType]>, RatingList)> {
316 if depth == 0 || stepper.is_finished() {
317 return Vec::new();
318 }
319
320 let is_own_turn = stepper.player() == player;
322 let moves = self.create_move_ratings(
323 stepper,
324 sliding.move_cut_difference(),
325 sliding.move_limit(),
326 Rater::cut_and_sort_with_equivalency,
327 );
328 let mut moves = moves
329 .into_iter()
330 .map(|(_, indizes, eq)| {
331 stepper.forward_step(&indizes);
332 let (rating, children) =
333 self.collect_and_cut(depth - 1, stepper, player, delay_depth, sliding.next());
334 stepper.backward_step();
335 (rating, indizes, eq, children)
336 })
337 .collect::<Vec<_>>();
338
339 if depth >= 2 * delay_depth {
341 self.cut_moves(&mut moves, sliding, |&(r, _, _, _)| r, is_own_turn);
342 }
343
344 let mut moves = self.resolve_equivalencies(
346 moves,
347 stepper,
348 self.params.equivalency_penalty,
349 sliding,
350 |x| x,
351 |stepper| self.collect_and_cut(depth - 1, stepper, player, delay_depth, sliding.next()),
352 is_own_turn,
353 );
354
355 if depth >= 2 * delay_depth && moves.len() > sliding.branch_cut_limit() {
357 self.cut_moves(&mut moves, sliding, |&(r, _, _)| r, is_own_turn);
358 }
359 moves
360 }
361
362 fn collect_and_cut(
363 &self,
364 depth: usize,
365 stepper: &mut EngineStepper<T>,
366 player: usize,
367 delay_depth: usize,
368 sliding: Sliding,
369 ) -> (RatingType, RatingList) {
370 if depth == 0 || stepper.is_finished() {
371 return (self.final_rating(depth, stepper, player), Vec::new());
372 }
373
374 let is_own_turn = stepper.player() == player;
376 let mut moves = self.create_move_ratings(
377 stepper,
378 sliding.move_cut_difference(),
379 sliding.move_limit(),
380 Rater::cut_and_sort_with_equivalency,
381 );
382 for (rating, indizes, _) in moves.iter_mut() {
383 stepper.forward_step(indizes);
384 *rating = self.min_max(depth - 1, stepper, player, sliding.next());
385 stepper.backward_step();
386 }
387
388 if depth >= (2 * delay_depth - 1) {
390 self.cut_moves(&mut moves, sliding, |&(r, _, _)| r, is_own_turn);
391 }
392
393 let mut moves = self
395 .resolve_equivalencies(
396 moves,
397 stepper,
398 self.params.equivalency_penalty,
399 sliding,
400 |(rating, index, equivalent_moves)| (rating, index, equivalent_moves, ()),
401 |stepper| (self.min_max(depth - 1, stepper, player, sliding.next()), ()),
402 is_own_turn,
403 )
404 .into_iter()
405 .map(|(rating, idz, _)| (rating, idz))
406 .collect::<Vec<_>>();
407
408 if depth >= (2 * delay_depth - 1) && moves.len() > sliding.branch_cut_limit() {
410 self.cut_moves(&mut moves, sliding, |&(r, _)| r, is_own_turn);
411 }
412
413 let min = moves
415 .iter()
416 .min_by(|&&(r1, _), &&(r2, _)| Self::compare(r1, r2, is_own_turn));
417 (min.unwrap().0, moves)
418 }
419
420 fn min_max(
421 &self,
422 depth: usize,
423 stepper: &mut EngineStepper<T>,
424 player: usize,
425 sliding: Sliding,
426 ) -> RatingType {
427 if depth == 0 || stepper.is_finished() {
428 return self.final_rating(depth, stepper, player);
429 }
430
431 let is_own_turn = stepper.player() == player;
432 let moves = self.create_move_ratings(
433 stepper,
434 sliding.move_cut_difference(),
435 sliding.move_limit(),
436 Rater::cut_and_sort,
437 );
438 let ratings = moves.into_iter().map(|(_, indizes)| {
439 stepper.forward_step(&indizes);
440 let result = self.min_max(depth - 1, stepper, player, sliding.next());
441 stepper.backward_step();
442 result
443 });
444 if is_own_turn {
445 ratings.max().expect(INTERNAL_ERROR)
446 } else {
447 ratings.min().expect(INTERNAL_ERROR)
448 }
449 }
450
451 fn final_rating(
452 &self,
453 depth: usize,
454 stepper: &mut EngineStepper<T>,
455 player: usize,
456 ) -> RatingType {
457 let val =
458 self.rate_and_map
459 .rate_game_state(stepper.data(), stepper.decision_context(), player);
460 if stepper.is_finished() {
461 RatingType::try_from(depth + 1).unwrap() * val
462 } else {
463 val
464 }
465 }
466
467 fn cut_moves<E, F>(
468 &self,
469 moves: &mut Vec<E>,
470 sliding: Sliding,
471 rating_key_fn: F,
472 is_own_turn: bool,
473 ) where
474 F: Fn(&E) -> RatingType,
475 {
476 moves.sort_unstable_by(|l, r| {
477 Self::compare(rating_key_fn(l), rating_key_fn(r), is_own_turn)
478 });
479 let min = rating_key_fn(moves.first().unwrap());
480 moves.retain(|entry| {
481 RatingType::abs(rating_key_fn(entry) - min) <= sliding.branch_cut_difference()
482 });
483 moves.truncate(sliding.branch_cut_limit());
485 }
486
487 fn resolve_equivalencies<E, O, FnR, FnC>(
488 &self,
489 moves: Vec<E>,
490 stepper: &mut EngineStepper<T>,
491 equiv_penalty: RatingType,
492 sliding: Sliding,
493 resolve_el: FnR,
494 recursive_call: FnC,
495 is_own_turn: bool,
496 ) -> Vec<(RatingType, Box<[IndexType]>, O)>
497 where
498 FnR: Fn(E) -> (RatingType, Box<[IndexType]>, Vec<Box<[IndexType]>>, O),
499 FnC: Fn(&mut EngineStepper<T>) -> (RatingType, O),
500 {
501 let choices = sliding.equivalency_class_choices();
502 let mut buffer = Vec::new();
503 let mut result = Vec::new();
504 for element in moves {
505 buffer.clear();
506 let (rating, indizes, equivalent_moves, other) = resolve_el(element);
507 buffer.push((rating, indizes, other));
508 for curr_idz in equivalent_moves
509 .into_iter()
510 .take(sliding.equivalency_class_limit())
511 {
512 stepper.forward_step(&curr_idz);
513 let (curr_rating, curr_other) = recursive_call(stepper);
514 buffer.push((curr_rating, curr_idz, curr_other));
515 stepper.backward_step();
516 }
517
518 buffer.sort_unstable_by(|&(l, _, _), &(r, _, _)| Self::compare(l, r, is_own_turn));
520 for (i, (rating, indizes, other)) in buffer.drain(..).enumerate().take(choices) {
521 let sign = if is_own_turn { -1 } else { 1 };
522 result.push((
523 rating + sign * i as RatingType * equiv_penalty,
524 indizes,
525 other,
526 ))
527 }
528 }
529 result
530 }
531
532 fn compare(l: i32, r: i32, is_own_turn: bool) -> Ordering {
533 if is_own_turn {
534 r.cmp(&l)
535 } else {
536 l.cmp(&r)
537 }
538 }
539
540 fn prune(&self, tree: &mut SearchTreeState) {
541 let depth = tree.depth();
542 let mut buffer = Vec::new();
543 tree.prune(|level, moves, mut retained| {
544 let sign = if level % 2 == 0 { 1 } else { -1 };
545 let mut within_bounds = |min, max, diff| {
546 assert!(diff >= 0);
547 buffer.clear();
548 buffer.extend(moves.iter().enumerate().map(|(i, t)| (i, sign * t.rating)));
549 buffer.sort_unstable_by_key(|&(_, r)| -r); assert!(buffer.len() > 1 && buffer[0].1 >= buffer[1].1);
551 let (_, best) = buffer[0];
552 let num_retained = {
553 let mut index = min;
554 while index < buffer.len() && index < max {
555 let (_, r) = buffer[index];
556 if r + diff < best {
557 break;
558 }
559 index += 1;
560 }
561 index
562 };
563 buffer.truncate(num_retained);
564 buffer.sort_unstable_by_key(|&(i, _)| i);
565 for &(i, _) in buffer.iter() {
566 retained.add(i);
567 }
568 };
569 assert!(level < depth);
570 match (self.pruning_fn)(PruningInput {
571 total_depth: depth,
572 current_depth: level,
573 num_branches: moves.len(),
574 }) {
575 PruningKind::KeepAll => (0..moves.len()).for_each(|i| retained.add(i)),
576 PruningKind::KeepN(num) => within_bounds(num, num, 0),
577 PruningKind::KeepByDiff(diff) => within_bounds(0, moves.len(), diff),
578 PruningKind::WithinBounds(min, max, diff) => within_bounds(min, max, diff),
579 }
580 });
581 }
582
583 #[inline]
584 fn create_move_ratings<E: Ord + Debug>(
585 &self,
586 stepper: &mut EngineStepper<T>,
587 move_cut_difference: RatingType,
588 move_limit: usize,
589 rater_fn: fn(Rater, RatingType) -> Vec<E>,
590 ) -> Vec<E> {
591 let (mut rater, context) = Rater::new(stepper.engine(), |context| {
592 self.rate_and_map.apply_type_mapping(context)
593 });
594 self.rate_and_map.rate_moves(
595 &mut rater,
596 &context,
597 stepper.data(),
598 stepper.decision_context(),
599 );
600 let min = rater.current_max() - move_cut_difference;
601 let mut result = rater_fn(rater, min);
602 assert!(!result.is_empty());
603 if result.len() > move_limit {
604 result.truncate(move_limit);
606 }
607 result
608 }
609}
610
611#[cfg(test)]
612mod test {
613 use hivetuilib::engine::{Engine, GameState};
614
615 use crate::{
616 engine_stepper::EngineStepper,
617 test::{RateAndMapZeroOne, ZeroOneContext, ZeroOneGame},
618 IndexType, MinMaxAlgorithm, Params, SlidingParams,
619 };
620
621 fn indizes(input: &[IndexType]) -> Box<[IndexType]> {
622 Box::from(input)
623 }
624
625 #[test]
626 fn min_max_test() {
627 let sliding = SlidingParams::with_defaults(4, 2, 4, 4, 2, 2, 4, 1);
628 let params = Params::new(4, sliding.clone(), 1);
629 let alg = MinMaxAlgorithm::new(params, RateAndMapZeroOne);
630 let data = ZeroOneGame::new(false, 6);
631 let mut engine = Engine::new_logging(2, data);
632 let mut stepper = EngineStepper::new(&mut engine);
633
634 assert_eq!(alg.min_max(0, &mut stepper, 0, sliding.get(1..)), 0);
635 assert_eq!(alg.min_max(0, &mut stepper, 1, sliding.get(1..)), 0);
636 assert_eq!(alg.min_max(1, &mut stepper, 0, sliding.get(1..)), 1);
637 assert_eq!(alg.min_max(1, &mut stepper, 1, sliding.get(1..)), -1);
638 assert_eq!(alg.min_max(2, &mut stepper, 0, sliding.get(1..)), -1);
639 assert_eq!(alg.min_max(2, &mut stepper, 1, sliding.get(1..)), 1);
640 assert_eq!(alg.min_max(3, &mut stepper, 0, sliding.get(1..)), 0);
641 assert_eq!(alg.min_max(3, &mut stepper, 1, sliding.get(1..)), 0);
642 assert_eq!(alg.min_max(4, &mut stepper, 0, sliding.get(1..)), -4);
643 assert_eq!(alg.min_max(4, &mut stepper, 1, sliding.get(1..)), 4);
644 assert_eq!(alg.min_max(6, &mut stepper, 0, sliding.get(1..)), -12);
645 assert_eq!(alg.min_max(6, &mut stepper, 1, sliding.get(1..)), 12);
646 }
647
648 #[test]
649 fn collect_and_cut_test() {
650 let sliding = SlidingParams::with_defaults(4, 2, 4, 4, 2, 2, 4, 1);
651 let params = Params::new(4, sliding.clone(), 1);
652 let alg = MinMaxAlgorithm::new(params, RateAndMapZeroOne);
653 let data = ZeroOneGame::new(false, 6);
654 let mut engine = Engine::new_logging(2, data);
655 let mut stepper = EngineStepper::new(&mut engine);
656
657 assert_eq!(
658 alg.collect_and_cut(0, &mut stepper, 0, 2, sliding.get(1..)),
659 (0, Vec::new())
660 );
661 assert_eq!(
662 alg.collect_and_cut(0, &mut stepper, 1, 2, sliding.get(1..)),
663 (0, Vec::new())
664 );
665 assert_eq!(
666 alg.collect_and_cut(1, &mut stepper, 0, 2, sliding.get(1..)),
667 (1, vec![(1, indizes(&[0])), (-1, indizes(&[1]))])
668 );
669 assert_eq!(
670 alg.collect_and_cut(1, &mut stepper, 1, 2, sliding.get(1..)),
671 (-1, vec![(-1, indizes(&[0])), (1, indizes(&[1]))])
672 );
673 assert_eq!(
674 alg.collect_and_cut(2, &mut stepper, 0, 2, sliding.get(1..)),
675 (-1, vec![(-1, indizes(&[0])), (-9, indizes(&[1]))])
676 );
677 assert_eq!(
678 alg.collect_and_cut(2, &mut stepper, 1, 2, sliding.get(1..)),
679 (1, vec![(1, indizes(&[0])), (9, indizes(&[1]))])
680 );
681 assert_eq!(
682 alg.collect_and_cut(2, &mut stepper, 0, 1, sliding.get(1..)),
683 (-1, vec![(-1, indizes(&[0]))])
684 );
685 assert_eq!(
686 alg.collect_and_cut(2, &mut stepper, 1, 1, sliding.get(1..)),
687 (1, vec![(1, indizes(&[0]))])
688 );
689 }
690
691 #[test]
692 fn collect_recursive_test() {
693 let sliding = SlidingParams::with_defaults(4, 2, 4, 4, 2, 2, 4, 1);
694 let params = Params::new(4, sliding.clone(), 1);
695 let alg = MinMaxAlgorithm::new(params, RateAndMapZeroOne);
696 let data = ZeroOneGame::new(false, 6);
697 let mut engine = Engine::new_logging(2, data);
698 let mut stepper = EngineStepper::new(&mut engine);
699
700 assert_eq!(
701 alg.collect_recursive(0, &mut stepper, 0, 2, sliding.get(1..)),
702 Vec::new()
703 );
704 assert_eq!(
705 alg.collect_recursive(0, &mut stepper, 1, 2, sliding.get(1..)),
706 Vec::new()
707 );
708 assert_eq!(
709 alg.collect_recursive(1, &mut stepper, 0, 2, sliding.get(1..)),
710 vec![
711 (1, indizes(&[0]), Vec::new()),
712 (-1, indizes(&[1]), Vec::new())
713 ]
714 );
715 assert_eq!(
716 alg.collect_recursive(1, &mut stepper, 1, 2, sliding.get(1..)),
717 vec![
718 (-1, indizes(&[0]), Vec::new()),
719 (1, indizes(&[1]), Vec::new())
720 ]
721 );
722 assert_eq!(
723 alg.collect_recursive(2, &mut stepper, 0, 2, sliding.get(1..)),
724 vec![
725 (
726 -1,
727 indizes(&[0]),
728 vec![
729 (-1, indizes(&[1, 1])),
730 (1, indizes(&[0, 1])),
731 (1, indizes(&[1, 0]))
732 ]
733 ),
734 (
735 -9,
736 indizes(&[1]),
737 vec![
738 (-9, indizes(&[1, 1])),
739 (-1, indizes(&[0, 1])),
740 (-1, indizes(&[1, 0]))
741 ]
742 )
743 ]
744 );
745 assert_eq!(
746 alg.collect_recursive(2, &mut stepper, 1, 2, sliding.get(1..)),
747 vec![
748 (
749 1,
750 indizes(&[0]),
751 vec![
752 (1, indizes(&[1, 1])),
753 (-1, indizes(&[0, 1])),
754 (-1, indizes(&[1, 0]))
755 ]
756 ),
757 (
758 9,
759 indizes(&[1]),
760 vec![
761 (9, indizes(&[1, 1])),
762 (1, indizes(&[0, 1])),
763 (1, indizes(&[1, 0]))
764 ]
765 )
766 ]
767 );
768 assert_eq!(
769 alg.collect_recursive(2, &mut stepper, 0, 1, sliding.get(1..)),
770 vec![(
771 -1,
772 indizes(&[0]),
773 vec![
774 (-1, indizes(&[1, 1])),
775 (1, indizes(&[0, 1])),
776 (1, indizes(&[1, 0]))
777 ]
778 )]
779 );
780 assert_eq!(
781 alg.collect_recursive(2, &mut stepper, 1, 1, sliding.get(1..)),
782 vec![(
783 1,
784 indizes(&[0]),
785 vec![
786 (1, indizes(&[1, 1])),
787 (-1, indizes(&[0, 1])),
788 (-1, indizes(&[1, 0]))
789 ]
790 )]
791 );
792 }
793
794 #[test]
795 fn run_test() {
796 let sliding = SlidingParams::with_defaults(1, 2, 4, 4, 2, 2, 4, 1);
797 let params = Params::new(1, sliding.clone(), 1);
798 let mut alg = MinMaxAlgorithm::new(params, RateAndMapZeroOne);
799 alg.params.first_cut_delay_depth = 1;
800 let data = ZeroOneGame::new(true, 8);
801 let mut engine = Engine::new_logging(2, data);
802 assert_eq!(
803 alg.run(&engine),
804 Ok((1, Box::from([0, 0]), ZeroOneContext::ZeroAnd))
805 );
806
807 let sliding = SlidingParams::with_defaults(2, 1, 4, 4, 4, 2, 4, 1);
808 let params = Params::new(2, sliding.clone(), 1);
809 let mut alg = MinMaxAlgorithm::new(params, RateAndMapZeroOne);
810 alg.params.first_cut_delay_depth = 1;
811 assert_eq!(
812 alg.run(&engine),
813 Ok((4, Box::from([0, 0]), ZeroOneContext::ZeroAnd))
814 );
815
816 match engine.pull() {
817 GameState::PendingDecision(dec) => dec.select_option(0),
818 _ => unreachable!(),
819 }
820 match engine.pull() {
821 GameState::PendingDecision(dec) => dec.select_option(1),
822 _ => unreachable!(),
823 }
824 match engine.pull() {
825 GameState::PendingEffect(eff) => eff.all_effects(),
826 _ => unreachable!(),
827 }
828 assert_eq!(
829 alg.run(&engine),
830 Ok((-4, Box::from([1]), ZeroOneContext::Flat))
831 );
832 }
833}