Deal System Testing

Overview

This document details the testing approach for the Deal system within Rummage's multiplayer Commander implementation. Deals are a key political mechanic allowing players to make formal agreements with benefits and consequences, adding strategic depth to multiplayer interaction.

Deal Creation Tests

Tests for the creation and initialization of deals:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_creation() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a simple deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[1],
                duration: DealDuration::Turns(2),
            },
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![
            DealReward::DrawCards { count: 1 },
        ],
        penalties: vec![
            DealPenalty::LifeLoss { amount: 5 },
        ],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Verify deal was created and is in pending state
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal = deal_registry.get_deal(deal_id).unwrap();
    
    assert_eq!(deal.proposer, players[0]);
    assert_eq!(deal.target, players[1]);
    assert_eq!(deal.terms.len(), 2);
    assert_eq!(deal.status, DealStatus::Pending);
}

#[test]
fn test_deal_term_validation() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal with invalid terms (can't restrict a player not in the deal)
    let invalid_deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[2], // Invalid: players[2] not part of deal
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Verify deal was rejected
    let deal_registry = app.world.resource::<DealRegistry>();
    let invalid_deal = deal_registry.get_deal(invalid_deal_id).unwrap();
    
    assert_eq!(invalid_deal.status, DealStatus::Rejected);
    assert_eq!(
        invalid_deal.rejection_reason, 
        Some(DealRejectionReason::InvalidTerms)
    );
    
    // Create a valid deal
    let valid_deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[1], // Valid: players[1] is target
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Verify valid deal is pending
    let valid_deal = deal_registry.get_deal(valid_deal_id).unwrap();
    assert_eq!(valid_deal.status, DealStatus::Pending);
}

#[test]
fn test_deal_duration_validation() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal with excessively long duration
    let long_deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(20), // Too long
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(20), // Exceeds maximum allowed duration
    });
    app.update();
    
    // Verify deal was rejected
    let deal_registry = app.world.resource::<DealRegistry>();
    let long_deal = deal_registry.get_deal(long_deal_id).unwrap();
    
    assert_eq!(long_deal.status, DealStatus::Rejected);
    assert_eq!(
        long_deal.rejection_reason, 
        Some(DealRejectionReason::ExcessiveDuration)
    );
}
}

Deal Negotiation Tests

Testing the deal negotiation mechanics:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_acceptance() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![
            DealReward::DrawCards { count: 1 },
        ],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Target accepts the deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Verify deal is now active
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal = deal_registry.get_deal(deal_id).unwrap();
    
    assert_eq!(deal.status, DealStatus::Active);
    
    // Verify reward was given
    let player1_hand_size = get_player_hand_size(&app, players[1]);
    assert_eq!(player1_hand_size, 8); // Assuming starting hand size of 7 + 1 from reward
}

#[test]
fn test_deal_rejection() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Target rejects the deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Reject,
    });
    app.update();
    
    // Verify deal is now rejected
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal = deal_registry.get_deal(deal_id).unwrap();
    
    assert_eq!(deal.status, DealStatus::Rejected);
    assert_eq!(deal.rejection_reason, Some(DealRejectionReason::TargetRejected));
}

#[test]
fn test_deal_counter_proposal() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[1],
                duration: DealDuration::Turns(3),
            },
        ],
        rewards: vec![
            DealReward::DrawCards { count: 1 },
        ],
        penalties: vec![],
        duration: DealDuration::Turns(3),
    });
    app.update();
    
    // Target makes a counter-proposal
    let counter_proposal = DealCounterProposal {
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[1],
                duration: DealDuration::Turns(2), // Shorter duration
            },
        ],
        rewards: vec![
            DealReward::DrawCards { count: 2 }, // More cards
        ],
        penalties: vec![],
        duration: DealDuration::Turns(2), // Shorter duration
    };
    
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::CounterProposal(counter_proposal),
    });
    app.update();
    
    // Verify original deal is now countered
    let deal_registry = app.world.resource::<DealRegistry>();
    let original_deal = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(original_deal.status, DealStatus::Countered);
    
    // Find the counter proposal
    let counter_deals: Vec<_> = deal_registry.deals_iter()
        .filter(|d| d.status == DealStatus::Pending && d.proposer == players[1] && d.target == players[0])
        .collect();
    
    assert_eq!(counter_deals.len(), 1);
    let counter_deal = &counter_deals[0];
    
    // Verify counter deal has the modified terms
    assert_eq!(counter_deal.rewards.len(), 1);
    if let DealReward::DrawCards { count } = counter_deal.rewards[0] {
        assert_eq!(count, 2);
    } else {
        panic!("Expected DrawCards reward");
    }
    
    // Proposer accepts counter-proposal
    app.world.send_event(RespondToDealEvent {
        responder: players[0],
        deal_id: counter_deal.id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Verify counter deal is now active
    let updated_counter_deal = deal_registry.get_deal(counter_deal.id).unwrap();
    assert_eq!(updated_counter_deal.status, DealStatus::Active);
}
}

