Jump to content

Recommended Posts

Posted
23 minutes ago, Doc12 said:

I really screw myself over whenever I play really well evil because then no one trusts me in a game when I'm village 😛

@Hoid Slayerno thoughts about me ? 🥺

Looking back, there really was an alternate game where I didn't get converted c1. I had found the Shardbearer c1, and we could have lynched him c2, making it all the harder for whoever did get converted to even the score. So again, Mistfallen's one action changed the game (even though I had to put in all the work ) 

Only that you fixed all of our mechanic mistakes with your plotting

A true hero

Posted

 

15 minutes ago, Mistfallen Soldier said:

lol

We were going to do Jo, and then it switched to you, so at least one of you would’ve been an Elim, if not both (perhaps Jo would have converted you)

But anyways, yeah, I am very proud of myself for doing that

I wouldn't have converted anyone until I was in the middle of getting executed, so i would have been a solo act for far longer. Not sure who I would have converted.

Posted

honestly I might've considered protecting Archer but I kinda forgot that day and night weren't seperated 😛

that's what I get for not paying attention 😛

anyways seems like it was a fun game, gg to all those that played and thanks to the GMs

sorry for fibbing about being your spren coco, no hard feelings I hope I mean its not like it rly worked considering how the first day ended, I did feel you seemed villagey though

re: game balance, agreed that if things had gone differently, a spren with an elim radiant could have yielded basically a bonus elim with a vote manipulation power, which could've made things quite a bit more elim favouring

but you know how that song and dance goes, the life-link between spren and radiant makes it very much all-or-nothing, either you get 2 elims or 0

the best laid plans of mice and spren often go awry :P

If the Spren win condition were tweaked a bit, perhaps there could be incentives to remain more neutral and play both sides, making the role less prone to tipping the balance. I could probably spitball a few ideas if you ever want to run this again sometime. Or not. Honestly it's probably ok to leave it as-is. If you do ever do this setup again, I bet you'll have a better idea how to handle it.

anyways balance isn't everything

Posted
5 hours ago, DrakeMarshall said:

 

re: game balance, agreed that if things had gone differently, a spren with an elim radiant could have yielded basically a bonus elim with a vote manipulation power, which could've made things quite a bit more elim favouring

but you know how that song and dance goes, the life-link between spren and radiant makes it very much all-or-nothing, either you get 2 elims or 0

the best laid plans of mice and spren often go awry :P

If the Spren win condition were tweaked a bit, perhaps there could be incentives to remain more neutral and play both sides, making the role less prone to tipping the balance. I could probably spitball a few ideas if you ever want to run this again sometime. Or not. Honestly it's probably ok to leave it as-is. If you do ever do this setup again, I bet you'll have a better idea how to handle it.

anyways balance isn't everything

I heard that and in indignation-

-am forced to agree since you only play each setup once

Posted
20 hours ago, Doc12 said:

Oh I promise I am not that scary :P Dive you've been evil with me once (kinda) and we lost bad in the AN XD 

It's funny that you spectators didn't even see my PMs - I was chatting up almost all the villagers and telling them what they wanted to hear, which is why no one suspected me in the end :) But then again, I PM everyone regardless of whether I'm evil or good, so always trust me when I'm PMing you because I am friendly and kind :D 

Oh I was scared alright when you went after me D2 of that game back when we didn't know yet that we were both elims :P.

I know from experience that you do so well convincing people of your innocence if given the opportunity to do so in PMs haha so I can see why the villagers trusted you until the end

Posted (edited)
25 minutes ago, Divergent said:

I know from experience that you do so well convincing people of your innocence if given the opportunity to do so in PMs haha so I can see why the villagers trusted you until the end

