Tycoon, a card game

Last updated:

A fun added aspect of Persona 5 Royal edition is the card game "Tycoon", which you can play against the AI. It's also a perfectly reasonable real-life card game, playable with a 52 or 54 card deck. The precise rules aren't very well written anywhere, tho, and I'm regularly forgetting small details, so I'll compile here the complete Tycoon rules as I know them.

(Note: As she is played in P5R, Tycoon uses the card ordering 3...KA2, which is kinda weird. In these rules I've reverted to the more standard 2...KA order, and adjusted things accordingly.)

Setup

Tycoon is a card game for 4 players, played with a standard 52 or 54 (2 jokers) card deck, with capitalist and revolutionary themes. It is a simple trick-taking game with a few fun twists, most notably a cross-round mechanic that depends on how well you did the previous round. It can be played for score across a finite number of rounds, or indefinitely for fun.

To start, deal out all cards equally to players, so everyone has a 13-card hand. (If using jokers, two random players will start with a 14-card hand; it doesn't matter which.)

On the first round, whoever has the 2 of Hearts reveals it, and starts the first hand. (They do not need to play the 2 of Hearts in that starting hand.)

Play

A round of Tycoon is played for a variable number of hands, until all but one player is out of cards.

Whoever starts the hand lays down 1-4 cards of a single value (for example, 555, a triple) in the center of the table. The next player in clockwise order either lays down the same number of cards with higher value (for example, QQQ, another triple), or passes for the rest of the hand. (Value is standard 2-KA order.) Play continues until three players have passed, at which point the last person to have laid down cards wins the hand, and starts the next. If the winner of the hand is no longer in the game (because they played all their cards), the next player in clockwise order starts the next hand.

Special card rules:

  • If jokers are being used, they can be treated as "wild" to increase the size of a hand (99X for a triple 9, for example), or played by themselves as a single or double (if the hand is currently a single or double). If played by themselves, jokers beat all other hands. The 2 of Spades, however, can be played to beat a single joker. When played in this way, nothing else can beat the 2 of Spades.
  • 7s are "lucky" - playing 7s immediately wins the hand and lets you start the next.
  • Four-of-a-kind triggers a Revolution. For the rest of the round, card values are inverted, so low beats high. (Jokers still beat everything, and 2 of Spades still beats a single Joker.) A second four-of-a-kind triggers a Counter-Revolution, restoring card values to their original value; further revolutions continue flip-flopping values.

End of Round

The round ends when only one player has cards left. Whoever played all their cards first is the Tycoon of the round; second place is the Rich Man, third is the Poor Man, and the final player is the Beggar. If playing for points, players score 3/2/1/0 depending on position.

On subsequent rounds, your position in the previous round matters.

  • If using Jokers, the Tycoon and Rich Man get 14 cards.
  • After dealing, the Tycoon and Beggar trade 2 cards, and the Rich Man and Poor Man trade a single card. The Beggar and Poor Man must give their highest value cards, with Jokers being the highest value. (This is taken on faith, but can be verified during the round.) The Tycoon and Rich Man can give cards of their choice (usually their lowest cards, unless they're a useful set).
  • The Beggar starts the first hand of the round.
  • The Tycoon, with all their advantages, must continue to empty their hand first to maintain their position. If someone else empties their hand before them, the Tycoon "goes bankrupt", immediately losing the round, discarding their remaining cards, and becoming the Beggar; the remaining two players continue playing to determine who is Rich and who is Poor.

End of Game

You can play for a predetermined number of rounds and declare a winner based on points, or just play without points until you're tired of it.

Three-player Variant?

I've never tried this, but I think the game would work as a 3p variant as well. I'd be interested to hear people's experiences with this.

  • You still deal out the entire deck (either 18/17/17 for 52, or 18/18/18 for 54).
  • The three positions are Tycoon, Beggar, and Bourgeoisie. The Tycoon and Beggar continue to trade two cards between rounds; the Bourgeoisie does nothing.
  • When a Tycoon goes bankrupt (someone else empties their hand first), they don't immediately discard their hand and become the Beggar. Instead, the remaining player gives them a card of their choice, and play continues to determine second place.

Otherwise the game plays as normal.

Houserules in my D&D Games, Part 2

Last updated:

A few years back I laid out some houserules I was planning to use for an upcoming D&D campaign. That campaign finished up, and I'm thinking about running another, so I've spent some time thinking over what I liked and disliked with what I did, and what I'd tweak further.

I'll start with my current plans, then go over my evaluations and reasoning later.

Carried Over From Last Time

First, Flexibility, Unearthed Arcana, and The Wagon are still in play from my previous post.

Justification

The first two are just good sense. We didn't actually use The Wagon much, but that's fine, it just means mundane items didn't end up mattering much, and we didn't need to worry about them. Overall low impact and good for players.

New Rest System

  • Short rests take 10 minutes, and are otherwise unchanged.
  • "Field rests" are an 8-hour rest (generally sleeping) when you're in a dangerous/stressful place (aka out on an adventure). They're identical to short rests, except you also regain HP equal to your level (without having to spend hit dice), and let you regain one spell slot or one use of a long-rest ability.
  • Long rests are as normal, but can only be taken in a safe place where you can properly relax, like a safe inn in a town where you don't have any reason to fear attack. "Magic camping" like Leomund's Tiny Hut or Rope Trick don't count; they give field rests.

Justification

Still trying to find the right balance of how people actually play (1-2 encounters a day, usually) versus how the rules assume people play (5-8 encounters a day), which is just nonsense; real-world play effectively gives long-rest classes (aka spellcasters) a massive buff since they can unload all their resources in most battles.

Making short rests shorter just makes them easier to squeeze in, narratively, without significantly impacting how often they can be used. It's actually totally fine for people to short rest between every battle, honestly, if they can swing it, and overusing a "shorter" short rest is better than not using short rests at all because pausing the action for an hour is hard to justify.

Previously my long rests were a downtime activity, aka several days of rest between adventures. This just proved narratively binding, bc I couldn't give my players a long rest where an adventure did expect them to receive one. Limiting to "safety and relaxation" gives a lot more flexibility while still avoiding the major problem of "fully rested between every encounter on the road" that I was trying to fix.

Field rest is me simplifying my previous rest and HP rules. First, using the Wizard's spell-recovery mechanic just turned out to be a little too complex for people who weren't actively playing wizards; switching to a short and sweet "regain one slot" should hopefully fix that up. It then chains nicely with "or one long-rest ability use". The power level of this regained thing can vary wildly, but I'm okay with that. Really I just want to give players some ability to recover from a harsh fight that drained more resources than expected, besides abandoning the dungeon or whatever. Hopefully this hits well.

Group Initiative + Init Rewards

When combat starts, all players roll initiative against the enemy's leader (or any appropriate enemy). If at least half win the opposed roll, the party gets first turn; otherwise enemies do. All party members (or all enemies) can act in any order during their turn.

Additionally, any player who wins their initiative roll gets to apply advantage or disadvantage to all rolls during their first round, whether the party won or lost the group roll.

Justification

Fast/slow initiative was okay, but ended up with a few problems. Always letting players go first had a larger impact than I'd hoped, and took a lot of tension out of the start of combat - you were always guaranteed you could get set up on that first turn (barring a legit Surprise). Also, the decision of fast vs slow caused a little more analysis-paralysis than I anticipated. Overall, it was just slightly too much downside for not quite enough benefit.

Group initiative is still a hit, tho. The flexibility is great in setting up combos, and it's just plain easier than the slog of calling out initiatives and figuring out order.

Also, fast/slow made the Init stat completely irrelevant, but there's just enough stuff that interacts with it in the rules that this was a little annoying. Bringing it back in approximately its normal role just means less edits.

But also, Init in RAW is pretty much irrelevant. All a high Init does is slightly predispose you to get your first action before an enemy's first action, but after that first round it has zero effect, and the variability of the d20 combined with how often you roll it (much less than attacks/saves) means in practice it had no real noticeable effect.

Changing it to a group roll with a pass/fail condition rather than individually graded should help a lot with this. You don't feel bad when the -1 Init heavy-armor fighter rolls higher than you; everyone who succeeds gets the same benefit, and helps the entire group. And when your +4 bonus does roll high (as it'ls do 25% more often than the -1), you'll get a noticeable benefit out of it rather than just a trivial scheduling benefit.

Attacks and Cantrips Auto-Hit, + Damage Crits

The biggest change so far: if you take the Attack action, or cast a cantrip, you hit automatically. (Or if a cantrip uses a saving throw, the enemy fails automatically.)

Additional effects on the attack (like a saving throw from a Battlemaster technique) are still rolled as normal. Non-Attack actions and levelled spells also roll as normal.

Instead of crits from rolling 20s, damage dice from an auto-hit explode: any die that rolls max gets rolled again, adding to the result, and this can happen multiple times. This applies to all dice from the attack, including things like Smite or Sneak Attack.

When something additional happens on a crit (orc ability, sword of sharpness, etc), it now triggers if the weapon's damage dice sum to 15 or more (after exploding). This does not include stat bonuses, additional dice, etc., just the core weapon's die. (This is subject to change.)

The Champion's ability to crit on 19-20 becomes that your dice now explode on one additional number - d4 crits on 3 or 4, d12 on 11 or 12, etc. (This is definitely subject to change, I don't think I'm happy with its results.)

Justification

A regular complaint from players, especially melee-ers, is that it feels very bad to have a "wasted" turn when your attacks all whiff - especially before 5th level when you've only got one attack, but it continues to happen a good amount of the time at higher levels too. Spellcasters are often hitting multiple people, and dealing half damage even when they fail, so they always feel like they're "contributing", but too often a melee class can feel like they did nothing in a round due to bad rolls.

So hey, let's fix that. The bread and butter of melee classes Just Works now. Not only can you always contribute at least some damage, but your crits feel a bit better too (always sucks to roll a crit and then, like, roll a 1 and a 2 on the damage dice). This also effectively buffs these classes' damage output by 50%-100% relative to spellcasters, which is just as much a good thing here as it is in the Rest rules.

(Cantrips fall into the same "feel bad" camp - when you're spending your turn being conservative with a cantrip rather than using a spell slot, it feels just as bad when you miss that and feel like you did nothing for the round.)

I'll compensate for the buffs on the DM side by putting a little extra HP on monsters, which is extremely easy for me to do on the fly.

The only real problem for me is that exploding-dice massively favor 2d4/2d6 weapons over their 1d8/1d12 equivalents. The math already favors them slightly in the normal rules (2d6 averages 7 damage, while 1d12 averages 6.5), but the gulf widens even more here. I'll see how I feel about this in practice; I know most players don't spend time writing a dice-simulation library to run numbers on these sorts of things, so probably it'll just not matter to them. ^_^

Best Potionomics Deck I've Found

Last updated:

Potionomics is a really fun game I've been following for a while, that just recently finally released. You play a potion-maker, crafting and selling potions, sending heroes out on adventures (buffed by your potions) to gather rare potion ingredients, and befriend and romance a bunch of adorable NPCs. I love it! The writing is so good!

Anyway, a big part of the mechanics is selling your potions, or promoting them to a judge during the potion competitions. This is done with a very fun little card-battling subsystem, where you deal "interest" to the customer to raise your potion's price, avoid using up all of their "patience", and they deal "stress" back to you in return.

Your starting deck sucks, and you get better cards from befriending NPCs. After some experimentation and reading, I think I have hit on the optimal deck, tho. It's based on Chorus, card draw, and cost reduction, and can almost always fully max a customer's interest by turn 2 or 3 at the latest, while keeping your stress minimal.

Basic Deck

You can swap some cards for better ones later, but these are required to get the deck working. It can be fully built on Day 21 at the earliest, just after you recruit Corsac and have his first card.

  • Xid Rank 6, for Jingle, Chorus, Improv, and Rhythm
  • Saffron Rank 5, for Meditate and Deep Connection
  • Quinn Rank 5, for Press The Attack and Pressure
  • Baptiste Rank 1, for Captivate
  • Corsac Rank 1, for Ferocity of the Squirrel
  • Owl Rank 2, for Scheme and Two Is Better Than One

The decklist is:

  • Jingle x2
  • Chorus x4
  • Improv x2
  • Rhythm x2
  • Meditate x2
  • Deep Connection x1
  • Press The Attack x1
  • Pressure x1
  • Captivate x1
  • Ferocity of the Squirrel x1
  • Scheme x2
  • Two Is Better Than One x1

Deck Strategy

The powerhouse of this Deck is Chorus - you'll play it 5-6 times per negotiation. All your card draw is to enable you to draw more Choruses. Improv and Scheme are your major card draw; Quinn's draw cards are last-ditch but can occasionally save your negotiation.

Ferocity is very important for the additional interest - you'll play 10-15 cards per negotiation, so this is worth 20-30 extra interest for free, as much as an additional Chorus! And remember that the effect lasts the entire haggle session, carrying over to later customers, so you don't need to cast it again (unless you have patience to spare or it's free, to get the 2 interest from playing a card).

Two Is Better Than One is used to clone Improv and Chorus two or three times each. Remember that the cloned cards also last the entire haggle session, so some early plays will improve your deck for all later customers. If you've filled up on Improv/Chorus or just don't have them in your hand, feel free to clone Jingle or Captivate, or Meditate if your stress is getting worrisome. Don't clone too much, tho - you don't want your deck hitting more than 27 or 28 cards total or it'll choke up your draws.

Jingle is a surprise key card - not only will it reduce Scheme or Rhythm to 0 cost, but it can actually reduce 1 or 0-cost cards to negative cost, making them add patience to the customer! (You'll see this in the UI as something like "--2", meaning it's adding 2 patience.) In other words, it's equivalent to a non-opener Captivate, or if Rhythm is active, to an opener Captivate!

Rhythm is important for kicking the deck into long-combo mode on turn 2 or 3, as it makes most of your deck free. If you have a Rhythm in your hand, it can be useful to play it and any Choruses you're holding, then just end your turn immediately - your next turn will be far more productive instead. Just make sure you'll have 2 or 3 patience at the start of your next turn, so you can play a Scheme.

Captivate's use is obvious, and Deep Connection is similar. You don't always need to cast Deep Connection, but burning thru a hand for the free Ferocity interest and making it all back from Deep Connection can be very useful.

Finally, managing stress is very important - you need all the cards you draw to keep this deck going, so having them replaced by Stress Cards can kill a negotiation. (Plus they prevent you from using Deep Connection!) Play Meditate whenever you can, and prefer ending a negotiation early if the customer is threatening to send you past 10% stress. That 10-20% stress interval is the Danger Zone, as even mildly unlucky RNG can suddenly put three or more stress cards in your hand and bump your stress even higher.

Perfected Deck

Making the deck perfect just requires spending more time with Saffron (you were gonna do that anyway for her coupons).

At rank 7, you earn Regulated Breathing, which should immediately replace Meditate. Like Ferocity, this buff lasts the entire haggle session; unlike Ferocity, it stacks every time you play it, so by the third or fourth customer you can bump the effect enough to be reducing your stress by 6+ each turn, keeping you at 0% no matter what the opponent does and ensuring you never draw a stress card unless the opponent specifically forces it.

At rank 9, you earn Serenity of Mind, which should immediately replace Scheme. You're always keeping your stress below 20% - at this point, it's almost always at 0% - so this is a strict upgrade, and a massive one at that.

Alternative Deck

You can get a reasonably-functioning version of this deck going during the second week, if you're quick to raise Xid and Saffron, and spend some time with Muktuk as well.

  • Use Build Rapport (Baptiste rank 2) instead of Two Is Better Than One - Sympathy can offset the lack of additional Choruses.
  • Use Bravado (Muktuk rank 5) instead of Ferocity - it's more powerful (3 interest per card) but only lasts two turns.
  • Swap one or both of your Meditates for Guided Thought - less stress reduction, but keeps the interest pressure going.
  • Use a second Captivate instead of Deep Connection, if you haven't gotten Saffron to rank 5 yet for the card.

Unfortunately Xid's Rhythm (rank 6) is pretty core to making the deck pop off, so boosting her relationship as quickly as possible with gifts and hang-outs should be a top priority.

Roll.js, an Exact Dice-Simulation Library

Last updated:

Are you the sort of person who likes to play around with RPG or boardgame mechanics? Have you ever looked at some dice-based mechanic and wondered just what the outcome chances really were, but instead just gave up, rolled a couple of times, and relied on vibes? Have you tried using AnyDice.com but gave up when you saw you'd have to learn a funky DSL to do anything non-trivial? Do you know JavaScript?

If you answered yes to some of those questions, I've got a new library for you! Roll.js is an exact-results dice-simulation library - it doesn't do simulations of the "repeat 10k times and report the average", it tracks every outcome with its precise chance of occurring (up to floating-point accuracy, at least).

The README explains the library itself, and there's a playground tool that'll let you write code against it immediately and even share the results with others! There are several bits of example code in the README, and several more in the playground (click the ? in the upper-right).

For example, a simple "d20 with advantage" roll is:

Roll.d20.advantage()

(playground link)

Want to know average damage of a greatsword after Great Weapon Master is applied (re-roll 1s and 2s, a single time)?

Roll.nd(2, 6).replace(x=> x <= 2, Roll.d6).sum();

(playground link)

Wanna build a d5 out of a d6 by rerolling 6s until they stop coming up, as long as it takes?

// "reroll()" calls map on its returned Roll 
// results again, until things stabilize.
Roll.d6.reroll({
  map: x=> (x == 6) ? Roll.d6 : x
});

(playground link)

Wanna do something complicated, like figure out what the chances are of dying/stabilizing/reviving from a D&D death save?

Roll.d20.reroll({
	summarize(roll, oldSummary={}) {
		return {
			successes:(roll>=10?1:0) + (oldSummary.successes || 0),
			failures:(roll<10?1:0) + (roll==1?1:0) + (oldSummary.failures || 0),
			nat20: (roll==20),
			toString() { return `${this.successes}/${this.failures}/${this.nat20}`; },
		}
	},
	map(summary) {
		if(summary.nat20) return "revive";
		if(summary.successes >= 3) return "stabilize";
		if(summary.failures >= 3) return "die";
		return Roll.d20;
	}
})

(playground link)


Point is, this library can do a lot of stuff, pretty easily, and you can use the JS you already know to do arbitrarily complicated stuff with it. I originally wrote it because I was fed up rewriting simulation code every time my brother and I were thinking about D&D homebrew, and in particular when I wrote some code to test out an alternate death-save mechanic it was getting too complicated; I figured I could just do it right, once, and (after several days of swearing at infinite-looping reroll code) I was correct!

At the moment the convenience functions (like .advantage()) are pretty biased towards D&D 5e usage, but I'm happy to add more for other dice systems. If you have any you'd like to see, let me know in a comment!

How To: Correctly Test for Python's Version

Last updated:

Python has four obvious built-in ways to retrieve its current version. If your software has a minimum version requirement, you'll use one of these methods to test whether the Python currently running your code is a high-enough version to do so successfully.

Three of these ways are worthless, actively-harmful traps. If you read nothing else, read this: use sys.version_info.

The four ways are:

  • sys.version_info is the good way, returning a tuple of numbers, one for each component of the version
  • sys.version is a terrible way, returning a string containing the full version identifier
  • platform.python_version() is another terrible way, also returning a string
  • platform.python_version_tuple() is a final and especially terrible way, returning A TUPLE OF GODDAM STRINGS FOR THE COMPONENTS

Deets

sys.version_info returns a tuple of numeric components. Well, it returns a namedtuple, but same deal. If I convert it to a plain tuple, on my system I get:

(3, 7, 12, 'final', 0)

Tuples in Python have nice sorting behavior; they'll compare against other tuples element-wise, with missing items considered as coming before present items. This means that if I write sys.version_info >= (3, 7), it'll be true - the 3 and 7 match, then the remaining components in sys.version mean it's slightly greater than the (3, 7) literal.

Importantly, because this uses element-wise comparison and the elements are (mostly) numbers, it won't fail for stupid reasons, like version 3.10 coming out. If you compare (3, 10) >= (3, 7) it correctly returns True, because 10 is greater than 7.


platform.python_version_tuple() also returns a tuple of components, but each component is a string. Currently on my system it returns ('3', '7', '12').

If you compare it with a literal tuple, like platform.python_version_tuple() >= ('3', '7') (note that you have to write the literal with strings, or else it'll error), it'll appear to work - you'll get back True. If the function returns ('3', '6') or ('3', '8') it also appears to do the right thing, returning False and True respectively. Bump it to ('3', '9') and it's still True, as expected. But advance to ('3', '10') and it suddenly returns False, indicating that version 3.10 is less than version 3.7.

This is because string-wise comparison is not the same as numerical comparison. Strings compare letter-by-letter, and "1" (the first letter of "10") is indeed less than "7", and so fails the >= check.

This is a very easy mistake for end-users to make. It's an unforgiveable mistake for Python, the language, to make. Returning a tuple, indicating it's been parsed, but filling that tuple with strings when they know the components are numeric and will be used as if they were numbers, is just absolute clown-shoes behavior.


sys.version and platform.python_version() just straight up return strings. Once again, naive comparison seems to work: on my system sys.version returns '3.7.12 (default, Nov 11 2021, 11:50:43) \n[GCC 10.3.0]', and if you run sys.version >= '3.7', it returns True as expected.

Both of these fail when the version is 3.10, in the exact same way as platform.python_version_tuple().

Conclusion

Always, always, ALWAYS use sys.version_info to get the current Python version, for any purpose. Anything else will break your code for stupid reasons.