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

Options and preferences: gameoptions.json, gamepreferences.json: Difference between revisions

From Board Game Arena
Jump to navigation Jump to search
m (Fixed a few typos.)
 
(55 intermediate revisions by 14 users not shown)
Line 1: Line 1:
{{Studio_Framework_Navigation}}
{{Studio_Framework_Navigation}}


In this file, you can define your game options (i.e. game variants) and user preferences.
In <code>gameoptions.json</code>, you can define your game options (i.e. game variants).
 
In <code>gamepreferences.json</code>, you can define user preferences.
    
    
Note: If your game has no variants or preferences, you don't have to modify this file.
Note: If your game has no variants or preferences, you don't have to modify these files.
 
'''IMPORTANT:''' after edits to these files, you have to go to the control panel and press "Reload game options configuration" on Studio for your changes '''to take effect'''.


'''IMPORTANT:''' after you edited this file and syncronized to ftp folder you have to go to the control panel and press "Reload game options configuration" for your changes '''to take effect'''.
Make sure you understand difference between options and preferences:
* Game options - something usually in the rule book defined as "variant" (except for player count, which is automatically handled). For example, whether to include ''The River'' in Carcassonne.
* User preferences - personal choices of each player only visible to that specific player - i.e. layout, whether or not to prompt for action, whether or not auto-opt in in some actions, etc.


Make sure you understand difference bettween options and preferences:
__TOC__
* Game options - something usually in the rule book defined as "variant" (except for 2,3,X people - that is automatically handled - you can query it)
* User preferences - personal choices of each player only visible to that specific player - i.e. layout, either or not to prompt for action, ether or not auto-opt in in some actions, etc


== Game Options ==
== Game Options ==
Line 15: Line 19:
Game options are selected by the table creator and usually correspond to game variants, for example if the game includes expansions or certain special rules.
Game options are selected by the table creator and usually correspond to game variants, for example if the game includes expansions or certain special rules.


These variants are defined in gameoptions.inc.php as the $game_options variable:   
These variants are defined in <code>gameoptions.json</code> as an object:   
   $game_options = array(...); // exactly named that
 
   {
    "100": { "name": "Game Setup", ... },
    "101": { "name": "Draft", ... }
  }
 
Each key corresponds to the option id, and each value is that option definition, which is described below.
 
