refactor regions

This commit is contained in:
bridge
2025-09-10 22:55:31 +08:00
parent 6425e80ffe
commit 12fdccfee5
24 changed files with 664 additions and 438 deletions

307
src/classes/region.py Normal file
View File

@@ -0,0 +1,307 @@
from dataclasses import dataclass, field
from typing import Union, TypeVar, Type
from enum import Enum
from abc import ABC, abstractmethod
from src.utils.df import game_configs
from src.classes.essence import EssenceType, Essence
def get_tiles_from_shape(shape: 'Shape', north_west_cor: str, south_east_cor: str) -> list[tuple[int, int]]:
"""
根据形状和两个角点坐标,计算出对应的所有坐标点
Args:
shape: 区域形状
north_west_cor: 西北角坐标,格式: "x,y"
south_east_cor: 东南角坐标,格式: "x,y"
Returns:
所有坐标点的列表
"""
nw_coords = tuple(map(int, north_west_cor.split(',')))
se_coords = tuple(map(int, south_east_cor.split(',')))
min_x, min_y = nw_coords
max_x, max_y = se_coords
coordinates = []
if shape == Shape.SQUARE or shape == Shape.RECTANGLE:
# 正方形和长方形:填充整个矩形区域
for x in range(min_x, max_x + 1):
for y in range(min_y, max_y + 1):
coordinates.append((x, y))
elif shape == Shape.MEANDERING:
# 蜿蜒形状(如河流):创建一条从西北到东南的蜿蜒路径
# 计算河流的宽度(根据距离动态调整)
distance_x = max_x - min_x
distance_y = max_y - min_y
total_distance = max(distance_x, distance_y)
# 河流宽度:距离越长,河流越宽
if total_distance < 10:
width = 1
elif total_distance < 30:
width = 2
else:
width = 3
# 生成中心路径点
path_points = []
if distance_x >= distance_y:
# 主要沿X轴方向流动
for x in range(min_x, max_x + 1):
# 计算对应的y坐标添加一些蜿蜒效果
progress = (x - min_x) / max(distance_x, 1)
base_y = min_y + int(progress * distance_y)
# 添加蜿蜒效果:使用简单的正弦波
import math
wave_amplitude = min(3, distance_y // 4) if distance_y > 0 else 0
wave_y = int(wave_amplitude * math.sin(progress * math.pi * 2))
y = max(min_y, min(max_y, base_y + wave_y))
path_points.append((x, y))
else:
# 主要沿Y轴方向流动
for y in range(min_y, max_y + 1):
progress = (y - min_y) / max(distance_y, 1)
base_x = min_x + int(progress * distance_x)
# 添加蜿蜒效果
import math
wave_amplitude = min(3, distance_x // 4) if distance_x > 0 else 0
wave_x = int(wave_amplitude * math.sin(progress * math.pi * 2))
x = max(min_x, min(max_x, base_x + wave_x))
path_points.append((x, y))
# 为每个路径点添加宽度
for px, py in path_points:
for dx in range(-width//2, width//2 + 1):
for dy in range(-width//2, width//2 + 1):
nx, ny = px + dx, py + dy
# 确保在边界内
if min_x <= nx <= max_x and min_y <= ny <= max_y:
coordinates.append((nx, ny))
# 去重并排序
return sorted(list(set(coordinates)))
@dataclass
class Region(ABC):
"""
区域抽象基类
理想中,一些地块应当在一起组成一个区域。
比如,某山;某湖、江、海;某森林;某平原;某城市;
一些分布比如物产按照Region来分布。
再比如灵气应当也是按照region分布的。
默认一个region内部的属性是共通的。
同时NPC应当对Region有观测和认知。
"""
id: int
name: str
desc: str
shape: 'Shape'
north_west_cor: str # 西北角坐标,格式: "x,y"
south_east_cor: str # 东南角坐标,格式: "x,y"
# 这些字段将在__post_init__中设置
cors: list[tuple[int, int]] = field(init=False) # 存储所有坐标点
center_loc: tuple[int, int] = field(init=False)
area: int = field(init=False)
def __post_init__(self):
"""初始化计算字段"""
# 先计算所有坐标点
self.cors = get_tiles_from_shape(self.shape, self.north_west_cor, self.south_east_cor)
# 基于坐标点计算面积
self.area = len(self.cors)
# 计算中心位置(基于实际坐标点的平均值)
if self.cors:
avg_x = sum(coord[0] for coord in self.cors) // len(self.cors)
avg_y = sum(coord[1] for coord in self.cors) // len(self.cors)
self.center_loc = (avg_x, avg_y)
else:
# 如果没有坐标点使用边界框中心作为fallback
nw_coords = tuple(map(int, self.north_west_cor.split(',')))
se_coords = tuple(map(int, self.south_east_cor.split(',')))
self.center_loc = (
(nw_coords[0] + se_coords[0]) // 2,
(nw_coords[1] + se_coords[1]) // 2
)
def __hash__(self) -> int:
return hash(self.id)
def __eq__(self, other) -> bool:
if not isinstance(other, Region):
return False
return self.id == other.id
@abstractmethod
def get_region_type(self) -> str:
"""返回区域类型的字符串表示"""
pass
class Shape(Enum):
"""
区域形状类型
"""
SQUARE = "square" # 正方形
RECTANGLE = "rectangle" # 长方形
MEANDERING = "meandering" # 蜿蜒的(如河流)
@classmethod
def from_str(cls, shape_str: str) -> 'Shape':
"""
从字符串创建Shape实例
Args:
shape_str: 形状的字符串表示,如 "square", "rectangle", "meandering"
Returns:
对应的Shape枚举值
Raises:
ValueError: 如果字符串不匹配任何已知的形状类型
"""
for shape in cls:
if shape.value == shape_str:
return shape
raise ValueError(f"Unknown shape type: {shape_str}")
@dataclass
class NormalRegion(Region):
"""
普通区域 - 平原、大河之类的,没有灵气或灵气很低
"""
def get_region_type(self) -> str:
return "normal"
def __str__(self) -> str:
return f"普通区域:{self.name} - {self.desc}"
@dataclass
class CultivateRegion(Region):
"""
修炼区域 - 有灵气的区域,可以修炼
"""
essence_type: EssenceType # 最高灵气类型
essence_density: int # 最高灵气密度
essence: Essence = field(init=False) # 灵气对象,根据 essence_type 和 essence_density 生成
def __post_init__(self):
# 先调用父类的 __post_init__
super().__post_init__()
# 创建灵气对象主要灵气类型设置为指定密度其他类型设置为0
essence_density_dict = {essence_type: 0 for essence_type in EssenceType}
essence_density_dict[self.essence_type] = self.essence_density
self.essence = Essence(essence_density_dict)
def get_region_type(self) -> str:
return "cultivate"
def __str__(self) -> str:
return f"修炼区域:{self.name}{self.essence_type}行灵气:{self.essence_density}- {self.desc}"
@dataclass
class CityRegion(Region):
"""
城市区域 - 不能修炼,但会有特殊操作
"""
def get_region_type(self) -> str:
return "city"
def __str__(self) -> str:
return f"城市区域:{self.name} - {self.desc}"
T = TypeVar('T', NormalRegion, CultivateRegion, CityRegion)
def _load_regions(region_type: Type[T], config_name: str) -> tuple[dict[int, T], dict[str, T]]:
"""
通用的区域加载函数
Args:
region_type: 区域类型 (NormalRegion, CultivateRegion, CityRegion)
config_name: 配置文件名 ("normal_region", "cultivate_region", "city_region")
Returns:
(按ID索引的字典, 按名称索引的字典)
"""
regions_by_id: dict[int, T] = {}
regions_by_name: dict[str, T] = {}
region_df = game_configs[config_name]
for _, row in region_df.iterrows():
# 构建基础参数
base_params = {
"id": int(row["id"]),
"name": str(row["name"]),
"desc": str(row["desc"]),
"shape": Shape.from_str(str(row["shape"])),
"north_west_cor": str(row["north-west-cor"]),
"south_east_cor": str(row["south-east-cor"])
}
# 如果是修炼区域,添加额外参数
if region_type == CultivateRegion:
base_params["essence_type"] = EssenceType.from_str(str(row["root_type"]))
base_params["essence_density"] = int(row["root_density"])
region = region_type(**base_params)
regions_by_id[region.id] = region
regions_by_name[region.name] = region
return regions_by_id, regions_by_name
def load_all_regions() -> tuple[
dict[int, Union[NormalRegion, CultivateRegion, CityRegion]],
dict[str, Union[NormalRegion, CultivateRegion, CityRegion]]
]:
"""
统一加载所有类型的区域数据
返回: (按ID索引的字典, 按名称索引的字典)
"""
all_regions_by_id: dict[int, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
all_regions_by_name: dict[str, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
# 加载普通区域
normal_by_id, normal_by_name = _load_regions(NormalRegion, "normal_region")
all_regions_by_id.update(normal_by_id)
all_regions_by_name.update(normal_by_name)
# 加载修炼区域
cultivate_by_id, cultivate_by_name = _load_regions(CultivateRegion, "cultivate_region")
all_regions_by_id.update(cultivate_by_id)
all_regions_by_name.update(cultivate_by_name)
# 加载城市区域
city_by_id, city_by_name = _load_regions(CityRegion, "city_region")
all_regions_by_id.update(city_by_id)
all_regions_by_name.update(city_by_name)
return all_regions_by_id, all_regions_by_name
# 从配表加载所有区域数据
regions_by_id, regions_by_name = load_all_regions()
# 分别加载各类型区域数据
normal_regions_by_id, normal_regions_by_name = _load_regions(NormalRegion, "normal_region")
cultivate_regions_by_id, cultivate_regions_by_name = _load_regions(CultivateRegion, "cultivate_region")
city_regions_by_id, city_regions_by_name = _load_regions(CityRegion, "city_region")