feat: Change move and hook signature#891
Conversation
This follows the proposal in #662 to use a single context argument for move (and other game functions): ```js function move( { G, ctx, playerID, ...pluginAPIs }, ...args ) {} ``` This is the initial core work to achieve this. Some outstanding TODOs remain, as well as rewriting docs and examples once this is done.
nicolodavis
left a comment
There was a problem hiding this comment.
We can probably punt on bringing the bot code to be consistent with the new format, but I think having playerView and undoable adopt the new format right away would prevent bugs in user code when we release this.
| ActionShape, | ||
| State, | ||
| Ctx, | ||
| FnContext, |
There was a problem hiding this comment.
I do agree with the point that it probably makes sense to call the aggregate (G + ctx + ...) as Context, which begs the question about what to do to ctx.
Do you think ctx: GameContext is a suitable name combination? I'm less convinced by flow.
There was a problem hiding this comment.
I’ll try to think further on what it might be named. I suppose I’m trying to avoid context.ctx as it seems rather confusing and vague to have a context inside a context. Some other options to brainstorm from (but something terser would be better): playState, flowState, engineState. I personally like how flow combines with the various members, e.g. flow.currentPlayer, flow.playOrder, flow.phase etc. while not being too verbose, but perhaps there’s something better.
That said, G + ctx is a pretty core concept for current users, so I could also definitely understand keeping it as is if we can’t find anything better.
There was a problem hiding this comment.
I dont think this is a major problem, because the Context in this case is only used internally. Most of our docs were updated to use the object destructuring assignments, so it doesnt look confusing for users.
I had the idea of typing move arguments using ts-toolbelt’s `Misc.JSON.Value` type which represents the various possible results of parsing a JSON object because that is what is permitted for serialization over the wire. This makes it harder to type moves though because you can no longer do `({ G }, id: number) => {}`. On the one hand this could be nice as it reminds the move to narrow the type as it comes potentially from an untrusted source (similar to if typing with `unknown`) but that doesn’t seem to quite justify the additional frustrating overhead when writing moves.
|
Will this be merged? |
|
I received feedback that people really want this and are already using the alpha. I vote for finishing reviewing/merging all other outstanding PRs and doing a minor release. After that, we merge this PR and do a major release. What do you think, @delucis ? |
|
Major would mean [email protected]. Not totally opposed but I think we could also release this as a minor (0.50.0 was my target for merging this) — minors below v1 are treated as major semver by npm, so it’s OK to introduce breaking changes from minor to minor. So if we have stuff we want to get out before this, that could be 0.50.0 and then this as 0.51.0 or 1.0.0 if we’re ready! (One thing I had always thought needed work prior to v1 was the bot/AI architecture, but improving that can be a v2 goal too if we want). |
|
Oh ok, I don't mind either way, as long as we do a release before this PR goes in, because it might take a while to redactor all games to it. Tonight I will try to review your code, and then we can proceed! |
|
Sounds good. Thanks @vdfdev! 🙌 |
|
Uh oh, I think I borked the merge, will revert and fix it. |
…ure"" This reverts commit bff9fe0.
|
Seems like aa99a9c conflicted with this PR. I was able to fix one test, but there is one that still fails. I will keep trying to fix it |
|
@delucis Hey, I have a pretty huge bgio codebase and after the update got over 500 typescript errors. |
📣 Try the pre-release today 📣
You can start working with the new hook signature by installing the alpha release channel from npm:
If you run into any issues, let us know!
Closes #662.
This PR implements the change from
(G, ctx)to({ G, ctx, playerID, ...plugins }).Game code that currently looks like this:
Would become:
This applies across moves and hooks.
To-do
The following parts of game code haven’t been changed yet:
playerViewis currently still(G, ctx, playerID) => G. Should this become({ G, ctx, playerID }) => G?I haven’t updated the bot interface at this point. Should the following be changed for consistency?
enumerate(G: any, ctx: Ctx, playerID: PlayerID)Various other methods in the MCTS bot implementation
The
undoablemethod of the long-form move syntax, which can be(G, ctx) => boolean. Should this become({ G, ctx }) => boolean?Notes
In refactoring tests, I noticed that this change often results in (to my mind) clearer game code and will hopefully help to clear up ongoing confusion about
ctxbeing different in moves and on the client. Types are also greatly improved.One thing this helped clarify for me, was that
playerID(currentlyctx.playerID) is only available in moves, not in any hooks. Given the slightly confusing nature ofplayerIDin hooks, this may not be a bad thing. For example, it’s not necessarily easy to understand whyplayerIDinturn.onBeginis the ID of the player whose action caused the previous turn to end, not the ID of the player whose turn is beginning. That said, if we addonEnd/onBeginevents for stages as proposed in onBegin and onEnd for Stages #608, there will be a need to revisit this in the future.While updating the docs, I noticed it wasn’t very clear what to call the whole object that contains
G,ctxetc. I think of this whole lot as “context” for lack of a better word, but that would be confusing asctxis already in use. I wonder if in fact, this refactor might be an opportunity to renamectxitself to something more descriptive. Not sure what that name would be, butflowcomes to mind.I’m not sure when will be best to merge and release this as obviously it’s a significant breaking change for everyone, but I wanted to get the ball rolling and we can keep this PR up-to-date as other changes arrive on the main branch.
Migration Paths
This change is pretty impactful across game definitions. Here are some migration strategies.
1. Rewrite your functions
Ideally, you would rewrite your game to use the new syntax. In general this will result in simpler code. For example:
2. Use a stop-gap plugin for compatibility
If you don’t want to rewrite your moves, you can use a custom plugin to wrap those functions so they continue to receive the old style of arguments.
3. Gradual migration
Using a plugin is an all-or-nothing approach — it forces you to continue using the old-style of arguments. If you want to gradually migrate your code, you’ll need to wrap only functions you haven’t updated yet. To do this we could adapt the
fnWrapmethod from above to selectively wrap certain moves.Depending on how your project is structured, you may be able to simplify applying this adapter rather than doing it manually for each function, for example if you store your moves something like this:
You could apply the adapter programmatically:
Then you could migrate by splitting your moves into two collections:
General Tips
Use Typescript to help find errors
If you’re already using TypeScript, make sure you use boardgame.io’s bundled types for your game to catch changes:
If you’re writing plain JavaScript, you can often still use TypeScript types to get some in-editor hints by using Typescript’s JSDoc syntax (VS Code does this well for example).