'''Note:''' the file must be valid [https://www.json.org/ JSON]. That is, trailing commas and comments are not allowed. However you can use still standard json hack to add a field like "$comment": "Here we go"


Each option is a pair in the format: number => 'option description array'.  
'''Note 2:''' the key in json file must be a string type, even its correspond to a number in other places (such as php, or other references in same json).


All options defined in this file should have a corresponding "game state label" with the same ID (see "initGameStateLabels" in yourgame.game.php)
All options defined in this file should have a corresponding "game state label" with the same ID (see "initGameStateLabels" in yourgame.game.php)


             self::initGameStateLabels ( array (
             self::initGameStateLabels ([
                         ...
                         ...
                         "my_game_variant" => 100,
                         "my_game_variant" => 100,
               ) );
               ]);


Numbers have to be exactly from 100 to 199 (there can be gaps).
Numbers have to be exactly from 100 to 199 (there can be gaps).


That is how you access them during runtime:
That is how you access them during runtime (by name):


     public function isSecondVariant() {
     public function isSecondVariant() {
Line 35: Line 47:
     }
     }


Or this
Or this (by numberic id)
     $this->gamestate->table_globals[100]
     $this->gamestate->table_globals[100]
To access json data (the metadata only) can use
  $game_options = $this->getTableOptions();
=== Details of game options format ===


The following are the parameters of option description array:
The following are the values of the option definition object:
* '''name''' - '''mandatory'''. The name of the option visible for table creator. Value must be wrapped in totranslate function.
* '''name''' - '''mandatory'''. The name of the option visible for table creator. This is automatically marked for translation.
* '''values''' - '''mandatory'''. The array (map) of values with additional parameters per value.
* '''values''' - '''mandatory'''. The map representing possible values of this option. The key of the map is possible value of this option, its a number but has to be string in json. The value is an object descring it.
** '''name''' - '''mandatory'''. String representation of the numeric value visible to table creator. Value must be wrapped in totranslate function.
** '''name''' - '''mandatory'''. String representation of the numeric value visible to table creator. This is automatically marked for translation.
** '''description''' - String description of this value to use when the name of the option is not self-explanatory. Displayed at the table under the option when this value is selected.
** '''description''' - String description of this value to use when the name of the option is not self-explanatory. Displayed at the table under the option when this value is selected. Note: if there is no description, this should be omitted.
** '''tmdisplay''' - String representation of the option visible in the table description, usually if variant "names" are On and Off, but the description would be same as option name when On, and nothing when Off. ('''Warning''': due to some caching, a change in tmdisplay may not be effective immediately in the studio, even after forcing a reload of gameoptions.inc.php.)
** '''tmdisplay''' - String representation of the option visible in the table description in the lobby. Usually if a variant values are On and Off (default), the tmdisplay would be same as description name when On, and nothing (empty string) when Off. ('''Warning''': due to some caching, a change in tmdisplay may not be effective immediately in the studio, even after forcing a reload of options.) '''Pro Tip:''' You can use this as a pre-game communication by adding fake options that just do nothing in the game but make it easier to find other player wanted the same game configuration (see the crew deep sea for example).
** '''nobeginner''' - Set to true if not recommended for begginers
** '''nobeginner''' - Set to true if not recommended for beginners
** '''firstgameonly''' - Set to true if this option is recommended only for the first game (discovery option)
** '''firstgameonly''' - Set to true if this option is recommended only for the first game (discovery option)
** '''beta''' - Option in beta stage on development
** '''beta''' - Set to true to indicate that this option is in "beta" development stage (there will be a warning for players starting the game)
** '''alpha''' - Set to true to indicate that this option is in "alpha" development stage (there will be a warning, and starting the game will be allowed only in training mode except for the developer)
** '''premium''' - Option can be only used by premium members
** '''premium''' - Option can be only used by premium members
* '''default''' - indicates the default value to use for this option (optional, if not present the first value listed is the default)
* '''default''' - indicates the default value to use for this option (optional, if not present the first value listed is the default)
* '''displaycondition''' - checks the conditions before displaying the option for selection. All conditions must be true for the option to display. Supported condition types:
* '''displaycondition''' - (array of conditions) - checks the conditions before displaying the option for selection. All (or any) conditions must be true for the option to be displayed. All or any depends on value of displayconditionoperand
** ''minplayers'' condition ensures at least this many players
Supported display condition types:
** ''maxplayers'' conditions ensure at most this many players
** ''minplayers'' condition ensures at least this many players (Note: if your game works with a disjoint interval of player counts, you can supply an array of valid counts instead of a single value)
** ''otheroption'' condition ensures another option is set to this given values.  
** ''maxplayers'' condition ensures at most this many players
** ''otheroptionisnot'' conditions ensure another option is NOT set to this given values
** ''otheroption'' condition ensures another option is set to given values.  
** ''otheroptionisnot'' condition ensure another option is NOT set to this given values
* '''displayconditionoperand''' - can be 'and' (this is the default) or 'or'. Allows to change the behaviour to display the option if one of the conditions is true instead of all of them.
* '''displayconditionoperand''' - can be 'and' (this is the default) or 'or'. Allows to change the behaviour to display the option if one of the conditions is true instead of all of them.
* '''startcondition''' - checks the conditions (on options VALUES) before starting the game. All conditions must be true for the game to start, otherwise players will get a red error message when attempting to begin the game. Supported condition types:
* '''startcondition''' - (map from value to conditions array) - checks the conditions (on options VALUES) before starting the game. All conditions must be true for the game to start, otherwise players will get a red error message when attempting to begin the game.  
** ''minplayers'' condition ensures at least this many players
Supported start condition types:
** ''maxplayers'' conditions ensure at most this many players
** ''minplayers'' condition ensures at least this many players (an array of values is not supported here)
** ''maxplayers'' condition ensures at most this many players
** ''otheroption'' conditions ensure another option is set to this given values. That works the same as in '''displaycondition'''.
** ''otheroption'' conditions ensure another option is set to this given values. That works the same as in '''displaycondition'''.
** ''otheroptionisnot'' conditions ensure another option is NOT set to this given value.  That works the same as in '''displaycondition'''.
** ''otheroptionisnot'' conditions ensure another option is NOT set to this given value.  That works the same as in '''displaycondition'''.
** ''gamestartonly'' if you have options that are exclusive, for example for a solo mode: you can have a maxplayer of 1 for one option and a minplayer of 2 for an other option and you will be stuck. See below for an example.
*: For all these condition types, a ''gamestartonly'' boolean option can be added, if you have options that are exclusive. Setting this boolean to ''true'' will defer the evaluation of the startcondition to the game creation, instead of preventing the player to select options that are exclusive at all. See [[#gamestartonly|below]] for an example. <span style="background: #FFCDD2; color: #B71C1C; padding: 2px;">But this should never be used -- see the warning below</span>
* '''notdisplayedmessage''' - if option is not suppose to be visible because of displaycondition but this is set, the text will be visible instead of combo drop down
* '''notdisplayedmessage''' - if option is not suppose to be visible because of displaycondition but this is set, the text will be visible instead of combo drop down
* '''level''' - what kind of option it is: ''base'', ''major'', or ''additional''. See [[#level|below]] for more informations.


Common options for all tables (reserved range 200-299):
Common (reserved) options for all tables (reserved range 200-299):
*201 (const GAMESTATE_RATING_MODE) - ELO OFF (aka Training mode),  
*201 (const GAMESTATE_RATING_MODE) - ELO OFF (aka Training mode),  
*200 (const GAMESTATE_CLOCK_MODE) - game speed profile, array(0,1,2) - realtime (technically <10 realtime but you cannot define range in php), values >=10 - turn based (currently 10..21)
**0 = Normal
**1 = Training
**2 = Arena
*200 (const GAMESTATE_CLOCK_MODE) - game speed profile
**[0, 1, 2] - realtime (technically <10 realtime but you cannot define range in php)
**values >=10 - turn based (currently 10..21)
**Note there are two similar values, 9 = realtime "no time limit with friends only" vs. 20 = turn-based "no time limit with friends only"


Example:
'''Example:'''
<pre>
<pre>
$game_options = array(
{
    100 => array(
  "100": {
        'name' => totranslate('my game option'),
    "name": "Game Variant",
         'values' => array(
    "values": {
            // A simple value for this option:
      "1": {
            1 => array(
        "name": "Learning",
                'name' => totranslate('option 1')
        "firstgameonly": true,
            ),
        "tmdisplay": "Learning"
      },
      "2": {
        "name": "Base",
      },
      "3": {
        "name": "Extended",
        "nobeginner": true,
        "beta": true,
        "tmdisplay": "Extended"
      }
    },
    "default": 2
  },
  "101": {
    "name": "Draft variant",
    "values": {
      "1": {
        "name": "No draft"
      },
      "2": {
        "name": "Draft",
        "tmdisplay": "Draft",
        "premium": true,
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroption",
        "id": 100,
        "value": [ 2, 3 ]
      },
      {
        "type": "otheroption",
        "id": 201,
        "value": 1
      }
    ],
    "startcondition": {
      "1": [],
      "2": [
        {
          "type": "maxplayers",
          "value": 3,
          "message": "Draft option is available for 3 players maximum."
        }
      ]
    }
  },
  "102": {
    "name": "Takeovers",
    "values": {
      "2": {
        "name": "No takeover"
      },
      "1": {
        "name": "Allow takeovers",
        "tmdisplay": "Takeovers",
        "premium": true,
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroption",
        "id": 100,
        "value": [ 3 ]
      }
    ],
    "startcondition": {
      "2": [],
      "1": [
        {
          "type": "maxplayers",
          "value": 2,
          "message": "Rebel vs Imperium Takeover Scenario is available for 2 players only."
        }
      ]
    }
  }
}
</pre>
 
'''Example of option that condition on ELO off:'''
{
  "100": {
    "name": "Learning Game (No Research)",
    "values": {
      "1": {
        "name": "Off",
         "tmdisplay": ""
      },
      "2": {
        "name": "On",
        "tmdisplay": "Learning Game"
      }
    },
    "startcondition": {
      "2": {
        "type": "otheroption",
        "id": 201,
        "value": 1,
        "message": "Learning variant available only in friendly mode"
      }
    }
  }
}
'''Example of condition that is only available for REALTIME game mode:'''
<pre>
"displaycondition": [
    { "type": "otheroption", "id": 200, "value": [0, 1, 2] }
],
</pre>
 
'''Example of using condition on your own option:'''
<pre>
{
  "102": {
    "name": "Scenarios",
    "values": {
      "1": {
        "name": "Off",
        "nobeginner": false
      },
      "2": {
        "name": "On",
        "tmdisplay": "Scenarios",
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroptionisnot",
        "id": 100,
        "value": 1
      }
    ],
    "notdisplayedmessage": "Scenarios variant is not available if Learning variant is chosen"
  }
}


            // A simple value for this option.
</pre>
            // If this value is chosen, the value of "tmdisplay" is displayed in the game lobby
            2 => array(
                'name' => totranslate('option 2'),
                'tmdisplay' => totranslate('option 2')
            ),


            // Another value, with other options:
'''Example of handling solo vs multiplayer options:'''<pre>
            //  beta=true => this option is in beta version right now.
{
            //  nobeginner=true  =>  this option is not recommended for beginners
  "100": {
            3 => array(
    "name": "Board setup",
                'name' => totranslate('option 3'),
    "values": {
                'beta' => true,
      "1": {
                'nobeginner' => true
        "name": "Mirror setup",
            ),
        "description": "The starting player shuffles their 6 Farm Cards and randomly lays a card face up in each of the round spaces of their Fruit Island Board. The other players then lay their cards in exactly the same way, copying the order of the starting player.",
        ),
        "tmdisplay": "Mirror setup"
        'default' => 1
      },
    ),
      "2": {
   
        "name": "Random setup",
    101 => array(
        "description": "Instead of every player copying the same card configuration as the starting player, every player shuffles their Farm Cards and lays down the cards randomly on their Fruit Island Board.",
        'name' => totranslate('Draft variant'),
        "tmdisplay": "Random setup"
        'values' => array(
      }
            1 => array(
    },
                'name' => totranslate('No draft')
    "displaycondition": [
            ),
      {
            2 => array(
        "type": "minplayers",
                'name' => totranslate('Draft'),
        "value": [
                'tmdisplay' => totranslate('Draft'),
          2,
                'premium' => true,
          3,
                'nobeginner' => true
          4
            ),
        ]
        ),
      }
        'displaycondition' => array(
    ]
            // Note: do not display this option unless these conditions are met
  },
            array(
  "102": {
                'type' => 'otheroption',
    "name": "Solo difficulty",
                'id' => 100, // Game specific option defined in the same array above
    "values": [
                'value' => array(2, 3, 4)
      {
            ),
        "name": "Banana-apprentice",
            // Note: do not display this option unless these conditions are met
        "description": "Do not remove any seed before starting the game.",
            array( 'type' => 'otheroption',
        "tmdisplay": "Banana-apprentice"
                'id' => 201, // ELO OFF hardcoded framework option
      },
                'value' => 1 // 1 if OFF
      {
            )
        "name": "Pear to the Throne",
        ),
        "description": "Remove 1 seed before starting the game.",
        "tmdisplay": "Pear to the Throne"
      }
    ],
    "displaycondition": [
      {
        "type": "maxplayers",
        "value": 1
      }
    ]
  }
}


        'startcondition' => array(
            1 => array(),
            2 => array(
                array(
                    'type' => 'maxplayers',
                    'value' => 3,
                    'message' => totranslate('Draft option is available for 3 players maximum.')
                )
            ),
        ),
    ),
   
    102 => array(
        'name' => totranslate('Takeovers'),
        'values' => array(
            2 => array(
                'name' => totranslate('No takeover')
            ),
            1 => array(
                'name' => totranslate('Allow takeovers'),
                'tmdisplay' => totranslate('Takeovers'),
                'premium' => true,
                'nobeginner' => true
            ),
        ),
        'displaycondition' => array( // Note: do not display this option unless these conditions are met
            array(
                'type' => 'otheroption',
                'id' => 100,
                'value' => array(3, 4)
            )
        ),
        'startcondition' => array(
            2 => array(),
            1 => array(
                array(
                    'type' => 'maxplayers',
                    'value' => 2,
                    'message' => totranslate('Rebel vs Imperium Takeover Scenario is available for 2 players only.')
                )
            ),
        ),
    )
);
</pre>
</pre>


Example of option that condition on ELO off
==== displaycondition vs startcondition ====
 
<code>displaycondition</code> should be used when an option should not be present in the list under certain conditions.
 
For example, displaying the option which is consistent with the player count.
 
* 2 player maps: A B C D
* 3 player maps: E F G H
* 4 player maps: I J K L
 
<code>startcondition</code> should be used when a specific combination of option values is invalid, but the option itself still makes sense to show.
 
For example:
 
* Player 1 faction: A, B, C, D, E, F, G
* Player 2 faction: A, B, C, D, E, F, G
* startcondition: both players can't be the same faction
 
These options should both be displayed at all times, but there are some invalid configurations.


<pre>
The other difference is displaycondition affect option itself, while startcondition affect specific values selected
$game_options = array(
 
==== gamestartonly ====
 
<div style="padding: 1em; background: #FFCDD2; color: #B71C1C">
<b>WARNING:</b> [https://studio.boardgamearena.com/bug?id=104 See studio bug #104]. You should <b>NEVER</b> use <code>gamestartonly</code>! Otherwise, you allow players to (unknowingly!) create a turn-based table with impossible options that can never start. Players receive no indication that the option combination they selected is invalid until the last player joins the table (hours or days later) and the game attempts to auto-start. The table won't start because of the <code>startcondition</code>, and the table options cannot be changed because it's a turn-based table, so the table must be abandoned. This is a horrible user experience. Please don't subject players to this.
</div>
 
On the table configuration page, it won't let you select a combination which is invalid according to <code>startcondition</code>. Doing so will show a red warning, and will revert the option to the previous value. This means you could end up in a situation where you can't easily change between certain options (requiring you to set or unset options in a specific order).
 
The consequence of the <code>gamestartonly</code> flag is that the player _can_ select an invalid combination. However, when clicking "Start", an invalid combination will produce an error.
 
Probably the easiest way to see what the difference is is with an example.


        100 => array(
With a _Clans of Caledonia_ table:
                'name' => totranslate('Learning Game (No Research)'),
* set to training mode
                'values' => array(
* set player count to 1
                       
* try setting "Clan Auction" to one of the "On" options
                        1 => array( 'name' => totranslate('Off'), 'tmdisplay' => totranslate('') ),
* try starting the game, there's an error! Ie. <code>"gamestartonly" => true</code>
                        2 => array( 'name' => totranslate('On'), 'tmdisplay' => totranslate('Learning Game') ),
                       
                ),
                'displaycondition' => array(
                        // Note: do not display this option unless these conditions are met
                        array( 'type' => 'otheroption',
                                'id' => 201, // ELO OFF hardcoded framework option
                                'value' => 1, // 1 if OFF


                        )
Then, with a _Sushi Go Party!_ table:
                ),
* set number of players to 7
                'notdisplayedmessage' => totranslate('Learning variant available only with ELO off')
* try setting "Sushi Go! / Sushi Go Party!" to "Sushi Go!"
                ),
* you can't set the option! Ie. <code>"gamestartonly" => false</code>


);
In code:
</pre>


Example of condition that is only available for REALTIME game mode
<pre>
<pre>
'displaycondition' => array(
{
     // Note: do not display this option until these conditions are met - game speed is selected as realtime
  "100": {
     array( 'type' => 'otheroption', 'id' => GAMESTATE_CLOCK_MODE, 'value' => array(0,1,2) )
     "name": "Option name",
),
    "values": [],
     "startcondition": {
      "1": [
        {
          "type": "maxplayers",
          "value": 2,
          "message": "This option is available for 2 players only.",
          "gamestartonly": true
        }
      ]
    }
  }
}
</pre>
</pre>


Example of using condition on your own option
==== level ====
<pre>
Note: At this moment this only has an impact in fancy lobby mode; The presentation of level or checkboxes is not available via normal table creation UI (such as in studio, or when you click Play button in control panel).<pre>
        102 => array(
{
                'name' => totranslate('Scenarios'),
    "100": {
                'values' => array(
        "name": "Option name",
                        1 => array( 'name' => totranslate('Off'),
        "values": [...],
                                'nobeginner' => false  ),
        "level": "major"
                        2 => array( 'name' => totranslate('On'), 'tmdisplay' => totranslate('Scenarios'),
    }
                                'nobeginner' => true  ),
}
                       
                ),
                'displaycondition' => array(
                        // Note: do not display this option unless these conditions are met
                        array( 'type' => 'otheroptionisnot',
                                'id' => 100, // learning variant
                                'value' => 2, // 1 if OFF,2 is ON
                               
                        )
                ),
                'notdisplayedmessage' => totranslate('Scenarios variant is not available if Learning variant is chosen')
        ),
</pre>
</pre>


Example of ''gamestartonly'':
* '''base''' is the default value (you don't need to specify it).
<pre>
* '''major''' denotes a major option, which will always be displayed on top, and with a specific UI.
    102 => array(
* '''additional''' means this option will not be displayed by default, to unclutter the option panel.
        'name' => totranslate('Mode'),
 
        'values' => array(
About major options:
            1 => array(
* a game can only have '''1 major option''', and of course, it must be a important game changer.
                'name' => totranslate('Normal')
* the pictures will default to the standard Game Box, and can be set independently for each value, from the [[Game metadata manager]] (in the "Major Variants" section). Note: this cannot be tested from Studio as there is not studio-only Game metadata manager, to see Major Variants option - the game has to be deployed at least as alpha.
            ),
* the major option can have more than 2 values (there will then be an arrow to slide to the other values, carousel-like)
            2 => array(
* the naming of major variant values should be concise and the values should have descriptive text (not just "enabled" or "disabled")
                'name' => totranslate('Solo'),
** 👍 Option name "Expansion", option values "Base game", "Bigger is Better" => Displayed as ''"Expansion: Base game"'' or ''"Expansion: Bigger is Better"''
            ),
** 👎 Option name "Bigger is Better", option values "Enabled", "Disabled => Displayed as ''"Bigger is Better: Enabled"'' or ''"Bigger is Better: Disabled"''
        ),
 
        'startcondition' => array(
About additional options:
            1 => array(
* Please set each option that concerns small details in this category: Advanced players will find this option anyway, and it will simplify the interface of the page of your game.
                array(
 
                    'type' => 'minplayers',
[[File:Option-levels.png|800px|Option levels]]
                    'value' => 2,
 
                    'message' => totranslate('Normal mode is 2 for players or more')
==== option presentation ====
                    'gamestartonly' => true,
 
                )
An option WILL be displayed as a '''Checkbox''' instead of a selector if certain conditions are met:
            ),
 
            1 => array(
* option has only '''2 values'''
                array(
* values are either (case insensitive):
                    'type' => 'maxplayers',
** ''yes'' and ''no''
                    'value' => 1,
** ''on'' and ''off''
                    'message' => totranslate('Solo mode is for 1 player')
** ''enabled'' and ''disabled''
                    'gamestartonly' => true,
 
                )
The "default" value still has to be specified, and can be "on" or "off" (it's actually just a difference in display).
            ),
 
        ),
 
    )
[[File:Option-display.png|800px|Example of checkbox display]]
</pre>
 
 


== User Preferences ==
== User Preferences ==
Line 265: Line 413:
User preferences is something cosmetic about the game interface which however can create user wars, so you can satisfy all users
User preferences is something cosmetic about the game interface which however can create user wars, so you can satisfy all users
by giving them individual preferences. You should use this only if it significantly improves the interface for a large proportion of users.
by giving them individual preferences. You should use this only if it significantly improves the interface for a large proportion of users.
These preferences appear in the three-line hamburger menu in the top corner of the bga menu (and also at the bottom on the page in the Options tab). 
"Display game logs" and "Display tooltips" are baked-in by default, but you can extend this list as below.
The preference json slightly resembles options, but these are conceptually different. The numbers comes from a different space and do not correspond or conflict with options,
i.e. preference 100 has nothing to do with option 100. You can use range 100-199 with gaps.
[[File:century_preferences_menu.PNG|400px|The user preferences menu for the game Century]]
<blockquote>
''These preferences are a good place to put accessibility options - as Century did for its Colorblind Support.''
</blockquote>


<pre>
<pre>
$game_preferences = array(
{
    100 => array(
  "100": {
        'name' => totranslate('Notation style'),
    "name": "Colorblind Support",
        'needReload' => true, // after user changes this preference game interface would auto-reload
    "needReload": true,
        'values' => array(
    "values": {
            1 => array( 'name' => totranslate( 'Classic' ), 'cssPref' => 'notation_classic' ),
      "1": {
            2 => array( 'name' => totranslate( 'Tournament' ), 'cssPref' => 'notation_tournament' )
        "name": "None",
         ),
        "cssPref": "colorblind_off"
         'default' => 2
      },
    )
      "2": {
);
        "name": "Numbers",
        "cssPref": "colorblind_on"
      },
      "3": {
         "name": "Shapes",
         "cssPref": "colorblind_shapes"
      }
    },
    "default": 1
  }
}
</pre>
</pre>


There is two ways to check/apply this. In java Script
There is two ways to check/apply this. In javascript


   if (this.prefs[100].value == 2) ...
   if (this.getGameUserPreference(100) == 2) ...


This checks if preferences 100 has selected value 2.
This checks if preferences 100 has selected value 2.


Second, if cssPref specified it will be applied to the '''<html>''' tag. So you can use different css styling for the preference. Note: it seems needed to set needReload to true for that class change to be effective.
Second, if cssPref is specified, it will be applied to the '''<html>''' tag. So you can use different css styling for the preference. Note: you also need to set needReload to true for that class change to be effective.


As user you have to select them from the Gear menu when game is started. On studio only user0 will have it actually working (bug?).
As user you have to select them from the Gear menu when game is started. On studio only dev0 account will have it actually working (bug?).


The following are the parameters of preferences description array:
The following are the parameters of preferences description array:
* '''name''' - '''mandatory'''. The name of the preference. Value will be automatically wrapped in totranslate if you don't.
* '''name''' - '''mandatory'''. The name of the preference. This string is marked for translation.
* '''needReload''' - If set to true, the game interface will auto-reload after a change of the preference.
* '''needReload''' - If set to true, the game interface will auto-reload after a change of the preference.
* '''values''' - '''mandatory'''. The array (map) of values with additional parameters per value.
* '''values''' - '''mandatory'''. The map of values with additional parameters per value.
** '''name''' - '''mandatory'''. String representation of the numeric value. Value will be automatically wrapped in totranslate if you don't.
** '''name''' - '''mandatory'''. String representation of the numeric value. This string is marked for translation.
** '''cssPref''' - CSS class to add to the '''<html>''' tag. Currently it is added or removed only after a reload (see needReload).
** '''cssPref''' - CSS class to add to the '''<html>''' tag. Currently it is added or removed only after a reload (see needReload).
* '''default''' - Indicates the default value to use for this preference (optional, if not present the first value listed is the default).
* '''default''' - Indicates the default value to use for this preference (optional, if not present the first value listed is the default).


=== Listening for preference changes ===
=== Listening for preference changes and updating preference from code ===
 
The BGA framework offers read/write and callback for user preference changes. See [[Game interface logic: yourgamename.js#User preferences]]
The BGA framework lacks any callback to notify your game when a user preference is changed (see [https://studio.boardgamearena.com/bug?id=36 proposal #36]), but you can create your own if you need to run some UI code in response to a preference change.
 
; In ggg.js
<pre>
    setup: function (gamedatas) {
      ... // your setup code here
      this.setupPreference();
    },
   
    setupPreference: function () {
      // Extract the ID and value from the UI control
      var _this = this;
      function onchange(e) {
        var match = e.target.id.match(/^preference_[cf]ontrol_(\d+)$/);
        if (!match) {
          return;
        }
        var prefId = +match[1];
        var prefValue = +e.target.value;
        _this.prefs[prefId].value = prefValue;
        _this.onPreferenceChange(prefId, prefValue);
      }
     
      // Call onPreferenceChange() when any value changes
      dojo.query(".preference_control").connect("onchange", onchange);
     
      // Call onPreferenceChange() now
      dojo.forEach(
        dojo.query("#ingame_menu_content .preference_control"),
        function (el) {
          onchange({ target: el });
        }
      );
    },
   
    onPreferenceChange: function (prefId, prefValue) {
      console.log("Preference changed", prefId, prefValue);
      ... // your code here to handle the change
    },
</pre>
 
=== Updating preference from code ===
The BGA framework lacks any method to update a user preference from the code (see [https://studio.boardgamearena.com/bug?id=36 proposal #36]), but you can create your own if you need to.
 
; In ggg.js
<pre>
    updatePreference: function(prefId, newValue) {
        // Select preference value in control:
        dojo.query('#preference_control_' + prefId + ' > option[value="' + newValue
        // Also select fontrol to fix a BGA framework bug:
            + '"], #preference_fontrol_' + prefId + ' > option[value="' + newValue
            + '"]').forEach((value) => dojo.attr(value, 'selected', true));
        // Generate change event on control to trigger callbacks:
        const newEvt = document.createEvent('HTMLEvents');
        newEvt.initEvent('change', false, true);
        $('preference_control_' + prefId).dispatchEvent(newEvt);
    },
</pre>


=== Accessing User Preferences on the server ===
=== Accessing User Preferences on the server ===
Surprise, surprise - this is lacking too. Mostly.
PHP has a variable called $this->player_preferences.
There is a variable you can acesses called $this->player_preferences.
This contains a table of player preferences, ONLY accessible in setupNewGame().
This contains table of user preferences, ONLY accessible in setupNewGame().
You can use this to populate a table that you manage yourself.
You can use this to populate a table that you manage yourself.
The table has to be updated when user changes preference (and you have to hook up a lister and initiate a special axac call out-of-turn).
You have to update the table when a player changes preference (and you have to hook up a listener and initiate an AJAX call out-of-turn).
Check the code of Agricola for details but this should work:.
Check the code of Agricola for details but this should work:


; In dbmodel.sql
; In dbmodel.sql
Line 384: Line 496:
}
}
</pre>
</pre>
Warning: <code>$this->player_preferences[$player_id]</code> can be null if the player has not set any player preferences.
Note: to access json values you can call:
  $game_preferences = $this->getTablePreferences();
Warning: if you use bga undo system make sure this table is not restored from undo state, as this likely independed from it


; In ggg.js
; In ggg.js
<pre>
<pre>
setup(gamedatas) {
 
...
// use code for initPreferencesObserver from above example
this.initPreferencesObserver();
 
},
onGameUserPreferenceChanged(prefId, prefValue) {
initPreferencesObserver() {
    dojo.query('.preference_control').on('change', (e) => {
        const match = e.target.id.match(/^preference_[cf]ontrol_(\d+)$/);
        if (!match) {
            return;
        }
        const pref = match[1];
        const newValue = e.target.value;
        this.prefs[pref].value = newValue;
        this.onPreferenceChange(pref, newValue);
    });
},
onPreferenceChange(prefId, prefValue) {
    prefId = parseInt(prefId);
     if (prefId === 101 /*TODO: You probably want to send only some preferences*/) {
     if (prefId === 101 /*TODO: You probably want to send only some preferences*/) {
         // TODO: Code your own action in ggg.action.php and send it prefId and prefValue
         // TODO: Code your own action in ggg.action.php and send it prefId and prefValue
Line 412: Line 518:
},
},
</pre>
</pre>
== Translations ==
Note that any name or description values in these JSON files are automatically added to the translation system.
== Migration ==
Previously, options and preferences were specified in a single <code>gameoptions.inc.php</code> file.
BGA has switched to a JSON format to make parsing easier on the server-side, and to avoid a reliance on PHP for static config.
The PHP format will continue to work for ''existing'' games, and although preferred, there is no need to migrate unless you want to. However, newly created games must use the new format.
'''Note''': once a game has been committed with a <code>gameoptions.json</code> file, however, it is not possible to go back without admin intervention.
If you want to migrate:
Simple method:
* Go to studio and press Reload game options configuration
* It will dump json on the log window - you can take this, format it and save into 2 files (if you have both). For gamepreference.json remove option "200" - this is default common option, it should not be in your file.
Manual method:
* Remove all calls to <code>totranslate()</code>, and replace with the plain string
* Remove any references to BGA's PHP constants, such as GAMESTATE_RATING_MODE, and replace with the plain value (in this case, 201)
* You can populate <code>gameinfos.json</code> with the result of <code>json_encode($game_options, JSON_PRETTY_PRINT)</code>
* You can populate <code>gamepreferences.json</code> with the result of <code>json_encode($game_preferences, JSON_PRETTY_PRINT)</code>
* If you include gameoptions.inc.php directly, to read the values, then replace those calls with <code>$this->getTableOptions()</code> or <code>$this->getTablePreferences()</code> as appropriate which return arrays parsed from the JSON files
This message was posted to the developer Discord channel:<blockquote>'''Game options change (Optional!)'''
<code>gameoptions.inc.php</code> is now considered legacy, and <code>gameoptions.json</code> and <code>gamepreferences.json</code> are recommended for new projects:
* The wiki and the template project have been updated: [[Options and preferences: gameoptions.json, gamepreferences.json#User Preferences|https://en.doc.boardgamearena.com/Options_and_preferences:_gameoptions.json,_gamepreferences.json]]
* Any questions/problems can come to me, or can be asked here.
* The format of the json files matches the format of the old php files
We realise there are some drawbacks (sorry!):
* No comments in the JSON file, meaning you have to check the wiki for examples
* No PHP constants in the JSON file, meaning magic numbers
However, we hope it's good for BGA because:
* Simpler to parse (for robots and humans)
* Easier to check for errors (we could perhaps use an XSLT one day)
* No need to run game-specific PHP code on the metasite
'''NOTE''': Switching is totally optional, and the legacy files will still work for existing projects, and for those who know about them. </blockquote>
[[Category:Studio]]

Latest revision as of 07:21, 5 November 2024


Game File Reference



Useful Components

Official

  • Deck: a PHP component to manage cards (deck, hands, picking cards, moving cards, shuffle deck, ...).
  • Draggable: a JS component to manage drag'n'drop actions.
  • Counter: a JS component to manage a counter that can increase/decrease (ex: player's score).
  • ExpandableSection: a JS component to manage a rectangular block of HTML than can be displayed/hidden.
  • 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)

  • Wrapper: a JS component to wrap a <div> element around its child, even if these elements are absolute positioned.

Unofficial



Game Development Process



Guides for Common Topics



Miscellaneous Resources

In gameoptions.json, you can define your game options (i.e. game variants).

In gamepreferences.json, you can define user preferences.

Note: If your game has no variants or preferences, you don't have to modify these files.

IMPORTANT: after edits to these files, you have to go to the control panel and press "Reload game options configuration" on Studio for your changes to take effect.

Make sure you understand difference between options and preferences:

  • Game options - something usually in the rule book defined as "variant" (except for player count, which is automatically handled). For example, whether to include The River in Carcassonne.
  • User preferences - personal choices of each player only visible to that specific player - i.e. layout, whether or not to prompt for action, whether or not auto-opt in in some actions, etc.

Game Options

Game options are selected by the table creator and usually correspond to game variants, for example if the game includes expansions or certain special rules.

These variants are defined in gameoptions.json as an object:

 {
    "100": { "name": "Game Setup", ... },
    "101": { "name": "Draft", ... }
 }

Each key corresponds to the option id, and each value is that option definition, which is described below.

Note: the file must be valid JSON. That is, trailing commas and comments are not allowed. However you can use still standard json hack to add a field like "$comment": "Here we go"

Note 2: the key in json file must be a string type, even its correspond to a number in other places (such as php, or other references in same json).

All options defined in this file should have a corresponding "game state label" with the same ID (see "initGameStateLabels" in yourgame.game.php)

            self::initGameStateLabels ([
                       ...
                       "my_game_variant" => 100,
             ]);

Numbers have to be exactly from 100 to 199 (there can be gaps).

That is how you access them during runtime (by name):

   public function isSecondVariant() {
       return $this->getGameStateValue('my_game_variant') == 2;
   }

Or this (by numberic id)

   $this->gamestate->table_globals[100]

To access json data (the metadata only) can use

  $game_options = $this->getTableOptions();

Details of game options format

The following are the values of the option definition object:

  • name - mandatory. The name of the option visible for table creator. This is automatically marked for translation.
  • values - mandatory. The map representing possible values of this option. The key of the map is possible value of this option, its a number but has to be string in json. The value is an object descring it.
    • name - mandatory. String representation of the numeric value visible to table creator. This is automatically marked for translation.
    • description - String description of this value to use when the name of the option is not self-explanatory. Displayed at the table under the option when this value is selected. Note: if there is no description, this should be omitted.
    • tmdisplay - String representation of the option visible in the table description in the lobby. Usually if a variant values are On and Off (default), the tmdisplay would be same as description name when On, and nothing (empty string) when Off. (Warning: due to some caching, a change in tmdisplay may not be effective immediately in the studio, even after forcing a reload of options.) Pro Tip: You can use this as a pre-game communication by adding fake options that just do nothing in the game but make it easier to find other player wanted the same game configuration (see the crew deep sea for example).
    • nobeginner - Set to true if not recommended for beginners
    • firstgameonly - Set to true if this option is recommended only for the first game (discovery option)
    • beta - Set to true to indicate that this option is in "beta" development stage (there will be a warning for players starting the game)
    • alpha - Set to true to indicate that this option is in "alpha" development stage (there will be a warning, and starting the game will be allowed only in training mode except for the developer)
    • premium - Option can be only used by premium members
  • default - indicates the default value to use for this option (optional, if not present the first value listed is the default)
  • displaycondition - (array of conditions) - checks the conditions before displaying the option for selection. All (or any) conditions must be true for the option to be displayed. All or any depends on value of displayconditionoperand

Supported display condition types:

    • minplayers condition ensures at least this many players (Note: if your game works with a disjoint interval of player counts, you can supply an array of valid counts instead of a single value)
    • maxplayers condition ensures at most this many players
    • otheroption condition ensures another option is set to given values.
    • otheroptionisnot condition ensure another option is NOT set to this given values
  • displayconditionoperand - can be 'and' (this is the default) or 'or'. Allows to change the behaviour to display the option if one of the conditions is true instead of all of them.
  • startcondition - (map from value to conditions array) - checks the conditions (on options VALUES) before starting the game. All conditions must be true for the game to start, otherwise players will get a red error message when attempting to begin the game.

Supported start condition types:

    • minplayers condition ensures at least this many players (an array of values is not supported here)
    • maxplayers condition ensures at most this many players
    • otheroption conditions ensure another option is set to this given values. That works the same as in displaycondition.
    • otheroptionisnot conditions ensure another option is NOT set to this given value. That works the same as in displaycondition.
    For all these condition types, a gamestartonly boolean option can be added, if you have options that are exclusive. Setting this boolean to true will defer the evaluation of the startcondition to the game creation, instead of preventing the player to select options that are exclusive at all. See below for an example. But this should never be used -- see the warning below
  • notdisplayedmessage - if option is not suppose to be visible because of displaycondition but this is set, the text will be visible instead of combo drop down
  • level - what kind of option it is: base, major, or additional. See below for more informations.


Common (reserved) options for all tables (reserved range 200-299):

  • 201 (const GAMESTATE_RATING_MODE) - ELO OFF (aka Training mode),
    • 0 = Normal
    • 1 = Training
    • 2 = Arena
  • 200 (const GAMESTATE_CLOCK_MODE) - game speed profile
    • [0, 1, 2] - realtime (technically <10 realtime but you cannot define range in php)
    • values >=10 - turn based (currently 10..21)
    • Note there are two similar values, 9 = realtime "no time limit with friends only" vs. 20 = turn-based "no time limit with friends only"

Example:

{
  "100": {
    "name": "Game Variant",
    "values": {
      "1": {
        "name": "Learning",
        "firstgameonly": true,
        "tmdisplay": "Learning"
      },
      "2": {
        "name": "Base",
      },
      "3": {
        "name": "Extended",
        "nobeginner": true,
        "beta": true,
        "tmdisplay": "Extended"
      }
    },
    "default": 2
  },
  "101": {
    "name": "Draft variant",
    "values": {
      "1": {
        "name": "No draft"
      },
      "2": {
        "name": "Draft",
        "tmdisplay": "Draft",
        "premium": true,
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroption",
        "id": 100,
        "value": [ 2, 3 ]
      },
      {
        "type": "otheroption",
        "id": 201,
        "value": 1
      }
    ],
    "startcondition": {
      "1": [],
      "2": [
        {
          "type": "maxplayers",
          "value": 3,
          "message": "Draft option is available for 3 players maximum."
        }
      ]
    }
  },
  "102": {
    "name": "Takeovers",
    "values": {
      "2": {
        "name": "No takeover"
      },
      "1": {
        "name": "Allow takeovers",
        "tmdisplay": "Takeovers",
        "premium": true,
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroption",
        "id": 100,
        "value": [ 3 ]
      }
    ],
    "startcondition": {
      "2": [],
      "1": [
        {
          "type": "maxplayers",
          "value": 2,
          "message": "Rebel vs Imperium Takeover Scenario is available for 2 players only."
        }
      ]
    }
  }
}

Example of option that condition on ELO off:

{
  "100": {
    "name": "Learning Game (No Research)",
    "values": {
      "1": {
        "name": "Off",
        "tmdisplay": ""
      },
      "2": {
        "name": "On",
        "tmdisplay": "Learning Game"
      }
    },
    "startcondition": {
      "2": {
        "type": "otheroption",
        "id": 201,
        "value": 1,
        "message": "Learning variant available only in friendly mode"
      }
    }
  }
}

Example of condition that is only available for REALTIME game mode:

"displaycondition": [
    { "type": "otheroption", "id": 200, "value": [0, 1, 2] }
],

Example of using condition on your own option:

{
  "102": {
    "name": "Scenarios",
    "values": {
      "1": {
        "name": "Off",
        "nobeginner": false
      },
      "2": {
        "name": "On",
        "tmdisplay": "Scenarios",
        "nobeginner": true
      }
    },
    "displaycondition": [
      {
        "type": "otheroptionisnot",
        "id": 100,
        "value": 1
      }
    ],
    "notdisplayedmessage": "Scenarios variant is not available if Learning variant is chosen"
  }
}

Example of handling solo vs multiplayer options:

{
  "100": {
    "name": "Board setup",
    "values": {
      "1": {
        "name": "Mirror setup",
        "description": "The starting player shuffles their 6 Farm Cards and randomly lays a card face up in each of the round spaces of their Fruit Island Board. The other players then lay their cards in exactly the same way, copying the order of the starting player.",
        "tmdisplay": "Mirror setup"
      },
      "2": {
        "name": "Random setup",
        "description": "Instead of every player copying the same card configuration as the starting player, every player shuffles their Farm Cards and lays down the cards randomly on their Fruit Island Board.",
        "tmdisplay": "Random setup"
      }
    },
    "displaycondition": [
      {
        "type": "minplayers",
        "value": [
          2,
          3,
          4
        ]
      }
    ]
  },
  "102": {
    "name": "Solo difficulty",
    "values": [
      {
        "name": "Banana-apprentice",
        "description": "Do not remove any seed before starting the game.",
        "tmdisplay": "Banana-apprentice"
      },
      {
        "name": "Pear to the Throne",
        "description": "Remove 1 seed before starting the game.",
        "tmdisplay": "Pear to the Throne"
      }
    ],
    "displaycondition": [
      {
        "type": "maxplayers",
        "value": 1
      }
    ]
  }
}

displaycondition vs startcondition

displaycondition should be used when an option should not be present in the list under certain conditions.

For example, displaying the option which is consistent with the player count.

  • 2 player maps: A B C D
  • 3 player maps: E F G H
  • 4 player maps: I J K L

startcondition should be used when a specific combination of option values is invalid, but the option itself still makes sense to show.

For example:

  • Player 1 faction: A, B, C, D, E, F, G
  • Player 2 faction: A, B, C, D, E, F, G
  • startcondition: both players can't be the same faction

These options should both be displayed at all times, but there are some invalid configurations.

The other difference is displaycondition affect option itself, while startcondition affect specific values selected

gamestartonly

WARNING: See studio bug #104. You should NEVER use gamestartonly! Otherwise, you allow players to (unknowingly!) create a turn-based table with impossible options that can never start. Players receive no indication that the option combination they selected is invalid until the last player joins the table (hours or days later) and the game attempts to auto-start. The table won't start because of the startcondition, and the table options cannot be changed because it's a turn-based table, so the table must be abandoned. This is a horrible user experience. Please don't subject players to this.

On the table configuration page, it won't let you select a combination which is invalid according to startcondition. Doing so will show a red warning, and will revert the option to the previous value. This means you could end up in a situation where you can't easily change between certain options (requiring you to set or unset options in a specific order).

The consequence of the gamestartonly flag is that the player _can_ select an invalid combination. However, when clicking "Start", an invalid combination will produce an error.

Probably the easiest way to see what the difference is is with an example.

With a _Clans of Caledonia_ table:

  • set to training mode
  • set player count to 1
  • try setting "Clan Auction" to one of the "On" options
  • try starting the game, there's an error! Ie. "gamestartonly" => true

Then, with a _Sushi Go Party!_ table:

  • set number of players to 7
  • try setting "Sushi Go! / Sushi Go Party!" to "Sushi Go!"
  • you can't set the option! Ie. "gamestartonly" => false

In code:

{
  "100": {
    "name": "Option name",
    "values": [],
    "startcondition": {
      "1": [
        {
          "type": "maxplayers",
          "value": 2,
          "message": "This option is available for 2 players only.",
          "gamestartonly": true
        }
      ]
    }
  }
}

level

Note: At this moment this only has an impact in fancy lobby mode; The presentation of level or checkboxes is not available via normal table creation UI (such as in studio, or when you click Play button in control panel).

{
    "100": {
        "name": "Option name",
        "values": [...],
        "level": "major"
    }
}
  • base is the default value (you don't need to specify it).
  • major denotes a major option, which will always be displayed on top, and with a specific UI.
  • additional means this option will not be displayed by default, to unclutter the option panel.

About major options:

  • a game can only have 1 major option, and of course, it must be a important game changer.
  • the pictures will default to the standard Game Box, and can be set independently for each value, from the Game metadata manager (in the "Major Variants" section). Note: this cannot be tested from Studio as there is not studio-only Game metadata manager, to see Major Variants option - the game has to be deployed at least as alpha.
  • the major option can have more than 2 values (there will then be an arrow to slide to the other values, carousel-like)
  • the naming of major variant values should be concise and the values should have descriptive text (not just "enabled" or "disabled")
    • 👍 Option name "Expansion", option values "Base game", "Bigger is Better" => Displayed as "Expansion: Base game" or "Expansion: Bigger is Better"
    • 👎 Option name "Bigger is Better", option values "Enabled", "Disabled => Displayed as "Bigger is Better: Enabled" or "Bigger is Better: Disabled"

About additional options:

  • Please set each option that concerns small details in this category: Advanced players will find this option anyway, and it will simplify the interface of the page of your game.

Option levels

option presentation

An option WILL be displayed as a Checkbox instead of a selector if certain conditions are met:

  • option has only 2 values
  • values are either (case insensitive):
    • yes and no
    • on and off
    • enabled and disabled

The "default" value still has to be specified, and can be "on" or "off" (it's actually just a difference in display).


Example of checkbox display


User Preferences

User preferences is something cosmetic about the game interface which however can create user wars, so you can satisfy all users by giving them individual preferences. You should use this only if it significantly improves the interface for a large proportion of users. These preferences appear in the three-line hamburger menu in the top corner of the bga menu (and also at the bottom on the page in the Options tab). "Display game logs" and "Display tooltips" are baked-in by default, but you can extend this list as below.

The preference json slightly resembles options, but these are conceptually different. The numbers comes from a different space and do not correspond or conflict with options, i.e. preference 100 has nothing to do with option 100. You can use range 100-199 with gaps.

The user preferences menu for the game Century


These preferences are a good place to put accessibility options - as Century did for its Colorblind Support.

{
  "100": {
    "name": "Colorblind Support",
    "needReload": true,
    "values": {
      "1": {
        "name": "None",
        "cssPref": "colorblind_off"
      },
      "2": {
        "name": "Numbers",
        "cssPref": "colorblind_on"
      },
      "3": {
        "name": "Shapes",
        "cssPref": "colorblind_shapes"
      }
    },
    "default": 1
  }
}

There is two ways to check/apply this. In javascript

 if (this.getGameUserPreference(100) == 2) ...

This checks if preferences 100 has selected value 2.

Second, if cssPref is specified, it will be applied to the <html> tag. So you can use different css styling for the preference. Note: you also need to set needReload to true for that class change to be effective.

As user you have to select them from the Gear menu when game is started. On studio only dev0 account will have it actually working (bug?).

The following are the parameters of preferences description array:

  • name - mandatory. The name of the preference. This string is marked for translation.
  • needReload - If set to true, the game interface will auto-reload after a change of the preference.
  • values - mandatory. The map of values with additional parameters per value.
    • name - mandatory. String representation of the numeric value. This string is marked for translation.
    • cssPref - CSS class to add to the <html> tag. Currently it is added or removed only after a reload (see needReload).
  • default - Indicates the default value to use for this preference (optional, if not present the first value listed is the default).

Listening for preference changes and updating preference from code

The BGA framework offers read/write and callback for user preference changes. See Game interface logic: yourgamename.js#User preferences

Accessing User Preferences on the server

PHP has a variable called $this->player_preferences. This contains a table of player preferences, ONLY accessible in setupNewGame(). You can use this to populate a table that you manage yourself. You have to update the table when a player changes preference (and you have to hook up a listener and initiate an AJAX call out-of-turn). Check the code of Agricola for details but this should work:

In dbmodel.sql
CREATE TABLE IF NOT EXISTS `user_preferences` (
  `player_id` int(10) NOT NULL,
  `pref_id` int(10) NOT NULL,
  `pref_value` int(10) NOT NULL,
  PRIMARY KEY (`player_id`, `pref_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
In ggg.game.php
protected function setupNewGame($players, $options = array())
{
// TODO: Save $this->player_preferences: key is player_id, value is array with pref_id as key and pref_value as value
}

Warning: $this->player_preferences[$player_id] can be null if the player has not set any player preferences.

Note: to access json values you can call:

  $game_preferences = $this->getTablePreferences();

Warning: if you use bga undo system make sure this table is not restored from undo state, as this likely independed from it

In ggg.js

// use code for initPreferencesObserver from above example

onGameUserPreferenceChanged(prefId, prefValue) {
    if (prefId === 101 /*TODO: You probably want to send only some preferences*/) {
        // TODO: Code your own action in ggg.action.php and send it prefId and prefValue
        //       The on the server side you can save it all in the user_preferences
        //       table you created above.
    }
},

Translations

Note that any name or description values in these JSON files are automatically added to the translation system.

Migration

Previously, options and preferences were specified in a single gameoptions.inc.php file.

BGA has switched to a JSON format to make parsing easier on the server-side, and to avoid a reliance on PHP for static config.

The PHP format will continue to work for existing games, and although preferred, there is no need to migrate unless you want to. However, newly created games must use the new format.

Note: once a game has been committed with a gameoptions.json file, however, it is not possible to go back without admin intervention.

If you want to migrate:

Simple method:

  • Go to studio and press Reload game options configuration
  • It will dump json on the log window - you can take this, format it and save into 2 files (if you have both). For gamepreference.json remove option "200" - this is default common option, it should not be in your file.

Manual method:

  • Remove all calls to totranslate(), and replace with the plain string
  • Remove any references to BGA's PHP constants, such as GAMESTATE_RATING_MODE, and replace with the plain value (in this case, 201)
  • You can populate gameinfos.json with the result of json_encode($game_options, JSON_PRETTY_PRINT)
  • You can populate gamepreferences.json with the result of json_encode($game_preferences, JSON_PRETTY_PRINT)
  • If you include gameoptions.inc.php directly, to read the values, then replace those calls with $this->getTableOptions() or $this->getTablePreferences() as appropriate which return arrays parsed from the JSON files

This message was posted to the developer Discord channel:

Game options change (Optional!)

gameoptions.inc.php is now considered legacy, and gameoptions.json and gamepreferences.json are recommended for new projects:

We realise there are some drawbacks (sorry!):

  • No comments in the JSON file, meaning you have to check the wiki for examples
  • No PHP constants in the JSON file, meaning magic numbers

However, we hope it's good for BGA because:

  • Simpler to parse (for robots and humans)
  • Easier to check for errors (we could perhaps use an XSLT one day)
  • No need to run game-specific PHP code on the metasite

NOTE: Switching is totally optional, and the legacy files will still work for existing projects, and for those who know about them.