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

BgaCards: Difference between revisions

From Board Game Arena
Jump to navigation Jump to search
No edit summary
No edit summary
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Studio_Framework_Navigation}}
{{Studio_Framework_Navigation}}


'''[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/demo/index.html D<big>emo</big>]'''   
'''[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/demo/index.html <big>Demo</big>]'''   


'''<big>[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/docs/index.html Doc]</big>'''
'''<big>[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/docs/index.html Doc]</big>'''
Line 8: Line 8:
'''bga-cards''' is a javascript component to display cards.
'''bga-cards''' is a javascript component to display cards.


The lib will handle associated animations (moving between stocks, flipping or rotating the cards).
The library will handle associated animations (moving between stocks, flipping or rotating the cards).


The game Frenchtarot is an example of usage (with JS), or Verso (with TypeScript)
The game Frenchtarot is an example of usage (with JS), or Verso (with TypeScript).
 
Also https://en.doc.boardgamearena.com/Tutorial_hearts is now using bga-cards for tutorial game implementation.


== Usage ==
== Usage ==
Load the lib:
Load the library:
<pre>
<pre>
define([
define([
Line 19: Line 21:
     "ebg/core/gamegui",
     "ebg/core/gamegui",
     "ebg/counter",
     "ebg/counter",
     getLibUrl('bga-animations', '1.x'), // the lib uses bga-animations so this is required!
     getLibUrl('bga-animations', '1.x'), // the library uses bga-animations so this is required!
     getLibUrl('bga-cards', '1.x'),
     getLibUrl('bga-cards', '1.x'),
],
],
Line 43: Line 45:
         });
         });
</pre>
</pre>
Only setup an animation manager if you don't already have one, else re-use the same one.
Only set up an animation manager if you don't already have one, otherwise re-use the same one.


Example of usage:
Example of usage:
<pre>
<pre>
    setup: function (gamedatas) {
        // ...


         // create the stock, in the game setup
         // create the stock, in the game setup
         this.cardStock = new BgaCards.LineStock(this.cardsManager, document.getElementById('card-stock'));
         this.cardStock = new BgaCards.LineStock(this.cardsManager, document.getElementById('card-stock'));
         this.cardStock.addCards(gamedatas.cards); // cards should be something like [{ id: 1, type: 3, type_arg: 2, location: 'table', location_arg: 0 }]  
         this.cardStock.addCards(gamedatas.cards); // cards should be something like [{ id: 1, type: 3, type_arg: 2, location: 'table', location_arg: 0 }]  
 
     
notif_revealNewCards: async function(args) {
  }
    await this.cardStock.addCards(args.newCards); // similar form as above
  notif_revealNewCards: async function(args) {
}
        await this.cardStock.addCards(args.newCards); // similar form as above
  }


</pre>
</pre>


