Overview
When we last saw our hero game being developed it was just starting to handle Dominoes - wiring up some of the plumbing and being able to determine if a played card is valid. Now I want to make it look more like how it would in person, by showing the played cards in the center of the table and laid in order by suit.
Layout cards in the center of the table
The first step towards this is to create a place for the cards to go by modifying the
<game_name>_<game_name>.tpl
file.<div id="playertables" class="whiteblock">
<!-- BEGIN player -->
<div class="playertable playertable_{DIR}">
<div class="playertablename" style="color:#{PLAYER_COLOR}">{PLAYER_NAME}</div>
<div id="dealer_p{PLAYER_ID}" class="playertableselectedgame">{SELECTED_GAME}</div>
<div id="playertablecard_{PLAYER_ID}" class="playertablecard"></div>
</div>
<!-- END player -->
<div id="dominoesplayarea"></div>
</div>
<div id="myhand_wrap" class="whiteblock">
<h3>{MY_HAND}</h3>
<div id="myhand">
</div>
</div>
<script type="text/javascript">
// Javascript HTML templates
var jstpl_card = '<div class="card cardontable" id="cardontable_${card_id}" style="background-position:-${x}px -${y}px"></div>';
var jstpl_game_display = '<div id="glt_game_${game_type}_${player_id}" class="game_display game_display_${game_type} ${played}">${game_abbr}</div>';
</script>
The changes here are moving the "whiteblock" class from the inner
div
beginning the player to the playertables
div and adding the dominoesplayarea
div. The last change is a change to the id
attribute for the jstpl_card
template. Previously it was cardontable_${player_id}
. Unfortunately, when the same player puts multiple cards on the table the IDs collide and the behavior gets a little funny when you try moving them around. Fortunately, nothing was looking for them based on the player so the change didn't impact much other than swapping out the player_id
for card_id
when building the template.Then the styling needs to be updated to allow for cards to be put in this newly created div.
#dominoesplayarea {
position: relative;
width: 440px;
height: 300px;
top: 140px;
left: 180px;
}
#playertables {
position: relative;
width: 800px;
height: 580px;
}
This makes the
playertables
div take up enough space to allow for the cards to be played and puts the dominoesplayarea
tucked inside of the area the player puts their cards during the other games.Okay, let's actually put a card there
When a card is played the back end sends a
playCard
notification to the front end that currently makes the card visible under the player's name. I could just modify that to be aware of what game was being played, but I opted to create a different one to try to keep the understanding of what the game is on the back end. So the playCard
function in the back end now looks like this.function playCard($card_id) {
self::checkAction("playCard");
$player_id = self::getActivePlayerId();
$current_card = $this->cards->getCard($card_id);
$selected_game_id = self::getGameStateValue(SELECTED_GAME);
if ($this->games[$selected_game_id]['type'] == "dominoes") {
$this->checkPlayableCardForDominoes($current_card);
$this->cards->moveCard($card_id, CARDS_ON_TABLE, $player_id);
if (!self::getGameStateValue(SPINNER)) {
self::setGameStateValue(SPINNER, $current_card['type_arg']);
}
self::notifyAllPlayers(
'playCardForDominoes',
clienttranslate('${player_name} plays ${suit_displayed}${value_displayed}'),
[
'player_name' => self::getActivePlayerName(),
'suit_displayed' => $this->suits[$current_card['type']]['name'],
'value_displayed' => $this->values_label[$current_card['type_arg']],
'suit' => $current_card['type'],
'value' => $current_card['type_arg'],
'card_id' => $card_id,
'player_id' => $player_id,
'spinner' => self::getGameStateValue(SPINNER)
]
);
$this->gamestate->nextState("turnTaken");
} else {
$this->checkPlayableCard($player_id, $current_card);
$this->cards->moveCard($card_id, CARDS_ON_TABLE, $player_id);
if (!self::getGameStateValue(TRICK_SUIT)) {
self::setGameStateValue(TRICK_SUIT, $current_card['type']);
}
self::notifyAllPlayers(
'playCard',
clienttranslate('${player_name} plays ${suit_displayed}${value_displayed}'),
[
'player_name' => self::getActivePlayerName(),
'suit_displayed' => $this->suits[$current_card['type']]['name'],
'value_displayed' => $this->values_label[$current_card['type_arg']],
'suit' => $current_card['type'],
'value' => $current_card['type_arg'],
'card_id' => $card_id,
'player_id' => $player_id
]
);
$this->gamestate->nextState("cardPlayed");
}
}
I realized while developing the front end that it needed to know what the spinner was to help with placing cards appropriately. (The spinner is rotated perpendicular to the other cards so players don't forget what rank it is.) Now the front end needs to handle this notification. Reminder - that involves updating the
setupNotifications
function to subscribe to it and indicate the function to call. I also called setSynchronous
for it with a 100 delay, just like for the playCard
notification. Here's the notif_playCardForDominoes
function.notif_playCardForDominoes : function(notif) {
this.playCardInCenter(
notif.args.player_id,
notif.args.suit,
notif.args.value,
notif.args.card_id,
notif.args.spinner
);
},
Yeah - not too exciting, since the same logic will need to be called in the
setup
function, but we'll get to that after the real work is shown.playCardInCenter : function(player_id, suit, value, card_id, spinner) {
dojo.place(this.format_block('jstpl_card', {
x : this.cardwidth * (value - 2),
y : this.cardheight * (suit - 1),
card_id : card_id
}), 'dominoesplayarea');
if (player_id != this.player_id) {
// Some opponent played a card
// Move card from player panel
this.placeOnObject('cardontable_' + card_id, 'overall_player_board_' + player_id);
} else {
// You played a card. If it exists in your hand, move card from there and remove
// corresponding item
if ($('myhand_item_' + card_id)) {
this.placeOnObject('cardontable_' + card_id, 'myhand_item_' + card_id);
this.playerHand.removeFromStockById(card_id);
}
}
dojo.style('cardontable_' + card_id, 'z-index', value);
const x_pos = 20 + (suit-1) * 110;
const y_pos = 200 - ((value-7) * 20) - (Number(value) > Number(spinner) ? 30 : 0);
this.slideToObjectPos('cardontable_' + card_id, 'dominoesplayarea', x_pos, y_pos).play();
if (value === spinner) {
this.rotateTo('cardontable_' + card_id, 90);
}
},
This is very similar to the
playCardOnTable
function that handles other games, but the jstpl_card
template is put into a different div, and the new element for the template is slid to a more specific spot. The z-index
is used to avoid cards going hiding on a refresh since they are placed overlapping each other. Finally, if it's a spinner rotate it.Now we need to update the
setup
function so when someone reloads the page it won't mess with things.for (let i in gamedatas.cardsontable) {
const card = gamedatas.cardsontable[i];
const suit = card.type;
const value = card.type_arg;
var player_id = card.location_arg;
if (gamedatas.selected_game_type === 'dominoes') {
this.playCardInCenter(player_id, suit, value, card.id, gamedatas.spinner);
} else {
this.playCardOnTable(player_id, suit, value, card.id);
}
}
With this function being aware of the selected game it makes me seriously consider the benefit of using a different notification. I'm going to stick with it for now since it's working, but might revisit that choice after Dominoes is supported.
Conclusion
Almost there! I'm psyched with how the cards are showing up for Dominoes and the game can mostly be played through. Just a couple of remaining details.
- When an Ace is played the player can play as many cards as they want to - as long as they're valid - before passing.
- Need to end the game, which involves scoring it.
- Need to tighten up the passing and at least know if the player passed when they could've played.
No comments:
Post a Comment