This is a documentation for Board Game Arena: play board games online !
Anti-Stock
Overivew
If Stock component works for you then great, you don't need this article. If you start tweaking it and you are doing something slightly more complex and you cannot find answer, it may be best to not use it. This wiki on how to replace this. What is Stock? Its component that does a lot of stuff in one:
- It generates inline attributes of object the represent graphics and sizes of "cards" (can be anything really)
- It also manages layout of one single container of such cards, and cards within it - including animation
- It also manages selection in that container
Recipe to get rid of stock (the gist of it):
- Create all cards as divs in template file with help of view.php is needed
- Create generic card css with image, sizing etc, create/generate css per card type with background positioning - there are scripts in sharecode to generate grid positioning (what stock does)
- Create a parent for card placement and use dojo.place to move cards there, for css use your favour layout for this, including margins, etc
- Trickky one: When cards are moved for animation you have to use special method, none of animation methods from bga parent class will work as card will not have absolute positioning, you can use methods from sharedcode project or create your own (the trick is during animation object has to have absolute positioning which is removed after animation)
Presentation
In modern word the objects that we want to render will be always represented by div element in the dom. This div will have unique Id, classes and also custom data attributes which can be used for styling and selection.
This is some sort of card 1 of type card_21. Note that you probably don't need both card_21 as class and as data-num, you can use either for selection or styling.
<div id="card_21_1" class="card card_21" data-num="21"></div>
This is 10 of hearts and king of clubs inside a hand inside a game
<div id="game" class="classic_deck"> <div id="hand" class="hand"> <div id="card_H_10" class="card" data-suit='H' data-rank="10"> </div> <div id="card_C_K" class="card" data-suit='C' data-rank="K"> </div> </div> </div>
If you prefer not to use custom attributes you can do this
<div id="card_H_10" class="card_H_10 suit_H rank_10"> </div>
Card id, does not have to be like this either, I use this one because it maps to my db, but if you using Deck component for cards in "deck" table, you can use something like
<div id="deck_33" class="card_H_10 suit_H rank_10"> </div>
where 33 is id which maps to database id of deck table.
Now we can use css to show graphics and defined size. Note unlike stock - the size should not be bound this component, you cards can have different size for example in tooltips vs hand
We will use the BGA card stock https://x.boardgamearena.net/data/others/cards/FULLREZ_CARDS_ORIGINAL_NORMAL.jpg. It has 15 columns.
.card { background-image: url('https://x.boardgamearena.net/data/others/cards/FULLREZ_CARDS_ORIGINAL_NORMAL.jpg'); /* don't do full url in your game, copy this file inside img folder */ background-size: 1500% auto; /* this mean size of background is 15 times bigger than size of card, because its sprite */ border-radius: 5%; width: 10em; height: 13.5em; box-shadow: 0.1em 0.1em 0.2em 0.1em #555; } .card[data-rank="10"] { /* 10 is column number 10 - 2 because we start from 0 and first card is sprite is 2. The multiplier is (15 - 1) is because we have 15 columns. -1 is because % in CSS is weird like that. */ background-position-x: calc(100% / (15 - 1) * (10 - 2)); } .card[data-rank="K"] { /* King will be number 13 in rank */ background-position-x: calc(100% / (15 - 1) * (13 - 2)); } .card[data-suit="H"] { /* Hears row position is 1 (because we count from 0). Multiplier (4 - 1) is because we have 4 rows and -1 is because % in CSS is weird like that. */ background-position-y: calc(100% / (4 - 1) * (1)); } .card[data-suit="C"] { /* Clubs row position is 2 */ background-position-y: calc(100% / (4 - 1) * (2)); }
Now if you want to see these cards, we can put them in some specific component for example hand, and there we can define layout and sizing just for hand
.hand .card { font-size: 2.4rem; display: inline-block; }
Working example: https://codepen.io/VictoriaLa/pen/rNwgWrB
To finish the deck you have to finish css for all rows/columns which whould be only 10-20 records, really easy and repetitive! If you really cannot write that much css manually you can also generate it using php code (this is side kick command line - does not go to your game)
<?php // this simple script generates sprite css // it has no params - fix inline to change what it generates $from=1; // from index $to=40+4; // to index $maxcol=4; // number of columns in the sprite $scol=$maxcol-1; $srow=((int)(($to-$from)/$maxcol)); for ($num=$from;$num<=$to;$num++) { $index=$num-$from; $row=(int)($index/$maxcol); $col=$index%$maxcol; echo ".tech_E_$num { background-position: calc(100% / $scol * $col) calc(100% / $srow * $row);}\n"; } ?>
And now if you want to user to have preferece for what deck style to use its just matter of change class from "classic_deck" to "fancy_deck" in our game parent div and adding in CSS
.fancy_deck .card { background-image: url('https://x.boardgamearena.net/data/others/cards/FULLREZ_CARDS_DESIGN_COLORED.jpg'); }
Generating Dom Elements
Static - basic
You basically write all your cards in .tpl file. Just cut & paste you template and fix few classes. That is easiest! In your js code you never create new element, you just move existing element inside dome. If you need a copy for tooltips, logs or button you can clone one of them change id and add/remove class if needed.
<div id="limbo" class="limbo"> <div id="card_H_10" class="card" data-suit='H' data-rank="10"> </div> <div id="card_C_K" class="card" data-suit='C' data-rank="K"> </div> ... rice and repeat ... </div>
Static - bga template engine
in .tpl
in view.php
$this->page->begin_block($template, "deck_cards"); $ranks = [2,3,4,5,6,7,8,9,10,'J','Q','K','A']; $suits = ['S','H','C','D']; foreach ($ranks as $rank) { foreach ($suits as $suit) { $this->page->insert_block("deck_cards", [ 'SUIT' => "$suit, 'RANK' => "$rank, ]); }
}
Dynamic using .tpl thingy
In the js section of .tpl
var jstpl_card = '<div id="card_{SUIT}_{RANK}" class="card card_{SUIT}_{RANK}" data-suit="{SUIT}" data-rank="{RANK}"></div>';
in js:
let cardDiv = this.format_block('jstpl_card', { SUIT : 'H', RANK : 'A' }); // this in js code somewhere before placing it
Dynamic using dojo
let suit = 'H'; let rank = 'A'; let cardNode = dojo.create('div', {id: `card_${suit}_${rank}`, class: `card_${suit}_${rank}`, dataSuit: suit, dataRank: rank}); dojo.place(cardNode, 'hand'); // place in hand for example
Dynamic not using dojo
let suit = 'H'; let rank = 'A'; let div = document.createElement('div'); div.id = `card_${suit}_${rank}`; div.className = `card card_${suit}_${rank}`; div.setAttribute('data-suit',suit); div.setAttribute('data-rank',rank); //console.log(div); $('hand').appendChild(div);
As you can see this is not most compact method, so you if you do that a lot probably create a function that generates this (which also would set a click handler and tooltip for example)
Layout
You can use all web resources and powser of css to do any sort of fancy layout for your cards, you not bound to any predefined layouts. The simplest would be display: inline-block as we already did above. You can also add margins to overlap cards for example
.tableau { position: relative; width: 50em; height: 30em; outline: dashed 1px green; transform: rotate(-90deg) scale(0.5); padding: 1em; display: flex; justify-content: center; flex-direction: row; flex-wrap: wrap; } .tableau .card{ margin-right: -2em; }
In this example cards on tableu are smaller, rotated 90 degrees and overlaping.
https://codepen.io/VictoriaLa/pen/PojvWEV
Selection and Click Handlers
To hookup selection or clicking, you have to manually hook up handler to all cards, which is easy
document.querySelectorAll(".card").forEach(node=>node.addEventListener("click", (event) => { var target = event.target; target.classList.toggle('selected'); } }));
And you define class for selected in css
.selected { outline: dashed blue 0.1em; }
How if you need to do some action based on select this can be your do something button bandler (this will create array of ids of cards)
const card_ids = Array.from(document.querySelectorAll('.card.selected')).map(x=>x.id);
If you need single selection, just add a call to remove all previous 'selected' from all card, before adding it.
If you don't need selection you can hook ajax call directly to handler above.
Animation
The animation is the only tricky part because it does not come for free. The proper animation function is pretty lengthy so I won't even put it here. Basically it short you would have to reparent out object without visual movement and then you can run animation on positions, and after it is done remove absolute positioning.
The simplier method is moving object itself, you can see code and example in https://github.com/elaskavaia/bga-sharedcode/blob/master/sharedcode.js (slideToObjectRelative).
And more complex method moves "phantom" object on oversurface (which is neither parent of original object nor destination), code can found here (to use this your need 2 js methods for movement and classes for oversurface and oversurfacew > * from css):
https://codepen.io/VictoriaLa/pen/PojvWEV