Example #1
0
def getLaunchConfig(options):
    player = PlayerPreGame(getPlayer(options.player),
                           selectedRace=options.race,
                           observe=options.observe)
    ret = Config(  # create game config
        expo=c.EXPO_SELECT[options.exp],
        version=options.version,
        ladder=getLadder(options.ladder),
        players=[player],
        whichPlayer=player.name,
        mode=c.types.GameModes(options.mode),
        themap=options.map,  # or selectMap(options.map),
        numObservers=options.obs,
        trust=True,
        #slaves      = [],
        fogDisabled=options.nofog,
        stepSize=options.step,
        opponents=options.opponents.split(',') if options.opponents else [],
        fullscreen=not options.windowed,
        raw=options.raw,
        score=options.score,
        feature=options.feature,
        render=options.rendered,
        #resolution
        #minimap
        #layerwidth
        #replay      = options.replay,
        #debug       = options.debug,
    )
    ret.connection  # force generation of IP address and ports attributes
    return ret
Example #2
0
 def inflate(self, newData={}):
     """ensure all object attribute values are objects"""
     self.__dict__.update(newData)
     #if not isinstance(self.state, types.GameStates):      self.state     = types.GameStates(self.state)
     if self.expo and not isinstance(self.expo, types.ExpansionNames):
         self.expo = types.ExpansionNames(self.expo)
     if self.version and not isinstance(self.version, versions.Version):
         self.version = versions.Version(self.version)
     if self.ladder and not isinstance(self.ladder, Ladder):
         self.ladder = Ladder(self.ladder)
     for i, player in enumerate(self.players):  # iterate over all players
         if isinstance(player, str): self.players[i] = getPlayer(player)
         elif not isinstance(player, PlayerRecord):
             self.players[i] = buildPlayer(*player)
     if self.mode and not isinstance(self.mode, types.GameModes):
         self.mode = types.GameModes(self.mode)
     if self.themap and not isinstance(self.themap, MapRecord):
         self.themap = selectMap(name=self.themap)
