Overview
Finally getting to the point where the game can be played most of the way through. Scoring is available for all but two of the games and which games have been selected are shown for each player. The next step I wanted to take was to make the gameplay a little quicker, so let's get into it.
Player preference to not verify card play
Currently, to play a card the player must select the card, then click the "Play card" button. Some players (and certainly me while testing it) will just want the cards to be played when they are selected. Since this is an individual player preference it should be handled with the user preferences. Which involved creating a new
gamepreferences.json
file and defining the preference with some details about its behavior.{
"100": {
"name": "Prompt for card play confirmation",
"needReload": true,
"values": {
"1": {
"name": "Enabled"
},
"2": {
"name": "Disabled"
}
},
"default": 1
}
}
All of the changes I wanted to make related to this were in the front end and referencing this preference is done through
this.prefs[100]
. The first thing I did was to not show the action button to the player. This change was done in the onUpdateActionButtons
function.case 'playerTurn':
if (this.prefs[100].value == 1) {
this.addActionButton('btnPlayCard', _('Play card'), 'onBtnPlayCard');
}
break;
I check if the value is 1, indicating it's enabled and that we want to show the action button. Then I want to play a card when it is selected. In the
setup
function, we want to add a listener to the player's hand.if (this.prefs[100].value == 2) {
dojo.connect(this.playerHand, 'onChangeSelection', this, 'onHandCardSelect');
}
In this case, we're checking if the value is 2, indicating it's disabled and the action button won't be shown. Here's the
onHandCardSelection
function.onHandCardSelect: function(control_name, item_id) {
console.log("onHandCardSelect listener");
if (!this.isCurrentPlayerActive()) return;
if (item_id === undefined) return;
this.onBtnPlayCard();
},
The check for
undefined
short circuits this function the second time it is called - when the card is unselected.Finally, there's the case when someone has preselected the card they want to play and we want to play it as soon as it gets to their turn. This means a change to the
onEnteringState
function.onEnteringState: function( stateName, args ) {
console.log( 'Entering state: '+stateName );
switch( stateName )
{
case 'playerTurn':
if (this.isCurrentPlayerActive()) {
if (this.prefs[100].value == 2) {
const selected_cards = this.playerHand.getSelectedItems();
if (selected_cards.length === 1) {
this.onBtnPlayCard();
}
}
}
break;
case 'dummmy':
break;
}
},
End a hand early when there are no more points
Some games need to be played all the way through, but others can have all of the points in the hand come out in the first round. It's a little tedious to need to play through the rest of the cards when nothing will come of it. Since this will vary based on the game selected it seems like a good place to revisit the scorer classes I created for calculating how many points each player took in their hands. I added a new
remainingPoints
function that returns true
if there are cards in hand that score points for the game. Let's take a look at the implementation for the game Queens.function remainingPoints(array $cards_in_hands): bool {
foreach ($cards_in_hands as $card) {
if ($card['type_arg'] == QUEEN || ($card['type'] == HEART && $card['type_arg'] == KING)) {
return true;
}
}
return false;
}
This checks the given array of cards to see if there are any Queens or the King of Hearts. This will be used in the
stNextPlayer
function in the <game_name>.game.php
file.if ($this->cards->countCardInLocation(HAND) == 0) {
$this->gamestate->nextState("endHand");
} else {
$scorer = $this->getScorer();
$cards_in_hands = $this->cards->getCardsInLocation(HAND);
if (!$scorer->remainingPoints($cards_in_hands)) {
$cards_left = [];
$players = $this->loadPlayersBasicInfos();
foreach ($players as $player_id => $player) {
$cards_left_list = [];
$hand = $this->cards->getCardsInLocation(HAND, $player_id);
usort($hand, [$this, "sortCards"]);
foreach ($hand as $card) {
$cards_left_list[] = $this->suits[$card['type']]['name'].''.$this->values_label[$card['type_arg']];
}
$cards_left[] = self::getPlayerNameById($player_id).' - '.implode(', ', $cards_left_list);
}
$cards_left_final = implode('<br>', $cards_left);
self::notifyAllPlayers('earlyEnd', clienttranslate('Ending the hand early as all scoring cards are out<br><br>Cards left:<br>${cards_left}'), [
'cards_left' => $cards_left_final,
'remaining_cards' => $cards_in_hands,
]);
$this->gamestate->nextState("endHand");
} else {
$this->gamestate->nextState("nextTrick");
}
}
If no points are remaining in anyone's hand, this provides a notification to inform players what cards are still out there. The front end will make use of that notification to update where the cards are visually. As is usual the
<game_name>.js
file needs to be updated in two places. The first is adding a subscription to the "earlyEnd" notification in the setupNotifications
function. The next is the implementation of the function called for that notification - notif_earlyEnd
.notif_earlyEnd: function(notif) {
for (let i in notif.args.remaining_cards) {
const card = notif.args.remaining_cards[i];
this.playCardOnTable(card.location_arg, card.type, card.type_arg, card.id);
}
document.querySelectorAll('.cardontable').forEach(e => this.slideToObjectAndDestroy(e, 'playertables'));
},
This puts all of the cards onto the table, then slides them to the "playertables" container, which holds the played cards and removes them from view.
Conclusion
I thought checking for point cards was going to be harder than it ended up being. I started exploring creating a
Card
class to encapsulate checking for equality and seeing if I could make it read better when they were created, but I ended up giving up on it. It seemed a small improvement for the work, though maybe I'll have another idea around it in the future.There are still some things that might speed up the gameplay - for instance, recognizing if the player can't lose the rest of the tricks - but I think I want to be able to finish a game. That means finishing up the scoring for Guillotine and finally getting around to dealing with Dominoes. That might need to be split over a couple of sessions as it plays completely differently.
No comments:
Post a Comment