Skip to content

PlayerCounter

PlayerCounter manages a distinct numeric value for each player, such as money or energy. For a single value shared by the table, use TableCounter.

It can automatically update a front-end Counter when its value changes.

Creating a PlayerCounter

Create a counter through bga->counterFactory in the Game constructor:

$this->playerCredits = $this->bga->counterFactory->createPlayerCounter('credits', useNo: true);

The factory accepts these parameters:

createPlayerCounter(
    string $name,
    ?int $min = 0,
    ?int $max = null,
    CounterVisibility $visibility = CounterVisibility::VISIBLE,
    bool $useNo = false,
    ?bool $strict = null,
): PlayerCounter

The default minimum is 0, with no maximum. Set either bound to null to remove it.

Import Bga\GameFramework\Components\Counters\CounterVisibility to set visibility:

  • CounterVisibility::VISIBLE (default): everyone can see the values.
  • CounterVisibility::SELF: each player can see only their own value. Other values display as "-".
  • CounterVisibility::HIDDEN: nobody can see the values; they display as "-".

With useNo: true, pass player numbers to initDb, get, set, and inc. Otherwise, pass player IDs. Strict mode rejects players who are not at the table. When strict is omitted, it is enabled for player numbers and disabled for player IDs.

API

initDb(array $playerIdsOrNos, int $initialValue = 0) Creates database table if needed (bga_player_counters). Must be called from setupNewGame (or if adding to older games in upgradeTableDb).

Parameters:

  • array $playerIdsOrNos the player IDs or numbers whose counters should be initialized. With useNo: true, supply player numbers, as in the example below. With player IDs, use array_keys($players); a game with an automaton may also need its ID.
  • int $initialValue initial value, if different than 0

get(int $playerIdOrNo): int Returns the current value of the counter for a player.

Parameters:

  • int $playerIdOrNo the player id (or no if useNo: true)

Throws:

  • UnknownPlayerException if \$playerId is not in the player ids initialized by initDb

set(int $playerIdOrNo, int $value, ?\Bga\GameFramework\NotificationMessage $message): int Set the value of the counter, and send a notif (of type setPlayerCounter) to update the value on the front side. Returns the new value.

Parameters:

  • int $playerIdOrNo the player id (or no if useNo: true)
  • int $value the new value
  • ?\Bga\GameFramework\NotificationMessage $message the next notif to send to the front. Empty for no log, null for no notif at all (the front will not be updated).

Throws:

  • OutOfRangeCounterException if the value is outside the min/max
  • UnknownPlayerException if \$playerId is not in the player ids initialized by initDb

inc(int $playerIdOrNo, int $inc, ?\Bga\GameFramework\NotificationMessage $message): int Increment the value of the counter, and send a notif (of type setPlayerCounter) to update the value on the front side. Returns the new value. Note: if the inc is 0, no notif will be sent.

Parameters:

  • int $playerIdOrNo the player id (or no if useNo: true)
  • int $inc the value to add to the current value
  • ?\Bga\GameFramework\NotificationMessage $message the next notif to send to the front. Empty for no log, null for no notif at all (the front will not be updated).

Throws:

  • OutOfRangeCounterException if the value is outside the min/max
  • UnknownPlayerException if \$playerId is not in the player ids initialized by initDb

getMin(): int Returns the lowest value.

getMax(): int Returns the highest value.

getAll(): array Return each player's value in an associative array keyed by player ID, or by player number when useNo is enabled.

setAll(int $value, ?\Bga\GameFramework\NotificationMessage $message): int Set the value of the counter for all the players, and send a notif (of type setPlayerCounterAll) to update the value on the front side. Returns the new value.

Parameters:

  • int $value the new value
  • ?\Bga\GameFramework\NotificationMessage $message the next notif to send to the front. Empty for no log, null for no notif at all (the front will not be updated).

Throws:

  • OutOfRangeCounterException if the value is outside the min/max

fillResult(array &$result, ?string $fieldName = null, ?int $currentPlayerId = null) Updates the result object, to be used in the getAllDatas function. Will set the value on each $result["players"] sub-array.

