Skip to content

Commit

Permalink
Fixes the problem described at davechurchill/StarcraftAITournamentMan…
Browse files Browse the repository at this point in the history
…ager#42

Note that for BWAPI v4.4.0 only (currently anyway), onStart() is now also timed (although by default this would not cause any timeouts because by default, times for frame numbers <= 10 are ignored), in order to be more likely to detect early whether event times vary. I decided not to bother making the TM time onStart() for the older BWAPI versions too, because I doubt anyone would configure "TM_FRAME_THRESH_TIMED 0" and care about whether onStart() is timed.
  • Loading branch information
chriscoxe committed Sep 9, 2020
1 parent f7e0cd3 commit 67085c6
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 21 deletions.
76 changes: 58 additions & 18 deletions BWAPI_440/Source/TournamentModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,47 @@ TMAI::~TMAI() noexcept
}
}

void TMAI::updateFrameTimers()
{
const int eventTime = BWAPI::Broodwar->getLastEventTime();
const int frameCount = BWAPI::Broodwar->getFrameCount();

// For a client bot, if the TM calls BWAPI v4.4.0's getLastEventTime() it
// returns the total time for all events for the current frame (not just
// for the last event), and it returns the same value regardless of which
// TM callback method (onUnitDiscover(), onFrame() etc) is calling
// getLastEventTime(). We don't want to count the same amount multiple
// times. So, we try to detect whether we should interpret the value as
// the total time for all events for that frame or just the time for the
// last event, by examining whether getLastEventTime() has ever returned
// different values during the same frame. For the frames before it is
// detected, we interpret it as meaning the total time for all events for
// that frame. Future versions of BWAPI might solve the problem for us,
// but for v4.4.0 at least, we use this workaround. BWAPI versions before
// v4.4.0 don't time client bots at all, so the workaround isn't needed
// in those versions.
if (frameCount != oldFrameCount)
{
frameTimes[frameCount] = eventTime;
numPrevEventsThisFrame = 1;
oldFrameCount = frameCount;
}
else
{
if (eventTimesVaried)
{
frameTimes[frameCount] += eventTime;
}
else if (eventTime != frameTimes[frameCount])
{
eventTimesVaried = true;
frameTimes[frameCount] = (frameTimes[frameCount] * numPrevEventsThisFrame) + eventTime;
}

++numPrevEventsThisFrame;
}
}

