update city

This commit is contained in:
bridge
2025-09-12 23:02:41 +08:00
parent e2f7afd6e3
commit 7933f20614
10 changed files with 201 additions and 78 deletions

3
.gitignore vendored
View File

@@ -146,4 +146,5 @@ logs/
TODO
local_config.yml
台本/
台本/
笔记/

View File

@@ -7,9 +7,10 @@ import inspect
from src.classes.essence import Essence, EssenceType
from src.classes.root import Root, corres_essence_type
from src.classes.region import Region, CultivateRegion, NormalRegion
from src.classes.region import Region, CultivateRegion, NormalRegion, CityRegion
from src.classes.event import Event, NULL_EVENT
from src.classes.item import Item
from src.classes.item import Item, items_by_name
from src.classes.prices import prices
if TYPE_CHECKING:
from src.classes.avatar import Avatar
@@ -431,7 +432,52 @@ class Harvest(DefineAction, ActualActionMixin):
return False
ALL_ACTION_CLASSES = [Move, Cultivate, Breakthrough, MoveToRegion, Play, Hunt, Harvest]
ALL_ACTUAL_ACTION_CLASSES = [Cultivate, Breakthrough, MoveToRegion, Play, Hunt, Harvest]
ALL_ACTION_NAMES = ["Move", "Cultivate", "Breakthrough", "MoveToRegion", "Play", "Hunt", "Harvest"]
ALL_ACTUAL_ACTION_NAMES = ["Cultivate", "Breakthrough", "MoveToRegion", "Play", "Hunt", "Harvest"]
@long_action(step_month=1)
class Sold(DefineAction, ActualActionMixin):
"""
在城镇出售指定名称的物品,一次性卖出持有的全部数量。
收益为 item_price * item_num动作耗时1个月。
"""
COMMENT = "在城镇出售持有的某类物品的全部"
PARAMS = {"item_name": "str"}
def _execute(self, item_name: str) -> None:
region = self.avatar.tile.region
if not isinstance(region, CityRegion):
return
# 找到物品
item = items_by_name.get(item_name)
if item is None:
return
# 检查持有数量
quantity = self.avatar.get_item_quantity(item)
if quantity <= 0:
return
# 计算价格并结算
price_per = prices.get_price(item)
total_gain = price_per * quantity
# 扣除物品并增加灵石
removed = self.avatar.remove_item(item, quantity)
if not removed:
return
self.avatar.magic_stone = self.avatar.magic_stone + total_gain
def get_event(self, item_name: str) -> Event:
return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇出售 {item_name}")
@property
def is_doable(self) -> bool:
# 只允许在城镇且背包非空时出现在动作空间
region = self.avatar.tile.region
return isinstance(region, CityRegion) and bool(self.avatar.items)
ALL_ACTION_CLASSES = [Move, Cultivate, Breakthrough, MoveToRegion, Play, Hunt, Harvest, Sold]
ALL_ACTUAL_ACTION_CLASSES = [Cultivate, Breakthrough, MoveToRegion, Play, Hunt, Harvest, Sold]
ALL_ACTION_NAMES = ["Move", "Cultivate", "Breakthrough", "MoveToRegion", "Play", "Hunt", "Harvest", "Sold"]
ALL_ACTUAL_ACTION_NAMES = ["Cultivate", "Breakthrough", "MoveToRegion", "Play", "Hunt", "Harvest", "Sold"]

View File

@@ -113,7 +113,7 @@ class LLMAI(AI):
"""
异步决策逻辑通过LLM决定执行什么动作和参数
"""
global_info = world.get_prompt()
global_info = world.get_info()
avatar_infos = {avatar.id: avatar.get_prompt() for avatar in avatars_to_decide}
info = {
"avatar_infos": avatar_infos,

View File

@@ -1,5 +1,7 @@
from typing import Union
class MagicStone(int):
"""
灵石实际上是一个int类代表持有的下品灵石的数量。
@@ -15,4 +17,14 @@ class MagicStone(int):
def __str__(self) -> str:
_upper, _middle, _value = self.exchange()
return f"上品灵石:{_upper},中品灵石:{_middle},下品灵石:{_value}"
return f"上品灵石:{_upper},中品灵石:{_middle},下品灵石:{_value}"
def __add__(self, other: Union['MagicStone', int]) -> 'MagicStone':
if isinstance(other, int):
return MagicStone(self.value + other)
return MagicStone(self.value + other.value)
def __sub__(self, other: Union['MagicStone', int]) -> 'MagicStone':
if isinstance(other, int):
return MagicStone(self.value - other)
return MagicStone(self.value - other.value)

100
src/classes/map.py Normal file
View File

@@ -0,0 +1,100 @@
from typing import TYPE_CHECKING, Optional
from src.classes.tile import Tile, TileType
if TYPE_CHECKING:
from src.classes.region import Region
class Map():
"""
通过dict记录position 到 tile。
"""
def __init__(self, width: int, height: int):
self.tiles = {}
self.width = width
self.height = height
# 加载所有region数据到Map中
self._load_regions()
def _load_regions(self):
"""从配置文件加载所有区域数据到Map实例中"""
# 延迟导入避免循环导入
from src.classes.region import regions_by_id, regions_by_name
from src.classes.region import normal_regions_by_id, normal_regions_by_name
from src.classes.region import cultivate_regions_by_id, cultivate_regions_by_name
from src.classes.region import city_regions_by_id, city_regions_by_name
self.regions = regions_by_id
self.region_names = regions_by_name
self.normal_regions = normal_regions_by_id
self.normal_region_names = normal_regions_by_name
self.cultivate_regions = cultivate_regions_by_id
self.cultivate_region_names = cultivate_regions_by_name
self.city_regions = city_regions_by_id
self.city_region_names = city_regions_by_name
def is_in_bounds(self, x: int, y: int) -> bool:
"""
判断坐标是否在地图边界内。
"""
return 0 <= x < self.width and 0 <= y < self.height
def create_tile(self, x: int, y: int, tile_type: TileType):
self.tiles[(x, y)] = Tile(tile_type, x, y, region=None)
def get_tile(self, x: int, y: int) -> Tile:
return self.tiles[(x, y)]
def get_center_locs(self, locs: list[tuple[int, int]]) -> tuple[int, int]:
"""
获取locs的中心位置。
如果几何中心恰好在位置列表中,返回几何中心;
否则返回距离几何中心最近的实际位置。
"""
if not locs:
return (0, 0)
# 分别计算x和y坐标的平均值
avg_x = sum(loc[0] for loc in locs) // len(locs)
avg_y = sum(loc[1] for loc in locs) // len(locs)
center = (avg_x, avg_y)
# 如果几何中心恰好在位置列表中,直接返回
if center in locs:
return center
# 否则找到距离几何中心最近的实际位置
def distance_squared(loc: tuple[int, int]) -> int:
"""计算到中心点的距离平方(避免开方运算)"""
return (loc[0] - avg_x) ** 2 + (loc[1] - avg_y) ** 2
return min(locs, key=distance_squared)
def get_region(self, x: int, y: int) -> Optional['Region']:
"""
获取一个region。
"""
return self.tiles[(x, y)].region
def get_info(self) -> str:
"""返回地图的简要信息,按类型分组罗列区域并说明用途。"""
parts: list[str] = []
# 修炼区域
parts.append("修炼区域(可以修炼以增进修为):")
parts.extend([f"- {str(region)}" for region in self.cultivate_regions.values()])
parts.append("")
# 普通区域
parts.append("普通区域(可以狩猎或采集):")
parts.extend([f"- {str(region)}" for region in self.normal_regions.values()])
parts.append("")
# 城市区域
parts.append("城市区域(可以交易):")
parts.extend([f"- {str(region)}" for region in self.city_regions.values()])
return "\n".join(parts)

23
src/classes/prices.py Normal file
View File

@@ -0,0 +1,23 @@
from src.classes.cultivation import Realm
from src.classes.item import Item
class Prices:
"""
价格体系。
刚开始我只准备做一个比较简单的价格体系,之后可能复杂化。
目前是所有的城镇都可以出售材料,同时这些材料的价格是固定的,并且全局公开。
价格只和对应的realm绑定。
"""
def __init__(self):
self.realm_to_prices = {
Realm.Qi_Refinement: 10,
Realm.Foundation_Establishment: 50,
Realm.Core_Formation: 100,
Realm.Nascent_Soul: 200,
}
def get_price(self, item: Item) -> int:
return self.realm_to_prices[item.realm]
# 预先创建全局价格实例,供全局使用
prices = Prices()

View File

@@ -32,64 +32,3 @@ class Tile():
y: int
region: 'Region' = None # 可以是一个region的一部分也可以不属于任何region
class Map():
"""
通过dict记录position 到 tile。
"""
def __init__(self, width: int, height: int):
self.tiles = {}
self.width = width
self.height = height
# 加载所有region数据到Map中
self._load_regions()
def _load_regions(self):
"""从配置文件加载所有区域数据到Map实例中"""
# 延迟导入避免循环导入
from src.classes.region import load_all_regions
self.regions, self.region_names = load_all_regions()
def is_in_bounds(self, x: int, y: int) -> bool:
"""
判断坐标是否在地图边界内。
"""
return 0 <= x < self.width and 0 <= y < self.height
def create_tile(self, x: int, y: int, tile_type: TileType):
self.tiles[(x, y)] = Tile(tile_type, x, y, region=None)
def get_tile(self, x: int, y: int) -> Tile:
return self.tiles[(x, y)]
def get_center_locs(self, locs: list[tuple[int, int]]) -> tuple[int, int]:
"""
获取locs的中心位置。
如果几何中心恰好在位置列表中,返回几何中心;
否则返回距离几何中心最近的实际位置。
"""
if not locs:
return (0, 0)
# 分别计算x和y坐标的平均值
avg_x = sum(loc[0] for loc in locs) // len(locs)
avg_y = sum(loc[1] for loc in locs) // len(locs)
center = (avg_x, avg_y)
# 如果几何中心恰好在位置列表中,直接返回
if center in locs:
return center
# 否则找到距离几何中心最近的实际位置
def distance_squared(loc: tuple[int, int]) -> int:
"""计算到中心点的距离平方(避免开方运算)"""
return (loc[0] - avg_x) ** 2 + (loc[1] - avg_y) ** 2
return min(locs, key=distance_squared)
def get_region(self, x: int, y: int) -> 'Region | None':
"""
获取一个region。
"""
return self.tiles[(x, y)].region

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from src.classes.tile import Map
from src.classes.map import Map
from src.classes.calendar import Year, Month, MonthStamp
@dataclass
@@ -8,6 +8,8 @@ class World():
map: Map
month_stamp: MonthStamp
def get_prompt(self) -> str:
regions_str = "\n".join([str(region) for region in self.map.regions.values()])
return f"世界地图上存在的区域为:{regions_str}"
def get_info(self) -> str:
map_intro = "世界上的区域为:"
map_info = self.map.get_info()
info = f"{map_intro}\n{map_info}"
return info

View File

@@ -1,4 +1,5 @@
from src.classes.tile import Map, TileType
from src.classes.map import Map
from src.classes.tile import TileType
from src.classes.essence import Essence, EssenceType
def create_cultivation_world_map() -> Map:

View File

@@ -11,11 +11,10 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from src.front.front import Front
from src.sim.simulator import Simulator
from src.classes.world import World
from src.classes.tile import Map, TileType
from src.classes.map import Map
from src.classes.tile import TileType
from src.classes.avatar import Avatar, Gender
from src.classes.calendar import Month, Year, MonthStamp, create_month_stamp
from src.classes.action import Move
from src.classes.essence import Essence, EssenceType
from src.classes.cultivation import CultivationProgress
from src.classes.root import Root
from src.classes.age import Age