How to use states for a player vs bot

Hi,

So our game has two modes , a player vs player and a player vs bot.

Both are server authoritative, even for the bot that must handle by the server.

At the moment, I wrote all the logic with custom rpc functions but it’s not working as intended.

The problem is that the values of the variables I’m using are not entirely changing, it can be changed in a function but as soon as another function needs access to that variable or the client needs access to it in order to decide what to do next, they only get the default value of that variable.

By digging here, I’ve read that we need to use match state in order to modify variables.

So I tried this in order to have variables that can be modified :

interface State 
{

testBoolean:boolean


// Match label
label: MatchLabel
// Ticks where no actions have occurred.
emptyTicks: number
// Currently connected users, or reserved spaces.
presences: {[userId: string]: nkruntime.Presence}
// Number of users currently in the process of connecting to the match.
joinsInProgress: number
// True if there's a game currently in progress.
playing: boolean
// Current state of the board.
board: Board
// Mark assignments to player user IDs.
marks: {[userId: string]: Mark | null}
// Whose turn it currently is.
mark: Mark
// Ticks until they must submit their move.
deadlineRemainingTicks: number
// The winner of the current game.
winner: Mark | null
// The winner positions.
winnerPositions: BoardPosition[] | null
// Ticks until the next game starts, if applicable.
nextGameRemainingTicks: number

}

var state:State;

But the compiler is giving me an error message :

error TS2741: Property ‘testBoolean’ is missing in type ‘{ label: MatchLabel; emptyTicks: number; presences: {}; joinsInProgress: number; playing: false; board: never; marks: {}; mark: Mark.UNDEFINED; deadlineRemainingTicks: number; winner: null; winnerPositions: null; nextGameRemainingTicks: number; }’ but required in type ‘State’.

If I remove my testBoolean variable, compile works.

But how exactly am I suppose to create variables that client can ask the server to change ?

@atv You’re on the right track!

You changed the State interface definition, now you should also change the concrete type that implements the interface a bit further down in the same code: nakama-project-template/match_handler.ts at master · heroiclabs/nakama-project-template · GitHub

That should fix your error and let you keep building your server-side logic. :+1:

Thanks.

If I write this :

let customFuncRpc: nkruntime.RpcFunction =
        function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
    logger.info('payload: %q', payload);

    
    let json = JSON.parse(payload);

    var label: MatchLabel = {
        open: 1,
        fast: 0,
    }

    var state: State = {
        label: label,
        emptyTicks: 0,
        presences: {},
        joinsInProgress: 0,
        playing: false,
        board: [],
        marks: {},
        mark: Mark.UNDEFINED,
        deadlineRemainingTicks: 0,
        winner: null,
        winnerPositions: null,
        nextGameRemainingTicks: 0,
        testBoolean:false,
    }

    

    let serverResponse=
    {
        serverResponse:serverResponseString,
        clientTestBoolean:state.testBoolean,
        


    }


    return JSON.stringify(serverResponse);
}

let testBooleanRpc: nkruntime.RpcFunction =
        function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
    logger.info('payload: %q', payload);

   
    let json = JSON.parse(payload);

    var s: State = state as State;

    let serverResponse=
    {
        clientTestBoolean:s.testBoolean,
        


    }


    return JSON.stringify(serverResponse);
}

Visual Code gives an error at line 100 :

var s: State = state as State;

ts2304 : cannot find name “state”

If I try to solve that issue by adding state in the parameters of my rpc function like this :

let testBooleanRpc: nkruntime.RpcFunction =
        function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string,state: nkruntime.MatchState,)

I get this error :

ts2322: Type ‘(ctx: Context, logger: Logger, nk: Nakama, payload: string, state: MatchState) => string’ is not assignable to type ‘RpcFunction’.

Do I really have to use all the premade functions made for match ?

I’d rather avoid them as first I don’t really see how to make them fit for a match vs a bot where I need to control the bot server side, I fear the match loop where players inputs will be if I understand correctly will be a mess of if statements and mostly I don’t see how to call these functions from the client from the template on Github.

Match state cannot be accessed from RPC functions. Match state belongs entirely to the match handler that created it, and can only be read/changed in the match handler functions.

You’ll need to implement all match handler functions, and your logic for changing state usually belongs in matchJoin, matchLeave, and matchLoop depending on what you need to do.

For example it’s a common pattern to have a message opcode case inside matchLoop where you receive a message with a particular opcode from clients and update the match state accordingly, based on which operation you’ve decided that opcode should indicate.

Ok, so I transfered my functions to the match handler.

I’ve seen that a client can create a match with a rpc function like this :

function rpcPairImpairPvBCreateMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {
    var matchId = nk.matchCreate('pairimpairvsbot', payload);
    return JSON.stringify({ matchId });
}

But in vscode, I have the following error for the payload variable :

Argument of type ‘string’ is not assignable to parameter of type ‘{ [key: string]: any; } | undefined’.

{ matchId } cannot be converted to valid JSON. Try something like { "match_id": matchId } instead.

It doesn’t change the problem, it’s in that line here :

var matchId = nk.matchCreate('pairimpairvsbot',payload);

the payload is underlined in red with the following error :

Argument of type ‘string’ is not assignable to parameter of type ‘{ [key: string]: any; } | undefined’.

I took it from that example in the docs :

function rpcCreateMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {
    var matchId = nk.matchCreate('pingpong', payload);
    return JSON.stringify({ matchId });
}

Ok, so I simply removed the payload as I saw it was markzdas optionnal. So now I don’t have any errors anymore and I can get back the match id so I can make the user join the match thanks to the match id.

Now I have a problem with the match loop as the server is not sending messages to the client and not doing what client asked for but I’ll get to it in another post, it’s really inconvenient to debug to have the main functions server side be on event tick.