This commit is contained in:
bridge
2025-08-20 01:18:04 +08:00
parent b309b2749c
commit 7851cbba0d
14 changed files with 782 additions and 17 deletions

9
tests/conftest.py Normal file
View File

@@ -0,0 +1,9 @@
import os
import sys
# 将项目根目录加入 Python 路径,确保可以导入 `src` 包
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)

205
tests/run_front.py Normal file
View File

@@ -0,0 +1,205 @@
import os
import sys
import random
from typing import List, Tuple
# 将项目根目录加入 Python 路径,确保可以导入 `src` 包
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
# 依赖项目内部模块
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.avatar import Avatar, Gender
from src.classes.calendar import Month, Year
from src.classes.action import Move
def clamp(value: int, lo: int, hi: int) -> int:
return max(lo, min(hi, value))
def circle_points(cx: int, cy: int, r: int, width: int, height: int) -> List[Tuple[int, int]]:
pts: List[Tuple[int, int]] = []
r2 = r * r
for y in range(clamp(cy - r, 0, height - 1), clamp(cy + r, 0, height - 1) + 1):
for x in range(clamp(cx - r, 0, width - 1), clamp(cx + r, 0, width - 1) + 1):
if (x - cx) * (x - cx) + (y - cy) * (y - cy) <= r2:
pts.append((x, y))
return pts
def build_rich_random_map(width: int = 30, height: int = 20, *, seed: int | None = None) -> Map:
if seed is not None:
random.seed(seed)
game_map = Map(width=width, height=height)
# 1) 底色:平原
for y in range(height):
for x in range(width):
game_map.create_tile(x, y, TileType.PLAIN)
# 2) 西部大漠(左侧宽带),先铺设便于后续北/南带覆盖
desert_w = max(4, width // 5)
for y in range(height):
for x in range(0, desert_w):
game_map.create_tile(x, y, TileType.DESERT)
# 绿洲
for _ in range(random.randint(2, 3)):
cx = random.randint(1, max(1, desert_w - 1))
cy = random.randint(2, height - 3)
r = random.randint(1, 2)
for x, y in circle_points(cx, cy, r, width, height):
if x < desert_w:
game_map.create_tile(x, y, TileType.WATER)
# 3) 北部雪山与冰原(顶部宽带,覆盖整宽度)
north_band = max(3, height // 5)
for y in range(0, north_band):
for x in range(width):
game_map.create_tile(x, y, TileType.SNOW_MOUNTAIN)
# 局部冰川簇
for _ in range(random.randint(2, 3)):
cx = random.randint(1, width - 2)
cy = random.randint(0, north_band - 1)
r = random.randint(1, 2)
for x, y in circle_points(cx, cy, r, width, height):
if y < north_band:
game_map.create_tile(x, y, TileType.GLACIER)
# 4) 南部热带雨林(底部宽带,覆盖整宽度)
south_band = max(3, height // 5)
for y in range(height - south_band, height):
for x in range(width):
game_map.create_tile(x, y, TileType.RAINFOREST)
# 5) 最东海域(右侧宽带),最后铺海以覆盖前面的地形;随后在海中造岛
sea_band_w = max(3, width // 6)
sea_x0 = width - sea_band_w
for y in range(height):
for x in range(sea_x0, width):
game_map.create_tile(x, y, TileType.SEA)
# 岛屿:在海域内生成若干小岛(平原/森林)
for _ in range(random.randint(3, 5)):
cx = random.randint(sea_x0, width - 2)
cy = random.randint(1, height - 2)
r = random.randint(1, 2)
kind = random.choice([TileType.PLAIN, TileType.FOREST])
for x, y in circle_points(cx, cy, r, width, height):
if x >= sea_x0:
game_map.create_tile(x, y, kind)
# 6) 若干湖泊(水域圆斑,限制在中部非海域)
for _ in range(random.randint(3, 5)):
cx = random.randint(max(2, desert_w + 1), sea_x0 - 2)
cy = random.randint(north_band + 1, height - south_band - 2)
r = random.randint(1, 3)
for x, y in circle_points(cx, cy, r, width, height):
if x < sea_x0:
game_map.create_tile(x, y, TileType.WATER)
# 7) 中部山脉:几条短链(避开海域和上下带)
for _ in range(random.randint(2, 4)):
length = random.randint(6, 12)
x = random.randint(desert_w + 1, sea_x0 - 2)
y = random.randint(north_band + 1, height - south_band - 2)
dx, dy = random.choice([(1, 0), (1, 1), (1, -1)])
for _ in range(length):
if 0 <= x < sea_x0 and north_band <= y < height - south_band:
game_map.create_tile(x, y, TileType.MOUNTAIN)
x += dx
y += dy
# 8) 中部森林:几个圆斑
for _ in range(random.randint(4, 7)):
cx = random.randint(desert_w + 1, sea_x0 - 2)
cy = random.randint(north_band + 1, height - south_band - 2)
r = random.randint(2, 4)
for x, y in circle_points(cx, cy, r, width, height):
game_map.create_tile(x, y, TileType.FOREST)
# 9) 城市2~4个尽量落在非极端地形
cities = 0
attempts = 0
while cities < random.randint(2, 4) and attempts < 200:
attempts += 1
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
t = game_map.get_tile(x, y)
if t.type not in (TileType.WATER, TileType.SEA, TileType.MOUNTAIN, TileType.GLACIER, TileType.SNOW_MOUNTAIN, TileType.DESERT):
game_map.create_tile(x, y, TileType.CITY)
cities += 1
return game_map
def random_gender() -> Gender:
return Gender.MALE if random.random() < 0.5 else Gender.FEMALE
def make_avatars(world: World, count: int = 12) -> list[Avatar]:
avatars: list[Avatar] = []
width, height = world.map.width, world.map.height
for i in range(count):
name = f"NPC{i+1:03d}"
birth_year = Year(random.randint(1990, 2010))
birth_month = random.choice(list(Month))
age = random.randint(16, 60)
gender = random_gender()
# 找一个非海域的出生点
for _ in range(200):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
t = world.map.get_tile(x, y)
if t.type not in (TileType.WATER, TileType.SEA, TileType.MOUNTAIN):
break
else:
x, y = random.randint(0, width - 1), random.randint(0, height - 1)
avatar = Avatar(
world=world,
name=name,
id=i + 1,
birth_month=birth_month,
birth_year=birth_year,
age=age,
gender=gender,
pos_x=x,
pos_y=y,
)
avatar.tile = world.map.get_tile(x, y)
avatar.bind_action(Move)
avatars.append(avatar)
return avatars
def main():
# 为了每次更丰富,使用随机种子;如需复现可将 seed 固定
# random.seed(42)
width, height = 36, 24
game_map = build_rich_random_map(width=width, height=height)
world = World(map=game_map)
sim = Simulator()
sim.avatars.extend(make_avatars(world, count=14))
front = Front(
world=world,
simulator=sim,
tile_size=28,
margin=8,
step_interval_ms=350,
window_title="Cultivation World — Front Demo",
)
front.run()
if __name__ == "__main__":
main()

28
tests/test_basic.py Normal file
View File

@@ -0,0 +1,28 @@
from src.classes.avatar import Avatar, Gender
from src.classes.calendar import Month, Year
from src.classes.world import World
from src.classes.tile import Map, TileType
def test_basic():
"""
测试整个基础代码能不能run起来
"""
map = Map(width=2, height=2)
for x in range(2):
for y in range(2):
map.create_tile(x, y, TileType.PLAIN)
world = World(map=map)
avatar = Avatar(
world=world,
name="John Doe",
id=1,
birth_month=Month.JANUARY,
birth_year=Year(2000),
age=20,
gender=Gender.MALE
)

53
tests/test_simulator.py Normal file
View File

@@ -0,0 +1,53 @@
import random
from src.sim.simulator import Simulator
from src.classes.avatar import Avatar, Gender
from src.classes.calendar import Month, Year
from src.classes.world import World
from src.classes.tile import Map, TileType
from src.classes.action import Move
def test_simulator_step_moves_avatar_and_sets_tile():
# 固定随机种子,确保决定的移动是可预测的
random.seed(0)
# 构建 3x3 地图并填充地块
game_map = Map(width=3, height=3)
for x in range(3):
for y in range(3):
game_map.create_tile(x, y, TileType.PLAIN)
world = World(map=game_map)
# 将角色放在地图中心,避免越界
avatar = Avatar(
world=world,
name="Tester",
id=1,
birth_month=Month.JANUARY,
birth_year=Year(2000),
age=20,
gender=Gender.MALE,
pos_x=1,
pos_y=1,
)
# 绑定移动动作
avatar.bind_action(Move)
sim = Simulator()
sim.avatars.append(avatar)
# 执行一步模拟
sim.step()
# 断言位置在边界内
assert 0 <= avatar.pos_x < game_map.width
assert 0 <= avatar.pos_y < game_map.height
# 断言 tile 已正确设置且与位置一致
assert avatar.tile is not None
assert avatar.tile.x == avatar.pos_x
assert avatar.tile.y == avatar.pos_y