Simulating the Pig-Slaying Card Game

Game Overview

This problem involves a complex simulation of a card game variant. The objective is to determine the final outcome given initial roles, hands, and a deck of cards, assuming all players follow strict behavioral rules.

Victory Conditions

  • Master Pig (MP): Survives while eliminating all Rebel Pigs.
  • Loyal Pig (ZP): Protects the Master Pig at all costs; shares the Master's victory condition.
  • Rebel Pig (FP): Eliminates the Master Pig.

Game Flow

At the start, every player holds 4 cards, with max HP and current HP set to 4. The game proceeds counter-clockwise (index 1, 2, ..., n, then back to 1). Each turn consists of:

  1. Draw Phase: Draw 2 cards from the deck to the rightmost side of the hand.
  2. Play Phase: Use cards from left to right.
    • Without the "Pig Crossbow" weapon, only one "Slash" can be used per turn.
    • Used cards (except equipped weapons) are discarded permanently.

Card Types

Basic Cards

  • Peach (P): Restore 1 HP during your turn if HP is below max. Can be used when HP drops to 0 outside your turn.
  • Slash (K): Attack a target within range (default range is 1). If not dodged by a "Dodge", deals 1 damage.
  • Dodge (D): Discard to negate a "Slash" attack.

Trick Cards

  • Duel (F): Target vs. User alternate discarding "Slash". The first unable to discard takes 1 damage.
  • Southern Invasion (N): All others must discard a "Slash" or take 1 damage.
  • Volley (W): Similar to Invasion, but requires discarding a "Dodge".
  • Nullify (J): Counter a trick card.
    • Against Duel: Cancels the duel.
    • Against Invasion/Volley: Protects the current target being processed.
    • Against Nullify: Cancels the counter-attempt.

Equipment

  • Pig Crossbow (Z): Allows unlimited "Slash" uses per turn. Equipping a new weapon discards the old one.

Key Mechanics

  • Damage Source: The user of Slash, Invasion, or Volley. In a Duel, the source is the player who forced the opponent to lack a Slash.
  • Distance: Calculated as the number of players in the counter-clocwkise path + 1. Deaths alter distances.
  • Death: If HP ≤ 0 and insufficient Peaches are held to revive to 1 HP, the player dies. All cards are discarded.
  • Rewards: Killing a Rebel grants the killer 3 cards. If Master kills a Loyal, Master discards all cards.

Behavioral Logic

Identity Declaration

  • Loyal: Shows loyalty by helping Master/Loyalists and attacking Rebels.
  • Rebel: Shows rebellion by attacking Master/Loyalists and helping Rebels.
  • Neutral: Unidentified players are not assisted by the Master until they cause damage via AoE.

General Rules (All Roles)

  • Use Peach immediately if HP < 4.
  • Equip weapons and use AoE cards (Invasion/Volley) immediately.
  • Dodge Slashes and respond to AoE if possible.
  • Do not assist unidentified players.

Role-Specific Rules

  • Master:
    • Labels players as "Suspicious Rebels" if they damage him via AoE.
    • Attacks the first Suspicious or Declared Rebel counter-clockwise.
    • Always fights aggressively in Duels unless the opponent is a Loyalist he falsely suspects.
  • Loyalist:
    • Attacks the first Declared Rebel counter-clockwise.
    • Refuses to discard Slashes in a Duel against the Master.
  • Rebel:
    • Prioritizes attacking the Master. If not possible, attacks the first Declared Loyalist.
    • Fights aggressively in Duels.
    • Assists fellow Rebels.

Impleemntation Strategy

The simulation is broken down by features. We track player states using arrays for HP, hand cards, identity status, and equipment.

Data Structures

int health[15];          // Current HP
int cardCount[15];       // Total cards owned (hand + discarded)
char hand[15][1010];     // Card storage
bool isAlive[15];        // Death status
bool revealed[15];       // Has the player declared identity?
bool suspicious[15];     // Master's suspicion list
bool hasCrossbow[15];    // Weapon flag
int nextPlayer[15];      // Circular linked list (counter-clockwise)
int prevPlayer[15];
string role[15];         // "MP", "ZP", "FP"
char deck[1010];         // Draw pile
int drawPos;             // Current position in deck
int rebelCount;          // Live rebel count
bool used[15][1010];     // Track discarded cards in hand

Core Helper Functions

These functions handle card management and basic checks.

// Count specific card type in hand
int countCard(int player, char type) {
    int res = 0;
    for (int i = 1; i <= cardCount[player]; i++)
        if (!used[player][i] && hand[player][i] == type) res++;
    return res;
}

// Remove K cards of a specific type from hand
void removeCard(int player, int k, char type) {
    if (k <= 0) return;
    int removed = 0;
    for (int i = 1; i <= cardCount[player] && removed < k; i++)
        if (!used[player][i] && hand[player][i] == type) {
            used[player][i] = true;
            removed++;
        }
}

// Restore health during own turn
void usePeach(int player) {
    if (health[player] < 4) {
        health[player]++;
        removeCard(player, 1, 'P');
    }
}

// Attempt revival when HP <= 0 (outside own turn)
bool tryRevive(int player) {
    if (health[player] > 0) return false;
    int peachNum = countCard(player, 'P');
    if (peachNum >= (abs(health[player]) + 1)) {
        removeCard(player, abs(health[player]) + 1, 'P');
        health[player] = 1;
        return false; // Survived
    } else {
        // Die
        for (int i = 1; i <= cardCount[player]; i++) used[player][i] = true;
        hasCrossbow[player] = false;
        isAlive[player] = false;
        return true; // Dead
    }
}

