Buy Me a Coffee at ko-fi.com

WE0001-1119-2024 - Game Development Plan

Initial Setup

Game Core Systems Development

Preview: Building a 2D action-adventure game with room-based exploration

Technical Requirements Development

WE0001-1119-2024 - Iteration 1

  • State Management System implemented
  • Basic room generation working
  • Player movement implemented
  • Basic UI elements present

Overview

Building a Zelda-style action-adventure game with:

  • Room-based exploration
  • Combat
  • Inventory system
  • Multiple game states

Core Systems (โœ… Have, โŒ Missing)

  1. Core Game Loop
  • โœ… Game initialization
  • โœ… Basic game loop
  • โœ… Frame rate control
  • โœ… Input handling
  1. Player Systems
  • โœ… Basic movement
  • โœ… Room transitions
  • โŒ Player stats (health, etc.)
  • โŒ Player collision with enemies
  • โŒ Player attack system
  1. Room/Level Systems
  • โœ… Room generation
  • โœ… Room transitions
  • โœ… Door system
  • โœ… Basic tile system
  • โŒ Room hazards/obstacles
  • โŒ Interactive objects
  1. UI Systems
  • โœ… Basic HUD
  • โœ… Minimap
  • โœ… Inventory toggle
  • โŒ Health display
  • โŒ Menu system
  • โŒ Dialog system
  1. Game States
  • โœ… State management
  • โœ… Basic state transitions
  • โŒ Pause state
  • โŒ Game Over state
  • โŒ Victory state
  1. Items/Inventory
  • โœ… Basic item system
  • โœ… Inventory container
  • โŒ Item usage
  • โŒ Item effects
  • โŒ Collectibles
  1. Combat/Interaction
  • โŒ Basic combat system
  • โŒ Enemy AI
  • โŒ Damage system
  • โŒ Combat feedback
  • โŒ Death/respawn
  1. Audio
  • โŒ Sound effects
  • โŒ Background music
  • โŒ Audio manager
  • โŒ Volume control

This plan shows whatโ€™s already implemented (โœ…) and what still needs to be built (โŒ), providing a clear roadmap for development.

Incorporating AI-generated characters with a Retrieval-Augmented Generation (RAG) system and a dynamic knowledge management framework will significantly enhance the Zelda-style Pygame project. This integration will allow for real-time, evolving narratives and lifelike interactions with NPCs. Below is a comprehensive guide to implementing this advanced system, ensuring it remains congruent, efficient, and maintainable.


1. Overview of the Enhanced System

Core Components

  1. Local AI Models: Utilize lightweight, locally-hosted AI models (e.g., GPT-J, GPT-Neo) to generate dynamic NPC dialogues and behaviors.
  2. Retrieval-Augmented Generation (RAG): Enhance AI responses by retrieving relevant context from the gameโ€™s event log.
  3. Knowledge Management System: Maintain concise, updated summaries of each NPCโ€™s knowledge to guide AI-generated interactions.
  4. StoryLog Integration: Seamlessly interface the AI system with the existing StoryLog for coherent event tracking and retrieval.

Workflow

  1. Event Occurs: An in-game event is logged in the StoryLog.
  2. Knowledge Update: Relevant NPCsโ€™ knowledge summaries are updated based on the event.
  3. NPC Interaction: When the player interacts with an NPC, the system retrieves pertinent events from the StoryLog and feeds them into the AI model alongside the NPCโ€™s knowledge summary.
  4. AI Response: The AI model generates a context-aware response, creating a dynamic and engaging dialogue.

2. Setting Up Local AI Models

Choosing the Right Model

For real-time performance and local hosting, consider the following models:

  • GPT-J-6B: A robust model with 6 billion parameters, suitable for complex dialogues.
  • GPT-Neo-2.7B: A lighter alternative with 2.7 billion parameters, balancing performance and resource usage.

Installation and Dependencies

  1. Install Required Libraries

    pip install transformers torch faiss-cpu
  2. Download the Model

    Hereโ€™s an example using GPT-Neo:

    from transformers import AutoModelForCausalLM, AutoTokenizer
     
    model_name = "EleutherAI/gpt-neo-2.7B"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(model_name)

    Note: Ensure your system has sufficient memory (at least 16GB RAM recommended) to handle the model.


3. Enhancing the Knowledge Management System

3.1. Knowledge Summary Structure

For each NPC, maintain a concise summary with bullet points capturing essential knowledge. This summary guides the AI in generating relevant and consistent responses.

3.2. Implementing the Knowledge Manager

Create a new module, knowledge_manager.py, to handle knowledge summaries.

# knowledge_manager.py
 
from typing import List, Dict
 
class KnowledgeManager:
    def __init__(self):
        # Initialize a dictionary to store summaries for each character
        self.knowledge_summaries: Dict[str, List[str]] = {}
 
    def initialize_character(self, character_id: str):
        if character_id not in self.knowledge_summaries:
            self.knowledge_summaries[character_id] = []
 
    def update_knowledge(self, character_id: str, event_description: str):
        if character_id not in self.knowledge_summaries:
            self.initialize_character(character_id)
        # Keep summaries concise (e.g., max 10 bullet points)
        if len(self.knowledge_summaries[character_id]) >= 10:
            self.knowledge_summaries[character_id].pop(0)
        self.knowledge_summaries[character_id].append(f"- {event_description}")
 
    def get_summary(self, character_id: str) -> str:
        if character_id not in self.knowledge_summaries:
            self.initialize_character(character_id)
        return "\n".join(self.knowledge_summaries[character_id])

3.3. Integrating with StoryLog

Modify the StoryLog class to interface with the KnowledgeManager.

# gamelog.py (continued)
 
from knowledge_manager import KnowledgeManager
 
class StoryLog:
    def __init__(self, file_path: str):
        self.file_path = file_path
        self.data = {
            "events": [],
            "characters": {},
            "rooms": {}
        }
        self.load()
        self.knowledge_manager = KnowledgeManager()
 
    # Existing methods...
 
    def add_character(self, character_id: str, name: str, role: str, attributes: Dict[str, int]):
        if character_id not in self.data["characters"]:
            self.data["characters"][character_id] = {
                "character_id": character_id,
                "name": name,
                "role": role,
                "knowledge": [],
                "attributes": attributes
            }
            self.knowledge_manager.initialize_character(character_id)
            self.save()
 
    def log_event(self, description: str, event_type: str, participants: List[str],
                  location_room_id: str, coordinates: Dict[str, int],
                  knowledge: Dict[str, Dict[str, str]],  # e.g., {"knowing_characters": ["player"], "awareness_mode": {"player": "direct"}}
                  metadata: Optional[Dict] = None):
        event_id = str(uuid.uuid4())
        timestamp = datetime.utcnow().isoformat() + "Z"
        event = {
            "event_id": event_id,
            "timestamp": timestamp,
            "description": description,
            "type": event_type,
            "participants": participants,
            "location": {
                "room_id": location_room_id,
                "coordinates": coordinates
            },
            "knowledge": knowledge,
            "metadata": metadata or {}
        }
        self.data["events"].append(event)
        self.update_character_knowledge(event_id, knowledge)
        self.save()
 
        # Update KnowledgeManager summaries
        knowing_characters = knowledge.get("knowing_characters", [])
        for char_id in knowing_characters:
            self.knowledge_manager.update_knowledge(char_id, description)
 
        return event_id
 
    # Existing methods...

4. Implementing the Retrieval-Augmented Generation (RAG) System

4.1. Overview

The RAG system enhances AI responses by retrieving relevant context from the StoryLog. This ensures that AI-generated dialogues are coherent and contextually appropriate.

4.2. Implementing the RAG System

Create a new module, rag_system.py, to handle retrieval and response generation.

# rag_system.py
 
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from typing import List
import faiss
import numpy as np
 
class RAGSystem:
    def __init__(self, model_name: str):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForCausalLM.from_pretrained(model_name)
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.model.to(self.device)
 
        # Initialize FAISS index for efficient retrieval
        self.index = faiss.IndexFlatL2(768)  # Assuming BERT embeddings
        self.embeddings = []  # Store embeddings corresponding to events
 
    def embed_text(self, texts: List[str]) -> np.ndarray:
        # Use a separate embedding model (e.g., Sentence-BERT)
        from sentence_transformers import SentenceTransformer
        embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
        embeddings = embedding_model.encode(texts, convert_to_numpy=True)
        return embeddings
 
    def build_index(self, events: List[Dict]):
        descriptions = [event["description"] for event in events]
        embeddings = self.embed_text(descriptions)
        self.index.add(embeddings)
        self.embeddings.extend(embeddings)
 
    def retrieve_context(self, query: str, top_k: int = 5) -> List[str]:
        query_embedding = self.embed_text([query])
        D, I = self.index.search(query_embedding, top_k)
        context = [self.embeddings[i] for i in I[0] if i < len(self.embeddings)]
        return context
 
    def generate_response(self, prompt: str, max_length: int = 150) -> str:
        inputs = self.tokenizer.encode(prompt, return_tensors='pt').to(self.device)
        outputs = self.model.generate(inputs, max_length=max_length, do_sample=True, top_p=0.95, top_k=60)
        response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        return response

Notes:

  • Embeddings: For efficient retrieval, use a separate embedding model like Sentence-BERT (all-MiniLM-L6-v2) to convert text into embeddings.
  • FAISS: A library for efficient similarity search and clustering of dense vectors. It indexes the embeddings for rapid retrieval.

4.3. Integrating RAG with StoryLog

Modify the StoryLog to build the FAISS index upon loading events.

# gamelog.py (continued)
 
from rag_system import RAGSystem
 
class StoryLog:
    def __init__(self, file_path: str):
        self.file_path = file_path
        self.data = {
            "events": [],
            "characters": {},
            "rooms": {}
        }
        self.load()
        self.knowledge_manager = KnowledgeManager()
        self.rag_system = RAGSystem(model_name="EleutherAI/gpt-neo-2.7B")
        self.rag_system.build_index(self.data["events"])
 
    def log_event(self, description: str, event_type: str, participants: List[str],
                  location_room_id: str, coordinates: Dict[str, int],
                  knowledge: Dict[str, Dict[str, str]],
                  metadata: Optional[Dict] = None):
        event_id = str(uuid.uuid4())
        timestamp = datetime.utcnow().isoformat() + "Z"
        event = {
            "event_id": event_id,
            "timestamp": timestamp,
            "description": description,
            "type": event_type,
            "participants": participants,
            "location": {
                "room_id": location_room_id,
                "coordinates": coordinates
            },
            "knowledge": knowledge,
            "metadata": metadata or {}
        }
        self.data["events"].append(event)
        self.update_character_knowledge(event_id, knowledge)
        self.save()
 
        # Update KnowledgeManager summaries
        knowing_characters = knowledge.get("knowing_characters", [])
        for char_id in knowing_characters:
            self.knowledge_manager.update_knowledge(char_id, description)
 
        # Update RAG index with new event
        new_embedding = self.rag_system.embed_text([description])
        self.rag_system.index.add(new_embedding)
        self.rag_system.embeddings.append(new_embedding[0])
 
        return event_id
 
    # Existing methods...

5. Creating the AICharacter Class

Develop an AICharacter class to encapsulate each NPCโ€™s AI-driven behavior and interactions.

# ai_character.py
 
from gamelog import StoryLog
from knowledge_manager import KnowledgeManager
from rag_system import RAGSystem
import torch
 
class AICharacter:
    def __init__(self, character_id: str, story_log: StoryLog, knowledge_manager: KnowledgeManager, rag_system: RAGSystem):
        self.character_id = character_id
        self.story_log = story_log
        self.knowledge_manager = knowledge_manager
        self.rag_system = rag_system
 
    def generate_dialogue(self, player_input: str) -> str:
        # Retrieve the character's knowledge summary
        summary = self.knowledge_manager.get_summary(self.character_id)
 
        # Retrieve relevant context from StoryLog using RAG
        context = self.rag_system.retrieve_context(player_input, top_k=3)
        context_text = " ".join([self.story_log.get_event_by_id(event_id)["description"] for event_id in context])
 
        # Formulate the prompt
        character_info = self.story_log.get_character_info(self.character_id)
        prompt = f"""
        You are {character_info['name']}, a {character_info['role']} in a Zelda-style adventure game.
        Knowledge Summary:
        {summary}
 
        Relevant Context:
        {context_text}
 
        Player says: "{player_input}"
 
        Your response should be helpful, engaging, and consistent with your knowledge.
        """
 
        # Generate response using RAG system
        response = self.rag_system.generate_response(prompt)
        return response

Key Features

  • Dialogue Generation: Combines the NPCโ€™s knowledge summary and relevant game events to generate coherent responses.
  • Contextual Awareness: Uses RAG to fetch relevant past events, ensuring responses are contextually appropriate.

6. Integrating AI Characters into the Game Loop

6.1. Modifying the Game Loop

Update game.py to utilize the AICharacter class for NPC interactions.

# game.py (continued)
 
from ai_character import AICharacter
 
# Initialize AICharacters
ai_characters = {}
for char_id in story_log.data["characters"]:
    if char_id.startswith("npc"):  # Assuming NPCs have IDs starting with 'npc'
        ai_characters[char_id] = AICharacter(
            character_id=char_id,
            story_log=story_log,
            knowledge_manager=story_log.knowledge_manager,
            rag_system=story_log.rag_system
        )
 
def handle_npc_interaction(npc_id: str, player_input: str):
    if npc_id in ai_characters:
        response = ai_characters[npc_id].generate_dialogue(player_input)
        # Display response in the game UI (placeholder)
        print(f"{story_log.get_character_info(npc_id)['name']} says: {response}")
    else:
        print("NPC not found.")
 
# Modify the interaction handling in the game loop
def on_talk_to_elder(player, elder):
    # Example with choice
    choice = get_player_choice("Do you want to share the discovery? (share/keep_secret): ")
    on_player_choice_share(player, elder, choice)
    # Generate AI dialogue
    handle_npc_interaction(elder.character_id, "I have something important to tell you.")
 
# Similarly, modify other interaction functions as needed

6.2. Handling Player Input for NPCs

Since Pygame doesnโ€™t support real-time text input natively, implement a simple text input mechanism or use placeholders for dialogue prompts.

Example Placeholder for Player Input:

def get_player_input(prompt: str) -> str:
    # Placeholder using console input
    print(prompt)
    return input()

Integrate into Interaction:

def handle_npc_interaction(npc_id: str):
    player_input = get_player_input("You: ")
    response = ai_characters[npc_id].generate_dialogue(player_input)
    print(f"{story_log.get_character_info(npc_id)['name']} says: {response}")

Note: For a seamless in-game experience, consider implementing a text box within Pygame to capture player input.


7. Optimizing Performance and Maintaining Consistency

7.1. Performance Considerations

  • Model Size: Choose a model that balances performance and resource usage. GPT-Neo-2.7B is recommended for local setups.
  • Batch Processing: Handle AI requests in batches if possible to optimize GPU usage.
  • Asynchronous Processing: Utilize asynchronous programming to prevent the AI generation from blocking the game loop.

7.2. Maintaining Narrative Consistency

  • Knowledge Summaries: Keep summaries concise (max 10 bullet points) to ensure relevance and prevent AI from being overwhelmed.
  • Event Relevance: Update knowledge summaries only with events pertinent to each NPC.
  • Contextual Prompts: Carefully craft prompts to guide the AI in generating consistent and relevant responses.

7.3. Error Handling

Implement robust error handling to manage potential issues with AI generation.

def handle_npc_interaction(npc_id: str):
    try:
        player_input = get_player_input("You: ")
        response = ai_characters[npc_id].generate_dialogue(player_input)
        print(f"{story_log.get_character_info(npc_id)['name']} says: {response}")
    except Exception as e:
        print(f"Error generating dialogue: {e}")
        print(f"{story_log.get_character_info(npc_id)['name']} says: I seem to be having some issues right now.")

8. Comprehensive Example Integration

Bringing all components together, hereโ€™s how your project structure and integration would look:

8.1. Project Structure

project_root/
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ gamelog.py
โ”‚   โ”œโ”€โ”€ markdown_exporter.py
โ”‚   โ”œโ”€โ”€ rag_system.py
โ”‚   โ”œโ”€โ”€ knowledge_manager.py
โ”‚   โ”œโ”€โ”€ ai_character.py
โ”‚   โ”œโ”€โ”€ game.py
โ”‚   โ”œโ”€โ”€ player.py
โ”‚   โ”œโ”€โ”€ npc.py
โ”‚   โ”œโ”€โ”€ room_manager.py
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ””โ”€โ”€ helpers.py
โ”‚
โ”œโ”€โ”€ story_log.json
โ”œโ”€โ”€ story.md
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_gamelog.py
โ”‚   โ””โ”€โ”€ test_integration.py
โ”‚
โ””โ”€โ”€ assets/
    โ”œโ”€โ”€ images/
    โ”œโ”€โ”€ sounds/
    โ””โ”€โ”€ fonts/

8.2. Full Integration in game.py

# game.py
 
import pygame
import sys
from gamelog import StoryLog, StateError
from markdown_exporter import MarkdownExporter
from room_manager import RoomManager
from player import Player
from npc import NPC
from ai_character import AICharacter
 
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Zelda-Style Adventure")
clock = pygame.time.Clock()
 
# Initialize StoryLog and Exporter
story_log = StoryLog('story_log.json')
exporter = MarkdownExporter(story_log)
 
# Game Initialization (Characters and Rooms)
# Characters and rooms are added in StoryLog's initializer or can be added here
 
# Initialize RoomManager
room_manager = RoomManager(story_log)
room_manager.load_room("room_1")
 
# Player and NPC Classes (Simplified)
class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.speed = 5
        self.health = 100
 
    def move(self, keys):
        if keys[pygame.K_w]:
            self.y -= self.speed
        if keys[pygame.K_s]:
            self.y += self.speed
        if keys[pygame.K_a]:
            self.x -= self.speed
        if keys[pygame.K_d]:
            self.x += self.speed
 
    def attack(self):
        # Placeholder for attack logic
        pass
 
class NPC:
    def __init__(self, character_id, x, y):
        self.character_id = character_id
        self.x = x
        self.y = y
 
    def interact(self, player):
        if self.character_id == "npc_01":
            on_discover_cave(player, self)
        elif self.character_id == "npc_02":
            on_talk_to_elder(player, self)
 
# Initialize Player and NPCs
player = Player(400, 300)
npcs = {
    "npc_01": NPC("npc_01", 200, 200),
    "npc_02": NPC("npc_02", 300, 400)
}
 
# Initialize AICharacters
ai_characters = {}
for char_id in story_log.data["characters"]:
    if char_id.startswith("npc"):  # Assuming NPCs have IDs starting with 'npc'
        ai_characters[char_id] = AICharacter(
            character_id=char_id,
            story_log=story_log,
            knowledge_manager=story_log.knowledge_manager,
            rag_system=story_log.rag_system
        )
 
def on_discover_cave(player, npc):
    try:
        event_id = story_log.log_event(
            description="The player discovered a hidden cave with the help of the Old Sage.",
            event_type="discovery",
            participants=["player", "npc_01"],
            location_room_id="room_2",
            coordinates={"x": 100, "y": 150},
            knowledge={
                "knowing_characters": ["player", "npc_01"],
                "awareness_mode": {
                    "player": "direct",
                    "npc_01": "direct"
                }
            },
            metadata={
                "important": True,
                "tags": ["cave", "exploration"]
            }
        )
        print(f"Event {event_id} logged: Cave discovered.")
    except StateError as e:
        print(e)
 
def on_talk_to_elder(player, elder):
    # Example with choice
    choice = get_player_choice("Do you want to share the discovery? (share/keep_secret): ")
    on_player_choice_share(player, elder, choice)
    # Generate AI dialogue
    handle_npc_interaction(elder.character_id)
 
def on_player_choice_share(player, elder, choice: str):
    try:
        if choice == "share":
            event_id = story_log.log_event(
                description="The player shared the discovery of the hidden cave with the Village Elder.",
                event_type="dialogue",
                participants=["player", "npc_02"],
                location_room_id="room_1",
                coordinates={"x": 300, "y": 400},
                knowledge={
                    "knowing_characters": ["player", "npc_02"],
                    "awareness_mode": {
                        "player": "direct",
                        "npc_02": "direct"
                    }
                },
                metadata={
                    "important": True,
                    "tags": ["dialogue", "information"]
                }
            )
            print(f"Event {event_id} logged: Shared discovery with elder.")
        elif choice == "keep_secret":
            event_id = story_log.log_event(
                description="The player decided to keep the discovery of the hidden cave a secret.",
                event_type="decision",
                participants=["player"],
                location_room_id="room_1",
                coordinates={"x": 300, "y": 400},
                knowledge={
                    "knowing_characters": ["player"],
                    "awareness_mode": {
                        "player": "direct"
                    }
                },
                metadata={
                    "important": True,
                    "tags": ["decision", "secret"]
                }
            )
            print(f"Event {event_id} logged: Kept discovery a secret.")
    except StateError as e:
        print(e)
 
def get_player_choice(prompt: str) -> str:
    # Placeholder for actual input in Pygame
    # In a real game, you'd have a UI for choices
    print(prompt)
    choice = input().strip().lower()
    return choice
 
def generate_npc_dialogue(npc_id: str):
    character = story_log.get_character_info(npc_id)
    if not character:
        return "No dialogue available."
 
    known_events = story_log.get_character_knowledge(npc_id)
    dialogue = f"{character['name']} says:\n\n"
 
    for event in known_events:
        if event['type'] == 'discovery':
            dialogue += f"- I heard you found the {event['location']['room_id']}. Amazing!\n"
        elif event['type'] == 'dialogue':
            dialogue += f"- It's good to see you share your discoveries with us.\n"
        elif event['type'] == 'decision':
            dialogue += f"- Your decision to {event['description'].split()[1]} has shaped our village.\n"
 
    # Integrate with LLM (pseudo-code)
    # response = llm.generate_dialogue(dialogue)
    # return response
 
    # Placeholder response
    return dialogue
 
def handle_npc_interaction(npc_id: str):
    try:
        player_input = get_player_input("You: ")
        response = ai_characters[npc_id].generate_dialogue(player_input)
        print(f"{story_log.get_character_info(npc_id)['name']} says: {response}")
    except Exception as e:
        print(f"Error generating dialogue: {e}")
        print(f"{story_log.get_character_info(npc_id)['name']} says: I seem to be having some issues right now.")
 
def get_player_input(prompt: str) -> str:
    # Placeholder using console input
    # Replace with in-game text box for real implementation
    print(prompt)
    return input()
 
def on_transition_to_cave(player):
    try:
        new_coords = {"x": 100, "y": 150}  # Example coordinates in the new room
        room_manager.transition_room(room_manager.current_room_id, "room_2", new_coords)
        player.x, player.y = new_coords["x"], new_coords["y"]
        on_discover_cave(player, npcs["npc_01"])
    except StateError as e:
        print(e)
 
def check_player_reach_cave(player):
    if room_manager.current_room_id == "room_2":
        # Player is already in the cave
        return
    # Simplified condition for reaching cave entrance
    if player.x >= 100 and player.y >= 150:
        on_transition_to_cave(player)
 
# Save and Load Game Functions
def save_game():
    story_log.save_to_file()
 
def load_game():
    try:
        story_log.load_from_file()
    except FileNotFoundError as e:
        print(e)
 
# Game Loop
running = True
while running:
    clock.tick(60)  # 60 FPS
 
    # Event Handling
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
 
        # Example Interaction: Press E to interact with NPCs
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_e:
                # Simple proximity check for interaction
                for npc in npcs.values():
                    if abs(player.x - npc.x) < 50 and abs(player.y - npc.y) < 50:
                        handle_npc_interaction(npc.character_id)
 
            # Example: Press M to export to Markdown
            if event.key == pygame.K_m:
                exporter.export_to_markdown('story.md')
                print("Story exported to Markdown.")
 
            # Example: Press S to save game
            if event.key == pygame.K_s:
                save_game()
                print("Game saved.")
 
            # Example: Press L to load game
            if event.key == pygame.K_l:
                load_game()
 
    # Input Handling
    keys = pygame.key.get_pressed()
    player.move(keys)
 
    # Update Game State
    check_player_reach_cave(player)
 
    # Render
    screen.fill((50, 50, 100))  # Example background color
 
    # Render Player
    pygame.draw.rect(screen, (255, 255, 0), pygame.Rect(player.x, player.y, 30, 30))
 
    # Render NPCs
    for npc in npcs.values():
        pygame.draw.rect(screen, (0, 255, 0), pygame.Rect(npc.x, npc.y, 30, 30))
 
    # Draw current room description (Text Rendering)
    room_info = story_log.get_room_info(room_manager.current_room_id)
    if room_info:
        font = pygame.font.SysFont(None, 24)
        description = font.render(room_info["description"], True, (255, 255, 255))
        screen.blit(description, (10, 10))
 
    # Update Display
    pygame.display.flip()
 
# Save and Quit
story_log.save()
pygame.quit()
sys.exit()

9. Testing and Validation

9.1. Unit Tests for AICharacter

Ensure that the AICharacter class generates appropriate dialogues based on knowledge summaries and context.

# tests/test_ai_character.py
 
import unittest
from gamelog import StoryLog
from knowledge_manager import KnowledgeManager
from rag_system import RAGSystem
from ai_character import AICharacter
 
class TestAICharacter(unittest.TestCase):
    def setUp(self):
        self.story_log = StoryLog('test_story_log.json')
        self.story_log.add_character(
            character_id="npc_01",
            name="Old Sage",
            role="mentor",
            attributes={"health": 100, "strength": 5}
        )
        self.story_log.add_room(
            room_id="room_1",
            name="Starting Village",
            description="A peaceful village with a central square.",
            tiles=[[0, 0, 1], [1, 0, 1], [1, 1, 1]],
            objects=[{"type": "chest", "id": "chest_01", "x": 300, "y": 400}],
            npcs=["npc_02"]
        )
        self.rag_system = RAGSystem(model_name="EleutherAI/gpt-neo-2.7B")
        self.rag_system.build_index(self.story_log.data["events"])
        self.ai_character = AICharacter(
            character_id="npc_01",
            story_log=self.story_log,
            knowledge_manager=self.story_log.knowledge_manager,
            rag_system=self.rag_system
        )
        self.story_log.knowledge_manager.update_knowledge("npc_01", "Test event for AI character.")
 
    def tearDown(self):
        import os
        if os.path.exists('test_story_log.json'):
            os.remove('test_story_log.json')
 
    def test_generate_dialogue(self):
        player_input = "Hello, wise Sage!"
        response = self.ai_character.generate_dialogue(player_input)
        self.assertIsInstance(response, str)
        self.assertIn("Test event", response)
 
if __name__ == '__main__':
    unittest.main()

9.2. Integration Tests

Ensure that AI characters interact correctly within the game environment.

# tests/test_integration.py
 
import unittest
from gamelog import StoryLog
from knowledge_manager import KnowledgeManager
from rag_system import RAGSystem
from ai_character import AICharacter
from room_manager import RoomManager
 