void TMAI::onStart()
{
Broodwar->setGUI(drawGUI);
Expand All @@ -236,6 +277,8 @@ void TMAI::onStart()

state.init();

updateFrameTimers();

if (autoObs)
{
autoObserver.onStart();
Expand All @@ -257,11 +300,8 @@ void TMAI::onEnd(bool isWinner)

void TMAI::onFrame()
{
const int lastEventTime = BWAPI::Broodwar->getLastEventTime();
const int frameCount = BWAPI::Broodwar->getFrameCount();

// Add the frame times for this frame.
frameTimes[frameCount] += lastEventTime;
updateFrameTimers();

// If the user is not allowed to change some settings, we keep re-applying them,
// in case the user changed something they are not allowed to by typing chat messages.
Expand Down Expand Up @@ -516,7 +556,7 @@ void TMAI::drawGameTimer(int x, int y)

void TMAI::onSendText(std::string text)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onSendText(text);
Expand All @@ -525,7 +565,7 @@ void TMAI::onSendText(std::string text)

void TMAI::onReceiveText(BWAPI::Player player, std::string text)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onReceiveText(player, text);
Expand All @@ -534,7 +574,7 @@ void TMAI::onReceiveText(BWAPI::Player player, std::string text)

void TMAI::onPlayerLeft(BWAPI::Player player)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onPlayerLeft(player);
Expand All @@ -543,7 +583,7 @@ void TMAI::onPlayerLeft(BWAPI::Player player)

void TMAI::onNukeDetect(BWAPI::Position target)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onNukeDetect(target);
Expand All @@ -552,7 +592,7 @@ void TMAI::onNukeDetect(BWAPI::Position target)

void TMAI::onUnitDiscover(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitDiscover(unit);
Expand All @@ -561,7 +601,7 @@ void TMAI::onUnitDiscover(BWAPI::Unit unit)

void TMAI::onUnitEvade(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitEvade(unit);
Expand All @@ -570,7 +610,7 @@ void TMAI::onUnitEvade(BWAPI::Unit unit)

void TMAI::onUnitShow(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitShow(unit);
Expand All @@ -579,7 +619,7 @@ void TMAI::onUnitShow(BWAPI::Unit unit)

void TMAI::onUnitHide(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitHide(unit);
Expand All @@ -588,7 +628,7 @@ void TMAI::onUnitHide(BWAPI::Unit unit)

void TMAI::onUnitCreate(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitCreate(unit);
Expand All @@ -597,7 +637,7 @@ void TMAI::onUnitCreate(BWAPI::Unit unit)

void TMAI::onUnitDestroy(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitDestroy(unit);
Expand All @@ -606,7 +646,7 @@ void TMAI::onUnitDestroy(BWAPI::Unit unit)

void TMAI::onUnitMorph(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitMorph(unit);
Expand All @@ -615,7 +655,7 @@ void TMAI::onUnitMorph(BWAPI::Unit unit)

void TMAI::onUnitComplete(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitComplete(unit);
Expand All @@ -624,7 +664,7 @@ void TMAI::onUnitComplete(BWAPI::Unit unit)

void TMAI::onUnitRenegade(BWAPI::Unit unit)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onUnitRenegade(unit);
Expand All @@ -633,7 +673,7 @@ void TMAI::onUnitRenegade(BWAPI::Unit unit)

void TMAI::onSaveGame(std::string gameName)
{
frameTimes[BWAPI::Broodwar->getFrameCount()] += BWAPI::Broodwar->getLastEventTime();
updateFrameTimers();
if (autoObs)
{
autoObserver.onSaveGame(gameName);
Expand Down
6 changes: 5 additions & 1 deletion BWAPI_440/Source/TournamentModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class TMState
public:

std::vector<int> timeOutExceeded;
const std::string stateFileVer = "3.0.0";
const std::string stateFileVer = "3.0.1";
std::string selfName;
std::string enemyName;
std::string selfRaceInit;
Expand Down Expand Up @@ -96,6 +96,8 @@ class TMAI : public BWAPI::AIModule
int localSpeed = 0;
int frameSkip = 0;
int frameCountTimeoutsExceeded = 0;
int oldFrameCount = -1;
int numPrevEventsThisFrame = 0;
bool gameOver = false;
bool disableUserInput = true;
bool disableUserInputLocalSpeed = true;
Expand All @@ -106,11 +108,13 @@ class TMAI : public BWAPI::AIModule
bool drawGUI = true;
bool autoObs = true;
bool autoResumeGame = true;
bool eventTimesVaried = false;

public:

TMAI();
virtual ~TMAI() noexcept;
virtual void updateFrameTimers();
virtual void drawUnitInformation(int x, int y);
virtual void drawTMSettings(int x, int y);
virtual void drawGameTimer(int x, int y);
Expand Down
2 changes: 1 addition & 1 deletion Documentation/SCHNAIL/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Ensure that the bwapi-data/TM folder exists for the bot and the human before sta

Remember to ensure SCHNAIL sets "tournament = bwapi-data\TM\TournamentModule.dll" in bwapi.ini for both players, assuming that's where it lives.

The per-frame timeouts option (TM_TIMEOUTS) defaults to the same timeouts settings as AIIDE/CoG. SSCAIT allows more time, i.e. ">= 320 frames longer than 85 milliseconds" rather than ">= 320 frames longer than 55 milliseconds". PurpleWave times out a lot using the defaults. BASIL doesn't use per-frame timeouts, which is why PurpleWave works fine on BASIL. If you want to add a little leeway for users that have slow machines or for bots like PurpleWave, you might want to try something like 85 instead of 55. It's a trade-off between fairness for bot authors vs wasting users' time if bots are too slow a lot of the time.
The per-frame timeouts option (TM_TIMEOUTS) defaults to the same timeouts settings as AIIDE/CoG. SSCAIT allows more time, i.e. ">= 320 frames longer than 85 milliseconds" rather than ">= 320 frames longer than 55 milliseconds". BASIL doesn't use per-frame timeouts. So, some BASIL/SSCAIT bots might time out a lot using the defaults. If you want to add a little leeway for users that have slow machines or for some BASIL/SSCAIT bots, you might want to try something like 85 instead of 55. It's a trade-off between fairness for bot authors vs wasting users' time if bots are too slow a lot of the time.

I couldn't prevent users from pausing the game, but by default the Tournament Module will immediately resume the game whenever it is paused (if you prefer, this can be disabled via TM_AUTO_RESUME_GAME).

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ You can use environment variables and/or a configuration file to control options
## How to use
To use this project, be sure to specify the path of this tournament module DLL such as `tournament = bwapi-data\TournamentModule.dll` or `tournament = bwapi-data\TM\TournamentModule.dll` in `bwapi.ini` for the appropriate player(s), depending on the path that you want BWAPI to load it from. If a BWAPI bot is used to control the local player, the version of BWAPI used to compile this tournament module should correspond to the version of BWAPI used by the local bot, otherwise the TM/bot may not start or may crash etc due to incompatibility. On the machine running the bot, the BWAPI version that was used to compile bwapi.dll, TournamentModule.dll and the bot has to match. It's impossible to know whether this TM would work properly with a custom or incorrect BWAPI version. It might work, it might not, or it might seem to work but get crashes or strange problems sometimes, so it is discouraged.

If used, the value of the per-frame timeouts option (`TM_TIMEOUTS`) may need to be tweaked for some bots, depending on how slowly those bot(s) are on the local system's hardware/OS. Some bots might often time out if run on a system that is slower than they were tested on.
If used, the value of the per-frame timeouts option (`TM_TIMEOUTS`) may need to be tweaked for some bots, depending on how slowly those bot(s) are on the local system's hardware/OS. Some bots might often time out if run on a system that is slower than they were tested on, or were only intended to be used in competitions/ladders that have more relaxed timeout allowances.

Warning: if you have a process that monitors the state file produced by this project, it's possible that the the state file may be written then rewritten shortly afterwards, so if Starcraft is still running, your system should detect and wait a little while & retry, perhaps a few times, to read the file if the file is deleted or shortened or changes etc underneath it.

Expand Down

0 comments on commit 67085c6

Please sign in to comment.