boardgameio / boardgame.io

State Management and Multiplayer Networking for Turn-Based Games
https://boardgame.io
MIT License
9.93k stars 707 forks source link

Game Object API #455

Closed nicolodavis closed 4 years ago

nicolodavis commented 4 years ago

Context

Our previous idea for a UI framework (#282) was to ship with a set of React components for cards / decks / tiles / hexes etc. that people could use to cobble together games. However, this has the following problems:

G: {
  deck: ['card1', 'card2', ... ],
}

Manipulating the deck involves writing code to mutate this list directly.

New Proposal

Let's build an API so that you can create objects in G that correspond to entities in games:

  1. Objects: cards, tokens.
  2. Collections: decks, stacks.
  3. Zones: places on a map or board where you can place these things.

For example:

setup: () => ({
  deck: bgio.Deck({ cards: [ 'card1', 'card2', ... ]})
})

The storage representation could look almost identical to the very first example:

G: {
  deck: {
    cards: ['card1', 'card2', ... ],
  },
}

Once you've initialized your Game Objects this way, you get the following:

1. You can interact with them using a nice API that uses verbs that are relevant to games.

move: (G, ctx) => {
  // these manipulate the underlying datastructures behind the scenes
  G.shuffleDeck('deck');
  G.drawCard({ from: 'deck', to: 'hand' });
}

2. We can auto-render the UI.

Sounds ambitious, but I think this is attainable. This is also in line with our stated mission of "just provide your game logic and we do everything else".

I'm imagining that we can create an implementation that can interpret the state in G and render the cards / decks etc. automatically. What's more, different people can create different renderers that adhere to the spec and all of a sudden your game can run in 2D, 3D etc. This is a totally optional feature of course (people are still free to use the Game Object API and render their games manually).

There are two important aspects to achieve auto-rendering:

Static Configuration:

We don't make this distinction currently. Stuff like card text, the sizes of objects etc. needs to go in a separate area and G just keeps track of where things are and what their (mutable) attributes are.

Available Actions:

On a turn, we should be able to tell what options are available to the user so that we can automatically build the UI interactions necessary. When the user completes the interactions, it triggers a move:

For example, if we want the user to "play a card from their hand":

moves: {
  play: (G, ctx, card) => { ... },
},

rules: {
  action: CHOOSE,    
  num: 1,
  from: 'hand',
  onComplete: 'play',    // call the play move with the result of the selection
}

We'll also have to build constructs to express things like "do one of the following" or "do all of the following in order" and have everything be nice and composable.

3. You don't have to configure bots separately.

The attentive reader will notice that there is an overlap between this and how we tell bots what actions are available to them. The goal would be to unify both. In theory we can also support the lower level API for bots (which merely enumerates possible moves by spelling them out in addition to their arguments), but it would be great if we just have this higher level declarative API.

Implementation

We can ship with one implementation of this spec that uses SVG (or Canvas, or just simple DOM elements) with a minimal set of operations. We can add more features eventually. The key point to note is that this is an incrementally adoptable feature, so we don't have to fret about not being able to express every single game because people can always fall back to maintaining state manually in G and rendering it using code that they write.

nicolodavis commented 4 years ago

After discussing this with some folks on Gitter, I'm convinced that this should probably be a separate thing (not part of boardgame.io) and made available as a plugin instead.

Closing this issue for now.