mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
Fix #6659: Correct logging for bottom-of-library card moves (#6764)
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Some checks failed
Build Desktop / Configure (push) Has been cancelled
Build Docker Image / amd64 & arm64 (push) Has been cancelled
Build Desktop / Debian 11 (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Fedora 42 (push) Has been cancelled
Build Desktop / Servatrice_Debian 11 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 22.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 15 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
* Fix #6659: Correct logging for bottom-of-library card moves Cause: - This issue happens due to logic of moving the card from the top of the deck being reused when moving from the bottom of the deck, in a way that makes it impossible to check if the card came from the bottom. Resolution: - Updated the logging logic in the client for card moves. - Added a gRPC parameter ('is_from_bottom') for card moves. - Updates the server logic to reverse the order of the card move if the 'is_from_bottom' parameter is true. - Added a test to show the expected behaviour of the fix. NOTE: While the changes in this patch seem big, this is due to changing the loop in the moveCard function to a helper function, in order to make the bug fix change. The only change to the loop was to pass a variable attribution to the moveCard function because it was redundant to be in the loop. * chore: run format on test * refactor: new way to check if a move is from the bottom of the deck * refactor: change isFromBottom check to static function * update comments Co-authored-by: ebbit1q <ebbit1q@gmail.com> --------- Co-authored-by: ebbit1q <ebbit1q@gmail.com>
This commit is contained in:
parent
9226bc9ddd
commit
a20f3c0fb4
7 changed files with 380 additions and 157 deletions
|
|
@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
|
||||||
fromStr = tr(" from the top of their library");
|
fromStr = tr(" from the top of their library");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (position >= zone->getCards().size() - 1) {
|
} else if (position == zone->getCards().size()) {
|
||||||
if (cardName.isEmpty()) {
|
if (cardName.isEmpty()) {
|
||||||
if (ownerChange) {
|
if (ownerChange) {
|
||||||
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
|
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
#include <libcockatrice/rng/rng_abstract.h>
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
|
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
|
||||||
int _playerId,
|
int _playerId,
|
||||||
|
|
@ -228,6 +229,37 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determines whether a set of moved cards is from the bottom of the deck
|
||||||
|
*/
|
||||||
|
static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set<MoveCardStruct> &cardsToMove)
|
||||||
|
{
|
||||||
|
if (!startZone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startZone->getName() != ZoneNames::DECK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int movedCount = static_cast<int>(cardsToMove.size());
|
||||||
|
int tailStart = startZone->getCards().size() - movedCount;
|
||||||
|
if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the move is a contiguous block at the end of the deck, fail fast when not
|
||||||
|
int expectedPosition = tailStart;
|
||||||
|
for (const auto &card : cardsToMove) {
|
||||||
|
if (card.position != expectedPosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++expectedPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
||||||
Server_CardZone *startzone,
|
Server_CardZone *startzone,
|
||||||
const QList<const CardToMove *> &_cards,
|
const QList<const CardToMove *> &_cards,
|
||||||
|
|
@ -244,8 +276,11 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetzone->hasCoords() && (xCoord <= -1)) {
|
if (!targetzone->hasCoords()) {
|
||||||
xCoord = targetzone->getCards().size();
|
yCoord = 0;
|
||||||
|
if (xCoord <= -1) {
|
||||||
|
xCoord = targetzone->getCards().size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<MoveCardStruct> cardsToMove;
|
std::set<MoveCardStruct> cardsToMove;
|
||||||
|
|
@ -285,164 +320,21 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
||||||
bool revealTopStart = false;
|
bool revealTopStart = false;
|
||||||
bool revealTopTarget = false;
|
bool revealTopTarget = false;
|
||||||
|
|
||||||
for (auto cardStruct : cardsToMove) {
|
bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove);
|
||||||
Server_Card *card = cardStruct.card;
|
|
||||||
int originalPosition = cardStruct.position;
|
|
||||||
|
|
||||||
bool sourceBeingLookedAt;
|
if (isFromBottom) {
|
||||||
int position = startzone->removeCard(card, sourceBeingLookedAt);
|
std::ranges::reverse_view reversedCardsToMove{cardsToMove};
|
||||||
|
for (auto card : reversedCardsToMove) {
|
||||||
// Attachment relationships can be retained when moving a card onto the opponent's table
|
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
|
||||||
if (startzone->getName() != targetzone->getName()) {
|
isReversed, undoingDraw);
|
||||||
// Delete all attachment relationships
|
|
||||||
if (card->getParentCard()) {
|
|
||||||
card->setParentCard(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a copy of the list because the original one gets modified during the loop
|
|
||||||
QList<Server_Card *> attachedCards = card->getAttachedCards();
|
|
||||||
for (auto &attachedCard : attachedCards) {
|
|
||||||
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (startzone != targetzone) {
|
for (auto card : cardsToMove) {
|
||||||
// Delete all arrows from and to the card
|
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
|
||||||
for (auto *player : game->getPlayers().values()) {
|
isReversed, undoingDraw);
|
||||||
QList<int> arrowsToDelete;
|
|
||||||
for (Server_Arrow *arrow : player->getArrows()) {
|
|
||||||
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
|
|
||||||
arrowsToDelete.append(arrow->getId());
|
|
||||||
}
|
|
||||||
for (int j : arrowsToDelete) {
|
|
||||||
player->deleteArrow(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldDestroyOnMove(card, startzone, targetzone)) {
|
|
||||||
Event_DestroyCard event;
|
|
||||||
event.set_zone_name(startzone->getName().toStdString());
|
|
||||||
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
|
|
||||||
ges.enqueueGameEvent(event, playerId);
|
|
||||||
|
|
||||||
if (Server_Card *stashedCard = card->takeStashedCard()) {
|
|
||||||
stashedCard->setId(newCardId());
|
|
||||||
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()),
|
|
||||||
playerId);
|
|
||||||
card->deleteLater();
|
|
||||||
card = stashedCard;
|
|
||||||
} else {
|
|
||||||
card->deleteLater();
|
|
||||||
card = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (card) {
|
|
||||||
++xIndex;
|
|
||||||
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
|
|
||||||
|
|
||||||
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
|
|
||||||
|
|
||||||
if (targetzone->hasCoords()) {
|
|
||||||
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
|
|
||||||
} else {
|
|
||||||
yCoord = 0;
|
|
||||||
card->resetState(targetzone->getName() == ZoneNames::STACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetzone->insertCard(card, newX, yCoord);
|
|
||||||
int targetLookedCards = targetzone->getCardsBeingLookedAt();
|
|
||||||
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
|
|
||||||
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
|
|
||||||
if (sourceKnownToPlayer) {
|
|
||||||
targetLookedCards += 1;
|
|
||||||
} else {
|
|
||||||
targetLookedCards = newX;
|
|
||||||
}
|
|
||||||
targetzone->setCardsBeingLookedAt(targetLookedCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
|
|
||||||
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
|
|
||||||
|
|
||||||
int oldCardId = card->getId();
|
|
||||||
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
|
|
||||||
card->setId(targetzone->getPlayer()->newCardId());
|
|
||||||
}
|
|
||||||
card->setFaceDown(faceDown);
|
|
||||||
|
|
||||||
Event_MoveCard eventOthers;
|
|
||||||
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
|
|
||||||
eventOthers.set_start_zone(startzone->getName().toStdString());
|
|
||||||
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
|
|
||||||
if (startzone != targetzone) {
|
|
||||||
eventOthers.set_target_zone(targetzone->getName().toStdString());
|
|
||||||
}
|
|
||||||
eventOthers.set_y(yCoord);
|
|
||||||
eventOthers.set_face_down(faceDown);
|
|
||||||
|
|
||||||
Event_MoveCard eventPrivate(eventOthers);
|
|
||||||
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
|
|
||||||
startzone->getType() != ServerInfo_Zone::HiddenZone) {
|
|
||||||
eventPrivate.set_card_id(oldCardId);
|
|
||||||
eventPrivate.set_new_card_id(card->getId());
|
|
||||||
} else {
|
|
||||||
eventPrivate.set_card_id(-1);
|
|
||||||
eventPrivate.set_new_card_id(-1);
|
|
||||||
}
|
|
||||||
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
|
|
||||||
QString privateCardName = card->getName();
|
|
||||||
eventPrivate.set_card_name(privateCardName.toStdString());
|
|
||||||
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
|
|
||||||
}
|
|
||||||
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
|
|
||||||
eventPrivate.set_position(position);
|
|
||||||
} else {
|
|
||||||
eventPrivate.set_position(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
eventPrivate.set_x(newX);
|
|
||||||
|
|
||||||
if (
|
|
||||||
// cards from public zones have their id known, their previous position is already known, the event does
|
|
||||||
// not accomodate for previous locations in zones with coordinates (which are always public)
|
|
||||||
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
|
|
||||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
|
||||||
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
|
||||||
eventOthers.set_position(position);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
|
||||||
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
|
||||||
eventOthers.set_x(newX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
|
|
||||||
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
|
|
||||||
eventOthers.set_card_id(oldCardId);
|
|
||||||
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
|
|
||||||
QString publicCardName = card->getName();
|
|
||||||
eventOthers.set_card_name(publicCardName.toStdString());
|
|
||||||
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
|
|
||||||
}
|
|
||||||
eventOthers.set_new_card_id(card->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
|
||||||
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
|
|
||||||
|
|
||||||
if (originalPosition == 0) {
|
|
||||||
revealTopStart = true;
|
|
||||||
}
|
|
||||||
if (newX == 0) {
|
|
||||||
revealTopTarget = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle side effects for this card
|
|
||||||
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revealTopStart) {
|
if (revealTopStart) {
|
||||||
revealTopCardIfNeeded(startzone, ges);
|
revealTopCardIfNeeded(startzone, ges);
|
||||||
}
|
}
|
||||||
|
|
@ -462,6 +354,174 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges,
|
||||||
|
Server_CardZone *startzone,
|
||||||
|
Server_CardZone *targetzone,
|
||||||
|
MoveCardStruct cardStruct,
|
||||||
|
int xCoord,
|
||||||
|
int yCoord,
|
||||||
|
int &xIndex,
|
||||||
|
bool &revealTopStart,
|
||||||
|
bool &revealTopTarget,
|
||||||
|
bool isReversed,
|
||||||
|
bool undoingDraw)
|
||||||
|
{
|
||||||
|
Server_Card *card = cardStruct.card;
|
||||||
|
int originalPosition = cardStruct.position;
|
||||||
|
|
||||||
|
bool sourceBeingLookedAt;
|
||||||
|
int position = startzone->removeCard(card, sourceBeingLookedAt);
|
||||||
|
|
||||||
|
// Attachment relationships can be retained when moving a card onto the opponent's table
|
||||||
|
if (startzone->getName() != targetzone->getName()) {
|
||||||
|
// Delete all attachment relationships
|
||||||
|
if (card->getParentCard()) {
|
||||||
|
card->setParentCard(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy of the list because the original one gets modified during the loop
|
||||||
|
QList<Server_Card *> attachedCards = card->getAttachedCards();
|
||||||
|
for (auto &attachedCard : attachedCards) {
|
||||||
|
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startzone != targetzone) {
|
||||||
|
// Delete all arrows from and to the card
|
||||||
|
for (auto *player : game->getPlayers().values()) {
|
||||||
|
QList<int> arrowsToDelete;
|
||||||
|
for (Server_Arrow *arrow : player->getArrows()) {
|
||||||
|
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
|
||||||
|
arrowsToDelete.append(arrow->getId());
|
||||||
|
}
|
||||||
|
for (int j : arrowsToDelete) {
|
||||||
|
player->deleteArrow(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDestroyOnMove(card, startzone, targetzone)) {
|
||||||
|
Event_DestroyCard event;
|
||||||
|
event.set_zone_name(startzone->getName().toStdString());
|
||||||
|
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
|
||||||
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
|
if (Server_Card *stashedCard = card->takeStashedCard()) {
|
||||||
|
stashedCard->setId(newCardId());
|
||||||
|
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), playerId);
|
||||||
|
card->deleteLater();
|
||||||
|
card = stashedCard;
|
||||||
|
} else {
|
||||||
|
card->deleteLater();
|
||||||
|
card = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card) {
|
||||||
|
++xIndex;
|
||||||
|
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
|
||||||
|
|
||||||
|
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
|
||||||
|
|
||||||
|
if (targetzone->hasCoords()) {
|
||||||
|
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
|
||||||
|
} else {
|
||||||
|
card->resetState(targetzone->getName() == ZoneNames::STACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetzone->insertCard(card, newX, yCoord);
|
||||||
|
int targetLookedCards = targetzone->getCardsBeingLookedAt();
|
||||||
|
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
|
||||||
|
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
|
||||||
|
if (sourceKnownToPlayer) {
|
||||||
|
targetLookedCards += 1;
|
||||||
|
} else {
|
||||||
|
targetLookedCards = newX;
|
||||||
|
}
|
||||||
|
targetzone->setCardsBeingLookedAt(targetLookedCards);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
|
||||||
|
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
|
||||||
|
|
||||||
|
int oldCardId = card->getId();
|
||||||
|
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
|
||||||
|
card->setId(targetzone->getPlayer()->newCardId());
|
||||||
|
}
|
||||||
|
card->setFaceDown(faceDown);
|
||||||
|
|
||||||
|
Event_MoveCard eventOthers;
|
||||||
|
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
|
||||||
|
eventOthers.set_start_zone(startzone->getName().toStdString());
|
||||||
|
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
|
||||||
|
if (startzone != targetzone) {
|
||||||
|
eventOthers.set_target_zone(targetzone->getName().toStdString());
|
||||||
|
}
|
||||||
|
eventOthers.set_y(yCoord);
|
||||||
|
eventOthers.set_face_down(faceDown);
|
||||||
|
|
||||||
|
Event_MoveCard eventPrivate(eventOthers);
|
||||||
|
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
|
||||||
|
startzone->getType() != ServerInfo_Zone::HiddenZone) {
|
||||||
|
eventPrivate.set_card_id(oldCardId);
|
||||||
|
eventPrivate.set_new_card_id(card->getId());
|
||||||
|
} else {
|
||||||
|
eventPrivate.set_card_id(-1);
|
||||||
|
eventPrivate.set_new_card_id(-1);
|
||||||
|
}
|
||||||
|
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
|
||||||
|
QString privateCardName = card->getName();
|
||||||
|
eventPrivate.set_card_name(privateCardName.toStdString());
|
||||||
|
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||||
|
}
|
||||||
|
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
|
||||||
|
eventPrivate.set_position(position);
|
||||||
|
} else {
|
||||||
|
eventPrivate.set_position(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventPrivate.set_x(newX);
|
||||||
|
|
||||||
|
if (
|
||||||
|
// cards from public zones have their id known, their previous position is already known, the event does
|
||||||
|
// not accomodate for previous locations in zones with coordinates (which are always public)
|
||||||
|
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
|
||||||
|
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||||
|
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||||
|
eventOthers.set_position(position);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||||
|
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||||
|
eventOthers.set_x(newX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
|
||||||
|
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
|
||||||
|
eventOthers.set_card_id(oldCardId);
|
||||||
|
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
|
||||||
|
QString publicCardName = card->getName();
|
||||||
|
eventOthers.set_card_name(publicCardName.toStdString());
|
||||||
|
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||||
|
}
|
||||||
|
eventOthers.set_new_card_id(card->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
||||||
|
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
|
||||||
|
|
||||||
|
if (originalPosition == 0) {
|
||||||
|
revealTopStart = true;
|
||||||
|
}
|
||||||
|
if (newX == 0) {
|
||||||
|
revealTopTarget = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle side effects for this card
|
||||||
|
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges,
|
void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges,
|
||||||
const MoveCardStruct &cardStruct,
|
const MoveCardStruct &cardStruct,
|
||||||
Server_CardZone *startzone,
|
Server_CardZone *startzone,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,19 @@ public:
|
||||||
bool fixFreeSpaces = true,
|
bool fixFreeSpaces = true,
|
||||||
bool undoingDraw = false,
|
bool undoingDraw = false,
|
||||||
bool isReversed = false);
|
bool isReversed = false);
|
||||||
|
|
||||||
|
void processMoveCard(GameEventStorage &ges,
|
||||||
|
Server_CardZone *startzone,
|
||||||
|
Server_CardZone *targetzone,
|
||||||
|
MoveCardStruct cardStruct,
|
||||||
|
int xCoord,
|
||||||
|
int yCoord,
|
||||||
|
int &xIndex,
|
||||||
|
bool &revealTopStart,
|
||||||
|
bool &revealTopTarget,
|
||||||
|
bool isReversed,
|
||||||
|
bool undoingDraw);
|
||||||
|
|
||||||
virtual void onCardBeingMoved(GameEventStorage &ges,
|
virtual void onCardBeingMoved(GameEventStorage &ges,
|
||||||
const MoveCardStruct &cardStruct,
|
const MoveCardStruct &cardStruct,
|
||||||
Server_CardZone *startzone,
|
Server_CardZone *startzone,
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,5 @@ target_link_libraries(
|
||||||
add_subdirectory(card_zone_algorithms)
|
add_subdirectory(card_zone_algorithms)
|
||||||
add_subdirectory(carddatabase)
|
add_subdirectory(carddatabase)
|
||||||
add_subdirectory(loading_from_clipboard)
|
add_subdirectory(loading_from_clipboard)
|
||||||
|
add_subdirectory(movecard_tests)
|
||||||
add_subdirectory(oracle)
|
add_subdirectory(oracle)
|
||||||
|
|
|
||||||
16
tests/movecard_tests/CMakeLists.txt
Executable file
16
tests/movecard_tests/CMakeLists.txt
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
add_executable(reverse_card_move_test reverse_card_move_test.cpp)
|
||||||
|
|
||||||
|
if(NOT GTEST_FOUND)
|
||||||
|
add_dependencies(reverse_card_move_test gtest)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
reverse_card_move_test
|
||||||
|
PRIVATE libcockatrice_network_server_remote
|
||||||
|
PRIVATE libcockatrice_rng
|
||||||
|
PRIVATE Threads::Threads
|
||||||
|
PRIVATE ${GTEST_BOTH_LIBRARIES}
|
||||||
|
PRIVATE ${TEST_QT_MODULES}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test)
|
||||||
91
tests/movecard_tests/reverse_card_move_test.cpp
Normal file
91
tests/movecard_tests/reverse_card_move_test.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#include "game/server_abstract_player.h"
|
||||||
|
#include "game/server_card.h"
|
||||||
|
#include "game/server_cardzone.h"
|
||||||
|
#include "game/server_game.h"
|
||||||
|
#include "server_response_containers.h"
|
||||||
|
#include "server_room.h"
|
||||||
|
#include "server_test_helpers.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
|
RNG_Abstract *rng = nullptr; // this needs to be defined due to other functions in server
|
||||||
|
|
||||||
|
TEST(ReverseCardMoveTest, MoveCardFromBottomTest)
|
||||||
|
{
|
||||||
|
ServerInfo_User user;
|
||||||
|
user.set_name("test-user");
|
||||||
|
|
||||||
|
// instantiate a fake server instance
|
||||||
|
FakeServer server;
|
||||||
|
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||||
|
Server_Game game(user, 1, "", "", 2, QList<int>(), false, false, false, false, false, false, 20, false, &room);
|
||||||
|
Server_AbstractPlayer player(&game, 1, user, false, nullptr);
|
||||||
|
Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone);
|
||||||
|
Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone);
|
||||||
|
|
||||||
|
// setup the deck with 20 useless cards
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
auto *cardUseless = new Server_Card({"Card Useless", "card-Useless"}, player.newCardId(), i, 0);
|
||||||
|
deckZone.insertCard(cardUseless, i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 4 cards to the end of it
|
||||||
|
auto *cardA = new Server_Card({"Card A", "card-a"}, player.newCardId(), 20, 0);
|
||||||
|
auto *cardB = new Server_Card({"Card B", "card-b"}, player.newCardId(), 21, 0);
|
||||||
|
auto *cardC = new Server_Card({"Card C", "card-c"}, player.newCardId(), 22, 0);
|
||||||
|
auto *cardD = new Server_Card({"Card D", "card-d"}, player.newCardId(), 23, 0);
|
||||||
|
|
||||||
|
deckZone.insertCard(cardA, 20, 0);
|
||||||
|
deckZone.insertCard(cardB, 21, 0);
|
||||||
|
deckZone.insertCard(cardC, 22, 0);
|
||||||
|
deckZone.insertCard(cardD, 23, 0);
|
||||||
|
|
||||||
|
// try to move them, with the expected client given order (n-3, n-2, n-1, n)
|
||||||
|
CardToMove moveA;
|
||||||
|
moveA.set_card_id(cardA->getId());
|
||||||
|
CardToMove moveB;
|
||||||
|
moveB.set_card_id(cardB->getId());
|
||||||
|
CardToMove moveC;
|
||||||
|
moveC.set_card_id(cardC->getId());
|
||||||
|
CardToMove moveD;
|
||||||
|
moveD.set_card_id(cardD->getId());
|
||||||
|
|
||||||
|
QList<const CardToMove *> cardsToMove = {&moveA, &moveB, &moveC, &moveD};
|
||||||
|
GameEventStorage ges;
|
||||||
|
|
||||||
|
const auto response = player.moveCard(ges, &deckZone, cardsToMove, &exileZone, 0, 0, false, false, false);
|
||||||
|
|
||||||
|
EXPECT_EQ(response, Response::RespOk);
|
||||||
|
|
||||||
|
int positionA;
|
||||||
|
int positionB;
|
||||||
|
int positionC;
|
||||||
|
int positionD;
|
||||||
|
// find the cards in the destination zone and check they are the right card
|
||||||
|
EXPECT_EQ(exileZone.getCard(cardA->getId(), &positionA), cardA);
|
||||||
|
EXPECT_EQ(exileZone.getCard(cardB->getId(), &positionB), cardB);
|
||||||
|
EXPECT_EQ(exileZone.getCard(cardC->getId(), &positionC), cardC);
|
||||||
|
EXPECT_EQ(exileZone.getCard(cardD->getId(), &positionD), cardD);
|
||||||
|
|
||||||
|
// check that they are at the expected index
|
||||||
|
EXPECT_EQ(cardA->getX(), 3);
|
||||||
|
EXPECT_EQ(cardB->getX(), 2);
|
||||||
|
EXPECT_EQ(cardC->getX(), 1);
|
||||||
|
EXPECT_EQ(cardD->getX(), 0);
|
||||||
|
|
||||||
|
// also check if the given positions are correct
|
||||||
|
EXPECT_EQ(positionA, 3);
|
||||||
|
EXPECT_EQ(positionB, 2);
|
||||||
|
EXPECT_EQ(positionC, 1);
|
||||||
|
EXPECT_EQ(positionD, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
42
tests/movecard_tests/server_test_helpers.h
Normal file
42
tests/movecard_tests/server_test_helpers.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include "server.h"
|
||||||
|
#include "server_database_interface.h"
|
||||||
|
|
||||||
|
class MockDatabaseInterface : public Server_DatabaseInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AuthenticationResult checkUserPassword(Server_ProtocolHandler *,
|
||||||
|
const QString &,
|
||||||
|
const QString &,
|
||||||
|
const QString &,
|
||||||
|
QString &,
|
||||||
|
int &,
|
||||||
|
bool) override
|
||||||
|
{
|
||||||
|
return NotLoggedIn;
|
||||||
|
}
|
||||||
|
ServerInfo_User getUserData(const QString &, bool) override
|
||||||
|
{
|
||||||
|
return ServerInfo_User();
|
||||||
|
}
|
||||||
|
int getNextGameId() override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int getNextReplayId() override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int getActiveUserCount(QString) override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeServer : public Server
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FakeServer()
|
||||||
|
{
|
||||||
|
setDatabaseInterface(new MockDatabaseInterface());
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue