1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

- Refactored victory loss condition checks

- Added toString() method to EVictoryLossCheckResult enum class to improve debugging
- Removed mostly unused CFunctionList2
- Added missing header files for vcmiclient project to CMakeLists
- Tweaked SDL suggests bpp message a bit
- Added showInfoDialogAndWait (info dialog and waits, used from client thread) and showOkDialog (callback to ok click, used from GUI thread) to player interface
- Added showOkDialog method to CInfoWindow (unused for now, but may be used later)
This commit is contained in:
beegee1 2013-11-30 09:43:31 +00:00
parent 2f698acf98
commit c786a3076a
14 changed files with 166 additions and 139 deletions

View File

@ -752,10 +752,10 @@ static void setScreenRes(int w, int h, int bpp, bool fullscreen, bool resetVideo
}
bool bufOnScreen = (screenBuf == screen);
if(suggestedBpp != bpp)
{
logGlobal->warnStream() << "Note: SDL suggests to use " << suggestedBpp << " bpp instead of" << bpp << " bpp ";
logGlobal->infoStream() << boost::format("Using %s bpp (bits per pixel) for the video mode. Default or overriden setting was %s bpp.") % suggestedBpp % bpp;
}
//For some reason changing fullscreen via config window checkbox result in SDL_Quit event

View File

@ -6,17 +6,13 @@ include_directories(${SDL_INCLUDE_DIR} ${SDLIMAGE_INCLUDE_DIR} ${SDLMIXER_INCLUD
include_directories(${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} ${FFMPEG_INCLUDE_DIRS})
set(client_SRCS
CPreGame.cpp
Client.cpp
CPlayerInterface.cpp
CMT.cpp
GUIClasses.cpp
battle/CBattleInterface.cpp
../CCallback.cpp
battle/CBattleInterface.cpp
battle/CBattleAnimations.cpp
battle/CBattleInterfaceClasses.cpp
battle/CCreatureAnimation.cpp
gui/CGuiHandler.cpp
gui/CIntObject.cpp
gui/CIntObjectClasses.cpp
@ -24,6 +20,12 @@ set(client_SRCS
gui/Geometries.cpp
gui/CCursorHandler.cpp
gui/SDL_Extensions.cpp
CPreGame.cpp
Client.cpp
CPlayerInterface.cpp
CMT.cpp
GUIClasses.cpp
AdventureMapClasses.cpp
CAdvmapInterface.cpp
CAnimation.cpp
@ -44,15 +46,22 @@ set(client_SRCS
NetPacksClient.cpp
)
set(client_HEADERS
CSoundBase.h
FunctionList.h
gui/SDL_Pixels.h
)
if(WIN32)
add_executable(vcmiclient WIN32 ${client_SRCS})
add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS})
elseif(APPLE)
# OS X specific includes
include_directories(${SPARKLE_INCLUDE_DIR})
# OS X specific source files
set(client_SRCS ${client_SRCS} SDLMain.m OSX.mm Info.plist vcmi.icns ../osx/vcmi_dsa_public.pem)
add_executable(vcmiclient MACOSX_BUNDLE ${client_SRCS})
add_executable(vcmiclient MACOSX_BUNDLE ${client_SRCS} ${client_HEADERS})
# OS X specific libraries
target_link_libraries(vcmiclient ${SPARKLE_FRAMEWORK})
@ -93,7 +102,7 @@ elseif(APPLE)
add_custom_command(TARGET vcmiclient POST_BUILD COMMAND ${MakeVCMIBundle})
else()
add_executable(vcmiclient ${client_SRCS})
add_executable(vcmiclient ${client_SRCS} ${client_HEADERS})
endif()
target_link_libraries(vcmiclient vcmi ${Boost_LIBRARIES} ${SDL_LIBRARY} ${SDLIMAGE_LIBRARY} ${SDLMIXER_LIBRARY} ${SDLTTF_LIBRARY} ${ZLIB_LIBRARIES} ${FFMPEG_LIBRARIES} ${RT_LIB} ${DL_LIB})

View File

@ -1022,6 +1022,21 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
}
}
void CPlayerInterface::showInfoDialogAndWait(std::vector<Component> & components, const MetaString & text)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
std::vector<Component*> comps;
for(auto & elem : components)
{
comps.push_back(&elem);
}
std::string str;
text.toString(str);
showInfoDialog(str,comps, 0);
waitWhileDialog();
}
void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, bool DelComps, const std::vector<CComponent*> & components)
{
boost::unique_lock<boost::recursive_mutex> un(*pim);
@ -1031,6 +1046,27 @@ void CPlayerInterface::showYesNoDialog(const std::string &text, CFunctionList<vo
CInfoWindow::showYesNoDialog(text, &components, onYes, onNo, DelComps, playerID);
}
void CPlayerInterface::showOkDialog(std::vector<Component> & components, const MetaString & text, const boost::function<void()> & onOk)
{
boost::unique_lock<boost::recursive_mutex> un(*pim);
std::vector<Component*> comps;
for(auto & elem : components)
{
comps.push_back(&elem);
}
std::string str;
text.toString(str);
stopMovement();
showingDialog->setn(true);
std::vector<CComponent*> intComps;
for(auto & component : comps)
intComps.push_back(new CComponent(*component));
CInfoWindow::showOkDialog(str, &intComps, onOk, true, playerID);
}
void CPlayerInterface::showBlockingDialog( const std::string &text, const std::vector<Component> &components, QueryID askID, int soundID, bool selection, bool cancel )
{
EVENT_HANDLER_CALLED_BY_CLIENT;
@ -2251,6 +2287,31 @@ void CPlayerInterface::acceptTurn()
adventureInt->endTurn.callback();
}
// warn player if he has no town
if(cb->howManyTowns() == 0)
{
auto playerColor = *cb->getPlayerID();
std::vector<Component> components;
components.push_back(Component(Component::FLAG, playerColor.getNum(), 0, 0));
MetaString text;
auto daysWithoutCastle = cb->getPlayer(playerColor)->daysWithoutCastle;
if(daysWithoutCastle == 6)
{
text.addTxt(MetaString::ARRAY_TXT,129); //%s, this is your last day to capture a town or you will be banished from this land.
text.addReplacement(MetaString::COLOR, playerColor.getNum());
}
else
{
text.addTxt(MetaString::ARRAY_TXT,128); //%s, you only have %d days left to capture a town or you will be banished from this land.
text.addReplacement(MetaString::COLOR, playerColor.getNum());
text.addReplacement(7 - daysWithoutCastle);
}
showInfoDialogAndWait(components, text);
}
}
void CPlayerInterface::tryDiggging(const CGHeroInstance *h)

View File

@ -229,9 +229,14 @@ public:
void updateInfo(const CGObjectInstance * specific);
void init(shared_ptr<CCallback> CB);
int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
// show dialogs
void showInfoDialog(const std::string &text, CComponent * component);
void showInfoDialog(const std::string &text, const std::vector<CComponent*> & components = std::vector<CComponent*>(), int soundID = 0, bool delComps = false);
void showInfoDialogAndWait(std::vector<Component> & components, const MetaString & text);
void showOkDialog(std::vector<Component> & components, const MetaString & text, const boost::function<void()> & onOk);
void showYesNoDialog(const std::string &text, CFunctionList<void()> onYes, CFunctionList<void()> onNo, bool DelComps = false, const std::vector<CComponent*> & components = std::vector<CComponent*>()); //deactivateCur - whether current main interface should be deactivated; delComps - if components will be deleted on window close
void stopMovement();
bool moveHero(const CGHeroInstance *h, CGPath path);
void initMovement(const TryMoveHero &details, const CGHeroInstance * ho, const int3 &hp );//initializing objects and performing first step of move

View File

@ -83,48 +83,3 @@ public:
}
}
};
template<typename Signature>
class CFunctionList2
{
public:
std::vector<boost::function<Signature> > funcs;
CFunctionList2(int){};
CFunctionList2(){};
template <typename Functor>
CFunctionList2(const Functor &f)
{
funcs.push_back(boost::function<Signature>(f));
}
CFunctionList2(const boost::function<Signature> &first)
{
funcs.push_back(first);
}
CFunctionList2(boost::function<Signature> &first)
{
funcs.push_back(first);
}
CFunctionList2 & operator+=(const boost::function<Signature> &first)
{
funcs.push_back(first);
return *this;
}
void clear()
{
funcs.clear();
}
operator bool() const
{
return funcs.size();
}
template <typename Arg>
void operator()(const Arg & a) const
{
std::vector<boost::function<Signature> > funcs2 = funcs; //backup
for(size_t i=0;i<funcs2.size(); ++i)
{
funcs2[i](a);
}
}
};

View File

@ -742,6 +742,16 @@ void CInfoWindow::showYesNoDialog(const std::string & text, const std::vector<CC
GH.pushInt(temp);
}
void CInfoWindow::showOkDialog(const std::string & text, const std::vector<CComponent*> *components, const boost::function<void()> & onOk, bool delComps, PlayerColor player)
{
std::vector<std::pair<std::string,CFunctionList<void()> > > pom;
pom.push_back(std::pair<std::string,CFunctionList<void()> >("IOKAY.DEF",0));
CInfoWindow * temp = new CInfoWindow(text, player, *components, pom, delComps);
temp->buttons[0]->callback += onOk;
GH.pushInt(temp);
}
CInfoWindow * CInfoWindow::create(const std::string &text, PlayerColor playerID /*= 1*/, const std::vector<CComponent*> *components /*= nullptr*/, bool DelComps)
{
std::vector<std::pair<std::string,CFunctionList<void()> > > pom;

View File

@ -106,6 +106,7 @@ public:
//use only before the game starts! (showYesNoDialog in LOCPLINT must be used then)
static void showInfoDialog( const std::string & text, const std::vector<CComponent*> *components, bool DelComps = true, PlayerColor player = PlayerColor(1));
static void showOkDialog(const std::string & text, const std::vector<CComponent*> *components, const boost::function<void()> & onOk, bool delComps = true, PlayerColor player = PlayerColor(1));
static void showYesNoDialog( const std::string & text, const std::vector<CComponent*> *components, const CFunctionList<void( ) > &onYes, const CFunctionList<void()> &onNo, bool DelComps = true, PlayerColor player = PlayerColor(1));
static CInfoWindow *create(const std::string &text, PlayerColor playerID = PlayerColor(1), const std::vector<CComponent*> *components = nullptr, bool DelComps = false);

View File

@ -534,7 +534,7 @@ void CHighlightableButtonsGroup::addButton(const std::map<int,std::string> &tool
buttons.push_back(bt);
}
CHighlightableButtonsGroup::CHighlightableButtonsGroup(const CFunctionList2<void(int)> &OnChange, bool musicLikeButtons)
CHighlightableButtonsGroup::CHighlightableButtonsGroup(const CFunctionList<void(int)> &OnChange, bool musicLikeButtons)
: onChange(OnChange), musicLike(musicLikeButtons)
{}

View File

@ -171,14 +171,14 @@ public:
class CHighlightableButtonsGroup : public CIntObject
{
public:
CFunctionList2<void(int)> onChange; //called when changing selected button with new button's id
CFunctionList<void(int)> onChange; //called when changing selected button with new button's id
std::vector<CHighlightableButton*> buttons;
bool musicLike; //determines the behaviour of this group
//void addButton(const std::map<int,std::string> &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid);
void addButton(CHighlightableButton* bt);//add existing button, it'll be deleted by CHighlightableButtonsGroup destructor
void addButton(const std::map<int,std::string> &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid, const CFunctionList<void()> &OnSelect=0, int key=0); //creates new button
CHighlightableButtonsGroup(const CFunctionList2<void(int)> &OnChange, bool musicLikeButtons = false);
CHighlightableButtonsGroup(const CFunctionList<void(int)> & OnChange, bool musicLikeButtons = false);
~CHighlightableButtonsGroup();
void select(int id, bool mode); //mode==0: id is serial; mode==1: id is unique button id
void selectionChanged(int to);

View File

@ -3327,15 +3327,19 @@ bool EVictoryLossCheckResult::loss() const
return !victory();
}
std::string EVictoryLossCheckResult::toString() const
{
if(*this == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) return "No victory or loss";
else if(*this == EVictoryLossCheckResult::VICTORY_STANDARD) return "Victory standard";
else if(*this == EVictoryLossCheckResult::VICTORY_SPECIAL) return "Victory special";
else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS) return "Loss standard heroes and towns";
else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) return "Loss standard towns and time over";
else if(*this == EVictoryLossCheckResult::LOSS_SPECIAL) return "Loss special";
else return "Unknown type";
}
std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult)
{
if(victoryLossCheckResult == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) os << "No victory or loss";
else if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_STANDARD) os << "Victory standard";
else if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_SPECIAL) os << "Victory special";
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS) os << "Loss standard heroes and towns";
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) os << "Loss standard towns and time over";
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_SPECIAL) os << "Loss special";
else os << "Unknown type";
os << victoryLossCheckResult.toString();
return os;
}