Deal Enforcement Tests

Testing the enforcement and violation of deals:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_enforcement_attack_restriction() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin)
       .add_plugin(CombatPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create player creatures
    let attacker = app.world.spawn((
        Creature::default(),
        Permanent { controller: players[1], ..Default::default() },
    )).id();
    
    // Create a deal with attack restriction
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::AttackRestriction {
                restricted_player: players[0], // Target can't attack proposer
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![
            DealPenalty::LifeLoss { amount: 3 },
        ],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Target accepts the deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Capture life total before violation
    let initial_life = app.world.get::<Player>(players[1]).unwrap().life_total;
    
    // Target attempts to attack proposer (violating deal)
    app.world.send_event(DeclareAttackerEvent {
        attacker,
        defender: players[0],
    });
    app.update();
    
    // Verify deal violation was detected
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal = deal_registry.get_deal(deal_id).unwrap();
    
    assert!(deal.violations.contains(&DealViolation {
        violator: players[1],
        violation_type: ViolationType::AttackRestriction,
        turn: app.world.resource::<TurnState>().turn_number,
    }));
    
    // Verify penalty was applied
    let new_life = app.world.get::<Player>(players[1]).unwrap().life_total;
    assert_eq!(new_life, initial_life - 3);
}

#[test]
fn test_deal_expiration() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin)
       .add_plugin(TurnStructurePlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal with short duration
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(1),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(1), // 1 turn duration
    });
    app.update();
    
    // Target accepts the deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Verify deal is active
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_before = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_before.status, DealStatus::Active);
    
    // Advance turn
    advance_turn(&mut app);
    app.update();
    
    // Verify deal is now expired
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_after = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_after.status, DealStatus::Expired);
}

#[test]
fn test_deal_auto_termination_on_player_loss() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create and activate a deal
    let deal_id = create_and_accept_deal(&mut app, players[0], players[1]);
    
    // Verify deal is active
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_before = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_before.status, DealStatus::Active);
    
    // Player leaves game
    app.world.send_event(PlayerEliminatedEvent { player: players[0] });
    app.update();
    
    // Verify deal is now terminated
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_after = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_after.status, DealStatus::Terminated);
    assert_eq!(
        deal_after.termination_reason, 
        Some(DealTerminationReason::PlayerEliminated)
    );
}
}

Deal History and Reputation Tests

Testing deal history tracking and reputation systems:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_history_tracking() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create and activate multiple deals
    let deal1_id = create_and_accept_deal(&mut app, players[0], players[1]);
    let deal2_id = create_and_accept_deal(&mut app, players[1], players[2]);
    let deal3_id = create_and_accept_deal(&mut app, players[0], players[2]);
    
    // Player 2 violates deal with player 0
    simulate_deal_violation(&mut app, deal3_id, players[2]);
    
    // Get deal history
    let history = app.world.resource::<DealHistory>();
    
    // Check player 0's history
    let player0_history = history.get_player_history(players[0]);
    assert_eq!(player0_history.deals_proposed, 2);
    assert_eq!(player0_history.deals_honored, 2); // Both active or not violated yet
    
    // Check player 2's history
    let player2_history = history.get_player_history(players[2]);
    assert_eq!(player2_history.deals_proposed, 0);
    assert_eq!(player2_history.deals_accepted, 2);
    assert_eq!(player2_history.deals_violated, 1);
}

#[test]
fn test_reputation_system() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create and accept several deals
    let deal1_id = create_and_accept_deal(&mut app, players[0], players[1]);
    let deal2_id = create_and_accept_deal(&mut app, players[0], players[2]);
    let deal3_id = create_and_accept_deal(&mut app, players[0], players[3]);
    
    // Player 3 violates deal
    simulate_deal_violation(&mut app, deal3_id, players[3]);
    
    // Check reputations
    let reputation_system = app.world.resource::<ReputationSystem>();
    
    let player0_rep = reputation_system.get_reputation(players[0]);
    let player1_rep = reputation_system.get_reputation(players[1]);
    let player3_rep = reputation_system.get_reputation(players[3]);
    
    // Player 0 should have good reputation (keeps deals)
    assert!(player0_rep.score > 0.0);
    
    // Player 1 should have neutral/positive reputation (honors deals)
    assert!(player1_rep.score >= 0.0);
    
    // Player 3 should have negative reputation (violated deal)
    assert!(player3_rep.score < 0.0);
    
    // Test reputation effects on deal proposals
    // Player 3 (bad reputation) tries to make a deal
    let deal4_id = app.world.send_event(CreateDealEvent {
        proposer: players[3],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Target is more likely to reject due to proposer's reputation
    let ai_decision = app.world.resource::<AiDealSystem>()
        .evaluate_deal_proposal(players[1], deal4_id);
    
    // AI should factor in reputation (exact values depend on implementation)
    assert!(ai_decision.trust_factor < 0.5);
}
}

Integration Tests

Testing deal system integration with other game systems:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_integration_with_combat() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin)
       .add_plugin(CombatPlugin);
       
    // Setup players and creatures
    let players = setup_four_player_game(&mut app);
    
    let creature1 = spawn_test_creature(&mut app, 2, 2, players[0]);
    let creature2 = spawn_test_creature(&mut app, 3, 3, players[1]);
    
    // Create non-aggression pact
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Accept deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Try to declare attack between deal participants
    app.world.send_event(DeclareAttackerEvent {
        attacker: creature1,
        defender: players[1],
    });
    app.update();
    
    // Verify combat restriction was enforced
    let combat_state = app.world.resource::<CombatState>();
    assert!(!combat_state.is_attacking(creature1));
    
    // But can still attack other players
    app.world.send_event(DeclareAttackerEvent {
        attacker: creature1,
        defender: players[2], // Not part of deal
    });
    app.update();
    
    // Verify attack was allowed
    let combat_state = app.world.resource::<CombatState>();
    assert!(combat_state.is_attacking(creature1));
    assert_eq!(combat_state.get_defender(creature1), Some(players[2]));
}

#[test]
fn test_deal_integration_with_card_effects() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create "deal breaker" card
    let deal_breaker = app.world.spawn((
        Card::default(),
        DealBreakerEffect,
    )).id();
    
    // Create and accept a deal
    let deal_id = create_and_accept_deal(&mut app, players[0], players[1]);
    
    // Verify deal is active
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_before = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_before.status, DealStatus::Active);
    
    // Cast deal breaker card (e.g., "Council's Judgment")
    app.world.send_event(CastCardEvent {
        caster: players[2],
        card: deal_breaker,
        targets: vec![EntityTarget::Deal(deal_id)],
    });
    app.update();
    
    // Resolve card effect
    app.world.send_event(ResolveCardEffectEvent {
        card: deal_breaker,
    });
    app.update();
    
    // Verify deal was terminated
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal_after = deal_registry.get_deal(deal_id).unwrap();
    assert_eq!(deal_after.status, DealStatus::Terminated);
    assert_eq!(
        deal_after.termination_reason, 
        Some(DealTerminationReason::CardEffect)
    );
}
}

UI and Notification Tests

Testing UI and notification aspects of the deal system:

