1use super::*;
2use rbp_cards::*;
3use rbp_core::*;
4use std::ops::Not;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Partial {
37 pov: Turn,
38 actions: Vec<Action>,
39 reveals: Arrangement,
40}
41
42impl Arbitrary for Partial {
43 fn random() -> Self {
44 Self::initial(Turn::Choice(0))
45 }
46}
47
48impl Partial {
49 pub fn initial(pov: Turn) -> Self {
51 Self {
52 pov,
53 actions: Vec::new(),
54 reveals: Arrangement::from(Street::Pref),
55 }
56 }
57 pub fn with_pov(&self, pov: Turn) -> Self {
59 Self {
60 pov,
61 actions: self.actions.clone(),
62 reveals: self.reveals.clone(),
63 }
64 }
65}
66
67impl Recall for Partial {
68 fn root(&self) -> Game {
69 Game::blinds()
70 .into_iter()
71 .fold(self.base(), |mut g, a| g.consume(a))
72 }
73 fn actions(&self) -> &[Action] {
74 &self.actions
75 }
76}
77
78impl Partial {
80 pub fn betting_edges(&self) -> Vec<Edge> {
82 let game = self.head();
83 self.choices()
84 .into_iter()
85 .filter(|edge| matches!(edge, Edge::Open(_) | Edge::Raise(_)))
86 .filter(|edge| matches!(game.actionize(*edge), Action::Raise(_)))
87 .collect()
88 }
89
90 pub fn histories(&self) -> Vec<(Observation, Perfect)> {
97 self.seen()
98 .opponents()
99 .map(|villain| {
100 let hole = Hole::from(villain.pocket().clone());
101 (villain, Perfect::from((self, hole)))
102 })
103 .collect()
104 }
105}
106
107impl From<(Turn, Arrangement)> for Partial {
109 fn from((pov, reveals): (Turn, Arrangement)) -> Self {
110 let actions = Vec::new();
111 Self {
112 pov,
113 actions,
114 reveals,
115 }
116 }
117}
118
119impl From<Street> for Partial {
121 fn from(_: Street) -> Self {
122 todo!()
123 }
124}
125
126impl From<(Turn, Observation, Vec<Action>)> for Partial {
127 fn from((pov, seen, actions): (Turn, Observation, Vec<Action>)) -> Self {
128 Self::try_build(pov, seen, actions).expect("valid action sequence")
129 }
130}
131
132impl Partial {
133 pub fn try_build(pov: Turn, seen: Observation, actions: Vec<Action>) -> anyhow::Result<Self> {
139 let reveals = Arrangement::from(seen);
140 let initial = Self {
141 pov,
142 actions: Vec::new(),
143 reveals,
144 };
145 actions.into_iter().try_fold(initial, |r, a| r.try_push(a))
146 }
147}
148
149impl Partial {
151 pub fn base(&self) -> Game {
153 Game::default().wipe(Hole::from(self.seen()))
156 }
157 pub fn street(&self) -> Street {
159 self.head().street()
160 }
161 pub fn dealt(&self) -> Street {
163 Street::from(self.actions.iter().filter(|a| a.is_chance()).count() as isize)
164 }
165 pub fn turn(&self) -> Turn {
167 self.pov
168 }
169 pub fn arr(&self) -> Arrangement {
171 self.reveals.clone()
172 }
173 pub fn seen(&self) -> Observation {
175 self.reveals.observation()
176 }
177 pub fn reset(&self) -> Self {
179 Self {
180 pov: self.turn(),
181 reveals: self.reveals.clone(),
182 actions: Vec::new(),
183 }
184 }
185 pub fn cursor(&self) -> petgraph::graph::NodeIndex {
187 petgraph::graph::NodeIndex::new(self.actions().len().saturating_sub(1))
188 }
189 pub fn plays(&self) -> Vec<(Position, Action, Street)> {
191 self.states()
192 .windows(2)
193 .zip(self.actions().iter().cloned())
194 .filter_map(|(pair, action)| {
195 action
196 .is_choice()
197 .then(|| (pair[0].turn().position(), action, pair[0].street()))
198 })
199 .collect()
200 }
201 pub fn aggressor(&self) -> Option<Position> {
204 self.plays()
205 .into_iter()
206 .filter_map(|(pos, action, _)| action.is_aggro().then_some(pos))
207 .last()
208 }
209 pub fn truncate(&self, street: Street) -> Self {
211 let pov = self.turn();
212 let reveals = self.reveals.clone();
213 let actions = self
214 .states()
215 .into_iter()
216 .skip(1)
217 .zip(self.actions().iter().cloned())
218 .map(|(game, action)| (action, game))
219 .collect::<Vec<(Action, Game)>>()
220 .into_iter()
221 .take_while(|(_, game)| game.street() <= street)
222 .map(|(action, _)| action)
223 .collect::<Vec<Action>>();
224 let recall = Self {
225 pov,
226 reveals,
227 actions,
228 };
229 recall.sprout()
230 }
231
232 pub fn replace(&self, reveals: Arrangement) -> Self {
234 let mut actions = self.actions().to_vec();
235 actions
236 .iter_mut()
237 .filter(|a| a.is_chance())
238 .zip(reveals.draws())
239 .for_each(|(old, new)| *old = new);
240 Self {
241 pov: self.turn(),
242 actions,
243 reveals,
244 }
245 }
246
247 pub fn decisions(&self, street: Street) -> Vec<Action> {
249 let mut actions = Vec::new();
250 let mut current = Street::Pref;
251 for action in self.actions().iter().cloned() {
252 if action.is_chance() {
253 current = current.next();
254 } else if current == street {
255 actions.push(action);
256 }
257 }
258 actions
259 }
260
261 pub fn board(&self) -> Vec<Card> {
263 let street = self.head().street();
264 Street::all()
265 .iter()
266 .skip(1)
267 .filter(|s| **s <= street)
268 .cloned()
269 .flat_map(|s| self.revealed(s))
270 .collect()
271 }
272
273 pub fn revealed(&self, street: Street) -> Vec<Card> {
275 self.reveals.revealed(street)
276 }
277 pub fn isomorphism(&self) -> Isomorphism {
279 Isomorphism::from(self.seen())
280 }
281
282 pub fn empty(&self) -> bool {
284 self.actions().is_empty()
285 }
286
287 pub fn aligned(&self) -> bool {
289 self.seen().public().clone()
290 == self
291 .actions()
292 .iter()
293 .filter(|a| a.is_chance())
294 .filter_map(|a| a.hand())
295 .fold(Hand::empty(), Hand::add)
296 }
297}
298
299impl Partial {
301 pub fn undo(&self) -> Self {
303 debug_assert!(self.can_undo());
304 let mut copy = self.clone();
305 copy.actions.pop();
306 copy.recoil()
307 }
308 pub fn push(&self, action: Action) -> Self {
310 self.try_push(action).expect("valid action")
311 }
312 pub fn try_push(&self, action: Action) -> anyhow::Result<Self> {
317 if !self.can_push(&action) {
318 return Err(anyhow::anyhow!(
319 "illegal action {:?} at {:?}",
320 action,
321 self.head().turn()
322 ));
323 }
324 let mut copy = self.clone();
325 copy.actions.push(action);
326 Ok(copy.sprout())
327 }
328}
329
330impl Partial {
332 pub fn validate(self) -> anyhow::Result<Self> {
334 let recall = self.sprout();
335 if !recall.aligned() {
336 return Err(anyhow::anyhow!("recall is not aligned {}", self));
337 }
338 if !recall.can_play() {
339 return Err(anyhow::anyhow!("recall is not playable {}", self));
340 }
341 Ok(recall)
342 }
343}
344
345impl Partial {
347 fn sprout(&self) -> Self {
349 let mut copy = self.clone();
350 while copy.can_deal() {
351 let street = copy.head().street().next();
352 let reveal = copy.revealed(street).into();
353 copy.actions.push(Action::Draw(reveal));
354 }
355 copy
356 }
357
358 fn recoil(&self) -> Self {
360 let mut copy = self.clone();
361 while copy.can_deal() {
362 copy.actions.pop();
363 }
364 copy
365 }
366}
367
368impl Partial {
370 pub fn can_play(&self) -> bool {
372 self.head().turn() == self.turn() && self.head().street() == self.seen().street() }
375
376 pub fn can_push(&self, action: &Action) -> bool {
378 self.head().is_allowed(action)
379 }
380
381 pub fn can_undo(&self) -> bool {
383 !self.actions.is_empty()
384 }
385
386 fn can_deal(&self) -> bool {
388 self.can_know() && self.head().turn() == Turn::Chance
389 }
390
391 fn can_know(&self) -> bool {
393 self.head().street() < self.seen().street()
394 }
395}
396
397impl std::fmt::Display for Partial {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 const L: usize = 4;
403 const R: usize = 44;
404 const A: usize = 8;
405 let hole = self
406 .reveals
407 .pocket()
408 .iter()
409 .map(|c| format!("{}", c))
410 .collect::<Vec<_>>()
411 .join(" ");
412 let board = self
413 .board()
414 .iter()
415 .map(|c| format!("{}", c))
416 .collect::<Vec<_>>()
417 .join(" ");
418 let cards = if board.is_empty() {
419 format!("{}", hole)
420 } else {
421 format!("{} │ {}", hole, board)
422 };
423 writeln!(f, "┌{}┬{}┐", "─".repeat(L), "─".repeat(R))?;
424 writeln!(
425 f,
426 "│ {:>2} │ {:<w$} │",
427 self.turn().label(),
428 cards,
429 w = R - 2
430 )?;
431 writeln!(f, "├{}┼{}┤", "─".repeat(L), "─".repeat(R))?;
432 Street::all()
433 .iter()
434 .filter_map(|street| {
435 let actions = self.decisions(*street);
436 actions.is_empty().not().then_some((street, actions))
437 })
438 .try_for_each(|(street, actions)| {
439 let grid = actions
440 .iter()
441 .map(|a| format!("{:<w$}", a.symbol(), w = A))
442 .collect::<String>();
443 writeln!(f, "│ {:>2} │ {:<w$} │", street.symbol(), grid, w = R - 2)
444 })?;
445 write!(f, "└{}┴{}┘", "─".repeat(L), "─".repeat(R))
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use std::ops::Not;
453
454 #[test]
456 fn initial_invariants() {
457 let r = Partial::initial(Turn::Choice(0));
458 assert!(r.empty());
459 assert!(r.aligned());
460 assert_eq!(r.reset(), r);
461 assert_eq!(r.seen().street(), Street::Pref);
462 assert_eq!(r.head().street(), Street::Pref);
463 assert_eq!(r.actions().len(), 0);
464 }
465
466 #[test]
469 fn reset_idempotent() {
470 let r = Partial::initial(Turn::Choice(0))
471 .push(Action::Call(1))
472 .push(Action::Raise(5))
473 .push(Action::Raise(20))
474 .push(Action::Call(15));
475 assert_eq!(r.reset(), r.reset().reset());
476 }
477
478 #[test]
480 fn push_undo_inverse() {
481 let r = Partial::initial(Turn::Choice(0));
482 let a = r.head().legal().first().cloned().expect("legal");
483 assert_eq!(r.push(a).undo().subgame().length(), r.subgame().length());
484 }
485
486 #[test]
490 fn base_vs_root_vs_head() {
491 let r = Partial::initial(Turn::Choice(0));
492 let base = r.base();
493 let root = r.root();
494 let head = r.head();
495 assert_eq!(base.street(), Street::Pref);
496 assert_eq!(root.street(), Street::Pref);
497 assert_eq!(head.street(), Street::Pref);
498 assert_eq!(base.pot(), 0); assert_eq!(root.pot(), Game::sblind() + Game::bblind()); assert_eq!(head.pot(), Game::sblind() + Game::bblind()); }
502
503 #[test]
506 fn states_reconstruction() {
507 let r = Partial::initial(Turn::Choice(0)).push(Action::Call(1));
508 let states = r.states();
509 assert_eq!(states.len(), r.actions().len() + 1);
510 assert_eq!(states.first(), Some(&r.root()));
511 assert_eq!(states.last(), Some(&r.head()));
512 states
513 .windows(2)
514 .zip(r.actions().iter())
515 .for_each(|(pair, &act)| assert_eq!(pair[1], pair[0].apply(act)));
516 }
517
518 #[test]
520 fn subgame_current_street() {
521 let r = Partial::initial(Turn::Choice(0));
522 assert_eq!(r.subgame().length(), 0);
523 let r = r.push(Action::Call(1));
524 assert_eq!(r.subgame().length(), 1);
525 }
526
527 #[test]
530 fn alignment_check() {
531 let obs = Observation::from(Street::Flop);
532 let act = vec![
533 Action::Call(1), Action::Check,
535 ];
536 assert!(Partial::from((Turn::Choice(0), obs, act)).aligned());
537 assert!(
538 Partial::from((Turn::Choice(0), Arrangement::from(Street::Flop)))
539 .push(Action::Call(1))
540 .push(Action::Check)
541 .aligned()
542 );
543 }
544
545 #[test]
548 fn behindness_observation_ahead() {
549 let behind = Partial {
550 pov: Turn::Choice(0),
551 actions: Vec::new(),
552 reveals: Arrangement::from(Street::Turn),
553 };
554 assert!(behind.seen().street() > behind.head().street()); assert!(behind.aligned().not()); }
557
558 #[test]
560 fn board_by_street() {
561 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Rive)));
562 assert_eq!(r.board().len(), 0);
563 let r = r.push(Action::Call(1)).push(Action::Check);
564 assert_eq!(r.board().len(), 3);
565 let r = r.push(Action::Check).push(Action::Check);
566 assert_eq!(r.board().len(), 4);
567 let r = r.push(Action::Check).push(Action::Check);
568 assert_eq!(r.board().len(), 5);
569 }
570
571 #[test]
574 fn truncate_to_street() {
575 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Flop)))
576 .push(Action::Call(1)) .push(Action::Check) .push(Action::Check) .push(Action::Check); let t = r.truncate(Street::Pref);
581 assert!(r.head().street() == Street::Flop);
583 assert!(t.head().street() == Street::Flop);
584 assert!(t.actions().len() < r.actions().len());
585 }
586
587 #[test]
589 fn decisions_per_street() {
590 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Flop)))
591 .push(Action::Call(1))
592 .push(Action::Check)
593 .push(Action::Check)
594 .push(Action::Check);
595 assert_eq!(r.decisions(Street::Pref).len(), 2);
596 assert_eq!(r.decisions(Street::Flop).len(), 2);
597 assert!(r.decisions(Street::Pref).iter().all(|a| a.is_choice()));
598 assert!(r.decisions(Street::Flop).iter().all(|a| a.is_choice()));
599 }
600
601 #[test]
603 fn playability_all_streets() {
604 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Rive)));
605 assert_eq!(r.head().turn(), Turn::Choice(0));
606 assert_eq!(r.head().street(), Street::Pref);
607 let r = r.push(Action::Call(1)).push(Action::Check);
608 assert_eq!(r.head().street(), Street::Flop);
609 assert_eq!(r.head().turn(), Turn::Choice(1));
610 let r = r.push(Action::Check).push(Action::Check);
611 assert_eq!(r.head().street(), Street::Turn);
612 assert_eq!(r.head().turn(), Turn::Choice(1));
613 let r = r.push(Action::Check).push(Action::Check);
614 assert_eq!(r.head().street(), Street::Rive);
615 assert_eq!(r.head().turn(), Turn::Choice(1));
616 assert!(r.aligned());
617 }
618
619 #[test]
621 fn playability_not_our_turn() {
622 let r =
623 Partial::from((Turn::Choice(0), Arrangement::from(Street::Pref))).push(Action::Call(1));
624 assert_eq!(r.head().turn(), Turn::Choice(1));
625 }
626
627 #[test]
629 fn from_arrangement_empty_actions() {
630 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Pref)));
631 assert_eq!(r.actions().len(), 0);
632 assert_eq!(r.root().pot(), Game::sblind() + Game::bblind());
634 }
635
636 #[test]
638 fn from_tuple_stores_actions() {
639 let obs = Observation::from(Street::Pref);
640 let act = vec![
641 Action::Call(1), ];
643 let r = Partial::from((Turn::Choice(0), obs, act.clone()));
644 assert_eq!(r.actions().len(), act.len());
645 assert_eq!(r.complete().len(), Game::blinds().len() + act.len());
647 }
648
649 #[test]
651 fn replace_swaps_arrangement() {
652 let obs = Observation::from(Street::Flop);
653 let act = vec![
654 Action::Call(1), Action::Check,
656 ];
657 let old = Partial::from((Turn::Choice(0), obs, act));
658 let new = old.replace(Arrangement::from(Street::Flop));
659 assert_ne!(new.seen(), old.seen());
660 assert_eq!(new.turn(), old.turn());
661 }
662
663 #[test]
665 fn revealed_per_street() {
666 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Turn)));
667 assert_eq!(r.revealed(Street::Flop).len(), 3);
668 assert_eq!(r.revealed(Street::Turn).len(), 1);
669 assert_eq!(r.revealed(Street::Rive).len(), 0);
670 }
671
672 #[test]
674 fn empty_means_no_decisions() {
675 assert!(Partial::initial(Turn::Choice(0)).empty());
676 assert!(
677 Partial::initial(Turn::Choice(0))
678 .push(Action::Call(1))
679 .empty()
680 .not()
681 );
682 }
683
684 #[test]
686 fn aggression_counts_trailing() {
687 let obs = Observation::from(Street::Pref);
688 let act = vec![
689 Action::Raise(4), Action::Raise(8),
691 ];
692 let r = Partial::from((Turn::Choice(0), obs, act));
693 assert_eq!(
694 r.aggression(),
695 r.subgame()
696 .into_iter()
697 .rev()
698 .take_while(|e| e.is_choice())
699 .filter(|e| e.is_aggro())
700 .count()
701 );
702 }
703
704 #[test]
706 fn choices_nonempty() {
707 assert!(
708 Partial::from((Turn::Choice(0), Arrangement::from(Street::Pref)))
709 .choices()
710 .length()
711 > 0
712 );
713 }
714
715 #[test]
717 fn can_play_conditions() {
718 let r = Partial::from((Turn::Choice(0), Arrangement::from(Street::Pref)));
719 assert_eq!(r.can_play(), r.turn() == Turn::Choice(0)); let s = r.push(Action::Call(1));
721 assert_eq!(s.can_play(), s.turn() == Turn::Choice(1)); }
723
724 #[test]
726 fn can_undo_conditions() {
727 let r = Partial::initial(Turn::Choice(0));
728 assert!(r.can_undo().not());
729 assert!(r.push(Action::Call(1)).can_undo());
730 }
731
732 #[test]
734 fn can_push_conditions() {
735 let r = Partial::initial(Turn::Choice(0));
736 assert!(r.can_push(&Action::Call(1)));
737 assert!(r.can_push(&Action::Check).not());
738 }
739}