mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
193
worlds/_sc2common/bot/player.py
Normal file
193
worlds/_sc2common/bot/player.py
Normal file
@@ -0,0 +1,193 @@
|
||||
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
|
||||
Reference in New Issue
Block a user