This is a documentation for Board Game Arena: play board games online !

Your game state machine: states.inc.php: Розніца паміж версіямі

З пляцоўкі Board Game Arena
Перайсці да навігацыі Перайсці да пошуку
др (→‎args: Fix typo)
 
(Не паказана 58 прамежкавых версій 15 удзельнікаў)
Радок 1: Радок 1:
{{Studio_Framework_Navigation}}


This file describes the game states machine of your game (all the game states properties, and the transitions to get from one state to another).
This file describes the state machine of your game (all the game states properties, and the transitions to get from one state to another).


Important: to understand the game state machine, the best is to read this presentation first:
Important: to understand the game state machine, it's recommended that you read this presentation first:


[http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine]
[http://www.slideshare.net/boardgamearena/bga-studio-focus-on-bga-game-state-machine Focus on BGA game state machine]
Радок 8: Радок 9:
== Overall structure ==
== Overall structure ==


The machine states is described by a PHP associative array.
The machine states are described by a PHP associative array.


Example:
Example:
Радок 40: Радок 41:
=== id ===
=== id ===


The keys determine game states IDs (in the example above: 1 and 2).
The keys determine game state IDs (in the example above: 1 and 2).


IDs must be positive integers.
IDs must be positive integers.
Радок 46: Радок 47:
ID=1 is reserved for the first game state and should not be used (and you must not modify it).
ID=1 is reserved for the first game state and should not be used (and you must not modify it).


ID=99 is reserved for the last game state of the game (end of the game) (and you must not modify it).
ID=99 is reserved for the last game state (end of the game) (and you must not modify it).
 
Note: you may use any ID, even an ID greater than 100. But you cannot use 1 or 99.
 
Note²: You must not use the same ID twice.
 
Note³: When a game is in prod and you change the ID of a state, all active games (including many turn based) will behave unpredictably.


=== name ===
=== name ===


(mandatory)
('''Mandatory''')


The name of a game state is used to identify it in your game logic.
The name of a game state is used to identify it in your game logic.


Several game states can share the same name, however this is not recommended.
Several game states can share the same name; however, this is not recommended.
 
Warning! Do not put spaces in the name. This could cause unexpected problems in some cases.


PHP example:
PHP example:
Радок 85: Радок 94:
=== type ===
=== type ===


(mandatory)
('''Mandatory''')


You can use 3 types of game states:
You can use 3 types of game states:
* activeplayer (1 player is active and must play)
* activeplayer (1 player is active and must play.)
* multipleactiveplayer (1..N players can be active and must play)
* multipleactiveplayer (1..N players can be active and must play.)
* game (no player is active. This is a transitional state to do something automatic specified by game rules)
* game (No player is active. This is a transitional state to do something automatic specified by the game rules.)
 
'''Note:''' Make sure you don't mistype the value of this attribute. If you do (e.g. 'multiactiveplayer' instead of 'multipleactiveplayer'), things won't work, and you might have a hard time figuring out why.


=== description ===
=== description ===


(mandatory)
('''Mandatory''')


The description is the string that is displayed in the main action bar (top of the screen) when the state is active.
The description is the string that is displayed in the main action bar (top of the screen) when the state is active.


When a string is specified as a description, you must use "clienttranslate" in order the string can be translate on the client side:
When a string is specified as a description, you must use "clienttranslate" in order for the string to be translated on the client side:


<pre>
<pre>
Радок 106: Радок 117:
In the description string, you can use ${actplayer} to refer to the active player.
In the description string, you can use ${actplayer} to refer to the active player.


You can also use custom arguments in your description. These custom arguments correspond to values returned by your "arg" PHP method (see below "arg" field).
You can also use custom arguments in your description. These custom arguments correspond to values returned by your "args" PHP method (see below "args" field).


Example of custom field:
Example of custom field:
Радок 113: Радок 124:
In states.inc.php:
In states.inc.php:
         "description" => clienttranslate('${actplayer} must choose ${nbr} identical energies'),
         "description" => clienttranslate('${actplayer} must choose ${nbr} identical energies'),
         "arg" => "argMyArgumentMethod"
         "args" => "argMyArgumentMethod"


In mygame.game.php:
In mygame.game.php:
Радок 124: Радок 135:
</pre>
</pre>


Note: You may specify an empty string ("") here if it never happens that the game remains in this state (ie: if this state immediately jump to another state when activated).
Note: You may specify an empty string ("") here if it never happens that the game remains in this state (i.e., if this state immediately jumps to another state when activated).


Note²: Usually, you specify a string for "activeplayer" and "multipleactiveplayer" game states, and you specify an empty string for "game" game states. BUT, if you are using synchronous notifications, the client can remains few seconds on a "game" type game state, and in this case this may be useful to display a description in the status bar during this state.
Note²: Usually, you specify a string for "activeplayer" and "multipleactiveplayer" game states, and you specify an empty string for "game" game states. BUT, if you are using synchronous notifications, the client can remain on a "game" type game state for a few seconds, and in this case it may be useful to display a description in the status bar while in this state.


=== descriptionmyturn ===
=== descriptionmyturn ===


(mandatory for "activeplayer" and "multipleactiveplayer" game states type)
('''Mandatory''' when the state type is "activeplayer" or "multipleactiveplayer")


"descriptionmyturn" has exactly the same role and properties than "description", except that this value is displayed to the current active player - or to all active players in case of a multipleactiveplayer game state.
"descriptionmyturn" has exactly the same role and properties as "description", except that this value is displayed to the current active player - or to all active players in case of a multipleactiveplayer game state.


In general, we have this situation:
In general, we have this situation:
Радок 141: Радок 152:
</pre>
</pre>


Note: you can use ${you} in description my turn in order the description can display "You" instead of the name of the player.
Note: you can use ${you} in descriptionmyturn so the description will display "You" instead of the name of the player.
 
=== action ===
 
('''Mandatory''' when the state type is "game.")
 
"action" specifies a PHP method to call when entering this game state.
 
Example:
In states.inc.php:
<pre>
    28 => array(
        "name" => "gameTurnNextPlayer",
        "description" => clienttranslate('Updating some stuff...'),
        "type" => "game",
        "action" => "st_gameTurnNextPlayer",
</pre>
In mygame.game.php:
<pre>
    function st_gameTurnNextPlayer() {
        $player_id = $this->getActivePlayerId();
        $next_player_id = $this->getPlayerAfter($player_id);
        $this->giveExtraTime($next_player_id);
        $this->incStat(1, 'turns_number', $next_player_id);
        $this->incStat(1, 'turns_number');
        $this->gamestate->changeActivePlayer($next_player_id);
        $this->gamestate->nextState('next');
    }
</pre>
 
Usually, for a "game" state type, the action method is used to perform automatic functions specified by the rules (for example: check victory conditions, deal cards for a new round, go to the next player, etc.) and then jump to another game state.
 
Note: a BGA convention specifies that PHP methods called with "action" are prefixed by "st".
 
Note: this field CAN be used for player states to set something up; e.g., for multiplayer states, it can make all players active.
 
Example for action in player state:
in states.inc.php:
<pre>
    2 => array(
    "name" => "playerTurnPlace",
    "description" => clienttranslate('Other player must place ships'),
    "descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),
    "type" => "multipleactiveplayer",
            'action' => 'st_MultiPlayerInit',
            'args' => 'arg_playerTurnPlace',
    "possibleactions" => array( "playPlace" ),
            "transitions" => array( "next" => 4, "last" => 99)
    ),
</pre>
in mygame.game.php:
<pre>
    function st_MultiPlayerInit() {
        $this->gamestate->setAllPlayersMultiactive();
    }
</pre>
 
=== transitions ===
 
('''Mandatory''')
 
With "transitions" you specify which game state(s) you can jump to from a given game state.
 
Example:
<pre>
    25 => array(
        "name" => "myGameState",
        "transitions" => array( "nextPlayer" => 27, "endRound" => 39 ),
        ....
    }
</pre>
 
In the example above, if "myGameState" is the current active game state, I can jump to the game state with ID 27 or the game state with ID 39.
 
Example to jump to ID 27:
<pre>
In mygame.game.php:
    $this->gamestate->nextState( "nextPlayer" );
</pre>
 
Important: "nextPlayer" is the name of the transition, and NOT the name of the target game state. Multiple transitions can lead to the same game state.
 
Note: If there is only 1 transition, you may give it an empty name.
 
Example:
<pre>
In states.inc.php:
    "transitions" => array( "" => 27 ),
 
In mygame.game.php:
    $this->gamestate->nextState(  );    // We don't need to specify a transition as there is only one here
</pre>
 
=== possibleactions ===
 
('''Mandatory''' when the game state is "activeplayer" or "multipleactiveplayer")
 
"possibleactions" defines the actions possible by the players in this game state, and ensures they cannot cannot perform actions that are not allowed in this state.
 
Example:
 
<pre>
In states.game.php:
      "possibleactions" => array( "playCard", "pass" ),
 
In mygame.game.php:
        function playCard( ...)
        {
            self::checkAction( "playCard" );    // Will fail if "playCard" is not specified in "possibleactions" in the current game state.
 
            ....
 
In mygame.js:
        playCard: function( ... )
        {
            if( this.checkAction( "playCard" ) ) // Will fail if "playCard" is not specified in "possibleactions" in the current game state.
            {  return ;  }
 
            ....
 
</pre>
 
=== args ===
 
(optional)
 
Sometimes it happens that you need some information on the client side (i.e., for your game interface) only for a specific game state.
 
Example 1: in ''Reversi'', the list of possible moves during the playerTurn state.
 
Example 2: in ''Caylus'', the number of remaining king's favors to choose in the state where the player is choosing a favor.
 
Example 3: in ''Can't Stop'', the list of possible die combinations to be displayed to the active player so that he can choose from among them.
 
In such a situation, you can specify a method name as the « args » argument for your game state. This method must retrieve some piece of information about the game (example: for ''Reversi'', the list of possible moves) and return it.
 
Thus, this data can be transmitted to the clients and used by the clients to display it. It should always be an associative array.
 
Let's see a complete example using args with ''Reversi'' game:
 
In states.inc.php, we specify an '''args''' argument for gamestate '''playerTurn''':
<pre>
    10 => array(
        "name" => "playerTurn",
"description" => clienttranslate('${actplayer} must play a disc'),
"descriptionmyturn" => clienttranslate('${you} must play a disc'),
        "type" => "activeplayer",
        "args" => "argPlayerTurn",    // <==== HERE
        "possibleactions" => array( 'playDisc' ),
        "transitions" => array( "playDisc" => 11, "zombiePass" => 11 )
    ),
</pre>
 
It corresponds to a '''argPlayerTurn''' method in our PHP code (reversi.game.php):
<pre>
    function argPlayerTurn() {
        return array(
            'possibleMoves' => self::getPossibleMoves()
        );
    }
</pre>
 
Then, when we enter into the '''playerTurn''' game state on the client side, we can highlight the possible moves on the board using information returned by '''argPlayerTurn''':
 
<pre>
        onEnteringState: function( stateName, args )  {
          console.log( 'Entering state: '+stateName );
           
            switch( stateName )  {
            case 'playerTurn':
                this.updatePossibleMoves( args.args.possibleMoves );
                break;
            }
        },
</pre>
 
==== Naming and API conventions ====
 
As a BGA convention, PHP methods called with "args" are prefixed by "arg" followed by state name (example: '''argPlayerTurn''').
 
All "arg*" methods are put into corresponding section of .game.php file commented as
  //////////// Game state arguments
 
Arg method MUST return array. Return integer or string will result in some undebugguggable exceptions.
 
Arg method MUST be defined in php if it is declared in state description, if you don't need it comment it out from the states.inc.php file (the '''args''' parameter). If you not sure if you need it or not you can keep it returning empty array until you figure it out.
 
    function argPlayerTurn() {
        return array(); // must be an array
    }
 
 
'''Warning''': the "args" method can be called before the "action" method so don't expect data modifications by the "action" method to be available in the "args" method!
Also don't modify database in this method!
 
You should NOT be calling getCurrentPlayer() in the context of this function, since state transitions are broadcasted to all player independent of who initiated it. Instead you should send information on per player basis (Note: if this is private info see section below).
 
If you using this function in multi-player state, you should not call getActivePlayer() either, you should send per player info:
 
    function arg_playerTurn() {
        $res = array ();
        $players = $this->loadPlayersBasicInfos();
        foreach ( $players as $player_id => $player_info ) {
            $color = $player_info ['player_color'];
            $res [$player_id] = array("color"=>$color);
        }
        return $res;
    }
 
Note: you never need to send player color like this, this is just an example.
 
Note 2: you CAN call methods that return all active players if multi-player states if its relevant.
 
 
==== Other usages ====
 
You can use values returned by your "args" method to have some custom values in your "description"/"descriptionmyturn", i.e. in states.inc.php:
 
  "descriptionmyturn" => clienttranslate('${you} must play ${color} disc'),
 
So arg function will be something like:
 
  function argPlayerTurn() {
        return array('color'=>this->getActivePlayerColor());
  }
 
You can use args also in '''onUpdateActionButtons''' function on js side, however two important notes:
* it is just '''args''' (not args.args like in '''onEnteringState''')
* it is args of the current state, which may not be state you think it will be! This method is called during change of active player, which happens in some weird place especially during "multipleactiveplayer" states. If you pulling your hair thinking why its "undefined" on js side - check the current state.
 
==== Private info in args ====
 
By default, all data provided through this method are PUBLIC TO ALL PLAYERS. Please do not send any private data with this method, as a cheater could see it even it is not used explicitly by the game interface logic.
 
However, it is possible to specify that some data should be sent to specific players only.
 
Example 1: send information to active player(s) only:
 
<pre>
    function argPlayerTurn()  {
        return array(
            '_private' => array(          // Using "_private" keyword, all data inside this array will be made private
                'active' => array(      // Using "active" keyword inside "_private", you select active player(s)
                    'somePrivateData' => self::getSomePrivateData()  // will be send only to active player(s)
                )
            ),
            'possibleMoves' => self::getPossibleMoves()          // will be sent to all players
        );
    }
</pre>
 
Inside the js file, these variables will be available through `args.args._private`. (e.g. `args.args._private.somePrivateData` -- it is not `args._private.active.somePrivateData` nor is it `args.somePrivateData`)
 
Example 2: send information to a specific player (<specific_player_id>) only:
 
<pre>
    function argPlayerTurn()  {
        $specific_player_id = ...; // calculate some-how
        return array(
            '_private' => array(  // all data inside this array will be private
                $specific_player_id => array(  // will be sent only to that player 
                    'somePrivateData' => self::getSomePrivateData() 
                )
            ),
 
            'possibleMoves' => self::getPossibleMoves()  // will be sent to all players
        );
    }
</pre>
 
IMPORTANT: in certain situations (i.e. "multipleactiveplayer" game state) these "private data" features can have a significant impact on performance. Please do not use if not needed.
 
It is also possible to use these private args in the "description" messages, like "${you} have to play ${_private.count} cards".
 
=== updateGameProgression ===
 
(optional)
 
If you specify "updateGameProgression => true" in a game state, your "getGameProgression" PHP method will be called at the beginning of this game state - and thus the game progression of the game will be updated.
 
''At least one'' of your game states (any one) must specify "updateGameProgression=>true".
 
== Implementation Notes ==
 
 
=== Using Named Constants for States ===
 
Using numeric constants is prone to errors. If you want you can declare state constants as PHP named constants. This way you can
use them in the states file and in game.php as well
 
EXAMPLE:
 
states.inc.php:
 
<pre>
// define contants for state ids
if (!defined('STATE_END_GAME')) { // ensure this block is only invoked once, since it is included multiple times
  define("STATE_PLAYER_TURN", 2);
  define("STATE_GAME_TURN", 3);
  define("STATE_PLAYER_TURN_CUBES", 4);
  define("STATE_END_GAME", 99);
}
$machinestates = array(
 
  ...
 
    STATE_PLAYER_TURN => array(
    "name" => "playerTurn",
    "description" => clienttranslate('${actplayer} must select an Action Space or Pass'),
    "descriptionmyturn" => clienttranslate('${you} must select an Action Space or Pass'),
    "type" => "activeplayer",
                "args" => 'arg_playerTurn',
    "possibleactions" => array( "selectWorkerAction", "pass" ),
    "transitions" => array(
            "loopback" => STATE_PLAYER_TURN,
            "playCubes" => STATE_PLAYER_TURN_CUBES,
            "pass" => STATE_GAME_TURN )
    ),
</pre>
 
=== Example of multipleactiveplayer state ===
 
This is an example of a multipleactiveplayer state:
 
  2 =>  array (
    'name' => 'playerTurnSetup',
    'type' => 'multipleactiveplayer',
    'description' => clienttranslate('Other players must choose one Objective'),
    'descriptionmyturn' => clienttranslate('${you} must choose one Objective card to keep'),
    'possibleactions' =>    array ('playKeep' ),
    'transitions' =>    array (      'next' => 5, 'loopback' => 2, ),
    'action' => 'st_MultiPlayerInit',
    'args' => 'arg_playerTurnSetup',
  ),
 
In game.php:
    // this will make all players multiactive just before entering the state
    function st_MultiPlayerInit() {
        $this->gamestate->setAllPlayersMultiactive();
    }
 
Note: if you want exact this function its already defined in table class its called 'stMakeEveryoneActive'
 
When ending the player action, instead of a state transition, deactivate player.
 
    function action_playKeep($cardId) {
        $this->checkAction('playKeep');
        $player_id = $this->getCurrentPlayerId(); // CURRENT!!! not active
        ... // some logic here
        $this->gamestate->setPlayerNonMultiactive($player_id, 'next'); // deactivate player; if none left, transition to 'next' state
    }
 
=== Diffrence between Single active and Multi active states ===
In a classic "activePlayer" state:
 
* You cannot change the active player DURING the state. This is to ensure that during 1 activePlayer state, only ONE player is active
* As a consequence, you must set the active player BEFORE entering the activePlayer state
* Finally, during onEnteringState, on JS side, the active player is signaled as active and the information is reliable and usable.
 
In a "multiplePlayer" state:
 
* You can (and must) change the active players DURING the state
* During such a state, players can be activated/desactivated anytime during the state, giving you the maximum of possibilities.
* You shouldn't set actives player before entering the state. But you can set it in "state initialized" php function (see example above st_MultiPlayerInit)
* Finally, during onEnteringState, on JS side, the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.

Актуальная версія на 20:58, 9 кастрычніка 2020

Studio Framework Navigation

File structure of a BGA game

Game logic (Server side)
Game interface (Client side)
Other components
BGA Studio game components reference
  • Deck: a PHP component to manage cards (deck, hands, picking cards, moving cards, shuffle deck, ...).
  • Counter: a JS component to manage a counter that can increase/decrease (ex: player's score).
  • Scrollmap: a JS component to manage a scrollable game area (useful when the game area can be infinite. Examples: Saboteur or Takenoko games).
  • Stock: a JS component to manage and display a set of game elements displayed at a position.
  • Zone: a JS component to manage a zone of the board where several game elements can come and leave, but should be well displayed together (See for example: token's places at Can't Stop).

Undocumented component (if somebody knows please help with docs)

  • Draggable: a JS component to manage drag'n'drop actions.
  • ExpandableSection: a JS component to manage a rectangular block of HTML than can be displayed/hidden.
  • Wrapper: a JS component to wrap a <div> element around its child, even if these elements are absolute positioned.
BGA Studio user guide


This file describes the state machine of your game (all the game states properties, and the transitions to get from one state to another).

Important: to understand the game state machine, it's recommended that you read this presentation first:

Focus on BGA game state machine

Overall structure

The machine states are described by a PHP associative array.

Example:

$machinestates = array(

    // The initial state. Please do not modify.
    1 => array(
        "name" => "gameSetup",
        "description" => clienttranslate("Game setup"),
        "type" => "manager",
        "action" => "stGameSetup",
        "transitions" => array( "" => 2 )
    ),
    
    // Note: ID=2 => your first state

    2 => array(
    		"name" => "playerTurn",
    		"description" => clienttranslate('${actplayer} must play a card or pass'),
    		"descriptionmyturn" => clienttranslate('${you} must play a card or pass'),
    		"type" => "activeplayer",
    		"possibleactions" => array( "playCard", "pass" ),
    		"transitions" => array( "playCard" => 2, "pass" => 2 )
    ),

Syntax

id

The keys determine game state IDs (in the example above: 1 and 2).

IDs must be positive integers.

ID=1 is reserved for the first game state and should not be used (and you must not modify it).

ID=99 is reserved for the last game state (end of the game) (and you must not modify it).

Note: you may use any ID, even an ID greater than 100. But you cannot use 1 or 99.

Note²: You must not use the same ID twice.

Note³: When a game is in prod and you change the ID of a state, all active games (including many turn based) will behave unpredictably.

name

(Mandatory)

The name of a game state is used to identify it in your game logic.

Several game states can share the same name; however, this is not recommended.

Warning! Do not put spaces in the name. This could cause unexpected problems in some cases.

PHP example:


// Get current game state
$state = $this->gamestate->state();
if( $state['name'] == 'myGameState' )
{
...
}

JS example:

        onEnteringState: function( stateName, args )
        {
            console.log( 'Entering state: '+stateName );
            
            switch( stateName )
            case 'myGameState':
            
                // Do some stuff at the beginning at this game state
                ....
                
                break;

type

(Mandatory)

You can use 3 types of game states:

  • activeplayer (1 player is active and must play.)
  • multipleactiveplayer (1..N players can be active and must play.)
  • game (No player is active. This is a transitional state to do something automatic specified by the game rules.)

Note: Make sure you don't mistype the value of this attribute. If you do (e.g. 'multiactiveplayer' instead of 'multipleactiveplayer'), things won't work, and you might have a hard time figuring out why.

description

(Mandatory)

The description is the string that is displayed in the main action bar (top of the screen) when the state is active.

When a string is specified as a description, you must use "clienttranslate" in order for the string to be translated on the client side:

 		"description" => clienttranslate('${actplayer} must play a card or pass'),

In the description string, you can use ${actplayer} to refer to the active player.

You can also use custom arguments in your description. These custom arguments correspond to values returned by your "args" PHP method (see below "args" field).

Example of custom field:


In states.inc.php:
        "description" => clienttranslate('${actplayer} must choose ${nbr} identical energies'),
        "args" => "argMyArgumentMethod"

In mygame.game.php:
    function argMyArgumentMethod()
    {
        return array(
            'nbr' => 2  // In this case ${nbr} in the description will be replaced by "2"
        );    
    }

Note: You may specify an empty string ("") here if it never happens that the game remains in this state (i.e., if this state immediately jumps to another state when activated).

Note²: Usually, you specify a string for "activeplayer" and "multipleactiveplayer" game states, and you specify an empty string for "game" game states. BUT, if you are using synchronous notifications, the client can remain on a "game" type game state for a few seconds, and in this case it may be useful to display a description in the status bar while in this state.

descriptionmyturn

(Mandatory when the state type is "activeplayer" or "multipleactiveplayer")

"descriptionmyturn" has exactly the same role and properties as "description", except that this value is displayed to the current active player - or to all active players in case of a multipleactiveplayer game state.

In general, we have this situation:

        "description" => clienttranslate('${actplayer} can take some actions'),
        "descriptionmyturn" => clienttranslate('${you} can take some actions'),

Note: you can use ${you} in descriptionmyturn so the description will display "You" instead of the name of the player.

action

(Mandatory when the state type is "game.")

"action" specifies a PHP method to call when entering this game state.

Example: In states.inc.php:

    28 => array(
        "name" => "gameTurnNextPlayer",
        "description" => clienttranslate('Updating some stuff...'),
        "type" => "game",
        "action" => "st_gameTurnNextPlayer",

In mygame.game.php:

    function st_gameTurnNextPlayer() {
        $player_id = $this->getActivePlayerId();
        $next_player_id = $this->getPlayerAfter($player_id);
        $this->giveExtraTime($next_player_id);
        $this->incStat(1, 'turns_number', $next_player_id);
        $this->incStat(1, 'turns_number');
        $this->gamestate->changeActivePlayer($next_player_id);
        $this->gamestate->nextState('next');
    }

Usually, for a "game" state type, the action method is used to perform automatic functions specified by the rules (for example: check victory conditions, deal cards for a new round, go to the next player, etc.) and then jump to another game state.

Note: a BGA convention specifies that PHP methods called with "action" are prefixed by "st".

Note: this field CAN be used for player states to set something up; e.g., for multiplayer states, it can make all players active.

Example for action in player state: in states.inc.php:

    2 => array(
    		"name" => "playerTurnPlace",
    		"description" => clienttranslate('Other player must place ships'),
    		"descriptionmyturn" => clienttranslate('${you} must place ships (click on YOUR SHIPS board to place)'),
    		"type" => "multipleactiveplayer",
            'action' => 'st_MultiPlayerInit',
            'args' => 'arg_playerTurnPlace',
    		"possibleactions" => array( "playPlace" ),
            "transitions" => array( "next" => 4, "last" => 99)
    ),

in mygame.game.php:

    function st_MultiPlayerInit() {
        $this->gamestate->setAllPlayersMultiactive();
    }

transitions

(Mandatory)

With "transitions" you specify which game state(s) you can jump to from a given game state.

Example:

    25 => array(
        "name" => "myGameState",
        "transitions" => array( "nextPlayer" => 27, "endRound" => 39 ),
        ....
    }

In the example above, if "myGameState" is the current active game state, I can jump to the game state with ID 27 or the game state with ID 39.

Example to jump to ID 27:

In mygame.game.php:
    $this->gamestate->nextState( "nextPlayer" );

Important: "nextPlayer" is the name of the transition, and NOT the name of the target game state. Multiple transitions can lead to the same game state.

Note: If there is only 1 transition, you may give it an empty name.

Example:

In states.inc.php:
    "transitions" => array( "" => 27 ),

In mygame.game.php:
    $this->gamestate->nextState(  );     // We don't need to specify a transition as there is only one here

possibleactions

(Mandatory when the game state is "activeplayer" or "multipleactiveplayer")

"possibleactions" defines the actions possible by the players in this game state, and ensures they cannot cannot perform actions that are not allowed in this state.

Example:

In states.game.php:
       	"possibleactions" => array( "playCard", "pass" ),

In mygame.game.php:
        function playCard( ...)
        {
             self::checkAction( "playCard" );    // Will fail if "playCard" is not specified in "possibleactions" in the current game state.

            ....

In mygame.js:
        playCard: function( ... )
        {
            if( this.checkAction( "playCard" ) ) // Will fail if "playCard" is not specified in "possibleactions" in the current game state.
            {  return ;   }

            ....

args

(optional)

Sometimes it happens that you need some information on the client side (i.e., for your game interface) only for a specific game state.

Example 1: in Reversi, the list of possible moves during the playerTurn state.

Example 2: in Caylus, the number of remaining king's favors to choose in the state where the player is choosing a favor.

Example 3: in Can't Stop, the list of possible die combinations to be displayed to the active player so that he can choose from among them.

In such a situation, you can specify a method name as the « args » argument for your game state. This method must retrieve some piece of information about the game (example: for Reversi, the list of possible moves) and return it.

Thus, this data can be transmitted to the clients and used by the clients to display it. It should always be an associative array.

Let's see a complete example using args with Reversi game:

In states.inc.php, we specify an args argument for gamestate playerTurn:

    10 => array(
        "name" => "playerTurn",
		"description" => clienttranslate('${actplayer} must play a disc'),
		"descriptionmyturn" => clienttranslate('${you} must play a disc'),
        "type" => "activeplayer",
        "args" => "argPlayerTurn",    // <==== HERE
        "possibleactions" => array( 'playDisc' ),
        "transitions" => array( "playDisc" => 11, "zombiePass" => 11 )
    ),

It corresponds to a argPlayerTurn method in our PHP code (reversi.game.php):

    function argPlayerTurn() {
        return array(
            'possibleMoves' => self::getPossibleMoves()
        );
    }

Then, when we enter into the playerTurn game state on the client side, we can highlight the possible moves on the board using information returned by argPlayerTurn:

        onEnteringState: function( stateName, args )  {
           console.log( 'Entering state: '+stateName );
            
            switch( stateName )  {
            case 'playerTurn':
                this.updatePossibleMoves( args.args.possibleMoves );
                break;
            }
        },

Naming and API conventions

As a BGA convention, PHP methods called with "args" are prefixed by "arg" followed by state name (example: argPlayerTurn).

All "arg*" methods are put into corresponding section of .game.php file commented as

  //////////// Game state arguments

Arg method MUST return array. Return integer or string will result in some undebugguggable exceptions.

Arg method MUST be defined in php if it is declared in state description, if you don't need it comment it out from the states.inc.php file (the args parameter). If you not sure if you need it or not you can keep it returning empty array until you figure it out.

   function argPlayerTurn() {
       return array(); // must be an array
   }


Warning: the "args" method can be called before the "action" method so don't expect data modifications by the "action" method to be available in the "args" method! Also don't modify database in this method!

You should NOT be calling getCurrentPlayer() in the context of this function, since state transitions are broadcasted to all player independent of who initiated it. Instead you should send information on per player basis (Note: if this is private info see section below).

If you using this function in multi-player state, you should not call getActivePlayer() either, you should send per player info:

   function arg_playerTurn() {
       $res = array ();
       $players = $this->loadPlayersBasicInfos();
       foreach ( $players as $player_id => $player_info ) {
           $color = $player_info ['player_color'];
           $res [$player_id] = array("color"=>$color);
       }
       return $res;
   }

Note: you never need to send player color like this, this is just an example.

Note 2: you CAN call methods that return all active players if multi-player states if its relevant.


Other usages

You can use values returned by your "args" method to have some custom values in your "description"/"descriptionmyturn", i.e. in states.inc.php:

  "descriptionmyturn" => clienttranslate('${you} must play ${color} disc'),

So arg function will be something like:

 function argPlayerTurn() {
       return array('color'=>this->getActivePlayerColor()); 
 }

You can use args also in onUpdateActionButtons function on js side, however two important notes:

  • it is just args (not args.args like in onEnteringState)
  • it is args of the current state, which may not be state you think it will be! This method is called during change of active player, which happens in some weird place especially during "multipleactiveplayer" states. If you pulling your hair thinking why its "undefined" on js side - check the current state.

Private info in args

By default, all data provided through this method are PUBLIC TO ALL PLAYERS. Please do not send any private data with this method, as a cheater could see it even it is not used explicitly by the game interface logic.

However, it is possible to specify that some data should be sent to specific players only.

Example 1: send information to active player(s) only:

    function argPlayerTurn()  {
        return array(
            '_private' => array(          // Using "_private" keyword, all data inside this array will be made private
                'active' => array(       // Using "active" keyword inside "_private", you select active player(s)
                    'somePrivateData' => self::getSomePrivateData()   // will be send only to active player(s)
                )
            ),
            'possibleMoves' => self::getPossibleMoves()          // will be sent to all players
        );
    }

Inside the js file, these variables will be available through `args.args._private`. (e.g. `args.args._private.somePrivateData` -- it is not `args._private.active.somePrivateData` nor is it `args.somePrivateData`)

Example 2: send information to a specific player (<specific_player_id>) only:

    function argPlayerTurn()  {
        $specific_player_id = ...; // calculate some-how
        return array(
            '_private' => array(   // all data inside this array will be private
                $specific_player_id => array(   // will be sent only to that player   
                    'somePrivateData' => self::getSomePrivateData()   
                )
            ),

            'possibleMoves' => self::getPossibleMoves()   // will be sent to all players
        );
    }

IMPORTANT: in certain situations (i.e. "multipleactiveplayer" game state) these "private data" features can have a significant impact on performance. Please do not use if not needed.

It is also possible to use these private args in the "description" messages, like "${you} have to play ${_private.count} cards".

updateGameProgression

(optional)

If you specify "updateGameProgression => true" in a game state, your "getGameProgression" PHP method will be called at the beginning of this game state - and thus the game progression of the game will be updated.

At least one of your game states (any one) must specify "updateGameProgression=>true".

Implementation Notes

Using Named Constants for States

Using numeric constants is prone to errors. If you want you can declare state constants as PHP named constants. This way you can use them in the states file and in game.php as well

EXAMPLE:

states.inc.php:

// define contants for state ids
if (!defined('STATE_END_GAME')) { // ensure this block is only invoked once, since it is included multiple times
   define("STATE_PLAYER_TURN", 2);
   define("STATE_GAME_TURN", 3);
   define("STATE_PLAYER_TURN_CUBES", 4);
   define("STATE_END_GAME", 99);
}
 
$machinestates = array(

   ...

    STATE_PLAYER_TURN => array(
    		"name" => "playerTurn",
    		"description" => clienttranslate('${actplayer} must select an Action Space or Pass'),
    		"descriptionmyturn" => clienttranslate('${you} must select an Action Space or Pass'),
    		"type" => "activeplayer",
                "args" => 'arg_playerTurn',
    		"possibleactions" => array( "selectWorkerAction", "pass" ),
    		"transitions" => array( 
    		        "loopback" => STATE_PLAYER_TURN,
    		        "playCubes" => STATE_PLAYER_TURN_CUBES,
    		        "pass" => STATE_GAME_TURN )
    ),

Example of multipleactiveplayer state

This is an example of a multipleactiveplayer state:

 2 =>  array (
   'name' => 'playerTurnSetup',
   'type' => 'multipleactiveplayer',
   'description' => clienttranslate('Other players must choose one Objective'),
   'descriptionmyturn' => clienttranslate('${you} must choose one Objective card to keep'),
   'possibleactions' =>     array ('playKeep' ),
   'transitions' =>    array (       'next' => 5, 'loopback' => 2, ),
   'action' => 'st_MultiPlayerInit',
   'args' => 'arg_playerTurnSetup',
 ),

In game.php:

   // this will make all players multiactive just before entering the state
   function st_MultiPlayerInit() {
       $this->gamestate->setAllPlayersMultiactive();
   }

Note: if you want exact this function its already defined in table class its called 'stMakeEveryoneActive'

When ending the player action, instead of a state transition, deactivate player.

   function action_playKeep($cardId) {
       $this->checkAction('playKeep');
       $player_id = $this->getCurrentPlayerId(); // CURRENT!!! not active
       ... // some logic here
       $this->gamestate->setPlayerNonMultiactive($player_id, 'next'); // deactivate player; if none left, transition to 'next' state
   }

Diffrence between Single active and Multi active states

In a classic "activePlayer" state:

  • You cannot change the active player DURING the state. This is to ensure that during 1 activePlayer state, only ONE player is active
  • As a consequence, you must set the active player BEFORE entering the activePlayer state
  • Finally, during onEnteringState, on JS side, the active player is signaled as active and the information is reliable and usable.

In a "multiplePlayer" state:

  • You can (and must) change the active players DURING the state
  • During such a state, players can be activated/desactivated anytime during the state, giving you the maximum of possibilities.
  • You shouldn't set actives player before entering the state. But you can set it in "state initialized" php function (see example above st_MultiPlayerInit)
  • Finally, during onEnteringState, on JS side, the active players are NOT actives yet so you must use onUpdateActionButtons to perform the client side operation which depends on a player active/unactive status.