3 players, 2 roles

223 views
Skip to first unread message

a.st...@gmail.com

unread,
May 11, 2023, 8:59:51 PM5/11/23
to nodeGame
Hi Stefano,

I hope you're doing well. I was hoping I could poke your brain regarding a logistical issue with extending a two-player game to include a third, shadow player.

As you may remember, our game involves two players alternating between the roles of sender and receiver on a round-by-round basis, implemented as follows:

stager.extendStep{'sender turn', ..
   roles: {
      SENDER: {},
       RECEIVER: {}
   }
}

stager.extendStep{'receiver turn', ..
   roles: {
      RECEIVER: {},
       SENDER: {}
   }
}

For example, p1 might be the sender in round 1 and the receiver in round 2. In this case, p2 would be the receiver in round 1 and the sender in round 2, and so on.

The question, however, is how I could add a third player to this scheme such that this player shadows p2. Thus, p3 would have to be a shadow receiver in round 1 and a shadow sender in round 2.

Currently, the MATCH_ROLES setting only includes the options SENDER and RECEIVER, but the game (rightfully) forces the specification of a third option. I'm thinking I need to use the assignerCb in logic.js somehow to duplicate for p3 whatever role is assigned to p2 in any trial (challenge #1). Then in player.js I would need to somehow infer which player is p3 (challenge #2) to ensure this player is only 'shadowing' the game, rather than co-playing it (i.e. their actions won't be visible to the other players).

Do you think this is the best way of going about it and, if so, how to address these challenges? For reference, the example on the wiki describes a similar situation involving a SOLO player/role. I've thought about going down that route but that would require distributing 4 roles across the 3 players (SENDER, RECEIVER, SHADOW_SENDER, SHADOW_RECEIVER). The aforementioned implementation appears more straightforward in this regard.

All the best,
Arjen



Stefano B

unread,
May 12, 2023, 3:40:32 AM5/12/23
to node...@googlegroups.com
Hi Arjen,

All good here, hope the same for you. 

Unfortunately, I do not fully understand what you want to achieve with "shadowing." Can you please explain it very plainly?

Cheers,
Stefano



--
You received this message because you are subscribed to the Google Groups "nodeGame" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodegame+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nodegame/a9f93914-83e1-4ce6-97aa-8be57e86f112n%40googlegroups.com.

Arjen Stolk

unread,
May 12, 2023, 8:53:25 AM5/12/23
to node...@googlegroups.com
Doing great, thanks.

Unfortunately, I do not fully understand what you want to achieve with "shadowing." Can you please explain it very plainly?

Apologies, it was probably too fancy of a term to describe the fact that this third player is playing the game unilaterally. That is, when they are the receiver, they get to see the movements of the sender (p1). However, their own actions as the receiver in the same around are, in return, not visible to p1. Likewise, when p3 is the sender, their receiver actions are also not visible to p1. Perhaps this schematic can help to convey what I had in mind.

shadow-01.png

Importantly, p3 still needs to be assigned the receiver role in round 1 and the sender role in round 2, etc. That is, they would have the same role as p2 on any given round. Unlike p2, p3's actions are never shown to p1, hence p3 is sort of shadow-playing the game.

I think that if we can assign p3 the same role as p2 in any round, this should solve challenge #1. The question then is how to do this in the logic using assignerCb and what to specify for MATCH_ROLES and the MATCH-ALGORITHM.

Best,
Arjen

 

shakty

unread,
May 15, 2023, 6:52:33 AM5/15/23
to nodeGame
Thanks for the clarification!

I would try a low level solution, in which you bypass the matching API for more control.

 I tested with the ultimatum game, I hope it's understandable so you can adapt it for your own game.

// LOGIC:

 stager.extendStep('bidder', {
        // Disable auto sync players with logic.
        syncStepping: false,
        init: function() {
             if (!node.game.p1) node.game.p1 = node.game.pl.db[0].id;
             if (!node.game.p2) node.game.p2 = node.game.pl.db[1].id;
             if (!node.game.shadow) node.game.shadow = node.game.pl.db[2].id;

                const targetStep = node.game.getCurrentGameStage();

                // Do matching accordingly.
                let p1Role, p2Role;
                if (targetStep.round % 2 === 0) {
                    p1Role = 'BIDDER';
                    p2Role = 'RESPONDER';
                }
                else {
                    p1Role = 'RESPONDER';
                    p2Role = 'BIDDER';
                }

                // P1.
                let remoteOptions = {
                    targetStep: targetStep,
                    plot: { role: p1Role }
                };
                node.remoteCommand('goto_step', node.game.p1, remoteOptions);

                //  P2.
                remoteOptions = {
                    targetStep: targetStep,
                    plot: { role: p2Role, shadow: false }
                };

                node.remoteCommand('goto_step', node.game.p2, remoteOptions);

                // Shadow P2.
                remoteOptions = {
                    targetStep: targetStep,
                    plot: { role: p2Role, shadow: true }
                };
                node.remoteCommand('goto_step', node.game.shadow, remoteOptions);
        }
    });

// PLAYER:

