refactor relationship changes

This commit is contained in:
bridge
2025-11-26 15:06:41 +08:00
parent e8bf436797
commit 37b51b7650
13 changed files with 333 additions and 101 deletions

View File

@@ -90,9 +90,8 @@ class Battle(InstantAction):
# 生成战斗小故事
target = self._get_target(avatar_name)
start_text = self._start_event_content if hasattr(self, '_start_event_content') else result_event.content
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
# 战斗强制双人模式,允许改变关系
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT, allow_relation_changes=True)
story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True)
return [result_event, story_event]

View File

@@ -135,8 +135,7 @@ class Breakthrough(TimedAction):
# 故事参与者:本体 +(可选)相关角色
prompt = TribulationSelector.get_story_prompt(str(calamity))
story = await StoryTeller.tell_story(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt)
# 突破强制单人模式,不改变关系(因为没有双修/战斗那样的互动)
story = await StoryTeller.tell_story(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, self._calamity_other, prompt=prompt, allow_relation_changes=False)
events.append(Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True))
return events

View File

@@ -478,7 +478,8 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
base_event = Event(month_at_finish, event_text, related_avatars=related_avatars, is_major=True)
# 生成故事事件
story = await StoryTeller.tell_story(event_text, res_text, *actors_for_story, prompt=story_prompt)
# 奇遇强制单人模式,不改变关系(因为关系已经在硬逻辑中处理了)
story = await StoryTeller.tell_story(event_text, res_text, *actors_for_story, prompt=story_prompt, allow_relation_changes=False)
story_event = Event(month_at_finish, story, related_avatars=related_avatars, is_story=True)
# 返回基础事件和故事事件
@@ -488,5 +489,3 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
__all__ = [
"try_trigger_fortune",
]

View File

