From 0b6044551a1d4c7929f3c287e80a2259ef0719c6 Mon Sep 17 00:00:00 2001 From: Xawotihs Date: Thu, 13 Oct 2011 19:43:51 +0000 Subject: [PATCH] - Added new code for serialization/deserializaiton of full games including initial game and all the player actions. - Added an undo menu using this code (beware, it's still very very alpha). - Removed various warning - Cleaned up avatar loading - Added full random lists load/save including the deck shuffling (not sure if I could not replace that with seed load/save) - Moved momir and Co rules configuration out of GameStateDuel - Create a GameType type to avoid mixing int everywhere --- projects/mtg/include/GameApp.h | 18 +- projects/mtg/include/GameObserver.h | 23 +- projects/mtg/include/GameStateDuel.h | 5 +- projects/mtg/include/MTGCardInstance.h | 3 +- projects/mtg/include/MTGDefinitions.h | 17 ++ projects/mtg/include/MTGGameZones.h | 1 + projects/mtg/include/Player.h | 16 +- projects/mtg/include/Rules.h | 4 +- projects/mtg/include/TestSuiteAI.h | 2 +- projects/mtg/include/utils.h | 4 +- projects/mtg/src/AIPlayer.cpp | 6 +- projects/mtg/src/AIPlayerBaka.cpp | 19 +- projects/mtg/src/ActionStack.cpp | 3 + projects/mtg/src/AllAbilities.cpp | 1 + projects/mtg/src/CardGui.cpp | 2 - projects/mtg/src/Damage.cpp | 4 + projects/mtg/src/DeckManager.cpp | 9 +- projects/mtg/src/DuelLayers.cpp | 3 +- projects/mtg/src/GameObserver.cpp | 381 ++++++++++++++++++++++--- projects/mtg/src/GameStateDuel.cpp | 32 +-- projects/mtg/src/GuiCombat.cpp | 4 +- projects/mtg/src/GuiHand.cpp | 13 + projects/mtg/src/GuiStatic.cpp | 16 +- projects/mtg/src/MTGGameZones.cpp | 54 +++- projects/mtg/src/Player.cpp | 48 +++- projects/mtg/src/Rules.cpp | 8 +- projects/mtg/src/StoryFlow.cpp | 2 +- projects/mtg/src/utils.cpp | 53 +++- 28 files changed, 595 insertions(+), 156 deletions(-) diff --git a/projects/mtg/include/GameApp.h b/projects/mtg/include/GameApp.h index 90554e82d..a8159a94c 100644 --- a/projects/mtg/include/GameApp.h +++ b/projects/mtg/include/GameApp.h @@ -38,22 +38,6 @@ enum #endif //NETWORK_SUPPORT }; -enum -{ - GAME_TYPE_CLASSIC, - GAME_TYPE_MOMIR, - GAME_TYPE_RANDOM1, - GAME_TYPE_RANDOM2, - GAME_TYPE_STORY, - GAME_TYPE_DEMO, - GAME_TYPE_STONEHEWER, - GAME_TYPE_HERMIT, - -#ifdef NETWORK_SUPPORT - GAME_TYPE_SLAVE, -#endif //NETWORK_SUPPORT -}; - class MTGAllCards; class TransitionBase; @@ -73,7 +57,7 @@ private: GameState* mGameStates[GAME_STATE_MAX]; public: - int gameType; + GameType gameType; Rules * rules; CardEffect *effect; #ifdef NETWORK_SUPPORT diff --git a/projects/mtg/include/GameObserver.h b/projects/mtg/include/GameObserver.h index 4a93ded69..19355f305 100644 --- a/projects/mtg/include/GameObserver.h +++ b/projects/mtg/include/GameObserver.h @@ -26,10 +26,21 @@ class GameObserver{ protected: MTGCardInstance * cardWaitingForTargets; queue eventsQueue; + list actionsList; int untap(MTGCardInstance * card); bool WaitForExtraPayment(MTGCardInstance* card); void initialize(); + void cleanup(); + string startupGameSerialized; + bool parseLine(const string& s); + void logAction(const string& s) { + actionsList.push_back(s); + }; + bool processActions(bool undo); + friend ostream& operator<<(ostream&, GameObserver&); + bool load(const string& s, bool undo); + bool mLoading; public: int currentPlayerId; @@ -49,6 +60,7 @@ class GameObserver{ vector players; //created outside time_t startedAt; Rules * mRules; + GameType mGameType; TargetChooser * getCurrentTargetChooser(); void stackObjectClicked(Interruptible * action); @@ -74,10 +86,9 @@ class GameObserver{ void gameStateBasedEffects(); void enchantmentStatus(); void Affinity(); - void eventOccured(); void addObserver(MTGAbility * observer); bool removeObserver(ActionElement * observer); - void startGame(Rules * rules); + void startGame(GameType, Rules * rules); void untapPhase(); MTGCardInstance * isCardWaiting(){ return cardWaitingForTargets; } int isInPlay(MTGCardInstance * card); @@ -90,6 +101,14 @@ class GameObserver{ int receiveEvent(WEvent * event); bool connectRule; + + void logAction(Player* player, const string& s=""); + void logAction(int playerId, const string& s="") { + logAction(players[playerId], s); + }; + void logAction(MTGCardInstance* card, MTGGameZone* zone = NULL); + bool undo(); + bool isLoading(){ return mLoading; }; }; #endif diff --git a/projects/mtg/include/GameStateDuel.h b/projects/mtg/include/GameStateDuel.h index 78c9cb702..05d04dec8 100644 --- a/projects/mtg/include/GameStateDuel.h +++ b/projects/mtg/include/GameStateDuel.h @@ -78,9 +78,10 @@ public: MENUITEM_MAIN_MENU = -13, MENUITEM_EVIL_TWIN = kEvilTwinMenuID, MENUITEM_MULLIGAN = -15, + MENUITEM_UNDO = -16, #ifdef NETWORK_SUPPORT - MENUITEM_REMOTE_CLIENT = -16, - MENUITEM_REMOTE_SERVER = -17, + MENUITEM_REMOTE_CLIENT = -17, + MENUITEM_REMOTE_SERVER = -18, #endif MENUITEM_MORE_INFO = kInfoMenuID }; diff --git a/projects/mtg/include/MTGCardInstance.h b/projects/mtg/include/MTGCardInstance.h index a7e67fe69..613c1c90b 100644 --- a/projects/mtg/include/MTGCardInstance.h +++ b/projects/mtg/include/MTGCardInstance.h @@ -210,9 +210,8 @@ public: static MTGCardInstance ExtraRules[2]; + bool parseLine(const string& ss); }; -ostream& operator<<(ostream&, const MTGCardInstance&); - #endif diff --git a/projects/mtg/include/MTGDefinitions.h b/projects/mtg/include/MTGDefinitions.h index b3263480e..21bb97d6a 100644 --- a/projects/mtg/include/MTGDefinitions.h +++ b/projects/mtg/include/MTGDefinitions.h @@ -7,6 +7,23 @@ const float DEFAULT_TEXT_FONT_SCALE = 1.0f; #include using std::string; + +typedef enum +{ + GAME_TYPE_CLASSIC, + GAME_TYPE_MOMIR, + GAME_TYPE_RANDOM1, + GAME_TYPE_RANDOM2, + GAME_TYPE_STORY, + GAME_TYPE_DEMO, + GAME_TYPE_STONEHEWER, + GAME_TYPE_HERMIT, + +#ifdef NETWORK_SUPPORT + GAME_TYPE_SLAVE, +#endif //NETWORK_SUPPORT +} GameType; + class Constants { public: diff --git a/projects/mtg/include/MTGGameZones.h b/projects/mtg/include/MTGGameZones.h index c0f564566..e8b6846bd 100644 --- a/projects/mtg/include/MTGGameZones.h +++ b/projects/mtg/include/MTGGameZones.h @@ -83,6 +83,7 @@ class MTGGameZone { void debugPrint(); MTGCardInstance * removeCard(MTGCardInstance * card, int createCopy = 1); MTGCardInstance * hasCard(MTGCardInstance * card); + size_t getIndex(MTGCardInstance * card); void cleanupPhase(); void beforeBeginPhase(); diff --git a/projects/mtg/include/Player.h b/projects/mtg/include/Player.h index 8a3f2e6b5..d63b02c4d 100644 --- a/projects/mtg/include/Player.h +++ b/projects/mtg/include/Player.h @@ -15,6 +15,10 @@ class Player: public Damageable { protected: ManaPool * manaPool; + JTexture * mAvatarTex; + JQuadPtr mAvatar; + bool loadAvatar(string file, string resName = "playerAvatar"); + public: enum ENUM_PLAY_MODE @@ -25,8 +29,6 @@ public: }; string mAvatarName; - JTexture * mAvatarTex; - JQuadPtr mAvatar; int playMode; bool nomaxhandsize; MTGPlayerCards * game; @@ -35,7 +37,7 @@ public: string deckFileSmall; string deckName; string phaseRing; - int offerInterruptOnPhase; + int offerInterruptOnPhase; Player(GameObserver *observer, string deckFile, string deckFileSmall, MTGDeck * deck = NULL); virtual ~Player(); virtual void setObserver(GameObserver*g); @@ -89,22 +91,18 @@ public: { } - void loadAvatar(string file); - /** ** Returns the path to the stats file of currently selected deck. */ std::string GetCurrentDeckStatsFile(); bool parseLine(const string& s); + friend ostream& operator<<(ostream&, const Player&); }; class HumanPlayer: public Player { public: - HumanPlayer(GameObserver *observer, string deckFile, string deckFileSmall = "", MTGDeck * deck = NULL); - + HumanPlayer(GameObserver *observer, string deckFile, string deckFileSmall, MTGDeck * deck = NULL); }; -ostream& operator<<(ostream&, const Player&); - #endif diff --git a/projects/mtg/include/Rules.h b/projects/mtg/include/Rules.h index 099cc7af7..7fd66ae83 100644 --- a/projects/mtg/include/Rules.h +++ b/projects/mtg/include/Rules.h @@ -43,7 +43,7 @@ protected: Player * loadPlayerRandom(GameObserver* observer, int isAI, int mode); Player * initPlayer(GameObserver *observer, int playerId); MTGDeck * buildDeck(int playerId); - int strToGameMode(string s); + GameType strToGameMode(string s); bool postUpdateInitDone; public: enum @@ -57,7 +57,7 @@ public: string bg; string filename; - int gamemode; + GameType gamemode; bool hidden; string displayName; int unlockOption; diff --git a/projects/mtg/include/TestSuiteAI.h b/projects/mtg/include/TestSuiteAI.h index 1022b5331..f82567571 100644 --- a/projects/mtg/include/TestSuiteAI.h +++ b/projects/mtg/include/TestSuiteAI.h @@ -67,7 +67,7 @@ public: public: int startTime, endTime; - int gameType; + GameType gameType; unsigned int seed; int nbFailed, nbTests, nbAIFailed, nbAITests; TestSuite(const char * filename); diff --git a/projects/mtg/include/utils.h b/projects/mtg/include/utils.h index b18590329..639a79362 100644 --- a/projects/mtg/include/utils.h +++ b/projects/mtg/include/utils.h @@ -63,9 +63,11 @@ std::string wordWrap(const std::string& s, float width, int fontId); //basic hash function unsigned long hash_djb2(const char *str); -int loadRandValues(string s); +void loadRandValues(string s); +ostream& saveRandValues(ostream& out); int filesize(const char * filename); int WRand(); +ptrdiff_t MRand (ptrdiff_t i); #ifdef LINUX void dumpStack(); diff --git a/projects/mtg/src/AIPlayer.cpp b/projects/mtg/src/AIPlayer.cpp index dcf38f1c0..bb1af7128 100644 --- a/projects/mtg/src/AIPlayer.cpp +++ b/projects/mtg/src/AIPlayer.cpp @@ -97,7 +97,7 @@ int AIAction::clickMultiAct(vector& actionTargets) actionTargets.erase(actionTargets.begin() + f); } } - std::random_shuffle(actionTargets.begin(), actionTargets.end()); + std::random_shuffle(actionTargets.begin(), actionTargets.end(), MRand); //shuffle to make it less predictable, otherwise ai will always seem to target from right to left. making it very obvious. for(int k = 0;k < int(actionTargets.size());k++) { @@ -172,7 +172,7 @@ int AIPlayer::clickMultiTarget(TargetChooser * tc, vector& potentia potentialTargets.erase(potentialTargets.begin() + f); } } - std::random_shuffle(potentialTargets.begin(), potentialTargets.end()); + std::random_shuffle(potentialTargets.begin(), potentialTargets.end(), MRand); if(potentialTargets.size()) clickstream.push(NEW AIAction(this, NULL,tc->source,potentialTargets)); while(clickstream.size()) @@ -257,7 +257,7 @@ AIPlayer * AIPlayerFactory::createAIPlayer(GameObserver *observer, MTGAllCards * { bool isOpponentAI = opponent->isAI() == 1; DeckMetaData *meta = DeckManager::GetInstance()->getDeckMetaDataByFilename( opponent->deckFile, isOpponentAI); - if ( meta->getVictoryPercentage() >= 65) + if ( meta && meta->getVictoryPercentage() >= 65) deckSetting = HARD; } diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index 52dd247a9..08ebf0485 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -1410,7 +1410,7 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard if(tc->belongsToAbility.size()) { AbilityFactory af(observer); - MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,NULL,NULL,tc->source); + MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,0,NULL,tc->source); cardEffect = af.abilityEfficiency(withoutGuessing,this,MODE_TARGET,tc,NULL); delete withoutGuessing; } @@ -1458,7 +1458,7 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard if(tc->maxtargets != 1 && tc->belongsToAbility.size()) { AbilityFactory af(observer); - MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,NULL,NULL,tc->source); + MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,0,NULL,tc->source); OrderedAIAction * effCheck = NEW OrderedAIAction(this, withoutGuessing,(MTGCardInstance*)tc->source,card); if(effCheck->getEfficiency()) { @@ -2200,17 +2200,16 @@ AIPlayerBaka::AIPlayerBaka(GameObserver *observer, string file, string fileSmall } - mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); - if (!mAvatarTex) + if(avatarFile != "") { - avatarFile = "baka.jpg"; - mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); + if(!loadAvatar(avatarFile, "bakaAvatar")) + { + avatarFile = "baka.jpg"; + loadAvatar(avatarFile, "bakaAvatar"); + } + mAvatarName = avatarFile; } - if (mAvatarTex) - mAvatar = WResourceManager::Instance()->RetrieveQuad(avatarFile, 0, 0, 35, 50, "bakaAvatar", RETRIEVE_NORMAL, - TEXTURE_SUB_AVATAR); - if (fileSmall == "ai_baka_eviltwin") mAvatar->SetHFlip(true); diff --git a/projects/mtg/src/ActionStack.cpp b/projects/mtg/src/ActionStack.cpp index c11a8879a..c9fce6087 100644 --- a/projects/mtg/src/ActionStack.cpp +++ b/projects/mtg/src/ActionStack.cpp @@ -589,6 +589,7 @@ int ActionStack::setIsInterrupting(Player * player) int playerId = (player == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = -1; observer->isInterrupting = player; + observer->logAction(player, "yes"); return 1; } @@ -960,6 +961,7 @@ void ActionStack::cancelInterruptOffer(int cancelMode) askIfWishesToInterrupt = NULL; observer->isInterrupting = NULL; timer = -1; + observer->logAction(playerId, "no"); } void ActionStack::endOfInterruption() @@ -967,6 +969,7 @@ void ActionStack::endOfInterruption() int playerId = (observer->isInterrupting == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = 0; observer->isInterrupting = NULL; + observer->logAction(playerId, "endinterruption"); } bool ActionStack::CheckUserInput(JButton key) diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index e14fb8c07..376a18f1b 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -2360,6 +2360,7 @@ int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) game->mLayers->stackLayer()->cancelInterruptOffer(); this->forceDestroy = 1; removeMenu = true; + game->logAction(source->controller(), "choice " + choice); return reactToTargetClick(object); } diff --git a/projects/mtg/src/CardGui.cpp b/projects/mtg/src/CardGui.cpp index 193e06594..07a97ae52 100644 --- a/projects/mtg/src/CardGui.cpp +++ b/projects/mtg/src/CardGui.cpp @@ -661,7 +661,6 @@ void CardGui::AlternateRender(MTGCard * card, const Pos& pos) formattedfield = FormattedData(formattedfield, "expansion", setlist[card->setId].c_str()); } - float w = font->GetStringWidth(formattedfield.c_str()) * kWidthScaleFactor; font->DrawString(formattedfield.c_str(), x + (Carditem->mPosX - BigWidth / 2) * pos.actZ, pos.actY + (Carditem->mPosY) * pos.actZ); } @@ -994,7 +993,6 @@ void CardGui::TinyCropRender(MTGCard * card, const Pos& pos, JQuad * quad) formattedfield = FormattedData(formattedfield, "expansion", setlist[card->setId].c_str()); } - float w = font->GetStringWidth(formattedfield.c_str()) * kWidthScaleFactor; font->DrawString(formattedfield.c_str(), x + (Carditem->mPosX - BigWidth / 2) * pos.actZ, pos.actY + (Carditem->mPosY) * pos.actZ); } diff --git a/projects/mtg/src/Damage.cpp b/projects/mtg/src/Damage.cpp index f0dc4885f..6cdbb9262 100644 --- a/projects/mtg/src/Damage.cpp +++ b/projects/mtg/src/Damage.cpp @@ -305,6 +305,10 @@ ostream& DamageStack::toString(ostream& out) const ostream& operator<<(ostream& out, const Damageable& p) { + out << "life=" << p.life << endl; + out << "poisoncount=" << p.poisonCount << endl; + out << "damagecount=" << p.damageCount << endl; + out << "preventable=" << p.preventable << endl; return out; } diff --git a/projects/mtg/src/DeckManager.cpp b/projects/mtg/src/DeckManager.cpp index ab27dd849..685310e91 100644 --- a/projects/mtg/src/DeckManager.cpp +++ b/projects/mtg/src/DeckManager.cpp @@ -182,8 +182,13 @@ DeckManager* DeckManager::GetInstance() // p2 is the opponent int DeckManager::getDifficultyRating(Player *statsPlayer, Player *player) { - DeckMetaData *meta = DeckManager::GetInstance()->getDeckMetaDataByFilename(player->deckFile, (player->isAI() == 1) ); - return meta->getDifficulty(); + if(player->deckFile != "") + { + DeckMetaData *meta = DeckManager::GetInstance()->getDeckMetaDataByFilename(player->deckFile, (player->isAI() == 1) ); + return meta->getDifficulty(); + } + else + return EASY; } DeckManager::~DeckManager() diff --git a/projects/mtg/src/DuelLayers.cpp b/projects/mtg/src/DuelLayers.cpp index 30a25daf9..9e55daef3 100644 --- a/projects/mtg/src/DuelLayers.cpp +++ b/projects/mtg/src/DuelLayers.cpp @@ -79,8 +79,9 @@ void DuelLayers::Update(float dt, Player * currentPlayer) for (int i = 0; i < nbitems; ++i) objects[i]->Update(dt); int isAI = currentPlayer->isAI(); - if (isAI) + if (isAI && !currentPlayer->getObserver()->isLoading()) currentPlayer->Act(dt); + CheckUserInput(isAI); } diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index 4364a1743..93eb59d12 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -10,14 +10,12 @@ #include #include "MTGGamePhase.h" #include "GuiPhaseBar.h" - -GameObserver::GameObserver() -{ - initialize(); -} +#include "AIPlayerBaka.h" +#include "MTGRules.h" void GameObserver::initialize() { + mGameType = GAME_TYPE_CLASSIC; currentPlayer = NULL; currentActionPlayer = NULL; isInterrupting = NULL; @@ -32,6 +30,61 @@ void GameObserver::initialize() combatStep = BLOCKERS; mRules = NULL; connectRule = false; + mLoading = false; +} + +void GameObserver::cleanup() +{ + SAFE_DELETE(targetChooser); + SAFE_DELETE(mLayers); + SAFE_DELETE(phaseRing); + SAFE_DELETE(replacementEffects); + for (size_t i = 0; i < players.size(); ++i) + { + SAFE_DELETE(players[i]); + } + players.clear(); + + currentPlayer = NULL; + currentActionPlayer = NULL; + isInterrupting = NULL; + currentPlayerId = 0; + currentGamePhase = -1; + targetChooser = NULL; + cardWaitingForTargets = NULL; + mExtraPayment = NULL; + gameOver = NULL; + phaseRing = NULL; + replacementEffects = NEW ReplacementEffects(); + combatStep = BLOCKERS; + connectRule = false; + actionsList.clear(); +} + +GameObserver::~GameObserver() +{ + LOG("==Destroying GameObserver=="); + SAFE_DELETE(targetChooser); + SAFE_DELETE(mLayers); + SAFE_DELETE(phaseRing); + SAFE_DELETE(replacementEffects); + for (size_t i = 0; i < players.size(); ++i) + { + SAFE_DELETE(players[i]); + } + players.clear(); + LOG("==GameObserver Destroyed=="); +} + +GameObserver::GameObserver() +{ + initialize(); +} + +GameObserver::GameObserver(vector _players) +{ + initialize(); + setPlayers(_players); } void GameObserver::setPlayers(vector _players) @@ -43,12 +96,6 @@ void GameObserver::setPlayers(vector _players) } } -GameObserver::GameObserver(vector _players) -{ - initialize(); - setPlayers(_players); -} - int GameObserver::getCurrentGamePhase() { return currentGamePhase; @@ -231,7 +278,7 @@ void GameObserver::userRequestNextGamePhase() { nextGamePhase(); } - + logAction(currentPlayer, "next"); } int GameObserver::forceShuffleLibraries() @@ -250,8 +297,9 @@ int GameObserver::forceShuffleLibraries() return result; } -void GameObserver::startGame(Rules * rules) +void GameObserver::startGame(GameType gtype, Rules * rules) { + mGameType = gtype; turn = 0; mRules = rules; if (rules) @@ -266,6 +314,11 @@ void GameObserver::startGame(Rules * rules) currentPlayer = players[0]; currentActionPlayer = currentPlayer; phaseRing = NEW PhaseRing(this); + + stringstream stream; + stream << *this; + startupGameSerialized = stream.str(); + if (rules) rules->initGame(this); @@ -311,6 +364,26 @@ void GameObserver::startGame(Rules * rules) } } } + + switch(gtype) { + case GAME_TYPE_MOMIR: + { + addObserver(NEW MTGMomirRule(this, -1, MTGCollection())); + break; + } + case GAME_TYPE_STONEHEWER: + { + addObserver(NEW MTGStoneHewerRule(this, -1,MTGCollection())); + break; + } + case GAME_TYPE_HERMIT: + { + addObserver(NEW MTGHermitRule(this, -1)); + break; + } + default: + break; + } } void GameObserver::addObserver(MTGAbility * observer) @@ -328,21 +401,6 @@ bool GameObserver::removeObserver(ActionElement * observer) } -GameObserver::~GameObserver() -{ - LOG("==Destroying GameObserver=="); - SAFE_DELETE(targetChooser); - SAFE_DELETE(mLayers); - SAFE_DELETE(phaseRing); - SAFE_DELETE(replacementEffects); - for (size_t i = 0; i < players.size(); ++i) - { - SAFE_DELETE(players[i]); - } - players.clear(); - LOG("==GameObserver Destroyed=="); -} - void GameObserver::Update(float dt) { Player * player = currentPlayer; @@ -890,8 +948,12 @@ bool GameObserver::WaitForExtraPayment(MTGCardInstance * card) int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) { Player * clickedPlayer = NULL; - if (!card) + if (!card) { clickedPlayer = ((Player *) object); + logAction(clickedPlayer); + } else { + logAction(card); + } if (targetChooser) { int result; @@ -928,8 +990,8 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) return 1; } - if (WaitForExtraPayment(card)) - return 1; + if (WaitForExtraPayment(card)) + return 1; int reaction = 0; @@ -959,18 +1021,18 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) } reaction = mLayers->actionLayer()->isReactingToClick(card); - if (reaction == -1) - return mLayers->actionLayer()->reactToClick(card); + if (reaction == -1) + return mLayers->actionLayer()->reactToClick(card); } else {//this handles abilities on a menu...not just when card is being played reaction = mLayers->actionLayer()->isReactingToTargetClick(object); - if (reaction == -1) - return mLayers->actionLayer()->reactToTargetClick(object); + if (reaction == -1) + return mLayers->actionLayer()->reactToTargetClick(object); } - if (!card) - return 0; + if (!card) + return 0; //Current player's hand if (currentPlayer->game->hand->hasCard(card) && currentGamePhase == Constants::MTG_PHASE_CLEANUP @@ -998,7 +1060,6 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) } return 0; - } int GameObserver::untap(MTGCardInstance * card) @@ -1118,3 +1179,247 @@ int GameObserver::targetListIsSet(MTGCardInstance * card) return 0; } + +ostream& operator<<(ostream& out, GameObserver& g) +{ + if(g.startupGameSerialized == "") + { + out << "[init]" << endl; + out << "player=" << g.currentPlayerId + 1 << endl; + if(g.currentGamePhase != -1) + out << "phase=" << g.phaseRing->phaseName(g.currentGamePhase) << endl; + out << "[player1]" << endl; + out << *(g.players[0]) << endl; + out << "[player2]" << endl; + out << *(g.players[1]) << endl; + return out; + } + else + { + out << "rvalues:"; + out << saveRandValues(out); + out << endl; + out << g.startupGameSerialized; + } + + out << "[do]" << endl; + list::iterator it; + + for(it = (g.actionsList.begin()); it != (g.actionsList.end()); it++) + { + out << (*it) << endl; + } + + out << "[end]" << endl; + return out; +} + +bool GameObserver::parseLine(const string& s) +{ + size_t limiter = s.find("="); + if (limiter == string::npos) limiter = s.find(":"); + string areaS; + if (limiter != string::npos) + { + areaS = s.substr(0, limiter); + if (areaS.compare("player") == 0) + { + currentPlayerId = atoi(s.substr(limiter + 1).c_str()) - 1; + return true; + } + else if (areaS.compare("phase") == 0) + { + currentGamePhase = PhaseRing::phaseStrToInt(s.substr(limiter + 1).c_str()); + return true; + } + } + return false; +} + +bool GameObserver::load(const string& ss, bool undo) +{ + int state = -1; + string s; + stringstream stream(ss); + string deckFile = players[0]->deckFile; + string deckFileSmall = players[0]->deckFileSmall; + + DebugTrace("Loading " + ss); + + cleanup(); + + while (std::getline(stream, s)) + { + if (!s.size()) continue; + if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); //Handle DOS files + if (!s.size()) continue; + if (s[0] == '#') continue; + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + if (s.find("seed ") == 0) + { +// seed = atoi(s.substr(5).c_str()); + continue; + } + if (s.find("rvalues:") == 0) + { + loadRandValues(s.substr(8).c_str()); + continue; + } + switch (state) + { + case -1: + if (s.compare("[init]") == 0) + state++; + break; + case 0: + if (s.compare("[player1]") == 0) + { + state++; + } + else + { + parseLine(s); + } + break; + case 1: + if (s.compare("[player2]") == 0) + { + state++; + } + else + { + if(!players[0]) + players.push_back(new HumanPlayer(this, deckFile, deckFileSmall)); + players[0]->parseLine(s); + } + break; + case 2: + if (s.compare("[do]") == 0) + { + state++; + } + else + { + if(!players[1]) { + AIPlayerFactory playerCreator; + players.push_back(playerCreator.createAIPlayer(this, MTGCollection(), players[0])); + } + players[1]->parseLine(s); + } + break; + case 3: + if (s.compare("[end]") == 0) + { + turn = 0; + mLayers = NEW DuelLayers(); + mLayers->init(this); + currentPlayer = players[currentPlayerId]; + phaseRing = NEW PhaseRing(this); + startedAt = time(0); + + mRules->initGame(this); + phaseRing->goToPhase(0, currentPlayer, false); + phaseRing->goToPhase(currentGamePhase, currentPlayer); + processActions(undo); + } + else + { + logAction(s); + } + break; + } + } + + return true; +} + +bool GameObserver::processActions(bool undo) +{ + bool result = false; + + list copyList = actionsList; + actionsList.clear(); + + mLoading = true; + list::iterator ite; + float counter = 0.0f; + + // To handle undo, we'll remove the last P1 action and all P2 actions after. + if(undo) { + while(copyList.back().find("p2") != string::npos) + copyList.pop_back(); + copyList.pop_back(); + } + for(ite = copyList.begin(); ite != copyList.end(); ite++) + { + string s = *ite; + Player* p = players[1]; + if (s.find("p1") != string::npos) + p = players[0]; + + MTGGameZone* zone = NULL; + if(s.find(p->game->hand->getName()) != string::npos) + zone = p->game->hand; + else if(s.find(p->game->battlefield->getName()) != string::npos) + zone = p->game->battlefield; + else if(s.find(p->game->graveyard->getName()) != string::npos) + zone = p->game->graveyard; + else if(s.find(p->game->library->getName()) != string::npos) + zone = p->game->library; + + if(zone) { + size_t begin = s.find("[")+1; + size_t size = s.find("]")-begin; + int index = atoi(s.substr(begin, size).c_str()); + cardClick(zone->cards[index], zone->cards[index]); + } else if (s.find("yes") != string::npos) { + mLayers->stackLayer()->setIsInterrupting(p); + } else if (s.find("no") != string::npos) { + mLayers->stackLayer()->cancelInterruptOffer(); + } else if (s.find("endinterruption") != string::npos) { + mLayers->stackLayer()->endOfInterruption(); + } else if (s.find("next") != string::npos) { + userRequestNextGamePhase(); + } else if (s.find("choice") != string::npos) { + int choice = atoi(s.substr(s.find("choice ") + 7).c_str()); + mLayers->actionLayer()->doReactTo(choice); + } else if (s == "p1" || s == "p2") { + cardClick(NULL, p); + } else { + assert(0); + } + + // let's fake an update + Update(counter); + counter += 1.000f; + // or two + Update(counter); + counter += 1.000f; + } + + mLoading = false; + return result; +} + +void GameObserver::logAction(Player* player, const string& s) { + if(player == players[0]) + logAction("p1." + s); + else + logAction("p2." + s); +} + +void GameObserver::logAction(MTGCardInstance* card, MTGGameZone* zone) { + stringstream stream; + if(zone == NULL) zone = card->currentZone; + stream << "p" << ((card->controller()==players[0])?"1.":"2.") + << zone->getName()<< "[" << zone->getIndex(card) << "]"; + logAction(stream.str()); +} + +bool GameObserver::undo() +{ + stringstream stream; + stream << *this; + DebugTrace(stream.str()); + return load(stream.str(), true); +} diff --git a/projects/mtg/src/GameStateDuel.cpp b/projects/mtg/src/GameStateDuel.cpp index 43a997c9d..8d6c23575 100644 --- a/projects/mtg/src/GameStateDuel.cpp +++ b/projects/mtg/src/GameStateDuel.cpp @@ -10,7 +10,6 @@ #include "DeckManager.h" #include "DeckMetaData.h" -#include "MTGRules.h" #include "Credits.h" #include "Translate.h" #include "Rules.h" @@ -267,11 +266,7 @@ void GameStateDuel::loadTestSuitePlayers() game->setPlayers(mPlayers); mParent->gameType = testSuite->gameType; - game->startGame(mParent->rules); - if (mParent->gameType == GAME_TYPE_MOMIR) - { - game->addObserver(NEW MTGMomirRule(game, -1, MTGCollection())); - } + game->startGame(mParent->gameType, mParent->rules); } #endif @@ -447,19 +442,7 @@ void GameStateDuel::Update(float dt) if (!game) { game = new GameObserver(mPlayers); - game->startGame(mParent->rules); - if (mParent->gameType == GAME_TYPE_MOMIR) - { - game->addObserver(NEW MTGMomirRule(game, -1, MTGCollection())); - } - if (mParent->gameType == GAME_TYPE_STONEHEWER) - { - game->addObserver(NEW MTGStoneHewerRule(game, -1,MTGCollection())); - } - if (mParent->gameType == GAME_TYPE_HERMIT) - { - game->addObserver(NEW MTGHermitRule(game, -1)); - } + game->startGame(mParent->gameType, mParent->rules); //start of in game music code musictrack = ""; @@ -539,6 +522,7 @@ void GameStateDuel::Update(float dt) } //END almosthumane - mulligan menu->Add(MENUITEM_MAIN_MENU, "Back to main menu"); + menu->Add(MENUITEM_UNDO, "Undo"); menu->Add(MENUITEM_CANCEL, "Cancel"); } setGamePhase(DUEL_STATE_MENU); @@ -922,10 +906,14 @@ void GameStateDuel::ButtonPressed(int controllerId, int controlId) menu->Close(); setGamePhase(DUEL_STATE_CANCEL); break; - - //END almosthumane - mulligan + case MENUITEM_UNDO: + { + game->undo(); + menu->Close(); + setGamePhase(DUEL_STATE_CANCEL); + break; + } } - } } diff --git a/projects/mtg/src/GuiCombat.cpp b/projects/mtg/src/GuiCombat.cpp index e93603c2b..2801443f3 100644 --- a/projects/mtg/src/GuiCombat.cpp +++ b/projects/mtg/src/GuiCombat.cpp @@ -389,8 +389,8 @@ void GuiCombat::Render() damage = 0; if (activeAtk->card->has(Constants::TRAMPLE)) { - observer->opponent()->mAvatar->SetHotSpot(18, 25); - enemy_avatar.Render(observer->opponent()->mAvatar.get()); + observer->opponent()->getIcon()->SetHotSpot(18, 25); + enemy_avatar.Render(observer->opponent()->getIcon().get()); WFont * mFont = WResourceManager::Instance()->GetWFont(Fonts::MAIN_FONT); mFont->SetColor(ARGB(255, 255, 64, 0)); { diff --git a/projects/mtg/src/GuiHand.cpp b/projects/mtg/src/GuiHand.cpp index 18236f05a..5129cb43a 100644 --- a/projects/mtg/src/GuiHand.cpp +++ b/projects/mtg/src/GuiHand.cpp @@ -64,6 +64,12 @@ bool GuiHand::isInHand(CardView* card) GuiHandOpponent::GuiHandOpponent(GameObserver* observer, MTGHand* hand) : GuiHand(observer, hand) { + vector::iterator ite; + for(ite = hand->cards.begin(); ite != hand->cards.end(); ite++) + { + WEventZoneChange event(*ite, NULL, hand); + receiveEventPlus(&event); + } } void GuiHandOpponent::Render() @@ -92,6 +98,13 @@ GuiHandSelf::GuiHandSelf(GameObserver* observer, MTGHand* hand) : backpos.x = SCREEN_WIDTH - 30 * 7 - 14; backpos.UpdateNow(); } + + vector::iterator ite; + for(ite = hand->cards.begin(); ite != hand->cards.end(); ite++) + { + WEventZoneChange event(*ite, NULL, hand); + receiveEventPlus(&event); + } } GuiHandSelf::~GuiHandSelf() diff --git a/projects/mtg/src/GuiStatic.cpp b/projects/mtg/src/GuiStatic.cpp index 9543fcc43..f448abc83 100644 --- a/projects/mtg/src/GuiStatic.cpp +++ b/projects/mtg/src/GuiStatic.cpp @@ -56,27 +56,27 @@ void GuiAvatar::Render() float x0 = actX; float y0 = actY; - if (player->mAvatar.get()) + if (player->getIcon().get()) { if (corner == BOTTOM_RIGHT) { - x0 -= player->mAvatar->mWidth * actZ; - y0 -= player->mAvatar->mHeight * actZ; + x0 -= player->getIcon()->mWidth * actZ; + y0 -= player->getIcon()->mHeight * actZ; } switch (corner) { case TOP_LEFT: - player->mAvatar->SetHotSpot(0, 0); + player->getIcon()->SetHotSpot(0, 0); break; case BOTTOM_RIGHT: - player->mAvatar->SetHotSpot(35, 50); + player->getIcon()->SetHotSpot(35, 50); break; } - player->mAvatar->SetColor(ARGB((int)actA, 255, avatarRed, avatarRed)); - r->RenderQuad(player->mAvatar.get(), actX, actY, actT, actZ, actZ); + player->getIcon()->SetColor(ARGB((int)actA, 255, avatarRed, avatarRed)); + r->RenderQuad(player->getIcon().get(), actX, actY, actT, actZ, actZ); if (mHasFocus) { - r->FillRect(x0, x0, player->mAvatar->mWidth * actZ, player->mAvatar->mHeight * actZ, ARGB(abs(128 - wave),255,255,255)); + r->FillRect(x0, x0, player->getIcon()->mWidth * actZ, player->getIcon()->mHeight * actZ, ARGB(abs(128 - wave),255,255,255)); } } diff --git a/projects/mtg/src/MTGGameZones.cpp b/projects/mtg/src/MTGGameZones.cpp index 3187132b0..8280cea4d 100644 --- a/projects/mtg/src/MTGGameZones.cpp +++ b/projects/mtg/src/MTGGameZones.cpp @@ -498,6 +498,18 @@ MTGCardInstance * MTGGameZone::hasCard(MTGCardInstance * card) } +size_t MTGGameZone::getIndex(MTGCardInstance * card) +{ + size_t i; + for(i = 0; i < cards.size(); i++) + { + if(cards[i] == card) + return i; + } + return -1; +} + + unsigned int MTGGameZone::countByType(const char * value) { int result = 0; @@ -669,7 +681,7 @@ void MTGGameZone::cleanupPhase() void MTGGameZone::shuffle() { - std::random_shuffle(cards.begin(), cards.end()); + std::random_shuffle(cards.begin(), cards.end(), MRand ); } void MTGGameZone::addCard(MTGCardInstance * card) @@ -999,16 +1011,15 @@ ostream& MTGInPlay::toString(ostream& out) const } ostream& operator<<(ostream& out, const MTGGameZone& z) { - return z.toString(out); -} - -ostream& operator<<(ostream& out, const MTGPlayerCards& z) -{ - out << z.library->nb_cards << " "; - for (int i = 0; i < z.library->nb_cards; i++) - out << z.library->cards[i]->getMTGId() << " "; - + for (int i = 0; i < z.nb_cards; i++) + { + out << z.cards[i]->getMTGId(); + if(i < z.nb_cards - 1) + out << ","; + } return out; + +// return z.toString(out); } bool MTGGameZone::parseLine(const string& ss) @@ -1022,6 +1033,7 @@ bool MTGGameZone::parseLine(const string& ss) } cards.clear(); cardsMap.clear(); + nb_cards = 0; while(s.size()) { @@ -1072,6 +1084,28 @@ bool MTGGameZone::parseLine(const string& ss) return result; } +ostream& operator<<(ostream& out, const MTGPlayerCards& z) +{ + if(z.library->cards.size()) { + out << "library="; + out << *(z.library) << endl; + } + if(z.battlefield->cards.size()) { + out << "inplay="; + out << *(z.battlefield) << endl; + } + if(z.graveyard->cards.size()) { + out << "graveyard="; + out << *(z.graveyard) << endl; + } + if(z.hand->cards.size()) { + out << "hand="; + out << *(z.hand) << endl; + } + + return out; +} + bool MTGPlayerCards::parseLine(const string& s) { size_t limiter = s.find("="); diff --git a/projects/mtg/src/Player.cpp b/projects/mtg/src/Player.cpp index 692a0dcdb..7f959d300 100644 --- a/projects/mtg/src/Player.cpp +++ b/projects/mtg/src/Player.cpp @@ -10,7 +10,7 @@ #endif Player::Player(GameObserver *observer, string file, string fileSmall, MTGDeck * deck) : - Damageable(observer, 20), mAvatarName("") + Damageable(observer, 20), mAvatarName(""), offerInterruptOnPhase(Constants::MTG_PHASE_DRAW) { if(deck == NULL && file != "testsuite" && file != "remote" && file != "") deck = NEW MTGDeck(file.c_str(), MTGCollection()); @@ -18,7 +18,7 @@ Player::Player(GameObserver *observer, string file, string fileSmall, MTGDeck * game = NULL; deckFile = file; deckFileSmall = fileSmall; - handsize = 0; + handsize = 0; manaPool = NEW ManaPool(this); nomaxhandsize = false; poisonCount = 0; @@ -34,6 +34,11 @@ Player::Player(GameObserver *observer, string file, string fileSmall, MTGDeck * game->setOwner(this); deckName = deck->meta_name; } + else + { + game = new MTGPlayerCards(); + game->setOwner(this); + } mDeck = deck; } @@ -59,7 +64,7 @@ Player::~Player() SAFE_DELETE(mDeck); } -void Player::loadAvatar(string file) +bool Player::loadAvatar(string file, string resName) { if (mAvatarTex) { @@ -67,8 +72,12 @@ void Player::loadAvatar(string file) mAvatarTex = NULL; } mAvatarTex = WResourceManager::Instance()->RetrieveTexture(file, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); - if (mAvatarTex) - mAvatar = WResourceManager::Instance()->RetrieveQuad(file, 0, 0, 35, 50, "playerAvatar", RETRIEVE_NORMAL, TEXTURE_SUB_AVATAR); + if (mAvatarTex) { + mAvatar = WResourceManager::Instance()->RetrieveQuad(file, 0, 0, 35, 50, resName, RETRIEVE_NORMAL, TEXTURE_SUB_AVATAR); + return true; + } + + return false; } const string Player::getDisplayName() const @@ -93,6 +102,9 @@ int Player::getId() JQuadPtr Player::getIcon() { + if(!mAvatarTex) + loadAvatar(mAvatarName); + return mAvatar; } @@ -105,7 +117,7 @@ Player * Player::opponent() HumanPlayer::HumanPlayer(GameObserver *observer, string file, string fileSmall, MTGDeck * deck) : Player(observer, file, fileSmall, deck) { - loadAvatar("avatar.jpg"); + mAvatarName = "avatar.jpg"; playMode = MODE_HUMAN; } @@ -231,6 +243,11 @@ bool Player::parseLine(const string& s) phaseRing = s.substr(limiter + 1); return true; } + else if (areaS.compare("deckfile") == 0) + { + deckFile = s.substr(limiter + 1); + return true; + } else if (areaS.compare("offerinterruptonphase") == 0) { for (int i = 0; i < Constants::NB_MTG_PHASES; i++) @@ -259,5 +276,22 @@ bool Player::parseLine(const string& s) ostream& operator<<(ostream& out, const Player& p) { - return out << *(p.game); + out << *(Damageable*)&p; + string manapoolstring = p.manaPool->toString(); + if(manapoolstring != "") + out << "manapool=" << manapoolstring << endl; + if(p.mAvatarName != "") + out << "avatar=" << p.mAvatarName << endl; + if(p.phaseRing != "") + out << "customphasering=" << p.phaseRing << endl; + out << "offerinterruptonphase=" << Constants::MTGPhaseCodeNames[p.offerInterruptOnPhase] << endl; + if(p.deckFile != "") + out << "deckfile=" << p.deckFile << endl; + + if(p.game) + { + out << *(p.game); + } + + return out; } diff --git a/projects/mtg/src/Rules.cpp b/projects/mtg/src/Rules.cpp index e2430a7c8..704f2ac09 100644 --- a/projects/mtg/src/Rules.cpp +++ b/projects/mtg/src/Rules.cpp @@ -1,7 +1,7 @@ #include "PrecompiledHeader.h" -#include "Rules.h" #include "MTGDefinitions.h" +#include "Rules.h" #include "ManaCost.h" #include "Player.h" #include "AIMomirPlayer.h" @@ -303,6 +303,8 @@ Player * Rules::initPlayer(GameObserver *g, int playerId) return loadPlayerRandom(g, isAI, GAME_TYPE_RANDOM1); case GAME_TYPE_RANDOM2: return loadPlayerRandom(g, isAI, GAME_TYPE_RANDOM2); + default: + return NULL; } } //TODO p may still be NULL, what do we do to handle this? Above switch has no default case to handle the case where p is NULL @@ -385,7 +387,7 @@ void Rules::initGame(GameObserver *g) p->preventable = initState.playerData[i].player->preventable; if (initState.playerData[i].player->mAvatarName.size()) { - p->loadAvatar(initState.playerData[i].player->mAvatarName); + p->mAvatarName = initState.playerData[i].player->mAvatarName; } MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay }; MTGGameZone * loadedPlayerZones[] = { initState.playerData[i].player->game->graveyard, @@ -590,7 +592,7 @@ int Rules::load(string _filename) return 1; } -int Rules::strToGameMode(string s) +GameType Rules::strToGameMode(string s) { if (s.compare("momir") == 0) return GAME_TYPE_MOMIR; if (s.compare("random1") == 0) return GAME_TYPE_RANDOM1; diff --git a/projects/mtg/src/StoryFlow.cpp b/projects/mtg/src/StoryFlow.cpp index b5498a888..1b1ffbb7b 100644 --- a/projects/mtg/src/StoryFlow.cpp +++ b/projects/mtg/src/StoryFlow.cpp @@ -324,7 +324,7 @@ void StoryDuel::init() game = new GameObserver(players); rules->gamemode = GAME_TYPE_STORY; - game->startGame(rules); + game->startGame(GAME_TYPE_STORY, rules); } StoryDuel::StoryDuel(TiXmlElement* root, StoryFlow * mParent) : diff --git a/projects/mtg/src/utils.cpp b/projects/mtg/src/utils.cpp index 10d52e925..98094b64c 100644 --- a/projects/mtg/src/utils.cpp +++ b/projects/mtg/src/utils.cpp @@ -19,14 +19,35 @@ namespace wagic } using std::vector; +using std::queue; int randValuesCursor = -1; vector randValues; -int loadRandValues(string s) +queue loadedRandomValues; +queue usedRandomValues; + +ostream& saveRandValues(ostream& out) { - randValues.clear(); - randValuesCursor = -1; + while(usedRandomValues.size()) + { + out << usedRandomValues.front(); + if(usedRandomValues.size() >= 1) + out << ","; + + usedRandomValues.pop(); + } + + return out; +} + +void loadRandValues(string s) +{ + while(loadedRandomValues.size()) + loadedRandomValues.pop(); + while(usedRandomValues.size()) + usedRandomValues.pop(); + while (s.size()) { unsigned int value; @@ -41,18 +62,28 @@ int loadRandValues(string s) value = atoi(s.c_str()); s = ""; } - if (value) randValues.push_back(value); + if (value) loadedRandomValues.push(value); } - if (randValues.size()) randValuesCursor = 0; - return 1; +} + +ptrdiff_t MRand (ptrdiff_t i) +{ + return WRand()%i; } int WRand() { - if (randValuesCursor == -1) return rand(); - int result = randValues[randValuesCursor]; - randValuesCursor++; - if ((size_t) randValuesCursor >= randValues.size()) randValuesCursor = 0; + int result; + if (!loadedRandomValues.size()) + { + result = rand(); + } + else + { + result = loadedRandomValues.front(); + loadedRandomValues.pop(); + } + usedRandomValues.push(result); return result; } @@ -379,4 +410,4 @@ std::string ensureFolder(const std::string & folderName) result.append("/"); } return result; -} \ No newline at end of file +}