Unable to move items from one array to another

Hello all,

I’ve got a query that’s been bugging me for a while regarding the TypeScript runtime. There’s some methods that don’t quite behave like expected. The biggest one, that is causing me a lot of headache is moving items from one array to another. I’ll explain below.

My game has the following interfaces:

interface Pile {
  cardTypes: number[];
  cards: Card[];

interface Card {
  cardID: string;
  battleCardID: string;
  position: number;
  counters: number;

And the following function (I’ve tried about 5-6 different ways of achieving this, always the same result):

function moveElements(source: Pile, target: Pile, battlecardID: string) {

  for (var i = source.cards.length - 1; i >= 0; i--) {
    if (source.cards[i].battleCardID == battlecardID) {

      var elem = source.cards.splice(i, 1)[0];
      console.log("Moving card: " + elem.cardID);
      var cardsAdded = target.cards.push(elem);
      console.log("Cards Added: " + cardsAdded);


  console.log("Cards Left: " + source.cards.length);
  console.log("Cards In New Pile: " + target.cards.length);

I want to move a Card from one pile to another. This is an example of 2 piles:

var pile1: Pile = {
  "cardTypes": [ 7 ],
  "cards": [
      "battleCardID": "8ddc1899-6b05-46b0-bec3-fbc0d9cab368",
      "cardID": "RH-OVR-APU1",
      "counters": 0,
      "position": 4
      "battleCardID": "a1b751ba-cace-4110-9824-df1f93f12c36",
      "cardID": "RH-UNO-PCR1",
      "counters": 0,
      "position": 35
      "battleCardID": "1f7e09b7-2772-4a45-884d-71f69e4344f5",
      "cardID": "RH-OVR-BCR2",
      "counters": 0,
      "position": 5
      "battleCardID": "7c16482f-8132-4b15-a565-02899b24d263",
      "cardID": "RH-SYS-BCR1",
      "counters": 0,
      "position": 18
      "battleCardID": "7c16482f-8132-4b15-a565-02899b242263",
      "cardID": "RH-SYS-ACR2",
      "counters": 0,
      "position": 12


var pile2: Pile = { cardTypes: [], cards: [] };

This is the call that is supposed to move the card:


Testing the above in a couple of TS playgrounds prints the below (which is the correct behavior):

[LOG]: "Moving card: RH-OVR-BCR2"
[LOG]: "Cards Added: 1"
[LOG]: "Cards Left: 4"
[LOG]: "Cards In New Pile: 1"

But in the Nakama console, I get the following:

{"level":"debug","caller":"server/runtime_javascript_logger.go:104","msg":"Moving card: RH-OVR-BCR2"}
{"level":"debug","caller":"server/runtime_javascript_logger.go:104","msg":"Cards Added: 1"}
{"level":"debug","caller":"server/runtime_javascript_logger.go:104","msg":"Cards Left: 5"}
{"level":"debug","caller":"server/runtime_javascript_logger.go:104","msg":"Cards In New Pile: 0"}

In the match state in Nakama, the card is actually removed from the array, but leaves a null in its place. Pile2 still has a blank array.

Is this to do with something that Nakama processes differently internally? Something to do with the tsconfig compilerOptions (added below), perhaps? I’ve matched these in the TS Playground.

"compilerOptions": {
    "target": "es2015",
    "typeRoots": [
    "outFile": "./build/index.js",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true

Upgraded Typescript to v4.9.4
Upgraded nakama-runtime to v1.26.0

Changed compilerOptions to:

"compilerOptions": {
    "typeRoots": [
    "outFile": "./build/index.js",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2015",
    "jsx": "preserve",
    "moduleResolution": "node"

Same result.

Then I updated the moveElements function to:

function moveElements(source: Pile, target: Pile, battlecardID: string, logger: nkruntime.Logger) {

    var index = source.cards.findIndex(x => x.battleCardID == battlecardID);
    if (index >= 0) {
        var elem = source.cards.splice(index, 1)[0];

        logger.debug("Moving card: " + elem.cardID);
        var cardsAdded = target.cards.push(elem);
        logger.debug("Cards Added: " + cardsAdded);
    logger.debug("Cards Left: " + source.cards.length);
    logger.debug("Cards In New Pile: " + target.cards.length);

Hello @Antviss, are you on the latest version of Nakama (v3.15)? If not please upgrade and retry.

If the issue persists, it is most likely not the TS compiler options but within Nakama’s JS engine. If you could please put together a very simple snippet of code that reproduces the issue within the InitModule function so that we can test and debug, it would help us greatly.

The server was running v3.14. Upgraded to v3.15 to check, still the same.

Added the test function to InitModule but it returns correct values in the log. So it’s probably something to do with the Match Runtime.

I was worried that it might be a case of it not being able to complete during a match loop, but I set the tick rate to 1 and it still occurs.

For reference, this is the only logic that runs during a match loop. Isolated it to make sure there’s no other logic bit that acts upon it once it completes:

function FFA_matchLoop(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: FFA_MatchState, messages: nkruntime.MatchMessage[]): { state: FFA_MatchState; } | null {

    FFA_processMessages(messages, state, dispatcher, nk, logger);

    return {
const FFA_MessagesLogic: { [opCode: number]: (message: nkruntime.MatchMessage, gameState: FFA_MatchState, dispatcher: nkruntime.MatchDispatcher, nakama: nkruntime.Nakama, logger: nkruntime.Logger) => void } =
    5004: FFA_CARD_MOVED,

interface Player {
    presence: nkruntime.Presence;
    piles: Piles;
interface Piles {
    hand: Pile;
    plan: Pile;

interface CardMoved {
    fromPile: number,
    toPile: number,
    battleCardId: Card,
    position?: number

function FFA_CARD_MOVED(message: nkruntime.MatchMessage, gameState: FFA_MatchState, dispatcher: nkruntime.MatchDispatcher, nakama: nkruntime.Nakama, logger: nkruntime.Logger): void {

    var cardMoved: CardMoved = JSON.parse(nakama.binaryToString(message.data));
    gameState.players.forEach(_player => {
        if (_player.presence.userId == message.sender.userId) 
                moveElements(_player.piles.hand, _player.piles.plan, cardMoved.battleCardId, logger);
                dispatcher.broadcastMessage(FFA_OpCode.CARD_MOVED, JSON.stringify(cardMoved), [_player.presence], _player.presence);


Thank you very much for your reply!

Quick update. Doing something like the below works, but feels a bit hacky for my liking. This tells me that the issue has to do with age-old argument of “references vs values”. Don’t understand why it works ok during InitModule and any other Typescript environments I tried, but misbehaves during match loops.

var index = _player.piles.hand.cards.findIndex(x => x.battleCardID == found.battleCardID);
if (index >= 0) {

    var handArray: Card[] = JSON.parse(JSON.stringify(_player.piles.hand.cards));
    var planArray: Card[] = JSON.parse(JSON.stringify(_player.piles.plan.cards));

    logger.debug("Moving card: " + handArray[index].cardID);
    var cardsAdded = planArray.push(handArray[index]);
    handArray.splice(index, 1)[0];

    logger.debug("Cards Added: " + cardsAdded);

    _player.piles.hand.cards = handArray;
    _player.piles.plan.cards = planArray;

Unfortunately we couldn’t reproduce the issue using a simple match loop that appends to an array, would you be able to provide a very simple set of match handlers that manifest it?

i encountered that array.push() does not behave the way you expect it to. This may be a goja problem, but i dont know.

Try adding elements to array like this:

target.cards = { ...target.cards, elem };

(btw i use lodash to make life working with arrays much easier in generel)