class TestIntegration(unittest.TestCase):
    def setUp(self):
        self.story_log = StoryLog('test_story_log.json')
        self.story_log.add_character(
            character_id="npc_02",
            name="Village Elder",
            role="elder",
            attributes={"health": 120, "strength": 8}
        )
        self.story_log.add_room(
            room_id="room_1",
            name="Starting Village",
            description="A peaceful village with a central square.",
            tiles=[[0, 0, 1], [1, 0, 1], [1, 1, 1]],
            objects=[{"type": "chest", "id": "chest_01", "x": 300, "y": 400}],
            npcs=["npc_02"]
        )
        self.rag_system = RAGSystem(model_name="EleutherAI/gpt-neo-2.7B")
        self.rag_system.build_index(self.story_log.data["events"])
        self.ai_character = AICharacter(
            character_id="npc_02",
            story_log=self.story_log,
            knowledge_manager=self.story_log.knowledge_manager,
            rag_system=self.rag_system
        )
        self.room_manager = RoomManager(self.story_log)
        self.room_manager.load_room("room_1")
        self.story_log.log_event(
            description="Village Elder shares wisdom with the player.",
            event_type="dialogue",
            participants=["player", "npc_02"],
            location_room_id="room_1",
            coordinates={"x": 300, "y": 400},
            knowledge={
                "knowing_characters": ["player", "npc_02"],
                "awareness_mode": {
                    "player": "direct",
                    "npc_02": "direct"
                }
            },
            metadata={
                "important": True,
                "tags": ["dialogue", "wisdom"]
            }
        )
 
    def tearDown(self):
        import os
        if os.path.exists('test_story_log.json'):
            os.remove('test_story_log.json')
 
    def test_ai_character_interaction(self):
        player_input = "Can you help me find the hidden cave?"
        response = self.ai_character.generate_dialogue(player_input)
        self.assertIsInstance(response, str)
        self.assertIn("hidden cave", response.lower())
 
if __name__ == '__main__':
    unittest.main()

10. Future Enhancements and Best Practices

10.1. Modular File Organization

Maintain a clean and organized project structure to facilitate scalability and maintainability.

project_root/
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ gamelog.py
โ”‚   โ”œโ”€โ”€ markdown_exporter.py
โ”‚   โ”œโ”€โ”€ rag_system.py
โ”‚   โ”œโ”€โ”€ knowledge_manager.py
โ”‚   โ”œโ”€โ”€ ai_character.py
โ”‚   โ”œโ”€โ”€ game.py
โ”‚   โ”œโ”€โ”€ player.py
โ”‚   โ”œโ”€โ”€ npc.py
โ”‚   โ”œโ”€โ”€ room_manager.py
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ””โ”€โ”€ helpers.py
โ”‚
โ”œโ”€โ”€ story_log.json
โ”œโ”€โ”€ story.md
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_gamelog.py
โ”‚   โ”œโ”€โ”€ test_ai_character.py
โ”‚   โ””โ”€โ”€ test_integration.py
โ”‚
โ””โ”€โ”€ assets/
    โ”œโ”€โ”€ images/
    โ”œโ”€โ”€ sounds/
    โ””โ”€โ”€ fonts/

10.2. Using Data Classes for Structured Data

Leverage Pythonโ€™s dataclasses to define structured data entities for clarity and type safety.

# data_models.py
 
from dataclasses import dataclass, field
from typing import List, Dict, Optional
 
@dataclass
class Knowledge:
    knowing_characters: List[str]
    awareness_mode: Dict[str, str]
 
@dataclass
class Event:
    event_id: str
    timestamp: str
    description: str
    type: str
    participants: List[str]
    location_room_id: str
    coordinates: Dict[str, int]
    knowledge: Knowledge
    metadata: Dict[str, any] = field(default_factory=dict)
 
@dataclass
class Character:
    character_id: str
    name: str
    role: str
    attributes: Dict[str, int]
    knowledge: List[str] = field(default_factory=list)
 
@dataclass
class Room:
    room_id: str
    name: str
    description: str
    tiles: List[List[int]]
    objects: List[Dict]
    npcs: List[str]

Integration Example:

Modify StoryLog to utilize these data classes for better structure.

# gamelog.py (continued)
 
from data_models import Event, Character, Room
 
class StoryLog:
    # Existing methods...
 
    def log_event(self, description: str, event_type: str, participants: List[str],
                  location_room_id: str, coordinates: Dict[str, int],
                  knowledge: Dict[str, Dict[str, str]],
                  metadata: Optional[Dict] = None):
        event_id = str(uuid.uuid4())
        timestamp = datetime.utcnow().isoformat() + "Z"
        knowledge_obj = Knowledge(**knowledge)
        event_obj = Event(
            event_id=event_id,
            timestamp=timestamp,
            description=description,
            type=event_type,
            participants=participants,
            location_room_id=location_room_id,
            coordinates=coordinates,
            knowledge=knowledge_obj,
            metadata=metadata or {}
        )
        self.data["events"].append(event_obj.__dict__)
        self.update_character_knowledge(event_id, knowledge)
        self.save()
 
        # Update KnowledgeManager summaries
        knowing_characters = knowledge.get("knowing_characters", [])
        for char_id in knowing_characters:
            self.knowledge_manager.update_knowledge(char_id, description)
 
        # Update RAG index with new event
        new_embedding = self.rag_system.embed_text([description])
        self.rag_system.index.add(new_embedding)
        self.rag_system.embeddings.append(new_embedding[0])
 
        return event_id
 
    # Existing methods...