Look at the demo page and the demo source code for a list of all possibilities!
Look at the demo page and the demo source code for a list of all possibilities!
To see the source code with all the examples - use browser developer tools - source tab (from demo link).
== Random Observations ==
=== Sprite Markup ===
If you use the stock component, the card images won't work out of the box, you have to add sprite markup manually, it can be done easily in the card manager constructor:
        setupFrontDiv: (card, div) => {
          // ...
          div.style.backgroundPositionX = `calc(100% / 14 * (${card.type_arg} - 2))`; // 14 is the number of columns in stock image minus 1
          div.style.backgroundPositionY = `calc(100% / 3 * (${card.type} - 1))`; // 3 is the number of rows in stock image minus 1
If you prefer to use your own CSS, you can mark it up with data attributes instead (and do the positioning using CSS):
        setupFrontDiv: (card, div) => {
          div.dataset.type = card.type; // suit 1..4
          div.dataset.typeArg = card.type_arg; // value 2..14
=== Card faces ===
This component supports two-faced cards, so you can do flip animations (mostly). To take advantage of that you have to define the card back also using setupBackDiv in the manager.
Beware that the function that defines what face is up is actually called isCardVisible (which is not confusing at all), and you redefine it to return a function which determines the side dynamically.
By default this function is defined as:
  isCardVisible: (card) => card.type
which is also super non-intuitive. I suggest always defining a custom function for this (which will likely just be isCardVisible: () => true), except for something like BgaCards.Deck.
=== Void Stock ===
As of 1.0.7 VoidStock still lacks some animations such as shrink and fadeOut, however the same can be achieved without VoidStock by just providing animation settings in removeCards methods, i.e.:
      await this.lineStock.removeCards(cards, {
        fadeOut: true,
        slideTo: $(`otherhand_${playerId}`),
      });
=== Type casting from PHP Deck ===
You would think you can just use the database method to get cards from Deck and send this to the UI and use it in bga-cards? No, it's not that easy.
Two problems:
1) The default db accessor creates a map rather than an array, let's say you do this in getAllDatas in PHP:
        // Cards in player hand
        $result['hand'] = $this->cards->getCardsInLocation('hand', $current_player_id);
In .js you have to convert this to an array (or in PHP), i.e.:
      this.handStock.addCards(Object.values(this.gamedatas.hand));
OR the same in PHP (don't need both):
      $result['hand'] =  array_values($this->cards->getCardsInLocation('hand', $current_player_id));
2) Some of the bga-cards methods, most notably BgaCards.sort, assume fields (such as 'type') are integers, but if sent by PHP like in the example above they are strings (even worse, some APIs return strings and some integers).
If this is the ONLY problem (i.e. sort) you can get away with making your own sort function, otherwise you would have to make an object converter and apply it to all objects sent from notifications and getAllDatas that need to go in bga-cards APIs.
See the hearts tutorial for helper method examples (i.e. find the method remapToBgaCard).
=== Asynchronicity ===
It's not immediately obvious but many functions that add or remove cards are async (i.e. return Promise), that means not only do you have to wait (or chain) them if running one after another, you also have
to be aware of residual effects of promises when using these methods in setup(), onEnteringState() and so on.
For example addCards is async while setSelectableCards is not. So if you add cards to your "hand" in the setup method, and try to set what is selectable in onEnteringState - that won't work properly,
unless you also promisify the call to setSelectableCards using setTimeout.


== Versioning ==
== Versioning ==
The lib is using semver, so you can require 1.x to be sure to have the last fixes without risking a breaking change. Any breaking change will be noted on the Changelog section.
The library is using semver, so you can require 1.x to be sure to have the latest fixes without risking a breaking change. Any breaking change will be noted in the Changelog section.


== Using with TypeScript ==
== Using with TypeScript ==
If you use TypeScript and this lib, you can download the '''[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/dist/bga-cards.d.ts d.ts]''' file to put in on your game folder to benefit from auto-completion. Depending on the way you build, you might need to remove the last line (the export instruction) to be able to use it.
If you use TypeScript and this library, you can download the '''[https://x.boardgamearena.net/data/game-libs/bga-cards/1.x/dist/bga-cards.d.ts d.ts]''' file to put in your game folder to benefit from auto-completion. Depending on the way you build, you might need to remove the last line (the export instruction) to be able to use it.


If your game class is not declared on the define callback, you will need to modify it with this trick (to avoid a "ReferenceError: BgaAnimations is not defined" error) :
If your game class is not declared on the define callback, you will need to modify it with this trick (to avoid a "ReferenceError: BgaAnimations is not defined" error) :
Line 72: Line 130:
         "dojo/_base/declare",
         "dojo/_base/declare",
         "ebg/core/gamegui",
         "ebg/core/gamegui",
        "ebg/counter",
        "ebg/stock",
         getLibUrl('bga-animations', '1.x'),
         getLibUrl('bga-animations', '1.x'),
        getLibUrl('bga-cards', '1.x'),
     ],
     ],