// Anywhere in the step callback:

 let shadow = node.game.getProperty('shadow');
                   
                    if (shadow) {
                         // ... 
                    }
                    else {
                        // ...
                    }


Let me know if it works and if you have questions.

Best,
Stefano

Stefano B

unread,
May 15, 2023, 7:01:04 AM5/15/23
to node...@googlegroups.com
I forgot something. If you are using the "partner" reference in player.js, you should set it in the remoteOptions, e.g.:

 plot: { role: p2Role, partner: node.game.p1 }



a.st...@gmail.com

unread,
May 16, 2023, 9:55:07 PM5/16/23
to nodeGame
Thanks so much for this suggestion! I wonder if there would be a simpler, maybe higher level solution. For instance, I think it would be fine if p3 is also assigned the Sender or Receiver role in each round as long as we make the node.say (partner notification) conditional on the player being p1 or p2 (and not p3, the shadow player).

Specifically, would it be possible to have the matching algorithm alternate the roles between just p1 and p2, and then copy p2's role onto p3? For example,

round 1: [Sender, Receiver, Receiver]
round 2: [Receiver, Sender, Sender]
round 3: [Sender, Receiver, Receiver]
and so on

Currently the roles are assigned at the roleassignment step in the logic:

stager.extendStep('roleassignment', {
matcher: {
roles: settings.MATCH_ROLES,
match: 'round_robin',
cycle: settings.MATCH_ALGORITHM,
assignerCb: assignerCb,
fixedRoles: fixedRoles
},
});
 
In the game.settings, the treatment is defined as:

MIN_PLAYERS: 3,
MATCH_ROLES: ['SENDER', 'RECEIVER', 'RECEIVER'], // human, human, overhearer
MATCH_ALGORITHM: 'repeat_invert', // repeats the same order of matching, but inverts the roles

Note, I'm not sure whether the round_robin and repeat_invert combo would do what we would want it to do. My hope would be that we can solve it with the assignerCb somehow? 

shakty

unread,
May 23, 2023, 9:04:55 AM5/23/23
to nodeGame
Hi Arjen,

Sorry for the delay, I was away for some time.

I think the approach I described before is not a bad one. 

It should be possible to do it with the assignerCb, but I feel you would need more roles (e.g., RECEIVER_SHADOW, and SENDER_SHADOW). 

Otherwise you also need to send additional information to the client, i.e., to let him know that it is just a shadow. You could do it with a msg during the same step or before. 

Another option is always going through the logic (perhaps is what you are doing already). E.g., players send messages to the logic (instead of another player), and the logic takes care of re-routing them if they are not from a shadow player.

Let me know your thoughts on this approaches.

Best,
Stefano

Arjen Stolk

unread,
May 23, 2023, 8:28:49 PM5/23/23
to node...@googlegroups.com
Hi Stefano,

No worries, hope you've had a nice break. I'm not actually sure which route would be preferable here. Just to step back maybe, considering the main example,
matcher: {
        roles: [ 'BIDDER', 'RESPONDENT', 'SOLO' ],
        match: 'roundrobin',
        cycle: 'repeat_invert'
How would you go about specifying the assignerCb to achieve the following ordering:
round 1: [BIDDERRESPONDENTSOLO]
round 2: [RESPONDENTBIDDERSOLO]
round 3: [BIDDERRESPONDENTSOLO]
...

That is, inverting solely the first and second players' roles? I think that if we can solve that, this would go some ways in the right direction already.

Best wishes,
Arjen




You received this message because you are subscribed to a topic in the Google Groups "nodeGame" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodegame/4S_l4qQlm-A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodegame+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nodegame/b5024943-0731-462a-94eb-0d380118b5e1n%40googlegroups.com.

shakty

unread,
May 24, 2023, 6:06:23 AM5/24/23
to nodeGame
Hi,

The matcher API is currently a bit limited, but it's possible to achieve what you want, even though the syntax is not so intuitive.

See below and let me know.

Cheers,
Stefano

    stager.setOnInit(function() {
        const pl = node.game.pl;
        node.game.P1 =  pl.db[0].id;
        node.game.P2 =  pl.db[1].id;
        node.game.SHADOW =  pl.db[2].id;
        // assignerCb is executed before entering the new step, so round
        // won't be be accurate in there, and we keep track here.
        node.game.shadowCounter = 0;
    });

// In the relevant step:

        matcher: {
            roles: [ 'BIDDER', 'RESPONDER', 'SOLO' ],
            match: 'roundrobin', 
            rounds: 1,
            reInit: true,
            assignerCb: function(arrayIds) {
                // round 1: [BIDDER, RESPONDENT, SOLO]
                // round 2: [RESPONDENT, BIDDER, SOLO]
                // round 3: [BIDDER, RESPONDENT, SOLO]
                // ...
                let round = ++node.game.shadowCounter;
                // The order of what is returned here must match the internal matches of nodegame,
                // which for round 1 of round robin is: [ p0, MISS ], [ p1, p2 ]
                // Because the first match has a miss, it is going to be assigned the SOLO role (the last one). 
                if (round % 2 === 1) {
                    return [ node.game.SHADOW, node.game.P1, node.game.P2 ];
                }
                else {
                    return [ node.game.SHADOW, node.game.P2, node.game.P1 ];
                }
            }
        }

Arjen Stolk

unread,
Jun 18, 2023, 9:17:22 PM6/18/23
to node...@googlegroups.com
Thanks again for thinking along. I went ahead and tried out this suggestion, and it works up to the point where SHADOW needs to receive information from P1. The reason why this does not work is because P1 and P2 are partners, but SHADOW is not (in fact, console.log(this.partner) for SHADOW returns 'bot' rather than a player ID). As a result, node.say('done', this.partner) does not convey any information to SHADOW from P1, and the game cannot continue.

I've tried to bypass this issue using matcher.getMatches, but this method seems to be designed for the logic, not the player code. Any ideas? Is the issue due to our implementation creating two dyads ([ p0, MISS ], [ p1, p2 ]) rather than a single triad? Is the node.say method capable of sending messages to more than one partner? Should I maybe try the solution listed here?

There are probably several ways of going about it, hence your advice would be appreciated!



Stefano B

unread,
Jun 19, 2023, 6:04:44 AM6/19/23
to node...@googlegroups.com
Hi Arjen,

Yes the Matcher API only works on dyads, so playing in a triad is a bit forced. We would need to pass the info of the shadow players to the other ones. Two approaches:

a) The logic should send the id of the shadow early on to the other players, then he specific player simply send an additional msg to shadow [ or you specify the ids in an array: node.say('xxx', [ this.parner, this.shadow ]) ]

