I got bored, so I'm trying to write a Java program that can reasonably recreate most of the shenanigans that happen in a BotC game. I'm putting it here because [it's clogging the text game general chat](#text-game-general message) and people seem interested in it, apparently? I have some funny ideas for how to use this program for an eventual text game I want to storytell, maybe in August when my birthday rolls around. We'll see.
#A Storyteller Drinks Java
141 messages · Page 1 of 1 (latest)
@keen spade Would you be willing to help me some more with this, or...
Something I'm wondering is how I make roles interact with ST consults, if at all.
Since all interactions are effectively forced to go through the Storyteller, it doesn't make that much sense for the role to have a function that just immediately diverts to some ST.prompt(...) function. But then there's the Amnesiac ability which gets information that must fit into some category.
I can try to assist if needed for specific questions.
Eww... inheritence < delegation
A bit busy in general IRL and with running a game as is, but can answer questions when they come up
Bruh, why does it seem like everyone and their mom is a CS major.
I was kinda expecting to get booed out for randomly bringing this up, yet it seems like everyone I've talked to has contributed.
now that's unfair
I'm only a CS minor
that said, the specific kind of person that plays a social deduction game via discord text is likely to also be pretty familiar with computers at some level
Tho has summoned the who unspoken (java)
I think I did the thing.
I'm a mathematics major, currently a Ph.D student. Have done programming work for my research though.
You could use record for seat lol
Matron. Pit Hag. Barber.
Well, technically Matron moves seats around, but Pit Hag changes the underlying role while not changing the player nor their alignment.
Regarding this -- should I make it so that the basic Role data is separated from, say, a EachNight or OnTrigger interface?
I think its a fun idea to have those as interfaces, though problem could be that its "too magical".
Also i think the defaultAlignment is a weird property. Spy misreg only allowed for good, TF and outsiders. While recluse demon evil minion
Well, it's the starting alignment of the role. It can change for things like Cult Leader, Mezepheles, or Goon.
I'm not sure of a better way to handle Misregistration.
"Can misregister as"?
Also misregistration could trigger math
So maybe the decision needs to be somewhere else
Maybe the grim as a whole stores the math number.
The real prize here is a program that can ST Quantum clocktower, since thats the only game mode with absolutely no ST agency, so a program is a strictly better ST than a human 😉
Well technically the ST does tiebreakers in quantum. Though nothing too bad for a computer
Regular Quantum Text Games run by a computer would be dope tho
The rules mandate that tiebreaks be random, its not discretionary
Another weird thing: do reminders bind to the player, the role, or to the seat?
If the Matron swaps the spent professor away from the Tea lady, for example, then the professor "no ability" token should move, but the "can't die" token should not.
But if, say, the Philosopher is Pit Hagged into the Sweetheart, then the Philosopher's "Drunk" token should be removed, but that token can be anywhere on the grim.
I'm kinda thinking I put them on both the Role and the player, as a 2-way link.
That way, if the Role is removed, then it can tell the other seats to remove the relevant tokens, and if the player is moved, it can tell the role to check if it needs to update who has what tokens.
Possibly a remindertokenholder
Oh, I see.
Since I'd probably end passing the entire grim as a parameter to a "check the reminders" function, I might as well store them in a single location.
This seems like a decent final project for a Data Structures course.
Or maybe Data management. I'm not taking either course anytime soon, though.
Actually, no, at my uni it would probably be CS421 - Principles of Database System Implementation. This is literally it.
Once I figure out a foolproof method to store and access data in all the special cases, then implementing all of the roles becomes easy.
Which is basically like saying "once I code in all the special cases, then the general case is easy".
Where can I see the rules for how Quantum BotC works? I could try working on such a program sometime in the future.
Perhaps you would get more feedback if you put your code on GitHub or some other shared repository and then we could take a look at it?
I would personally not include reminder tokens in the state unless it is necessary to generate the game state for a Spy or Widow and in that case, the reminder token should be an attribute of the player since Matron allows players to switch seats.
Player should probably have at least the following attributes:
Name
Character
Alignment
Voting History
Nomination History
Dead/Alive State
Sober/Poisoned state
etc ... etc...
Quantum Mechanics 
Law 1: All players always exist in a superposition of all characters and states in which they could possibly be.
Law 2: Players tell the Storyteller their choices and information each night, which become true unless mechanically impossible. Players learn when they make an impossible choice, and must change that choice. If mutually exclusive events would occur simultaneously, one occurs at random.
Law 3: Players receive specific prompts if and only if their superposition has collapsed to only contain states where they receive that specific prompt.
Law 4: Players appear to die if and only if their superposition has collapsed to only contain states where they have died.
Law 5: Players can nominate and vote without consuming their vote token if and only if they appear alive.
Law 6: The game proceeds in strict night order, but if a player's choice is impossible, they make a new choice for the entire night (ideally submitting multiple possibilities at once, processed in order submitted), so they may submit a choice that will happen before their first attempt would have happened in the night.
Law 7: Starting alignment will be rolled randomly. After that, starting evils (if any) will learn each other and then submit who they learn as demon/minions within that team when they submit N1 actions. Evil starting information happens simultaneously, at the very start of N1.
Law 8: All game rules are always followed, and the setup is always valid.
Law 9: The game continues if possible, ending if and only if its superposition has collapsed to only contain states where it ends.
Law 10: When the game ends, good wins ties within a possible game state (per normal BotC rules; example is executing the demon at final 3). Evil wins ties between possible game states (unique Quantum rule; example is executing the superpositioned Lleech/Pukka at final 3).
implementing quantum gossip is left as an exercise for the reader
Gossips are required to be in the form of valid if statements using variables that can be evaluated by the ST
"It is the opinion of the ST that good is winning"
< Crash >
RAW, gossips must be definite statements, so "good is winning" shouldn't be allowed anyway 😉
“in the current gamestate, a general would get a ‘good’”
thats still indefinite as a general can get whatever the ST subjectively thinks
General can get indefinite info but gossip can't
no, Gossips have to be lua scripts
this is the funniest thing I've read on the server in a long time thanks for making me chuckle
hey same (I have never touched java though)
Okay, so I've been thinking about it a bit more, and I think the best method to deal with reminder shenanigans would be to have a Set of "in play" tokens derived from the roles in play.
The in play tokens may or may not be on the grim, but only those tokens can be on any given player.
Then, I have a list of sets of tokens (List<Set<Reminder>>) indicating which reminders (if any) are on a given player.
Or, I can add a Set<Reminder> property to each Seat.
So then, if the Fortune Teller picks Sterling and De, I can then check Seats 6 and 9, see that the FortuneTeller.RED_HERRING Token is in Seat 6, and return true.
Why have a List<Set<Reminder>> tho
I'm realizing that. I'm instead folding it into the Seats' information.
Not sure how to delineate the tokens. Strings don't have enough functionality.
I should probably just focus on implementing something like TB. I keep thinking of niche things like how to deal with the Fang Gu "once" reminder if it leaves and re-enters play. Maybe bind a persistent token to the ST?
not a bad idea, I've seen people run the Fang Gu "once" token and the Plague Doctor's ST Minion ability in the same way, putting it in the center of the grim
making the ST the store of any global traits isn't a bad idea when throwing things together
have fun implementing every recluse yes but dont
A lot of them come for free.
Well, the ST is prompted every time a decision involving misinfo comes up.
"The Chef should get a 0, but is poisoned. What value will they be shown?"
"The Recluse neighbors the Poisoner. Should that increase the Chef number?"
The hard part at that point is determining how many times the Mathematician should be pinged.
There will probably be a config thing that makes pedantically stupid "yes but don't" scenarios be ignored in favor of the obvious conclusion, like the recluse learning who the minions are.
You can use an interface and enum implement it. Then you dont need a string lil
That interface would need to be updated every time a new token is added.
Wait... uh... enums and interfaces?
You can define an interface as a type and use enums to have only one instance of the drunk reminder token
so tempted to make a c++ version of this thread
It’d be harder to make a punny name though
Storytellers_c_all
I'm watching a thing on Dependency Injection; I feel like this isn't what I'm looking for.
Try using the attachment service at https://www.patreon.com/codeaesthetic
You'll also find deleted scenes, song names and more
Maybe I could do a thing where I can "summarize" the data on a Seat. Instead of asking for a bunch of properties, I ask the Seat to generate an Ad-Hoc summary of its properties, which the Role requesting the data can deal with.
So something like:
PlayerData {
trueRole: Recluse
trueType: Outsider
trueAlignment: Good
apparentRoles: [Recluse, Poisoner, Spy, Baron, Scarlet_Woman, Imp]
apparentTypes: [Outsider, Minion, Demon]
apparentAlignments: [Good, Evil]
reminders: [Undertaker -- Executed]
}
Then, the undertaker can see this PlayerData, and the program can prompt the storyteller about whether the Recluse registers as the Recluse, or as one of the evil TB roles.
Then, when the Bootlegger's Almanac is ported to this, implementing Huli Jing isn't a nightmare.
Anyways, scuffed FT implementation.
Okay, I've reached a roadblock: Arbitrary class instantiation.
Somewhere in memory, there needs to be a list containing all of the Role classes that can be instantiated. But instnatiating is hard, or at the very least not recommended. I can't really hardcode it for extensibility.
Because it'd need to be something like a HashMap<String, Class<? extends Role>> where I can then run allRoles.get("FortuneTeller").newInstance(). But it seems... Janky.
This would be so easy in Python... this is so scuffed.
I think this looks cleaner. Thoughts?
idk what’s going on in your PlayerData class, but it seems more intuitive to have a getAlignment() method and have things like recluse/spy override it?
rather than mustAppearEvil and canAppearEvil
the ST discretion can happen in the recluse/spy classes instead of in the chef’s which saves you rewriting some code
ideally an ST sees the call stack when prompted so knows that this is a chef check and not, say, an empath
The problem is that if, if that happens multiple times for the chef, then the math number may not be updated correctly.
The ST will be prompted with context as to what's going on when they are asked for a decision.
What happens when there's a Mez-turned Recluse? They always register as evil. The alignment can't be bound to the role, because if the recluse is then Pit Hagged then the new role might be marked as good. Or what happens if a good player is turned into the Baron or something?
Factory Pattern or just reflection as you said
- alignment as a field - for characters that can’t misregister just return whatever that is
- for evil recluse/good spy I imagine you can just check if actual alignment is evil/good and return that first
but you’ve clearly thought this through more than I have
so if I’m spouting nonsense then feel free to ignore :P
aaah I see the problem with what I’m proposing - a Player has-a character and has-a alignment and it’s messy for a character to know which player has it (to pull alignment)
Evil Cult leader, for example.
I'm trying to design around every contingency so writing out every role becomes as straightforward as possible.
A Storyteller explores the C
Although I still feel like my code is dogwater.
forgot that the word for running code is executing so I thought I got clocktower jumpscared

Oh wow, that's such a cool idea, I might take it up.
It would be an ST assist tool
i am working on a tool for quantum
Gonna be honest, I've hit a major roadblock with roles: how can I easily add all the roles and their abilities, and how can another user easily add new roles?
Like, most of the data, such as what type a character is, can be imported via JSON. Once I figure out how.
But how does the night action work? How would you encode, say, a Fortune Teller? Sure, you can add that a FT picks 2 players, learns if either is a demon, and has a persistent player-dependent "red herring" reminder, but how would you tell the program that the player who is the red herring must be good, and must register as the demon to the Fortune teller?
What is preventing you from having logic in your program for the abilities?
I think for FT for example you can modify your application state to add the player to the red herring variable and then add logic for how the program interacts with that player.
@misty sky I'd be interested to hear how you fare in general - I am currently doing a 'research' project for a university course looking at using model checking/SMT solving for Uncertainty principle (to build a ST helper tool). I decided to take this route because under the hood, we're just looking at a bunch of temporal logic constraints and ideally the model will be able to give back a minimal core of actions made by players that are logically viable - thus telling the storyteller which person/s they need to randomly select to pick again because their choice is logically impossible.
I've only got 14 weeks within the course to make some sort of implementation and write a report, so we'll see how far I get..
Have you tried simulating a minimal set of characters or are you still building the framework with every possible character in mind?
This has piqued my interest. I almost feel as though reminder tokens would need to be a field on all 3 of Seat, Role, and Player. All 3 may need their own unique hasReminder method, but then one of Grimoire or Seat would also have a "global" hasReminder method that checks an aggregated list of all reminders for the relevant Seat, Role, and Player.
public class Grimoire {
public bool seatHasReminder(Seat seat, Reminder reminder) {
return seat.hasReminder(reminder) || getPlayer(seat).hasReminder(reminder) || getRole(seat).hasReminder(reminder);
}
}
As for adding new roles, I expect that would always have to be manual. For the FT example, the nightAction method handles the Red Herring misregistration, and the Role class can have a setup method can be used to perform setup actions such as FT Red Herring or WW Townsfolk/Wrong reminder tokens during the setup phase of the game:
public class FortuneTeller extends Role {
public void setup(Grimoire grim, int playerId) {
int choiceId = Prompt.getNumber("Pick a player to be the Fortune Teller's Red Herring", 0, grim.playerCount());
PlayerData data = grim.getSeat(choiceId).data(grim);
while (data.getAlignment() != Alignment.Good) {
choiceId = Prompt.getNumber("The Red Herring must be on a Good player", 0, grim.playerCount());
}
data.addReminder(RED_HERRING);
}
}
Excuse my code if it's non-idiomatic or non-compiling 🙂 I'm a C# guy
Oh
I decided that, instead of making potentially a hundred subclasses of Role, I would instead dependency inject the EachNight function using a factory and storage class.
The problem is allowing users to add new roles without needing to add code and recompile.
I think I could create a shitty programming language (eg: "ClockScript") that could abstract the logic, but that would take a while, and I don't know much about Programming Language design.
So, instead, I'm gonna just hardcode all the logic for the base 3 scripts.
It seems that BMR is gonna be the easiest script to implement. TB has misregistration, which is a can of worms, and SnV has the Savant, which... ugh, and the Mathematician.
The problem is that BMR has the Minstrel. I'm not sure how to deal with "global" events. Maybe I could add a system where the grim queries all other players to see if they have any influence on a player's info. Then I can compute the barista-correct info, and then prompt the ST to see if they want/need to come up with something different.
Gonna be honest -- I have no clue what SMT is. Most of the stuff done by this program is gonna be either wholly automated because of how mechanics are typically immutable, or will defer completely to the ST for choosing, say, if the recluse misregisters.
Anyways, my first thing is trying to implement the setup. The problem is that setup modification is annoying.
For example: The drunk. In a 12-player game of TB, there are 2 outsiders and 7 townsfolk. With a drunk, you have to put in 8 townsfolk and 1 outsider, and retroactively make a player drunk. I'm not sure how to represent it.
Maybe something like:
setup_modifier: {
"requires": ["King"], // choirboy
"jinxed": ["Spy", "Widow"], // Heretic
"bagged": false // Drunk
"OUTSIDER": [1, 0, -1], // Sentinel
"MINION": [1], // Lil' Monsta
"DEMON": [Infinity], // Riot
}
in the JSON I'm making.
Actually... this can't represent "most" for Legion... I could lazily put [5,6,7...], but 5 with 15 is invalid.
That’s a good point.
SMT is a solving engine - can tell you if a world is satisfiable or not - is it logically possible for all constraints to be true.
I am implementing a specific quantum script because my focus is on quantum and a bunch of characters break quantum Clocktower (e.g. savant, pit-hag, spy).
Can you define demon numbers for riot/legion as a function of the number of players?
This is JSON being parsed by Java. You can't put functions in it.
You could have it parse enums instead of raw numbers and then process them after parsing
So I put in "HALF" and have it parse that?