Monday, December 18, 2023

Getting a Hand of Cards to Show

Overview

This is the next step in my learning how to develop a game for the online platform Board Game Arena (BGA). This skips over some things that are necessary for development that are covered in the Hearts tutorial as well as duplicating parts of it. The intent is to capture things with a different focus and to help reinforce some connections that I missed when I was going through the tutorial.

The game I'm working on through this uses a poker deck of cards, but the tooling is designed to work with all sorts of card types that are used in various games. Some naming of things might not make as much sense in this context, but there's a reason behind it. :)

Initialize a deck of cards

The Deck component (see documentation) provides the basics for the back end side of things. This allows you to define and work with a deck in the abstract as the card images are not needed until we get to the front end. To use the Deck you need a database table to hold details about each card.

CREATE TABLE IF NOT EXISTS `card` (
  `card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `card_type` varchar(16) NOT NULL,
  `card_type_arg` int(11) NOT NULL,
  `card_location` varchar(16) NOT NULL,
  `card_location_arg` int(11) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Initialize a deck and point it at this table as part of the construction of the game.
$this->cards = self::getNew("module.common.deck");
$this->cards->init("card"); // The string passed here must match the name of the table created.

Create the card definitions and put them in a location.
$cards = [];
foreach ($this->suits as $suit_id => $suit) {
  for ($value=7; $value<=14; $value++) { // The game I'm working on is only using 7 - A
    $cards[] = ['type' => $suit_id, 'type_arg' => $value, 'nbr' => 1); // create one card of each type
  }
}
$this->cards->createCards($cards, 'deck'); // 'deck' is the location to put them in

The $this->suits needs to be defined somewhere and since it's value never changes the suggestion is to put it in material.inc.php (see documentation) so it can be referenced everywhere in the game logic.
$this->suits = [
  1 => ['name' => clienttranslate('spade'),
        'nametr' => self::_('spade')],
  2 => ['name' => clienttranslate('heart'),
        'nametr' => self::_('heart')],
  3 => ['name' => clienttranslate('club'),
        'nametr' => self::_('club')],
  4 => ['name' => clienttranslate('diamond'),
        'nametr' => self::_('diamond')],
];

Deal out the initial hand of cards

The heart and soul of a game created in BGA is the state machine as it controls the flow of the game and what is possible at each. It's important to consider when the cards should be dealt out and what should happen. The game I'm working on is a trick taking game that will need to the cards dealt out at the start of each hand. Therefore, I created a state for a newHand that goes in the states.inc.php file (see documentation). Be sure the initial state, "gameState", transitions to this new state with "transitions" => ["" => 2]".
2 => [
  "name" => "newHand",
  "description" => "",
  "type" => "game",
  "action" => "stNewHand",
  "updateGameProgression" => true,
  "transitions" => []
],

That state references an action function, stNewHand, that will deal the cards out.
$this->cards->shuffle('deck');
// Deal 8 cards to each player
$players = self::loadPlayersBasicInfos();
foreach ($players as $player_id => $player) {
  $cards = $this->cards->pickCards(8, 'deck', $player_id);
  // Notify player about their cards
  self::notifyPlayer($player_id, 'newHand', '', ['cards' => $cards]);
}

The call to self::notifyPlayer isn't strictly necessary to get the cards to show up, but it will be necessary as the game progresses and future hands are dealt out.

Finally, in terms of the backend, the hands of cards need to be made available to the front end, so it can know what to show for the hand. There is a function called getAllDatas that is used for making that happen. Adding the following to it will get the player's hand that is logged into the app.
$result['hand'] = $this->cards->getCardsInLocation('hand', $current_player_id);

Show the player's hand

The first thing is to add an image of the deck to your project. Ideally, it should be a single image to reduce loading time. I added the file img\cards.jpg to my project which you'll see referenced in the code later on. (See general advice around this in the documentation.)

Then the view needs to be updated to have a place to show the player's hand. This goes in the <game_name>_<game_name>.tpl file - something like the following will work:
<div id="myhand_wrap" class="whiteblock">
  <h3>{MY_HAND}</h3>
  <div id="myhand">
  </div>
</div>

Which then needs to be populated using javascript, so in the <game_name>.js file add a function like:
displayHand: function(cards) {
  for (var i in cards) {
    var card = cards[i];
    var suit = card.type;
    var value = card.type_arg;
    this.playerHand.addToStockWithId(this.getCardUniqueId(suit, value), card.id);
  }
},

However, for this to work requires a couple of things to be set up. BGA provides a component called Stock to help with this (see documentation). Add it to the define block at the top of the file by adding "ebg/stock".
define([
  "dojo","dojo/_base/declare",
  "ebg/core/gamegui",
  "ebg/counter",
  "ebg/stock"
],

Next, as part of the setup function define the player's hand and create the cards that can go in it. Note, when creating the hand (the call to this.playerHand.create) it expects the id of the container in the view (we defined that as 'myhand' at the beginning of this section) and the width and height of a single card in the image we added. I'm making use of a full poker deck image, though the game I'm building only makes use of the cards 7 and up, so I note there are 13 images per row, but only create values 7 through 14.
this.playerHand = new ebg.stock();
this.playerHand.create(this, $('myhand'), 72, 96);
this.playerHand.image_items_per_row = 13;
this.playerHand.setSelectionMode(1);

// Create card types
for (var suit = 1; suit <= 4; suit++) {
  for (var value = 7; value <= 14; value++) {
    var card_type_id = this.getCardUniqueId(suit, value);
    this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);
  }
}

Finally, we display the cards that are in the player's hand that we did on the backend. The setup function has a gamedatas parameter that has all the details in it, so we can refer to 'hand' that we used in the previous section when we dealt out the cards ($result['hand']). Additionally, we'll just call the displayHand function we created earlier.
this.displayHand(this.gamedatas.hand);

Conclusion

Now we have a hand of cards showing! Though, this is still only the very beginning of things as the only thing that can be done is see the first hand.

No comments: