diff --git a/velonimo.css b/velonimo.css index 162f80f..6aec7ae 100644 --- a/velonimo.css +++ b/velonimo.css @@ -86,10 +86,6 @@ Board box-shadow: 2px 2px 5px black; border: 1px solid #3b3119; } -/* @TODO: remove if unused */ -#board-carpet.yellow { - background-color: #e9c646; -} .player-table { position: absolute; /* position is dynamically computed */ @@ -101,7 +97,7 @@ Board box-sizing: border-box; background-color: rgba(255,255,255,0.7); padding: 5px; - z-index: 2; + z-index: 0; } .player-table.active { border: 2px solid #ff0000; @@ -348,6 +344,9 @@ END Cards /** Animations */ +.moving-card { + z-index: 100; +} .moving-jersey { position: absolute; width: 65px; diff --git a/velonimo.game.php b/velonimo.game.php index 6761ace..f640265 100644 --- a/velonimo.game.php +++ b/velonimo.game.php @@ -385,6 +385,11 @@ function playCards(array $playedCardIds, bool $cardsPlayedWithJersey) { ); $numberOfCurrentPlayerCards = $numberOfCurrentPlayerCards + $numberOfCardsToPickFromDeck; $translatedMessage = clienttranslate('${player_name} picks ${numberOfCards} card(s) from the top of the deck'); + self::notifyAllPlayers('cardsMovedFromDeckToAnotherPlayer', $translatedMessage, [ + 'receiverPlayerId' => $currentPlayer->getId(), + 'numberOfCards' => $numberOfCardsToPickFromDeck, + 'player_name' => $currentPlayer->getName(), + ]); foreach ($players as $player) { if ($player->getId() === $currentPlayer->getId()) { self::notifyPlayer($currentPlayer->getId(), 'cardsReceivedFromDeck', $translatedMessage, [ @@ -392,12 +397,6 @@ function playCards(array $playedCardIds, bool $cardsPlayedWithJersey) { 'numberOfCards' => $numberOfCardsToPickFromDeck, 'player_name' => $currentPlayer->getName(), ]); - } else { - self::notifyPlayer($player->getId(), 'cardsMovedFromDeckToAnotherPlayer', $translatedMessage, [ - 'receiverPlayerId' => $currentPlayer->getId(), - 'numberOfCards' => $numberOfCardsToPickFromDeck, - 'player_name' => $currentPlayer->getName(), - ]); } } } @@ -592,6 +591,13 @@ function selectPlayerToPickCards(int $selectedPlayerId) { // notify players $formattedPickedCards = $this->formatCardsForClient($pickedCards); $translatedMessage = clienttranslate('${player_name} picks ${numberOfCards} card(s) from ${player_name2} hand'); + self::notifyAllPlayers('cardsMovedBetweenTwoOtherPlayers', $translatedMessage, [ + 'receiverPlayerId' => $currentPlayer->getId(), + 'senderPlayerId' => $selectedPlayer->getId(), + 'numberOfCards' => $numberOfCardsToPick, + 'player_name' => $currentPlayer->getName(), + 'player_name2' => $selectedPlayer->getName(), + ]); foreach ($players as $player) { if ($player->getId() === $currentPlayer->getId()) { self::notifyPlayer($currentPlayer->getId(), 'cardsReceivedFromAnotherPlayer', $translatedMessage, [ @@ -609,14 +615,6 @@ function selectPlayerToPickCards(int $selectedPlayerId) { 'player_name' => $currentPlayer->getName(), 'player_name2' => $selectedPlayer->getName(), ]); - } else { - self::notifyPlayer($player->getId(), 'cardsMovedBetweenTwoOtherPlayers', $translatedMessage, [ - 'receiverPlayerId' => $currentPlayer->getId(), - 'senderPlayerId' => $selectedPlayer->getId(), - 'numberOfCards' => $numberOfCardsToPick, - 'player_name' => $currentPlayer->getName(), - 'player_name2' => $selectedPlayer->getName(), - ]); } } @@ -677,6 +675,13 @@ function selectCardsToGiveBack(array $selectedCardIds) { // notify players $formattedSelectedCards = $this->formatCardsForClient($selectedCards); $translatedMessage = clienttranslate('${player_name} gives back ${numberOfCards} card(s) to ${player_name2}'); + self::notifyAllPlayers('cardsMovedBetweenTwoOtherPlayers', $translatedMessage, [ + 'receiverPlayerId' => $receiverPlayer->getId(), + 'senderPlayerId' => $currentPlayer->getId(), + 'numberOfCards' => $numberOfCardsToGiveBack, + 'player_name2' => $receiverPlayer->getName(), + 'player_name' => $currentPlayer->getName(), + ]); foreach ($players as $player) { if ($player->getId() === $currentPlayer->getId()) { self::notifyPlayer($currentPlayer->getId(), 'cardsSentToAnotherPlayer', $translatedMessage, [ @@ -694,14 +699,6 @@ function selectCardsToGiveBack(array $selectedCardIds) { 'player_name2' => $receiverPlayer->getName(), 'player_name' => $currentPlayer->getName(), ]); - } else { - self::notifyPlayer($player->getId(), 'cardsMovedBetweenTwoOtherPlayers', $translatedMessage, [ - 'receiverPlayerId' => $receiverPlayer->getId(), - 'senderPlayerId' => $currentPlayer->getId(), - 'numberOfCards' => $numberOfCardsToGiveBack, - 'player_name2' => $receiverPlayer->getName(), - 'player_name' => $currentPlayer->getName(), - ]); } } @@ -791,6 +788,7 @@ function argPlayerGiveCardsBackAfterPicking() { function stStartRound() { $this->discardPlayedCards(); + $this->discardAttackRewardCards(); // take back all cards and shuffle them $this->deck->moveAllCardsInLocation(null, self::CARD_LOCATION_DECK); $this->deck->shuffle(self::CARD_LOCATION_DECK); @@ -1317,7 +1315,12 @@ private function discardPlayedCards(): void { self::setGameStateValue(self::GAME_STATE_PREVIOUS_PLAYED_CARDS_VALUE, 0); self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_VALUE, 0); self::setGameStateValue(self::GAME_STATE_LAST_PLAYED_CARDS_PLAYER_ID, 0); - self::notifyAllPlayers('cardsDiscarded', '', []); + self::notifyAllPlayers('playedCardsDiscarded', '', []); + } + + private function discardAttackRewardCards(): void { + $this->deck->moveAllCardsInLocation(self::CARD_LOCATION_ATTACK_REWARD, self::CARD_LOCATION_DISCARD); + self::notifyAllPlayers('attackRewardCardsDiscarded', '', []); } private function updatePlayerRoundsRanking(VelonimoPlayer $player): void { diff --git a/velonimo.js b/velonimo.js index 6fed81e..cabaa54 100644 --- a/velonimo.js +++ b/velonimo.js @@ -184,7 +184,6 @@ const PLAYERS_PLACES_BY_NUMBER_OF_PLAYERS = { }, }; -// @TODO: support "spectators" define([ 'dojo','dojo/_base/declare', 'ebg/core/gamegui', @@ -271,7 +270,6 @@ function (dojo, declare) { this.setupAttackRewardCards(gamedatas.attackRewardCards); } - // @TODO: support spectators (do not show "my hand" in this case) // Init playerHand "ebg.stock" component this.playerHand = new ebg.stock(); this.playerHand.create(this, $(DOM_ID_PLAYER_HAND), CARD_WIDTH, CARD_HEIGHT); @@ -622,10 +620,7 @@ function (dojo, declare) { } }, setupGiveCardsBackAfterPickingActionButton: function () { - if (this.playerHand.isSelected(CARD_ID_JERSEY)) { - this.playerHand.unselectItem(CARD_ID_JERSEY); - } - this.displayCardsAsNonSelectable(this.addJerseyToCards([])); + this.refreshPlayerHandSelectableCards(); const selectedCards = this.getSelectedPlayerCards(); if (!$(DOM_ID_ACTION_BUTTON_GIVE_CARDS)) { @@ -1201,23 +1196,33 @@ function (dojo, declare) { } }); - // display non-selectable cards as non-selectable - this.displayCardsAsNonSelectable( - this.getSelectedPlayerCards() - .reduce(this.getPlayerCardsThatCannotBePlayedWithCardsReducer(playerCards), []) - ); + this.refreshPlayerHandSelectableCards(); }, /** * @param {number} cardId */ onPlayerCardUnselected: function (cardId) { + this.refreshPlayerHandSelectableCards(); + }, + refreshPlayerHandSelectableCards: function () { const playerCards = this.getAllPlayerCards(); + const selectedCards = this.getSelectedPlayerCards(); - // display non-selectable cards as non-selectable - this.displayCardsAsNonSelectable( - this.getSelectedPlayerCards() - .reduce(this.getPlayerCardsThatCannotBePlayedWithCardsReducer(playerCards), []) - ); + if ( + this.isCurrentPlayerActive() + && this.currentState === 'playerGiveCardsBackAfterPicking' + ) { + if (this.playerHand.isSelected(CARD_ID_JERSEY)) { + this.playerHand.unselectItem(CARD_ID_JERSEY); + } + this.displayCardsAsNonSelectable(this.addJerseyToCards([])); + } else if (selectedCards.length === 0) { + this.displayCardsAsNonSelectable([]); + } else { + this.displayCardsAsNonSelectable( + selectedCards.reduce(this.getPlayerCardsThatCannotBePlayedWithCardsReducer(playerCards), []) + ); + } }, /** * @param {Object[]} cards @@ -1233,7 +1238,7 @@ function (dojo, declare) { }, unselectAllCards: function () { this.playerHand.unselectAll(); - this.displayCardsAsNonSelectable([]); + this.refreshPlayerHandSelectableCards(); }, /** * @param {Object[]} cards @@ -1311,7 +1316,7 @@ function (dojo, declare) { const backgroundPositionX = this.getAbsoluteCardBackgroundPositionXFromCardPosition(position); const backgroundPositionY = this.getAbsoluteCardBackgroundPositionYFromCardPosition(position); animations[i] = this.slideTemporaryObject( - `
`, + `
`, domId, domId, `player-table-${this.player_id}-hand`, @@ -1320,6 +1325,7 @@ function (dojo, declare) { ); dojo.connect(animations[i], 'onEnd', () => { this.playerHand.addToStockWithId(position, cards[i].id); + this.refreshPlayerHandSelectableCards(); }); animations[i].play(); } @@ -1332,7 +1338,7 @@ function (dojo, declare) { moveHiddenTemporaryCardsFromDomIdToDomId: function (fromDomId, toDomId, numberOfCards) { for (let i = 0; i < numberOfCards; i++) { this.slideTemporaryObject( - `
`, + `
`, fromDomId, fromDomId, toDomId, @@ -1427,6 +1433,7 @@ function (dojo, declare) { dojo.connect(animation, 'onEnd', () => { if (receiverPlayerId === this.player_id) { this.addCardsToPlayerHand(cards); + this.refreshPlayerHandSelectableCards(); } this.fadeOutAndDestroy(rewardCardDomId); }); @@ -1467,6 +1474,7 @@ function (dojo, declare) { this.placeOnObject(`cards-stack-${topOfStackCardId}`, `${DOM_ID_PLAYER_HAND}_item_${topOfStackCardId}`); cards.forEach((card) => { this.playerHand.removeFromStockById(card.id); + this.refreshPlayerHandSelectableCards(); }); } @@ -1513,7 +1521,7 @@ function (dojo, declare) { const backgroundPositionY = this.getAbsoluteCardBackgroundPositionYFromCardPosition(position); const animationStartDomId = $(`${DOM_ID_PLAYER_HAND}_item_${sortedCards[i].id}`) ? `${DOM_ID_PLAYER_HAND}_item_${sortedCards[i].id}` : `player-table-${this.player_id}-hand`; animations[i] = this.slideTemporaryObject( - `
`, + `
`, `player-table-${this.player_id}-hand`, animationStartDomId, `player-table-${receiverId}-hand`, @@ -1523,11 +1531,13 @@ function (dojo, declare) { dojo.connect(animations[i], 'onEnd', () => { if (sortedCards[i + 1]) { this.playerHand.removeFromStockById(sortedCards[i + 1].id); + this.refreshPlayerHandSelectableCards(); } }); animations[i].play(); } this.playerHand.removeFromStockById(sortedCards[0].id); + this.refreshPlayerHandSelectableCards(); }, /** * @param {number} senderId @@ -1589,9 +1599,10 @@ function (dojo, declare) { } this.playerHand.removeFromStockById(CARD_ID_JERSEY); + this.refreshPlayerHandSelectableCards(); }, movePlayedCardsToPreviousPlayedCards: function () { - dojo.query(`.${DOM_CLASS_CARDS_STACK_PREVIOUS_PLAYED}`).forEach(dojo.destroy); + dojo.query(`.${DOM_CLASS_CARDS_STACK_PREVIOUS_PLAYED}`).forEach(this.fadeOutAndDestroy); dojo.query(`#${DOM_ID_LAST_PLAYED_CARDS} .${DOM_CLASS_CARDS_STACK}`).forEach((elementDomId) => { dojo.addClass(elementDomId, DOM_CLASS_CARDS_STACK_PREVIOUS_PLAYED); const animation = this.slideToObject(elementDomId, DOM_ID_PREVIOUS_LAST_PLAYED_CARDS); @@ -1599,9 +1610,12 @@ function (dojo, declare) { animation.play(); }); }, + discardAttackRewardCards: function () { + dojo.query(`#${DOM_ID_ATTACK_REWARD_CARD} .${DOM_CLASS_CARDS_STACK}`).forEach(this.fadeOutAndDestroy); + }, discardPlayedCards: function () { this.playedCardsValue = 0; - dojo.query(`#${DOM_ID_PLAYED_CARDS_WRAPPER} .${DOM_CLASS_CARDS_STACK}`).forEach(dojo.destroy); + dojo.query(`#${DOM_ID_PLAYED_CARDS_WRAPPER} .${DOM_CLASS_CARDS_STACK}`).forEach(this.fadeOutAndDestroy); }, discardPlayerSpeechBubbles: function () { dojo.query(`.${DOM_CLASS_PLAYER_SPEECH_BUBBLE_SHOW}`).forEach((elementDomId) => { @@ -1719,23 +1733,32 @@ function (dojo, declare) { ['cardsDealt', 1], ['roundStarted', 1], ['cardsPlayed', 1000], - ['cardsDiscarded', 1], + ['playedCardsDiscarded', 1], ['cardsReceivedFromAnotherPlayer', 1000], ['cardsSentToAnotherPlayer', 1000], - ['cardsMovedBetweenTwoOtherPlayers', 1000], + ['cardsMovedBetweenTwoOtherPlayers', 1000, (notif) => (notif.args.receiverPlayerId === this.player_id || notif.args.senderPlayerId === this.player_id)], ['roundEnded', 1], - /* START 2P */ + // /!\ 2P mode only + ['attackRewardCardsDiscarded', 1], + // /!\ 2P mode only ['attackRewardCardsMovedToPlayer', 1000], + // /!\ 2P mode only ['attackRewardCardsRevealed', 1000], + // /!\ 2P mode only ['cardsReceivedFromDeck', 1000], - ['cardsMovedFromDeckToAnotherPlayer', 1000], - /* END 2P */ + // /!\ 2P mode only + ['cardsMovedFromDeckToAnotherPlayer', 1000, (notif) => notif.args.receiverPlayerId === this.player_id], ].forEach((notif) => { const name = notif[0]; const lockDurationInMs = notif[1]; + const ignoreNotifIfTrue = notif[2]; dojo.subscribe(name, this, `notif_${name}`); this.notifqueue.setSynchronous(name, lockDurationInMs); + + if (ignoreNotifIfTrue) { + this.notifqueue.setIgnoreNotificationCheck(name, ignoreNotifIfTrue); + } }); }, notif_cardsDealt: function (data) { @@ -1747,6 +1770,7 @@ function (dojo, declare) { ) { this.addJerseyToPlayerHand(); } + this.refreshPlayerHandSelectableCards(); }, notif_roundStarted: function (data) { this.currentRound = data.args.currentRound; @@ -1777,7 +1801,7 @@ function (dojo, declare) { this.useJerseyForCurrentRound(); } }, - notif_cardsDiscarded: function (data) { + notif_playedCardsDiscarded: function (data) { this.discardPlayerSpeechBubbles(); this.discardPlayedCards(); }, @@ -1813,6 +1837,12 @@ function (dojo, declare) { this.restoreJerseyForCurrentRound(); this.moveJerseyToCurrentWinner(currentJerseyWearerId); }, + /** + * /!\ 2P mode only + */ + notif_attackRewardCardsDiscarded: function (data) { + this.discardAttackRewardCards(); + }, /** * /!\ 2P mode only */