add map
This commit is contained in:
9
tests/conftest.py
Normal file
9
tests/conftest.py
Normal 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
205
tests/run_front.py
Normal 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
28
tests/test_basic.py
Normal 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
53
tests/test_simulator.py
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user