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
							 |