b) The logic re-routes the incoming msgs to shadow:

node.on.done(function(msg) { if (...) node.say('xxx', node.game.shadow) };

The approaches are mostly equivalent. Personally, b) is a bit better because it does not disclose the id of the shadow to the other players, but that should not be the deciding factor.

Cheers,
Stefano

Arjen Stolk

unread,
Jun 19, 2023, 10:02:00 PM6/19/23
to node...@googlegroups.com
Okay thanks, that makes sense. 

This may be more of an elegance issue than of pure functionality, but currently player.js features a SHADOW/SOLO role in each step that essentially does if P2 == sender, do the sender thing for SHADOW, else if P2 == receiver, do the receiver thing. Going back to your code suggestion (thanks!), would it be possible still to have the following ..

                // round 1: [BIDDER, RESPONDENT, SOLO]
                // round 2: [RESPONDENT, BIDDER, SOLO]
                // round 3: [BIDDER, RESPONDENT, SOLO]
  
.. behave as ..

                // round 1: [BIDDER, RESPONDENT, RESPONDENT]
                // round 2: [RESPONDENT, BIDDER, BIDDER]
                // round 3: [BIDDER, RESPONDENT, RESPONDENT]

Could this be achieved by having "roles: [ 'BIDDER', 'RESPONDER', 'SOLO' ]" getting changed on each round maybe?

I figure this would additionally allow us to flexibly move the SOLO around the triad, instead of it always being P3.

Stefano B

unread,
Jun 20, 2023, 4:32:12 AM6/20/23
to node...@googlegroups.com
Hi Arjen,

I am a bit confused. I understood the setup until your question, but I thought our goal was to fix P3 = SOLO across rounds. Has this changed in the meantime?

Cheers,
Stefano

Arjen Stolk

unread,
Jun 20, 2023, 8:35:26 AM6/20/23
to node...@googlegroups.com
Hi Stefano,

You understood correctly, I was simply thinking of possible applications where we may want to have that functionality. Given that it probably requires changing low-level code, do you think this feature is perhaps best saved for a future version of nodegame?

As a possible short term solution, would it be possible to update the roles (rather than IDs) each round in the assignerCb, or else in the matcher directly? If not, I'll be pursuing the SOLO implementation further.

Best,
Arjen

Stefano Balietti

unread,
Jun 20, 2023, 8:40:40 AM6/20/23
to node...@googlegroups.com
Hi,

Thanks for the quick reply. Something I still do not understand: if we don't use the custom callback but instead we use the default matcher behavior, roles are rotated through the ids. Isn't that equivalent to updating the ids? 

Cheers,
Stefano 

shakty

unread,
Jun 20, 2023, 8:44:37 AM6/20/23
to nodeGame
Generally, I feel these type of back and forth interactions do not work well with this forum. I was planning to do it for some time, but here is a Discord server for a more fluent experience: https://discord.gg/dDfRPcDA

Will make a somewhat formal announcement later, but people can still join and ask questions there (in addition to here).


Arjen Stolk

unread,
Jun 20, 2023, 8:47:17 AM6/20/23
to node...@googlegroups.com
It would be great to have a quick back and forth. I'll look into the Discord server.

To answer your question here, I think one possible obstacle is that the distribution of roles changes on a round-by-round basis. For example,

                // round 1: [BIDDER, RESPONDENT, RESPONDENT]  >> 2 RESPONDENTS
                // round 2: [RESPONDENT, BIDDER, BIDDER]             >> 2 BIDDERS
                // round 3: [BIDDER, RESPONDENT, RESPONDENT]  >> 2 RESPONDENTS

Reply all
Reply to author
Forward
0 new messages