How to set up a match label for match listings queries?

Hello,

A filter on match labels can be added to a match listing query, but I don’t see any way to add match labels, either when creating a match or after, via the WebSocket or REST APIs? Is it in anyway possible or should I open a feature request?

Thanks,
David

Hello @BimDav,

I’ll link examples from our tic-tac-toe project template.

Match labels are set from within the match handlers returning the initial label in the matchInit hook or using the dispatcher matchLabelUpdate function.

Hope this helps.

Thanks for the answer @sesposito,

If I understand correctly, both these solutions are using the server-side API for authoritative matches. I would like to do it via the WebSocket / Rest APIs. Is that possible?

Match labels are not supported from client relayed matches, see: Match name/label - #2 by zyro.

Hi, I bumped into this requirement and came up with this idea after not finding a solution in the forum. I have a simple game which doesn’t require authoritative game state (I can do a server side RPC validation at the end of each round). But it does require match discovery. My solution was for the host to create 2 matches: one client relay (created first) and one authoritative. When the authoritative match is created, include the match ID of the client relay match in the label and have the host join the match straight away. When the player is looking for matches, if they find one they like, then their device can connect to the client relay match via the ID in the label. I also configure the authoritative match not to accept more than 1 connection and to terminate when the single host player leaves. As a downside, this means the host runs 2 socket connections for the duration of the matching, but it does mean that the server isn’t tied up with game state management for a simple requirement. Hope it’s useful for someone.

My label looks like this (idm is the client relay match ID): {"minRank":0,"maxWordLength":10,"minPlayers":5,"roundLength":60,"maxRank":7,"minWordLength":3,"idm":"00536156-4ae1-4e17-9d48-f613376fb0c7.","maxPlayers":5,"numRounds":10}

My match handling code:

export function matchInitHandler(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}): {state: nkruntime.MatchState, tickRate: number, label: string} {
  const newLabel = JSON.stringify(params);
  logger.info('Starting match, parameters: %s', newLabel);
  return {
    state: { presences: {}, shouldTerminate: false, emptyTicks: 0 },
    tickRate: 1, // 1 tick per second = 1 MatchLoop func invocations per second
    label: newLabel
  };
};

export function matchJoinHandler (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  logger.debug('matchJoinHandler(): Match presences: %s, match state: %s', JSON.stringify(presences), JSON.stringify(state));

  presences.forEach(function (p) {
    state.presences[p.sessionId] = p;
  });

  return {
    state
  };
}

export function matchJoinAttemptHandler (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {[key: string]: any }) : {state: nkruntime.MatchState, accept: boolean, rejectMessage?: string | undefined } | null {
  logger.debug('matchJoinAttemptHandler(): Match presence: %s, match state: %s', JSON.stringify(presence), Object.keys(state.presences).length);

  return {
	  state,
    // We accept the match creator only
	  accept: Object.keys(state.presences).length == 0
	};
}

export function matchLeaveHandler(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  Object.keys(presences).forEach(function(key: any) {
    delete(state.presences[presences[key].sessionId]);
  });

  // We terminate the match when the host player leaves - this is just for match advertising purposes
  state.shouldTerminate = true

  return { state };
}

export function matchSignalHandler(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : { state: nkruntime.MatchState, data?: string } | null {
	return { state };
}

export function matchLoopHandler (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : { state: nkruntime.MatchState} | null {
  // If we have no presences in the match according to the match state, then terminate it
  // The authoritative matches are only used for match advertising, so shouldn't be active for very long
  logger.debug('!!!!GAME LOOP!!!!: Match state: %s, %s', JSON.stringify(state), state.presences.length);
  if (Object.keys(state.presences).length === 0) {
    state.emptyTicks++;
  } else {
    state.emptyTicks = 0;
  }

  if (state.shouldTerminate || state.emptyTicks >= 10) {
    logger.info('Termination requested or match empty for >= 10 ticks, requesting termination, id: %s, label: %s', state.matchId, state.label);
    return null;
  }
  return {
    state
  };
}

export function matchTerminate(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : { state: nkruntime.MatchState} | null {
  logger.info('Terminating match, id: %s, label: %s', state.matchId, state.label);
	return {
	  state
	};
}

Actually my mistake. There’s only one socket connection required.