But I was innocent in the one long game where you were a pinch hitter 😛 Glad I convinced you then even if someone (glares in Mistfallen's direction) got me lynched anyway. 

 

Edit: You know. Maybe that's WHY I got killed day 1 of the AN... The GMs nerfed me by not opening PMs day 1 :(

Edited by Doc12
Posted
23 minutes ago, Doc12 said:

But I was innocent in the one long game where you were a pinch hitter 😛 Glad I convinced you then even if someone (glares in Mistfallen's direction) got me lynched anyway. 

 

Edit: You know. Maybe that's WHY I got killed day 1 of the AN... The GMs nerfed me by not opening PMs day 1 :(

When I saw that there wasn't a living Tineye D1, I was like "If Doc's playing this game, he'd be sad there's no PMs" 

Posted (edited)

Uh so I made this little script with this rule set because I thought it would be kind of fun to see it. Uh this kind of works like a master spreadsheet might but it is probably buggy and inefficient. If any more experienced coders (*cough cough* @Usseewa @KaladinsSenseOfHumourSpren) want to give me feedback, that would be appreciated

import random
import os

def clear_screen():
    os.system('cls' if os.name == 'nt' else 'clear')

class Player:
    def __init__(self, name, alignment, role=None, radiant_target=None):
        self.name = name
        self.alignment = alignment
        self.role = role
        self.is_alive = True
        self.is_roleblocked = False
        self.protected = False
        self.role_action_type = None
        self.role_action_target = None
        self.faction_action_type = None
        self.faction_action_target = None
        self.radiant_target = radiant_target
        self.spren_prot_used = False

    def __repr__(self):
        status = "" if self.is_alive else "[DEAD] "
        return f"{status}{self.name} ({self.alignment} {self.role if self.role else ''})"

# --- ABILITY CONFIGURATION ---
# Defines exactly which roles can do which actions
ROLE_ABILITIES = {
    'Direform': ['block'],
    'Stormform': ['redirect'],
    'Envoyform': ['decrease'],
    'Inspiring': ['increase', 'block_conv'],
    'Heroic': ['protect'],
    'Spren': ['protect_radiant'],
    'Shardbearer': ['kill'],
    'Discerning': ['scan'],
    'Sneaky': ['sneaky'],
    'Willful': [] # Passive role
}

# --- GLOBAL GAME STATE ---
conversion_charges = 2

def display_graveyard(players):
    dead_players = [f"{p.name} ({p.alignment} {p.role})" for p in players if not p.is_alive]
    print("\n=== GRAVEYARD (ELIMINATED) ===")
    if dead_players:
        for entry in dead_players:
            print(f"• {entry}")
    else:
        print("No one has died yet.")
    print("==============================")

def simulate_cycle(players):
    global conversion_charges
    alive_at_start = [p.name for p in players if p.is_alive]
    
    for p in players:
        p.is_roleblocked = False
        p.protected = False

    # 1. Stormform Redirect
    for p in [x for x in players if x.role == 'Stormform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'redirect' and p.role_action_target:
            storm_target = p.role_action_target
            actors_to_redirect = [a for a in players if (a.role_action_target == storm_target or a.faction_action_target == storm_target) and a != p]
            for actor in actors_to_redirect:
                new_dest = random.choice([x for x in players if x != storm_target and x.is_alive])
                if actor.role_action_target == storm_target: actor.role_action_target = new_dest
                if actor.faction_action_target == storm_target: actor.faction_action_target = new_dest
                print(f"STORM: {p.name} redirected {actor.name} to {new_dest.name}!")

    # 2. Direform Roleblock
    for p in [x for x in players if x.role == 'Direform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'block' and p.role_action_target:
            p.role_action_target.is_roleblocked = True
            print(f"BLOCK: {p.name} (Direform) roleblocked {p.role_action_target.name}")

    # 4. Protections (Heroic & Spren)
    protected_list = [p.role_action_target for p in players if p.role == 'Heroic' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.role == 'Spren' and x.is_alive and not x.is_roleblocked]:
        if p.role_action_type == 'protect_radiant' and not p.spren_prot_used and p.role_action_target == p.radiant_target:
            protected_list.append(p.radiant_target)
            p.spren_prot_used = True
            print(f"SUCCESS: {p.name} (Spren) protected {p.radiant_target.name}!")

    # 5. Conversion (Regals)
    conv_blocked = [p.role_action_target for p in players if p.role == 'Inspiring' and p.role_action_type == 'block_conv' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.alignment == 'Regal' and x.faction_action_type == 'convert' and not x.is_roleblocked and x.is_alive]:
        if conversion_charges <= 0: continue
        target = p.faction_action_target
        if target and target.alignment.capitalize() == 'Singer' and target not in conv_blocked and target.role != 'Willful':
            target.alignment = 'Regal'
            conversion_charges -= 1
            print(f"CONVERSION: {target.name} has joined the Regals!")

    # 6. Kills
    targets_to_die = []
    for p in [x for x in players if (x.faction_action_type == 'kill' or x.role == 'Shardbearer') and not x.is_roleblocked and x.is_alive]:
        target = p.faction_action_target if p.alignment == 'Regal' else p.role_action_target
        if target and target not in protected_list:
            targets_to_die.append(target)
    
    for target in list(set(targets_to_die)):
        target.is_alive = False

    # 7. Spren Death Logic
    for s in [x for x in players if x.role == 'Spren' and x.is_alive]:
        if s.radiant_target and not s.radiant_target.is_alive:
            s.is_alive = False
            print(f"LOSS: {s.name}'s Radiant ({s.radiant_target.name}) died. {s.name} is eliminated.")

    print("\n--- NIGHT DEATH REPORT ---")
    deaths = [n for n in alive_at_start if not next(pl for pl in players if pl.name == n).is_alive]
    if deaths:
        for d in deaths: print(f"DIED: {d}")
    else:
        print("No one died tonight.")
    
    display_graveyard(players)
    return players

def simulate_day_phase(players, votes):
    tally = {p.name: 0 for p in players if p.is_alive}
    penalized = [p.role_action_target.name for p in players if p.role == 'Envoyform' and p.role_action_type == 'decrease' and not p.is_roleblocked and p.is_alive and p.role_action_target]
    buffed = [p.role_action_target.name for p in players if p.role == 'Inspiring' and p.role_action_type == 'increase' and not p.is_roleblocked and p.is_alive and p.role_action_target]

    for v_name, t_name in votes.items():
        if t_name in tally:
            weight = 1
            if v_name in penalized: weight = 0
            elif v_name in buffed: weight = 2
            tally[t_name] += weight

    if tally:
        victim_name = max(tally, key=tally.get)
        if tally[victim_name] > 0:
            victim = next(p for p in players if p.name == victim_name)
            victim.is_alive = False
            print(f"\n--- DAY PHASE: {victim_name} voted out with {tally[victim_name]} votes ---")
    
    display_graveyard(players)
    return players

def check_winner(players):
    living = [p for p in players if p.is_alive]
    singers = [p for p in living if p.alignment == "Singer"]
    regals = [p for p in living if p.alignment == "Regal"]
    shard = [p for p in living if p.role == "Shardbearer"]

    if shard and len(shard) >= (len(singers) + len(regals)):
        return "SHARDBEARER"
    if not regals and not shard:
        return "SINGERS"
    if len(regals) > len(singers) and not shard:
        return "REGALS"
    return None

# --- SETUP ---
players = [
    Player("Rudy", "Regal", "Direform"),
    Player("Lucias", "Singer", "Discerning"),
    Player("Jolene", "Singer", "Heroic"),
    Player("Mariana", "Singer", "Willful"),
    Player("Aiden", "Shardbearer", "Shardbearer"),
    Player("Bailey", "Singer", "Spren", radiant_target="Vivienne"),
    Player("Kai", "Singer", "Inspiring"),
    Player("Tadashi", "Regal", "Stormform"),
    Player("Abraham", "Singer", "Sneaky"),
    Player("Neil", "Regal", "Envoyform"),
    ]
p = {player.name: player for player in players}
p['Bailey'].radiant_target = p['Jolene']

# --- MAIN GAME LOOP ---
cycle = 1
while True:
    clear_screen()
    print(f"=== CYCLE {cycle} ===")
    
    # 1. REGAL TEAM ACTION (Kill/Convert)
    regals_alive = [r for r in players if r.alignment == 'Regal' and r.is_alive]
    if regals_alive:
        print("\n--- REGAL FACTION ACTION (TEAM) ---")
        while True:
            t_act = input("Action (kill/convert) or blank: ").strip().lower()
            if not t_act or t_act in ['kill', 'convert']: break
            print("Invalid faction action!")
        
        if t_act:
            t_target_name = input("Target Name: ").strip()
            t_target = p.get(t_target_name)
            for r in regals_alive:
                r.faction_action_type, r.faction_action_target = t_act, t_target

    # 2. INDIVIDUAL ROLE ACTIONS (RESTRICTED)
    for name, actor in p.items():
        if not actor.is_alive: continue
        
        allowed = ROLE_ABILITIES.get(actor.role, [])
        if not allowed:
            print(f"\n{name} ({actor.role}) has no active abilities.")
            continue

        print(f"\n{name} ({actor.role}) Turn:")
        print(f"Available abilities: {', '.join(allowed)}")
        
        while True:
            act = input("Enter ability or blank to skip: ").strip().lower()
            if not act or act in allowed:
                break
            print(f"Invalid! {name} can only use: {', '.join(allowed)}")
        
        if act:
            target_name = input(f"Target for {name}: ").strip()
            actor.role_action_type = act
            actor.role_action_target = p.get(target_name)

    clear_screen()
    players = simulate_cycle(players)
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
    
    input("\nPress Enter to begin Voting Phase...")
    clear_screen()
    print("=== VOTING PHASE ===")
    votes = {}
    for n, v in p.items():
        if v.is_alive:
            vote = input(f"{n}, vote for someone: ").strip()
            votes[n] = vote

    players = simulate_day_phase(players, {k: v for k, v in votes.items() if v in p})
    
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
        
    cycle += 1
    for player in players:
        player.role_action_type = player.role_action_target = None
        player.faction_action_type = player.faction_action_target = None
    input("\nPress Enter for next cycle...")

 

Edited by Akimikoisthecutest
Posted
17 minutes ago, Akimikoisthecutest said:

Uh so I made this little script with this rule set because I thought it would be kind of fun to see it. Uh this kind of works like a master spreadsheet might but it is probably buggy and inefficient. If any more experienced coders (*cough cough* @Usseewa @KaladinsSenseOfHumourSpren) want to give me feedback, that would be appreciated

 

I'll give some feedback tomorrow probably when I can, tho Ksauce may beat me to it.

 

Posted
7 hours ago, Akimikoisthecutest said:

Uh so I made this little script with this rule set because I thought it would be kind of fun to see it. Uh this kind of works like a master spreadsheet might but it is probably buggy and inefficient. If any more experienced coders (*cough cough* @Usseewa @KaladinsSenseOfHumourSpren) want to give me feedback, that would be appreciated

 

I've only taken a cursory look, but this looks really good!

You know you can do this, right?

import random
import os

def clear_screen():
    os.system('cls' if os.name == 'nt' else 'clear')

class Player:
    def __init__(self, name, alignment, role=None, radiant_target=None):
        self.name = name
        self.alignment = alignment
        self.role = role
        self.is_alive = True
        self.is_roleblocked = False
        self.protected = False
        self.role_action_type = None
        self.role_action_target = None
        self.faction_action_type = None
        self.faction_action_target = None
        self.radiant_target = radiant_target
        self.spren_prot_used = False

    def __repr__(self):
        status = "" if self.is_alive else "[DEAD] "
        return f"{status}{self.name} ({self.alignment} {self.role if self.role else ''})"

# --- ABILITY CONFIGURATION ---
# Defines exactly which roles can do which actions
ROLE_ABILITIES = {
    'Direform': ['block'],
    'Stormform': ['redirect'],
    'Envoyform': ['decrease'],
    'Inspiring': ['increase', 'block_conv'],
    'Heroic': ['protect'],
    'Spren': ['protect_radiant'],
    'Shardbearer': ['kill'],
    'Discerning': ['scan'],
    'Sneaky': ['sneaky'],
    'Willful': [] # Passive role
}

# --- GLOBAL GAME STATE ---
conversion_charges = 2

def display_graveyard(players):
    dead_players = [f"{p.name} ({p.alignment} {p.role})" for p in players if not p.is_alive]
    print("\n=== GRAVEYARD (ELIMINATED) ===")
    if dead_players:
        for entry in dead_players:
            print(f"• {entry}")
    else:
        print("No one has died yet.")
    print("==============================")

def simulate_cycle(players):
    global conversion_charges
    alive_at_start = [p.name for p in players if p.is_alive]
    
    for p in players:
        p.is_roleblocked = False
        p.protected = False

    # 1. Stormform Redirect
    for p in [x for x in players if x.role == 'Stormform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'redirect' and p.role_action_target:
            storm_target = p.role_action_target
            actors_to_redirect = [a for a in players if (a.role_action_target == storm_target or a.faction_action_target == storm_target) and a != p]
            for actor in actors_to_redirect:
                new_dest = random.choice([x for x in players if x != storm_target and x.is_alive])
                if actor.role_action_target == storm_target: actor.role_action_target = new_dest
                if actor.faction_action_target == storm_target: actor.faction_action_target = new_dest
                print(f"STORM: {p.name} redirected {actor.name} to {new_dest.name}!")

    # 2. Direform Roleblock
    for p in [x for x in players if x.role == 'Direform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'block' and p.role_action_target:
            p.role_action_target.is_roleblocked = True
            print(f"BLOCK: {p.name} (Direform) roleblocked {p.role_action_target.name}")

    # 4. Protections (Heroic & Spren)
    protected_list = [p.role_action_target for p in players if p.role == 'Heroic' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.role == 'Spren' and x.is_alive and not x.is_roleblocked]:
        if p.role_action_type == 'protect_radiant' and not p.spren_prot_used and p.role_action_target == p.radiant_target:
            protected_list.append(p.radiant_target)
            p.spren_prot_used = True
            print(f"SUCCESS: {p.name} (Spren) protected {p.radiant_target.name}!")

    # 5. Conversion (Regals)
    conv_blocked = [p.role_action_target for p in players if p.role == 'Inspiring' and p.role_action_type == 'block_conv' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.alignment == 'Regal' and x.faction_action_type == 'convert' and not x.is_roleblocked and x.is_alive]:
        if conversion_charges <= 0: continue
        target = p.faction_action_target
        if target and target.alignment.capitalize() == 'Singer' and target not in conv_blocked and target.role != 'Willful':
            target.alignment = 'Regal'
            conversion_charges -= 1
            print(f"CONVERSION: {target.name} has joined the Regals!")

    # 6. Kills
    targets_to_die = []
    for p in [x for x in players if (x.faction_action_type == 'kill' or x.role == 'Shardbearer') and not x.is_roleblocked and x.is_alive]:
        target = p.faction_action_target if p.alignment == 'Regal' else p.role_action_target
        if target and target not in protected_list:
            targets_to_die.append(target)
    
    for target in list(set(targets_to_die)):
        target.is_alive = False

    # 7. Spren Death Logic
    for s in [x for x in players if x.role == 'Spren' and x.is_alive]:
        if s.radiant_target and not s.radiant_target.is_alive:
            s.is_alive = False
            print(f"LOSS: {s.name}'s Radiant ({s.radiant_target.name}) died. {s.name} is eliminated.")

    print("\n--- NIGHT DEATH REPORT ---")
    deaths = [n for n in alive_at_start if not next(pl for pl in players if pl.name == n).is_alive]
    if deaths:
        for d in deaths: print(f"DIED: {d}")
    else:
        print("No one died tonight.")
    
    display_graveyard(players)
    return players

def simulate_day_phase(players, votes):
    tally = {p.name: 0 for p in players if p.is_alive}
    penalized = [p.role_action_target.name for p in players if p.role == 'Envoyform' and p.role_action_type == 'decrease' and not p.is_roleblocked and p.is_alive and p.role_action_target]
    buffed = [p.role_action_target.name for p in players if p.role == 'Inspiring' and p.role_action_type == 'increase' and not p.is_roleblocked and p.is_alive and p.role_action_target]

    for v_name, t_name in votes.items():
        if t_name in tally:
            weight = 1
            if v_name in penalized: weight = 0
            elif v_name in buffed: weight = 2
            tally[t_name] += weight

    if tally:
        victim_name = max(tally, key=tally.get)
        if tally[victim_name] > 0:
            victim = next(p for p in players if p.name == victim_name)
            victim.is_alive = False
            print(f"\n--- DAY PHASE: {victim_name} voted out with {tally[victim_name]} votes ---")
    
    display_graveyard(players)
    return players

def check_winner(players):
    living = [p for p in players if p.is_alive]
    singers = [p for p in living if p.alignment == "Singer"]
    regals = [p for p in living if p.alignment == "Regal"]
    shard = [p for p in living if p.role == "Shardbearer"]

    if shard and len(shard) >= (len(singers) + len(regals)):
        return "SHARDBEARER"
    if not regals and not shard:
        return "SINGERS"
    if len(regals) > len(singers) and not shard:
        return "REGALS"
    return None

# --- SETUP ---
players = [
    Player("Rudy", "Regal", "Direform"),
    Player("Lucias", "Singer", "Discerning"),
    Player("Jolene", "Singer", "Heroic"),
    Player("Mariana", "Singer", "Willful"),
    Player("Aiden", "Shardbearer", "Shardbearer"),
    Player("Bailey", "Singer", "Spren", radiant_target="Vivienne"),
    Player("Kai", "Singer", "Inspiring"),
    Player("Tadashi", "Regal", "Stormform"),
    Player("Abraham", "Singer", "Sneaky"),
    Player("Neil", "Regal", "Envoyform"),
    ]
p = {player.name: player for player in players}
p['Bailey'].radiant_target = p['Vivienne']

# --- MAIN GAME LOOP ---
cycle = 1
while True:
    clear_screen()
    print(f"=== CYCLE {cycle} ===")
    
    # 1. REGAL TEAM ACTION (Kill/Convert)
    regals_alive = [r for r in players if r.alignment == 'Regal' and r.is_alive]
    if regals_alive:
        print("\n--- REGAL FACTION ACTION (TEAM) ---")
        while True:
            t_act = input("Action (kill/convert) or blank: ").strip().lower()
            if not t_act or t_act in ['kill', 'convert']: break
            print("Invalid faction action!")
        
        if t_act:
            t_target_name = input("Target Name: ").strip()
            t_target = p.get(t_target_name)
            for r in regals_alive:
                r.faction_action_type, r.faction_action_target = t_act, t_target

    # 2. INDIVIDUAL ROLE ACTIONS (RESTRICTED)
    for name, actor in p.items():
        if not actor.is_alive: continue
        
        allowed = ROLE_ABILITIES.get(actor.role, [])
        if not allowed:
            print(f"\n{name} ({actor.role}) has no active abilities.")
            continue

        print(f"\n{name} ({actor.role}) Turn:")
        print(f"Available abilities: {', '.join(allowed)}")
        
        while True:
            act = input("Enter ability or blank to skip: ").strip().lower()
            if not act or act in allowed:
                break
            print(f"Invalid! {name} can only use: {', '.join(allowed)}")
        
        if act:
            target_name = input(f"Target for {name}: ").strip()
            actor.role_action_type = act
            actor.role_action_target = p.get(target_name)

    clear_screen()
    players = simulate_cycle(players)
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
    
    input("\nPress Enter to begin Voting Phase...")
    clear_screen()
    print("=== VOTING PHASE ===")
    votes = {}
    for n, v in p.items():
        if v.is_alive:
            vote = input(f"{n}, vote for someone: ").strip()
            votes[n] = vote

    players = simulate_day_phase(players, {k: v for k, v in votes.items() if v in p})
    
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
        
    cycle += 1
    for player in players:
        player.role_action_type = player.role_action_target = None
        player.faction_action_type = player.faction_action_target = None
    input("\nPress Enter for next cycle...")

There's a code option next to the spoiler box one.

You know, I might do something like this for the Tyrean ruleset

Who need spreadsheets when you have code 😎

Posted (edited)
12 hours ago, KaladinsSenseOfHumourSpren said:

I've only taken a cursory look, but this looks really good!

You know you can do this, right?

import random
import os

def clear_screen():
    os.system('cls' if os.name == 'nt' else 'clear')

class Player:
    def __init__(self, name, alignment, role=None, radiant_target=None):
        self.name = name
        self.alignment = alignment
        self.role = role
        self.is_alive = True
        self.is_roleblocked = False
        self.protected = False
        self.role_action_type = None
        self.role_action_target = None
        self.faction_action_type = None
        self.faction_action_target = None
        self.radiant_target = radiant_target
        self.spren_prot_used = False

    def __repr__(self):
        status = "" if self.is_alive else "[DEAD] "
        return f"{status}{self.name} ({self.alignment} {self.role if self.role else ''})"

# --- ABILITY CONFIGURATION ---
# Defines exactly which roles can do which actions
ROLE_ABILITIES = {
    'Direform': ['block'],
    'Stormform': ['redirect'],
    'Envoyform': ['decrease'],
    'Inspiring': ['increase', 'block_conv'],
    'Heroic': ['protect'],
    'Spren': ['protect_radiant'],
    'Shardbearer': ['kill'],
    'Discerning': ['scan'],
    'Sneaky': ['sneaky'],
    'Willful': [] # Passive role
}

# --- GLOBAL GAME STATE ---
conversion_charges = 2

def display_graveyard(players):
    dead_players = [f"{p.name} ({p.alignment} {p.role})" for p in players if not p.is_alive]
    print("\n=== GRAVEYARD (ELIMINATED) ===")
    if dead_players:
        for entry in dead_players:
            print(f"• {entry}")
    else:
        print("No one has died yet.")
    print("==============================")

def simulate_cycle(players):
    global conversion_charges
    alive_at_start = [p.name for p in players if p.is_alive]
    
    for p in players:
        p.is_roleblocked = False
        p.protected = False

    # 1. Stormform Redirect
    for p in [x for x in players if x.role == 'Stormform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'redirect' and p.role_action_target:
            storm_target = p.role_action_target
            actors_to_redirect = [a for a in players if (a.role_action_target == storm_target or a.faction_action_target == storm_target) and a != p]
            for actor in actors_to_redirect:
                new_dest = random.choice([x for x in players if x != storm_target and x.is_alive])
                if actor.role_action_target == storm_target: actor.role_action_target = new_dest
                if actor.faction_action_target == storm_target: actor.faction_action_target = new_dest
                print(f"STORM: {p.name} redirected {actor.name} to {new_dest.name}!")

    # 2. Direform Roleblock
    for p in [x for x in players if x.role == 'Direform' and not x.is_roleblocked and x.is_alive]:
        if p.role_action_type == 'block' and p.role_action_target:
            p.role_action_target.is_roleblocked = True
            print(f"BLOCK: {p.name} (Direform) roleblocked {p.role_action_target.name}")

    # 4. Protections (Heroic & Spren)
    protected_list = [p.role_action_target for p in players if p.role == 'Heroic' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.role == 'Spren' and x.is_alive and not x.is_roleblocked]:
        if p.role_action_type == 'protect_radiant' and not p.spren_prot_used and p.role_action_target == p.radiant_target:
            protected_list.append(p.radiant_target)
            p.spren_prot_used = True
            print(f"SUCCESS: {p.name} (Spren) protected {p.radiant_target.name}!")

    # 5. Conversion (Regals)
    conv_blocked = [p.role_action_target for p in players if p.role == 'Inspiring' and p.role_action_type == 'block_conv' and not p.is_roleblocked and p.is_alive]
    for p in [x for x in players if x.alignment == 'Regal' and x.faction_action_type == 'convert' and not x.is_roleblocked and x.is_alive]:
        if conversion_charges <= 0: continue
        target = p.faction_action_target
        if target and target.alignment.capitalize() == 'Singer' and target not in conv_blocked and target.role != 'Willful':
            target.alignment = 'Regal'
            conversion_charges -= 1
            print(f"CONVERSION: {target.name} has joined the Regals!")

    # 6. Kills
    targets_to_die = []
    for p in [x for x in players if (x.faction_action_type == 'kill' or x.role == 'Shardbearer') and not x.is_roleblocked and x.is_alive]:
        target = p.faction_action_target if p.alignment == 'Regal' else p.role_action_target
        if target and target not in protected_list:
            targets_to_die.append(target)
    
    for target in list(set(targets_to_die)):
        target.is_alive = False

    # 7. Spren Death Logic
    for s in [x for x in players if x.role == 'Spren' and x.is_alive]:
        if s.radiant_target and not s.radiant_target.is_alive:
            s.is_alive = False
            print(f"LOSS: {s.name}'s Radiant ({s.radiant_target.name}) died. {s.name} is eliminated.")

    print("\n--- NIGHT DEATH REPORT ---")
    deaths = [n for n in alive_at_start if not next(pl for pl in players if pl.name == n).is_alive]
    if deaths:
        for d in deaths: print(f"DIED: {d}")
    else:
        print("No one died tonight.")
    
    display_graveyard(players)
    return players

def simulate_day_phase(players, votes):
    tally = {p.name: 0 for p in players if p.is_alive}
    penalized = [p.role_action_target.name for p in players if p.role == 'Envoyform' and p.role_action_type == 'decrease' and not p.is_roleblocked and p.is_alive and p.role_action_target]
    buffed = [p.role_action_target.name for p in players if p.role == 'Inspiring' and p.role_action_type == 'increase' and not p.is_roleblocked and p.is_alive and p.role_action_target]

    for v_name, t_name in votes.items():
        if t_name in tally:
            weight = 1
            if v_name in penalized: weight = 0
            elif v_name in buffed: weight = 2
            tally[t_name] += weight

    if tally:
        victim_name = max(tally, key=tally.get)
        if tally[victim_name] > 0:
            victim = next(p for p in players if p.name == victim_name)
            victim.is_alive = False
            print(f"\n--- DAY PHASE: {victim_name} voted out with {tally[victim_name]} votes ---")
    
    display_graveyard(players)
    return players

def check_winner(players):
    living = [p for p in players if p.is_alive]
    singers = [p for p in living if p.alignment == "Singer"]
    regals = [p for p in living if p.alignment == "Regal"]
    shard = [p for p in living if p.role == "Shardbearer"]

    if shard and len(shard) >= (len(singers) + len(regals)):
        return "SHARDBEARER"
    if not regals and not shard:
        return "SINGERS"
    if len(regals) > len(singers) and not shard:
        return "REGALS"
    return None

# --- SETUP ---
players = [
    Player("Rudy", "Regal", "Direform"),
    Player("Lucias", "Singer", "Discerning"),
    Player("Jolene", "Singer", "Heroic"),
    Player("Mariana", "Singer", "Willful"),
    Player("Aiden", "Shardbearer", "Shardbearer"),
    Player("Bailey", "Singer", "Spren", radiant_target="Vivienne"),
    Player("Kai", "Singer", "Inspiring"),
    Player("Tadashi", "Regal", "Stormform"),
    Player("Abraham", "Singer", "Sneaky"),
    Player("Neil", "Regal", "Envoyform"),
    ]
p = {player.name: player for player in players}
p['Bailey'].radiant_target = p['Vivienne']

# --- MAIN GAME LOOP ---
cycle = 1
while True:
    clear_screen()
    print(f"=== CYCLE {cycle} ===")
    
    # 1. REGAL TEAM ACTION (Kill/Convert)
    regals_alive = [r for r in players if r.alignment == 'Regal' and r.is_alive]
    if regals_alive:
        print("\n--- REGAL FACTION ACTION (TEAM) ---")
        while True:
            t_act = input("Action (kill/convert) or blank: ").strip().lower()
            if not t_act or t_act in ['kill', 'convert']: break
            print("Invalid faction action!")
        
        if t_act:
            t_target_name = input("Target Name: ").strip()
            t_target = p.get(t_target_name)
            for r in regals_alive:
                r.faction_action_type, r.faction_action_target = t_act, t_target

    # 2. INDIVIDUAL ROLE ACTIONS (RESTRICTED)
    for name, actor in p.items():
        if not actor.is_alive: continue
        
        allowed = ROLE_ABILITIES.get(actor.role, [])
        if not allowed:
            print(f"\n{name} ({actor.role}) has no active abilities.")
            continue

        print(f"\n{name} ({actor.role}) Turn:")
        print(f"Available abilities: {', '.join(allowed)}")
        
        while True:
            act = input("Enter ability or blank to skip: ").strip().lower()
            if not act or act in allowed:
                break
            print(f"Invalid! {name} can only use: {', '.join(allowed)}")
        
        if act:
            target_name = input(f"Target for {name}: ").strip()
            actor.role_action_type = act
            actor.role_action_target = p.get(target_name)

    clear_screen()
    players = simulate_cycle(players)
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
    
    input("\nPress Enter to begin Voting Phase...")
    clear_screen()
    print("=== VOTING PHASE ===")
    votes = {}
    for n, v in p.items():
        if v.is_alive:
            vote = input(f"{n}, vote for someone: ").strip()
            votes[n] = vote

    players = simulate_day_phase(players, {k: v for k, v in votes.items() if v in p})
    
    winner = check_winner(players)
    if winner:
        print(f"\n*** {winner} WIN! ***")
        break
        
    cycle += 1
    for player in players:
        player.role_action_type = player.role_action_target = None
        player.faction_action_type = player.faction_action_target = None
    input("\nPress Enter for next cycle...")

There's a code option next to the spoiler box one.

You know, I might do something like this for the Tyrean ruleset

Who need spreadsheets when you have code 😎

You could maybe look at the SE bot I made with Python, did do the Tyrian rules

Edited by Amanuensis
Posted
8 hours ago, Amanuensis said:

https://github.com/MahaloWell/SEBOT

I might not have pushed the most recent version (I forget if I'd edited it since, been mostly focused on my RPG since that brief Python stint) but I can check when I get home

Wow this is beyond my Python skill

Is this fully automatic? Like does it need the GM to say what actions players are taking?

Posted (edited)
18 hours ago, KaladinsSenseOfHumourSpren said:

Wow this is beyond my Python skill

Is this fully automatic? Like does it need the GM to say what actions players are taking?

Yeah so for the most part it's a fully automatic discord bot but there's an option for GMs to be more hands on with it for custom rules. Eventually we should be trying to run a game on discord to see how it plays out

Edited by Amanuensis

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...