Parameters:

  • array &$result the object to update
  • ?string $fieldName the field name to set in $result["players"], if different from the counter name.
  • ?int $currentPlayerId the current player for visibility "self".

setVisibility(CounterVisibility $visibility) Set the counter visibility.

isVisible(): bool

Return if the counter is visible by anyone.

isHidden(): bool

Return if the counter is invisible.

isSelf(): bool

Return if the counter is visible by the current player only.

setUseNo(bool $useNo) Tell if the counter input will be player no (true) or player id (false)

setStrict(bool $strict) Tell if the counter input will be validated. If true, the counter will trigger an exception if the no or id doesn't exist in the game.

Notification arguments

The counter automatically supplies these notification arguments. Add only the other arguments needed by your message:

  • name: the counter name
  • value: the new value
  • oldValue: the value before the update
  • inc: the increment
  • absInc: the absolute value of the increment, useful for messages describing a decrease
  • playerId: the player ID, even when the counter uses player numbers
  • player_name: the player name

For example:

$this->playerScore->inc(
    $playerId,
    -1,
    new NotificationMessage(clienttranslate('${player_name} loses ${absInc} points with played card ${card_name}'), ['card_name' => 'Hunter'])
);

The built-in playerScore counter uses player IDs by default. Import Bga\GameFramework\NotificationMessage before using it in this example.

Example

To store and display each player's energy, import and declare the counter in the Game class:

modules/php/Game.php
use Bga\GameFramework\Components\Counters\PlayerCounter;

// Inside the Game class:
public PlayerCounter $playerEnergy;

Create it in the Game constructor:

$this->playerEnergy = $this->bga->counterFactory->createPlayerCounter('energy', useNo: true);

Initialize it in setupNewGame. The $players array is keyed by player ID, so convert those IDs to player numbers:

$playerNos = array_map(
    fn($playerId) => (int)$this->getPlayerNoById((int)$playerId),
    array_keys($players),
);
$this->playerEnergy->initDb($playerNos);

In getAllDatas, after populating $result['players']:

$this->playerEnergy->fillResult($result);

In an action method in a State class:

$this->game->playerEnergy->inc($activePlayerNo, 1);

Here, $activePlayerNo is the active player number provided to the action method.

In the front-end setup, use player numbers for element IDs and player panel access. The playerId setting of ebg.counter still requires the player ID:

In Game.js setup
for (const player of Object.values(gamedatas.players)) {
    const playerNo = this.bga.players.getPlayerNoById(Number(player.id));
    this.bga.playerPanels.getElementByNo(playerNo).insertAdjacentHTML('beforeend', `
        <div id="energy-player-counter-${playerNo}"></div>
        <span>${_("Energy")}</span>
    `);
    const counter = new ebg.counter();
    counter.create(`energy-player-counter-${playerNo}`, {
        value: player.energy,
        playerCounter: 'energy',
        playerId: this.bga.players.getPlayerIdByNo(playerNo),
    });
}

The JS counter updates automatically when you call set or inc with notifications enabled.

Listening to counter notifications

Use the predefined notification handlers to update other parts of your interface as well as the Counter:

async notif_setPlayerCounter(args) {
    const { name, value, oldValue, inc, absInc, playerId } = args;
    if (name === 'energy') {
        const playerNo = this.bga.players.getPlayerNoById(playerId);
        this.getPlayerTable(playerNo).setEnergyTokens(value);
    }
}

In this example, getPlayerTable(playerNo) is a game-defined helper that returns the player's table component. Notifications carry player IDs even when the PHP counter uses player numbers.

Use notif_setPlayerCounterAll to handle changes made with setAll.

Built-in score counters

Games provide two PlayerCounter instances by default: playerScore and playerScoreAux. They update player_score and player_score_aux in the database, along with the corresponding front-end score counters. You do not need to update these database fields manually.

These counters have no minimum and start at 0. To use a different initial value, call $this->playerScore->setAll(2), for example. By default, they accept player IDs and do not use strict mode.

The default PlayerCounter provided by game relies on reading score from gamedatas. As such, make sure that you provide it in getAllDatas():

  $result["players"] = $this->getCollectionFromDb(
            "SELECT `player_id` `id`, `player_score` AS `score` FROM `player`"
        );