add calendar and region

This commit is contained in:
bridge
2025-08-20 22:51:42 +08:00
parent 7851cbba0d
commit 658f2aefdb
5 changed files with 226 additions and 20 deletions

View File

@@ -3,15 +3,25 @@ from dataclasses import dataclass
class Month(Enum):
JANUARY = "January"
FEBRUARY = "February"
MARCH = "March"
APRIL = "April"
MAY = "May"
JUNE = "June"
JULY = "July"
AUGUST = "August"
SEPTEMBER = "September"
JANUARY = 1
FEBRUARY = 2
MARCH = 3
APRIL = 4
MAY = 5
JUNE = 6
JULY = 7
AUGUST = 8
SEPTEMBER = 9
OCTOBER = 10
NOVEMBER = 11
DECEMBER = 12
class Year(int):
pass
def __add__(self, other: int) -> 'Year':
return Year(int(self) + other)
def next_month(month: Month, year: Year) -> tuple[Month, Year]:
if month == Month.DECEMBER:
return Month.JANUARY, year + 1
else:
return Month(month.value + 1), year

View File

@@ -1,5 +1,6 @@
from enum import Enum
from dataclasses import dataclass
from dataclasses import dataclass, field
import itertools
class TileType(Enum):
PLAIN = "plain" # 平原
@@ -13,6 +14,9 @@ class TileType(Enum):
GLACIER = "glacier" # 冰川/冰原
SNOW_MOUNTAIN = "snow_mountain" # 雪山
region_id_counter = itertools.count(1)
@dataclass
class Region():
"""
@@ -26,6 +30,18 @@ class Region():
name: str
description: str
qi: int # 灵气从0~255
id: int = field(init=False)
def __post_init__(self):
self.id = next(region_id_counter)
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
# 物产
# 灵气
# 其他
@@ -36,12 +52,11 @@ class Tile():
type: TileType
x: int
y: int
# region: Region
region: Region | None = None # 可以是一个region的一部分也可以不属于任何region
class Map():
"""
通过dict记录position 到 tile。
TODO: 记录region到position的映射。
TODO: 有特色的地貌,比如西部大漠,东部平原,最东海洋和岛国。南边热带雨林,北边雪山和冰原。
"""
def __init__(self, width: int, height: int):
@@ -59,4 +74,19 @@ class Map():
self.tiles[(x, y)] = Tile(tile_type, x, y)
def get_tile(self, x: int, y: int) -> Tile:
return self.tiles[(x, y)]
return self.tiles[(x, y)]
def create_region(self, name: str, description: str, qi: int, locs: list[tuple[int, int]]):
"""
创建一个region。
"""
region = Region(name=name, description=description, qi=qi)
for loc in locs:
self.tiles[loc].region = region
return region
def get_region(self, x: int, y: int) -> Region | None:
"""
获取一个region。
"""
return self.tiles[(x, y)].region

View File

@@ -61,6 +61,8 @@ class Front:
# 字体(优先中文友好字体;可显式传入 TTF 路径)
self.font = self._create_font(16)
self.tooltip_font = self._create_font(14)
# 区域名字体缓存:按需动态放大(随区域面积和格子大小自适应)
self._region_font_cache: Dict[int, object] = {}
# 配色
self.colors: Dict[str, Tuple[int, int, int]] = {
@@ -123,6 +125,7 @@ class Front:
self.screen.fill(self.colors["bg"])
self._draw_map()
self._draw_region_labels()
hovered = self._draw_avatars_and_pick_hover()
if hovered is not None:
self._draw_tooltip_for_avatar(hovered)
@@ -132,6 +135,16 @@ class Front:
text_surf = self.font.render(hint, True, self.colors["text"])
self.screen.blit(text_surf, (self.margin, 4))
# 年月右上角显示YYYY年MM月
try:
month_num = list(type(self.simulator.month)).index(self.simulator.month) + 1
except Exception:
month_num = 1
ym_text = f"{int(self.simulator.year)}{month_num:02d}"
ym_surf = self.font.render(ym_text, True, self.colors["text"])
screen_w, _ = self.screen.get_size()
self.screen.blit(ym_surf, (screen_w - self.margin - ym_surf.get_width(), 4))
pygame.display.flip()
def _draw_map(self):
@@ -159,6 +172,66 @@ class Front:
end_pos = (m + map_obj.width * ts, m + gy * ts)
pygame.draw.line(self.screen, grid_color, start_pos, end_pos, 1)
def _draw_region_labels(self):
pygame = self.pygame
map_obj = self.world.map
ts = self.tile_size
m = self.margin
# 聚合每个 region 的所有地块中心点Region 以自身 id 为哈希键
region_to_points: Dict[object, List[Tuple[int, int]]] = {}
# 直接遍历底层 tiles 字典更高效
for (x, y), tile in getattr(map_obj, "tiles", {}).items():
if getattr(tile, "region", None) is None:
continue
region_obj = tile.region
cx = m + x * ts + ts // 2
cy = m + y * ts + ts // 2
region_to_points.setdefault(region_obj, []).append((cx, cy))
if not region_to_points:
return
for region, points in region_to_points.items():
if not points:
continue
# 计算质心
avg_x = sum(p[0] for p in points) // len(points)
avg_y = sum(p[1] for p in points) // len(points)
name = getattr(region, "name", None)
if not name:
continue
# 按区域大小与格子尺寸决定字体大小
area = len(points)
base = int(self.tile_size * 1.1)
growth = int(max(0, min(24, (area ** 0.5))))
font_size = max(16, min(40, base + growth))
region_font = self._get_region_font(font_size)
# 渲染带轻微阴影的文字
text_surface = region_font.render(str(name), True, self.colors["text"]) # 主文字
shadow_surface = region_font.render(str(name), True, (0, 0, 0)) # 阴影
text_w = text_surface.get_width()
text_h = text_surface.get_height()
x = int(avg_x - text_w / 2)
y = int(avg_y - text_h / 2)
# 先画阴影,略微偏移
self.screen.blit(shadow_surface, (x + 1, y + 1))
# 再画主文字
self.screen.blit(text_surface, (x, y))
def _get_region_font(self, size: int):
# 缓存不同大小的字体,避免每帧重复创建
f = self._region_font_cache.get(size)
if f is None:
f = self._create_font(size)
self._region_font_cache[size] = f
return f
def _draw_avatars_and_pick_hover(self) -> Optional[Avatar]:
pygame = self.pygame
mouse_x, mouse_y = pygame.mouse.get_pos()

View File

@@ -1,8 +1,10 @@
from src.classes.calendar import Month, Year, next_month
class Simulator:
def __init__(self):
self.avatars = [] # list[Avatar]
self.year = Year(1)
self.month = Month.JANUARY
def step(self):
"""
@@ -14,4 +16,7 @@ class Simulator:
"""
# 结算角色行为
for avatar in self.avatars:
avatar.act()
avatar.act()
# 最后结算年月
self.month, self.year = next_month(self.month, self.year)