sumbit
commit
33ced41bc0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,540 @@
|
||||||
|
from abc import ABC
|
||||||
|
import copy
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List, Tuple
|
||||||
|
import gymnasium as gym
|
||||||
|
|
||||||
|
class MyEnum(Enum):
|
||||||
|
@classmethod
|
||||||
|
def from_value(cls, value):
|
||||||
|
"""根据数值获取相应的枚举成员"""
|
||||||
|
for member in cls:
|
||||||
|
if member.value == value:
|
||||||
|
return member
|
||||||
|
raise ValueError(f"{value} is not a valid value for {cls.__name__}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_string(cls, name):
|
||||||
|
"""根据字符串获取相应的枚举成员"""
|
||||||
|
try:
|
||||||
|
return cls[name]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(f"{name} is not a valid {cls.__name__}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_input(cls, input):
|
||||||
|
"""根据输入(字符串或数值)获取相应的枚举成员"""
|
||||||
|
if isinstance(input, str):
|
||||||
|
return cls.from_string(input)
|
||||||
|
elif isinstance(input, int):
|
||||||
|
return cls.from_value(input)
|
||||||
|
else:
|
||||||
|
raise ValueError("Input must be either a string or an integer.")
|
||||||
|
|
||||||
|
class MoveType(MyEnum):
|
||||||
|
AIR = 0
|
||||||
|
GROUND = 1
|
||||||
|
SEA = 2
|
||||||
|
SUBWATER = 3
|
||||||
|
|
||||||
|
class TerrainType(MyEnum):
|
||||||
|
PLAIN = 0
|
||||||
|
ROAD = 1
|
||||||
|
SEA = 2
|
||||||
|
SUBWATER = 3
|
||||||
|
|
||||||
|
class ActionType(MyEnum):
|
||||||
|
ILLEGAL = -1
|
||||||
|
END_OF_TURN = 0
|
||||||
|
MOVE = 1
|
||||||
|
ATTACK = 2
|
||||||
|
INTERACT = 3
|
||||||
|
RELEASE = 4
|
||||||
|
SWITCH_WEAPON = 5
|
||||||
|
SUPPLY = 6
|
||||||
|
|
||||||
|
class ActionStatus(MyEnum):
|
||||||
|
VALID = 0
|
||||||
|
OCCUPIED_DES = 1
|
||||||
|
OUT_OF_RANGE = 2
|
||||||
|
|
||||||
|
class AgentType(MyEnum):
|
||||||
|
Infantry = 0 # 步兵
|
||||||
|
Tank = 1 # 装甲单位
|
||||||
|
AntiAir = 2 # 自行防空炮
|
||||||
|
MobilizedInfantry = 3 # 机械化步兵
|
||||||
|
Helicopter = 4 # 直升机
|
||||||
|
Fighter = 5 # 战斗机
|
||||||
|
UAV = 6 # 无人机
|
||||||
|
CombatShip = 7 # 战舰
|
||||||
|
Carrier = 8 # 航母
|
||||||
|
Artillery = 9 # 火炮
|
||||||
|
Construction = 10 # 建筑
|
||||||
|
MissileLauncher = 11 # 导弹发射车
|
||||||
|
TransportHelicopter = 12 # 运输直升机
|
||||||
|
SupplyTruck = 13 # 移动补给车
|
||||||
|
Submarine = 14 # 潜艇
|
||||||
|
AdvancedTank = 15 # 先进坦克
|
||||||
|
Airport = 16 # 机场
|
||||||
|
SupplyStation = 17 # 补给站
|
||||||
|
RadarStation = 18 # 雷达站
|
||||||
|
Bomber = 19 # 轰炸机
|
||||||
|
AWACS = 20 # 预警机
|
||||||
|
TransportShip = 21 # 运输船
|
||||||
|
CommandPost = 22 # 指挥部
|
||||||
|
|
||||||
|
class ModuleType(MyEnum):
|
||||||
|
HANGER = 0
|
||||||
|
SUPPLY = 1
|
||||||
|
TRANSPORT = 2
|
||||||
|
|
||||||
|
class Weapon:
|
||||||
|
def __init__(self, name: str, attack_range: int, damage: float, max_ammo: int, ammo: int = None, strike_types: List[MoveType] = []):
|
||||||
|
self.name = name
|
||||||
|
self.attack_range = attack_range
|
||||||
|
self.damage = damage
|
||||||
|
self.max_ammo = max_ammo
|
||||||
|
self.ammo = ammo if ammo is not None else max_ammo
|
||||||
|
self.strike_types = strike_types
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.ammo = self.max_ammo
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"attack_range": self.attack_range,
|
||||||
|
"damage": self.damage,
|
||||||
|
"max_ammo": self.max_ammo,
|
||||||
|
"ammo": self.ammo,
|
||||||
|
"strike_types": [strike_type.name for strike_type in self.strike_types]
|
||||||
|
}
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
copied_strike_types = copy.deepcopy(self.strike_types, memo)
|
||||||
|
return Weapon(self.name, self.attack_range, self.damage, self.max_ammo, self.ammo, copied_strike_types)
|
||||||
|
|
||||||
|
class Action:
|
||||||
|
def __init__(self,
|
||||||
|
action_type: ActionType=ActionType.ILLEGAL,
|
||||||
|
agent_id: str = "",
|
||||||
|
des: Tuple[int, int] = (-1, -1),
|
||||||
|
target_id: str = "",
|
||||||
|
weapon_id: int = 0,
|
||||||
|
weapon_name: str = "",
|
||||||
|
start_time: int = 0,
|
||||||
|
end_time: int = -1,
|
||||||
|
state: 'AgentState' = None,
|
||||||
|
**kwargs):
|
||||||
|
self.agent_id = agent_id
|
||||||
|
self.target_id = target_id
|
||||||
|
self.des = des
|
||||||
|
self.action_type = action_type
|
||||||
|
self.weapon_id = weapon_id
|
||||||
|
self.weapon_name = weapon_name
|
||||||
|
self.start_time = start_time
|
||||||
|
self.end_time = start_time if end_time == -1 else end_time
|
||||||
|
self.end_type = None
|
||||||
|
self.state = state
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def json(self) -> dict:
|
||||||
|
from .utils import axial_to_cube_dict
|
||||||
|
return {
|
||||||
|
"curOrderedUnitID": self.agent_id,
|
||||||
|
"targetUnitID": self.target_id,
|
||||||
|
"destination": axial_to_cube_dict((self.des[0]-1000, self.des[1]-1000)),
|
||||||
|
"commandType": self.action_type.value
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.action_type == ActionType.MOVE:
|
||||||
|
return f"{self.agent_id} moves to {self.des}"
|
||||||
|
elif self.action_type == ActionType.ATTACK:
|
||||||
|
return f"{self.agent_id} attacks {self.target_id}"
|
||||||
|
elif self.action_type == ActionType.INTERACT:
|
||||||
|
return f"Interact {self.agent_id} with {self.target_id}"
|
||||||
|
elif self.action_type == ActionType.RELEASE:
|
||||||
|
return f"Release {self.target_id} from {self.agent_id} to {self.des}"
|
||||||
|
elif self.action_type == ActionType.END_OF_TURN:
|
||||||
|
return f"End of turn"
|
||||||
|
elif self.action_type == ActionType.SWITCH_WEAPON:
|
||||||
|
return f"{self.agent_id} switches to {self.weapon_name}"
|
||||||
|
else:
|
||||||
|
return "Unknown action"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"agent_id": self.agent_id,
|
||||||
|
"target_id": self.target_id,
|
||||||
|
"des": self.des,
|
||||||
|
"action_type": self.action_type.name,
|
||||||
|
"weapon_id": self.weapon_id,
|
||||||
|
"weapon_name": self.weapon_name,
|
||||||
|
"start_time": self.start_time,
|
||||||
|
"end_type": self.end_type
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command(Action):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def description(self) -> List[str]:
|
||||||
|
if self.command.action_type == ActionType.MOVE:
|
||||||
|
return [
|
||||||
|
f"Agent {self.agent_id} started to move to {self.command.des} at time {self.start_time}.",
|
||||||
|
f"Agent {self.agent_id} arrived at {self.command.des} at time {self.end_time}."
|
||||||
|
]
|
||||||
|
elif self.command.action_type == ActionType.ATTACK:
|
||||||
|
return [
|
||||||
|
f"Agent {self.agent_id} started to attack {self.command.des} at time {self.start_time}.",
|
||||||
|
f"Agent {self.agent_id} attacked {self.command.des} at time {self.end_time}."
|
||||||
|
]
|
||||||
|
elif self.command.action_type == ActionType.SUPPLY:
|
||||||
|
return [
|
||||||
|
f"Agent {self.agent_id} started to get supply from {self.command.des} at time {self.start_time}.",
|
||||||
|
f"Agent {self.agent_id} got supply from {self.command.des} at time {self.end_time}."
|
||||||
|
]
|
||||||
|
elif self.command.action_type == ActionType.SWITCH_WEAPON:
|
||||||
|
return [
|
||||||
|
f"Agent {self.agent_id} started to switch weapon to {self.command.weapon_name} at time {self.start_time}.",
|
||||||
|
f"Agent {self.agent_id} switched weapon to {self.command.weapon_name} at time {self.end_time}."
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"Unsupported command type: {self.command.action_type}")
|
||||||
|
|
||||||
|
class Agent:
|
||||||
|
def __init__(self,
|
||||||
|
agent_id: str,
|
||||||
|
agent_type: AgentType,
|
||||||
|
team_id: int,
|
||||||
|
faction_id: int,
|
||||||
|
move_type: MoveType,
|
||||||
|
pos: Tuple[int, int],
|
||||||
|
info_level: int, stealth_level: int,
|
||||||
|
max_endurance: int, defense: int,
|
||||||
|
max_fuel: float, mobility: float,
|
||||||
|
switchable_weapons = [],
|
||||||
|
modules = [],
|
||||||
|
is_key: bool = False):
|
||||||
|
self.agent_id = agent_id
|
||||||
|
self.agent_type = agent_type
|
||||||
|
self.team_id = team_id
|
||||||
|
self.faction_id = faction_id
|
||||||
|
self.move_type = MoveType(move_type)
|
||||||
|
self.init_pos = pos
|
||||||
|
self.info_level = info_level
|
||||||
|
self.stealth_level = stealth_level
|
||||||
|
self.max_endurance = max_endurance
|
||||||
|
self.defense = defense
|
||||||
|
self.endurance = max_endurance
|
||||||
|
self.max_fuel = max_fuel
|
||||||
|
self.mobility = mobility
|
||||||
|
self.switchable_weapons = switchable_weapons
|
||||||
|
self.modules = modules
|
||||||
|
self.is_key = is_key
|
||||||
|
|
||||||
|
# Get action space
|
||||||
|
from .utils import range_to_count
|
||||||
|
max_attack_range = max(weapon.attack_range for weapon in self.switchable_weapons) if self.switchable_weapons else 0
|
||||||
|
max_capacity = max(module.capacity if hasattr(module, "capacity") else 0 for module in self.modules) if self.modules else 0
|
||||||
|
# max_capacity = 6 if self.modules and any(hasattr(module, "parked_agents") for module in self.modules) else 0
|
||||||
|
self._action_space = gym.spaces.Dict(
|
||||||
|
{
|
||||||
|
action_type: space
|
||||||
|
for action_type, space in {
|
||||||
|
ActionType.MOVE: gym.spaces.Discrete(range_to_count(int(self.mobility))) \
|
||||||
|
if self.mobility > 0 else None,
|
||||||
|
ActionType.ATTACK: gym.spaces.Discrete(range_to_count(max_attack_range)) \
|
||||||
|
if max_attack_range > 0 else None,
|
||||||
|
ActionType.SWITCH_WEAPON: gym.spaces.Discrete(len(self.switchable_weapons)) \
|
||||||
|
if len(self.switchable_weapons) >= 1 else None,
|
||||||
|
ActionType.INTERACT: gym.spaces.Discrete(range_to_count(1)),
|
||||||
|
ActionType.RELEASE: gym.spaces.Discrete(max_capacity) \
|
||||||
|
if max_capacity > 0 else None,
|
||||||
|
}.items()
|
||||||
|
if space is not None # 过滤掉值为 None 的键值对
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self._has_supply = False
|
||||||
|
self._available_types = []
|
||||||
|
self._parked_agents = []
|
||||||
|
self._capacity = max_capacity # 实际上应该最多只有一个运输单元
|
||||||
|
|
||||||
|
for module in self.modules:
|
||||||
|
if module.module_type == ModuleType.SUPPLY:
|
||||||
|
self._has_supply = True
|
||||||
|
self.supply = module
|
||||||
|
elif module.module_type == ModuleType.HANGER:
|
||||||
|
self._parked_agents = module.parked_agents
|
||||||
|
elif module.module_type == ModuleType.TRANSPORT:
|
||||||
|
self._parked_agents = module.parked_agents
|
||||||
|
self._available_types.extend(module.available_types)
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.pos = self.init_pos
|
||||||
|
self.endurance = self.max_endurance
|
||||||
|
self.fuel = self.max_fuel
|
||||||
|
if self.switchable_weapons:
|
||||||
|
for weapon in self.switchable_weapons:
|
||||||
|
weapon.reset()
|
||||||
|
self.weapon = self.switchable_weapons[0]
|
||||||
|
else:
|
||||||
|
self.weapon = None
|
||||||
|
self.commenced_action = False
|
||||||
|
self.cmd_todo = []
|
||||||
|
self.todo = []
|
||||||
|
self.is_carried = False
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.agent_id < other.agent_id
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.agent_id == other.agent_id
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.agent_id)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.agent_id} ({self.move_type.name}) at {self.pos}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.agent_id} ({self.move_type.name}) at {self.pos}"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"agent_id": self.agent_id,
|
||||||
|
"agent_type": self.agent_type.name,
|
||||||
|
"team_id": self.team_id,
|
||||||
|
"faction_id": self.faction_id,
|
||||||
|
"move_type": self.move_type.name,
|
||||||
|
# "info_level": self.info_level,
|
||||||
|
# "stealth_level": self.stealth_level,
|
||||||
|
"defense": self.defense,
|
||||||
|
"max_endurance": self.max_endurance,
|
||||||
|
"max_fuel": self.max_fuel,
|
||||||
|
"switchable_weapons": [weapon.to_dict() for weapon in self.switchable_weapons],
|
||||||
|
"modules": [module.to_dict() for module in self.modules],
|
||||||
|
# 以下为可变属性
|
||||||
|
"pos": list(self.pos),
|
||||||
|
"endurance": self.endurance,
|
||||||
|
"fuel": self.fuel,
|
||||||
|
"mobility": self.mobility,
|
||||||
|
"weapon": self.weapon.to_dict() if self.weapon is not None else None
|
||||||
|
}
|
||||||
|
|
||||||
|
def plan_to_dict(self):
|
||||||
|
state = self.todo[-1].state if self.todo else self.state
|
||||||
|
return {
|
||||||
|
"agent_id": self.agent_id,
|
||||||
|
"agent_type": self.agent_type.name,
|
||||||
|
"move_type": self.move_type.name,
|
||||||
|
"defense": self.defense,
|
||||||
|
"max_endurance": self.max_endurance,
|
||||||
|
"max_fuel": self.max_fuel,
|
||||||
|
"switchable_weapons": [weapon.to_dict() for weapon in self.switchable_weapons],
|
||||||
|
"modules": [module.to_dict() for module in self.modules],
|
||||||
|
"pos": list(state.pos),
|
||||||
|
"endurance": self.endurance,
|
||||||
|
"fuel": self.fuel,
|
||||||
|
"mobility": self.mobility,
|
||||||
|
"weapon": self.weapon.to_dict() if self.weapon is not None else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive(self):
|
||||||
|
return self.endurance > 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack_range(self):
|
||||||
|
return self.weapon.attack_range if self.weapon is not None else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strike_types(self):
|
||||||
|
return self.weapon.strike_types if self.weapon is not None else []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def damage(self):
|
||||||
|
return self.weapon.damage if self.weapon is not None else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ammo(self):
|
||||||
|
return self.weapon.ammo if self.weapon is not None else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_space(self):
|
||||||
|
return self._action_space
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_supply(self):
|
||||||
|
return self._has_supply
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_types(self):
|
||||||
|
return self._available_types
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parked_agents(self):
|
||||||
|
return self._parked_agents
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capacity(self):
|
||||||
|
return self._capacity
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return AgentState(self.pos, self.endurance, self.fuel, self.weapon, self.commenced_action, self.cmd_todo, self.todo)
|
||||||
|
|
||||||
|
def update(self, state: 'AgentState'):
|
||||||
|
self.pos = state.pos
|
||||||
|
self.endurance = state.endurance
|
||||||
|
self.fuel = state.fuel
|
||||||
|
self.commenced_action = state.commenced_action
|
||||||
|
self.cmd_todo = copy.deepcopy(state.cmd_todo)
|
||||||
|
self.todo = copy.deepcopy(state.todo)
|
||||||
|
self.weapon = copy.deepcopy(state.weapon)
|
||||||
|
|
||||||
|
class AgentState:
|
||||||
|
def __init__(self, pos: Tuple[int, int], endurance: int, fuel: float, weapon: Weapon = None, commenced_action: bool = False, cmd_todo: List[Action] = [], todo: List[Action] = []):
|
||||||
|
self.pos = pos
|
||||||
|
self.endurance = endurance
|
||||||
|
self.fuel = fuel
|
||||||
|
self.commenced_action = commenced_action
|
||||||
|
self.weapon = copy.deepcopy(weapon) if weapon is not None else None
|
||||||
|
self.cmd_todo = copy.deepcopy(cmd_todo)
|
||||||
|
self.todo = copy.deepcopy(todo)
|
||||||
|
|
||||||
|
class Team:
|
||||||
|
def __init__(self, team_id: int, faction_id: int, agents: dict[str, Agent] = {}):
|
||||||
|
self.team_id = team_id
|
||||||
|
self.faction_id = faction_id
|
||||||
|
self.agents = agents
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.team_id < other.team_id
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.team_id == other.team_id
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.team_id)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Team {self.team_id}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Team {self.team_id}"
|
||||||
|
|
||||||
|
def add_agent(self, agent: Agent):
|
||||||
|
self.agents.append(agent)
|
||||||
|
|
||||||
|
def remove_agent(self, agent_id: str):
|
||||||
|
self.agents.pop(agent_id)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
for agent in self.agents.values():
|
||||||
|
agent.reset()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive_count(self):
|
||||||
|
return sum(1 for agent in self.agents.values() if agent.alive)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive_ids(self):
|
||||||
|
return [agent.agent_id for agent in self.agents.values() if agent.alive]
|
||||||
|
|
||||||
|
class TileNode:
|
||||||
|
def __init__(self, pos: Tuple[int, int], terrain_type: TerrainType, agent_id: str = None, is_city: bool = False):
|
||||||
|
self.pos = pos
|
||||||
|
self.terrain_type = TerrainType(terrain_type)
|
||||||
|
self.agent_id = agent_id
|
||||||
|
self.is_city = is_city
|
||||||
|
self.team_id = -1
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.chaotic_value = 0
|
||||||
|
self.occupy_value = 0
|
||||||
|
self.occupied_by = None
|
||||||
|
self.agent_id = None
|
||||||
|
self.team_id = -1
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.pos < other.pos
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.pos == other.pos
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.pos)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.pos} ({self.terrain_type.name})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.pos} ({self.terrain_type.name})"
|
||||||
|
|
||||||
|
|
||||||
|
class Map:
|
||||||
|
def __init__(self, nodes: dict[Tuple[int, int], TileNode]):
|
||||||
|
self.width = max(pos[0] for pos in nodes.keys()) + 1
|
||||||
|
self.height = max(pos[1] for pos in nodes.keys()) + 1
|
||||||
|
self.nodes = nodes
|
||||||
|
|
||||||
|
class Module(ABC):
|
||||||
|
def __init__(self, module_type: ModuleType, add_endurance: int, add_ammo: int, add_fuel: float, available_types: List[AgentType] = None, capacity: int = 0):
|
||||||
|
self.module_type = module_type
|
||||||
|
self.add_endurance = add_endurance
|
||||||
|
self.add_ammo = add_ammo
|
||||||
|
self.add_fuel = add_fuel
|
||||||
|
self.available_types = available_types if available_types is not None else []
|
||||||
|
self.capacity = capacity
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"module_type": self.module_type.name,
|
||||||
|
"add_endurance": self.add_endurance,
|
||||||
|
"add_ammo": self.add_ammo,
|
||||||
|
"add_fuel": self.add_fuel,
|
||||||
|
"available_types": [agent_type.name for agent_type in self.available_types]
|
||||||
|
}
|
||||||
|
|
||||||
|
class Hanger(Module):
|
||||||
|
def __init__(self, available_types: List[AgentType] = None, capacity: int = 6):
|
||||||
|
super().__init__(module_type=ModuleType.HANGER,
|
||||||
|
add_endurance=4,
|
||||||
|
add_ammo=-1,
|
||||||
|
add_fuel=-1,
|
||||||
|
available_types=available_types,
|
||||||
|
capacity=capacity)
|
||||||
|
self.parked_agents = []
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.parked_agents = []
|
||||||
|
|
||||||
|
class Supply(Module):
|
||||||
|
def __init__(self, available_types: List[AgentType] = None, capacity: int = 0):
|
||||||
|
super().__init__(module_type=ModuleType.SUPPLY,
|
||||||
|
add_endurance=2,
|
||||||
|
add_ammo=2,
|
||||||
|
add_fuel=-1,
|
||||||
|
available_types=available_types,
|
||||||
|
capacity=capacity)
|
||||||
|
|
||||||
|
class Transport(Module):
|
||||||
|
def __init__(self, available_types: List[AgentType] = None, capacity: int = 6):
|
||||||
|
super().__init__(module_type=ModuleType.TRANSPORT,
|
||||||
|
add_endurance=0,
|
||||||
|
add_ammo=0,
|
||||||
|
add_fuel=0,
|
||||||
|
available_types=available_types,
|
||||||
|
capacity=capacity)
|
||||||
|
self.parked_agents = []
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.parked_agents = []
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
import numpy as np
|
||||||
|
import pygame
|
||||||
|
import math
|
||||||
|
from .utils import *
|
||||||
|
from .agent import TileNode, Agent, AgentType
|
||||||
|
|
||||||
|
terrain_colors = [
|
||||||
|
(245, 245, 220), # 米色 Plain
|
||||||
|
(210, 180, 140), # 褐色 Road
|
||||||
|
(224, 255, 255), # 青色 Water
|
||||||
|
(173, 216, 230) # 蓝色 Subwater
|
||||||
|
]
|
||||||
|
|
||||||
|
team_colors = [
|
||||||
|
(255, 0, 0), # 红色
|
||||||
|
(0, 0, 255), # 蓝色
|
||||||
|
(0, 255, 0), # 绿色
|
||||||
|
(255, 255, 0), # 黄色
|
||||||
|
(255, 165, 0), # 橙色
|
||||||
|
(128, 0, 128), # 紫色
|
||||||
|
(0, 255, 255), # 青色
|
||||||
|
(255, 192, 203) # 粉色
|
||||||
|
]
|
||||||
|
|
||||||
|
icons_path = "./tianqiong/envs/icons"
|
||||||
|
|
||||||
|
unit_icons: Dict[str, Dict[int, pygame.Surface]] = {
|
||||||
|
AgentType.AntiAir: {0: pygame.image.load(f"{icons_path}/AntiAir_b.png"), 1: pygame.image.load(f"{icons_path}/AntiAir_r.png")},
|
||||||
|
AgentType.Airport: {0: pygame.image.load(f"{icons_path}/Airport_b.png"), 1: pygame.image.load(f"{icons_path}/Airport_r.png")},
|
||||||
|
AgentType.Artillery: {0: pygame.image.load(f"{icons_path}/Artillery_b.png"), 1: pygame.image.load(f"{icons_path}/Artillery_r.png")},
|
||||||
|
AgentType.Bomber: {0: pygame.image.load(f"{icons_path}/Bomber_b.png"), 1: pygame.image.load(f"{icons_path}/Bomber_r.png")},
|
||||||
|
AgentType.Carrier: {0: pygame.image.load(f"{icons_path}/Carrier_b.png"), 1: pygame.image.load(f"{icons_path}/Carrier_r.png")},
|
||||||
|
AgentType.Fighter: {0: pygame.image.load(f"{icons_path}/Fighter_b.png"), 1: pygame.image.load(f"{icons_path}/Fighter_r.png")},
|
||||||
|
AgentType.Helicopter: {0: pygame.image.load(f"{icons_path}/Helicopter_b.png"), 1: pygame.image.load(f"{icons_path}/Helicopter_r.png")},
|
||||||
|
AgentType.Infantry: {0: pygame.image.load(f"{icons_path}/Infantry_b.png"), 1: pygame.image.load(f"{icons_path}/Infantry_r.png")},
|
||||||
|
AgentType.Tank: {0: pygame.image.load(f"{icons_path}/Tank_b.png"), 1: pygame.image.load(f"{icons_path}/Tank_r.png")},
|
||||||
|
AgentType.TransportHelicopter: {0: pygame.image.load(f"{icons_path}/TransportHelicopter_b.png"), 1: pygame.image.load(f"{icons_path}/TransportHelicopter_r.png")},
|
||||||
|
AgentType.TransportShip: {0: pygame.image.load(f"{icons_path}/TransportShip_b.png"), 1: pygame.image.load(f"{icons_path}/TransportShip_r.png")},
|
||||||
|
AgentType.CombatShip: {0: pygame.image.load(f"{icons_path}/CombatShip_b.png"), 1: pygame.image.load(f"{icons_path}/CombatShip_r.png")},
|
||||||
|
}
|
||||||
|
|
||||||
|
class Renderer:
|
||||||
|
def __init__(self, nodes: List[TileNode], hex_size=20):
|
||||||
|
self.nodes = nodes
|
||||||
|
self.hex_size = hex_size
|
||||||
|
self.window_size, (self.offset_x, self.offset_y) = get_window_size_and_offsets(nodes, hex_size)
|
||||||
|
|
||||||
|
def render(self, player_agents: List[Agent], spotted_enemy_agents: List[Agent], attack_agent: Agent=None, defend_agent: Agent=None) -> np.ndarray:
|
||||||
|
screen = pygame.Surface(self.window_size)
|
||||||
|
offset_x = self.offset_x
|
||||||
|
offset_y = self.offset_y
|
||||||
|
hex_size = self.hex_size
|
||||||
|
nodes = self.nodes
|
||||||
|
draw_hex_grid(screen, offset_x, offset_y, hex_size, nodes)
|
||||||
|
for agent in player_agents:
|
||||||
|
pos = agent.pos
|
||||||
|
alpha = agent.endurance / agent.max_endurance * 255
|
||||||
|
draw_unit(screen, offset_x, offset_y, hex_size, axial_to_pixel(pos, hex_size), alpha=alpha, agent_type=agent.agent_type, team_id=agent.team_id)
|
||||||
|
for agent in spotted_enemy_agents:
|
||||||
|
pos = agent.pos
|
||||||
|
alpha = agent.endurance / agent.max_endurance * 255
|
||||||
|
draw_unit(screen, offset_x, offset_y, hex_size, axial_to_pixel(pos, hex_size), alpha=alpha, agent_type=agent.agent_type, team_id=agent.team_id)
|
||||||
|
|
||||||
|
if attack_agent and defend_agent:
|
||||||
|
start_pos = axial_to_pixel(attack_agent.pos, hex_size)
|
||||||
|
end_pos = axial_to_pixel(defend_agent.pos, hex_size)
|
||||||
|
draw_hexagon(screen, hex_size, (start_pos[0] + offset_x, start_pos[1] + offset_y), team_colors[attack_agent.team_id], True, 5)
|
||||||
|
draw_hexagon(screen, hex_size, (end_pos[0] + offset_x, end_pos[1] + offset_y), team_colors[attack_agent.team_id], True, 5)
|
||||||
|
|
||||||
|
rgb_array = pygame.surfarray.array3d(screen)
|
||||||
|
|
||||||
|
return np.transpose(rgb_array, (1, 0, 2))
|
||||||
|
|
||||||
|
# 轴坐标系到像素坐标的转换函数(尖角朝上)
|
||||||
|
def axial_to_pixel(pos: Tuple[int, int], hex_size):
|
||||||
|
q, r = pos
|
||||||
|
x = hex_size * math.sqrt(3) * q
|
||||||
|
y = hex_size * 3/2 * r
|
||||||
|
x += hex_size * math.sqrt(3) / 2 * r
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
# 绘制六边形的函数
|
||||||
|
def draw_hexagon(surface, hex_size, center, color=(255, 255, 255), only_frame=False, line_width=1):
|
||||||
|
line_color = (169, 169, 169) # 灰色
|
||||||
|
angle_offset = math.pi / 6 # 顶点指向上方
|
||||||
|
points = [
|
||||||
|
(
|
||||||
|
center[0] + hex_size * math.cos(angle_offset + math.pi / 3 * i),
|
||||||
|
center[1] + hex_size * math.sin(angle_offset + math.pi / 3 * i)
|
||||||
|
)
|
||||||
|
for i in range(6)
|
||||||
|
]
|
||||||
|
if not only_frame:
|
||||||
|
pygame.draw.polygon(surface, color, points)
|
||||||
|
pygame.draw.polygon(surface, line_color, points, width=1) # 绘制六边形边框
|
||||||
|
else:
|
||||||
|
pygame.draw.polygon(surface, color, points, width=line_width) # 绘制六边形边框
|
||||||
|
|
||||||
|
def get_window_size_and_offsets(nodes: List[TileNode], hex_size: int) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
||||||
|
min_x = min_y = float('inf')
|
||||||
|
max_x = max_y = float('-inf')
|
||||||
|
offset_x = offset_y = float('-inf')
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
q, r = node.pos
|
||||||
|
x, y = axial_to_pixel((q, r), hex_size)
|
||||||
|
min_x = min(min_x, x)
|
||||||
|
max_x = max(max_x, x)
|
||||||
|
min_y = min(min_y, y)
|
||||||
|
max_y = max(max_y, y)
|
||||||
|
offset_x = max(offset_x, -x)
|
||||||
|
offset_y = max(offset_y, -y)
|
||||||
|
|
||||||
|
# 加一些边距
|
||||||
|
margin = hex_size * 2
|
||||||
|
offset_x += margin
|
||||||
|
offset_y += margin
|
||||||
|
width = int(max_x - min_x + 2 * margin)
|
||||||
|
height = int(max_y - min_y + 2 * margin)
|
||||||
|
|
||||||
|
width = (width - 15) // 16 * 16 + 16
|
||||||
|
height = (height - 15) // 16 * 16 + 16
|
||||||
|
|
||||||
|
return (width, height), (int(offset_x), int(offset_y))
|
||||||
|
|
||||||
|
# 绘制单位的函数,带透明度
|
||||||
|
def draw_unit(surface, offset_x, offset_y, hex_size, center, alpha=128, agent_type=None, team_id=None):
|
||||||
|
# print(agent_type, team_id)
|
||||||
|
if agent_type in unit_icons and team_id in unit_icons[agent_type]:
|
||||||
|
# 如果有图标,用图标表示
|
||||||
|
icon = unit_icons[agent_type][team_id]
|
||||||
|
icon_size = hex_size * 1.2
|
||||||
|
icon = pygame.transform.scale(icon, (icon_size, icon_size)) # 缩放图标至合适尺寸
|
||||||
|
icon.set_alpha(alpha) # 设置透明度
|
||||||
|
surface.blit(icon, (center[0] + offset_x - icon_size // 2, center[1] + offset_y - icon_size // 2))
|
||||||
|
else:
|
||||||
|
# 如果没有图标,用圆圈表示单位
|
||||||
|
unit_surface = pygame.Surface((hex_size, hex_size), pygame.SRCALPHA)
|
||||||
|
unit_surface.set_alpha(alpha) # 设置透明度
|
||||||
|
color = team_colors[team_id]
|
||||||
|
pygame.draw.circle(unit_surface, color, (hex_size // 2, hex_size // 2), hex_size // 3)
|
||||||
|
surface.blit(unit_surface, (center[0] + offset_x - hex_size // 2, center[1] + offset_y - hex_size // 2))
|
||||||
|
|
||||||
|
# 主绘制功能
|
||||||
|
def draw_hex_grid(surface, offset_x, offset_y, hex_size, nodes: List[TileNode]):
|
||||||
|
background_color = (255, 255, 255) # 白色
|
||||||
|
surface.fill(background_color)
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
q, r = node.pos
|
||||||
|
color = terrain_colors[node.terrain_type.value]
|
||||||
|
pixel_x, pixel_y = axial_to_pixel((q, r), hex_size)
|
||||||
|
center = (pixel_x + offset_x, pixel_y + offset_y)
|
||||||
|
draw_hexagon(surface, hex_size, center, color)
|
||||||
|
|
|
@ -0,0 +1,301 @@
|
||||||
|
import heapq
|
||||||
|
from .agent import Agent, Weapon, Action, TileNode, Module, Supply, Hanger, Transport
|
||||||
|
from .agent import MoveType, TerrainType, ActionType, AgentType, ModuleType
|
||||||
|
from typing import Tuple, List, Dict, Union
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Define the cost matrix for agent types and terrain types
|
||||||
|
cost_matrix = [
|
||||||
|
# Plain Road Sea Hill
|
||||||
|
[ 1, 1, 1, 1], # Air
|
||||||
|
[ 1.5, 1, 0, 2], # Ground
|
||||||
|
[ 0, 0, 1, 0], # Sea
|
||||||
|
[ 0, 0, 1, 0], # Subwater
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_cost(move_type: MoveType, terrain_type: TerrainType):
|
||||||
|
return cost_matrix[move_type.value][terrain_type.value]
|
||||||
|
|
||||||
|
def get_axial_dis(pos1: Tuple[int, int], pos2: Tuple[int, int] = (0, 0)):
|
||||||
|
q1, r1 = pos1
|
||||||
|
q2, r2 = pos2
|
||||||
|
return (abs(q1 - q2) + abs(r1 - r2) + abs((q1 + r1) - (q2 + r2))) / 2
|
||||||
|
|
||||||
|
def astar_search(map_data: Dict[Tuple[int, int], object],
|
||||||
|
move_type: MoveType,
|
||||||
|
start: Tuple[int, int],
|
||||||
|
goal: Tuple[int, int] = None,
|
||||||
|
limit: float = float('inf'),
|
||||||
|
return_cost: bool = False) -> Union[List[Tuple[int, int]], Tuple[float, List[Tuple[int, int]]]]:
|
||||||
|
"""
|
||||||
|
A* search algorithm to find the shortest path from start to goal.
|
||||||
|
map_data: a dictionary of nodes, where the keys are the node coordinates and the values are the node objects
|
||||||
|
move_type: the type of agent to consider (0 for air, 1 for ground, 2 for sea, 3 for subwater)
|
||||||
|
start: the starting node
|
||||||
|
goal: the ending node
|
||||||
|
"""
|
||||||
|
if isinstance(start, tuple):
|
||||||
|
start = [start]
|
||||||
|
multi_source = False
|
||||||
|
elif isinstance(start, list):
|
||||||
|
multi_source = True
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid start type. Found{type(start)} but expected tuple or list.")
|
||||||
|
|
||||||
|
g = {}
|
||||||
|
f = {}
|
||||||
|
parent = {}
|
||||||
|
open_list = []
|
||||||
|
cand_parent_count = {}
|
||||||
|
|
||||||
|
for pos in start:
|
||||||
|
g[pos] = 0
|
||||||
|
f[pos] = 0
|
||||||
|
parent[pos] = None
|
||||||
|
cand_parent_count[pos] = 1
|
||||||
|
open_list.append((0.0, pos))
|
||||||
|
|
||||||
|
in_open_list = set(start)
|
||||||
|
heapq.heapify(open_list)
|
||||||
|
closed_list = set()
|
||||||
|
|
||||||
|
directions = [(1, 0), (1, -1), (0, -1), (-1, 0), (-1, 1), (0, 1)]
|
||||||
|
|
||||||
|
while open_list:
|
||||||
|
_, cur = heapq.heappop(open_list)
|
||||||
|
in_open_list.remove(cur)
|
||||||
|
|
||||||
|
if goal and cur == goal:
|
||||||
|
path = []
|
||||||
|
while cur not in start:
|
||||||
|
path.append(cur)
|
||||||
|
cur = parent[cur]
|
||||||
|
if multi_source:
|
||||||
|
if path:
|
||||||
|
path.pop(0) # 弹出实际上当前所在位置
|
||||||
|
path.append(cur) # 确定多源最短路的终点
|
||||||
|
else:
|
||||||
|
path.reverse()
|
||||||
|
return (cost, path) if return_cost else path
|
||||||
|
|
||||||
|
closed_list.add(cur)
|
||||||
|
|
||||||
|
for dq, dr in directions:
|
||||||
|
neighbor = (cur[0] + dq, cur[1] + dr)
|
||||||
|
if neighbor in map_data and neighbor not in closed_list:
|
||||||
|
cost = get_cost(move_type, map_data[neighbor].terrain_type) \
|
||||||
|
if not multi_source \
|
||||||
|
else get_cost(move_type, map_data[cur].terrain_type)
|
||||||
|
if cost == 0:
|
||||||
|
continue
|
||||||
|
tentative_g = g[cur] + cost
|
||||||
|
if neighbor not in g or tentative_g < g[neighbor]:
|
||||||
|
g[neighbor] = tentative_g
|
||||||
|
f[neighbor] = tentative_g + (get_axial_dis(neighbor, goal) if goal else 0)
|
||||||
|
if f[neighbor] > limit:
|
||||||
|
continue
|
||||||
|
parent[neighbor] = cur
|
||||||
|
cand_parent_count[neighbor] = 1
|
||||||
|
if neighbor not in in_open_list:
|
||||||
|
heapq.heappush(open_list, (f[neighbor], neighbor))
|
||||||
|
in_open_list.add(neighbor)
|
||||||
|
elif neighbor in g and tentative_g == g[neighbor] and f[neighbor] <= limit:
|
||||||
|
cand_parent_count[neighbor] += 1
|
||||||
|
if np.random.random() < 1 / cand_parent_count[neighbor]: # reservior sampling
|
||||||
|
parent[neighbor] = cur
|
||||||
|
|
||||||
|
if goal is not None:
|
||||||
|
raise ValueError("No path found")
|
||||||
|
|
||||||
|
return list(closed_list)
|
||||||
|
|
||||||
|
def get_path(agent, map_data, start, des: Union[Tuple[int, int], List[Tuple[int, int]]], limit=float('inf')) -> List[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Returns a list of move actions to move the agent from its current position to the destination.
|
||||||
|
"""
|
||||||
|
if isinstance(des, tuple):
|
||||||
|
raw_path = astar_search(map_data=map_data, move_type=agent.move_type, start=start, goal=des, limit=limit)
|
||||||
|
elif isinstance(des, list): # multi-source
|
||||||
|
raw_path = astar_search(map_data=map_data, move_type=agent.move_type, start=des, goal=start, limit=limit)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid destination type. Found{type(des)} but expected tuple or list.")
|
||||||
|
|
||||||
|
if not raw_path:
|
||||||
|
raise ValueError("No path found")
|
||||||
|
|
||||||
|
# Create the frame path by breaking the path into smaller moves
|
||||||
|
path = []
|
||||||
|
sum = 0
|
||||||
|
raw_path = [start] + raw_path
|
||||||
|
|
||||||
|
# print(agent.pos, des, raw_path)
|
||||||
|
|
||||||
|
for parent, node in zip(raw_path[:-1], raw_path[1:]):
|
||||||
|
cost = get_cost(agent.move_type, map_data[node].terrain_type)
|
||||||
|
sum += cost
|
||||||
|
if sum > agent.fuel:
|
||||||
|
raise ValueError(f"Not enough fuel to reach {des}")
|
||||||
|
if sum > agent.mobility:
|
||||||
|
sum = cost
|
||||||
|
path.append(parent)
|
||||||
|
path.append(node)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def axial_to_cube_dict(a):
|
||||||
|
q, r = a
|
||||||
|
return {
|
||||||
|
"x": q,
|
||||||
|
"y": -(q + r),
|
||||||
|
"z": r
|
||||||
|
}
|
||||||
|
|
||||||
|
def cube_dict_to_axial(c):
|
||||||
|
return (c["x"], c["z"])
|
||||||
|
|
||||||
|
def axial_to_cube(a):
|
||||||
|
q, r = a
|
||||||
|
return (q, -(q + r), r)
|
||||||
|
|
||||||
|
def cube_to_axial(c):
|
||||||
|
return (c[0], c[2])
|
||||||
|
|
||||||
|
def encode_axial(pos: Tuple[int, int]) -> int:
|
||||||
|
q, r = pos
|
||||||
|
if q == 0 and r == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
x, y, z = axial_to_cube(pos)
|
||||||
|
d = get_axial_dis(pos)
|
||||||
|
|
||||||
|
base_number = 3 * d * (d - 1) + 1
|
||||||
|
|
||||||
|
if x > 0 and y < 0 and z >= 0: # 0
|
||||||
|
return base_number + z
|
||||||
|
elif x <= 0 and y < 0 and z > 0: # 1
|
||||||
|
return base_number + d - x
|
||||||
|
elif x < 0 and y >= 0 and z > 0: # 2
|
||||||
|
return base_number + d * 2 + y
|
||||||
|
elif x < 0 and y > 0 and z <= 0: # 3
|
||||||
|
return base_number + d * 3 - z
|
||||||
|
elif x >= 0 and y > 0 and z < 0: # 4
|
||||||
|
return base_number + d * 4 + x
|
||||||
|
else: # x > 0 and y <= 0 and z < 0 # 5
|
||||||
|
return base_number + d * 5 - y
|
||||||
|
|
||||||
|
def decode_axial(num: int) -> Tuple[int, int]:
|
||||||
|
if num == 0:
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
l, r = 1, num
|
||||||
|
while l < r:
|
||||||
|
mid = (l + r) // 2
|
||||||
|
if range_to_count(mid) + 1 > num:
|
||||||
|
r = mid
|
||||||
|
else:
|
||||||
|
l = mid + 1
|
||||||
|
d = l
|
||||||
|
base_number = range_to_count(d - 1) + 1
|
||||||
|
# base_number = 1
|
||||||
|
# d = 1
|
||||||
|
# while base_number + 6 * d <= num:
|
||||||
|
# base_number += 6 * d
|
||||||
|
# d += 1
|
||||||
|
|
||||||
|
k = (num - base_number) // d
|
||||||
|
mod = (num - base_number) % d
|
||||||
|
|
||||||
|
if k == 0:
|
||||||
|
q = d - mod
|
||||||
|
r = mod
|
||||||
|
elif k == 1:
|
||||||
|
q = -mod
|
||||||
|
r = d
|
||||||
|
elif k == 2:
|
||||||
|
q = -d
|
||||||
|
r = d - mod
|
||||||
|
elif k == 3:
|
||||||
|
q = -(d - mod)
|
||||||
|
r = -mod
|
||||||
|
elif k == 4:
|
||||||
|
q = mod
|
||||||
|
r = -d
|
||||||
|
else: # k == 5
|
||||||
|
q = d
|
||||||
|
r = -(d - mod)
|
||||||
|
|
||||||
|
return (q, r)
|
||||||
|
|
||||||
|
def get_adj_pos(pos: Tuple[int, int], max_dis: int) -> List[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Returns a list of adjacent positons within the given range.
|
||||||
|
"""
|
||||||
|
d = [decode_axial(num) for num in range(1, range_to_count(int(max_dis)) + 1)]
|
||||||
|
return [(pos[0] + dq, pos[1] + dr) for dq, dr in d]
|
||||||
|
|
||||||
|
def range_to_count(max_dis: int) -> int: # not including 0
|
||||||
|
return 3 * max_dis * (max_dis + 1)
|
||||||
|
|
||||||
|
def dict_to_action(action_dict: dict) -> Action:
|
||||||
|
return Action(
|
||||||
|
action_type=ActionType.from_input(action_dict["action_type"]),
|
||||||
|
agent_id=action_dict["agent_id"],
|
||||||
|
target_id=action_dict.get("target_id", ""),
|
||||||
|
des=action_dict.get("des", (-1, -1)),
|
||||||
|
weapon_id=action_dict.get("weapon_id", -1),
|
||||||
|
weapon_name=action_dict.get("weapon_name", ""),
|
||||||
|
start_time=action_dict.get("start_time", 0),
|
||||||
|
end_type=action_dict.get("end_type", None),
|
||||||
|
attack_count=action_dict.get("attack_count", 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def dict_to_node(tile_dict: dict) -> TileNode:
|
||||||
|
return TileNode(
|
||||||
|
pos=(tile_dict["pos"]["q"], tile_dict["pos"]["r"]),
|
||||||
|
terrain_type=TerrainType.from_input(tile_dict["terrain_type"]),
|
||||||
|
# agent_id=tile_dict.get("agent_id", None),
|
||||||
|
# is_city=tile_dict.get("is_city", False),
|
||||||
|
# chaotic_value=tile_dict.get("chaotic_value", 0),
|
||||||
|
# occupy_value=tile_dict.get("occupy_value", 0),
|
||||||
|
# occupied_by=tile_dict.get("occupied_by", None)
|
||||||
|
)
|
||||||
|
|
||||||
|
def dict_to_weapon(weapon_dict: dict) -> Weapon:
|
||||||
|
return Weapon(
|
||||||
|
name=weapon_dict["name"],
|
||||||
|
attack_range=weapon_dict["attack_range"],
|
||||||
|
damage=weapon_dict["damage"],
|
||||||
|
max_ammo=weapon_dict["max_ammo"],
|
||||||
|
strike_types=[MoveType.from_input(strike_type) for strike_type in weapon_dict["strike_types"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
def dict_to_module(module_dict: dict) -> Module:
|
||||||
|
module_type = ModuleType.from_input(module_dict["module_id"])
|
||||||
|
available_types = [AgentType.from_input(agent_type) for agent_type in module_dict.get("available_types", [])] \
|
||||||
|
if module_dict.get("available_types", []) is not None else []
|
||||||
|
capacity = module_dict.get("capacity", 0)
|
||||||
|
if module_type == ModuleType.HANGER:
|
||||||
|
return Hanger(available_types=available_types, capacity=capacity)
|
||||||
|
elif module_type == ModuleType.SUPPLY:
|
||||||
|
return Supply(available_types=available_types, capacity=capacity)
|
||||||
|
elif module_type == ModuleType.TRANSPORT:
|
||||||
|
return Transport(available_types=available_types, capacity=capacity)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid module type: {module_type}")
|
||||||
|
|
||||||
|
def dict_to_agent(agent_dict: dict) -> Agent:
|
||||||
|
return Agent(
|
||||||
|
agent_id=agent_dict["agent_id"],
|
||||||
|
agent_type=AgentType.from_input(agent_dict["type"]),
|
||||||
|
team_id=agent_dict["team_id"],
|
||||||
|
faction_id=agent_dict["faction_id"],
|
||||||
|
move_type=MoveType.from_input(agent_dict["move_type"]),
|
||||||
|
pos=(agent_dict["pos"]["q"], agent_dict["pos"]["r"]),
|
||||||
|
info_level=agent_dict.get("info_level", 0),
|
||||||
|
stealth_level=agent_dict.get("stealth_level", 0),
|
||||||
|
max_endurance=agent_dict["max_endurance"],
|
||||||
|
defense=agent_dict["defense"],
|
||||||
|
max_fuel=agent_dict["max_fuel"],
|
||||||
|
mobility=agent_dict["mobility"],
|
||||||
|
switchable_weapons=[dict_to_weapon(weapon_dict) for weapon_dict in agent_dict.get("switchable_weapons", [])],
|
||||||
|
modules=[dict_to_module(module_dict) for module_dict in agent_dict.get("modules", [])] if agent_dict.get("modules", []) is not None else []
|
||||||
|
)
|
|
@ -0,0 +1,603 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"agent_id": "879a",
|
||||||
|
"pos": {
|
||||||
|
"q": 10,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 0.0,
|
||||||
|
"type": 16,
|
||||||
|
"team_id": 0,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 1,
|
||||||
|
"defense": 5.0,
|
||||||
|
"mobility": 0.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [],
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"module_id": 0,
|
||||||
|
"capacity": 6,
|
||||||
|
"available_types": [
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
19,
|
||||||
|
20
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_id": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "9f19",
|
||||||
|
"pos": {
|
||||||
|
"q": 11,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 0,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "a947",
|
||||||
|
"pos": {
|
||||||
|
"q": 12,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 0,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 50,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "cc4c",
|
||||||
|
"pos": {
|
||||||
|
"q": 13,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 1,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "c865",
|
||||||
|
"pos": {
|
||||||
|
"q": 14,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 1,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "4189",
|
||||||
|
"pos": {
|
||||||
|
"q": 22,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 1,
|
||||||
|
"team_id": 1,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 1,
|
||||||
|
"defense": 3.0,
|
||||||
|
"mobility": 4.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "0cd5",
|
||||||
|
"pos": {
|
||||||
|
"q": 23,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 1,
|
||||||
|
"team_id": 1,
|
||||||
|
"faction_id": 0,
|
||||||
|
"move_type": 1,
|
||||||
|
"defense": 3.0,
|
||||||
|
"mobility": 4.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "2602",
|
||||||
|
"pos": {
|
||||||
|
"q": 24,
|
||||||
|
"r": 0
|
||||||
|
},
|
||||||
|
"max_endurance": 3.0,
|
||||||
|
"max_fuel": 80.0,
|
||||||
|
"type": 12,
|
||||||
|
"team_id": 2,
|
||||||
|
"faction_id": 1,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 0.0,
|
||||||
|
"mobility": 5.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [],
|
||||||
|
"switchable_weaponsNames": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "c53f",
|
||||||
|
"pos": {
|
||||||
|
"q": 2,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 0.0,
|
||||||
|
"type": 16,
|
||||||
|
"team_id": 2,
|
||||||
|
"faction_id": 1,
|
||||||
|
"defense": 5.0,
|
||||||
|
"mobility": 0.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [],
|
||||||
|
"switchable_weaponsNames": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "95f3",
|
||||||
|
"pos": {
|
||||||
|
"q": 3,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 2,
|
||||||
|
"faction_id": 1,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "7772",
|
||||||
|
"pos": {
|
||||||
|
"q": 4,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 2,
|
||||||
|
"faction_id": 1,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "6f9d",
|
||||||
|
"pos": {
|
||||||
|
"q": 5,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 3,
|
||||||
|
"faction_id": 2,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "2477",
|
||||||
|
"pos": {
|
||||||
|
"q": 6,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 4.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 5,
|
||||||
|
"team_id": 3,
|
||||||
|
"faction_id": 2,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 1.0,
|
||||||
|
"mobility": 6.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": [
|
||||||
|
{
|
||||||
|
"name": "AKD-21\u53cd\u5766\u514b\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 8.0,
|
||||||
|
"max_ammo": 6,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LS-6\u5236\u5bfc\u70b8\u5f39",
|
||||||
|
"attack_range": 5,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 1,
|
||||||
|
"strike_types": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PL-15\u7a7a\u7a7a\u5bfc\u5f39",
|
||||||
|
"attack_range": 8,
|
||||||
|
"damage": 7.0,
|
||||||
|
"max_ammo": 4,
|
||||||
|
"strike_types": [
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "YJ-91\u7a7a\u8230\u5bfc\u5f39",
|
||||||
|
"attack_range": 6,
|
||||||
|
"damage": 15.0,
|
||||||
|
"max_ammo": 2,
|
||||||
|
"strike_types": [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "9183",
|
||||||
|
"pos": {
|
||||||
|
"q": 7,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 1,
|
||||||
|
"team_id": 3,
|
||||||
|
"faction_id": 2,
|
||||||
|
"move_type": 1,
|
||||||
|
"defense": 3.0,
|
||||||
|
"mobility": 4.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "45e5",
|
||||||
|
"pos": {
|
||||||
|
"q": 18,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 10.0,
|
||||||
|
"max_fuel": 120.0,
|
||||||
|
"type": 1,
|
||||||
|
"team_id": 3,
|
||||||
|
"faction_id": 2,
|
||||||
|
"move_type": 1,
|
||||||
|
"defense": 3.0,
|
||||||
|
"mobility": 4.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"agent_id": "7e23",
|
||||||
|
"pos": {
|
||||||
|
"q": 19,
|
||||||
|
"r": 14
|
||||||
|
},
|
||||||
|
"max_endurance": 3.0,
|
||||||
|
"max_fuel": 80.0,
|
||||||
|
"type": 12,
|
||||||
|
"team_id": 4,
|
||||||
|
"faction_id": 3,
|
||||||
|
"move_type": 0,
|
||||||
|
"defense": 0.0,
|
||||||
|
"mobility": 5.0,
|
||||||
|
"info_level": 6,
|
||||||
|
"stealth_level": 0.0,
|
||||||
|
"switchable_weapons": []
|
||||||
|
}
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,89 @@
|
||||||
|
import sys
|
||||||
|
sys.path.append("../")
|
||||||
|
|
||||||
|
from yes_cmdr.yes_cmdr_env import YesCmdrEnv
|
||||||
|
from yes_cmdr.common.agent import Action, Command, ActionType
|
||||||
|
|
||||||
|
def test_action_id_transform(env):
|
||||||
|
print("Testing action id transform")
|
||||||
|
env.reset()
|
||||||
|
for idx in range(env.action_space_size):
|
||||||
|
assert env.action_to_id(env.id_to_action(idx)) == idx, f"Expected {env.id_to_action(idx)} as {idx} but got {env.action_to_id(env.id_to_action(idx))}"
|
||||||
|
|
||||||
|
env.step(0)
|
||||||
|
|
||||||
|
for idx in range(env.action_space_size):
|
||||||
|
assert env.action_to_id(env.id_to_action(idx)) == idx, f"Expected {env.id_to_action(idx)} as {idx} but got {env.action_to_id(env.id_to_action(idx))}"
|
||||||
|
|
||||||
|
def test_random_action(env):
|
||||||
|
print("Testing random action")
|
||||||
|
env.reset()
|
||||||
|
for i in range(100):
|
||||||
|
action = env.random_action()
|
||||||
|
assert env.check_validity(action)[0], f"Action {action} is not valid"
|
||||||
|
env.step(action)
|
||||||
|
|
||||||
|
def test_bot_action(env):
|
||||||
|
print("Testing bot action")
|
||||||
|
env.reset()
|
||||||
|
for i in range(100):
|
||||||
|
action = env.bot_action()
|
||||||
|
assert env.check_validity(action)[0], f"Action {action} is not valid"
|
||||||
|
env.step(action)
|
||||||
|
|
||||||
|
def test_command(env):
|
||||||
|
env.reset()
|
||||||
|
|
||||||
|
done = False
|
||||||
|
step_count = 1
|
||||||
|
|
||||||
|
for idx in range(env.action_space_size):
|
||||||
|
assert env.action_to_id(env.id_to_action(idx)) == idx, f"Expected {env.id_to_action(idx)} as {idx} but got {env.action_to_id(env.id_to_action(idx))}"
|
||||||
|
|
||||||
|
agents_data = env.teams[0].agents
|
||||||
|
agents = agents_data.values()
|
||||||
|
|
||||||
|
for agent in agents:
|
||||||
|
print(agent.to_dict())
|
||||||
|
|
||||||
|
env.make_plan(Command(agent_id="9f19", target_id="95f3", attack_count=1, action_type=ActionType.ATTACK, start_time=1))
|
||||||
|
env.make_plan(Command(agent_id="9f19", target_id="2477", attack_count=1, action_type=ActionType.ATTACK, start_time=5))
|
||||||
|
for idx, action in enumerate(agents_data["9f19"].todo):
|
||||||
|
print(action)
|
||||||
|
|
||||||
|
env.make_plan(Command(agent_id="cc4c", target_id="c53f", attack_count=4, action_type=ActionType.ATTACK))
|
||||||
|
for idx, action in enumerate(agents_data["cc4c"].todo):
|
||||||
|
print(action)
|
||||||
|
|
||||||
|
while not done and step_count <= 20:
|
||||||
|
for agent in agents:
|
||||||
|
if agent.todo and agent.todo[0].start_time <= step_count:
|
||||||
|
action = env.todo_action(agent.agent_id)
|
||||||
|
print(action)
|
||||||
|
obs, reward, terminated, truncated, info = env.step(action)
|
||||||
|
print(f"Step {step_count}: Action {action.action_type.name} -> Reward: {reward}, Done: {done}")
|
||||||
|
print(f"Info: {info}")
|
||||||
|
if "exception" in info:
|
||||||
|
agent.todo.clear()
|
||||||
|
|
||||||
|
env.step(0)
|
||||||
|
env.step(0)
|
||||||
|
|
||||||
|
step_count += 1
|
||||||
|
|
||||||
|
env.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 配置环境参数
|
||||||
|
env = YesCmdrEnv(
|
||||||
|
data_path="./data/test",
|
||||||
|
max_team=5,
|
||||||
|
max_faction=4,
|
||||||
|
war_fog=False
|
||||||
|
)
|
||||||
|
|
||||||
|
test_action_id_transform(env)
|
||||||
|
test_random_action(env)
|
||||||
|
test_bot_action(env)
|
||||||
|
# test_command(env)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue