Last active
March 1, 2024 23:47
-
-
Save aghull/5e25b3624575b0eac82e4997d10ffb38 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* i have an idea. | |
* | |
* Background: | |
* | |
* The flow was designed at first to encompass all flow control, loops and | |
* branches, etc. later during Cursed development, I had the idea to simplify | |
* some types of flow control where a single action was completely "embedded" | |
* within another. I called these "follow-ups". They used a simple mechanism | |
* where at any point during the execution of a player action, you could call | |
* game.followUp and the game would just keep a record of the queue of | |
* follow-ups and then once the game ceded control back to the flow, if any | |
* follow-ups were in the queue, the flow would need to process them before it | |
* would consider the current step "complete". | |
* | |
* I was a little surprised how useful the idea was, and how many people | |
* gravitated towards it instead of the flow fundamentals. I considered it a bit | |
* of a niche. While it's nice and straightfoward that you can call follow-up at | |
* any point when you need another action, it's a little awkward because when | |
* you call follow-up, you are queueing up an action *after* the current block | |
* completes, and I thought that is a little counter-intuitive. However, in | |
* practice it usually is called at the end of the block anyways so it doesn't | |
* seem like a big foot-gun. So, I think it might be a useful concept to double | |
* down on. So here's my idea. | |
* | |
* The current follow-up only accepts a single action. But it could take an | |
* entire sub-flow. The current `game#followUp` would be deprecated but would | |
* gracefully create a subflow with a single playerAction step of a single | |
* action. And then you could migrate over to the full subflows with their full | |
* expressive potential. | |
* | |
* It would look a little like this: | |
*/ | |
game.defineFlow() // as today, but this is now the "main" flow | |
// a subflow named "cardActivity", takes some set of predetermined, immutable args when called | |
game.defineSubflow<{card: Card}>("cardActivity", ({ card }) => [ | |
eachPlayer({ | |
do: playerActions({ | |
... | |
}) | |
}), | |
... | |
]) | |
// then you could call it while processing an action, e.g. | |
playerCard: player => action({ | |
}).chooseOnBoard( | |
"card",... | |
).do( | |
() => { | |
... | |
if (card.isSpecial()) return game.doSubflow("cardActivity", { card }); | |
} | |
) | |
/** | |
* When the subflow completes, it cedes control back to it's calling flow, using | |
* the stack. I don't think there's an easy way to get return values from it, as | |
* in a function call, but the subflow can set state on the game to be used by | |
* its parent flow. | |
* | |
* This would then be a useful tool to define a high-level main flow, with | |
* subflows for each phase of the game defined separately. For power grid as an | |
* example, I could have the auction kick of an entire bidding subflow since | |
* it's self-contained once you know the auction plant. | |
* | |
* There would need to be special rules around the defineSubflow call. It must | |
* determinstically return the same flow given a set of arguments, and must not | |
* mutate the game state while it's defining the flow. I think it would be | |
* possible to put some guard-rails on this to avoid foot-guns. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment