Page 1 of 1

Active or multiactive player state ?

Posted: 30 July 2024, 09:30
by Matux38
Hi,

I'm working on an adaptation of Timebomb and I was wondering if I should use multipleactiveplayer or activeplayer state.

Basically, the game only allow 1 person to make an action at a time. But, at any moment, anyone can make an announce that has to be shown in the interface to other players, without blocking the game.

I was trying at first to be in activeplayer state and allow 2 other "side" actions but the message "it's not your turn" appears, even if I don't check the state in the game.php function.

What's the best way to handle this then please ?

Thanks :)

Re: Active or multiactive player state ?

Posted: 30 July 2024, 13:43
by Tisaac
Matux38 wrote: 30 July 2024, 09:30 Hi,

I'm working on an adaptation of Timebomb and I was wondering if I should use multipleactiveplayer or activeplayer state.

Basically, the game only allow 1 person to make an action at a time. But, at any moment, anyone can make an announce that has to be shown in the interface to other players, without blocking the game.

I was trying at first to be in activeplayer state and allow 2 other "side" actions but the message "it's not your turn" appears, even if I don't check the state in the game.php function.

What's the best way to handle this then please ?

Thanks :)
Dont use checkAction on js or backend, use checkPossibleAction instead as described in the doc for this kind of cases

Re: Active or multiactive player state ?

Posted: 30 July 2024, 14:19
by Matux38
Yeah, it looks like it's exactly what I'm looking for :o

However, it still shows "It is not your turn", even with this function call in XXX.game.php:

Code: Select all

function announce(...) {
$this->gamestate->checkPossibleAction('announce'); // announce = possible action in my game state file
$this->gamestate->setPlayersMultiactive( array($this->getCurrentPlayerId()), 'error', false); // Not sure about this line, but still the same without it
...
}
Is there anything else to do to make it work?

Re: Active or multiactive player state ?

Posted: 30 July 2024, 14:43
by thoun
You're probably doing the same kind of check in front side (js) too, as he said ;)

Check the network tab, if you don't see a new action triggering there on your click, the blocking is made in JS.

Re: Active or multiactive player state ?

Posted: 30 July 2024, 15:08
by Matux38
You're right, in the network tab, there's no AJAX call and therefore it's in my js file.
I looked at it a lot and I can't figure out why tho :(

I thought it may be cause by the default content in onUpdateActionButtons with the condtion on this.isCurrentPlayerActive() but commenting lines didn't solve the issue. The other parts of the file look fine. Maybe I don't see something?

I hate dumping code but here, as I don't really know which part is concerned, I think it's better to entirely link it:

Code: Select all

/**
 *------
 * BGA framework: Gregory Isabelli & Emmanuel Colin & BoardGameArena
 * TimebombBGA implementation : © <Your name here> <Your email address here>
 *
 * This code has been produced on the BGA studio platform for use on http://boardgamearena.com.
 * See http://en.boardgamearena.com/#!doc/Studio for more information.
 * -----
 *
 * timebombbga.js
 *
 * TimebombBGA user interface script
 * 
 * In this file, you are describing the logic of your user interface, in Javascript language.
 *
 */

define([
    "dojo","dojo/_base/declare",
    "ebg/core/gamegui",
    "ebg/counter"
],
function (dojo, declare) {
    return declare("bgagame.timebombbga", ebg.core.gamegui, {
        constructor: function(){
            console.log('timebombbga constructor');
              
            // Here, you can init the global variables of your user interface
            // Example:
            // this.myGlobalValue = 0;

        },
        
        /*
            setup:
            
            This method must set up the game user interface according to current game situation specified
            in parameters.
            
            The method is called each time the game interface is displayed to a player, ie:
            _ when the game starts
            _ when a player refreshes the game page (F5)
            
            "gamedatas" argument contains all datas retrieved by your "getAllDatas" PHP method.
        */
        
        setup: function( gamedatas )
        {
            console.log( "Starting game setup" );

            this.updateBoard(gamedatas);

            // Setup game notifications to handle (see "setupNotifications" method below)
            this.setupNotifications();

            console.log( "Ending game setup" );
        },
       

        ///////////////////////////////////////////////////
        //// Game & client states
        
        // onEnteringState: this method is called each time we are entering into a new game state.
        //                  You can use this method to perform some user interface changes at this moment.
        //
        onEnteringState: function( stateName, args )
        {
            console.log( 'Entering state: '+stateName );
            
            switch( stateName )
            {
                case 'playerTurn':
                    console.log("onEnteringState", stateName, args);
                    // Remove all event listeners and classes
                    let cards = document.querySelectorAll('.card-clickable');
                    cards.forEach(card => {
                        card.classList.remove('card-clickable');
                    });

                    // Add class to cards of everyone else for the current player to play
                    if (this.isCurrentPlayerActive()) {
                        let boards = document.getElementsByClassName('board');
                        // For each board of boards
                        for (let i = 0; i < boards.length; i++) {
                            let board = boards[i];
                            if (board.getAttribute('data-player-id') === args.active_player) {
                                continue;
                            }
                            let cardsDiv = board.querySelectorAll('.card');
                            cardsDiv.forEach(card => {
                                if (card.classList.contains('card-state-hidden')) card.classList.add('card-clickable');
                            });
                        }
                    }
                    break;
            }
        },

        // onLeavingState: this method is called each time we are leaving a game state.
        //                 You can use this method to perform some user interface changes at this moment.
        //
        onLeavingState: function( stateName )
        {
            console.log( 'Leaving state: '+stateName );
            
            switch( stateName )
            {
                case 'dummmy':
                    break;
            }
        },
        updateBoard: function(gamedatas) {
            console.log("updateBoard", gamedatas);
            // Empty boards div
            let boardsDiv = document.getElementById('boards');
            boardsDiv.innerHTML = '';

            let activePlayerId = gamedatas.active_player;

            let orderedAnnounces = [];
            for (let i = 0; i < gamedatas.announces.length; i++) {
                let announce = gamedatas.announces[i];
                orderedAnnounces[announce['player_id']+'_'+announce['type']] = announce['qty'];
            }

            // Setting up player boards
            for( var player_id in gamedatas.players )
            {
                let player = gamedatas.players[player_id];
                let ucRole = this.ucfirst(player.role);

                let playerBoardDiv = $('player_board_'+player_id);
                dojo.place(this.format_block('jstpl_player_board', player), playerBoardDiv);

                let roleDiv = playerBoardDiv.getElementsByClassName('role_'+player_id)[0];
                let wiresDiv = playerBoardDiv.getElementsByClassName('wires_'+player_id)[0];
                let bombDiv = playerBoardDiv.getElementsByClassName('bomb_'+player_id)[0];

                roleDiv.classList.add('role-'+player.role);

                let announceWires = orderedAnnounces[player_id+'_wire'];
                if (announceWires !== undefined) {
                    wiresDiv.innerHTML = announceWires;
                }
                let announceBomb = orderedAnnounces[player_id+'_bomb'];
                if (announceBomb !== undefined) {
                    bombDiv.innerHTML = announceBomb;
                }

                let cardsForPlayer = gamedatas.cards.filter(card => card.owner === player_id);

                // Div for player's board
                let boardDiv = document.createElement('div');
                boardDiv.classList.add('board', 'flex', 'flex-col', 'items-center', 'gap-2', 'p-2', 'whiteblock');
                boardDiv.setAttribute('data-player-id', player_id);
                if (player_id === activePlayerId) {
                    boardDiv.classList.add('active-player');
                }
                boardsDiv.appendChild(boardDiv);

                // Div for player's name
                let playerNameDiv = document.createElement('h3');
                playerNameDiv.classList.add('text-center');
                playerNameDiv.textContent = player.name;
                boardDiv.appendChild(playerNameDiv);

                // Div container for player's cards
                let cardsDivContainer = document.createElement('div');
                cardsDivContainer.classList.add('flex', 'items-center', 'gap-2', 'border');
                boardDiv.appendChild(cardsDivContainer);

                // Each div for player's card
                cardsForPlayer.forEach(card => {
                    let cardImg = document.createElement('div');
                    cardImg.classList.add('card', 'card-'+card.type);
                    cardImg.setAttribute('data-card-id', card.id);
                    cardImg.classList.add('card-state-'+card.state);
                    cardsDivContainer.appendChild(cardImg);
                });
            }

            document.querySelectorAll('.card').forEach(square => square.addEventListener('click', e => this.onCardClick(e)));

            document.querySelectorAll('.announceButton').forEach(div => div.addEventListener('click', e => this.announce(e)));
        },

        // onUpdateActionButtons: in this method you can manage "action buttons" that are displayed in the
        //                        action status bar (ie: the HTML links in the status bar).
        //        
        onUpdateActionButtons: function( stateName, args )
        {
            console.log( 'onUpdateActionButtons: '+stateName );
                      
            if( this.isCurrentPlayerActive() )
            {
                switch( stateName )
                {
/*
                 Example:

                 case 'myGameState':

                    // Add 3 action buttons in the action status bar:

                    this.addActionButton( 'button_1_id', _('Button 1 label'), 'onMyMethodToCall1' );
                    this.addActionButton( 'button_2_id', _('Button 2 label'), 'onMyMethodToCall2' );
                    this.addActionButton( 'button_3_id', _('Button 3 label'), 'onMyMethodToCall3' );
                    break;
*/
                }
            }
        },        

        ///////////////////////////////////////////////////
        //// Utility methods
        
        /*
        
            Here, you can defines some utility methods that you can use everywhere in your javascript
            script.
        
        */

        ucfirst: function(str) {
            if (str.length === 0) {
                return str;
            }
            return str.charAt(0).toUpperCase() + str.slice(1);
        },


        ///////////////////////////////////////////////////
        //// Player's action
        
        /*
        
            Here, you are defining methods to handle player's action (ex: results of mouse click on 
            game objects).
            
            Most of the time, these methods:
            _ check the action is possible at this game state.
            _ make a call to the game server
        
        */

        onCardClick: function(e) {
            e.preventDefault();
            e.stopPropagation();
            let card = e.target;
            if (!card.classList.contains('card-clickable')) return;
            let card_id = card.getAttribute('data-card-id');
            this.bgaPerformAction("cut", {
                card_id: card_id
            });
        },

        announce: function(e) {
            e.preventDefault();
            e.stopPropagation();
            let button = e.target;
            let qty = button.getAttribute('data-qty');
            let type = button.getAttribute('data-type');
            this.bgaPerformAction("announce", {
                type: type,
                qty: qty
            });
        },

        ///////////////////////////////////////////////////
        //// Reaction to cometD notifications

        /*
            setupNotifications:
            
            In this method, you associate each of your game notifications with your local method to handle it.
            
            Note: game notification names correspond to "notifyAllPlayers" and "notifyPlayer" calls in
                  your timebombbga.game.php file.
        
        */
        setupNotifications: function()
        {
            console.log( 'notifications subscriptions setup' );

            const notifs = [
                ['cut', 500],
                ['nextround', 2000],
                ['goodteam', 100],
                ['badteam', 100],
                ['beforegameend', 5000],
            ];

            notifs.forEach((notif) => {
                dojo.subscribe(notif[0], this, `notif_${notif[0]}`);
                this.notifqueue.setSynchronous(notif[0], notif[1]);
            });
        },
        notif_cut: function(notif) {
            let card_id = notif.args.card_id;
            let card_type = notif.args.card_type;
            let card = document.querySelector(`.card[data-card-id="${card_id}"]`);
            card.classList.remove('card-unknown');
            card.classList.add(`card-${card_type}`);
            card.classList.remove('card-state-hidden');
            card.classList.add('card-state-revealed');
        },
        notif_nextround: function(notif) {
            let gamedatas = notif.args.gamedatas;
            this.updateBoard(gamedatas);
        },
        notif_beforegameend: function(notif) {
            let gamedatas = notif.args.gamedatas;
            this.updateBoard(gamedatas);
        },
        notif_goodteam: function(notif) {
        },
        notif_badteam: function(notif) {
        },
   });
});
Do you see where I made a mistake in it?

Once again, thanks!

Re: Active or multiactive player state ?

Posted: 30 July 2024, 15:54
by thoun
bgaPerformAction does checkAction by default. Check the doc to know how to disable the check ;)

Re: Active or multiactive player state ?

Posted: 30 July 2024, 16:00
by Matux38
Oh my! I'm so dumb!

Yeah, with the correct parameter, it works exactly as expected yeah...

Code: Select all

this.bgaPerformAction("announce", {
                type: type,
                qty: qty
            }, { checkAction: false });
Thanks for the help :)