We are going to build a stateful interactive fiction engine that generates narrative and tracks player inventory, health, and location in structured JSON. You can wire it into a web frontend, a chat bot, or a terminal game without writing a custom rules engine. I use Oxlo.ai and the OpenAI SDK so the whole thing stays under fifty lines of Python, and because Oxlo.ai charges per request rather than per token, the growing message history in each turn does not inflate your cost.
What you'll need
- Python 3.10 or newer
pip install openai- An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Test the pipeline with a free-form prompt
Before adding state, I verify that the connection works and the model can improvise a scene. Llama 3.3 70B on Oxlo.ai follows creative instructions reliably without extra tuning.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": "You are a terse sci-fi narrator."},
{"role": "user", "content": "Start an adventure on a derelict space station."},
],
)
print(response.choices[0].message.content)
Step 2: Define the system prompt and JSON schema
Now I freeze the persona and the output schema so the frontend can parse the story reliably. The model must return inventory, health, location, narrative text, and available choices.
SYSTEM_PROMPT = """You are the game master for a text adventure.
Respond only in JSON. Do not write markdown or explanations outside the JSON.
The JSON must contain:
- narrative: string describing what happens
- location: string
- health: integer 0-100
- inventory: list of strings
- choices: list of strings, each an action the player can take
Keep the tone pulpy and fast-paced. Update inventory and health based on events."""
Step 3: Enforce JSON mode on Oxlo.ai
I add response_format to guarantee valid JSON. This removes the need for regex hacks on the client side.
from openai import OpenAI
import json
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "The player awakens in a crashed lifepod on a jungle moon. They have a flashlight and a medkit."},
],
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
print(json.dumps(scene, indent=2))
Step 4: Add the player input loop
Entertainment apps are interactive. I wrap the call in a loop that appends the user command and the last JSON state to the message history so the model remembers inventory and health.
from openai import OpenAI
import json
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
history = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Start the adventure. The player has a flashlight and a medkit."},
]
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=history,
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
print(scene["narrative"])
print("Choices:", scene["choices"])
# Player picks option 0
player_input = scene["choices"][0]
history.append({"role": "assistant", "content": json.dumps(scene)})
history.append({"role": "user", "content": f"The player chooses: {player_input}"})
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=history,
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
print(scene["narrative"])
print("Inventory:", scene["inventory"])
Step 5: Handle combat and state changes
To make it feel like a game, I prompt the model to introduce a hostile creature and let the dice fall where they may. The model manages health deductions and loot drops inside the same JSON schema.
from openai import OpenAI
import json
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
history = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Start the adventure. The player has a flashlight, a medkit, and 100 health."},
]
# First scene
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=history,
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
history.append({"role": "assistant", "content": json.dumps(scene)})
# Force a combat encounter
history.append({"role": "user", "content": "A stalking panther attacks. The player tries to fight with the flashlight."})
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=history,
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
print(scene["narrative"])
print(f"Health: {scene['health']}")
print(f"Inventory: {scene['inventory']}")
Run it
Here is the complete, self-contained script. Save it as adventure.py, export your key, and run it. I added a small input() loop so you can play it in the terminal.
from openai import OpenAI
import json
SYSTEM_PROMPT = """You are the game master for a text adventure.
Respond only in JSON. Do not write markdown or explanations outside the JSON.
The JSON must contain:
- narrative: string describing what happens
- location: string
- health: integer 0-100
- inventory: list of strings
- choices: list of strings, each an action the player can take
Keep the tone pulpy and fast-paced. Update inventory and health based on events."""
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
history = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Start the adventure. The player awakens in a crashed lifepod on a jungle moon. They have a flashlight and a medkit. Health is 100."},
]
while True:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=history,
response_format={"type": "json_object"},
)
scene = json.loads(response.choices[0].message.content)
print("\n" + scene["narrative"])
print(f"Location: {scene['location']} | Health: {scene['health']}")
print(f"Inventory: {', '.join(scene['inventory'])}")
if scene["health"] <= 0:
print("Game Over.")
break
for i, choice in enumerate(scene["choices"]):
print(f"{i}. {choice}")
try:
idx = int(input("Choose: "))
player_move = scene["choices"][idx]
except (ValueError, IndexError):
print("Invalid choice. Exiting.")
break
history.append({"role": "assistant", "content": json.dumps(scene)})
history.append({"role": "user", "content": f"The player chooses: {player_move}"})
Example session output:
You awaken to the hiss of escaping oxygen. The lifepod's hull is cracked, and vines already curl through the viewport. A dim path leads west into phosphorescent undergrowth. To the east, you see the twisted wreckage of your ship's cargo bay.
Location: Jungle Moon, Lifepod Site | Health: 100
Inventory: flashlight, medkit
0. Take the west path into the undergrowth
1. Crawl east into the cargo bay wreckage
Choose: 0
The undergrowth parts around your boots. Something large moves in the canopy. You find a plasma cutter half-buried in mud, but a venomous mantis-like creature drops onto your shoulder. You burn it off with the flashlight, but not before it sinks a barb into your arm.
Location: Jungle Moon, Undergrowth | Health: 82
Inventory: flashlight, medkit, plasma cutter
0. Press deeper toward the signal tower
1. Return to the lifepod to use the medkit
Choose:
Next steps
Swap in kimi-k2.6 or deepseek-v3.2 from Oxlo.ai if you want stronger reasoning for puzzle-heavy adventures, or stick with llama-3.3-70b for fast, creative prose. To ship this, expose the loop over FastAPI and add streaming so the narrative renders word by word. You can also call Oxlo.ai's images/generations endpoint with Flux.1 to generate scene art for every new location.
Top comments (0)