Example #3
0
def main(options=None):
    if options == None:  # if not provided, assume options are provided via command line
        parser = optionsParser()
        options = parser.parse_args()
        sys.argv = sys.argv[:
                            1]  # remove all arguments to avoid problems with absl FLAGS :(
    try:
        specifiedMap = selectMap(
            options.mapname,
            excludeName=options.exclude,
            closestMatch=True,  # force selection of at most one map
            **getSelectionParams(options))
    except Exception as e:
        print(e)
        return
    outTempName = specifiedMap.name + "_%d_%d." + c.SC2_REPLAY_EXT
    outTemplate = os.path.join(options.replaydir, outTempName)
    if options.editor:
        bankFile = getBankFilepath(specifiedMap.name)
        if os.path.isfile(bankFile):  # this map has saved previous scenarios
            bankName = re.sub("\..*?$", "", os.path.basename(bankFile))
            bankDir = os.path.dirname(bankFile)
            dirTime = re.sub("\.", "_", str(time.time()))
            tmpDir = os.path.join(bankDir, "%s_%s" % (bankName, dirTime))
            tmpXml = os.path.join(tmpDir, c.FILE_BANKLIST)
            tmpName = "%s.%s" % (bankName, c.SC2_MAP_EXT)
            tmpMapPath = os.path.join(tmpDir, tmpName)
            cfg = Config()
            dstMapDir = cfg.installedApp.mapsDir
            dstMapPath = os.path.join(dstMapDir, tmpName)
            if os.path.isdir(tmpDir):
                shutil.rmtree(tmpDir)
            try:
                os.makedirs(tmpDir)
                shutil.copyfile(
                    specifiedMap.path,
                    tmpMapPath)  # copy the original map file for modification
                with open(tmpXml, "w") as f:  # generate temporary xml data
                    f.write(c.BANK_DATA % bankName)
                if cfg.is64bit: mpqApp = c.PATH_MPQ_EDITOR_64BIT
                else: mpqApp = c.PATH_MPQ_EDITOR_32BIT
                cmd = c.MPQ_CMD % (mpqApp, tmpMapPath, tmpXml, c.FILE_BANKLIST)
                x = subprocess.call(
                    cmd)  # modify a temporary mapfile using the mpq editor
                print("Loaded previously defined %s scenarios." % (bankName))
                if os.path.isfile(
                        dstMapPath
                ):  # always ensure the destination mapfile is the modified version
                    os.remove(dstMapPath)
                shutil.copyfile(
                    tmpMapPath, dstMapPath
                )  # relocate the temporary map file into the maps folder
                tmpRecord = MapRecord(specifiedMap.name, dstMapPath, {})
                launchEditor(
                    tmpRecord
                )  # launch the editor using a temporary, modified map that includes the previously defined SC2Bank scenario data
            finally:
                time.sleep(
                    options.cleandelay
                )  # wait for the editor to launch before removing temporary files
                if os.path.isdir(
                        tmpDir
                ):  # always remove the temporary map and XML files
                    shutil.rmtree(tmpDir)
                while True:
                    try:
                        os.remove(dstMapPath
                                  )  # always remove the temporary map mpq file
                        break
                    except:
                        time.sleep(
                            2
                        )  # continue trying until the editor closes and the deletion is successful
        else:
            launchEditor(
                specifiedMap)  # run the editor using the game modification
    elif options.regression:
        batteries = options.test.split(",")
        raise NotImplementedError("TODO -- run each test battery")
    elif options.custom:
        playerNames = re.split("[,\s]+", options.players)
        if len(playerNames) != 2:  # must specify two players for 1v1
            if not options.players:
                playerNames = ""
            raise ValueError("must specify two players, but given %d: '%s'" %
                             (len(playerNames), playerNames))
        try:
            thisPlayer = getPlayer(
                playerNames[0]
            )  # player name stated first is expected to be this player
        except Exception:
            print("ERROR: player '%s' is not known" % playerNames[0])
            return
        if options.race == c.RANDOM:
            options.race = thisPlayer.raceDefault  # defer to a player's specified default race
        cfg = Config(
            themap=specifiedMap,
            ladder=getLadder("versentiedge"),
            players=[
                PlayerPreGame(thisPlayer,
                              selectedRace=c.types.SelectRaces(options.race))
            ],
            mode=c.types.GameModes(c.MODE_1V1),
            opponents=playerNames[1:],
            whichPlayer=thisPlayer.name,
            fogDisabled=
            True,  # disable fog to be able to see, set and remove enemy units
            **
            thisPlayer.initOptions,  # ensure desired data is sent in callback
        )
        cfg.raw = True  # required to be able to set up units using debug commands
        #cfg.display()
        scenarios = getSetup(specifiedMap, options, cfg)
        for scenario in scenarios:
            epoch = int(time.time(
            ))  # used for replay differentiation between each scenario
            failure = False
            for curLoop in range(
                    1, options.repeat + 1
            ):  # each loop of each scenario gets its own unique replay (count starting at one)
                outFile = outTemplate % (epoch, curLoop)
                launchOpts = defineLaunchOptions(scenario, outFile)
                failure = launcher.run(launchOpts, cfg=cfg)
                if failure: break
            if failure: break
    elif options.join:
        raise NotImplementedError("TODO -- implement remote play")
    else:
        parser.print_help()
        print("ERROR: must select a main option.")