10.3. Advanced AI Integration

Enhance AI-driven narratives by leveraging more sophisticated AI capabilities:

  1. Dynamic Event Generation: Allow AI to create new events based on the story progression.
  2. Emotion and Tone Control: Guide AI responses with specified emotions or tones to match the narrative mood.
  3. NPC Goals and Objectives: Define specific goals for NPCs to drive their interactions and responses.

10.4. Optimizing JSON Handling

For extensive story logs, consider using a database (e.g., SQLite) instead of JSON for better performance and scalability.

Example with SQLite:

  1. Setup:

    import sqlite3
     
    class StoryLogDB:
        def __init__(self, db_path: str):
            self.conn = sqlite3.connect(db_path)
            self.create_tables()
     
        def create_tables(self):
            cursor = self.conn.cursor()
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS events (
                    event_id TEXT PRIMARY KEY,
                    timestamp TEXT,
                    description TEXT,
                    type TEXT,
                    participants TEXT,
                    location_room_id TEXT,
                    coordinates_x INTEGER,
                    coordinates_y INTEGER,
                    knowledge TEXT,
                    metadata TEXT
                )
            ''')
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS characters (
                    character_id TEXT PRIMARY KEY,
                    name TEXT,
                    role TEXT,
                    attributes TEXT,
                    knowledge TEXT
                )
            ''')
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS rooms (
                    room_id TEXT PRIMARY KEY,
                    name TEXT,
                    description TEXT,
                    tiles TEXT,
                    objects TEXT,
                    npcs TEXT
                )
            ''')
            self.conn.commit()
     
        # Implement methods to add, retrieve, and update data
  2. Benefits:

    • Performance: Faster querying and data manipulation.
    • Scalability: Handles large datasets efficiently.
    • Concurrency: Supports multiple read/write operations.

11. Final Checklist and Best Practices

11.1. Maintain Modularity

  • Separation of Concerns: Keep distinct functionalities (e.g., AI, logging, rendering) in separate modules.
  • Reusable Components: Design classes and functions that can be easily reused or extended.

11.2. Consistent Testing

  • Automated Tests: Regularly run unit and integration tests to catch and fix bugs early.
  • Mocking AI Responses: For testing purposes, consider mocking AI responses to ensure predictable outcomes.

11.3. Comprehensive Documentation

  • Code Comments: Clearly comment complex sections of code for future reference.
  • README Files: Provide detailed instructions on setting up and running the project.
  • JSON Schema Documentation: Maintain up-to-date documentation of the JSON structure or database schema.

11.4. Version Control

  • Git Usage: Regularly commit changes with meaningful messages.
  • Branching Strategy: Use feature branches to develop new functionalities without disrupting the main codebase.
  • Backup: Ensure that your repository is backed up to prevent data loss.

11.5. Performance Optimization

  • Profile the Game: Identify and optimize performance bottlenecks.
  • Efficient Data Structures: Use appropriate data structures for fast data access and manipulation.
  • Resource Management: Ensure that AI model loading and inference are optimized to prevent lag.

12. Conclusion

By integrating AI-generated characters with a RAG system and a dynamic knowledge management framework, your Zelda-style Pygame project will offer rich, evolving narratives and immersive interactions. This advanced setup ensures that NPCs respond intelligently and consistently, enhancing the overall gaming experience.

Key Takeaways

  • Local AI Models: Provide dynamic and context-aware dialogues.
  • RAG System: Enhances AI responses with relevant game context.
  • Knowledge Management: Maintains concise summaries for each NPC to guide AI behavior.
  • Seamless Integration: Ensures that all systems work together harmoniously.
  • Maintainability and Scalability: Designed with future expansions and optimizations in mind.

Next Steps

  1. Implement In-Game Text Input: Replace console-based input with in-game text boxes for seamless interactions.
  2. Enhance AI Prompt Engineering: Refine prompts to improve the quality and relevance of AI-generated responses.
  3. Expand NPC Roles: Introduce more diverse NPC roles and behaviors to enrich the game world.
  4. Optimize AI Performance: Explore model optimization techniques like quantization to improve inference speed.
  5. User Interface Enhancements: Develop a robust UI for dialogues, choices, and other interactive elements.

Happy Coding and Storytelling! ๐ŸŽฎ๐Ÿ“œ

Tags

game-development #pygame #system-design #action-adventure