mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	
		
			
	
	
		
			194 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			194 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | from abc import ABC | ||
|  | from pathlib import Path | ||
|  | from typing import List, Union | ||
|  | 
 | ||
|  | from .bot_ai import BotAI | ||
|  | from .data import AIBuild, Difficulty, PlayerType, Race | ||
|  | 
 | ||
|  | 
 | ||
|  | class AbstractPlayer(ABC): | ||
|  | 
 | ||
|  |     def __init__( | ||
|  |         self, | ||
|  |         p_type: PlayerType, | ||
|  |         race: Race = None, | ||
|  |         name: str = None, | ||
|  |         difficulty=None, | ||
|  |         ai_build=None, | ||
|  |         fullscreen=False | ||
|  |     ): | ||
|  |         assert isinstance(p_type, PlayerType), f"p_type is of type {type(p_type)}" | ||
|  |         assert name is None or isinstance(name, str), f"name is of type {type(name)}" | ||
|  | 
 | ||
|  |         self.name = name | ||
|  |         self.type = p_type | ||
|  |         self.fullscreen = fullscreen | ||
|  |         if race is not None: | ||
|  |             self.race = race | ||
|  |         if p_type == PlayerType.Computer: | ||
|  |             assert isinstance(difficulty, Difficulty), f"difficulty is of type {type(difficulty)}" | ||
|  |             # Workaround, proto information does not carry ai_build info | ||
|  |             # We cant set that in the Player classmethod | ||
|  |             assert ai_build is None or isinstance(ai_build, AIBuild), f"ai_build is of type {type(ai_build)}" | ||
|  |             self.difficulty = difficulty | ||
|  |             self.ai_build = ai_build | ||
|  | 
 | ||
|  |         elif p_type == PlayerType.Observer: | ||
|  |             assert race is None | ||
|  |             assert difficulty is None | ||
|  |             assert ai_build is None | ||
|  | 
 | ||
|  |         else: | ||
|  |             assert isinstance(race, Race), f"race is of type {type(race)}" | ||
|  |             assert difficulty is None | ||
|  |             assert ai_build is None | ||
|  | 
 | ||
|  |     @property | ||
|  |     def needs_sc2(self): | ||
|  |         return not isinstance(self, Computer) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Human(AbstractPlayer): | ||
|  | 
 | ||
|  |     def __init__(self, race, name=None, fullscreen=False): | ||
|  |         super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) | ||
|  | 
 | ||
|  |     def __str__(self): | ||
|  |         if self.name is not None: | ||
|  |             return f"Human({self.race._name_}, name={self.name !r})" | ||
|  |         return f"Human({self.race._name_})" | ||
|  | 
 | ||
|  | 
 | ||
|  | class Bot(AbstractPlayer): | ||
|  | 
 | ||
|  |     def __init__(self, race, ai, name=None, fullscreen=False): | ||
|  |         """
 | ||
|  |         AI can be None if this player object is just used to inform the | ||
|  |         server about player types. | ||
|  |         """
 | ||
|  |         assert isinstance(ai, BotAI) or ai is None, f"ai is of type {type(ai)}, inherit BotAI from bot_ai.py" | ||
|  |         super().__init__(PlayerType.Participant, race, name=name, fullscreen=fullscreen) | ||
|  |         self.ai = ai | ||
|  | 
 | ||
|  |     def __str__(self): | ||
|  |         if self.name is not None: | ||
|  |             return f"Bot {self.ai.__class__.__name__}({self.race._name_}), name={self.name !r})" | ||
|  |         return f"Bot {self.ai.__class__.__name__}({self.race._name_})" | ||
|  | 
 | ||
|  | 
 | ||
|  | class Computer(AbstractPlayer): | ||
|  | 
 | ||
|  |     def __init__(self, race, difficulty=Difficulty.Easy, ai_build=AIBuild.RandomBuild): | ||
|  |         super().__init__(PlayerType.Computer, race, difficulty=difficulty, ai_build=ai_build) | ||
|  | 
 | ||
|  |     def __str__(self): | ||
|  |         return f"Computer {self.difficulty._name_}({self.race._name_}, {self.ai_build.name})" | ||
|  | 
 | ||
|  | 
 | ||
|  | class Observer(AbstractPlayer): | ||
|  | 
 | ||
|  |     def __init__(self): | ||
|  |         super().__init__(PlayerType.Observer) | ||
|  | 
 | ||
|  |     def __str__(self): | ||
|  |         return "Observer" | ||
|  | 
 | ||
|  | 
 | ||
|  | class Player(AbstractPlayer): | ||
|  | 
 | ||