Example #4
0
def run(options, cfg=None):
    if options.search or options.history:
        if not options.player: options.player = options.search
        if cfg == None: cfg = getLaunchConfig(options)
        try:
            httpResp = connectToServer.ladderPlayerInfo(
                cfg, options.search, getMatchHistory=options.history)
        except requests.exceptions.ConnectionError:
            return badConnect(cfg.ladder)
        if not httpResp.ok: return exitStatement(httpResp.text)
        printStr = "%15s : %s"
        for playerAttrs, playerHistory in httpResp.json():
            player = PlayerRecord(source=playerAttrs)
            print(player)
            for k in ["type", "difficulty", "initCmd", "rating"]:
                print(printStr % (k, getattr(player, k)))
            if "created" in playerAttrs:
                print(
                    printStr % ("created",
                                time.strftime('%Y-%m-%d',
                                              time.localtime(player.created))))
            if playerHistory:  # TODO -- do something with playerHistory, when implemented
                for h in playerH:
                    print(printStr % ("history", h))
            print()
    elif options.nogui:
        if cfg == None: cfg = getLaunchConfig(options)
        if False:  # only display in debug/verbose mode?
            print("REQUESTED CONFIGURATION")
            cfg.display()
            print(cfg.whoAmI().initCmd)
            print()
        try:
            httpResp = connectToServer.sendMatchRequest(cfg)
        except requests.exceptions.ConnectionError:
            return badConnect(cfg.ladder)
        ### cancel match request ### (would have to be issued as subprocess after match request, but before a match is assigned
        #import time
        #time.sleep(1)
        #print(connectToServer.cancelMatchRequest(cfg).text)
        if not httpResp.ok: return exitStatement(httpResp.text)
        data = httpResp.json()
        for pData in data[
                "players"]:  # if player matchup doesn't exist locally, retrieve server information and define the player
            pName = pData[0]
            try:
                getPlayer(pName)  # test whether player exists locally
            except ValueError:  # player w/ name pName is not defined locally
                try:
                    y = connectToServer.ladderPlayerInfo(
                        cfg, pName)  # get player info from ladder
                except requests.exceptions.ConnectionError:
                    return badConnect(cfg.ladder)
                settings = y[0][0]  # settings of player[0]
                del settings["created"]  # creation data is retained locally
                addPlayer(
                    settings)  # ensures that loading json + inflation succeeds
        matchCfg = Config()
        matchCfg.loadJson(data)
        print("SERVER-ASSIGNED CONFIGURATION")
        matchCfg.display()
        print()
        ### launch game; collect results ###
        thisPlayer = matchCfg.whoAmI()
        result = None
        replayData = ""
        callback = go.doNothing  # play with human control only (default)
        if thisPlayer.initCmd:  # launch the desired player appropriately
            if re.search(
                    "^\w+\.[\w\.]+$", thisPlayer.initCmd
            ):  # found a python command; extract callback and agent process/object stuff
                parts = thisPlayer.initCmd.split(".")
                moduleName = parts[0]
                try:
                    thing = importlib.import_module(moduleName)
                    for part in parts[
                            1:]:  # access callable defined by the agent
                        thing = getattr(thing, part)
                    callback = thing(
                    )  # execute to acquire a list of the callback and any additional, to-be-retained objects necessary to run the agent process
                except ModuleNotFoundError as e:
                    return exitStatement(
                        "agent %s initialization command (%s) did not begin with a module (expected: %s). Given: %s"
                        % (thisPlayer.name, thisPlayer.initCmd, moduleName, e))
                except AttributeError as e:
                    return exitStatement(
                        "invalid %s init command format (%s): %s" %
                        (thisPlayer, thisPlayer.initCmd, e))
                except Exception as e:
                    return exitStatement(
                        "general failure to initialize agent %s: %s %s" %
                        (thisPlayer.name, type(e), e))
            else:  # launch separate process that manages the agent and results
                # TODO -- ensure proper port info is supplied to the subprocess
                # TODO -- ensure proper configuration info is supplied to the subprocess
                p = subprocess.Popen(thisPlayer.initCmd.split())
                p.communicate()
                msg = "Command-line bot %s finished normally." % (thisPlayer)
                if p.returncode:  # otherwise rely on client to send game result to server (else server catches the non-reporting offender)
                    msg = "Command-line bot %s crashed (%d)." % (thisPlayer,
                                                                 p.returncode)
                    try:  # while non-python agent manages communication including match completion reporting to ladder server, some crash events can be reported by this process
                        httpResp = connectToServer.reportMatchCompletion(
                            matchCfg, result, replayData)
                        if not httpResp.ok: msg += " %s!" % (httpResp.text)
                    except requests.exceptions.ConnectionError:  # process can't report game result if it crashes
                        msg += " lost prior connection to %s!" % (cfg.ladder)
                return exitStatement(msg, code=p.returncode)
        try:
            if matchCfg.host:  # host contains details of the host to connect to
                func = join  # join game on host when host details are provided
            else:
                func = host  # no value means this machine is hosting
            result, replayData = func(matchCfg,
                                      agentCallBack=callback,
                                      scenario=options.scenario)
        except c.TimeoutExceeded as e:  # match failed to launch
            print(e)
            result = rh.launchFailure(matchCfg)  # report UNDECIDED results
        finally:
            if hasattr(callback, "shutdown"):
                callback.shutdown()  # close dependencies appropriately
            callback = None
        ### simulate sending match results ###
        #results = []
        #from numpy.random import choice
        #from time import sleep
        #import json
        #print("*"*80)
        #for i,p in enumerate(matchCfg.players):
        #    playerName = p.name
        #    if i%2: results.append( [playerName, int(not results[-1][1])] )
        #    else:   results.append( [playerName, int(choice([0, 1]))] )
        #sleep(1.5)
        #results = dict(results)
        #print(json.dumps(results, indent=4))
        #try:
        #    httpResp = connectToServer.reportMatchCompletion(matchCfg, results, "")
        #    if not httpResp.ok: return exitStatement(httpResp.text)
        #except requests.exceptions.ConnectionError:
        #    return badConnect(cfg.ladder)
        #print(httpResp.json())
        ### send actual results ###
        replaySize = len(replayData) if replayData else 0
        if replaySize and not options.scenario:
            print("FINAL RESULT: (%d)" % (replaySize))
            print(json.dumps(result, indent=4))
            if options.savereplay:  # save the replay if the option is specified
                dirName = os.path.dirname(options.savereplay)
                if dirName and not os.path.isdir(dirName):
                    os.makedirs(dirName)
                with open(options.savereplay, "wb") as f:
                    f.write(replayData)
        if result != None:  # regular result is expected to be reported by each player
            try:  # ensure replay data can be transmitted over http
                replayData = base64.encodestring(
                    replayData).decode()  # convert raw bytes into str
                httpResp = connectToServer.reportMatchCompletion(
                    matchCfg, result, replayData)
                if not httpResp.ok: return exitStatement(httpResp.text)
                if httpResp.text and "invalid" in httpResp.text:
                    return ValueError
            except requests.exceptions.ConnectionError:
                return badConnect(cfg.ladder)
            except Exception as e:
                return exitStatement("%s: %s" % (type(e), e))
            if replaySize:
                print(httpResp.json())  # also display effective rating changes
    elif options.add:
        versions.addNew(*options.add.split(','))
    elif options.update:
        keys = [
            "label",
            "version",
            "base-version",
            "data-hash",
            "fixed-hash",
            "replay-hash",
        ]
        data = {}
        for k, v in zip(keys, options.update.split(',')):
            data[k] = v
        versions.handle.update(data)
        versions.handle.save()
    elif options.versions:  # simply display the jsonData reformatted
        for v, record in sorted(versions.handle.ALL_VERS_DATA.items()):
            print(v)
            for k, v in sorted(record.items()):
                print("%15s: %s" % (k, v))
            print()
    else:  # without an action explicitly specified, launch the GUI with selected options
        if cfg == None: cfg = getLaunchConfig(options)
        cfg.display()
        # TODO -- launch GUI that manages communication to server
        print("ERROR: GUI mode is not yet implemented.")