Saturday, November 30, 2024

Create a Free Local AI Dungeon Game with Ollama

  This video shows how to create a dungeon and dragon game with help of local models with Ollama easily.


Code:


conda create -n ai python=3.10 -y && conda activate ai

mkdir mygame && cd mygame

pip install pydantic==2.8.2 gradio==4.44.1 ollama

system_prompt = f"""
Your job is to help create interesting futuristic worlds that \
players would love to explore.
Instructions:
- Only generate in plain text without formatting.
- Use simple clear language without being overly technical.
- You must stay below 3-5 sentences for each description.
"""

world_prompt = f"""
Generate a creative description for a unique futuristic world with an
interesting concept around humans colonizing new planets in a distant galaxy.

Output content in the form:
World Name: <WORLD NAME>
World Description: <WORLD DESCRIPTION>

World Name:"""

import os
import json
from ollama import chat
from ollama import ChatResponse

response: ChatResponse = chat(model='llama3.2',
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": world_prompt}
    ]
)

world_output = response['message']['content']
print(world_output)

world_output = world_output.strip()
world = {
    "name": world_output.split('\n')[0].strip()
    .replace('World Name: ', ''),
    "description": '\n'.join(world_output.split('\n')[1:])
    .replace('World Description:', '').strip()
}

kingdom_prompt = f"""
Create 3 different colonies for a futuristic world.
For each colony describe the leaders, societal structures, and notable achievements.

Output content in the form:
Colony 1 Name: <COLONY NAME>
Colony 1 Description: <COLONY DESCRIPTION>
Colony 2 Name: <COLONY NAME>
Colony 2 Description: <COLONY DESCRIPTION>
Colony 3 Name: <COLONY NAME>
Colony 3 Description: <COLONY DESCRIPTION>

World Name: {world['name']}
World Description: {world['description']}

Colony 1"""

response: ChatResponse = chat(model='llama3.2',
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": kingdom_prompt}
    ]
)

kingdoms = {}
kingdoms_output = response['message']['content']

for output in kingdoms_output.split('\n\n'):
  kingdom_name = output.strip().split('\n')[0] \
    .split('Name: ')[1].strip()
  print(f'Created colony "{kingdom_name}" in {world["name"]}')
  kingdom_description = output.strip().split('\n')[1] \
    .split('Description: ')[1].strip()
  kingdom = {
      "name": kingdom_name,
      "description": kingdom_description,
      "world": world['name']
  }
  kingdoms[kingdom_name] = kingdom
world['kingdoms'] = kingdoms

print(f'\nColony 1 Description: \
{kingdom["description"]}')


def get_town_prompt(world, kingdom):
    return f"""
    Create 3 different starports for a futuristic colony and world. \
    Describe the region they're in, important facilities, \
    and notable history.
   
    Output content in the form:
    Starport 1 Name: <STARPORT NAME>
    Starport 1 Description: <STARPORT DESCRIPTION>
    Starport 2 Name: <STARPORT NAME>
    Starport 2 Description: <STARPORT DESCRIPTION>
    Starport 3 Name: <STARPORT NAME>
    Starport 3 Description: <STARPORT DESCRIPTION>
   
    World Name: {world['name']}
    World Description: {world['description']}
   
    Colony Name: {kingdom['name']}
    Colony Description {kingdom['description']}
   
    Starport 1 Name:"""


def create_towns(world, kingdom):
    print(f'\nCreating starports for colony: {kingdom["name"]}...')
    response: ChatResponse = chat(model='llama3.2',
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": get_town_prompt(world, kingdom)}
        ]
    )  
    towns_output = response['message']['content']
   
    towns = {}
    for output in towns_output.split('\n\n'):
        town_name = output.strip().split('\n')[0]\
        .split('Name: ')[1].strip()
        print(f'- {town_name} created')
       
        town_description = output.strip().split('\n')[1]\
        .split('Description: ')[1].strip()
       
        town = {
          "name": town_name,
          "description": town_description,
          "world": world['name'],
          "kingdom": kingdom['name']
        }
        towns[town_name] = town
    kingdom["towns"] = towns
   
for kingdom in kingdoms.values():
    create_towns(world, kingdom)  

town = list(kingdom['towns'].values())[0]
print(f'\nStarport 1 Description: \
{town["description"]}')

def get_npc_prompt(world, kingdom, town):
    return f"""
    Create 3 different characters based on the world, colony, \
    and starport they're in. Describe the character's appearance and \
    role, as well as their motivations and challenges.
   
    Output content in the form:
    Character 1 Name: <CHARACTER NAME>
    Character 1 Description: <CHARACTER DESCRIPTION>
    Character 2 Name: <CHARACTER NAME>
    Character 2 Description: <CHARACTER DESCRIPTION>
    Character 3 Name: <CHARACTER NAME>
    Character 3 Description: <CHARACTER DESCRIPTION>
   
    World Name: {world['name']}
    World Description: {world['description']}
   
    Colony Name: {kingdom['name']}
    Colony Description: {kingdom['description']}
   
    Starport Name: {town['name']}
    Starport Description: {town['description']}
   
    Character 1 Name:"""

def create_npcs(world, kingdom, town):
    print(f'\nCreating characters for the starport of: {town["name"]}...')
    response: ChatResponse = chat(model='llama3.2',
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": get_npc_prompt(world, kingdom, town)}
        ]
    )
       
    npcs_output = response['message']['content']
    npcs = {}
    for output in npcs_output.split('\n\n'):
        lines = output.strip().split('\n')
        if len(lines) < 2:
            print(f"Warning: skipping invalid NPC output - {output}")
            continue
        npc_name_line = lines[0]
        if "Name: " not in npc_name_line:
            print(f"Warning: skipping invalid NPC output - {output}")
            continue
        npc_name = npc_name_line.split('Name: ')[1].strip()
        npc_description = ""
        for line in lines[1:]:
            if "Description: " in line:
                npc_description = line.split('Description: ')[1].strip()
            elif "Motivations and Challenges: " in line:
                npc_description += "\n" + line.split('Motivations and Challenges: ')[1].strip()
        print(f'- "{npc_name}" created')
       
        npc = {
        "name": npc_name,
        "description": npc_description,
        "world": world['name'],
        "kingdom": kingdom['name'],
        "town": town['name']
        }
        npcs[npc_name] = npc
    town["npcs"] = npcs


for kingdom in kingdoms.values():
    for town in kingdom['towns'].values():
        create_npcs(world, kingdom, town)
  # For now we'll only generate npcs for one kingdom
    break

npc = list(town['npcs'].values())[0]

print(f'\nNPC 1 in {town["name"]}, \
{kingdom["name"]}:\n{npc["description"]}')
   

def save_world(world, filename):
    with open(filename, 'w') as f:
        json.dump(world, f)

def load_world(filename):
    with open(filename, 'r') as f:
        return json.load(f)

save_world(world, 'MyWorld.json')

import gradio as gr
import os
demo = None #added to allow restart

def start_game(main_loop, share=False):
    # added code to support restart
    global demo
    # If demo is already running, close it first
    if demo is not None:
        demo.close()

    demo = gr.ChatInterface(
        main_loop,
        chatbot=gr.Chatbot(height=250, placeholder="Type 'start game' to begin"),
        textbox=gr.Textbox(placeholder="What do you do next?", container=False, scale=7),
        title="AI RPG",
        # description="Ask Yes Man any question",
        theme="soft",
        examples=["Look around", "Continue the story"],
        cache_examples=False,
        retry_btn="Retry",
        undo_btn="Undo",
        clear_btn="Clear",
                           )
    demo.launch(share=share, server_name="0.0.0.0")

def test_main_loop(message, history):
    return 'Entered Action: ' + message

start_game(test_main_loop)


world = load_world('MyWorld.json')
kingdom = world['kingdoms']['Aurora Isles']
town = kingdom['towns']["Helios Landing"]
character = town['npcs']['Dr. Lyra Flynn']

system_prompt = """You are an AI Game master. Your job is to create a
start to an adventure based on the world, colony, starport, and character
a player is playing as.
Instructions:
You must only use 2-4 sentences \
Write in second person. For example: "You are Alex" \
Write in present tense. For example "You are standing..." \
First describe the character and their background. \
Then describe where they start and what they see around them."""
world_info = f"""
World: {world}
Kingdom: {kingdom}
Town: {town}
Your Character: {character}
"""

response: ChatResponse = chat(model='llama3.2',
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": world_info + '\nYour Start:'}
    ]
)

start = response['message']['content']
print(start)
world['start'] = start
save_world(world, 'MyWorld.json')


def run_action(message, history, game_state):
   
    if(message == 'start game'):
        return game_state['start']

    system_prompt = """You are an AI Game master. Your job is to write what \
happens next in a player's adventure game.\
Instructions: \
You must only write 1-3 sentences in response. \
Always write in second person present tense. \
Ex. (You approach the control panel...)"""
   
    world_info = f"""
World: {game_state['world']}
Kingdom: {game_state['kingdom']}
Town: {game_state['town']}
Your Character:  {game_state['character']}"""

    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": world_info}
    ]
    for action in history:
        messages.append({"role": "assistant", "content": action[0]})
        messages.append({"role": "user", "content": action[1]})

    messages.append({"role": "user", "content": message})

    response: ChatResponse = chat(model='llama3.2',
        messages=messages
    )
   
    result = response['message']['content']
    return result
   
       
game_state = {
    "world": world['description'],
    "kingdom": kingdom['description'],
    "town": town['description'],
    "character": character['description'],
    "start": start,
}

def main_loop(message, history):
    return run_action(message, history, game_state)

No comments: