Fork Games Bug/ API Post

I am helping Devin create tools for the Baduk.Club and with the help of a few people in the community such as Trevoke and Devin himself we found a bug when creating forked games on the site. I assumed it is well-known but it doesn’t carry captures over to the new game made.

Another note is when creating a game through an API Post I’ve found that the forked games captures only the stones on the board in a string and uses that string to create a new game. If you use an API though you get games with illegal positions where stones are not captured.

Example 1: Test
Example 2: Test

In the examples I extracted the string of moves black and white posted then the examples happened.
After figuring out the relationship between the create a game tool and fork game tool I eventually removed captured stones manually. However there is one thing that does happened even with the correct string of moves.

Example 3: Black 17
Example 4: Black Box

Ultimately it seems that whatever interacts with the json either deletes something or misinterprets : “black”: “string of moves”,
“white”: “”
Devin was able to make a temporary fix by adding a space, and so far it hasn’t broken. It’s entirely possible it might break again but I think this last part might be on me. (I haven’t tried replicating it in reverse IE black " " white " string of moves")

My question here is how does the post/workflow interact with the string of moves when posting games? I think I can safely assume it doesn’t read or play out the moves but instead just places the stones on the board. Is there any way to fix the captures by adding a prisoners property to games? Would adding this property break forking a game?

1 Like

I’m having trouble understanding what Examples 3 and 4 are demonstrating. For example, in Ex. 4 I see on the entire border, and a single white stone in the center. What is the expected behavior?

1 Like

The expected result is the white player should place their own stone. with no white string and initial_player"white" there should be no move for white. Instead without a space the scripted places a stone automatically for white and its blacks turn. If you look at the game tree, you can see white did not start the game and black has the initial_player.

TLDR; white move string is null, script plays move for white, black plays first. check the game trees.

Ah, I think what is happening is that "null" is getting interpreted as a move string:

"nu" - invalid move, no stone is placed
"ll" - valid move corresponding to M8

I’m not sure where the "null" is coming from though. This should be an empty string.


Ahh, nice catch. This is probably what’s causing it. Honestly the null issue isn’t important.

More interesting to us is

  1. Forking a game doesn’t maintain captures
  2. How does OGS generate the initial board state when forking a game?

The goal here is to allow players to resume play as white and black from a historic game. This ideally we’d be able to show captures and we’d be able to just load an sgf (or we’d have access to whatever tool is parsing the sgf and turning it into a static board state)


Yeah unfortunately, I don’t think it works like that. I think “fork game” literally just takes the board state (as in the stones that are on the board) from the previous game and places the stones on the board as “initial state”.

Is an accurate score the main reason you want to record captures? If so, one could adjust komi when forking the game to reflect the captures? I totally realize that’s not optimal, just a thought


Right, I suppose the question would be what’s a good way to get the current board state from an sgf?

Good solution! I’d also come up with the same one, but as you said not optimal, but totally workable!

I suppose three low priority things to add to the massive OGS to-do list (not even needed by us but I Think Jae just wanted to make you aware)

  • OGS not interpreting null value correctly (maybe us not properly sending the api call)
  • method to fork a game from an sgf, not just a list of stones on the board
  • ability to fork a game with captures

Thanks as always for your thoughtful and helpful replies @benjito
We of the OGS community are really lucky to have you <3


Here’s what I assume the behavior and current logic behind the implementation is:

  1. When you hit “fork”, since OGS has access to the current board position via the Javascript Goban object, and since that object’s job is to represent the current board position via whatever moves got played, it is probably fairly easy for OGS to ask that object for a list of black stones on the board and a list of white stones on the board.
  2. This means that there’s an internally-consistent logic in OGS which prepares a payload with the given board position (you can actually see this yourself because the OGS fork button shows a preview of the board state that will be forked).

So from this perspective, the simplest thing to do would be to try and get the current board state from OGS. And by the way, did you know you can add a /move_number to get a game at a particular board position? e.g. Honor among players ( will get you move 45 of this particular game.

As far as forks don’t maintain capture – this could be considered a defect, but it could also be considered business logic: what if you started a brand-new game from this board position, without baggage?

I suspect that captures not being carried over is a defect, because it doesn’t make a ton of sense to me to have a board position without the captures – after all, forking a game does mean forking the game with its current score, and if you’re using Japanese rules, captures are part of the score.

As far as the null value not being interpreted correctly, that’s probably one of those things where OGS assumed its API wasn’t public :smiley: It’s probably more on us as users to make sure we send an empty string, honestly.


You are too sweet @Devin_Fraze :slight_smile: The OGS (and Go community) is lucky to have you as well!

The code for how OGS does it is open source, but this code is hairy to be honest :grimacing:

I’d take a look at GoEngine.tsx. If you’re working in JS, you might be able to do something like:

import { GoEngine } from "goban";

const MY_SGF = "(;FF[4]CA[UTF-8]GM[1] ... B[pd](W[dg] ...)))"

const engine = new GoEngine({original_sgf: MY_SGF})
const board_state = engine.computeInitialStateForForkedGame();

I’d say the first one is WAI (after all, “null” is a valid move string on a 25x25 board)

The second two, I agree those should be a thing!