nicolodavis 3 days ago

Original creator of boardgame.io here. A pleasant surprise to see this here after many years.

More recently, I've been working on https://boardgamelab.app/, which uses a visual programming language to model game rules while also taking care of the UI layer.

  • mankyd 3 days ago

    I feel like saying that is supports AI players, but not having a simple, already hosted example is a disservice. Even tic tac toe, or go fish would be a nice hook to help people understand what it actually delivers.

  • jfengel 3 days ago

    Oh, thank you. I've used your library a few times for personal projects, and it does exactly the thing that I needed. I really appreciate you having created this.

  • wordglyph 3 days ago

    Omg! I've been noodling about making my word game https://WordGlyph.xyz multi player and been dreading that journey but now here it is!

  • doctorpangloss 3 days ago

    > More recently, I've been working on https://boardgamelab.app/, which uses a visual programming language to model game rules while also taking care of the UI layer.

    Suppose there were a technology that could turn the canvas you authored into finished, consistent art; and a way to turn natural language rules into correct code. Would you use it? Why or why not?

    • anonymoushn 3 days ago

      It would be great to save time on the implementation of board game rules engines. Unfortunately the fine folks at FFG are really bad at figuring out what the rules actually are and telling people :(

    • lukan 3 days ago

      "turn the canvas you authored into finished, consistent art"

      Like a jpeg? Otherwise I don't understand your question.

      " a way to turn natural language rules into correct code"

      And this is straight impossible, as natural language is by definition ambigious in meaning and code is not. Try your luck with LLM's, they come closest.

      (a subset of natural language might work, but this is kind of a complex research topic)

  • thih9 3 days ago

    The boardgamelab.app looks like a stealth, proprietary project; at least at the moment its T&C[1] says: "under this license you may not (...) use the materials for any commercial purpose, or for any public display (commercial or non-commercial)".

    Do you have any plans for the business model?

    [1]: https://boardgamelab.app/terms-and-conditions

    • fourside 3 days ago

      What do you mean by stealth? I looked at the website and nothing about it makes it look like it would necessarily be free or open source.

      • thih9 3 days ago

        > What do you mean by stealth?

        I meant stealth as in: “or for any public display (commercial or non-commercial)”, see grandparent comment. I edited the grandparent comment now and added a comma between “stealth” and “proprietary”, hope this is clearer.

      • jzig 3 days ago

        Still needs clearer language IMO.

        • eklavya 2 days ago

          I just checked and it says MIT licensed. What am I missing?

          • nicolodavis 2 days ago

            I think you might be conflating boardgame.io and boardgamelab.app.

            boardgame.io - MIT licensed open-source JS library.

            boardgamelab.app - Proprietary platform for designing and playtesting board games.

  • beretguy 3 days ago

    There was this iOS game which died which I wanted to recreate:

    https://web.archive.org/web/20161020010853/http://www.82apps...

    But I have 0 knowledge of game development. Maybe this could make my job easier? Or maybe somebody else who know how to write game can do it? Please?

    • LosEstupidos 3 days ago

      [flagged]

      • lukan 3 days ago

        So you provided a thoughtful response, to show us how it is done?

        The question was basically, can you rewrite that game for me?

        Yeah sure, anything else?

        Sorry, it was really not adding much to the conversation. The first question on its own would have been a simple yes.

fleabitdev 3 days ago

This engine uses a Redux-like architecture. You have a State type (containing data like "the position of the black kingside rook") and a stream of in-game actions (like "knight to F3"). Each action is handled by a pure function which converts the current State to a new State. You can either transmit State deltas from the server to the client, or just transmit the actions themselves (https://longwelwind.net/blog/networking-turn-based-game/).

This design makes it easy to implement optimistic updates, rollback, replays, automated testing, and recovery after a disconnection. It's a surprisingly good fit for UI, too; you can render simple games as a React component which takes the current State as one of its props.

However, a stream of context-free actions can be a really inconvenient representation for some games. The rules of a board game are often like the control flow of a computer program: you'll see branching, iteration, local variables, function calls, structured concurrency, and sometimes even race conditions and reentrancy. When you try to represent all of this logic as a State object, you're basically maintaining a snapshot of a "call stack" as plain data, and manually resuming that "program" whenever you handle an action. It doesn't seem ideal.

I've been sketching a board game engine which would represent the game logic as normal code instead. It seems promising, but it really needs a couple of language features which don't exist in the mainstream yet, like serialisation of suspended async functions.

  • LudwigNagasena 3 days ago

    My main pain point with any sort of Flux-like state management is transitions [1]. The state of UI is not fully described by the state of the game [2]. If I play a card, the game state can be instantly updated to the next decison-making point, but in reality I want to show steps of the game through animations, some of which are concurrent and some of which are consecutive. That usually ends up in a mess; and I've never seen someone implement it nicely.

    [1] And generally dynamic stuff like drag-n-drop, which is infinitely times simpler in any other architecture than in React.

    [2] That is also true for business apps, but their animations are usually so simple you can simply use CSS.

    • Longwelwind 3 days ago

      The way I wanted to implement this in my turn-based game engine:

      If you implement the deterministic update pattern to handle state synchronisation you can add "event" inside the logic that handles updates that pause the processing allowing your animations to be played. In JS, for example:

          async function handleUpdate(update) {
              if (update.type == "sell-items") {
                  this.player.inventory[update.itemId] -= 1;
      
                  await emitEvent("itemSold");
      
                  this.player.money += 10;
      
                  await emitEvent("moneyGain");
              }
          }
      
      Server-side, "emitEvents" would be a no-op. Everything would resolve synchronously.

      Client-side, the UI can listen to those events to pause the updating of the game state to see the intermediary state of the game and play animations. When the animation is done, it can resolve the promise, resuming the game updating logic.

      If an update arrives while an update is being handled, it can be queued so it can be played after the current update finishes.

    • nkrisc 3 days ago

      I toyed with an approach once that separated animations from game state updates.

      Every player action could cause a cascade of updates, which would all be resolved “instantly” to the point no more cascaded updates were left to be processed.

      While this is happening, any update that includes an animation pushes that to an “animation stack”, then the animations are played back one by one to show the player what happened. In this animation state most input in disabled and the game is effectively on hold until the animations complete (or are skipped by the player).

      The “animations” were basically commands that the Model used to update the View, just with the option to apply them one by one over time. So the model is always up to date as fast as possible as possible, and the view just lags behind a bit and catches up.

      • jay_kyburz 3 days ago

        I've found in the past its good to have the model updates in the queue so that the animation controllers can be simpler. The animations simply need to observe the model and react to changes, and post events when they complete.

    • sjrd 3 days ago

      The way I did this was to design a more-or-less monadic container `Result<A>` for all my game logic functions. It batches a sequence of animation steps with a result. It can also model error conditions (like not having enough resources for example). I can then instantiate it any concrete result type, such as a full game state or just the result of individual computations. It was very nice to concisely write complicated game logic with animations while retaining the happy path.

      https://github.com/sjrd/barrage/blob/main/src/main/scala/be/...

      • LudwigNagasena 3 days ago

        Interesting. Unfortunately, your repo seems to be private.

        • sjrd 3 days ago

          Oh shoot, yes. I have imitation copies of the original game graphics in there, and so I can't make it public without violating copyright of their assets. :-(

          Here is a public gist with the `Result` data structure, as well a good portion of the file handling all the game mechanics, which should show it gets used. https://gist.github.com/sjrd/34fe234d1b6232cf42ffda5d23292d3...

    • semitones 3 days ago

      In my experience, the way to solve [1] and [2] is to design a game state that can also _fully_ describe the state of the UI, including animation cues.

    • gr4vityWall 3 days ago

      (game dev) What I did in the past was having actions generate both a new state, and a stack of 'instructions' that update the UI, which was deliberately not a 1:1 representation of the game state for the exact reasons you described.

      Then you can 'execute' the stack within a command VM of some sorts, where instructions can move sprites around, play sounds, etc. You can have 'high level' instructions ("display the enemy's death") implemented as a combination of low level instructions ('reduce that health bar count until it goes to zero' -> 'change entity X's sprite to dead sprite' -> 'give player 200 gold as reward' -> 'play sound' -> 'change text in the text bar to something' -> ...)

      It ended up working waaay better than what I was expecting, felt very easy to reason about, wasn't hard to maintain, and scratched that itch of implementing an interesting solution. :)

    • tinkrr 2 days ago

      I agree fully with [1], having recently experienced the pain of implementing a custom drag-and-drop UI in React.

      I ended up using ref's heavily to avoid stale closure and async re-render issues. Which basically amounts to circumventing React and interacting with the DOM directly.

  • bbminner 3 days ago

    Yep, that's the exact issue I wanted to address with my own twist on the idea of an online boardgame engine - I was trying to actually persist the callstack of an async wasm vm function (game loop) execution into a database in rust. It is working in a sense that you can implement battle ships or tick tack toe, but I did not quite finish it. Happy to still make repo public if helpful.

    To be more specific, async wasm function was implemented as a poll loop sync function exported to the caller, there was (at the time) no way to move wasm mv memory, so it was persisted while the game was live and replayed from a message log stored in a db after/if preemption.

  • nicolodavis 3 days ago

    I agree that asynchronous flows are hard to represent using a state object.

    I chose to go with a monadic approach in boardgamelab.app (I'm not limited by language features since I'm designing it from the ground up for this use case). The language is pure and functional with managed effects. You can express things like:

      set of cards -> filter by action cards -> choose -> [card]
      do stuff with [card]
    
    written in a synchronous style, while under the hood it:

    1. suspends the rule.

    2. waits for the user to make a choice.

    3. resumes the rule with the choice made by the user.

    (note: listed above is a simplified text representation. The Boardgame Lab structure editor uses a block-based visual language.)

    If written in Haskell, the underlying monad would look something like this:

      {-# LANGUAGE ExistentialQuantification #-}
    
      newtype Rule a = Rule {fn :: State -> (RuleResult a, State)}
    
      data RuleResult a
        = Done a
        | forall x. Show x => Choose [x] (x -> Rule a)
    
      instance Monad Rule where
        m >>= k = Rule $ \state ->
          case fn m state of
            (Done a, state') -> fn (k a) state'
            (Choose list m', state') -> (Choose list (m' >=> k), state')
    
    i.e. each rule returns either a completed result or a choice along with a continuation of the remainder of the rule past that choice.
  • NathanaelRea 3 days ago

    Can you explain more what type of game would need a call stack snapshot? I've never developed a game, but it seems like as long as you store like the initial state and prng you could always get the current state by replaying the full history. All the other logic would be stored outside the state, and only added when "committed". As long as prng is stable and you start from the clean state every time, you'd get the same outcome.

    • yojo 3 days ago

      I think I have a good example from Magic: the Gathering.

      There’s a card called “Fact or Fiction”. You reveal the top five cards of your deck. Then your opponent splits the cards into two piles. Then you pick one of the two piles to take into your hand.

      You’ll need to store structures representing the choices that are intermediary steps (split cards, pick stack) in your state, which is basically function calls and their params (a call stack). This example could get hairier - Magic also features cards with branching logic “choose 1: do a or b”. I can imagine designing cards with large and convoluted possible execution paths.

      You could have the card define a schema for state transitions/params and represent all these choices as JSON encoded POJOs, but as a developer it sounds a lot nicer to just be able to suspend an async function every time a choice is made.

      • Longwelwind 3 days ago

        I had the same issue in AGoT:BG and I solved it by representing the state of the game as a tree. At any point of the game, the current game state is a leaf of the tree.

        You'd represent this kind of choice as a child node. When the user has made their choice, the code can return to the parent node with the choice being made so it can continue with the next "step" of the game.

        • scrollaway 3 days ago

          This is the correct response. Hearthstone is structured like this internally.

          If you are curious about it, I wrote a cc0 spec which stores hearthstone game state in xml. It’s based on how hearthstone stores game state on the server and client, and it was the first time a replay format was created for hearthstone: https://hearthsim.info/hsreplay/

          Incidentally the UI we wrote for hearthstone replays is a react app. It’s funny because looking back it was the first time I used react and typescript, and both were not at all adopted by the js community yet at the time.

          https://github.com/hearthsim/joust

    • fleabitdev 3 days ago

      That's the exact approach I'm considering for the new engine I mentioned!

      Although that strategy enables you to store and recover the state of a game, it doesn't give you the ability to inspect a snapshot of that state. How can you print the card which has just been played, if that data only exists as an argument in the call stack of a suspended async function? In the same way that you can't inspect the local variables captured by a closure, mainstream languages also provide no way to inspect a suspended stack frame.

      This problem interferes with debugging, consistency checks (e.g. hashing the game state to check that two clients are in sync), and unit testing.

  • Savageman 3 days ago

    How does secret state fit in this? If you want each player hand to be secret, then each player has its own state?

    • fleabitdev 3 days ago

      boardgame.io only runs game logic on the server, and it censors the State just before sending it to each client. This strategy makes the UI feel less responsive, but it keeps things simple.

      The Swords and Ravens blog post recommends resolving actions on the client when they don't require secret information, but resolving other actions on the server. You'd also need to resolve actions on the server when they involve RNG.

      • Longwelwind 3 days ago

        I don't recommend resolving actions on the server in any situation:

        For actions that require secret information, you would filter the actions sent to the client of any secret information and make sure the code handling the action can handle both the action and the filtered actins.

        For actions involving RNG, make all randomness rely on a seed. This seed would be stored server-side and passed along the action when sent to the client. This makes sure the clients can deterministically reproduce the update.

voidmain0001 a day ago

I’m quite ignorant of JS development with Node. This package really interests me so I followed the tutorial. On the second step, I used npm to install boardgame.io which triggered a barrage of error messages related to deprecated packages and 69 vulnerabilities. I ran npm audit and it displayed numerous high severity. I tried the npm audit —force which broke boardgame.io I don’t plan on trying to fix these problems so does it mean that I shouldn’t try boardgame.io?

ferd 3 days ago

Looks very interesting.

Somewhat related: I've recently wrote a code-walkthrough (in Clojure) of modeling chess-like games.

https://neuroning.com/boardgames-exercise/

It's very basic and intended for teaching/learning functional programming, not a real library or engine like the OP.

rendaw 3 days ago

Are there any finished games that use this? I'd like to see a showcase.

DominikPeters 3 days ago

Is there a simple demo that one can test? I see a tutorial about tic tac toe, but the result doesn't seem to be hosted anywhere.

throw94838211 3 days ago

I tried to use this before, but many parts didn't really work right, lots of conflict between dependencies, especially the server parts. I had to abandon what I wanted to do with it. Shame because many ideas were interesting, but felt it was a bit untested.

Feathercrown 3 days ago

Ooh this is neat, I've been working on something similar. I'll have to check it out and see if they're using the same abstractions as I am. Could be enlightening!

Lucasoato 3 days ago

If anybody knows of any reference, article, book to study and implement turn-based engines like this, please add them under this thread.

tasuki 3 days ago

It's really cool that it has a MCTS bot built-in!

billnreed 3 days ago

I've seen this engine before and thought it was really cool!

I've been playing around with writing my own turn-based game engine with TypeScript: https://github.com/snowbillr/turn-based-game-engine

It's a WIP still, but what's really been the struggle for me is coming up with a data structure and traversal algorithm for the turn order. I wanted that to be extremely configurable by whoever uses the library.

```

engine.defineFlow((f) => {

  f.node({
    actions: f.actions(WelcomeMessage),
  })

  f.node(
    {
      actions: f.actions(RoundStart),
      cleanups: f.cleanups(RoundEnd, TestLog),
    },
    players.map(player => f.node({
      playerId: player.id,
      actions: f.actions(TurnStart),
      cleanups: f.cleanups(TurnEnd),
    })),
  )
});

```

This example shows a TicTacToe game defined like this:

```

- welcome

- round

  - player 1 turn

  - player 2 turn
```

What I ended up with was essentially a tree that's traversed depth-first to enter each node, and then goes back up the same traversal path until a sibling node is found to dive into.

That lets the user define rounds/turns/phases as children of each other in whatever shape they want.

It's been a real fun project!

downThePipe 6 days ago

Then what, print it out to play on the kitchen table?

  • ramon156 4 days ago

    > multiplayer networking Its in the description

  • tardibear 5 days ago

    It supports multiplayer over the network.

  • vasco 3 days ago

    Put the screen on the kitchen table and sit around it, call it CounterTop Surface, have a little imagination man.

    • thaumasiotes 3 days ago

      You say this like a joke, but that would bring a lot of usability and convenience improvements compared to a physical board game. Just moving stuff out of and back into the box can take a lot of work.

      It's easy to overlook rules that a computerized game automatically handles for you, though. For example, I'm part of a group that plays Heat ( https://boardgamearena.com/gamepanel?game=heat ) every week. An important part of that game is moving the game-controlled cars, and since the platform handles that, only one person in the group. This is potentially quite bad for strategy, depending on the rules you don't know. Another issue that comes up in the same game is people losing track of the size of their deck. The game is very generous about the allowable timing of deck-manipulation effects, but if you're not actively paying attention you can draw through your deck and trigger a reshuffle, wiping out your discard pile, when you wanted to do something special before the reshuffle. This isn't a mistake it's possible to make while using a physical game.

      • thaumasiotes 3 days ago

        > and since the platform handles that, only one person in the group.

        This should read "only one person in the group knows how it works".

      • jz67 3 days ago

        Hey sorry if this is not the place for it but I play heat a lot too and I was looking for a group like this on bgm since the random matches have a lot of people rage quiting etc. can I ask to join this group?

        • azemetre 3 days ago

          Do you play live or turn based games? I never had an issue of people rage quitting, I also tend to set the games to one turn per day which is reasonable for most people.

    • sethammons 3 days ago

      a tad further: a central phone or tablet as a main board that you interact with via touch and where you can then transfer over to your handheld device over the network to manage your hand. I've not seen this yet, and it would be a pretty great experience I think. No clean up

      • dalmo3 3 days ago

        This is basically how Jackbox games work.