Skip to main content

manabrew_engine/player/
player_controller.rs

1use std::collections::BTreeSet;
2
3use crate::agent::game_log::GameLogEvent;
4use crate::agent::types::{
5    BinaryChoiceKind, CombatCostAction, GameEntity, ManaAbilityOption, ManaCostAction, PlayOption,
6    RollSwapChoice, TargetChoice,
7};
8use crate::agent::PlayerAgent;
9use crate::combat::DefenderId;
10use crate::cost::{payment_decision::PaymentDecision, CostPart};
11use crate::game::GameState;
12use crate::ids::{CardId, PlayerId};
13use crate::mana::ManaPool;
14use crate::player::actions::PlayerAction;
15use crate::player::player_factory_util::build_priority_actions;
16use crate::player::DelayedReveal;
17use crate::spellability::SpellAbility;
18use forge_foundation::{ManaCost, ZoneType};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
21pub enum FullControlFlag {
22    ChooseCostOrder,
23    ChooseCostReductionOrderAndVariableAmount,
24    NoPaymentFromManaAbility,
25    NoFreeCombatCostHandling,
26    AllowPaymentStartWithMissingResources,
27    LayerTimestampOrder,
28}
29
30pub struct PlayerController<'a, A: PlayerAgent + ?Sized> {
31    pub game: &'a GameState,
32    pub player: PlayerId,
33    pub agent: &'a mut A,
34    pub full_controls: BTreeSet<FullControlFlag>,
35}
36
37impl<'a, A: PlayerAgent + ?Sized> PlayerController<'a, A> {
38    pub fn new(game: &'a GameState, player: PlayerId, agent: &'a mut A) -> Self {
39        Self {
40            game,
41            player,
42            agent,
43            full_controls: BTreeSet::new(),
44        }
45    }
46
47    pub fn snapshot_state(&mut self, mana_pools: &[ManaPool]) {
48        self.agent.snapshot_state(self.game, mana_pools);
49    }
50
51    pub fn available_priority_actions(
52        &self,
53        playable: &[PlayOption],
54        tappable_lands: &[CardId],
55        untappable_lands: &[CardId],
56        activatable: &[(CardId, usize)],
57    ) -> Vec<PlayerAction> {
58        build_priority_actions(playable, tappable_lands, untappable_lands, activatable)
59    }
60
61    pub fn add_full_control(&mut self, flag: FullControlFlag) {
62        self.full_controls.insert(flag);
63    }
64
65    pub fn remove_full_control(&mut self, flag: FullControlFlag) {
66        self.full_controls.remove(&flag);
67    }
68
69    pub fn has_full_control(&self, flag: FullControlFlag) -> bool {
70        self.full_controls.contains(&flag)
71    }
72
73    pub fn notify(&mut self, event: crate::agent::notification::GameNotification) {
74        self.agent.notify(event);
75    }
76
77    pub fn reveal_cards(
78        &mut self,
79        cards: &[CardId],
80        zone: ZoneType,
81        owner: PlayerId,
82        message_prefix: Option<&str>,
83    ) {
84        self.agent
85            .reveal_cards(self.game, self.player, cards, zone, owner, message_prefix);
86        let mut message = String::new();
87        if let Some(prefix) = message_prefix {
88            message.push_str(prefix);
89            message.push(' ');
90        }
91        message.push_str("Reveal ");
92        message.push_str(&format!("{zone:?} cards"));
93        self.agent
94            .notify(crate::agent::notification::GameNotification::Event(
95                GameLogEvent::rule(message)
96                    .with_player(owner)
97                    .with_card(*cards.first().unwrap_or(&CardId(0))),
98            ));
99    }
100
101    pub fn temp_show_cards(&mut self, cards: &[CardId]) {
102        self.reveal_cards(cards, ZoneType::Hand, self.player, Some("Show"));
103    }
104
105    pub fn end_temp_show_cards(&mut self) {}
106
107    pub fn reveal_delayed(&mut self, delayed: &DelayedReveal) {
108        let owner = delayed.owner.unwrap_or(self.player);
109        for &zone in &delayed.zone {
110            self.reveal_cards(
111                &delayed.cards,
112                zone,
113                owner,
114                delayed.message_prefix.as_deref(),
115            );
116        }
117    }
118
119    pub fn choose_attackers(
120        &mut self,
121        available: &[CardId],
122        possible_defenders: &[DefenderId],
123    ) -> Vec<(CardId, DefenderId)> {
124        self.agent
125            .choose_attackers(self.player, available, possible_defenders)
126    }
127
128    pub fn choose_blockers(
129        &mut self,
130        attackers: &[CardId],
131        available_blockers: &[CardId],
132        max_blockers: Option<usize>,
133    ) -> Vec<(CardId, CardId)> {
134        self.agent
135            .choose_blockers(self.player, attackers, available_blockers, max_blockers)
136    }
137
138    pub fn choose_blocker_for(&mut self, attackers: &[CardId], blocker: CardId) -> Option<CardId> {
139        self.agent
140            .choose_blocker_for(self.player, attackers, blocker)
141    }
142
143    pub fn exert_attackers(&mut self, attackers: &[CardId]) -> Vec<CardId> {
144        self.agent.exert_attackers(self.player, attackers)
145    }
146
147    pub fn enlist_attackers(&mut self, attackers: &[CardId]) -> Vec<CardId> {
148        self.agent.enlist_attackers(self.player, attackers)
149    }
150
151    pub fn choose_damage_assignment_order(
152        &mut self,
153        attacker: CardId,
154        blockers: &[CardId],
155    ) -> Vec<CardId> {
156        self.agent
157            .choose_damage_assignment_order(self.player, attacker, blockers)
158    }
159
160    pub fn assign_combat_damage(
161        &mut self,
162        attacker: CardId,
163        blockers_in_order: &[CardId],
164        defender: Option<DefenderId>,
165        damage_to_assign: i32,
166    ) -> Vec<(Option<CardId>, i32)> {
167        self.agent.assign_combat_damage(
168            self.game,
169            self.player,
170            attacker,
171            blockers_in_order,
172            defender,
173            damage_to_assign,
174        )
175    }
176
177    pub fn choose_target_player(
178        &mut self,
179        valid: &[PlayerId],
180        sa: Option<&SpellAbility>,
181    ) -> Option<PlayerId> {
182        self.agent.choose_target_player(self.player, valid, sa)
183    }
184
185    pub fn choose_target_card(
186        &mut self,
187        valid: &[CardId],
188        sa: Option<&SpellAbility>,
189    ) -> Option<CardId> {
190        self.agent.choose_target_card(self.player, valid, sa)
191    }
192
193    pub fn choose_target_any(
194        &mut self,
195        valid_players: &[PlayerId],
196        valid_cards: &[CardId],
197        sa: Option<&SpellAbility>,
198    ) -> TargetChoice {
199        self.agent
200            .choose_target_any(self.player, valid_players, valid_cards, sa)
201    }
202
203    pub fn choose_entities_for_effect(
204        &mut self,
205        candidates: &[GameEntity],
206        min: usize,
207        max: usize,
208    ) -> Vec<GameEntity> {
209        self.agent
210            .choose_entities_for_effect(self.player, candidates, min, max)
211    }
212
213    pub fn choose_single_entity_for_effect(
214        &mut self,
215        candidates: &[GameEntity],
216    ) -> Option<GameEntity> {
217        // Route through the trait's single-entity method so the deterministic
218        // parity agent emits `choose_single_entity_for_effect` (with `pick_one[N]`)
219        // rather than the multi-entity `choose_entities_for_effect`
220        // (`pick_count` + `pick_index` + `pick_many_unique`). Both pick the same
221        // candidate for size-1 lists, but the callback log lines and RNG
222        // consumption differ — the single-entity variant matches Java's
223        // `chooseSingleEntityForEffect` behaviour.
224        self.agent
225            .choose_single_entity_for_effect(self.player, candidates, false)
226    }
227
228    pub fn choose_cards_for_effect(
229        &mut self,
230        valid: &[CardId],
231        min: usize,
232        max: usize,
233    ) -> Vec<CardId> {
234        self.agent
235            .choose_cards_for_effect(self.player, valid, min, max)
236    }
237
238    pub fn choose_cards_for_zone_change(
239        &mut self,
240        valid: &[CardId],
241        min: usize,
242        max: usize,
243        select_prompt: &str,
244    ) -> Vec<CardId> {
245        self.agent.choose_cards_for_zone_change(
246            self.game,
247            self.player,
248            valid,
249            min,
250            max,
251            select_prompt,
252        )
253    }
254
255    pub fn choose_single_card_for_zone_change(
256        &mut self,
257        valid: &[CardId],
258        select_prompt: &str,
259        is_optional: bool,
260    ) -> Option<CardId> {
261        self.agent.choose_single_card_for_zone_change(
262            self.game,
263            self.player,
264            valid,
265            select_prompt,
266            is_optional,
267        )
268    }
269
270    pub fn choose_type(&mut self, type_category: &str, valid_types: &[String]) -> Option<String> {
271        self.agent
272            .choose_type(self.player, type_category, valid_types)
273    }
274
275    pub fn choose_some_type(
276        &mut self,
277        type_category: &str,
278        valid_types: &[String],
279    ) -> Option<String> {
280        self.choose_type(type_category, valid_types)
281    }
282
283    pub fn choose_number_from_list(
284        &mut self,
285        choices: &[i32],
286        message: &str,
287        source_card_id: Option<CardId>,
288    ) -> Option<i32> {
289        self.agent
290            .choose_number_from_list(self.player, choices, message, source_card_id)
291    }
292
293    pub fn choose_binary(
294        &mut self,
295        question: &str,
296        kind: BinaryChoiceKind,
297        default_choice: Option<bool>,
298        source: Option<CardId>,
299        api: Option<crate::ability::api_type::ApiType>,
300    ) -> bool {
301        self.agent
302            .choose_binary(self.player, question, kind, default_choice, source, api)
303    }
304
305    pub fn confirm_action(
306        &mut self,
307        mode: Option<&str>,
308        message: &str,
309        options: &[String],
310        source: Option<CardId>,
311        api: Option<crate::ability::api_type::ApiType>,
312    ) -> bool {
313        self.agent
314            .confirm_action(self.player, mode, message, options, source, api)
315    }
316
317    pub fn confirm_payment(
318        &mut self,
319        cost_kind: &str,
320        message: &str,
321        source: Option<CardId>,
322        api: Option<crate::ability::api_type::ApiType>,
323    ) -> bool {
324        self.agent
325            .confirm_payment(self.player, cost_kind, message, source, api)
326    }
327
328    pub fn pay_cost_to_prevent_effect(
329        &mut self,
330        cost_kind: &str,
331        message: &str,
332        source: Option<CardId>,
333        api: Option<crate::ability::api_type::ApiType>,
334        can_pay: bool,
335        targets: &[crate::agent::GameEntity],
336        effect_text: &str,
337    ) -> bool {
338        self.agent.pay_cost_to_prevent_effect(
339            self.player,
340            cost_kind,
341            message,
342            source,
343            api,
344            can_pay,
345            targets,
346            effect_text,
347        )
348    }
349
350    pub fn confirm_bid_action(
351        &mut self,
352        mode: Option<&str>,
353        message: &str,
354        bid: i32,
355        winner: Option<PlayerId>,
356    ) -> bool {
357        let mut options = vec![format!("Bid {bid}")];
358        if let Some(winner) = winner {
359            options.push(format!("Winner {}", winner.0));
360        }
361        self.confirm_action(mode, message, &options, None, None)
362    }
363
364    pub fn confirm_replacement_effect(
365        &mut self,
366        description: &str,
367        source: Option<CardId>,
368    ) -> bool {
369        self.confirm_action(Some("ReplacementEffect"), description, &[], source, None)
370    }
371
372    pub fn confirm_static_application(
373        &mut self,
374        message: &str,
375        logic: Option<&str>,
376        source: Option<CardId>,
377    ) -> bool {
378        let options = logic.into_iter().map(str::to_string).collect::<Vec<_>>();
379        self.confirm_action(Some("StaticAbility"), message, &options, source, None)
380    }
381
382    pub fn choose_optional_trigger(
383        &mut self,
384        description: &str,
385        source: Option<CardId>,
386        api: Option<crate::ability::api_type::ApiType>,
387    ) -> bool {
388        self.agent
389            .choose_optional_trigger(self.player, description, source, api)
390    }
391
392    pub fn choose_target_spell(
393        &mut self,
394        valid_entries: &[u32],
395        source: Option<CardId>,
396    ) -> Option<u32> {
397        self.agent
398            .choose_target_spell(self.player, valid_entries, source)
399    }
400
401    pub fn choose_mode(
402        &mut self,
403        descriptions: &[String],
404        min: usize,
405        max: usize,
406        source_card_id: Option<CardId>,
407    ) -> Vec<usize> {
408        self.agent
409            .choose_mode(self.player, descriptions, min, max, source_card_id)
410    }
411
412    pub fn pay_mana_cost(
413        &mut self,
414        card_id: CardId,
415        card_name: &str,
416        mana_cost: &str,
417        mana_cost_display: &str,
418        mana_cost_checkpoint: &str,
419        can_confirm_from_pool: bool,
420        allow_reserved_source_reuse: bool,
421        reserved_sacrifices: &[CardId],
422        mana_ability_options: &[ManaAbilityOption],
423        tappable_lands: &[CardId],
424        untappable_lands: &[CardId],
425        mana_pool: &ManaPool,
426    ) -> ManaCostAction {
427        self.agent.pay_mana_cost(
428            self.player,
429            card_id,
430            card_name,
431            mana_cost,
432            mana_cost_display,
433            mana_cost_checkpoint,
434            can_confirm_from_pool,
435            allow_reserved_source_reuse,
436            reserved_sacrifices,
437            mana_ability_options,
438            tappable_lands,
439            untappable_lands,
440            mana_pool,
441        )
442    }
443
444    pub fn pay_combat_cost(
445        &mut self,
446        attacker: CardId,
447        cost: i32,
448        description: &str,
449        mana_ability_options: &[ManaAbilityOption],
450        tappable_lands: &[CardId],
451        untappable_lands: &[CardId],
452        mana_pool_total: i32,
453    ) -> CombatCostAction {
454        self.agent.pay_combat_cost(
455            self.player,
456            attacker,
457            cost,
458            description,
459            mana_ability_options,
460            tappable_lands,
461            untappable_lands,
462            mana_pool_total,
463        )
464    }
465
466    pub fn decide_cost_part(
467        &mut self,
468        source: CardId,
469        cost_part: &CostPart,
470    ) -> Option<PaymentDecision> {
471        self.agent
472            .decide_cost_part(self.player, source, cost_part, self.game)
473    }
474
475    pub fn order_cost_parts(&mut self, parts: Vec<CostPart>) -> Vec<CostPart> {
476        self.agent.order_cost_parts(parts)
477    }
478
479    pub fn choose_color(&mut self, valid_colors: &[String]) -> Option<String> {
480        self.agent.choose_color(self.player, valid_colors)
481    }
482
483    pub fn choose_card_name(&mut self, valid_names: &[String]) -> Option<String> {
484        self.agent.choose_card_name(self.player, valid_names)
485    }
486
487    pub fn choose_scry(&mut self, source: Option<CardId>, cards: &[CardId]) -> Vec<Vec<CardId>> {
488        self.agent
489            .choose_scry(self.game, self.player, source, cards)
490    }
491
492    pub fn choose_surveil(&mut self, source: Option<CardId>, cards: &[CardId]) -> Vec<Vec<CardId>> {
493        self.agent
494            .choose_surveil(self.game, self.player, source, cards)
495    }
496
497    pub fn choose_reorder_library(&mut self, cards: &[CardId]) -> Vec<CardId> {
498        self.agent
499            .choose_reorder_library(self.game, self.player, cards)
500    }
501
502    pub fn choose_discard(&mut self, hand: &[CardId], num: usize) -> Vec<CardId> {
503        self.agent.choose_discard(self.player, hand, num)
504    }
505
506    pub fn choose_random_discard(&mut self, hand: &[CardId], num: usize) -> Vec<CardId> {
507        self.agent.choose_random_discard(self.player, hand, num)
508    }
509
510    pub fn choose_delve(
511        &mut self,
512        valid: &[CardId],
513        max: usize,
514        source: Option<CardId>,
515    ) -> Vec<CardId> {
516        self.agent.choose_delve(self.player, valid, max, source)
517    }
518
519    pub fn choose_improvise(
520        &mut self,
521        untapped_artifacts: &[CardId],
522        remaining_cost: &ManaCost,
523        source: Option<CardId>,
524    ) -> Vec<CardId> {
525        self.agent
526            .choose_improvise(self.player, untapped_artifacts, remaining_cost, source)
527    }
528
529    pub fn choose_convoke(
530        &mut self,
531        untapped_creatures: &[CardId],
532        remaining_cost: &ManaCost,
533        source: Option<CardId>,
534    ) -> Vec<CardId> {
535        self.agent
536            .choose_convoke(self.player, untapped_creatures, remaining_cost, source)
537    }
538
539    pub fn specify_mana_combo(
540        &mut self,
541        available_colors: &[String],
542        amount: usize,
543        source: Option<CardId>,
544        express_choice: Option<u16>,
545    ) -> Vec<String> {
546        self.agent.specify_mana_combo(
547            self.player,
548            available_colors,
549            amount,
550            source,
551            express_choice,
552        )
553    }
554
555    pub fn choose_roll_swap_value(
556        &mut self,
557        current_result: i32,
558        power: i32,
559        toughness: i32,
560        source: Option<CardId>,
561    ) -> Option<RollSwapChoice> {
562        self.agent
563            .choose_roll_swap_value(self.player, current_result, power, toughness, source)
564    }
565
566    pub fn reveal(
567        &mut self,
568        cards: &[CardId],
569        zone: ZoneType,
570        owner: PlayerId,
571        message_prefix: Option<&str>,
572    ) {
573        self.reveal_cards(cards, zone, owner, message_prefix);
574    }
575
576    pub fn notify_of_value(&mut self, label: &str, value: &str) {
577        self.notify(crate::agent::notification::GameNotification::Event(
578            GameLogEvent::info(format!("{label}: {value}")).with_player(self.player),
579        ));
580    }
581
582    pub fn choose_single_replacement_effect(&mut self, descriptions: &[String]) -> usize {
583        self.agent
584            .choose_single_replacement_effect(self.player, descriptions)
585    }
586
587    pub fn choose_land_or_spell(&mut self) -> Option<bool> {
588        self.agent.choose_land_or_spell(self.player)
589    }
590
591    pub fn choose_sector(&mut self, sectors: &[String]) -> Option<String> {
592        self.choose_some_type("Sector", sectors)
593    }
594
595    pub fn add_keyword_cost(&mut self, prompt: &str) -> bool {
596        self.choose_binary(
597            prompt,
598            BinaryChoiceKind::AddOrRemove,
599            Some(true),
600            None,
601            None,
602        )
603    }
604
605    pub fn cheat_shuffle(&mut self) {
606        self.notify(crate::agent::notification::GameNotification::Event(
607            GameLogEvent::info("Shuffle requested"),
608        ));
609    }
610
611    pub fn reset_inputs(&mut self) {}
612
613    pub fn can_play_unlimited_lands(&self) -> bool {
614        self.game.player(self.player).unlimited_land_plays
615    }
616}