In each round of the game "Fentasy Realm," after a player makes a choice, two things need to be generated:
- Narrative Outcome: A description of the immediate consequences of the choice, enhancing immersion.
- Card Information: If a card is obtained (determined by the card acquisition algorithm), generate a card name and a brief description for collection and boss battles.
You need to implement a function generate\_option\_outcome that, based on the current scene description, the player's chosen option text, the plyaer's moral alignment (optional), and the card type (good, evil, neutral, or None if no card is obtained), uses a large language model (Deepseek) to generate the corresponding outcome text and card information.
Module Location and Dependencies
- File Path:
src/game\_logic/option\_outcome\_generator.py - Dependencies:
llm\_hint: Reusellmobject andcheck\_api\_key.langchain\_core.prompts: For building prompt templates.- Standard libraries:
logging,json,re,os, etc.
- Reusable Resources: Can reuse
\_parse\_llm\_responseandPROJECT\_ROOTfromscene\_generator.py.
Function Design
3.1 Function Signature
import logging
import os
import json
import re
from typing import Optional, Dict, Any
from langchain_core.prompts import ChatPromptTemplate
from .config import config
from .llm_hint import llm, check_api_key
logger = logging.getLogger(__name__)
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
TEMPLATE_PATH = os.path.join(PROJECT_ROOT, "prompts", "option_outcome_prompt.txt")
# Load template when the module is loaded
try:
with open(TEMPLATE_PATH, "r", encoding="utf-8") as f:
template_str = f.read()
logger.info(f"Option outcome template loaded: {TEMPLATE_PATH}")
except FileNotFoundError:
logger.error(f"Option outcome template file not found: {TEMPLATE_PATH}")
template_str = None
def generate_option_outcome(
scene_description: str,
option_text: str,
morality_ratios: Optional[Dict[str, float]] = None,
card_type: Optional[str] = None # 'good', 'evil', 'neutral' or None (no card)
) -> Dict[str, Any]:
"""
Generate narrative outcome and card information.
Parameters:
scene_description: Current round's scene description text
option_text: Player's chosen option text
morality_ratios: Player's moral ratios, e.g., {'good':0.3, 'neutral':0.2, 'evil':0.5}, optional
card_type: Type of card obtained, 'good', 'evil', 'neutral' or None (no card)
Returns:
Dictionary containing:
{
"outcome": str, # Narrative outcome text
"card_name": Optional[str], # Card name if card_type is not None; otherwise None
"card_description": Optional[str] # Card description if card_type is not None; otherwise None
}
If generation fails, returns a dictionary with default values.
"""
# Implementation details follow
3.2 Return Value Description
- outcome: Required, string, describes the immediate result of the choice, approximately 30-100 characters.
- card_name: Must be provided if
card\_typeis not None; otherwise None. - card_description: Must be provided if
card\_typeis not None; otherwise None.
Prompt Template (option_outcome_prompt.txt)
Save the following content as prompts/option\_outcome\_prompt.txt:
You are a master of narrative in the world of cultivation. Based on the given scene, player's choice, and moral alignment, generate a narrative outcome and possibly a card name and description.
Scene Description: {scene_description}
Player Choice: {option_text}
Player Moral Alignment: Good {good_ratio:.0%}, Neutral {neutral_ratio:.0%}, Evil {evil_ratio:.0%}
Card Type Obtained: {card_type_text}
Please generate a JSON object in the following format:
{
"outcome": "Narrative outcome, about 30-100 characters, describing the immediate result of the choice.",
"card_name": "If a card is obtained, give it a 2-5 character name, e.g., 'Rescue Heart'; otherwise null",
"card_description": "If a card is obtained, write a 10-20 character description of its origin; otherwise null"
}
Requirements:
1. The narrative outcome should be vivid and natural, fitting the atmosphere of the cultivation world, closely related to the scene and choice.
2. If a card is obtained, the card name should have a cultivation flavor and be relevant to the event; the description should be concise and elegant.
3. If no card is obtained (card_type is "None"), then card_name and card_description must be null.
4. Moral alignment can be used as a reference to adjust the tone (e.g., more compassionate if good ratio is high, more ruthless if evil ratio is high), but do not force it.
5. Output the JSON directly without additional content.
Note: The {card\_type\_text} in the template needs to be generated in the code based on card\_type, for example:
- If
card\_typeis'good', thencard\_type\_text = 'Good' - If
card\_typeis'evil', thencard\_type\_text = 'Evil' - If
card\_typeis'neutral', thencard\_type\_text = 'Neutral' - If
card\_typeisNone, thencard\_type\_text = 'None'
Default Values Design
When the LLM call fails or parsing errors occur, return default results to ensure the game flow is not affected. Default values should be generated based on card\_type, but for simplicity, a set of default values can be used, ensuring the fields are correct.
DEFAULT_OUTCOME = "Your choice has caused a ripple in the realm."
DEFAULT_CARD_NAMES = {
'good': 'Good Fate',
'evil': 'Evil Consequence',
'neutral': 'Karma'
}
DEFAULT_CARD_DESCRIPTIONS = {
'good': 'A mark left by a good deed.',
'evil': 'The karmic result of an evil act.',
'neutral': 'A witness to a cautious observation.'
}
Fill in the corresponding default values based on card\_type when returning.
Implementation Steps
-
Check Template: If
template\_stris None, log an error and return default results. -
Check LLM and API Key: If it fails, return default results.
-
Prepare Data: ```
good_ratio = morality_ratios.get('good', 0.0) if morality_ratios else 0.0 neutral_ratio = morality_ratios.get('neutral', 0.0) if morality_ratios else 0.0 evil_ratio = morality_ratios.get('evil', 0.0) if morality_ratios else 0.0 if card_type == 'good': card_type_text = 'Good' elif card_type == 'evil': card_type_text = 'Evil' elif card_type == 'neutral': card_type_text = 'Neutral' else: card_type_text = 'None' -
Format Prompt: ```
prompt = ChatPromptTemplate.from_template(template_str) formatted_prompt = prompt.format( scene_description=scene_description, option_text=option_text, good_ratio=good_ratio, neutral_ratio=neutral_ratio, evil_ratio=evil_ratio, card_type_text=card_type_text ) -
Call LLM: Use
llm.invoke(formatted\_prompt), retry up to 2 times. -
Parse Response: Reuse
parse\_llm\_response(can be copied or imported fromlayer\_event\_generator), remove markdown code blocks, and parse JSON. -
Validate Data:
- Ensure the returned dictionary contains the
outcomefield. - If
card\_typeis not None,card\_nameandcard\_descriptioncannot be None. - If
card\_typeis None,card\_nameandcard\_descriptionshould be None. - If validation fails, log a warning and return default values.
- Ensure the returned dictionary contains the
-
Return Result.
Testing Suggestions
Create a unit test file tests/test\_option\_outcome\_generator.py covering at least:
- Successful Generation: Mock LLM to return valid JSON, verify the structure is correct, and card fields match
card\_type. - No Card Case:
card\_type=None, verifycard\_nameandcard\_descriptionare None. - Parsing Error: Simulate LLM returning invalid JSON, verify default values are returned.
- Missing Fields: Simulate LLM returning JSON with missing fields, verify default values are returned.
- Moral Ratios Passed: Verify the prompt includes the correct ratios.
- Template Missing: Simulate the template file not existing, verify default values are returned.
Submission Requirements
- Create
src/game\_logic/option\_outcome\_generator.pywith the above implementation. - Create
prompts/option\_outcome\_prompt.txtwith the prompt content. - Create
tests/test\_option\_outcome\_generator.pywith unit tests. - Code should be well-commented, with clear explanations for key logic.
- Ensure all tests pass.
Start implementing! Feel free to reach out if you have any questions.