// Discard all cards of a player (Master kills Loyalist)
void discardAll(int player) {
    for (int i = 1; i <= cardCount[player]; i++) used[player][i] = true;
    hasCrossbow[player] = false;
}

Death Handling

When a player dies, we update the linked list and check victory conditions.

void handleDeath(int killer, int victim) {
    isAlive[victim] = false;
    // Update circular list
    nextPlayer[prevPlayer[victim]] = nextPlayer[victim];
    prevPlayer[nextPlayer[victim]] = prevPlayer[victim];

    if (role[victim] == "MP") { outputResult(); exit(0); }
    
    if (role[victim] == "FP") {
        rebelCount--;
        if (rebelCount == 0) { outputResult(); exit(0); }
        // Killer draws 3 cards
        for (int i = 0; i < 3; i++) {
            if (drawPos <= m) hand[killer][++cardCount[killer]] = deck[drawPos++];
        }
    } else if (role[killer] == "MP") {
        discardAll(killer);
    }
}

Action Simulation (Slash, Duel, AoE)

We implement the logic for specific card plays based on the player's role and targets.

// Slash logic
void performSlash(int user, int target) {
    revealed[user] = true;
    bool hit = true;
    if (countCard(target, 'D') > 0) {
        removeCard(target, 1, 'D');
        hit = false;
    }
    removeCard(user, 1, 'K');
    if (hit) {
        health[target]--;
        if (health[target] <= 0) {
            if (tryRevive(target)) handleDeath(user, target);
        }
    }
}

// Duel logic
void performDuel(int user, int target) {
    revealed[user] = true;
    removeCard(user, 1, 'F');
    
    // Special Master logic regarding suspicious loyalists
    if (role[user] == "MP" && role[target] == "ZP" && suspicious[target] && !revealed[target]) {
        health[target]--;
        if (health[target] <= 0 && tryRevive(target)) handleDeath(user, target);
        return;
    }

    int userSlash = countCard(user, 'K');
    int tarSlash = countCard(target, 'K');
    
    // Loyalist refuses to fight Master
    if (role[target] == "MP" && role[user] == "ZP") tarSlash = 0;

    if (tarSlash <= userSlash) {
        // Target loses
        removeCard(target, tarSlash, 'K');
        removeCard(user, tarSlash, 'K');
        health[target]--;
        if (health[target] <= 0 && tryRevive(target)) handleDeath(user, target);
    } else {
        // User loses
        removeCard(user, userSlash, 'K');
        removeCard(target, userSlash + 1, 'K');
        health[user]--;
        if (health[user] <= 0 && tryRevive(user)) handleDeath(target, user);
    }
}

Target Selection

Finding the appropriate target based on role and identity status.

int findTarget(int player, string searcherRole) {
    int start = player;
    // Search counter-clockwise
    for (int offset = 1; offset <= n; offset++) {
        int curr = (start + offset - 1) % n + 1;
        if (!isAlive[curr]) continue;

        if (searcherRole == "MP") {
            if ((revealed[curr] && role[curr] == "FP") || suspicious[curr]) return curr;
        } else if (searcherRole == "ZP") {
            if (revealed[curr] && role[curr] == "FP") return curr;
        } else if (searcherRole == "FP") {
            if (curr == 1) return curr; // Master is priority
            if (revealed[curr] && role[curr] == "ZP") return curr;
        }
    }
    return -1; // No valid target
}

Main Turn Loop

The play phase iterates through the hand from left to right.

void executeTurn(int player) {
    // 1. Draw Phase
    for (int i = 0; i < 2; i++) {
        if (drawPos <= m) hand[player][++cardCount[player]] = deck[drawPos++];
    }

    bool slashUsed = false;
    // 2. Play Phase
    for (int i = 1; i <= cardCount[player]; i++) {
        if (used[player][i]) continue;
        char card = hand[player][i];
        
        switch (card) {
            case 'P': usePeach(player); break;
            case 'Z': hasCrossbow[player] = true; removeCard(player, 1, 'Z'); break;
            case 'K':
                if (!isAlive[player]) break;
                // Check if we can/should use Slash
                bool canSlash = (hasCrossbow[player] || !slashUsed);
                if (canSlash) {
                    int target = findTarget(player, role[player]);
                    // Basic logic: attack next player if no specific target found (simplified for snippet)
                    if (target == -1) target = nextPlayer[player]; 
                    
                    if (target != -1 && isAlive[target]) {
                        if (role[player] == "FP" && target != 1 && !revealed[target]) break; // Rebel waits for Master
                        performSlash(player, target);
                        slashUsed = true;
                    }
                }
                break;
            case 'F':
                int duelTarget = findTarget(player, role[player]);
                if (duelTarget != -1 && isAlive[duelTarget]) performDuel(player, duelTarget);
                break;
            case 'N': // Southern Invasion
            case 'W': // Volley
                // Implementation involves iterating through all other players
                // and checking for Nullify (J) cards in a chain reaction
                break;
        }
        if (!isAlive[player]) break;
    }
}

Full Simulation Loop

void simulateGame() {
    int current = 1;
    while (rebelCount > 0 && isAlive[1]) {
        if (isAlive[current]) {
            executeTurn(current);
        }
        current = nextPlayer[current];
    }
    outputResult();
}

Handling "Nullify" (J) and AoE

The 100-point solution requires implementing the "Nullify" chain. When a Trick card (F, N, W) is played, a sub-routine checks players in counter-clockwise order starting from the user. Each player decides to use "J" based on whether they want the Trick to succeed or fail relative to the current target.

Tags: Pig-Slaying C++ Simulation Game AI Card Game Logic Competitive Programming

Posted on Mon, 18 May 2026 12:50:49 +0000 by rowantrimmer