|  |     def __init__(self, player_id, p_type, requested_race, difficulty=None, actual_race=None, name=None, ai_build=None): | ||
|  |         super().__init__(p_type, requested_race, difficulty=difficulty, name=name, ai_build=ai_build) | ||
|  |         self.id: int = player_id | ||
|  |         self.actual_race: Race = actual_race | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def from_proto(cls, proto): | ||
|  |         if PlayerType(proto.type) == PlayerType.Observer: | ||
|  |             return cls(proto.player_id, PlayerType(proto.type), None, None, None) | ||
|  |         return cls( | ||
|  |             proto.player_id, | ||
|  |             PlayerType(proto.type), | ||
|  |             Race(proto.race_requested), | ||
|  |             Difficulty(proto.difficulty) if proto.HasField("difficulty") else None, | ||
|  |             Race(proto.race_actual) if proto.HasField("race_actual") else None, | ||
|  |             proto.player_name if proto.HasField("player_name") else None, | ||
|  |         ) | ||
|  | 
 | ||
|  | 
 | ||
|  | class BotProcess(AbstractPlayer): | ||
|  |     """
 | ||
|  |     Class for handling bots launched externally, including non-python bots. | ||
|  |     Default parameters comply with sc2ai and aiarena ladders. | ||
|  | 
 | ||
|  |     :param path: the executable file's path | ||
|  |     :param launch_list: list of strings that launches the bot e.g. ["python", "run.py"] or ["run.exe"] | ||
|  |     :param race: bot's race | ||
|  |     :param name: bot's name | ||
|  |     :param sc2port_arg: the accepted argument name for the port of the sc2 instance to listen to | ||
|  |     :param hostaddress_arg: the accepted argument name for the address of the sc2 instance to listen to | ||
|  |     :param match_arg: the accepted argument name for the starting port to generate a portconfig from | ||
|  |     :param realtime_arg: the accepted argument name for specifying realtime | ||
|  |     :param other_args: anything else that is needed | ||
|  | 
 | ||
|  |     e.g. to call a bot capable of running on the bot ladders: | ||
|  |         BotProcess(os.getcwd(), "python run.py", Race.Terran, "INnoVation") | ||
|  |     """
 | ||
|  | 
 | ||
|  |     def __init__( | ||
|  |         self, | ||
|  |         path: Union[str, Path], | ||
|  |         launch_list: List[str], | ||
|  |         race: Race, | ||
|  |         name=None, | ||
|  |         sc2port_arg="--GamePort", | ||
|  |         hostaddress_arg="--LadderServer", | ||
|  |         match_arg="--StartPort", | ||
|  |         realtime_arg="--RealTime", | ||
|  |         other_args: str = None, | ||
|  |         stdout: str = None, | ||
|  |     ): | ||
|  |         super().__init__(PlayerType.Participant, race, name=name) | ||
|  |         assert Path(path).exists() | ||
|  |         self.path = path | ||
|  |         self.launch_list = launch_list | ||
|  |         self.sc2port_arg = sc2port_arg | ||
|  |         self.match_arg = match_arg | ||
|  |         self.hostaddress_arg = hostaddress_arg | ||
|  |         self.realtime_arg = realtime_arg | ||
|  |         self.other_args = other_args | ||
|  |         self.stdout = stdout | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         if self.name is not None: | ||
|  |             return f"Bot {self.name}({self.race.name} from {self.launch_list})" | ||
|  |         return f"Bot({self.race.name} from {self.launch_list})" | ||
|  | 
 | ||
|  |     def cmd_line(self, | ||
|  |                  sc2port: Union[int, str], | ||
|  |                  matchport: Union[int, str], | ||
|  |                  hostaddress: str, | ||
|  |                  realtime: bool = False) -> List[str]: | ||
|  |         """
 | ||
|  | 
 | ||
|  |         :param sc2port: the port that the launched sc2 instance listens to | ||
|  |         :param matchport: some starting port that both bots use to generate identical portconfigs. | ||
|  |                 Note: This will not be sent if playing vs computer | ||
|  |         :param hostaddress: the address the sc2 instances used | ||
|  |         :param realtime: 1 or 0, indicating whether the match is played in realtime or not | ||
|  |         :return: string that will be used to start the bot's process | ||
|  |         """
 | ||
|  |         cmd_line = [ | ||
|  |             *self.launch_list, | ||
|  |             self.sc2port_arg, | ||
|  |             str(sc2port), | ||
|  |             self.hostaddress_arg, | ||
|  |             hostaddress, | ||
|  |         ] | ||
|  |         if matchport is not None: | ||
|  |             cmd_line.extend([self.match_arg, str(matchport)]) | ||
|  |         if self.other_args is not None: | ||
|  |             cmd_line.append(self.other_args) | ||
|  |         if realtime: | ||
|  |             cmd_line.extend([self.realtime_arg]) | ||
|  |         return cmd_line |