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
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)
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.")
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.")