#![allow(unused)]
fn main() {
#[test]
fn test_deal_ui_representation() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin)
       .add_plugin(UiPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create a deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer: players[0],
        target: players[1],
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Verify deal UI elements were created
    let deal_ui_elements = app.world.query_filtered::<Entity, With<DealUiElement>>()
        .iter(&app.world)
        .collect::<Vec<_>>();
    
    assert!(!deal_ui_elements.is_empty());
    
    // Check for proposal notification
    let notifications = app.world.query_filtered::<&Notification, With<DealProposalNotification>>()
        .iter(&app.world)
        .collect::<Vec<_>>();
    
    assert!(!notifications.is_empty());
    assert!(notifications[0].message.contains("proposed a deal"));
    
    // Accept deal
    app.world.send_event(RespondToDealEvent {
        responder: players[1],
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    // Check for acceptance notification
    let acceptance_notifications = app.world.query_filtered::<&Notification, With<DealAcceptanceNotification>>()
        .iter(&app.world)
        .collect::<Vec<_>>();
    
    assert!(!acceptance_notifications.is_empty());
    assert!(acceptance_notifications[0].message.contains("accepted"));
    
    // Verify active deal indicator visible
    let active_deal_indicators = app.world.query_filtered::<Entity, (With<ActiveDealIndicator>, With<Parent>)>()
        .iter(&app.world)
        .collect::<Vec<_>>();
    
    assert!(!active_deal_indicators.is_empty());
}

#[test]
fn test_deal_violation_notifications() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
       .add_plugin(PoliticsPlugin)
       .add_plugin(UiPlugin);
       
    // Setup players
    let players = setup_four_player_game(&mut app);
    
    // Create and accept a deal
    let deal_id = create_and_accept_deal(&mut app, players[0], players[1]);
    
    // Simulate deal violation
    simulate_deal_violation(&mut app, deal_id, players[1]);
    
    // Check for violation notification
    let violation_notifications = app.world.query_filtered::<&Notification, With<DealViolationNotification>>()
        .iter(&app.world)
        .collect::<Vec<_>>();
    
    assert!(!violation_notifications.is_empty());
    assert!(violation_notifications[0].message.contains("violated"));
}
}

Helper Functions

#![allow(unused)]
fn main() {
/// Creates a standard deal and accepts it
fn create_and_accept_deal(app: &mut App, proposer: Entity, target: Entity) -> DealId {
    // Create deal
    let deal_id = app.world.send_event(CreateDealEvent {
        proposer,
        target,
        terms: vec![
            DealTerm::NonAggressionPact {
                duration: DealDuration::Turns(2),
            },
        ],
        rewards: vec![],
        penalties: vec![],
        duration: DealDuration::Turns(2),
    });
    app.update();
    
    // Accept deal
    app.world.send_event(RespondToDealEvent {
        responder: target,
        deal_id,
        response: DealResponse::Accept,
    });
    app.update();
    
    deal_id
}

/// Simulates violating a deal
fn simulate_deal_violation(app: &mut App, deal_id: DealId, violator: Entity) {
    // Get deal
    let deal_registry = app.world.resource::<DealRegistry>();
    let deal = deal_registry.get_deal(deal_id).unwrap();
    
    // Determine violation type based on terms
    let violation_type = if let Some(DealTerm::NonAggressionPact { .. }) = deal.terms.first() {
        ViolationType::NonAggressionViolation
    } else if let Some(DealTerm::AttackRestriction { .. }) = deal.terms.first() {
        ViolationType::AttackRestriction
    } else {
        ViolationType::Generic
    };
    
    // Trigger violation
    app.world.send_event(DealViolationEvent {
        deal_id,
        violator,
        violation_type,
    });
    app.update();
}

/// Creates a test creature
fn spawn_test_creature(app: &mut App, power: i32, toughness: i32, controller: Entity) -> Entity {
    app.world.spawn((
        Creature {
            power,
            toughness,
            ..Default::default()
        },
        Permanent {
            controller,
            ..Default::default()
        },
    )).id()
}
}

Conclusion

The Deal System adds significant depth to political interactions in Commander. These tests ensure that deals are properly created, negotiated, enforced, and terminated under all relevant circumstances, while accurately tracking player reputation based on deal history.