boardgame.io
boardgame.io copied to clipboard
Using setup data for Local games
As of now, the setup function of the Game object can take as an argument a setupData object, which is given by the multiplayer server.
I was wondering if we could have a similar behaviour for Local games?
Here are my 2 ideas on how to add that to the framework:
- Passing a setupData object to the Client (preferred way)
var GameClient = Client({
game: Game,
board: Board,
multiplayer: Local(),
setupData: {}
});
- Passing a setupData object to the Game (not sure how though, maybe as a prop?)
var myGame = Game;
Game.setupData = {}
var GameClient = Client({
game: ,
board: Board,
multiplayer: Local(),
});
Let me know what you guys think. I'd be happy to work on it as soon as I get the go :)
I just thought through a few options and will lead with the simplest, but below I’ve also included some thoughts on how this might otherwise be implemented, which might also help show that the simplest approach could be the best.
Simplest
You can achieve this without any changes to the library 🎉. In local circumstances like these, you can wrap your game definition itself as a function and do something like:
const Game = customData => ({
name: 'my-game',
setup: ctx => {
// use customData
}
})
const GameClient = Client({
game: Game(localSetupData)
})
Even better, you can abstract this to a default wrapper for any game definition to provide setupData
:
import myGame from './my-game.js'
function wrapGameDefinition (game) {
return setupData => ({
...game,
setup: ctx => game.setup(ctx, setupData)
})
}
const wrappedGameDef = wrapGameDefinition(myGame)
const setupData = { /* local data */ }
const game = wrappedGameDef(setupData)
Given this is possible, is fairly straightforward, and requires no library changes, it might be preferable. (Maybe documenting this pattern could be a good idea if it’s a common requirement.)
If you’re still thinking it would be useful to have a setupData
option. Here are some more thoughts:
Read even more…
Adding a setupData
option
Of the two options you suggested, I think something like the first would be preferable as it doesn’t add a special field to the game definition that applies only on the client. Going that route, here are a few thoughts.
Implementation
You need to get your setupData
from the client, to the InitializeGame
call site, which will be either in the Master
class’s sync method (if using Local()
) or (more simply) in the Client
itself (when multiplayer
is falsy).
It’s a bit fiddly, but definitely possible.
Concept
For clients with multiplayer: undefined || false
, passing setupData
directly to the client seems like a reasonable approach, because you have a single client managing everything. However, when using the Local
transport factory as in your code examples, there’s actually a check in the code to see if a corresponding master already exists on the page in which case subsequent calls to Local
use an existing master, not a new instance. This way, several clients can share a single master.
Realising that, it would make most sense for setupData
to be passed to Local
not to the Client
and then to document the pattern as:
const transportFactory = Local({
setupData: { /* ... */ }
})
const Client1 = Client({
multiplayer: transportFactory
})
const Client2 = Client({
multiplayer: transportFactory
})
Nevertheless, this could be buggy if people started to try calling Local
repeatedly with different setupData
and expecting it to be respected.
Thanks @delucis,
I learnt a lot by reading your propositions and you sure know the framework better than I do! I like the wrapper option, makes me able to use the classic Game object for online games and the wrapper for local games.
Nonetheless, i think that having a similar behaviour between local and online would be needed at some point for the lib
@julienroulle No problem! I may have spent a few too many hours reading through the code over the past months…
I agree that consistency is a good aim. How exactly are you using the Local
class? To manage multiple views for pass-and-play or something?
The difficulty is that the Client
calls the Game
helper with the game definition passed to it and hands the result to the multiplayer implementation, so an “automatic” solution using something like the wrapper internally would currently need to happen at the Client
level. That runs into the weird thing of having to set setupData
on each client even though it has to be the same for each of them. (The same applies with passing the game definition.) In a way it would be nice if the client could get its game definition from a multiplayer implementation if in use.
@delucis I am using this for Local AI play, in a 1v1 boardgame, where the user can choose it's color, the first player, and the time (blitz mode).
Since I instantiate only one client I don't really mind doing this wrapper since I don't have to check that the setup was made correctly for the second player.
But in the end I'm still a bit confused as of why the setup happens on the Client
level and not directly into the Game
.
Maybe it could be possible to create a "Local API" for game creation?
Or maybe abstract the Lobby
implementation that would work both for local and online multiplayer?
Oh right, I’m not so familiar with the AI side of things.
The lobby design is a pretty significant complexity to add for local game management where you don’t need most of the abstractions it gives you (waiting for other players, authentication, etc.). There probably is some refactoring that might improve the separation of concerns, but it would be pretty significant if you consider that more than half of the options for the Client are potentially shared between clients: game
, debug
, numPlayers
, multiplayer
, gameID
and enhancer
could all potentially be set on some local API class and only playerID
and credentials
on the client.
Hi! I am trying to allow user customization for https://github.com/freeboardgames/FreeBoardGames.org ... I don't like the proposed solution of wrapping the game in local mode, because our games can work in several modes and that would mean two different variables/places to access in each case...
Are you okay if we go with the proposed solution of adding it to both Local() (for multiplayer running in a single browser) and Client() (for local-only games) ?
I can implement this for us :)
@flamecoals We could add that sure.
I’m not familiar with your codebase, but this shouldn’t require two variables necessarily to use the wrapping approach. Your local clients could handle this, something along the lines of:
import { Client } from 'boardgame.io/react';
const gameWithSetupData = (game, setupData) => ({
...game,
setup: ctx => game.setup(ctx, setupData)
});
const ClientWithSetupData = ({ game, setupData, ...props }) => {
props = {
...props,
game: gameWithSetupData(game, setupData),
};
return <Client {...props} />;
};
At that point you will basically be using setupData
on the local clients as you are proposing to add here.
Oh! I didn't get that it was that easy. So I can do that instead, thanks :)