View File

@ -371,6 +371,7 @@ public:
bool operator!=(EVictoryLossCheckResult const & other) const;
bool victory() const;
bool loss() const;
std::string toString() const;
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@ -2,7 +2,6 @@
#include "BattleHex.h"
#include "../client/FunctionList.h"
#include "ResourceSet.h"
#include "int3.h"
#include "GameConstants.h"

View File

@ -685,7 +685,9 @@ void CGameHandler::battleAfterLevelUp( const BattleResult &result )
}
visitObjectAfterVictory = false;
winLoseHandle(1<<finishingBattle->loser.getNum() | 1<<finishingBattle->victor.getNum()); //handle victory/loss of engaged players
//handle victory/loss of engaged players
std::set<PlayerColor> playerColors = boost::assign::list_of(finishingBattle->loser)(finishingBattle->victor);
checkVictoryLossConditions(playerColors);
if(result.result == BattleResult::SURRENDER || result.result == BattleResult::ESCAPE) //loser has escaped or surrendered
{
@ -1411,39 +1413,7 @@ void CGameHandler::newTurn()
elem->newTurn();
}
winLoseHandle(0xff);
//warn players without town
if(gs->day)
{
for (auto i=gs->players.cbegin() ; i!=gs->players.cend();i++)
{
if(i->second.status || i->second.towns.size() || i->second.color >= PlayerColor::PLAYER_LIMIT)
continue;
InfoWindow iw;
iw.player = i->first;
iw.components.push_back(Component(Component::FLAG, i->first.getNum(), 0, 0));
if(!i->second.daysWithoutCastle)
{
iw.text.addTxt(MetaString::GENERAL_TXT,6); //%s, you have lost your last town. If you do not conquer another town in the next week, you will be eliminated.
iw.text.addReplacement(MetaString::COLOR, i->first.getNum());
}
else if(i->second.daysWithoutCastle == 6)
{
iw.text.addTxt(MetaString::ARRAY_TXT,129); //%s, this is your last day to capture a town or you will be banished from this land.
iw.text.addReplacement(MetaString::COLOR, i->first.getNum());
}
else
{
iw.text.addTxt(MetaString::ARRAY_TXT,128); //%s, you only have %d days left to capture a town or you will be banished from this land.
iw.text.addReplacement(MetaString::COLOR, i->first.getNum());
iw.text.addReplacement(7 - i->second.daysWithoutCastle);
}
sendAndApply(&iw);
}
}
checkVictoryLossConditionsForAll();
synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
}
@ -1515,27 +1485,21 @@ void CGameHandler::run(bool resume)
resume = false;
for(; i != gs->players.end(); i++)
{
if((i->second.towns.size()==0 && i->second.heroes.size()==0)
|| i->first>=PlayerColor::PLAYER_LIMIT
|| i->second.status)
if(i->second.status == EPlayerStatus::INGAME)
{
continue;
}
states.setFlag(i->first,&PlayerStatus::makingTurn,true);
states.setFlag(i->first,&PlayerStatus::makingTurn,true);
{
YourTurn yt;
yt.player = i->first;
applyAndSend(&yt);
}
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players.at(i->first).makingTurn && !end2)
{
static time_duration p = milliseconds(200);
states.cv.timed_wait(lock,p);
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players.at(i->first).makingTurn && !end2)
{
static time_duration p = milliseconds(200);
states.cv.timed_wait(lock,p);
}
}
}
}
@ -1608,7 +1572,7 @@ bool CGameHandler::removeObject( const CGObjectInstance * obj )
ro.id = obj->id;
sendAndApply(&ro);
winLoseHandle(255); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
checkVictoryLossConditionsForAll(); //eg if monster escaped (removing objs after battle is done dircetly by endBattle, not this function)
return true;
}
@ -1811,7 +1775,9 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner)
SetObjectProperty sop(obj->id, 1, owner.getNum());
sendAndApply(&sop);
winLoseHandle(1<<owner.getNum() | 1<<oldOwner.getNum());
std::set<PlayerColor> playerColors = boost::assign::list_of(owner)(oldOwner);
checkVictoryLossConditions(playerColors);
if(owner < PlayerColor::PLAYER_LIMIT && dynamic_cast<const CGTownInstance *>(obj)) //town captured
{
const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
@ -1933,7 +1899,7 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
giveSpells (obj, hero);
if(gs->map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
checkLossVictory(hero->tempOwner); //transported artifact?
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
}
void CGameHandler::vistiCastleObjects (const CGTownInstance *t, const CGHeroInstance *h)
@ -2178,28 +2144,28 @@ void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
{
sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERTROOP)
winLoseHandle();
checkVictoryLossConditionsForAll();
}
void CGameHandler::sendAndApply( SetResource * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkLossVictory(info->player);
checkVictoryLossConditionsForPlayer(info->player);
}
void CGameHandler::sendAndApply( SetResources * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkLossVictory(info->player);
checkVictoryLossConditionsForPlayer(info->player);
}
void CGameHandler::sendAndApply( NewStructures * info )
{
sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::BUILDCITY)
checkLossVictory(getTown(info->tid)->tempOwner);
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
}
void CGameHandler::save(const std::string & filename )
@ -2525,7 +2491,7 @@ bool CGameHandler::buildStructure( ObjectInstanceID tid, BuildingID requestedID,
if(t->garrisonHero)
vistiCastleObjects (t, t->garrisonHero);
checkLossVictory(t->tempOwner);
checkVictoryLossConditionsForPlayer(t->tempOwner);
return true;
}
bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
@ -3927,7 +3893,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
{
SystemMessage temp_message(VLC->generaltexth->allTexts.at(260));
sendAndApply(&temp_message);
checkLossVictory(player);//Player enter win code or got required art\creature
checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
}
}
@ -5055,21 +5021,28 @@ void CGameHandler::engageIntoBattle( PlayerColor player )
sendAndApply(&pb);
}
void CGameHandler::winLoseHandle(ui8 players )
void CGameHandler::checkVictoryLossConditions(const std::set<PlayerColor> & playerColors)
{
for(size_t i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
for(auto playerColor : playerColors)
{
if(players & 1<<i && gs->getPlayer(PlayerColor(i)))
{
checkLossVictory(PlayerColor(i));
}
if(gs->getPlayer(playerColor)) checkVictoryLossConditionsForPlayer(playerColor);
}
}
void CGameHandler::checkLossVictory( PlayerColor player )
void CGameHandler::checkVictoryLossConditionsForAll()
{
std::set<PlayerColor> playerColors;
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
playerColors.insert(PlayerColor(i));
}
checkVictoryLossConditions(playerColors);
}
void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
{
const PlayerState *p = gs->getPlayer(player);
if(p->status == EPlayerStatus::WINNER || p->status == EPlayerStatus::LOSER || p->status == EPlayerStatus::WRONG) return;
if(p->status != EPlayerStatus::INGAME) return;
auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player);
@ -5091,7 +5064,7 @@ void CGameHandler::checkLossVictory( PlayerColor player )
for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
{
if(i->first < PlayerColor::PLAYER_LIMIT && i->first != player)//FIXME: skip already eliminated players?
if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME)
{
iw.player = i->first;
sendAndApply(&iw);
@ -5150,7 +5123,13 @@ void CGameHandler::checkLossVictory( PlayerColor player )
}
//eliminating one player may cause victory of another:
winLoseHandle(GameConstants::ALL_PLAYERS & ~(1<<player.getNum()));
std::set<PlayerColor> playerColors;
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
if(player.getNum() != i) playerColors.insert(PlayerColor(i));
}
checkVictoryLossConditions(playerColors);
}
//If player making turn has lost his turn must be over as well

View File

@ -106,8 +106,6 @@ public:
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
int moveStack(int stack, BattleHex dest); //returned value - travelled distance
void runBattle();
void checkLossVictory(PlayerColor player);
void winLoseHandle(ui8 players=255); //players: bit field - colours of players to be checked; default: all
////used only in endBattle - don't touch elsewhere
bool visitObjectAfterVictory;
@ -293,6 +291,11 @@ public:
private:
void makeStackDoNothing(const CStack * next);
void getVictoryLossMessage(PlayerColor player, EVictoryLossCheckResult victoryLossCheckResult, InfoWindow & out) const;
// Check for victory and loss conditions
void checkVictoryLossConditionsForPlayer(PlayerColor player);
void checkVictoryLossConditions(const std::set<PlayerColor> & playerColors);
void checkVictoryLossConditionsForAll();
};
void makeStackDoNothing();