function (dojo, declare, gamegui, counter, stock, BgaAnimations) {
function (dojo, declare, gamegui, BgaAnimations, BgaCards) {
     (window as any).BgaAnimations = BgaAnimations; //trick
     (window as any).BgaAnimations = BgaAnimations; //trick
    (window as any).BgaCards = BgaCards;
     return declare("bgagame.reforest", ebg.core.gamegui, new Reforest());
     return declare("bgagame.reforest", ebg.core.gamegui, new Reforest());
});
});
Line 83: Line 141:


== Changelog ==
== Changelog ==
'''1.0.9''': fix VoidStock.addCards signature
'''1.0.8''': fix slot & card selection override
'''1.0.8''': fix slot & card selection override



Latest revision as of 17:27, 14 November 2025


Game File Reference



Useful Components

Official

  • Deck: a PHP component to manage cards (deck, hands, picking cards, moving cards, shuffle deck, ...).
  • PlayerCounter and TableCounter: PHP components to manage counters.
  • 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).
  • bga-animations : a JS component for animations.
  • bga-cards : a JS component for cards.
  • bga-dice : a JS component for dice.
  • bga-autofit : a JS component to make text fit on a fixed size div.
  • bga-score-sheet : a JS component to help you display an animated score sheet at the end of the game.

Unofficial



Game Development Process



Guides for Common Topics



Miscellaneous Resources

Demo

Doc

Overview

bga-cards is a javascript component to display cards.

The library will handle associated animations (moving between stocks, flipping or rotating the cards).

The game Frenchtarot is an example of usage (with JS), or Verso (with TypeScript).

Also https://en.doc.boardgamearena.com/Tutorial_hearts is now using bga-cards for tutorial game implementation.

Usage

Load the library:

define([
    "dojo","dojo/_base/declare",
    "ebg/core/gamegui",
    "ebg/counter",
    getLibUrl('bga-animations', '1.x'), // the library uses bga-animations so this is required!
    getLibUrl('bga-cards', '1.x'),
],
function (dojo, declare, gamegui, counter, BgaAnimations, BgaCards) { // note that the index of `BgaAnimations` must match the index of the define array

In your game setup:

        // create the animation manager, and bind it to the `game.bgaAnimationsActive()` function
        this.animationManager = new BgaAnimations.Manager({
            animationsActive: () => this.bgaAnimationsActive(),
        });

        // create the card manager
        this.cardsManager = new BgaCards.Manager({
            animationManager: this.animationManager,
            type: 'mygame-card',
            getId: (card) => card.id,
            setupFrontDiv: (card, div) => {
                div.style.background = 'blue';
                this.addTooltipHtml(div.id, `tooltip of ${card.type}`);
            },
        });

Only set up an animation manager if you don't already have one, otherwise re-use the same one.

Example of usage:

    setup: function (gamedatas) {
        // ...

        // create the stock, in the game setup
        this.cardStock = new BgaCards.LineStock(this.cardsManager, document.getElementById('card-stock'));
        this.cardStock.addCards(gamedatas.cards); // cards should be something like [{ id: 1, type: 3, type_arg: 2, location: 'table', location_arg: 0 }] 
      
   }
   notif_revealNewCards: async function(args) {
        await this.cardStock.addCards(args.newCards); // similar form as above
   }

Look at the demo page and the demo source code for a list of all possibilities!

To see the source code with all the examples - use browser developer tools - source tab (from demo link).


Random Observations

Sprite Markup

If you use the stock component, the card images won't work out of the box, you have to add sprite markup manually, it can be done easily in the card manager constructor:

       setupFrontDiv: (card, div) => {
          // ...
         div.style.backgroundPositionX = `calc(100% / 14 * (${card.type_arg} - 2))`; // 14 is the number of columns in stock image minus 1
         div.style.backgroundPositionY = `calc(100% / 3 * (${card.type} - 1))`; // 3 is the number of rows in stock image minus 1

If you prefer to use your own CSS, you can mark it up with data attributes instead (and do the positioning using CSS):

       setupFrontDiv: (card, div) => {
         div.dataset.type = card.type; // suit 1..4
         div.dataset.typeArg = card.type_arg; // value 2..14

Card faces

This component supports two-faced cards, so you can do flip animations (mostly). To take advantage of that you have to define the card back also using setupBackDiv in the manager. Beware that the function that defines what face is up is actually called isCardVisible (which is not confusing at all), and you redefine it to return a function which determines the side dynamically. By default this function is defined as:

  isCardVisible: (card) => card.type

which is also super non-intuitive. I suggest always defining a custom function for this (which will likely just be isCardVisible: () => true), except for something like BgaCards.Deck.

Void Stock

As of 1.0.7 VoidStock still lacks some animations such as shrink and fadeOut, however the same can be achieved without VoidStock by just providing animation settings in removeCards methods, i.e.:

     await this.lineStock.removeCards(cards, {
       fadeOut: true,
       slideTo: $(`otherhand_${playerId}`),
     });

Type casting from PHP Deck

You would think you can just use the database method to get cards from Deck and send this to the UI and use it in bga-cards? No, it's not that easy. Two problems: 1) The default db accessor creates a map rather than an array, let's say you do this in getAllDatas in PHP:

       // Cards in player hand
       $result['hand'] = $this->cards->getCardsInLocation('hand', $current_player_id);

In .js you have to convert this to an array (or in PHP), i.e.:

     this.handStock.addCards(Object.values(this.gamedatas.hand));

OR the same in PHP (don't need both):

     $result['hand'] =  array_values($this->cards->getCardsInLocation('hand', $current_player_id));

2) Some of the bga-cards methods, most notably BgaCards.sort, assume fields (such as 'type') are integers, but if sent by PHP like in the example above they are strings (even worse, some APIs return strings and some integers). If this is the ONLY problem (i.e. sort) you can get away with making your own sort function, otherwise you would have to make an object converter and apply it to all objects sent from notifications and getAllDatas that need to go in bga-cards APIs. See the hearts tutorial for helper method examples (i.e. find the method remapToBgaCard).

Asynchronicity

It's not immediately obvious but many functions that add or remove cards are async (i.e. return Promise), that means not only do you have to wait (or chain) them if running one after another, you also have to be aware of residual effects of promises when using these methods in setup(), onEnteringState() and so on. For example addCards is async while setSelectableCards is not. So if you add cards to your "hand" in the setup method, and try to set what is selectable in onEnteringState - that won't work properly, unless you also promisify the call to setSelectableCards using setTimeout.

Versioning

The library is using semver, so you can require 1.x to be sure to have the latest fixes without risking a breaking change. Any breaking change will be noted in the Changelog section.

Using with TypeScript

If you use TypeScript and this library, you can download the d.ts file to put in your game folder to benefit from auto-completion. Depending on the way you build, you might need to remove the last line (the export instruction) to be able to use it.

If your game class is not declared on the define callback, you will need to modify it with this trick (to avoid a "ReferenceError: BgaAnimations is not defined" error) :

define([
        "dojo",
        "dojo/_base/declare",
        "ebg/core/gamegui",
        getLibUrl('bga-animations', '1.x'),
        getLibUrl('bga-cards', '1.x'),
    ],
function (dojo, declare, gamegui, BgaAnimations, BgaCards) {
    (window as any).BgaAnimations = BgaAnimations; //trick
    (window as any).BgaCards = BgaCards; 
    return declare("bgagame.reforest", ebg.core.gamegui, new Reforest());
});

Changelog

1.0.9: fix VoidStock.addCards signature

1.0.8: fix slot & card selection override

1.0.7: fix HandStock blinking for loose width container / fix wrong deck number on constructor / fix removeCard.fadeOut without slideTo

1.0.6: fix autoPlace with VoidStock

1.0.5: fix selection of parent card if it contains a child card

1.0.4: fix error on emptyHandMessage not defined

1.0.3: fix emptyHandMessage conflicting with sort

1.0.2: fix selection style override

1.0.1: Fix documentation

1.0.0: Initial version