@@ -4,12 +4,9 @@ from pathlib import Path
from typing import TYPE_CHECKING
from .mutual_action import MutualAction
from src.classes.relation import relation_display_names, Relation
from src.classes.relations import (
get_possible_new_relations,
get_possible_cancel_relations,
set_relation,
cancel_relation,
process_relation_changes,
get_relation_change_context,
)
from src.classes.event import Event, NULL_EVENT
from src.utils.config import CONFIG
@@ -52,18 +49,15 @@ class Conversation(MutualAction):
avatar_name_2: target_avatar.get_info(detailed=True),
}
# 可能的后天关系(转中文名,给模板阅读)
# 注意:这里计算的是 target 相对于 avatar 的可能关系
possible_new_relations = [relation_display_names[r] for r in get_possible_new_relations(self.avatar, target_avatar)]
# 可能取消的关系
possible_cancel_relations = [relation_display_names[r] for r in get_possible_cancel_relations(target_avatar, self.avatar)]
# 获取关系上下文
possible_new_relations, possible_cancel_relations = get_relation_change_context(self.avatar, target_avatar)
return {
"avatar_infos": avatar_infos,
"avatar_name_1": avatar_name_1,
"avatar_name_2": avatar_name_2,
"possible_new_relations": possible_new_relations,
"possible_cancal_relations": possible_cancel_relations, # 保持模板中的拼写
"possible_cancel_relations": possible_cancel_relations,
}
def _can_start(self, target: "Avatar") -> tuple[bool, str]:
@@ -86,8 +80,6 @@ class Conversation(MutualAction):
Conversation 不需要反馈FEEDBACK_ACTIONS 为空),直接生成内容。
"""
conversation_content = str(result.get("conversation_content", "")).strip()
new_relation_str = str(result.get("new_relation", "")).strip()
cancel_relation_str = str(result.get("cancal_relation", "")).strip() # 保持模板中的拼写
# 使用开始时间戳
month_stamp = self._start_month_stamp if self._start_month_stamp is not None else self.world.month_stamp
@@ -101,32 +93,8 @@ class Conversation(MutualAction):
)
EventHelper.push_pair(content_event, initiator=self.avatar, target=target, to_sidebar_once=True)
# 处理进入新关系
if new_relation_str:
rel = Relation.from_chinese(new_relation_str)
if rel is not None:
set_relation(target, self.avatar, rel)
set_event = Event(
month_stamp,
f"{target.name}{self.avatar.name} 的关系变为:{relation_display_names.get(rel, str(rel))}",
related_avatars=[self.avatar.id, target.id],
is_major=True
)
EventHelper.push_pair(set_event, initiator=self.avatar, target=target, to_sidebar_once=True)
# 处理取消关系
if cancel_relation_str:
rel = Relation.from_chinese(cancel_relation_str)
if rel is not None:
success = cancel_relation(target, self.avatar, rel)
if success:
cancel_event = Event(
month_stamp,
f"{target.name}{self.avatar.name} 取消了关系:{relation_display_names.get(rel, str(rel))}",
related_avatars=[self.avatar.id, target.id],
is_major=True
)
EventHelper.push_pair(cancel_event, initiator=self.avatar, target=target, to_sidebar_once=True)
# 处理关系变化 (调用通用逻辑)
process_relation_changes(self.avatar, target, result, month_stamp)
return ActionResult(status=ActionStatus.COMPLETED, events=[])

View File

@@ -105,20 +105,15 @@ class DualCultivation(MutualAction):
if success:
gain = int(self._dual_exp_gain)
result_text = f"{self.avatar.name}{target.name} 成功双修,{self.avatar.name} 获得修为经验 +{gain}"
result_text = f"{self.avatar.name} 获得修为经验 +{gain}"
result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id], is_major=True)
events.append(result_event)
# 生成恋爱/双修小故事
start_text = self._start_event_content or result_event.content
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT)
# 双修强制双人模式,允许改变关系
story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT, allow_relation_changes=True)
story_event = Event(self.world.month_stamp, story, related_avatars=[self.avatar.id, target.id], is_story=True)
events.append(story_event)
else:
result_text = f"{target.name} 拒绝了与 {self.avatar.name} 的双修"
result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id], is_major=True)
events.append(result_event)
return events

View File

@@ -88,21 +88,14 @@ class GiftSpiritStone(MutualAction):
return events
if success:
result_text = f"{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}{self.avatar.name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT}{self.avatar.magic_stone}{target.name} 灵石:{target.magic_stone - self.GIFT_AMOUNT}{target.magic_stone}"
result_text = f"{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}{self.avatar.
name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT}{self.avatar.magic_stone}{target.
name} 灵石:{target.magic_stone - self.GIFT_AMOUNT}{target.magic_stone}"
result_event = Event(
self.world.month_stamp,
result_text,
related_avatars=[self.avatar.id, target.id]
)
events.append(result_event)
else:
result_text = f"{target.name} 婉拒了 {self.avatar.name} 的灵石赠送"
result_event = Event(
self.world.month_stamp,
result_text,
related_avatars=[self.avatar.id, target.id]
)
events.append(result_event)
return events

View File

@@ -100,15 +100,7 @@ class Impart(MutualAction):
if success:
gain = int(self._impart_exp_gain)
result_text = f"{self.avatar.name}{target.name} 传道,{target.name} 获得修为经验 +{gain}"
result_event = Event(
self.world.month_stamp,
result_text,
related_avatars=[self.avatar.id, target.id]
)
events.append(result_event)
else:
result_text = f"{target.name} 婉拒了 {self.avatar.name} 的传道"
result_text = f"{target.name} 获得修为经验 +{gain}"
result_event = Event(
self.world.month_stamp,
result_text,

View File

@@ -5,7 +5,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING, List
from src.classes.relation import Relation, INNATE_RELATIONS, get_reciprocal, is_innate
from src.classes.relation import Relation, INNATE_RELATIONS, get_reciprocal, is_innate, relation_display_names
from src.classes.event import Event
from src.classes.action.event_helper import EventHelper
if TYPE_CHECKING:
from src.classes.avatar import Avatar
@@ -124,3 +126,58 @@ def get_possible_cancel_relations(from_avatar: "Avatar", to_avatar: "Avatar") ->
return [existing]
def get_relation_change_context(avatar1: "Avatar", avatar2: "Avatar") -> tuple[list[str], list[str]]:
"""
获取两角色间可能的新增关系和取消关系的中文显示列表。
用于构建 Prompt 上下文。
返回:(possible_new_relations, possible_cancel_relations)
"""
# 计算 avatar2 相对于 avatar1 的可能关系
new_rels = get_possible_new_relations(avatar1, avatar2)
cancel_rels = get_possible_cancel_relations(avatar1, avatar2)
new_strs = [relation_display_names[r] for r in new_rels]
cancel_strs = [relation_display_names[r] for r in cancel_rels]
return new_strs, cancel_strs
def process_relation_changes(initiator: "Avatar", target: "Avatar", result_dict: dict, month_stamp: int) -> None:
"""
处理 LLM 返回的关系变更请求。
兼容 Conversation 和 StoryTeller 的通用逻辑。
"""
new_relation_str = str(result_dict.get("new_relation", "")).strip()
# 兼容模板中的拼写错误 (cancal -> cancel)
cancel_relation_str = str(result_dict.get("cancel_relation", "")).strip()
if not cancel_relation_str:
cancel_relation_str = str(result_dict.get("cancal_relation", "")).strip()
# 处理进入新关系
if new_relation_str:
rel = Relation.from_chinese(new_relation_str)
if rel is not None:
set_relation(target, initiator, rel)
set_event = Event(
month_stamp,
f"{target.name}{initiator.name} 的关系变为:{relation_display_names.get(rel, str(rel))}",
related_avatars=[initiator.id, target.id],
is_major=True
)
EventHelper.push_pair(set_event, initiator=initiator, target=target, to_sidebar_once=True)
# 处理取消关系
if cancel_relation_str:
rel = Relation.from_chinese(cancel_relation_str)
if rel is not None:
success = cancel_relation(target, initiator, rel)
if success:
cancel_event = Event(
month_stamp,
f"{target.name}{initiator.name} 取消了关系:{relation_display_names.get(rel, str(rel))}",
related_avatars=[initiator.id, target.id],
is_major=True
)
EventHelper.push_pair(cancel_event, initiator=initiator, target=target, to_sidebar_once=True)

View File

@@ -8,6 +8,10 @@ if TYPE_CHECKING:
from src.utils.config import CONFIG
from src.utils.llm import call_llm_with_template, LLMMode
from src.classes.relations import (
process_relation_changes,
get_relation_change_context
)
story_styles = [
"平淡叙述:语句克制、少修饰、像旁观者记录。",
@@ -31,9 +35,11 @@ story_styles = [
class StoryTeller:
"""
故事生成器:基于模板与 LLM将给定事件扩展为简短的小故事。
同时负责处理可能的后天关系变化。
"""
TEMPLATE_PATH = CONFIG.paths.templates / "story.txt"
TEMPLATE_SINGLE_PATH = CONFIG.paths.templates / "story_single.txt"
TEMPLATE_DUAL_PATH = CONFIG.paths.templates / "story_dual.txt"
@staticmethod
def _build_avatar_infos(*actors: "Avatar") -> Dict[str, dict]:
@@ -54,43 +60,80 @@ class StoryTeller:
return avatar_infos
@staticmethod
def _build_template_data(event: str, res: str, avatar_infos: Dict[str, dict], prompt: str) -> dict:
def _build_template_data(event: str, res: str, avatar_infos: Dict[str, dict], prompt: str, *actors: "Avatar") -> dict:
"""构建模板渲染所需的数据字典"""
# 默认空关系列表
possible_new_relations = []
possible_cancel_relations = []
avatar_name_1 = ""
avatar_name_2 = ""
# 如果有两个有效角色,计算可能的关系
non_null = [a for a in actors if a is not None]
if len(non_null) >= 2:
# 计算 actors[1] 相对于 actors[0] 的可能关系
possible_new_relations, possible_cancel_relations = get_relation_change_context(non_null[0], non_null[1])
avatar_name_1 = non_null[0].name
avatar_name_2 = non_null[1].name
return {
"avatar_infos": avatar_infos,
"avatar_name_1": avatar_name_1,
"avatar_name_2": avatar_name_2,
"event": event,
"res": res,
"style": random.choice(story_styles),
"story_prompt": prompt,
"possible_new_relations": possible_new_relations,
"possible_cancel_relations": possible_cancel_relations,
}
@staticmethod
def _make_fallback_story(event: str, res: str, style: str) -> str:
"""生成降级文案"""
return f"{event}{res}{style}"
# 不再显示 style避免出戏
return f"{event}{res}"
@staticmethod
async def tell_story(event: str, res: str, *actors: "Avatar", prompt: str = "") -> str:
async def tell_story(event: str, res: str, *actors: "Avatar", prompt: str = "", allow_relation_changes: bool = False) -> str:
"""
生成小故事(异步版本)。
基于 `static/templates/story.txt` 模板,失败时返回降级文案。
根据 allow_relation_changes 参数选择模板:
- True: 使用 story_dual.txt支持关系变化需要至少2个角色
- False: 使用 story_single.txt仅生成故事无论角色数量
Args:
event: 事件描述
res: 结果描述
*actors: 参与的角色1-2个
prompt: 可选的故事提示词
allow_relation_changes: 是否允许故事导致关系变化默认为False单人模式
"""
avatar_infos = StoryTeller._build_avatar_infos(*actors)
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt)
non_null = [a for a in actors if a is not None]
try:
data = await call_llm_with_template(StoryTeller.TEMPLATE_PATH, infos, LLMMode.FAST)
# 只有当允许关系变化且有至少2个角色时才使用双人模板
is_dual = allow_relation_changes and len(non_null) >= 2
template_path = StoryTeller.TEMPLATE_DUAL_PATH if is_dual else StoryTeller.TEMPLATE_SINGLE_PATH
avatar_infos = StoryTeller._build_avatar_infos(*actors)
infos = StoryTeller._build_template_data(event, res, avatar_infos, prompt, *actors)
# 移除了 try-except 块,允许异常向上冒泡,以便 Fail Fast
data = await call_llm_with_template(template_path, infos, LLMMode.FAST)
story = data.get("story", "").strip()
# 仅在双人模式下处理关系变化
if is_dual:
avatar_1 = non_null[0]
avatar_2 = non_null[1]
# 尝试获取 month_stamp
month_stamp = getattr(avatar_1.world, "month_stamp", 0)
process_relation_changes(avatar_1, avatar_2, data, month_stamp)
if story:
return story
except Exception:
pass
return StoryTeller._make_fallback_story(event, res, infos["style"])

View File

@@ -1,21 +1,21 @@
你是一个决策者这是一个仙侠世界你负责来生成两个NPC间的对话内容。
你是一个决策者这是一个仙侠世界你负责来生成两个NPC间的对话内容,并决定两人是否会有关系的变化
你需要进行决策的NPC的dict[AvatarName, info]为
{avatar_infos}
正在进行的动作为:{avatar_name_1}和{avatar_name_2}正在对话。这个对话可能是善意的也可能是恶意的也可能是闲聊。内容和性质取决于NPC特质性格、天赋等、正邪、关系等因素。
{avatar_name_1}和{avatar_name_2}正在对话。这个对话可能是善意的也可能是恶意的也可能是闲聊。内容和性质取决于NPC特质性格、天赋等、正邪、关系等因素。
两者可能进入的关系:{possible_new_relations}
两者可能取消的关系:{possible_cancal_relations}
两者可能取消的关系:{possible_cancel_relations}
注意:进入/取消关系不是必须的,完全由你根据对话情况、双方性格、历史事件等判断决定。
注意只返回json格式的结果。
格式为:
{{
"{avatar_name_2}": {{
"thinking": ..., // 简单思考对话的情况
"thinking": ..., // 简单思考对话如何进行
"conversation_content": ... // 对话双方均为第三人称视角的对话100~150字仙侠语言风格。可以是聊天也可以是对话概括。
"new_relation": ... // 如果你认为可以让两者产生某种身份关系则返回关系的中文名否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
"cancal_relation": ... // 可选如果你认为可以让两者取消某种身份关系则返回关系的中文名否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
"cancel_relation": ... // 可选如果你认为可以让两者取消某种身份关系则返回关系的中文名否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
}}
}}

View File

@@ -0,0 +1,25 @@
你是一个故事讲述者这是一个仙侠世界你需要把一个事件扩展为一个约200~250字的小故事并根据事件发展和双方性格决定两人是否会有关系的变化。
你需要进行决策的NPC的dict[AvatarName, info]为
{avatar_infos}
两者可能进入的关系:{possible_new_relations}
两者可能取消的关系:{possible_cancel_relations}
注意:进入/取消关系不是必须的,完全由你根据故事情况、双方性格、历史事件等判断决定。
写作风格提示:{style}
额外主题提示:{story_prompt}
发生的事件为:
{event}
结果为:
{res}
注意只返回json格式的结果格式为
{{
"thinking": ..., // 简单思考故事剧情和关系变化
"story": "", // 第三人称的故事正文,仙侠语言风格
"new_relation": ... // 如果你认为可以让两者产生某种身份关系则返回关系的中文名否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
"cancel_relation": ... // 可选如果你认为可以让两者取消某种身份关系则返回关系的中文名否则返回空str。注意这是{avatar_name_2}相对于{avatar_name_1}的身份。
}}

View File

@@ -1,10 +1,11 @@
你是一个故事讲述者这是一个仙侠世界你需要把一个事件扩展为一个约200~250字的小故事。
写作风格提示:{style}
额外主题提示:{story_prompt}
你需要进行决策的NPC的dict[AvatarName, info]为
{avatar_infos}
写作风格提示:{style}
额外主题提示:{story_prompt}
发生的事件为:
{event}
结果为:
@@ -12,5 +13,7 @@
注意只返回json格式的结果格式为
{{
"story": "", // 第三人称的故事正文,仙侠语言风格
"thinking": ..., // 简单思考故事剧情
"story": "" // 第三人称的故事正文,仙侠语言风格
}}

View File

@@ -0,0 +1,159 @@
# Tile Generation Prompts
# Recommended Settings:
# - Model: Nano Banana / Any Pixel Art Model
# - Resolution: 1024x1024 (Recommended to resize to target size like 96x96 using "Nearest Neighbor" algorithm)
# - Sampler: Euler a / DPM++ 2M Karras
# Common Prefix: Emphasize pixel style, RPG view, grid layout, white background
BASE_PROMPT = (
"pixel art style, 16-bit rpg game assets, top-down view, "
"flat shading, high quality, sharp details, "
"white background, 3x3 grid layout, sprite sheet, "
"consistent lighting, same color palette"
)
TILE_PROMPTS = {
# ==========================================
# Category 1: High Frequency Backgrounds
# Strategy: Emphasize seamlessness, subtle variations, unified tone, avoid obtrusive objects
# ==========================================
# Plain/Grassland
"plain": (
f"{BASE_PROMPT}, "
"9 variations of green grass ground tiles, simple meadow texture, "
"clearly different texture patterns between tiles, clean green lawn, "
"some tiles with small stone clusters, some with sparse weeds, some with slightly drier patches, "
"seamless pattern style, harmonious but not identical soothing green color"
),
# Desert
"desert": (
f"{BASE_PROMPT}, "
"9 variations of yellow sand ground tiles, desert floor texture, "
"smooth sand with clearly different wind ripple directions and density, "
"some tiles with small pebble clusters or tiny dried bushes, "
"arid atmosphere, golden yellow color with slightly varied brightness, "
"uniform texture"
),
# Water/Sea
"water": (
f"{BASE_PROMPT}, "
"9 variations of blue water surface tiles, lake texture, "
"calm liquid surface with clearly different ripple shapes and wave crest directions, "
"some tiles with gentle foam, some with brighter specular highlights, some almost flat, "
"no islands, no rocks, clear blue color, "
"consistent water pattern"
),
# Glacier/Snow
"glacier": (
f"{BASE_PROMPT}, "
"9 variations of white snow ground tiles, frozen ground texture, "
"clean white snow with clearly different accumulation shapes, "
"some tiles with clear shallow footprints, some with cracked ice patterns, subtle blueish tint, "
"soft smooth surface, no rocks, seamless winter field"
),
# Swamp
"swamp": (
f"{BASE_PROMPT}, "
"9 variations of murky swamp ground tiles, dark purple and green mud, "
"wet muddy ground with clearly different puddle shapes and distributions, "
"some tiles with many bubbles, some with floating leaves, some almost only dark mud, "
"toxic atmosphere, dark and gloomy color palette, flat texture"
),
# ==========================================
# Category 2: Obstacles & Terrain
# Strategy: Emphasize independent objects, centered, clear outlines, slight shape variations without changing style
# ==========================================
# Forest - Temperate
"forest": (
f"{BASE_PROMPT}, "
"9 variations of green oak trees, individual map trees, "
"isolated trees centered in grid cells, "
"strong differences in tree crown shape, height and density, same green foliage color, "
"some tiles with one big tree, some with two thinner trees, some with a lower bushy tree, "
"compact trees, game asset style, clear outline"
),
# Rainforest - Tropical
"rainforest": (
f"{BASE_PROMPT}, "
"9 variations of jungle palm trees and broadleaf trees, individual trees, "
"isolated trees centered in grid cells, "
"tropical dark green vibrant colors, strong variation in crown size, leaf shapes and trunk height, "
"some tiles with hanging vines, some with extra side leaves, some with double trunks, "
"rpg map obstacle, clear outline"
),
# Mountain - Rocky
"mountain": (
f"{BASE_PROMPT}, "
"9 variations of grey rocky mountains, small individual mountain peaks, "
"isolated peaks centered in grid cells, "
"clearly different peak shapes and slopes, some tall and thin, some low and wide, some double peaks, "
"grey stone texture with moss hints, similar overall height, clear outline"
),
# Snow Mountain
"snow_mountain": (
f"{BASE_PROMPT}, "
"9 variations of snow-capped mountains, individual snowy peaks, "
"isolated peaks centered in grid cells, "
"different amounts of snow and rock exposed, some with wide snow caps, some with more rock showing, "
"white snow on top, grey rock at bottom, "
"compact shape, cold atmosphere, clear outline"
),
# Volcano
"volcano": (
f"{BASE_PROMPT}, "
"9 variations of active volcanoes, small individual volcano peaks, "
"isolated peaks centered in grid cells, "
"different crater openings and lava glow intensity, some narrow and tall, some wide and flat, "
"some tiles with heavy smoke plumes, some with side lava cracks, "
"dark rock with magma crater on top, dangerous red and black colors, clear outline"
),
# City - Icon for world map
"city": (
f"{BASE_PROMPT}, "
"9 variations of small ancient chinese houses, map icon style, "
"isolated buildings centered in grid cells, "
"clearly different roof shapes and small courtyard layouts, some with one house, some with two connected houses, "
"grey tiled roofs, white walls, asian architecture, "
"compact simple structure, similar size"
),
# Sect - More magnificent than cities
"sect": (
f"{BASE_PROMPT}, "
"9 variations of mystical chinese fantasy pavilions, map icon style, "
"isolated floating palaces or pagodas centered in grid cells, "
"strong variation in roof layers and tower heights, some with tall spires, some with wide halls, some with side towers, "
"elegant blue and gold roofs, magical aura, "
"ethereal atmosphere, clear outline"
),
# Ruins
"ruins": (
f"{BASE_PROMPT}, "
"9 variations of ancient stone ruins, broken pillars and walls, "
"isolated debris centered in grid cells, "
"clearly different collapse shapes and arrangements, some with standing half pillars, some mostly rubble, some with broken arches, "
"weathered grey stone, mossy and cracked, "
"mysterious atmosphere, clear outline"
)
}
if __name__ == "__main__":
print("=== Generated Prompts ===")
print(f"Base Prompt: {BASE_PROMPT}\n")
for key, prompt in TILE_PROMPTS.items():
print(f"--- {key.upper()} ---")
print(prompt)
print()