Skip to content

backwardCallbackResult and Undoing a Game State Step

Ryan edited this page Mar 13, 2019 · 1 revision

backwardCallbackResult and Undoing a Game State Step

backwardCallbackResult rolls back the game state by 1 block with the undo data from the previous block. It is similar to forwardCallbackResult, but we don't create any undo data because we're consuming some undo data.

In a production game, you will likely want to store more undo data than just for 1 block. This allows you to have a greater buffer in the unlikely event that you discover that you've been on a fork for more than 1 block. Remember, you can always post questions in the XAYA Development forums at https://forum.xaya.io/forum/6-development/.

Here's the method signature:

public static string backwardCallbackResult(string newState, 
	string blockData, 
	string undoData)
  • newState: Our GameState data
  • blockData: This is unused in this example
  • undoData: This is the data we use to roll back the game state by 1 block

To start, we initialise GameState and UndoData objects with deserialised data from our parameters.

GameState state = JsonConvert.DeserializeObject<GameState>(newState);
UndoData undo = JsonConvert.DeserializeObject<UndoData>(undoData);

Any given block can have new players join, so we need to keep track of those independently. We'll do that in a string list.

List<string> playersToRemove = new List<string>();

We need to check each player to see if they need to be rolled back. We do this by iterating through all players in the game state.

foreach (var mi in state.players)

To start our loop, we initialise some variables. We need to know the player's name and PlayerState. We get this from mi. A PlayerUndo variable is also created as null.

string name = mi.Key;	
PlayerState p = mi.Value;	
PlayerUndo u;

We only need to undo a player if they exist in our undo data, so we create a boolean flag for us to use and set its value.

undoIt = undo.players.ContainsKey(name);

The first thing to do if a player needs to be rewound, is to check if they are new players and add them to our playersToRemove list. We get the specific player through undo.players[name] and then we check the is_new property. Also, if the player is a new player, then we skip to the top of the loop. We'll remove the new players all at once later with our playersToRemove list.

if (undoIt)
{
    u = undo.players[name];

    if (u.is_new)
    {
        playersToRemove.Add(name);
        continue;
    }
}

Next, if the player has not finished moving according to the undo data, i.e. their Direction is not Direction.NONE, then we must check whether or not their current direction is NONE and they have no steps left. If so, we set their current direction to their undo data direction.

if (undoIt)
{
    u = undo.players[name];

    if (u.finished_dir != Direction.NONE)
    {
        if (p.dir == Direction.NONE && p.steps_left == 0)
        {
            p.dir = u.finished_dir;
        }
    }
}

Now, for all players we check if their current direction is not NONE. If so, we add a step and subtract the direction offset from their current position.

if (p.dir != Direction.NONE)
{
    p.steps_left += 1;
    Int32 dx = 0, dy = 0;
    HelperFunctions.GetDirectionOffset(p.dir, ref dx, ref dy);
    p.x -= dx;
    p.y -= dy;
}

To undo a player move we must set their current player state to their undo player state if our undoIt boolean flag is set for this player (this was set above in bool undoIt = undo.players.ContainsKey(name);).

So, for all players in the undo data, if their direction is not NONE, we set their current player state direction to the direction in the undo data. This effectively undoes their direction.

We also set their current player state steps to the number of steps in their undo data if it's not our default value of 99999999.

if (undoIt)
{
    u = undo.players[name];

    if (u.previous_dir != Direction.NONE)
    {
        p.dir = u.previous_dir;
    }

    if (u.previous_steps_left != 99999999)
    {
        p.steps_left = u.previous_steps_left;
    }
}

This effectively completes undoing the player's last move so we return back to the start of the loop, i.e.:

foreach (var mi in state.players)

That completes our loop over the players. The only remaining step to rewind 1 block is to remove all the new players that we stored in playersToRemove.

foreach (string nm in playersToRemove)
{
    state.players.Remove(nm);
}

Finally, we return the serialised GameState object so we can update the game state.

return JsonConvert.SerializeObject(state);

Your game will require more complex logic to undo a block, but the above should suffice to illustrate the general technique.

Clone this wiki locally