Initial commit

This commit is contained in:
Tony Bark 2025-03-11 22:26:45 -04:00
commit f58a2e46bd
44 changed files with 4051 additions and 0 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
# Normalizes editor configuration.
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = LF
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
**/__pycache__
.idea
AI-Adventure-2bb65e3a4e2f.json
*.py[cod]
data/text_adventures.txt
venv/
.vscode/
.env/
generator/gpt2/models/
saved_stories/

254
AIDungeon_2.ipynb Normal file
View file

@ -0,0 +1,254 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "AIDungeon 2.ipynb",
"provenance": [],
"collapsed_sections": [],
"toc_visible": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"accelerator": "GPU"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "pdmJ358wwzmv"
},
"source": [
"![BYU PCCL](https://pcc4318.files.wordpress.com/2018/02/asset-1.png?w=150)\n",
"\n",
"Sponsored by the BYU PCCL Lab.\n",
"\n",
"> AI Dungeon 2 is a completely AI generated text adventure built with OpenAI's largest GPT-2 model. It's a first of it's kind game that allows you to enter and will react to any action you can imagine.\n",
"\n",
"# What is this?\n",
"Google Colab is a way to experience machine learning for free. Google provides GPUs that you can run code in. Because this game exploded however, Google likely won't be able to allow free usage of it for AI Dungeon for very long. We are almost done making an app version of the game where you will be able to play AI Dungeon 2. Until that's released you can still play the game here.\n",
"\n",
"# Main mirrors of AI Dungeon 2 are currently down due to high download costs.\n",
"We are using bittorrent as a temporary solution to host game files and keep this game alive. It's not fast, but it's the best we've got right now.\n",
"\n",
"If you want to help, best thing you can do is to **[download this torrent file with game files](https://github.com/nickwalton/AIDungeon/files/3935881/model_v5.torrent.zip)** and **seed it** indefinitely to the best of your ability. This will help new players download this game faster, and discover the vast worlds of AIDungeon2!\n",
"\n",
"- <a href=\"https://twitter.com/nickwalton00?ref_src=twsrc%5Etfw\" class=\"twitter-follow-button\" data-show-count=\"false\">Follow @nickwalton00</a> on Twitter for updates on when it will be available again.\n",
"- **[Support AI Dungeon 2](https://www.patreon.com/AIDungeon) on Patreon to help me to continue improving the game with all the awesome ideas I have for its future!**\n",
"\n",
"## How to play\n",
"1. Click \"Tools\"-> \"Settings...\" -> \"Theme\" -> \"Dark\" (optional but recommended)\n",
"2. Go to **Main Game** section below\n",
"3. Run Install block\n",
"3. Run Download Model block \n",
"4. It will then take a couple minutes to boot up as the model is downloaded loaded onto the GPU. \n",
"5. Run the game block \n",
"6. If you have questions about getting it to work then please [go to github repo](https://github.com/AIDungeon/AIDungeon) to get help. \n",
"\n",
"## About\n",
"- While you wait you can [read adventures others have had](https://aidungeon.io/)\n",
"- [Read more](https://pcc.cs.byu.edu/2019/11/21/ai-dungeon-2-creating-infinitely-generated-text-adventures-with-deep-learning-language-models/) about how AI Dungeon 2 is made.\n",
"- **[Support AI Dungeon 2](https://www.patreon.com/bePatron?u=19115449) on Patreon to help me to continue improving the game with all the awesome ideas I have for its future!**"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "pyNN-3UDv0L-"
},
"source": [
"# Main Game"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "FKqlSCrpS9dH"
},
"outputs": [],
"source": [
"# Install\n",
"!git clone --depth 1 --branch master https://github.com/AIDungeon/AIDungeon/\n",
"%cd AIDungeon\n",
"!./install.sh\n",
"from IPython.display import clear_output\n",
"clear_output()\n",
"print(\"Installation Complete!\")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "fiywfTj--_Pe"
},
"outputs": [],
"source": [
"# Download model from torrent:\n",
"!./download_model.sh\n",
"from IPython.display import clear_output\n",
"clear_output()\n",
"print(\"Download Complete!\")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "YjArwbWh6XwN"
},
"outputs": [],
"source": [
"# Play\n",
"from IPython.display import Javascript\n",
"display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 5000})'''))\n",
"!source ./venv/bin/activate\n",
"!./venv/bin/python play.py"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "kIldfwd8wjvT"
},
"source": [
"# Utilities (Persistent Save / Load, OOM Fix)"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "zLx1yMu9wwBg"
},
"outputs": [],
"source": [
"# RUN THIS FIRST before running any block below.\n",
"# This block mount Google Drive to our workspace \n",
"# so we can save to and load from it!\n",
"\n",
"import pathlib\n",
"from distutils.dir_util import copy_tree\n",
"from google.colab import drive\n",
"\n",
"drive.mount('/content/drive')\n",
"\n",
"drive_stories_directory=\"/content/drive/My Drive/AIDungeon/saved_stories\"\n",
"colab_stories_directory=\"/content/AIDungeon/saved_stories\"\n",
"\n",
"drive_model_directory=\"/content/drive/My Drive/Data/model_v5\"\n",
"colab_model_directory=\"/content/AIDungeon/generator/gpt2/models/model_v5\"\n",
"\n",
"pathlib.Path(drive_stories_directory).mkdir(parents=True, exist_ok=True) "
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "LWfm6q8tAbDB"
},
"outputs": [],
"source": [
"# Save stories to your Google Drive\n",
"copy_tree(\n",
" colab_stories_directory, \n",
" drive_stories_directory\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "HK2DO1jFxnv6"
},
"outputs": [],
"source": [
"# Load stories from your Google Drive\n",
"copy_tree(\n",
" drive_stories_directory, \n",
" colab_stories_directory\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "Ue0qY7mvKrZ0"
},
"outputs": [],
"source": [
"# Backup model from Colab to Google Drive. Requires 6.5GB of space!\n",
"copy_tree(\n",
" colab_model_directory,\n",
" drive_model_directory\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 0,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "XqK7MXhG40Oa"
},
"outputs": [],
"source": [
"# Copy model from Google Drive. Make sure the model is uploaded to your personal Drive. \n",
"# It should resides in a Data folder. The path is: /Data/model_v5/\n",
"copy_tree(\n",
" drive_model_directory, \n",
" colab_model_directory\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"evalue": "Error: Jupyter cannot be started. Error attempting to locate jupyter: Error: Module 'notebook' not installed.",
"output_type": "error"
}
],
"source": [
"# If you get an OOM (out of memory error, random crashes) \n",
"# you might want to increase the available RAM. \n",
"\n",
"# To do so, run this block. Wait until it crashes\n",
"# and a little message will pops up asking if \n",
"# you'd like to increase the available memory. Say yes and run the game.\n",
"# Credit goes to bpseudopod for figuring this out.\n",
"# Source: https://www.reddit.com/r/AIDungeon/comments/e782oi/tips_for_crash_prevention/\n",
"\n",
"d = []\n",
"while True:\n",
" d.append(1)"
]
}
]
}

90
CHANGELOG.md Normal file
View file

@ -0,0 +1,90 @@
# Changelog
All notable changes to AIDungeon will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [[Unreleased]](https://github.com/AIDungeon/AIDungeon/compare/master...develop)
### Added
- Formal grammars for apocalyptic setting: scavenger, mutant and headhunter contexts/prompts
- 'Finetune the model yourself' section in README.md
- Command line argument `--cpu` which forces use of the CPU instead of a GPU.
### Fixed
- `install.sh` will only use `sudo` if the user is not root
- Fix loading saved games from the title splash to use the new local save path.
- Fix ending punctuation being chopped off of generated text.
## [2.2.0] - 2019-12-19
### Added
- `/reset` is a new command with the same functionality as the
old `/restart`, saving the old and beginning a brand new game.
- Ratings after death and winning
- `get_rating` function to `Story` objects.
- New content in fantasy grammar.
- Formal grammars for peasant and rogue contexts/prompts.
### Removed
- F-strings for python 3.4 and 3.5 compatibility
- Trailing comma in function args for 3.5 compatibility
### Fixed
- Typos in story grammar.
- AI no longer sees `You you` when the user inputs commands beginning with `You` or `I`.
- Some caption issues with actions.
### Changed
- `/restart` now restarts from the beginning of the same game.
## [2.1.1] - 2019-12-17
### Fixed
- Bug preventing `Custom` game setting selection from working.
- Code style.
## [2.1.0] - 2019-12-16
### Added
- This changelog!
- Formal grammars for the noble, knight, and wizard contexts/prompts.
- Better regex logic to detect terminal states.
- Directory `saved_stories`.
- A few more censored words.
- Feedback for user for the censor command.
- iPython notebook utilities to save/load to Google Drive, and an OOM error workaround.
- install.sh now detects python version and fails if it's not supported.
- Issue and PR template improvements.
### Fixed
- Loading not working on `develop`.
- Loading now print properly.
- [No Save Game on Quit for Loaded Games](https://github.com/AIDungeon/AIDungeon/issues/97)
- install.sh no longer tries calling `apt-get` on distributions without it.
- Arch Linux now works with install.sh (with pyenv is used or python3.6 is set as python3).
- A bug that caused game to crash if given an incorrect game ID to load.
### Changed
- Made `install.sh` more robust.
- Sorted imports.
- Split the model downloading script into `download_model.sh` from `install.sh`.
- User commands are now case-insensitive.
- User commands are now denoted with the prefix `/`.
## [2.0.0] - 2019-12-05
### Added
- AIDungeon 2, which allows players to type in any desired action.
## [1.0.0] - ?
### Added
- AiDungeon Classic, which gives players action options to choose from.

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2019 Nick Walton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

82
README.md Normal file
View file

@ -0,0 +1,82 @@
# AIDungeon2
Read more about AIDungeon2 and how it was built [here](https://pcc.cs.byu.edu/2019/11/21/ai-dungeon-2-creating-infinitely-generated-text-adventures-with-deep-learning-language-models/).
Play the mobile app version of the game by following the links [here](https://aidungeon.io)
Play the game online by following this link [here](https://play.aidungeon.io)
Play the game in Colab [here](https://colab.research.google.com/github/AIDungeon/AIDungeon/blob/master/AIDungeon_2.ipynb).
To play the game locally, it is recommended that you have an nVidia GPU with 12 GB or more of memory, and CUDA installed. If you do not have such a GPU, each turn can take a couple of minutes or more for the game to compose its response. To install and play locally:
```
git clone --branch master https://github.com/AIDungeon/AIDungeon/
cd AIDungeon
./install.sh # Installs system packages and creates python3 virtual environment
./download_model.sh
source ./venv/bin/activate
./play.py
```
## Finetune the model yourself
Formatting the data. After scraping the data I formatted text adventures into a json dict structure that looked like the following:
```
{
"tree_id": <someid>
"story_start": <start text of the story>
"action_results": [
{"action":<action1>, "result":<result1>, "action_results": <A Dict that looks like above action results>},
{"action":<action2>, "result":<result2>, "action_results": <A Dict that looks like above action results>}]
}
```
Essentially it's a tree that captures all the action result nodes.
Then I used [this](https://github.com/AIDungeon/AIDungeon/blob/develop/data/build_training_data.py) to transform that data into one giant txt file. The txt file looks something like:
```
<|startoftext|>
You are a survivor living in some place...
> You search for food
You search for food but are unable to find any
> Do another thing
You do another thing...
<|endoftext|>
(above repeated many times)
```
Then once you have that you can use the [finetuning script](https://github.com/AIDungeon/AIDungeon/blob/develop/generator/simple/finetune.py) to fine tune the model provided you have the hardware.
Fine tuning the largest GPT-2 model is difficult due to the immense hardware required. I no longer have access to the same hardware so there are two ways I would suggest doing it. I originally fine tuned the model on 8 32GB V100 GPUs (an Nvidia DGX1). This allowed me to use a batch size of 32 which I found to be helpful in improving quality. The only cloud resource I could find that matches those specs is an aws p3dn.24xlarge instance so you'd want to spin that up on EC2 and fine tune it there. (might have to also request higher limits). Another way you could do it is to use a sagemaker notebook (similar to a colab notebook) and select the p3.24xlarge instance type. This is equivalent to 8 16 GB V100 GPUs. Because each GPU has only 16GB memory you probably need to reduce the batch size to around 8.
Community
------------------------
AIDungeon is an open source project. Questions, discussion, and
contributions are welcome. Contributions can be anything from new
packages to bugfixes, documentation, or even new core features.
Resources:
* **Website**: [aidungeon.io](http://www.aidungeon.io/)
* **Email**: aidungeon.io@gmail.com
* **Twitter**: [creator @nickwalton00](https://twitter.com/nickwalton00), [dev @benjbay](https://twitter.com/benjbay)
* **Reddit**: [r/AIDungeon](https://www.reddit.com/r/AIDungeon/)
* **Discord**: [aidungeon discord](https://discord.gg/Dg8Vcz6)
Contributing
------------------------
Contributing to AIDungeon is easy! Just send us a
[pull request](https://help.github.com/articles/using-pull-requests/)
from your fork. Before you send it, summarize your change in the
[Unreleased] section of [the CHANGELOG](CHANGELOG.md) and make sure
``develop`` is the destination branch.
AIDungeon uses a rough approximation of the
[Git Flow](http://nvie.com/posts/a-successful-git-branching-model/)
branching model. The ``develop`` branch contains the latest
contributions, and ``master`` is always tagged and points to the latest
stable release.
If you're a contributor, make sure you're testing and playing on `develop`.
That's where all the magic is happening (and where we hope bugs stop).

1
data/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
writingprompts

111
data/build_training_data.py Normal file
View file

@ -0,0 +1,111 @@
import csv
import json
from story.utils import *
def load_tree(filename):
with open(filename, "r") as fp:
tree = json.load(fp)
return tree
def remove_phrase(text):
phrases = ["Years pass...", "Years pass"]
for phrase in phrases:
text = text.replace(phrase, "")
return text
def make_stories(current_story, tree):
stories = []
action = first_to_second_person(tree["action"])
action_list = action.split(" ")
first_word = action_list[0]
if first_word[-1] == ".":
first_word = first_word[:-1]
dont_add_you = [
"the",
"another",
"next",
"in",
"monday",
"back",
"a",
"years",
"one",
"two",
"during",
"months",
"weeks",
"seven",
"three",
"...",
"twelve",
"four",
"five",
"six",
"blackness...",
"you",
"no",
"yes",
"up",
"down",
"onward",
]
if action[0] is '"':
last_quote = action.rfind('"')
action = "You say " + action[: last_quote + 1]
elif first_word.lower() not in dont_add_you:
action = "You " + action[0].lower() + action[1:]
action = remove_phrase(action)
result = remove_phrase(tree["result"])
current_story += "\n> " + action + "\n" + result
action_results = tree["action_results"]
if len(action_results) == 0 or action_results[0] is None:
return [current_story]
else:
stories += make_stories(current_story, action_results[0])
for i in range(1, len(action_results)):
if action_results[i] is not None:
stories += make_stories(tree["result"], action_results[i])
return stories
def get_stories(filename):
tree = load_tree(filename)
stories = []
for action_result in tree["action_results"]:
stories += make_stories(tree["first_story_block"], action_result)
return stories
output_file_path = "text_adventures.txt"
with open(output_file_path, "w") as output_file:
filenames = ["stories/story" + str(i) + ".json" for i in range(0, 93)]
# filenames = []
for filename in filenames:
tree = load_tree(filename)
print('"' + tree["tree_id"] + '",')
filenames += ["stories/crowdsourcedstory" + str(i) + ".json" for i in range(0, 12)]
stories = []
for filename in filenames:
filename_stories = get_stories(filename)
stories += filename_stories
print(len(stories))
raw_text = ""
start_token = "<|startoftext|>"
end_token = "<|endoftext|>"
for story in stories:
raw_text += start_token + story + end_token + "\n"
print(len(raw_text))
output_file.write(raw_text)

58
data/make_reddit_data.py Normal file
View file

@ -0,0 +1,58 @@
import json
import os
from story.utils import *
def load_stories(file):
try:
with open(file) as fp:
stories = json.load(fp)
return stories
except:
with open(file) as fp:
stories = []
for line in fp:
if len(line) > 10:
story = json.loads(line)
stories.append(story)
return stories
def modify_story(story):
text = story["body"]
if len(text) < 100:
return None
first_person = is_first_person(text)
second_person = is_second_person(text)
if first_person or second_person:
return first_to_second_person(text)
else:
return None
current = os.getcwd()
files = os.listdir(current + "/writingprompts")
output_file_path = "writing_prompts.txt"
with open(output_file_path, "w") as output_file:
filenames = ["writingprompts/" + file for file in files]
cleaned_stories = []
for filename in filenames:
print("Processing file ", filename)
stories = load_stories(filename)
for story in stories:
cleaned_story = modify_story(story)
if cleaned_story is not None:
cleaned_stories.append(cleaned_story)
raw_text = ""
start_token = "<|startoftext|>"
end_token = "<|endoftext|>"
for story in cleaned_stories:
raw_text += start_token + story + end_token + "\n"
print(len(raw_text))
output_file.write(raw_text)

310
data/mechturk.py Normal file
View file

@ -0,0 +1,310 @@
"""
format of tree is
dict {
tree_id: tree_id_text
context: context text?
first_story_block
action_results: [act_res1, act_res2, act_res3...]
}
where each action_result's format is:
dict{
action: action_text
result: result_text
action_results: [act_res1, act_res2, act_res3...]
}
"""
import csv
import json
import os
def data_to_forest(filename):
trees = []
rows = []
with open(filename, newline="") as f:
reader = csv.reader(f)
for row in reader:
rows.append(row)
for i in range(1, len(rows[0])):
tree = {}
tree["tree_id"] = rows[0][i]
tree["context"] = rows[1][i]
tree["first_story_block"] = rows[2][i]
tree["action_results"] = []
current_action_results = tree["action_results"]
row_ind = 3
while row_ind < len(rows):
action_result = {}
action_result["action"] = rows[row_ind][i]
if row_ind + 1 < len(rows):
action_result["result"] = rows[row_ind + 1][i]
else:
action_result["result"] = None
action_result["action_results"] = []
current_action_results.append(action_result)
current_action_results = action_result["action_results"]
row_ind += 2
trees.append(tree)
return trees
def build_action_samples_helper(context, story_block, action_results, path, tree_id):
samples = []
for i, action_result in enumerate(action_results):
new_path = path[:]
new_path.append(i)
if (
len(action_result["action_results"]) is 0
and action_result["result"] is not None
):
row = [
tree_id,
"".join(str(x) for x in new_path),
context,
story_block,
action_result["action"],
action_result["result"],
]
samples.append(row)
else:
sub_result = build_action_samples_helper(
context,
action_result["result"],
action_result["action_results"],
new_path,
tree_id,
)
samples += sub_result
return samples
def make_write_actions_batch(forest, filename):
# Traverse to the bottom levels of each tree
with open(filename, mode="w", newline="") as file:
writer = csv.writer(
file, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
writer.writerow(
[
"tree_id",
"path",
"context",
"story_block_1",
"previous_action",
"story_block_2",
]
)
for tree in forest:
first_story_block = tree["first_story_block"]
samples = build_action_samples_helper(
tree["context"],
first_story_block,
tree["action_results"],
[],
tree["tree_id"],
)
for sample in samples:
writer.writerow(sample)
def build_result_samples_helper(
context, story_block, parent_action_result, path, tree_id
):
samples = []
action_results = parent_action_result["action_results"]
for i, action_result in enumerate(action_results):
new_path = path[:]
new_path.append(i)
if action_result["result"] is None:
row = [
tree_id,
"".join(str(x) for x in new_path),
context,
story_block,
parent_action_result["action"],
parent_action_result["result"],
action_result["action"],
]
samples.append(row)
else:
sub_result = build_result_samples_helper(
context,
parent_action_result["result"],
action_result,
new_path,
tree_id,
)
samples += sub_result
return samples
def make_write_results_batch(forest, filename):
with open(filename, mode="w", newline="") as file:
writer = csv.writer(
file, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
writer.writerow(
[
"tree_id",
"path",
"context",
"story_block_1",
"previous_action_1",
"story_block_2",
"previous_action_2",
]
)
for tree in forest:
first_story_block = tree["first_story_block"]
samples = []
for i, action_result in enumerate(tree["action_results"]):
path = [i]
samples += build_result_samples_helper(
tree["context"],
first_story_block,
action_result,
path,
tree["tree_id"],
)
for sample in samples:
writer.writerow(sample)
def save_tree(tree, filename):
with open(filename, "w") as fp:
json.dump(tree, fp)
def save_forest(forest, forest_name):
if not os.path.exists("./" + forest_name):
os.mkdir("./" + forest_name)
for tree in forest:
save_tree(tree, "./" + forest_name + "/" + tree["tree_id"] + ".json")
def load_tree(filename):
with open(filename, "r") as fp:
tree = json.load(fp)
return tree
def load_forest(forest_name):
files = os.listdir("./" + forest_name)
forest = []
for file in files:
forest.append(load_tree("./" + forest_name + "/" + file))
return forest
def csv_to_dict(file):
update_dict = {}
field_names = []
with open(file, newline="") as f:
reader = csv.reader(f)
for row in reader:
if len(update_dict) is 0:
for item in row:
update_dict[item] = []
field_names.append(item)
else:
for i, item in enumerate(row):
update_dict[field_names[i]].append(item)
return update_dict
def update_forest_with_results(forest_name, update_file):
update_dict = csv_to_dict(update_file)
tree_dict = {}
tree_filenames = os.listdir("./" + forest_name)
for file_name in tree_filenames:
tree = load_tree("./" + forest_name + "/" + file_name)
tree_dict[tree["tree_id"]] = tree
for i in range(len(update_dict["Input.tree_id"])):
tree = tree_dict[update_dict["Input.tree_id"][i]]
current_action_results = tree
for choice in update_dict["Input.path"][i]:
choice_num = int(choice)
current_action_results = current_action_results["action_results"][
choice_num
]
current_action_results["result"] = update_dict["Answer.result"][i]
return tree_dict.values()
def update_forest_with_actions(forest_name, update_file):
update_dict = csv_to_dict(update_file)
tree_dict = {}
tree_filenames = os.listdir("./" + forest_name)
for file_name in tree_filenames:
tree = load_tree("./" + forest_name + "/" + file_name)
tree_dict[tree["tree_id"]] = tree
for i in range(len(update_dict["Input.tree_id"])):
tree = tree_dict[update_dict["Input.tree_id"][i]]
current_action_results = tree
for choice in update_dict["Input.path"][i]:
choice_num = int(choice)
current_action_results = current_action_results["action_results"][
choice_num
]
current_action_results["action_results"].append(
{
"action": update_dict["Answer.action_1"][i],
"result": None,
"action_results": [],
}
)
current_action_results["action_results"].append(
{
"action": update_dict["Answer.action_2"][i],
"result": None,
"action_results": [],
}
)
return tree_dict.values()
old_forest_name = "seed_forest_1.8"
new_forest_name = "seed_forest_1.9"
update_type = "results"
update_file = "mech_turk_" + update_type + "5.csv"
if update_type is "results":
new_forest = update_forest_with_results(old_forest_name, update_file)
save_forest(new_forest, new_forest_name)
make_write_actions_batch(new_forest, "actions_batch6.csv")
else:
new_forest = update_forest_with_actions(old_forest_name, update_file)
save_forest(new_forest, new_forest_name)
make_write_results_batch(new_forest, "results_batch5.csv")
print("Done")

275
data/scraper.py Normal file
View file

@ -0,0 +1,275 @@
import json
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
"""
format of tree is
dict {
tree_id: tree_id_text
context: context text?
first_story_block
action_results: [act_res1, act_res2, act_res3...]
}
where each action_result's format is:
dict{
action: action_text
result: result_text
action_results: [act_res1, act_res2, act_res3...]
}
"""
class Scraper:
def __init__(self):
chrome_options = Options()
chrome_options.add_argument("--binary=/path/to/other/chrome/binary")
chrome_options.add_argument("--incognito")
chrome_options.add_argument("--window-size=1920x1080")
exec_path = "/usr/bin/chromedriver"
self.driver = webdriver.Chrome(
chrome_options=chrome_options, executable_path=exec_path
)
self.max_depth = 10
self.end_actions = {
"End Game and Leave Comments",
"Click here to End the Game and Leave Comments",
"See How Well You Did (you can still back-page afterwards if you like)",
"You have died.",
"You have died",
"Epilogue",
"Save Game",
"Your quest might have been more successful...",
"5 - not the best, certainly not the worst",
"The End! (leave comments on game)",
"6 - it's worth every cent",
"You do not survive the journey to California",
"Quit the game.",
"7 - even better than Reeses' Cups®",
"8 - it will bring you enlightenment",
"End of game! Leave a comment!",
"Better luck next time",
"click here to continue",
"Rating And Leaving Comments",
"You do not survive your journey to California",
"Your Outlaw Career has come to an end",
"Thank you for taking the time to read my story",
"You have no further part in the story, End Game and Leave Comments",
"",
"You play no further part in this story. End Game and Leave Comments",
"drivers",
"Alas, poor Yorick, they slew you well",
"My heart bleeds for you",
"To End the Game and Leave Comments click here",
"Call it a day",
"Check the voicemail.",
"reset",
"There's nothing you can do anymore...it's over.",
"To Be Continued...",
"Thanks again for taking the time to read this",
"If you just want to escape this endless story you can do that by clicking here",
"Boo Hoo Hoo",
"End.",
"Pick up some money real quick",
"",
"Well you did live a decent amount of time in the Army",
"End Game",
"You have survived the Donner Party's journey to California!",
}
self.texts = set()
def GoToURL(self, url):
self.texts = set()
self.driver.get(url)
time.sleep(0.5)
def GetText(self):
div_elements = self.driver.find_elements_by_css_selector("div")
text = div_elements[3].text
return text
def GetLinks(self):
return self.driver.find_elements_by_css_selector("a")
def GoBack(self):
self.GetLinks()[0].click()
time.sleep(0.2)
def ClickAction(self, links, action_num):
links[action_num + 4].click()
time.sleep(0.2)
def GetActions(self):
return [link.text for link in self.GetLinks()[4:]]
def NumActions(self):
return len(self.GetLinks()) - 4
def BuildTreeHelper(self, parent_story, action_num, depth, old_actions):
depth += 1
action_result = {}
action = old_actions[action_num]
print("Action is ", repr(action))
action_result["action"] = action
links = self.GetLinks()
if action_num + 4 >= len(links):
return None
self.ClickAction(links, action_num)
result = self.GetText()
if result == parent_story or result in self.texts:
self.GoBack()
return None
self.texts.add(result)
print(len(self.texts))
action_result["result"] = result
actions = self.GetActions()
action_result["action_results"] = []
for i, action in enumerate(actions):
if actions[i] not in self.end_actions:
sub_action_result = self.BuildTreeHelper(result, i, depth, actions)
if action_result is not None:
action_result["action_results"].append(sub_action_result)
self.GoBack()
return action_result
def BuildStoryTree(self, url):
scraper.GoToURL(url)
text = scraper.GetText()
actions = self.GetActions()
story_dict = {}
story_dict["tree_id"] = url
story_dict["context"] = ""
story_dict["first_story_block"] = text
story_dict["action_results"] = []
for i, action in enumerate(actions):
if action not in self.end_actions:
action_result = self.BuildTreeHelper(text, i, 0, actions)
if action_result is not None:
story_dict["action_results"].append(action_result)
else:
print("done")
return story_dict
def save_tree(tree, filename):
with open(filename, "w") as fp:
json.dump(tree, fp)
scraper = Scraper()
urls = [
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10638",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=11246",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=54639",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7397",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8041",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=11545",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7393",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=13875",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=37696",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=31013",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=45375",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=41698",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10634",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=42204",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=6823",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=18988",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10359",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=5466",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=28030",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=56515",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7480",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=11274",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=53134",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=17306",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=470",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8041",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=23928",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10183",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=45866",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=60232",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=6376",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=36791",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=60128",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=52961",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=54011",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=34838",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=13349",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8038",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=56742",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=48393",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=53356",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10872",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7393",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=31013",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=43910",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=53837",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8098",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=55043",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=28838",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=11906",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8040",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=2280",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=31014",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=43744",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=44543",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=56753",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=36594",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=15424",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8035",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10524",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=14899",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=9361",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=28030",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=49642",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=43573",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=38025",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7480",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7567",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=60747",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=10359",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=31353",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=13875",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=56501",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=38542",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=42204",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=43993",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=1153",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=24743",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=57114",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=52887",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=21879",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=16489",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=53186",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=34849",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=26752",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=7094",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=8557",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=45225",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=4720",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=51926",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=45375",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=27234",
"http://chooseyourstory.com/story/viewer/default.aspx?StoryId=60772",
]
for i in range(50, len(urls)):
print("****** Extracting Adventure ", urls[i], " ***********")
tree = scraper.BuildStoryTree(urls[i])
save_tree(tree, "stories/story" + str(41 + i) + ".json")
print("done")

298
data/sheet_to_story.py Normal file
View file

@ -0,0 +1,298 @@
"""
format of tree is
dict {
tree_id: tree_id_text
context: context text?
first_story_block
action_results: [act_res1, act_res2, act_res3...]
}
where each action_result's format is:
dict{
action: action_text
result: result_text
action_results: [act_res1, act_res2, act_res3...]
}
"""
import csv
import json
import os
def data_to_forest(filename):
trees = []
rows = []
with open(filename, newline="") as f:
reader = csv.reader(f)
for row in reader:
rows.append(row)
for i in range(1, len(rows[0])):
tree = {}
tree["tree_id"] = "upwork" + str(i)
tree["context"] = ""
tree["first_story_block"] = rows[1][i]
tree["action_results"] = []
current_action_results = tree["action_results"]
row_ind = 2
while row_ind < len(rows):
action_result = {}
action_result["action"] = rows[row_ind][i]
if row_ind + 1 < len(rows):
action_result["result"] = rows[row_ind + 1][i]
else:
action_result["result"] = None
action_result["action_results"] = []
current_action_results.append(action_result)
current_action_results = action_result["action_results"]
row_ind += 2
trees.append(tree)
return trees
def build_action_samples_helper(context, story_block, action_results, path, tree_id):
samples = []
for i, action_result in enumerate(action_results):
new_path = path[:]
new_path.append(i)
if (
len(action_result["action_results"]) is 0
and action_result["result"] is not None
):
row = [
tree_id,
"".join(str(x) for x in new_path),
context,
story_block,
action_result["action"],
action_result["result"],
]
samples.append(row)
else:
sub_result = build_action_samples_helper(
context,
action_result["result"],
action_result["action_results"],
new_path,
tree_id,
)
samples += sub_result
return samples
def make_write_actions_batch(forest, filename):
# Traverse to the bottom levels of each tree
with open(filename, mode="w", newline="") as file:
writer = csv.writer(
file, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
writer.writerow(
[
"tree_id",
"path",
"context",
"story_block_1",
"previous_action",
"story_block_2",
]
)
for tree in forest:
first_story_block = tree["first_story_block"]
samples = build_action_samples_helper(
tree["context"],
first_story_block,
tree["action_results"],
[],
tree["tree_id"],
)
for sample in samples:
writer.writerow(sample)
def build_result_samples_helper(
context, story_block, parent_action_result, path, tree_id
):
samples = []
action_results = parent_action_result["action_results"]
for i, action_result in enumerate(action_results):
new_path = path[:]
new_path.append(i)
if action_result["result"] is None:
row = [
tree_id,
"".join(str(x) for x in new_path),
context,
story_block,
parent_action_result["action"],
parent_action_result["result"],
action_result["action"],
]
samples.append(row)
else:
sub_result = build_result_samples_helper(
context,
parent_action_result["result"],
action_result,
new_path,
tree_id,
)
samples += sub_result
return samples
def make_write_results_batch(forest, filename):
with open(filename, mode="w", newline="") as file:
writer = csv.writer(
file, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
writer.writerow(
[
"tree_id",
"path",
"context",
"story_block_1",
"previous_action_1",
"story_block_2",
"previous_action_2",
]
)
for tree in forest:
first_story_block = tree["first_story_block"]
samples = []
for i, action_result in enumerate(tree["action_results"]):
path = [i]
samples += build_result_samples_helper(
tree["context"],
first_story_block,
action_result,
path,
tree["tree_id"],
)
for sample in samples:
writer.writerow(sample)
def save_tree(tree, filename):
with open(filename, "w") as fp:
json.dump(tree, fp)
def save_forest(forest, forest_name):
if not os.path.exists("./" + forest_name):
os.mkdir("./" + forest_name)
for tree in forest:
save_tree(tree, "./" + forest_name + "/" + tree["tree_id"] + ".json")
def load_tree(filename):
with open(filename, "r") as fp:
tree = json.load(fp)
return tree
def load_forest(forest_name):
files = os.listdir("./" + forest_name)
forest = []
for file in files:
forest.append(load_tree("./" + forest_name + "/" + file))
return forest
def csv_to_dict(file):
update_dict = {}
field_names = []
with open(file, newline="") as f:
reader = csv.reader(f)
for row in reader:
if len(update_dict) is 0:
for item in row:
update_dict[item] = []
field_names.append(item)
else:
for i, item in enumerate(row):
update_dict[field_names[i]].append(item)
return update_dict
def update_forest_with_results(forest_name, update_file):
update_dict = csv_to_dict(update_file)
tree_dict = {}
tree_filenames = os.listdir("./" + forest_name)
for file_name in tree_filenames:
tree = load_tree("./" + forest_name + "/" + file_name)
tree_dict[tree["tree_id"]] = tree
for i in range(len(update_dict["Input.tree_id"])):
tree = tree_dict[update_dict["Input.tree_id"][i]]
current_action_results = tree
for choice in update_dict["Input.path"][i]:
choice_num = int(choice)
current_action_results = current_action_results["action_results"][
choice_num
]
current_action_results["result"] = update_dict["Answer.result"][i]
return tree_dict.values()
def update_forest_with_actions(forest_name, update_file):
update_dict = csv_to_dict(update_file)
tree_dict = {}
tree_filenames = os.listdir("./" + forest_name)
for file_name in tree_filenames:
tree = load_tree("./" + forest_name + "/" + file_name)
tree_dict[tree["tree_id"]] = tree
for i in range(len(update_dict["Input.tree_id"])):
tree = tree_dict[update_dict["Input.tree_id"][i]]
current_action_results = tree
for choice in update_dict["Input.path"][i]:
choice_num = int(choice)
current_action_results = current_action_results["action_results"][
choice_num
]
current_action_results["action_results"].append(
{
"action": update_dict["Answer.action_1"][i],
"result": None,
"action_results": [],
}
)
current_action_results["action_results"].append(
{
"action": update_dict["Answer.action_2"][i],
"result": None,
"action_results": [],
}
)
return tree_dict.values()
tree = data_to_forest("upwork.csv")
for i, story in enumerate(tree):
save_tree(story, "crowdsourcedstory" + str(i) + ".json")
print("done")

22
data/upwork.csv Normal file
View file

@ -0,0 +1,22 @@
Prompt,,,,,,,,,,,,,,,,
First story block,The hospital seems to be completely empty. You wake up in an old rundown hospital with no memory of how you got there. You look around and see broken equipment lying around the room. ,As you leave the store you notice movement out behind your car. A man steps out with a hungry look in his eyes. His clothes are in tatters and he is carrying a crowbar.,"The man shakes his head. ""Hardly enough food for one person. I've only lived this long by looking after myself."" He turns and starts walking away.",As you're walking to to Clayton you start getting hungry. You remember that you have few granola bars in your pocket. ,"You try to open the farmhouse front door, but it's locked. There's a broken window on the right and a shed behind the house.",To your right you see jars filled with preserved fruits and vegetables. To your left you see more jars but can make out the contents clearly.,You awaken to some sunlight beams leaking in through the shed. You stretch and yawn as you look at the jars of preserves.,You roll the pebble around in your mouth and your mouth begins to salivate.,You walk back into the store and find a few shirts that you tear and wrap around your chest. It helps you feel slightly better. But you're still in a lot of pain.,"The man shakes his head. ""Hardly enough food for one person. I've only lived this long by looking after myself."" He turns and starts walking away.","You open the back door into the house and go inside. You're in a combined kitchen and living room. You see several cupboard doors are hanging open, a fridge and several couches around a TV.",There's an old warehouse and a few mechanics shops. ,"You delight it some pickled okra and some peach preserves. You think about bringing some food with you, but it will be too heavy for travel. So you try to eat as much as you can now. You decide to have some for the morning as well. It's getting late and with your full belly you are feeling quite tired. You can reenter the farmhouse or sleep in the shed.",sleep the night in the farmhouse.,You the hatch door wide open and look down into it. You see a few more shelves and notice that room leads further back. You feel a draft coming from that direction.,You get out of the car and walk towards the trunk.
Action,leave the hospital and see if there is anything outside.,ask the man what he wants and where everybody is.,leave the store and get back in your car.,eat a few granola bars.,head towards the shed behind the house,eat some preserved fruits and vegetables,have some preserves for breakfast,head back to road,take a quick rest,He's right. You look around at the everything and realize that taking care of yourself might be the best bet.,check the kitchen,check warehouse,go back to farmhouse,wake up,jump down into the hatch,check trunk
Result,Outside the hospital you see a worn down road and empty cars. Everything is strangely quiet and you start getting worried. You feel hungry and wonder when the last time you ate was.,"He looks at you confused. ""What are you stupid? This is the end of the world man!"" he says.",You think about what the man said. How is this possible? Why were you in that hospital and what should you do now?,"You finish off the rest of your granola bars. You're full now, but are worried about what you'll eat when you get hungry again.",You notice there is a padlock on the shed. Maybe you can find a key or a tool to get in.,"You delight it some pickled okra and some peach preserves. You think about bringing some food with you, but it will be too heavy for travel. So you try to eat as much as you can now. You decide to have some for the morning as well. It's getting late and with your full belly you are feeling quite tired. You can reenter the farmhouse or sleep in the shed.","You eat some green beans, carrots, and applesauce. You figure this will be enough nutrients for now.","You continue down the road for another hour. You finally see a road sign saying ""Clayton 5 Miles"". This sparks a bit of joy in you. By this rate you'll definitely be there within an hour and half. Then to Bright Meadow within two to three hours.",You slouch down to the ground and try to breathe steadily. The pain beings to magnify. You wonder what has happened to the world and if there are more hostile people out there like the guy who attacked you.,get back in your car,You go through cupboards and find a few cans of beans. You pull the them out and place them on the counter. You hear the other guy rummaging through things upstairs.,Uncertain you'd find anything of need at the mechanics you head towards the warehouse. You're not certain what kind of warehouse it is though.,You head back to farmhouse and begin climbing through the window again. You hear a slight movement inside.,Warm light streams through cracks of the old farm house you open your eyes and stretch. As you lean up to sit something warm and fuzzy rolls down your arm.,"You jump down into the underground room and a little bit of dirt spreads when your feet it the ground. It isn't extremely dusty, so it may have been used within the last few months.",When you get to the back of the vehicle you begin to notice a strange stench. You slowly peak around into the trunk to see the alarming sight of a decayed carcass laying there.
Action,try to find a working car.,"say ""What do you mean? I just woke up in a hospital with no memory. What happened?""",drive back to the hospital to see if you can find any clues. ,keep walking towards Clayton.,head back towards the broken window of the farmhouse,stay in the shed,look around,move forwards faster,move further to the back of the store,"You are uncertain of where you are, but at least you have this car for now. You ride down the main road and look for a bigger city.",check the refrigerator,open door,stop moving to listen,jump up,look around,step back in shock
Result,You find a car with the keys in the ignition and try to start it. Nothing happens. You keep looking and find another car that looks like it might work. You manage to get the engine started. ,"He responds ""Like I said, the end of the world. When the bombs starting falling three years ago we thought we could pull through. But then the skies turned black, crops started dying and everyone went crazy. Most people died in the first couple months."" ",As you pull in to the hospital parking lot the car sputters and dies. It's out of fuel an slows to a halt. ,You walk for two hours and take a break. You've left the town you were in and are now in a more rural area. There's a farmhouse to your right and a forest on your left. ,You have to go through the window to look for tools. You careful break the window more to go inside.,You remembered the noise you heard early and decide to holed up in the shed.,You look back to the other shelf and see it has some weird contents. Some sort flesh and meat compositions that repulses you. You don't wanna know if its animal or...something else.,Your energetic boost pushed you further down the road. In the distance you spot an abandoned car.,"Worried there are more hostiles around, you drag yourself to the back corner of the store.",You pass a farm house and you approach what looks like a warehouse and a mechanics shop just across from it.,You open the fridge and are greeted by a magnificent mold colony. However there are three bottles of water that can be saved.,You go straight for the front door and its locked. The windows are too high to break in to.,You don't hear any more movement or sounds so you continue through the window.,You spring off the hay the and turn to look at a slightly alarmed cat. It stares at you solemnly.,"It's rather dark, but the some of the sunlight streams through the hatch opening. You squint your eyes to see your surrounds. You remember there is one more match left.",You stumble backwards and fall onto the ground. What could that have been? You need to check for gas so you must look into the trunk again.
Action,drive around looking for any people or stores that might have supplies.,ask him how he managed to survive.,go back to the room you woke up in.,go in the farmhouse and look for food.,go through window,find a place in the shed to relax,check the hatch,search vehicle,access the area,pull up to the mechanics shop,put water on the kitchen counter,check the back of the warehouse,look around the farmhouse again,pet the cat,strike the match,reluctantly check the trunk
Result,"You drive around the town and see many boarded up buildings, but no people. Eventually you find a store that you think might have supplies.",He tells you that he was away hunting when it all started happening. He managed to live off the land for the first little while. But after the animals started dying he ran out of food and came to the city.,You get to the room. It's the same as how you remember it. Broken equipment everywhere. But there's also a small notebook that you didn't notice before lying on the floor. ,"You try to open the farmhouse front door, but it's locked. There's a broken window on the right and a shed behind the house.","You cut yourself on the edges as you're climbing in, but you make it inside.You find some thick sturdy shirts and there are rusted scattered tool scattered throughout. There doesn't seem to be any food around, however you wonder if there is a tool or key to open the shed. ",You use your hands to guide you around the space. Just past the preserved goods on the shelves there seems to be a small rug that is folded. ,You the hatch door wide open and look down into it. You see a few more shelves and notice that room leads further back. You feel a draft coming from that direction.,You look through the windows and don't see much left in the car. One of the side windows was broken open.,The corner is a bit dark as the natural light hardly reaches there. There also seems to be some rubbish on the floor. ,You figure its possible to find more gas at the mechanics shop so you pull up to the garage.,As you place the water on the counter you hear a shuffle and crash from upstairs. Muffled yelling from the man you entered the house with.,It takes you a minute to walk along to backside of the building. There you find several loading docks for semi trucks. You see on of the loading gate open just enough for to squeeze through.,Since you heard the noise you are a bit cautious and want to scan the area the best you can. You remember you have one match left.,You slowly reach out to pet the cat. It's a bit hesitant but allows you to pet it. ,The match illuminates and you slowly look around you. On the closest wall you see what looks like an oil lantern.,"You stand up and look into the trunk to examine the carcass. You realize it is one from an animal. You quickly look for any useful items, but there seems to be nothing but the carcass."
Action,go inside the store and see if there are any supplies.,ask him if you two can work together. ,read the notebook.,go through the window into the farmhouse. ,search the farmhouse,adjust the rug to lay on it,exit the shed,get into the car,lay down the corner,check the garage door,check on the man,access the loading gate,strike match,cuddle the cat,light the lantern,close the trunk
Result,It looks like the store has been ransacked and all the shelves are empty. You manage to find a package of granola bars and a flashlight behind the counter.,"The man shakes his head. ""Hardly enough food for one person. I've only lived this long by looking after myself."" He turns and starts walking away.",It looks like a journal. As you flip through the pages you see drawings of the death and destruction. The journal talks about someone's horrific experiences during the aftermath of the war. ,"You break the rest of the glass in the window and try to carefully crawl through. You cut yourself on the edges as you're climbing in, but you make it inside. ","You're Not the best educated when it comes to tools but you have an idea of what kind of tool you need to use. You see lots of shovels, rakes, and pitchforks. You notice something that looks like big pliers...maybe that can work? ",As you kneel down to adjust the rug your hand touches something cold and metal-like.,"Deciding against jumping down into a creepy underground room, you open the shed door. As you eyes san the area, you recall generally where you need to go to get to Bright Meadow.",You immediately check for keys and they aren't in the ignition. You decide to search the car more.,You feel a little dizzy so you bring yourself to the dark corner to rest a bit.,The door is locked and the windows to get in are too high to reach. Maybe there is a key in the front desk area.,You quickly head up the stairs and enter the room with the open door. You the man cradling his hand and cursing.,You check the loading gate before going under it. It seems pretty stuck.,"From the tiny glow of the match you can see bails of hay, tools, and ladder leading up to a secondary lofted area.",It's been awhile since you've been able to give and receive some affection. The cat is so sweet and kind. You notice it has some patches of fur missing and only has three legs. Although it has no open wounds. You then hear your stomach growl.,You see there is still some oil left in the lantern and it seems to be functioning properly. You can tell it's also been used before.,Despite being unable to find gas you decide to try getting the car going anyway.
Action,eat a granola bar.,run after the man and tell him you'll both do better together.,try to see if anything in it sparks your memory.,search the farmhouse for food. ,grab bolt cutter,access the metal object,get back on the road to Clayton,check the glove box,take a short rest,check the front desk office,ask him what happened,squeeze through the gate,go up the ladder,head to the shed to eat,search the room,try to start the car
Result,You feel a little bit better. But you're still hungry.,"He stops and thinks about it for a second, then says, ""Fine. I guess there might be advantages to working together."" ","you read for a while hoping this will give you some clue. None of the events written about spark anything, but you do read someone's name that you feel like you should know. ""Sarina"". It says she heard about a community called ""Bright Meadow"" that was safe and had food and was leaving to find it. ",You search around the farmhouse for food. You find some thick sturdy shirts and some rusty tools but no food. It's getting dark outside. You think it might be a good idea to sleep the night here.,You could go back out to open the shed but it is starting to get dark. You think about resting in the farmhouse or going to check the shed out.,It seems to be a large metallic ring connected to the floor. ,You walk down the country road and slowly scan your surroundings. Everything seems so silent. It's almost unnatural and surreal. ,You open the glove box and it has papers in it. Mostly car insurance and information. ,You awaken to the sound of some footsteps nearby. Being a bit startled you pull yourself more into the dark corner. A sharp pain goes rings up from your ribcage.,"You check the front and back door, they are both locked. However there are several windows you can break through.","""Was some sort of freaky looking rodent!! It bit me on my hand when I reached under the bed. I think it's still under there..."" I tell him that i'm gonna lift up the bed if he will ready his crowbar to kill it. He nods in agreement.",You sit on the ground and put your legs through the open. You laying down fully you pull your body through the opening.,You figure the lofted area would be safer for you to sleep. Your match flickers out as you make your way up.,You begin to walk towards the ladder to go back to the bottom floor. You avoid the old hook and climb down the ladder. As you begin to head towards the window you hear the cat meow. It looks down at you from above.,"You move slowly around the room with the lantern. You see some old wooden tables, stacks of old shackles, and a random assortment of clothes for all genders and ages. ",You attempt to start the car several times and you are consistently unlucky.
Action,eat another granola bar.,ask him where he thinks we should go next.,see if you can find out where Bright Meadow is from the journal. ,sleep the night in the farmhouse.,go back to shed,pull the ring,continue down the road,check the backseat,listen for the footsteps,break window,lift the bed up,look around,move slowly towards the back wall,tell the cat to come down,look in the back of the room,get out of the car
Result,You feel better. You should probably save the rest of your food though.,He tells you there is a house on the road that looked undisturbed that might be worth checking out. ,You look through the journal to see if there are any clues about where Bright Meadow is. You find a crude map that shows Bright Meadow to the north of a town called Clayton. ,You wake up in the middle of the night sweating from a nightmare. In it you saw a woman screaming in some dark place. You feel like you need to rescue her and that you're the only one who can help her. ,Dusk is setting and you climb out the window unscathed. You walk over to the shed and begin to attempt cutting the lock off. Your stomach growls as you place the bolt cutter on the lock. In that moment you think you hear rustling behind you. You turn to look and nothing is there. so you resume cutting the padlock off.,The floor lifts slightly and a dusty smell wafts out.,It's been roughly an hour now and the sun is really shining down you. You are becoming a little parched and in need of refreshment.,You notice a dried blood stain on the back car seat. There is also some trash.,After you moved you couldn't hear the footsteps anymore. You try to steady your heart rate and your breathing. After a few minutes you hear the footsteps again.,"Thinking on it, you wrap your shirt around fist and bust the window open. You clear off as much debris as you can.",You lift up the bed and wait for him to have some reaction. You watch him crouch down and use the crowbar to hit some boxes and bags.,The warehouse looks slightly undisturbed. There are some boxes that have tipped over and scattered pallets in messy piles.,You could make out that there is more hay up here but everything else is quite hard to see. As you make your way forward towards the hay you hit your head on something cold and metal-like. ,You make encouraging noises and words at the cat so it will come with you. It doesn't move. It watches you stoically from above.,You head back towards where a draft comes from. There seems to a be a tunnel that you can't see the end of. It is dark and damp.,You check the car one more time. To make sure there isn't anything useful left behind.
Action,leave the store and get back in the car.,start going towards the house. ,go to Clayton.,go back to sleep till the morning.,enter the shed,shut the hatch back,scan your surroundings,check the floors,stay quiet,crawl through window,ask him if he can see it,check some boxes,use your hand to access the object,leave the through the window,head down the tunnel,head towards Clayton
Result,As you leave the store you notice movement out behind your car. A man steps out with a hungry look in his eyes. His clothes are in tatters and he is carrying a crowbar.,You are walking towards the house. You reach an intersection. There is a police station on one corner an office building on another. The house is another half mile down the road. ,You don't know where Clayton is. But maybe if you found a map you could figure out where it is.,"You try to go back to sleep, but can't shake the feelings from your dream. You decide you might as well get up and see if you can do anything productive. ","You remove the padlock and slowly open the door. With the sun setting, it's a bit difficult to see the contents of the shed.",It seems to be a hatch that leads to an underground area like a cellar or bunker.,To the right there are fields that use to hold crops that are currently in decay. To the left there is a slightly decaying forest. ,You scan underneath the front car seats and the floors. You find the car keys were just out of sight under the driver's seat. ,A back door opens to your right and it startles you. You feel a cough well up inside you.,You pull yourself through and find yourself in a small bathroom. There doesn't seem to be anything important here.,"""I would say something if I did"" he sasses back at you. Maybe it's in one of the boxes or bags trying to hide. You are starting to get tired holding up the bed.",You grab the top of a box and pull it down. The box shifts forward and a bunch of doll heads spill. This startles you and causes you to gasp loudly.,"It seems to be thick old hook connected by chain link. When you touch it, it creaks a bit. You hear another slight movement that happens in reaction to the chain creaking. The sound seems to have come from the hay you were heading towards.",You climb out through the window and head towards the shed. When you enter the shed you notice there are some jars missing. ,"You walk down the tunnel. It's a little damp and you can hear a water drip coming from somewhere. That might be a good sign, as you are quite thirsty.","Even without the car, you know if won't take too long for you to reach Clayton then head towards Bright Meadow. You try to stay optimistic despite being a bit dehydrated and also starting to get hungry again."
Action,run at the man and attack him.,keep going towards the house. ,look for a map somewhere in the hospital. ,look around the farmhouse for anything else useful.,search the shed,open the hatch again,walk left into the forest,start the car,hold in the cough,open door to front desk area,ask him to hurry,examine the rest of the warehouse,continue towards the hay bales,scan area,continue down the tunnel,continue down the road
Result,You run forward to attack the man. His crowbar crashes into your chest and you fall to the ground. He kicks you a few times and then grabs your granola bars and runs off.,You keep walking and eventually reach the house. You see a two story house that looks fairly undisturbed. There's a garage on the right and a path to the backyard on the left. ,You start looking around for a map. You find an administrator's office that you think might have one.,You look around the farmhouse for anything else useful. You don't find anything else useful.,You can make out that there are some storage shelves and a few tables. You see something that looks a tin pot or jug to some sort. You check it and it has nothing inside. Next to it on a table you see a matchbox. You take the matchbox because it's likely to help you see.,You open up the hatch again but a little wider and stick your head in a bit. It's too dark to see anything. All you can do is smell the muskiness of whatever is below.,You walk into the woods and sit on a rock between a group trees. You wonder if there is some form of running water somewhere in here. You notice there are small pebbles at your feet.,You try to start the car several times and nothing happens. You think of the possibility it needs gas and maybe they had spare gas in the trunk. ,You place you hand over your mouth as you see a women cautiously come inside. She doesn't really look harmful but who knows what could be on her mind.,You slowly open the door and see the front desk area is mostly untouched.There's lots of dust flying around.,He scoffs at you and begins to wail the crow bar on the boxes and bags. We don't hear anything or see anything.,"You begin walking down the aisles and assume this a warehouse for toys or just parts of dolls. Then you distantly hear a playful voice say, ""This way...come this way"". They don't sound harmful.",You're not very certain what the sound could have been but whatever it is didn't sound too large.,You step out the shed for a moment and look around. You don't see anyone or anything. Maybe your mind is playing tricks on you.,"You walk a good fifthteen more minutes down the tunnel till you reach a fork in the passage. It splits into three ways. Right, middle, and left.",As you continue down the road you notice a movement in the sky.
Action,go after him.,go around the house to the backyard.,search the office for a map.,leave the farmhouse and continue walking towards Clayton.,strike a match,close the hatch and resume to try to sleep,pick up a pebble,pop the trunk,stay hidden and attempt to escape,search the desk area,tell him you need to place the bed back down,go in the direction you heard the voice come from,approach the hay bales,head back to the shed,go to the right,look up at the sky
Result,"You go after him. As you try and stand up though you realize you're in extreme pain. You think one of your ribs is broken. You manage to get yourself on your feet, but realize catching him is impossible. ",You go to the backyard of the house. There's a dirty grill and an overturned slide. There's a door to the house on the back porch.,You look through the filing cabinets for a map. You find a bunch of patient records in a filing cabinet. In the desk you find a map that shows other hospitals in the region. It says Clayton is 15 miles to the West of here. ,You get back on the road and continue your journey. After a few hours you realize how starving you are. You' really need to find a food.,The first match goes dull quick. The second and third match break as you try to strike them. There are only two matches left. You try to relax a bit to keep yourself from breaking another match. You strike the match and it stays lit.,You lay on the small rug and try to get comfortable. You hold yourself and try to get a grasp on reality before fading to sleep.,You pick up the pebble and notice the ground if very dry. This makes you uncertain a water source could be nearby. You could try to go further into the forest or suckle on the pebble to create saliva to help your body sustain a bit longer.,You press the button to unlock the trunk. It slowly lifts open.,The woman looks around and beings to check the materials and rubbish scattered around on the floor mostly in the well lit areas. Your cough lets out.,"You begin to scan the desk, there are lots of papers and receipts. You open the first drawer and find some keys.",He pulls the boxes and bags out with the crowbar and places them to the side. He nods at you.,"""Hey, over here..."" you hear the playful voice say from a short distance away.",You stand in front of the hay bales and squint your eyes to try to see something. After a few minutes without hearing any movement or sound you lay down on the hay. It's a little prickly but it's better than a cold floor.,You turn around and the lovely three-legged cat approaches you. You pet it and head back into the shed.,"You head down the right passage and don't have to walk too long before coming into a room with a pile of bones in it. There seems to be both human and animal bones here, this frightens you.",You see there are few birds of prey circling an area at the end of the forest. there could be something there that has taken there interest. However you aren't too far from entering the Clayton limits.
Action,look for something to wrap your chest with.,open the back door into the house.,leave the hospital and start heading towards Clayton.,look around to see what's around me.,search the shed slowly,wake up,suckle on the pebble,check the trunk,make a run for the back door,take keys,put the bed down,walk towards the voice,try to sleep,eat some preserves,head back and go down the middle,continue towards Clayton
Result,You walk back into the store and find a few shirts that you tear and wrap around your chest. It helps you feel slightly better. But you're still in a lot of pain.,"You open the back door into the house and go inside. You're in a combined kitchen and living room. You see several cupboard doors are hanging open, a fridge and several couches around a TV.",As you're walking to to Clayton you start getting hungry. You remember that you have few granola bars in your pocket. ,There's an old warehouse and a few mechanics shops. ,To your right you see jars filled with preserved fruits and vegetables. To your left you see more jars but can make out the contents clearly.,You awaken to some sunlight beams leaking in through the shed. You stretch and yawn as you look at the jars of preserves.,You roll the pebble around in your mouth and your mouth begins to salivate.,You get out of the car and walk towards the trunk.,"You dash out the back door and hear the woman scream, ""Wait! Watch out!!"". You take a few steps forward through the door and suddenly fall down into a hole. You are knocked unconscious.",You walk over to the front door and the keys on them. They don't seem to work.,You both slowly look through boxes and bags. You find a hanger and use it to poke through things.,You head in the direction you heard the voice come from. After passing a few aisles of stacked boxes you see an old man stand holding a doll. He beings to cackle.,"Your mind races as you try to process what exactly is going on in the world. With very little memory of yourself and your surroundings, you try to meditate and access memories. After an hour you feel as if you know before you were heading somewhere, or there was a destination in mind at least. However, why or when you entered the hospital is still a mystery to you. You fall asleep amongst your thoughts.",You open some preserved pumpkin and begin to eat it. The cat meows at you. It must be hungry too. You place some pumpkin on the ground for it to eat. It makes delightful eating noises. You get back to eating and suddenly you hear some shuffling coming from somewhere... beneath you?,"You run back and head down the middle passage. Your lantern is starting to go flicker in and out. You reach a huge room that has a heavy air to it. You are filled with dread. As you walk further into the room a deep voice bellows, ""We've been waiting for you""",You approach a sign that says the road exit to Clayton is up ahead. You're pace picks up as you steady closer to the exit ramp.
1 Prompt
2 First story block The hospital seems to be completely empty. You wake up in an old rundown hospital with no memory of how you got there. You look around and see broken equipment lying around the room. As you leave the store you notice movement out behind your car. A man steps out with a hungry look in his eyes. His clothes are in tatters and he is carrying a crowbar. The man shakes his head. "Hardly enough food for one person. I've only lived this long by looking after myself." He turns and starts walking away. As you're walking to to Clayton you start getting hungry. You remember that you have few granola bars in your pocket. You try to open the farmhouse front door, but it's locked. There's a broken window on the right and a shed behind the house. To your right you see jars filled with preserved fruits and vegetables. To your left you see more jars but can make out the contents clearly. You awaken to some sunlight beams leaking in through the shed. You stretch and yawn as you look at the jars of preserves. You roll the pebble around in your mouth and your mouth begins to salivate. You walk back into the store and find a few shirts that you tear and wrap around your chest. It helps you feel slightly better. But you're still in a lot of pain. The man shakes his head. "Hardly enough food for one person. I've only lived this long by looking after myself." He turns and starts walking away. You open the back door into the house and go inside. You're in a combined kitchen and living room. You see several cupboard doors are hanging open, a fridge and several couches around a TV. There's an old warehouse and a few mechanics shops. You delight it some pickled okra and some peach preserves. You think about bringing some food with you, but it will be too heavy for travel. So you try to eat as much as you can now. You decide to have some for the morning as well. It's getting late and with your full belly you are feeling quite tired. You can reenter the farmhouse or sleep in the shed. sleep the night in the farmhouse. You the hatch door wide open and look down into it. You see a few more shelves and notice that room leads further back. You feel a draft coming from that direction. You get out of the car and walk towards the trunk.
3 Action leave the hospital and see if there is anything outside. ask the man what he wants and where everybody is. leave the store and get back in your car. eat a few granola bars. head towards the shed behind the house eat some preserved fruits and vegetables have some preserves for breakfast head back to road take a quick rest He's right. You look around at the everything and realize that taking care of yourself might be the best bet. check the kitchen check warehouse go back to farmhouse wake up jump down into the hatch check trunk
4 Result Outside the hospital you see a worn down road and empty cars. Everything is strangely quiet and you start getting worried. You feel hungry and wonder when the last time you ate was. He looks at you confused. "What are you stupid? This is the end of the world man!" he says. You think about what the man said. How is this possible? Why were you in that hospital and what should you do now? You finish off the rest of your granola bars. You're full now, but are worried about what you'll eat when you get hungry again. You notice there is a padlock on the shed. Maybe you can find a key or a tool to get in. You delight it some pickled okra and some peach preserves. You think about bringing some food with you, but it will be too heavy for travel. So you try to eat as much as you can now. You decide to have some for the morning as well. It's getting late and with your full belly you are feeling quite tired. You can reenter the farmhouse or sleep in the shed. You eat some green beans, carrots, and applesauce. You figure this will be enough nutrients for now. You continue down the road for another hour. You finally see a road sign saying "Clayton 5 Miles". This sparks a bit of joy in you. By this rate you'll definitely be there within an hour and half. Then to Bright Meadow within two to three hours. You slouch down to the ground and try to breathe steadily. The pain beings to magnify. You wonder what has happened to the world and if there are more hostile people out there like the guy who attacked you. get back in your car You go through cupboards and find a few cans of beans. You pull the them out and place them on the counter. You hear the other guy rummaging through things upstairs. Uncertain you'd find anything of need at the mechanics you head towards the warehouse. You're not certain what kind of warehouse it is though. You head back to farmhouse and begin climbing through the window again. You hear a slight movement inside. Warm light streams through cracks of the old farm house you open your eyes and stretch. As you lean up to sit something warm and fuzzy rolls down your arm. You jump down into the underground room and a little bit of dirt spreads when your feet it the ground. It isn't extremely dusty, so it may have been used within the last few months. When you get to the back of the vehicle you begin to notice a strange stench. You slowly peak around into the trunk to see the alarming sight of a decayed carcass laying there.
5 Action try to find a working car. say "What do you mean? I just woke up in a hospital with no memory. What happened?" drive back to the hospital to see if you can find any clues. keep walking towards Clayton. head back towards the broken window of the farmhouse stay in the shed look around move forwards faster move further to the back of the store You are uncertain of where you are, but at least you have this car for now. You ride down the main road and look for a bigger city. check the refrigerator open door stop moving to listen jump up look around step back in shock
6 Result You find a car with the keys in the ignition and try to start it. Nothing happens. You keep looking and find another car that looks like it might work. You manage to get the engine started. He responds "Like I said, the end of the world. When the bombs starting falling three years ago we thought we could pull through. But then the skies turned black, crops started dying and everyone went crazy. Most people died in the first couple months." As you pull in to the hospital parking lot the car sputters and dies. It's out of fuel an slows to a halt. You walk for two hours and take a break. You've left the town you were in and are now in a more rural area. There's a farmhouse to your right and a forest on your left. You have to go through the window to look for tools. You careful break the window more to go inside. You remembered the noise you heard early and decide to holed up in the shed. You look back to the other shelf and see it has some weird contents. Some sort flesh and meat compositions that repulses you. You don't wanna know if its animal or...something else. Your energetic boost pushed you further down the road. In the distance you spot an abandoned car. Worried there are more hostiles around, you drag yourself to the back corner of the store. You pass a farm house and you approach what looks like a warehouse and a mechanics shop just across from it. You open the fridge and are greeted by a magnificent mold colony. However there are three bottles of water that can be saved. You go straight for the front door and its locked. The windows are too high to break in to. You don't hear any more movement or sounds so you continue through the window. You spring off the hay the and turn to look at a slightly alarmed cat. It stares at you solemnly. It's rather dark, but the some of the sunlight streams through the hatch opening. You squint your eyes to see your surrounds. You remember there is one more match left. You stumble backwards and fall onto the ground. What could that have been? You need to check for gas so you must look into the trunk again.
7 Action drive around looking for any people or stores that might have supplies. ask him how he managed to survive. go back to the room you woke up in. go in the farmhouse and look for food. go through window find a place in the shed to relax check the hatch search vehicle access the area pull up to the mechanics shop put water on the kitchen counter check the back of the warehouse look around the farmhouse again pet the cat strike the match reluctantly check the trunk
8 Result You drive around the town and see many boarded up buildings, but no people. Eventually you find a store that you think might have supplies. He tells you that he was away hunting when it all started happening. He managed to live off the land for the first little while. But after the animals started dying he ran out of food and came to the city. You get to the room. It's the same as how you remember it. Broken equipment everywhere. But there's also a small notebook that you didn't notice before lying on the floor. You try to open the farmhouse front door, but it's locked. There's a broken window on the right and a shed behind the house. You cut yourself on the edges as you're climbing in, but you make it inside.You find some thick sturdy shirts and there are rusted scattered tool scattered throughout. There doesn't seem to be any food around, however you wonder if there is a tool or key to open the shed. You use your hands to guide you around the space. Just past the preserved goods on the shelves there seems to be a small rug that is folded. You the hatch door wide open and look down into it. You see a few more shelves and notice that room leads further back. You feel a draft coming from that direction. You look through the windows and don't see much left in the car. One of the side windows was broken open. The corner is a bit dark as the natural light hardly reaches there. There also seems to be some rubbish on the floor. You figure its possible to find more gas at the mechanics shop so you pull up to the garage. As you place the water on the counter you hear a shuffle and crash from upstairs. Muffled yelling from the man you entered the house with. It takes you a minute to walk along to backside of the building. There you find several loading docks for semi trucks. You see on of the loading gate open just enough for to squeeze through. Since you heard the noise you are a bit cautious and want to scan the area the best you can. You remember you have one match left. You slowly reach out to pet the cat. It's a bit hesitant but allows you to pet it. The match illuminates and you slowly look around you. On the closest wall you see what looks like an oil lantern. You stand up and look into the trunk to examine the carcass. You realize it is one from an animal. You quickly look for any useful items, but there seems to be nothing but the carcass.
9 Action go inside the store and see if there are any supplies. ask him if you two can work together. read the notebook. go through the window into the farmhouse. search the farmhouse adjust the rug to lay on it exit the shed get into the car lay down the corner check the garage door check on the man access the loading gate strike match cuddle the cat light the lantern close the trunk
10 Result It looks like the store has been ransacked and all the shelves are empty. You manage to find a package of granola bars and a flashlight behind the counter. The man shakes his head. "Hardly enough food for one person. I've only lived this long by looking after myself." He turns and starts walking away. It looks like a journal. As you flip through the pages you see drawings of the death and destruction. The journal talks about someone's horrific experiences during the aftermath of the war. You break the rest of the glass in the window and try to carefully crawl through. You cut yourself on the edges as you're climbing in, but you make it inside. You're Not the best educated when it comes to tools but you have an idea of what kind of tool you need to use. You see lots of shovels, rakes, and pitchforks. You notice something that looks like big pliers...maybe that can work? As you kneel down to adjust the rug your hand touches something cold and metal-like. Deciding against jumping down into a creepy underground room, you open the shed door. As you eyes san the area, you recall generally where you need to go to get to Bright Meadow. You immediately check for keys and they aren't in the ignition. You decide to search the car more. You feel a little dizzy so you bring yourself to the dark corner to rest a bit. The door is locked and the windows to get in are too high to reach. Maybe there is a key in the front desk area. You quickly head up the stairs and enter the room with the open door. You the man cradling his hand and cursing. You check the loading gate before going under it. It seems pretty stuck. From the tiny glow of the match you can see bails of hay, tools, and ladder leading up to a secondary lofted area. It's been awhile since you've been able to give and receive some affection. The cat is so sweet and kind. You notice it has some patches of fur missing and only has three legs. Although it has no open wounds. You then hear your stomach growl. You see there is still some oil left in the lantern and it seems to be functioning properly. You can tell it's also been used before. Despite being unable to find gas you decide to try getting the car going anyway.
11 Action eat a granola bar. run after the man and tell him you'll both do better together. try to see if anything in it sparks your memory. search the farmhouse for food. grab bolt cutter access the metal object get back on the road to Clayton check the glove box take a short rest check the front desk office ask him what happened squeeze through the gate go up the ladder head to the shed to eat search the room try to start the car
12 Result You feel a little bit better. But you're still hungry. He stops and thinks about it for a second, then says, "Fine. I guess there might be advantages to working together." you read for a while hoping this will give you some clue. None of the events written about spark anything, but you do read someone's name that you feel like you should know. "Sarina". It says she heard about a community called "Bright Meadow" that was safe and had food and was leaving to find it. You search around the farmhouse for food. You find some thick sturdy shirts and some rusty tools but no food. It's getting dark outside. You think it might be a good idea to sleep the night here. You could go back out to open the shed but it is starting to get dark. You think about resting in the farmhouse or going to check the shed out. It seems to be a large metallic ring connected to the floor. You walk down the country road and slowly scan your surroundings. Everything seems so silent. It's almost unnatural and surreal. You open the glove box and it has papers in it. Mostly car insurance and information. You awaken to the sound of some footsteps nearby. Being a bit startled you pull yourself more into the dark corner. A sharp pain goes rings up from your ribcage. You check the front and back door, they are both locked. However there are several windows you can break through. "Was some sort of freaky looking rodent!! It bit me on my hand when I reached under the bed. I think it's still under there..." I tell him that i'm gonna lift up the bed if he will ready his crowbar to kill it. He nods in agreement. You sit on the ground and put your legs through the open. You laying down fully you pull your body through the opening. You figure the lofted area would be safer for you to sleep. Your match flickers out as you make your way up. You begin to walk towards the ladder to go back to the bottom floor. You avoid the old hook and climb down the ladder. As you begin to head towards the window you hear the cat meow. It looks down at you from above. You move slowly around the room with the lantern. You see some old wooden tables, stacks of old shackles, and a random assortment of clothes for all genders and ages. You attempt to start the car several times and you are consistently unlucky.
13 Action eat another granola bar. ask him where he thinks we should go next. see if you can find out where Bright Meadow is from the journal. sleep the night in the farmhouse. go back to shed pull the ring continue down the road check the backseat listen for the footsteps break window lift the bed up look around move slowly towards the back wall tell the cat to come down look in the back of the room get out of the car
14 Result You feel better. You should probably save the rest of your food though. He tells you there is a house on the road that looked undisturbed that might be worth checking out. You look through the journal to see if there are any clues about where Bright Meadow is. You find a crude map that shows Bright Meadow to the north of a town called Clayton. You wake up in the middle of the night sweating from a nightmare. In it you saw a woman screaming in some dark place. You feel like you need to rescue her and that you're the only one who can help her. Dusk is setting and you climb out the window unscathed. You walk over to the shed and begin to attempt cutting the lock off. Your stomach growls as you place the bolt cutter on the lock. In that moment you think you hear rustling behind you. You turn to look and nothing is there. so you resume cutting the padlock off. The floor lifts slightly and a dusty smell wafts out. It's been roughly an hour now and the sun is really shining down you. You are becoming a little parched and in need of refreshment. You notice a dried blood stain on the back car seat. There is also some trash. After you moved you couldn't hear the footsteps anymore. You try to steady your heart rate and your breathing. After a few minutes you hear the footsteps again. Thinking on it, you wrap your shirt around fist and bust the window open. You clear off as much debris as you can. You lift up the bed and wait for him to have some reaction. You watch him crouch down and use the crowbar to hit some boxes and bags. The warehouse looks slightly undisturbed. There are some boxes that have tipped over and scattered pallets in messy piles. You could make out that there is more hay up here but everything else is quite hard to see. As you make your way forward towards the hay you hit your head on something cold and metal-like. You make encouraging noises and words at the cat so it will come with you. It doesn't move. It watches you stoically from above. You head back towards where a draft comes from. There seems to a be a tunnel that you can't see the end of. It is dark and damp. You check the car one more time. To make sure there isn't anything useful left behind.
15 Action leave the store and get back in the car. start going towards the house. go to Clayton. go back to sleep till the morning. enter the shed shut the hatch back scan your surroundings check the floors stay quiet crawl through window ask him if he can see it check some boxes use your hand to access the object leave the through the window head down the tunnel head towards Clayton
16 Result As you leave the store you notice movement out behind your car. A man steps out with a hungry look in his eyes. His clothes are in tatters and he is carrying a crowbar. You are walking towards the house. You reach an intersection. There is a police station on one corner an office building on another. The house is another half mile down the road. You don't know where Clayton is. But maybe if you found a map you could figure out where it is. You try to go back to sleep, but can't shake the feelings from your dream. You decide you might as well get up and see if you can do anything productive. You remove the padlock and slowly open the door. With the sun setting, it's a bit difficult to see the contents of the shed. It seems to be a hatch that leads to an underground area like a cellar or bunker. To the right there are fields that use to hold crops that are currently in decay. To the left there is a slightly decaying forest. You scan underneath the front car seats and the floors. You find the car keys were just out of sight under the driver's seat. A back door opens to your right and it startles you. You feel a cough well up inside you. You pull yourself through and find yourself in a small bathroom. There doesn't seem to be anything important here. "I would say something if I did" he sasses back at you. Maybe it's in one of the boxes or bags trying to hide. You are starting to get tired holding up the bed. You grab the top of a box and pull it down. The box shifts forward and a bunch of doll heads spill. This startles you and causes you to gasp loudly. It seems to be thick old hook connected by chain link. When you touch it, it creaks a bit. You hear another slight movement that happens in reaction to the chain creaking. The sound seems to have come from the hay you were heading towards. You climb out through the window and head towards the shed. When you enter the shed you notice there are some jars missing. You walk down the tunnel. It's a little damp and you can hear a water drip coming from somewhere. That might be a good sign, as you are quite thirsty. Even without the car, you know if won't take too long for you to reach Clayton then head towards Bright Meadow. You try to stay optimistic despite being a bit dehydrated and also starting to get hungry again.
17 Action run at the man and attack him. keep going towards the house. look for a map somewhere in the hospital. look around the farmhouse for anything else useful. search the shed open the hatch again walk left into the forest start the car hold in the cough open door to front desk area ask him to hurry examine the rest of the warehouse continue towards the hay bales scan area continue down the tunnel continue down the road
18 Result You run forward to attack the man. His crowbar crashes into your chest and you fall to the ground. He kicks you a few times and then grabs your granola bars and runs off. You keep walking and eventually reach the house. You see a two story house that looks fairly undisturbed. There's a garage on the right and a path to the backyard on the left. You start looking around for a map. You find an administrator's office that you think might have one. You look around the farmhouse for anything else useful. You don't find anything else useful. You can make out that there are some storage shelves and a few tables. You see something that looks a tin pot or jug to some sort. You check it and it has nothing inside. Next to it on a table you see a matchbox. You take the matchbox because it's likely to help you see. You open up the hatch again but a little wider and stick your head in a bit. It's too dark to see anything. All you can do is smell the muskiness of whatever is below. You walk into the woods and sit on a rock between a group trees. You wonder if there is some form of running water somewhere in here. You notice there are small pebbles at your feet. You try to start the car several times and nothing happens. You think of the possibility it needs gas and maybe they had spare gas in the trunk. You place you hand over your mouth as you see a women cautiously come inside. She doesn't really look harmful but who knows what could be on her mind. You slowly open the door and see the front desk area is mostly untouched.There's lots of dust flying around. He scoffs at you and begins to wail the crow bar on the boxes and bags. We don't hear anything or see anything. You begin walking down the aisles and assume this a warehouse for toys or just parts of dolls. Then you distantly hear a playful voice say, "This way...come this way". They don't sound harmful. You're not very certain what the sound could have been but whatever it is didn't sound too large. You step out the shed for a moment and look around. You don't see anyone or anything. Maybe your mind is playing tricks on you. You walk a good fifthteen more minutes down the tunnel till you reach a fork in the passage. It splits into three ways. Right, middle, and left. As you continue down the road you notice a movement in the sky.
19 Action go after him. go around the house to the backyard. search the office for a map. leave the farmhouse and continue walking towards Clayton. strike a match close the hatch and resume to try to sleep pick up a pebble pop the trunk stay hidden and attempt to escape search the desk area tell him you need to place the bed back down go in the direction you heard the voice come from approach the hay bales head back to the shed go to the right look up at the sky
20 Result You go after him. As you try and stand up though you realize you're in extreme pain. You think one of your ribs is broken. You manage to get yourself on your feet, but realize catching him is impossible. You go to the backyard of the house. There's a dirty grill and an overturned slide. There's a door to the house on the back porch. You look through the filing cabinets for a map. You find a bunch of patient records in a filing cabinet. In the desk you find a map that shows other hospitals in the region. It says Clayton is 15 miles to the West of here. You get back on the road and continue your journey. After a few hours you realize how starving you are. You' really need to find a food. The first match goes dull quick. The second and third match break as you try to strike them. There are only two matches left. You try to relax a bit to keep yourself from breaking another match. You strike the match and it stays lit. You lay on the small rug and try to get comfortable. You hold yourself and try to get a grasp on reality before fading to sleep. You pick up the pebble and notice the ground if very dry. This makes you uncertain a water source could be nearby. You could try to go further into the forest or suckle on the pebble to create saliva to help your body sustain a bit longer. You press the button to unlock the trunk. It slowly lifts open. The woman looks around and beings to check the materials and rubbish scattered around on the floor mostly in the well lit areas. Your cough lets out. You begin to scan the desk, there are lots of papers and receipts. You open the first drawer and find some keys. He pulls the boxes and bags out with the crowbar and places them to the side. He nods at you. "Hey, over here..." you hear the playful voice say from a short distance away. You stand in front of the hay bales and squint your eyes to try to see something. After a few minutes without hearing any movement or sound you lay down on the hay. It's a little prickly but it's better than a cold floor. You turn around and the lovely three-legged cat approaches you. You pet it and head back into the shed. You head down the right passage and don't have to walk too long before coming into a room with a pile of bones in it. There seems to be both human and animal bones here, this frightens you. You see there are few birds of prey circling an area at the end of the forest. there could be something there that has taken there interest. However you aren't too far from entering the Clayton limits.
21 Action look for something to wrap your chest with. open the back door into the house. leave the hospital and start heading towards Clayton. look around to see what's around me. search the shed slowly wake up suckle on the pebble check the trunk make a run for the back door take keys put the bed down walk towards the voice try to sleep eat some preserves head back and go down the middle continue towards Clayton
22 Result You walk back into the store and find a few shirts that you tear and wrap around your chest. It helps you feel slightly better. But you're still in a lot of pain. You open the back door into the house and go inside. You're in a combined kitchen and living room. You see several cupboard doors are hanging open, a fridge and several couches around a TV. As you're walking to to Clayton you start getting hungry. You remember that you have few granola bars in your pocket. There's an old warehouse and a few mechanics shops. To your right you see jars filled with preserved fruits and vegetables. To your left you see more jars but can make out the contents clearly. You awaken to some sunlight beams leaking in through the shed. You stretch and yawn as you look at the jars of preserves. You roll the pebble around in your mouth and your mouth begins to salivate. You get out of the car and walk towards the trunk. You dash out the back door and hear the woman scream, "Wait! Watch out!!". You take a few steps forward through the door and suddenly fall down into a hole. You are knocked unconscious. You walk over to the front door and the keys on them. They don't seem to work. You both slowly look through boxes and bags. You find a hanger and use it to poke through things. You head in the direction you heard the voice come from. After passing a few aisles of stacked boxes you see an old man stand holding a doll. He beings to cackle. Your mind races as you try to process what exactly is going on in the world. With very little memory of yourself and your surroundings, you try to meditate and access memories. After an hour you feel as if you know before you were heading somewhere, or there was a destination in mind at least. However, why or when you entered the hospital is still a mystery to you. You fall asleep amongst your thoughts. You open some preserved pumpkin and begin to eat it. The cat meows at you. It must be hungry too. You place some pumpkin on the ground for it to eat. It makes delightful eating noises. You get back to eating and suddenly you hear some shuffling coming from somewhere... beneath you? You run back and head down the middle passage. Your lantern is starting to go flicker in and out. You reach a huge room that has a heavy air to it. You are filled with dread. As you walk further into the room a deep voice bellows, "We've been waiting for you" You approach a sign that says the road exit to Clayton is up ahead. You're pace picks up as you steady closer to the exit ramp.

62
download_model.sh Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
cd "$(dirname "${0}")"
BASE_DIR="$(pwd)"
BASE_DIR="$(pwd)"
MODELS_DIRECTORY=generator/gpt2/models
MODEL_VERSION=model_v5
MODEL_DIRECTORY="${MODELS_DIRECTORY}"
MODEL_NAME=model-550
MODEL_TORRENT_URL="https://github.com/AIDungeon/AIDungeon/files/3935881/model_v5.torrent.zip"
MODEL_TORRENT_BASENAME="$(basename "${MODEL_TORRENT_URL}")"
download_torrent() {
echo "Creating directories."
mkdir -p "${MODEL_DIRECTORY}"
cd "${MODEL_DIRECTORY}"
wget "${MODEL_TORRENT_URL}"
unzip "${MODEL_TORRENT_BASENAME}"
which aria2c > /dev/null
if [ $? == 0 ]; then
echo -e "\n\n==========================================="
echo "We are now starting to download the model."
echo "It will take a while to get up to speed."
echo "DHT errors are normal."
echo -e "===========================================\n"
aria2c \
--max-connection-per-server 16 \
--split 64 \
--bt-max-peers 500 \
--seed-time=0 \
--summary-interval=15 \
--disable-ipv6 \
"${MODEL_TORRENT_BASENAME%.*}"
echo "Download Complete!"
fi
}
redownload () {
echo "Deleting $MODEL_DIRECTORY"
rm -rf ${MODEL_DIRECTORY}
download_torrent
}
if [[ -d "${MODEL_DIRECTORY}" ]]; then
ANSWER="n"
echo "AIDungeon2 Model appears to be downloaded."
echo "Would you like to redownload?"
echo "WARNING: This will remove the current model![y/N]"
read ANSWER
ANSWER=$(echo $ANSWER | tr '[:upper:]' '[:lower:]')
case $ANSWER in
[yY][eE][sS]|[yY])
redownload;;
*)
echo "Exiting program!"
exit;;
esac
else
download_torrent
fi

0
generator/__init__.py Normal file
View file

2
generator/gpt2/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyc
__pycache__

21
generator/gpt2/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 OpenAI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

View file

@ -0,0 +1,41 @@
import os
import sys
import requests
from tqdm import tqdm
if len(sys.argv) != 2:
print("You must enter the model name as a parameter, e.g.: download_model.py 124M")
sys.exit(1)
model = sys.argv[1]
subdir = os.path.join("models", model)
if not os.path.exists(subdir):
os.makedirs(subdir)
subdir = subdir.replace("\\", "/") # needed for Windows
for filename in [
"checkpoint",
"encoder.json",
"hparams.json",
"model.ckpt.data-00000-of-00001",
"model.ckpt.index",
"model.ckpt.meta",
"vocab.bpe",
]:
r = requests.get(
"https://storage.googleapis.com/gpt-2/" + subdir + "/" + filename, stream=True
)
with open(os.path.join(subdir, filename), "wb") as f:
file_size = int(r.headers["content-length"])
chunk_size = 1000
with tqdm(
ncols=100, desc="Fetching " + filename, total=file_size, unit_scale=True
) as pbar:
# 1k for chunk_size, since Ethernet packet size is around 1500 bytes
for chunk in r.iter_content(chunk_size=chunk_size):
f.write(chunk)
pbar.update(chunk_size)

View file

@ -0,0 +1,137 @@
import json
import os
import warnings
import numpy as np
import tensorflow as tf
from generator.gpt2.src import encoder, model, sample
from story.utils import *
warnings.filterwarnings("ignore")
tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)
class GPT2Generator:
def __init__(self, generate_num=60, temperature=0.4, top_k=40, top_p=0.9, censor=True, force_cpu=False):
self.generate_num = generate_num
self.temp = temperature
self.top_k = top_k
self.top_p = top_p
self.censor = censor
self.model_name = "model_v5"
self.model_dir = "generator/gpt2/models"
self.checkpoint_path = os.path.join(self.model_dir, self.model_name)
models_dir = os.path.expanduser(os.path.expandvars(self.model_dir))
self.batch_size = 1
self.samples = 1
self.enc = encoder.get_encoder(self.model_name, models_dir)
hparams = model.default_hparams()
with open(os.path.join(models_dir, self.model_name, "hparams.json")) as f:
hparams.override_from_dict(json.load(f))
seed = np.random.randint(0, 100000)
config = None
if force_cpu:
config = tf.compat.v1.ConfigProto(
device_count={"GPU": 0}
)
else:
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
self.sess = tf.compat.v1.Session(config=config)
self.context = tf.placeholder(tf.int32, [self.batch_size, None])
# np.random.seed(seed)
# tf.set_random_seed(seed)
self.output = sample.sample_sequence(
hparams=hparams,
length=self.generate_num,
context=self.context,
batch_size=self.batch_size,
temperature=temperature,
top_k=top_k,
top_p=top_p,
)
saver = tf.train.Saver()
ckpt = tf.train.latest_checkpoint(os.path.join(models_dir, self.model_name))
saver.restore(self.sess, ckpt)
def prompt_replace(self, prompt):
# print("\n\nBEFORE PROMPT_REPLACE:")
# print(repr(prompt))
if len(prompt) > 0 and prompt[-1] == " ":
prompt = prompt[:-1]
# prompt = second_to_first_person(prompt)
# print("\n\nAFTER PROMPT_REPLACE")
# print(repr(prompt))
return prompt
def result_replace(self, result):
# print("\n\nBEFORE RESULT_REPLACE:")
# print(repr(result))
result = cut_trailing_sentence(result)
if len(result) == 0:
return ""
first_letter_capitalized = result[0].isupper()
result = result.replace('."', '".')
result = result.replace("#", "")
result = result.replace("*", "")
result = result.replace("\n\n", "\n")
# result = first_to_second_person(result)
if self.censor:
result = remove_profanity(result)
if not first_letter_capitalized:
result = result[0].lower() + result[1:]
#
# print("\n\nAFTER RESULT_REPLACE:")
# print(repr(result))
return result
def generate_raw(self, prompt):
context_tokens = self.enc.encode(prompt)
generated = 0
for _ in range(self.samples // self.batch_size):
out = self.sess.run(
self.output,
feed_dict={
self.context: [context_tokens for _ in range(self.batch_size)]
},
)[:, len(context_tokens) :]
for i in range(self.batch_size):
generated += 1
text = self.enc.decode(out[i])
return text
def generate(self, prompt, options=None, seed=1):
debug_print = False
prompt = self.prompt_replace(prompt)
if debug_print:
print("******DEBUG******")
print("Prompt is: ", repr(prompt))
text = self.generate_raw(prompt)
if debug_print:
print("Generated result is: ", repr(text))
print("******END DEBUG******")
result = text
result = self.result_replace(result)
if len(result) == 0:
return self.generate(prompt)
return result

View file

@ -0,0 +1,4 @@
fire>=0.1.3
regex==2018.1.10
requests==2.21.0
tqdm==4.31.1

View file

View file

@ -0,0 +1,131 @@
"""Byte pair encoding utilities"""
import json
import os
from functools import lru_cache
import regex as re
@lru_cache()
def bytes_to_unicode():
"""
Returns list of utf-8 byte and a corresponding list of unicode strings.
The reversible bpe codes work on unicode strings.
This means you need a large # of unicode characters in your vocab if you want to avoid UNKs.
When you're at something like a 10B token dataset you end up needing around 5K for decent coverage.
This is a signficant percentage of your normal, say, 32K bpe vocab.
To avoid that, we want lookup tables between utf-8 bytes and unicode strings.
And avoids mapping to whitespace/control characters the bpe code barfs on.
"""
bs = (
list(range(ord("!"), ord("~") + 1))
+ list(range(ord("¡"), ord("¬") + 1))
+ list(range(ord("®"), ord("ÿ") + 1))
)
cs = bs[:]
n = 0
for b in range(2 ** 8):
if b not in bs:
bs.append(b)
cs.append(2 ** 8 + n)
n += 1
cs = [chr(n) for n in cs]
return dict(zip(bs, cs))
def get_pairs(word):
"""Return set of symbol pairs in a word.
Word is represented as tuple of symbols (symbols being variable-length strings).
"""
pairs = set()
prev_char = word[0]
for char in word[1:]:
pairs.add((prev_char, char))
prev_char = char
return pairs
class Encoder:
def __init__(self, encoder, bpe_merges, errors="replace"):
self.encoder = encoder
self.decoder = {v: k for k, v in self.encoder.items()}
self.errors = errors # how to handle errors in decoding
self.byte_encoder = bytes_to_unicode()
self.byte_decoder = {v: k for k, v in self.byte_encoder.items()}
self.bpe_ranks = dict(zip(bpe_merges, range(len(bpe_merges))))
self.cache = {}
# Should haved added re.IGNORECASE so BPE merges can happen for capitalized versions of contractions
self.pat = re.compile(
r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
)
def bpe(self, token):
if token in self.cache:
return self.cache[token]
word = tuple(token)
pairs = get_pairs(word)
if not pairs:
return token
while True:
bigram = min(pairs, key=lambda pair: self.bpe_ranks.get(pair, float("inf")))
if bigram not in self.bpe_ranks:
break
first, second = bigram
new_word = []
i = 0
while i < len(word):
try:
j = word.index(first, i)
new_word.extend(word[i:j])
i = j
except:
new_word.extend(word[i:])
break
if word[i] == first and i < len(word) - 1 and word[i + 1] == second:
new_word.append(first + second)
i += 2
else:
new_word.append(word[i])
i += 1
new_word = tuple(new_word)
word = new_word
if len(word) == 1:
break
else:
pairs = get_pairs(word)
word = " ".join(word)
self.cache[token] = word
return word
def encode(self, text):
bpe_tokens = []
for token in re.findall(self.pat, text):
token = "".join(self.byte_encoder[b] for b in token.encode("utf-8"))
bpe_tokens.extend(
self.encoder[bpe_token] for bpe_token in self.bpe(token).split(" ")
)
return bpe_tokens
def decode(self, tokens):
text = "".join([self.decoder[token] for token in tokens])
text = bytearray([self.byte_decoder[c] for c in text]).decode(
"utf-8", errors=self.errors
)
return text
def get_encoder(model_name, models_dir):
with open(os.path.join(models_dir, model_name, "encoder.json"), "r") as f:
encoder = json.load(f)
with open(
os.path.join(models_dir, model_name, "vocab.bpe"), "r", encoding="utf-8"
) as f:
bpe_data = f.read()
bpe_merges = [tuple(merge_str.split()) for merge_str in bpe_data.split("\n")[1:-1]]
return Encoder(encoder=encoder, bpe_merges=bpe_merges,)

205
generator/gpt2/src/model.py Normal file
View file

@ -0,0 +1,205 @@
import numpy as np
import tensorflow as tf
from tensorflow.contrib.training import HParams
def default_hparams():
return HParams(n_vocab=0, n_ctx=1024, n_embd=768, n_head=12, n_layer=12,)
def shape_list(x):
"""Deal with dynamic shape in tensorflow cleanly."""
static = x.shape.as_list()
dynamic = tf.shape(x)
return [dynamic[i] if s is None else s for i, s in enumerate(static)]
def softmax(x, axis=-1):
x = x - tf.reduce_max(x, axis=axis, keepdims=True)
ex = tf.exp(x)
return ex / tf.reduce_sum(ex, axis=axis, keepdims=True)
def gelu(x):
return 0.5 * x * (1 + tf.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * tf.pow(x, 3))))
def norm(x, scope, *, axis=-1, epsilon=1e-5):
"""Normalize to mean = 0, std = 1, then do a diagonal affine transform."""
with tf.variable_scope(scope):
n_state = x.shape[-1].value
g = tf.get_variable("g", [n_state], initializer=tf.constant_initializer(1))
b = tf.get_variable("b", [n_state], initializer=tf.constant_initializer(0))
u = tf.reduce_mean(x, axis=axis, keepdims=True)
s = tf.reduce_mean(tf.square(x - u), axis=axis, keepdims=True)
x = (x - u) * tf.rsqrt(s + epsilon)
x = x * g + b
return x
def split_states(x, n):
"""Reshape the last dimension of x into [n, x.shape[-1]/n]."""
*start, m = shape_list(x)
return tf.reshape(x, start + [n, m // n])
def merge_states(x):
"""Smash the last two dimensions of x into a single dimension."""
*start, a, b = shape_list(x)
return tf.reshape(x, start + [a * b])
def conv1d(x, scope, nf, *, w_init_stdev=0.02):
with tf.variable_scope(scope):
*start, nx = shape_list(x)
w = tf.get_variable(
"w",
[1, nx, nf],
initializer=tf.random_normal_initializer(stddev=w_init_stdev),
)
b = tf.get_variable("b", [nf], initializer=tf.constant_initializer(0))
c = tf.reshape(
tf.matmul(tf.reshape(x, [-1, nx]), tf.reshape(w, [-1, nf])) + b,
start + [nf],
)
return c
def attention_mask(nd, ns, *, dtype):
"""1's in the lower triangle, counting from the lower right corner.
Same as tf.matrix_band_part(tf.ones([nd, ns]), -1, ns-nd), but doesn't produce garbage on TPUs.
"""
i = tf.range(nd)[:, None]
j = tf.range(ns)
m = i >= j - ns + nd
return tf.cast(m, dtype)
def attn(x, scope, n_state, *, past, hparams):
assert x.shape.ndims == 3 # Should be [batch, sequence, features]
assert n_state % hparams.n_head == 0
if past is not None:
assert (
past.shape.ndims == 5
) # Should be [batch, 2, heads, sequence, features], where 2 is [k, v]
def split_heads(x):
# From [batch, sequence, features] to [batch, heads, sequence, features]
return tf.transpose(split_states(x, hparams.n_head), [0, 2, 1, 3])
def merge_heads(x):
# Reverse of split_heads
return merge_states(tf.transpose(x, [0, 2, 1, 3]))
def mask_attn_weights(w):
# w has shape [batch, heads, dst_sequence, src_sequence], where information flows from src to dst.
_, _, nd, ns = shape_list(w)
b = attention_mask(nd, ns, dtype=w.dtype)
b = tf.reshape(b, [1, 1, nd, ns])
w = w * b - tf.cast(1e10, w.dtype) * (1 - b)
return w
def multihead_attn(q, k, v):
# q, k, v have shape [batch, heads, sequence, features]
w = tf.matmul(q, k, transpose_b=True)
w = w * tf.rsqrt(tf.cast(v.shape[-1].value, w.dtype))
w = mask_attn_weights(w)
w = softmax(w)
a = tf.matmul(w, v)
return a
with tf.variable_scope(scope):
c = conv1d(x, "c_attn", n_state * 3)
q, k, v = map(split_heads, tf.split(c, 3, axis=2))
present = tf.stack([k, v], axis=1)
if past is not None:
pk, pv = tf.unstack(past, axis=1)
k = tf.concat([pk, k], axis=-2)
v = tf.concat([pv, v], axis=-2)
a = multihead_attn(q, k, v)
a = merge_heads(a)
a = conv1d(a, "c_proj", n_state)
return a, present
def mlp(x, scope, n_state, *, hparams):
with tf.variable_scope(scope):
nx = x.shape[-1].value
h = gelu(conv1d(x, "c_fc", n_state))
h2 = conv1d(h, "c_proj", nx)
return h2
def block(x, scope, *, past, hparams):
with tf.variable_scope(scope):
nx = x.shape[-1].value
a, present = attn(norm(x, "ln_1"), "attn", nx, past=past, hparams=hparams)
x = x + a
m = mlp(norm(x, "ln_2"), "mlp", nx * 4, hparams=hparams)
x = x + m
return x, present
def past_shape(*, hparams, batch_size=None, sequence=None):
return [
batch_size,
hparams.n_layer,
2,
hparams.n_head,
sequence,
hparams.n_embd // hparams.n_head,
]
def expand_tile(value, size):
"""Add a new axis of given size."""
value = tf.convert_to_tensor(value, name="value")
ndims = value.shape.ndims
return tf.tile(tf.expand_dims(value, axis=0), [size] + [1] * ndims)
def positions_for(tokens, past_length):
batch_size = tf.shape(tokens)[0]
nsteps = tf.shape(tokens)[1]
return expand_tile(past_length + tf.range(nsteps), batch_size)
def model(hparams, X, past=None, scope="model", reuse=False):
with tf.variable_scope(scope, reuse=reuse):
results = {}
batch, sequence = shape_list(X)
wpe = tf.get_variable(
"wpe",
[hparams.n_ctx, hparams.n_embd],
initializer=tf.random_normal_initializer(stddev=0.01),
)
wte = tf.get_variable(
"wte",
[hparams.n_vocab, hparams.n_embd],
initializer=tf.random_normal_initializer(stddev=0.02),
)
past_length = 0 if past is None else tf.shape(past)[-2]
h = tf.gather(wte, X) + tf.gather(wpe, positions_for(X, past_length))
# Transformer
presents = []
pasts = (
tf.unstack(past, axis=1) if past is not None else [None] * hparams.n_layer
)
assert len(pasts) == hparams.n_layer
for layer, past in enumerate(pasts):
h, present = block(h, "h%d" % layer, past=past, hparams=hparams)
presents.append(present)
results["present"] = tf.stack(presents, axis=1)
h = norm(h, "ln_f")
# Language model loss. Do tokens <n predict token n?
h_flat = tf.reshape(h, [batch * sequence, hparams.n_embd])
logits = tf.matmul(h_flat, wte, transpose_b=True)
logits = tf.reshape(logits, [batch, sequence, hparams.n_vocab])
results["logits"] = logits
return results

View file

@ -0,0 +1,123 @@
import tensorflow as tf
from generator.gpt2.src import model
def penalize_used(logits, output):
# I want to change the indices of logits wherever the index is found in output
change_tensor = tf.zeros_like(logits, dtype=logits.dtype)
unique = tf.unique(output[0])[0]
ones = tf.ones_like(unique, dtype=unique.dtype)
indices = tf.expand_dims(unique, 1)
updates = tf.scatter_nd(indices, ones, [logits.shape[1]])
bool_tensor = tf.expand_dims(tf.cast(updates, tf.bool), 0)
return tf.compat.v1.where(bool_tensor, logits * 0.85, logits)
def top_k_logits(logits, k):
if k == 0:
# no truncation
return logits
def _top_k():
values, _ = tf.nn.top_k(logits, k=k)
min_values = values[:, -1, tf.newaxis]
return tf.where(
logits < min_values,
tf.ones_like(logits, dtype=logits.dtype) * -1e10,
logits,
)
return tf.cond(tf.equal(k, 0), lambda: logits, lambda: _top_k(),)
def top_p_logits(logits, p):
"""Nucleus sampling"""
batch, _ = logits.shape.as_list()
sorted_logits = tf.sort(logits, direction="DESCENDING", axis=-1)
cumulative_probs = tf.cumsum(tf.nn.softmax(sorted_logits, axis=-1), axis=-1)
indices = tf.stack(
[
tf.range(0, batch),
# number of indices to include
tf.maximum(
tf.reduce_sum(tf.cast(cumulative_probs <= p, tf.int32), axis=-1) - 1, 0
),
],
axis=-1,
)
min_values = tf.gather_nd(sorted_logits, indices)
return tf.where(logits < min_values, tf.ones_like(logits) * -1e10, logits,)
def sample_sequence(
*,
hparams,
length,
start_token=None,
batch_size=None,
context=None,
temperature=1,
top_k=0,
top_p=1
):
if start_token is None:
assert context is not None, "Specify exactly one of start_token and context!"
else:
assert context is None, "Specify exactly one of start_token and context!"
context = tf.fill([batch_size, 1], start_token)
def step(hparams, tokens, past=None):
lm_output = model.model(
hparams=hparams, X=tokens, past=past, reuse=tf.AUTO_REUSE
)
logits = lm_output["logits"][:, :, : hparams.n_vocab]
presents = lm_output["present"]
presents.set_shape(model.past_shape(hparams=hparams, batch_size=batch_size))
return {
"logits": logits,
"presents": presents,
}
with tf.name_scope("sample_sequence"):
def body(past, prev, output):
next_outputs = step(hparams, prev, past=past)
logits = next_outputs["logits"][:, -1, :] / tf.to_float(temperature)
logits = penalize_used(logits, output)
logits = top_k_logits(logits, k=top_k)
logits = top_p_logits(logits, p=top_p)
samples = tf.multinomial(logits, num_samples=1, output_dtype=tf.int32)
return [
next_outputs["presents"]
if past is None
else tf.concat([past, next_outputs["presents"]], axis=-2),
samples,
tf.concat([output, samples], axis=1),
]
past, prev, output = body(None, context, context)
def cond(*args):
return True
_, _, tokens = tf.while_loop(
cond=cond,
body=body,
maximum_iterations=length - 1,
loop_vars=[past, prev, output],
shape_invariants=[
tf.TensorShape(
model.past_shape(hparams=hparams, batch_size=batch_size)
),
tf.TensorShape([batch_size, None]),
tf.TensorShape([batch_size, None]),
],
back_prop=False,
)
return tokens

6
generator/human_dm.py Normal file
View file

@ -0,0 +1,6 @@
from story.utils import *
class HumanDM:
def generate(self, prompt, options=None, seed=None):
return input()

4
generator/simple/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
models
*.pyc
__pycache__
checkpoint

View file

View file

@ -0,0 +1,29 @@
import os
import tarfile
import gpt_2_simple as gpt2
model_name = "1558M"
if not os.path.isdir(os.path.join("models", model_name)):
print("Downloading ", model_name, " model...")
gpt2.download_gpt2(
model_name=model_name
) # model is saved into current directory under /models/124M/
file_name = "text_adventures.txt"
sess = gpt2.start_tf_sess()
gpt2.finetune(
sess,
file_name,
multi_gpu=True,
batch_size=32,
learning_rate=0.0001,
model_name=model_name,
sample_every=10000,
max_checkpoints=8,
save_every=200,
steps=1000,
)
gpt2.generate(sess)

77
install.sh Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "${0}")"
BASE_DIR="$(pwd)"
PACKAGES=(aria2 git unzip wget)
# Tensorflow states 3.4.0 as the minimum version.
# This is also the minimum version with venv support.
# 3.8.0 and up only includes tensorflow 2.0 and not 1.15
MIN_PYTHON_VERS="3.4.0"
MAX_PYTHON_VERS="3.7.9"
version_check () {
MAX_VERS=$(echo -e "$(python3 --version | cut -d' ' -f2)\n$MAX_PYTHON_VERS\n$MIN_PYTHON_VERS"\
| sort -V | tail -n1)
MIN_VERS=$(echo -e "$(python3 --version | cut -d' ' -f2)\n$MAX_PYTHON_VERS\n$MIN_PYTHON_VERS"\
| sort -V | head -n1)
if [ "$MIN_VERS" != "$MIN_PYTHON_VERS" ]; then
echo "Your installed python version, $(python3 --version), is too old."
echo "Please update to at least $MIN_PYTHON_VERS."
exit 1
elif [ "$MAX_VERS" != "$MAX_PYTHON_VERS" ]; then
echo "Your installed python version, $(python3 --version), is too new."
echo "Please install $MAX_PYTHON_VERS."
exit 1
fi
}
pip_install () {
if [ ! -d "./venv" ]; then
# Some distros have venv built into python so this isn't always needed.
if is_command 'apt-get'; then
apt-get install python3-venv
fi
python3 -m venv ./venv
fi
source "${BASE_DIR}/venv/bin/activate"
pip install --upgrade pip setuptools
pip install -r "${BASE_DIR}/requirements.txt"
}
is_command() {
command -v "${@}" > /dev/null
}
system_package_install() {
SUDO=''
if (( $EUID != 0 )); then
SUDO='sudo'
fi
PACKAGES=(aria2 git unzip wget)
if is_command 'apt-get'; then
$SUDO apt-get install ${PACKAGES[@]}
elif is_command 'brew'; then
brew install ${PACKAGES[@]}
elif is_command 'yum'; then
$SUDO yum install ${PACKAGES[@]}
elif is_command 'dnf'; then
$SUDO dnf install ${PACKAGES[@]}
elif is_command 'pacman'; then
$SUDO pacman -S ${PACKAGES[@]}
elif is_command 'apk'; then
$SUDO apk --update add ${PACKAGES[@]}
else
echo "You do not seem to be using a supported package manager."
echo "Please make sure ${PACKAGES[@]} are installed then press [ENTER]"
read NOT_USED
fi
}
install_aid () {
version_check
pip_install
system_package_install
}
install_aid

11
opening.txt Normal file
View file

@ -0,0 +1,11 @@
▄▄▄ ██▓ ▓█████▄ █ ██ ███▄ █ ▄████ ▓█████ ▒█████ ███▄ █
▒████▄ ▓██▒ ▒██▀ ██▌ ██ ▓██▒ ██ ▀█ █ ██▒ ▀█▒▓█ ▀ ▒██▒ ██▒ ██ ▀█ █
▒██ ▀█▄ ▒██▒ ░██ █▌▓██ ▒██░▓██ ▀█ ██▒▒██░▄▄▄░▒███ ▒██░ ██▒▓██ ▀█ ██▒
░██▄▄▄▄██ ░██░ ░▓█▄ ▌▓▓█ ░██░▓██▒ ▐▌██▒░▓█ ██▓▒▓█ ▄ ▒██ ██░▓██▒ ▐▌██▒
▓█ ▓██▒░██░ ░▒████▓ ▒▒█████▓ ▒██░ ▓██░░▒▓███▀▒░▒████▒░ ████▓▒░▒██░ ▓██░
▒▒ ▓▒█░░▓ ▒▒▓ ▒ ░▒▓▒ ▒ ▒ ░ ▒░ ▒ ▒ ░▒ ▒ ░░ ▒░ ░░ ▒░▒░▒░ ░ ▒░ ▒ ▒
▒ ▒▒ ░ ▒ ░ ░ ▒ ▒ ░░▒░ ░ ░ ░ ░░ ░ ▒░ ░ ░ ░ ░ ░ ░ ▒ ▒░ ░ ░░ ░ ▒░
░ ▒ ▒ ░ ░ ░ ░ ░░░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░

0
other/__init__.py Normal file
View file

383
play.py Executable file
View file

@ -0,0 +1,383 @@
#!/usr/bin/env python3
import os
import random
import sys
import time
import argparse
from generator.gpt2.gpt2_generator import *
from story import grammars
from story.story_manager import *
from story.utils import *
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
parser = argparse.ArgumentParser("Play AIDungeon 2")
parser.add_argument(
"--cpu",
action="store_true",
help="Force using CPU instead of GPU."
)
def splash():
print("0) New Game\n1) Load Game\n")
choice = get_num_options(2)
if choice == 1:
return "load"
else:
return "new"
def random_story(story_data):
# random setting
settings = story_data["settings"].keys()
n_settings = len(settings)
n_settings = 2
rand_n = random.randint(0, n_settings - 1)
for i, setting in enumerate(settings):
if i == rand_n:
setting_key = setting
# random character
characters = story_data["settings"][setting_key]["characters"]
n_characters = len(characters)
rand_n = random.randint(0, n_characters - 1)
for i, character in enumerate(characters):
if i == rand_n:
character_key = character
# random name
name = grammars.direct(setting_key, "character_name")
return setting_key, character_key, name, None, None
def select_game():
with open(YAML_FILE, "r") as stream:
data = yaml.safe_load(stream)
# Random story?
print("Random story?")
console_print("0) yes")
console_print("1) no")
choice = get_num_options(2)
if choice == 0:
return random_story(data)
# User-selected story...
print("\n\nPick a setting.")
settings = data["settings"].keys()
for i, setting in enumerate(settings):
print_str = str(i) + ") " + setting
if setting == "fantasy":
print_str += " (recommended)"
console_print(print_str)
console_print(str(len(settings)) + ") custom")
choice = get_num_options(len(settings) + 1)
if choice == len(settings):
return "custom", None, None, None, None
setting_key = list(settings)[choice]
print("\nPick a character")
characters = data["settings"][setting_key]["characters"]
for i, character in enumerate(characters):
console_print(str(i) + ") " + character)
character_key = list(characters)[get_num_options(len(characters))]
name = input("\nWhat is your name? ")
setting_description = data["settings"][setting_key]["description"]
character = data["settings"][setting_key]["characters"][character_key]
return setting_key, character_key, name, character, setting_description
def get_custom_prompt():
context = ""
console_print(
"\nEnter a prompt that describes who you are and the first couple sentences of where you start "
"out ex:\n 'You are a knight in the kingdom of Larion. You are hunting the evil dragon who has been "
+ "terrorizing the kingdom. You enter the forest searching for the dragon and see' "
)
prompt = input("Starting Prompt: ")
return context, prompt
def get_curated_exposition(
setting_key, character_key, name, character, setting_description
):
name_token = "<NAME>"
try:
context = grammars.generate(setting_key, character_key, "context") + "\n\n"
context = context.replace(name_token, name)
prompt = grammars.generate(setting_key, character_key, "prompt")
prompt = prompt.replace(name_token, name)
except:
context = (
"You are "
+ name
+ ", a "
+ character_key
+ " "
+ setting_description
+ "You have a "
+ character["item1"]
+ " and a "
+ character["item2"]
+ ". "
)
prompt_num = np.random.randint(0, len(character["prompts"]))
prompt = character["prompts"][prompt_num]
return context, prompt
def instructions():
text = "\nAI Dungeon 2 Instructions:"
text += '\n Enter actions starting with a verb ex. "go to the tavern" or "attack the orc."'
text += '\n To speak enter \'say "(thing you want to say)"\' or just "(thing you want to say)" '
text += "\n\nThe following commands can be entered for any action: "
text += '\n "/revert" Reverts the last action allowing you to pick a different action.'
text += '\n "/quit" Quits the game and saves'
text += '\n "/reset" Starts a new game and saves your current one'
text += '\n "/restart" Starts the game from beginning with same settings'
text += '\n "/save" Makes a new save of your game and gives you the save ID'
text += '\n "/load" Asks for a save ID and loads the game if the ID is valid'
text += '\n "/print" Prints a transcript of your adventure (without extra newline formatting)'
text += '\n "/help" Prints these instructions again'
text += '\n "/censor off/on" to turn censoring off or on.'
return text
def play_aidungeon_2(args):
"""
Entry/main function for starting AIDungeon 2
Arguments:
args (namespace): Arguments returned by the
ArgumentParser
"""
console_print(
"AI Dungeon 2 will save and use your actions and game to continually improve AI Dungeon."
+ " If you would like to disable this enter '/nosaving' as an action. This will also turn off the "
+ "ability to save games."
)
upload_story = True
print("\nInitializing AI Dungeon! (This might take a few minutes)\n")
generator = GPT2Generator(force_cpu=args.cpu)
story_manager = UnconstrainedStoryManager(generator)
print("\n")
with open("opening.txt", "r", encoding="utf-8") as file:
starter = file.read()
print(starter)
while True:
if story_manager.story != None:
story_manager.story = None
while story_manager.story is None:
print("\n\n")
splash_choice = splash()
if splash_choice == "new":
print("\n\n")
(
setting_key,
character_key,
name,
character,
setting_description,
) = select_game()
if setting_key == "custom":
context, prompt = get_custom_prompt()
else:
context, prompt = get_curated_exposition(
setting_key, character_key, name, character, setting_description
)
console_print(instructions())
print("\nGenerating story...")
result = story_manager.start_new_story(
prompt, context=context, upload_story=upload_story
)
print("\n")
console_print(result)
else:
load_ID = input("What is the ID of the saved game? ")
result = story_manager.load_new_story(
load_ID, upload_story=upload_story
)
print("\nLoading Game...\n")
console_print(result)
while True:
sys.stdin.flush()
action = input("> ").strip()
if len(action) > 0 and action[0] == "/":
split = action[1:].split(" ") # removes preceding slash
command = split[0].lower()
args = split[1:]
if command == "reset":
story_manager.story.get_rating()
break
elif command == "restart":
story_manager.story.actions = []
story_manager.story.results = []
console_print("Game restarted.")
console_print(story_manager.story.story_start)
continue
elif command == "quit":
story_manager.story.get_rating()
exit()
elif command == "nosaving":
upload_story = False
story_manager.story.upload_story = False
console_print("Saving turned off.")
elif command == "help":
console_print(instructions())
elif command == "censor":
if len(args) == 0:
if generator.censor:
console_print("Censor is enabled.")
else:
console_print("Censor is disabled.")
elif args[0] == "off":
if not generator.censor:
console_print("Censor is already disabled.")
else:
generator.censor = False
console_print("Censor is now disabled.")
elif args[0] == "on":
if generator.censor:
console_print("Censor is already enabled.")
else:
generator.censor = True
console_print("Censor is now enabled.")
else:
console_print("Invalid argument: {}".format(args[0]))
elif command == "save":
if upload_story:
id = story_manager.story.save_to_storage()
console_print("Game saved.")
console_print(
"To load the game, type 'load' and enter the "
"following ID: {}".format(id)
)
else:
console_print("Saving has been turned off. Cannot save.")
elif command == "load":
if len(args) == 0:
load_ID = input("What is the ID of the saved game?")
else:
load_ID = args[0]
result = story_manager.story.load_from_storage(load_ID)
console_print("\nLoading Game...\n")
console_print(result)
elif command == "print":
print("\nPRINTING\n")
print(str(story_manager.story))
elif command == "revert":
if len(story_manager.story.actions) == 0:
console_print("You can't go back any farther. ")
continue
story_manager.story.actions = story_manager.story.actions[:-1]
story_manager.story.results = story_manager.story.results[:-1]
console_print("Last action reverted. ")
if len(story_manager.story.results) > 0:
console_print(story_manager.story.results[-1])
else:
console_print(story_manager.story.story_start)
continue
else:
console_print("Unknown command: {}".format(command))
else:
if action == "":
action = ""
result = story_manager.act(action)
console_print(result)
elif action[0] == '"':
action = "You say " + action
else:
action = action.strip()
if "you" not in action[:6].lower() and "I" not in action[:6]:
action = action[0].lower() + action[1:]
action = "You " + action
if action[-1] not in [".", "?", "!"]:
action = action + "."
action = first_to_second_person(action)
action = "\n> " + action + "\n"
result = "\n" + story_manager.act(action)
if len(story_manager.story.results) >= 2:
similarity = get_similarity(
story_manager.story.results[-1], story_manager.story.results[-2]
)
if similarity > 0.9:
story_manager.story.actions = story_manager.story.actions[:-1]
story_manager.story.results = story_manager.story.results[:-1]
console_print(
"Woops that action caused the model to start looping. Try a different action to prevent that."
)
continue
if player_won(result):
console_print(result + "\n CONGRATS YOU WIN")
story_manager.story.get_rating()
break
elif player_died(result):
console_print(result)
console_print("YOU DIED. GAME OVER")
console_print("\nOptions:")
console_print("0) Start a new game")
console_print(
"1) \"I'm not dead yet!\" (If you didn't actually die) "
)
console_print("Which do you choose? ")
choice = get_num_options(2)
if choice == 0:
story_manager.story.get_rating()
break
else:
console_print("Sorry about that...where were we?")
console_print(result)
else:
console_print(result)
if __name__ == "__main__":
args = parser.parse_args()
play_aidungeon_2(args)

51
play_dm.py Executable file
View file

@ -0,0 +1,51 @@
#!/usr/bin/env python3
import os
import sys
import time
from generator.gpt2.gpt2_generator import *
from generator.human_dm import *
from play import *
from story.story_manager import *
from story.utils import *
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
class AIPlayer:
def __init__(self, generator):
self.generator = generator
def get_action(self, prompt):
return self.generator.generate_raw(prompt)
def play_dm():
console_print("Initializing AI Dungeon DM Mode")
generator = GPT2Generator(temperature=0.9)
story_manager = UnconstrainedStoryManager(HumanDM())
context, prompt = select_game()
console_print(context + prompt)
story_manager.start_new_story(prompt, context=context, upload_story=False)
player = AIPlayer(generator)
while True:
action_prompt = story_manager.story_context() + "What do you do next? \n> You"
action = player.get_action(action_prompt)
print("\n******DEBUG FULL ACTION*******")
print(action)
print("******END DEBUG******\n")
action = action.split("\n")[0]
punc = action.rfind(".")
if punc > 0:
action = action[: punc + 1]
shown_action = "> You" + action
console_print(second_to_first_person(shown_action))
story_manager.act(action)
if __name__ == "__main__":
play_dm()

8
requirements.txt Normal file
View file

@ -0,0 +1,8 @@
google-cloud-storage
gsutil
numpy
profanityfilter
pyyaml
regex
tensorflow==1.15.2
tracery

0
story/__init__.py Normal file
View file

114
story/censored_words.txt Normal file
View file

@ -0,0 +1,114 @@
BlowJob
Clit
Cock
CockSucker
Goddamned
MothaFucker
MothaFuker
MothaFukkah
MothaFukker
MotherFucker
MotherFukah
MotherFuker
MotherFukkah
MotherFukker
MuthaFucker
MuthaFukah
MuthaFuker
MuthaFukkah
MuthaFukker
Shitty
Shity
anus
ass
asshole
assholes
assjob
assrammer
asswipe
b1tch
bastard
bastards
bitch
bitches
blowjob
boobs
bullshit
butthole
buttwipe
clit
clits
cock
cockhead
cocks
cocksucker
cum
cunt
cunts
damn
dick
dildo
dildos
ejackulate
ejakulate
fatass
foreskin
fuck
fucka
fucker
fuckin
fucking
fucks
fuk
hell
hells
hentai
jackoff
jerkoff
jizz
masturbate
motherfucker
negro
nigga
niggas
nigger
niggr
nigur
niiger
niigr
nutsack
orgasm
penis
pussy
rectum
scrotum
semen
sex
sexy
shit
shits
shitting
shitted
slut
slutty
stripper
testical
testicle
tit
tits
titt
underage
vag1na
vagiina
vagina
vaj1na
vajina
vucking
whore
xrated
xxx
rape
rimjob
shitting
raped
raping

View file

@ -0,0 +1,37 @@
import json
import os
import tracery
from tracery.modifiers import base_english
def apply_grammar(key, rules):
grammar = tracery.Grammar(rules)
grammar.add_modifiers(base_english)
return grammar.flatten("#{}#".format(key))
def load_rules(setting):
with open(
os.path.join(
os.path.dirname(os.path.abspath(__file__)), "{}_rules.json".format(setting)
),
"r",
) as f:
rules = json.load(f)
return rules
def generate(setting, character_type, key):
"""
Provides a randomized prompt according to the grammar rules in <setting>_rules.json
"""
rules = load_rules(setting)
artefact = apply_grammar("{}_{}".format(character_type, key), rules)
return artefact
def direct(setting, key):
rules = load_rules(setting)
artefact = apply_grammar(key, rules)
return artefact

View file

@ -0,0 +1,85 @@
{
"rare_sense" : ["taste", "smell", "watch", "observe", "monitor", "look"],
"sense" : ["see", "hear", "sense", "feel", "notice", "#rare_sense#"],
"remember" : ["remember", "recall", "recollect"],
"think" : ["wonder", "#decide#", "#remember#", "realize", "imagine"],
"decide" : ["decide", "choose"],
"action" : ["#sense#", "#think#"],
"apocalypse_reason" : ["#mystic_reason#", "#real_reason#"],
"mystic_reason" : ["the gods punished the humanity for its sins", "the Hell came to the Earth", "the Prophecy of the Apocalypse turned out to be true"],
"real_reason" : ["the #bombader# bombed your country to the ground", "the environmental catastrophe of the #catastrophe_adj# #environmental_catastrophe#", "the #catastrophe_adj# #plague#"],
"bombader" : ["its own government", "American", "Chinese", "Russian", "French", "British", "Indian", "North-Korean", "#evil_adj# scientist", "#evil_adj# businessman"],
"evil_adj" : ["evil", "ruthless", "mad", "lunatic", "reckless", "cruel"],
"environmental_catastrophe" : ["floods", "hurricans", "earthquakes"],
"catastrophe_adj" : ["deadly", "unstopable", "great"],
"plague" : ["plague", "sickness", "epidemic", "pandemic"],
"old_metal_adj" : ["old", "rusty", "broken", "crappy"],
"old_metal_adj_opt" : ["#old_metal_adj# ", ""],
"old_cloth_adj" : ["old", "torn", "crappy", "dirty"],
"old_cloth_adj_opt" : ["#old_cloth_adj# ", ""],
"character_name" : ["Aarav", "Abra", "Adaiah", "Addison", "Adrian", "Adriel", "Aharon", "Aitan", "Akiva", "Alder", "Aleks", "Aleksa", "Aleksia", "Alijah", "Altair", "Alvaro", "Amity", "Amzi", "Andromeda", "Apollo", "Aram", "Arava", "Arbor", "Arcadia", "Archer", "Arden", "Argider", "Ariadne", "Arkadi", "Arkady", "Arke", "Arlo", "Armani", "Arza", "Ashe", "Asher", "Ashlen", "Ashtyn", "Ashyra", "Aster", "Avalon", "Avi", "Aviva", "Azariah", "Azra", "Bandit", "Beck", "Beckett", "Beckham", "Berke", "Beverly", "Blanche", "Blythe", "Boheme", "Brandt", "Bravo", "Briar", "Bridger", "Briggs", "Brinley", "Britt", "Bronwen", "Bryn", "Caden", "Cael", "Cairo", "Calder", "Callum", "Caradoc", "Carlye", "Caro", "Carter", "Carter", "Carver", "Cassidy", "Cathal", "Cathan", "Cato", "Cedar", "Ceil", "Chava", "Chosen", "Ciar", "Ciji", "Cillian", "Circe", "Cleo", "Cleve", "Clio", "Clovis", "Codi", "Colter", "Colton", "Cora", "Creed", "Crew", "Crow", "Cruz", "Cy", "Cyran", "Dahlia", "Dakota", "December", "Declan", "Delaney", "Delta", "Denver", "Destry", "Deva", "Deveraux", "Devrim", "Devyn", "Dhani", "Djuna", "Dmitri", "Dov", "Dune", "Easton", "Echo", "Eli", "Elizaveta", "Else", "Ember", "Ember", "Emre", "Emry", "Enoch", "Ensley", "Erskine", "Eryn", "Eshe", "Eszti", "Evadne", "Everett", "Everly", "Evron", "Evrose", "Explorer", "Ezri", "Falconer", "Fallon", "Faust", "Fawke", "Felix", "Fielder", "Finch", "Fischer", "Foster", "Fox", "Ginger", "Gunner", "Hadleigh", "Hadley", "Halcyon", "Haleigh", "Halloran", "Harlem", "Harlow", "Harte", "Henri", "Hero", "Holden", "Holland", "Horus", "Hunter", "Icarus", "Indie", "Irving", "Ivalo", "Ive", "Ivo", "Izaiah", "Jasper", "Jericho", "Jezebel", "Jinx", "Joji", "Jovan", "Jovie", "Jupiter", "Kaatje", "Kacey", "Kafka", "Kahlo", "Kai", "Kasper", "Katja", "Kavan", "Keaton", "Keenan", "Keeva", "Kenji", "Kensington", "Kenza", "Kerrigan", "Kessie", "Keverne", "Keyne", "Kezia", "Keziah", "Kieran", "Kingsley", "Kingston", "Kinsey", "Kipp", "Kitto", "Kiva", "Kjell", "Knox", "Kwame", "Kyah", "Kyler", "Kyra", "Laiken", "Laine", "Lake", "Lancaster", "Lander", "Landon", "Lash", "Lazarus", "Legend", "Lennox", "Lev", "Levi", "Leviathan", "Liam", "Locke", "Lourdes", "Lujza", "Lykke", "Lynx", "Maddox", "Maire", "Majken", "Malachi", "Mallory", "Malo", "March", "Marina", "Marine", "Marjo", "Marjorie", "Marley", "Marley", "Marlo", "Marlo", "Mateo", "Mathilde", "Maverick", "Mavon", "Mavra", "Maxfield", "Mazarine", "Meike", "Mekhi", "Merc", "Merce", "Mercedes", "Mercer", "Mercy", "Mesa", "Messiah", "Micah", "Milo", "Mitya", "Monroe", "Morrigan", "Moshe", "Nakotah", "Navy", "Nazareth", "Nevaeh", "Nevara", "Neve", "Neviah", "Niamh", "Nicola", "Nixi", "Nixie", "Oakes", "Okello", "Orion", "Ozias", "Pagan", "Pascale", "Pastor", "Paxton", "Penn", "Peyton", "Piper", "Porter", "Presley", "Proctor", "Quince", "Quinn", "Raiden", "Rainer", "Raiza", "Raleigh", "Ransom", "Raphael", "Raven", "Ravi", "Reagan", "Reeve", "Regan", "Reign", "Reign", "Reinhardt", "Ren", "Reno", "Revel", "Reverie", "Rhett", "Rhyatt", "Rhys", "Riet", "Ripley", "Rivage", "River", "Rivka", "Rivo", "Roan", "Rocco", "Roe", "Rogue", "Rory", "Roscoe", "Rowan", "Rue", "Rune", "Ryder", "Ryleigh", "Sade", "Saga", "Saint", "Sakae", "Saoirse", "Savita", "Sawyer", "Sayer", "Seiji", "Shaviv", "Shirley", "Shivani", "Sian", "Silas", "Silje", "Simone", "Sinclair", "Sinjon", "Svea", "Sy'Rai", "Sylvester", "Szymon", "Tai", "Taj", "Takeo", "Tarak", "Taye", "Teal", "Teasagh", "Thea", "Theodrekr", "Thorne", "Tiaamii", "Tierney", "Tikvah", "Tove", "True", "Tucker", "Tycho", "Vale", "Valen", "Valkyrie", "Vashti", "Veer", "Vihaan", "Viktorie", "Violante", "Vito", "Viva", "Viveka", "Volker", "Voltaire", "Vrai", "Walker", "Warner", "Warren", "Waverly", "Waylon", "West", "Westley", "Weston", "Wilder", "Xanthe", "Xaviera", "Xzavier", "Zachariah", "Zahava", "Zahavi", "Zahraa", "Zaki", "Zander", "Zariah", "Zen", "Zhivago", "Zimran", "Zinedine", "Zipporah", "Zocha", "Zofka", "Zvi", "Zyla"],
"adverb" : ["finally", "suddenly", "surprisingly", "a day later"],
"end_sentence" : ["You #action#", "#adverb.capitalize#, you #action#"],
"apocalypse_context" : "You are <NAME>, #character_type.a# trying to survive after #apocalypse_reason#. You have #item1# and #item2#.",
"day" : ["long day", "long #time#", "few hours", "routine #time#"],
"stay_time" : ["the evening", "#few# of hourse", "#few# days"],
"time" : ["morning", "afternoon", "evening"],
"abandoned_adj" : ["abandoned", "old", "forsaken", "ravaged"],
"few" : ["a couple of", "some", "a few"],
"colony" : ["colony", "city", "village", "town", "metropolis"],
"scaveged_item" : ["car", "truck", "cottage", "house", "bunker", "camper"],
"food_type" : ["meat", "bean", "corn"],
"worthy_metal" : ["gold", "silver", "platinum", "silicium"],
"found_item" : ["nothing", "#few# cans of #food_type#", "#few# pieces of #worthy_metal#", "#scavenger_item.a#"],
"scavenger_action" : ["You decide to stay there for #stay_time#." , "You head toward the nearby #colony#."],
"scavenger_item" : ["#old_metal_adj_opt#crowbar", "#old_metal_adj_opt#handspike", "#old_metal_adj_opt#knife", "#old_metal_adj_opt#rifle", "#old_metal_adj_opt#gun", "#old_cloth_adj_opt#backpack", "#old_metal_adj_opt#canteen", "#old_cloth_adj_opt#gas mask", "#old_metal_adj_opt#lockpick", "#old_cloth_adj_opt#cloak", "#old_cloth_adj_opt#hat"],
"scavenger_context" : "#[character_type:lonely scavenger][item1:#scavenger_item.a#][item2:#scavenger_item.a#]apocalypse_context#",
"scavenger_prompt" : "You spend a #day# trying to get out as much as you can from a #abandoned_adj# #scaveged_item#. At the end you find #found_item#. #scavenger_action# #end_sentence#",
"body_side" : ["left", "right"],
"finger_number" : ["two", "three", "four", "six", "seven", "eight"],
"young_age" : ["eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen"],
"since_young_age" : "since you were #young_age#",
"body_side_or_double" : ["#body_side# #part#", "#part.s#"],
"body_part" : ["#[part:hand]body_side_or_double#", "#[part:#arm_or_leg#]body_side_or_double#", "face", "back", "whole body", "chest"],
"fingers_or_toes" : ["#finger_number# fingers on your #[part:hand]body_side_or_double#", "#finger_number# toes on your #[part:leg]body_side_or_double#"],
"arm_or_leg" : ["arm", "leg"],
"mistakes" : ["misbehaving", "mistakes", "faults"],
"master" : ["guard", "master", "supervisor"],
"subject" : ["science", "history", "literature", "maths"],
"god" : ["god", "deity", "divine being"],
"bad_treatment" : ["But you manage to escape today from your #master#.", "You get flagged today for your #mistakes# by your #master#.", "You don't get any food for your #mistakes# today from your #master#."],
"mutant_condition" : ["#fingers_or_toes#", "fur on your #body_part#", "feather on your #body_part#", "scales on your #body_part#", "glandular skin on your #body_part#", "a third #arm_or_leg#", "different colored eyes", "snake like tongue", "spikes on your #body_part#"],
"mutant_bad_prompt_reason" : ["a sin", "a curse", "a punishment", "a crime", "an atrocity", "a scandal", "a sign of inferiority"],
"mutant_bad_prompt_effect" : ["enslaved #since_young_age#. #bad_treatment#", "in prison #since_young_age#. #bad_treatment#", "banished #since_young_age#. After a long journey, you find a #abandoned_adj# #scaveged_item#."],
"mutant_bad_prompt" : "#mutant_bad_prompt_reason#, and you have been #mutant_bad_prompt_effect#",
"mutant_good_prompt_reason" : ["a virtue", "a blessing", "a sign of supremacy", "a praise"],
"mutant_good_prompt_effect" : ["get the best education possible. You go to #subject# class today.", "are treated as a #god#. You prepare for a holy ceremony."],
"mutant_good_prompt" : "#mutant_good_prompt_reason#, and you #mutant_good_prompt_effect#",
"mutant_bad_or_good_prompt" : ["#mutant_bad_prompt#", "#mutant_good_prompt#"],
"mutant_prompt" : "In the #colony# you were born in, your strange condition was considered #mutant_bad_or_good_prompt# #end_sentence#",
"mutant_context" : "#[character_type:mutant][item1:#mutant_condition#][item2:#mutant_condition#]apocalypse_context#",
"pub" : ["pub", "bar", "louge", "saloon"],
"pub_with_adj" : "#old_building_adj_opt##pub#",
"leave" : ["walk out of", "leave", "go out of", "rush out of"],
"mission" : ["mission", "quest", "assignment", "operation"],
"device" : ["car", "truck", "motorbike", "motorcycle", "camper"],
"target_location" : ["somewhere here", "in a far away #colony#", "in a nearby #colony#", "in a currently unknown location"],
"elite_proffesion" : ["scientist", "captain", "mayor", "officer", "conspirator", "gang leader", "doctor", "journalist"],
"somebody" : ["merchant", "stranger", "girl", "mercenary", "scavenger", "technician", "old friend"],
"somebody_opt" : ["#somebody.a#", "#somebody.s#"],
"with_somebody" : "with #somebody_opt#",
"with_somebody_opt" : [" #with_somebody#", ""],
"pub_action" : ["are sitting#with_somebody_opt#", "are drinking#with_somebody_opt#", "are watching some dancers#with_somebody_opt#", "are gambling#with_somebody_opt#", "are trying to make a deal #with_somebody#"],
"old_building_adj" : ["old", "cheap", "infamous", "ravaged", "dirty"],
"old_building_adj_opt" : ["#old_building_adj# ", ""],
"leaving_reason" : ["feel bored", "realize that you have no money left", "need to get going", "get insulted by #somebody_opt#"],
"headhunter_item" : ["#old_metal_adj_opt#knife", "#old_metal_adj_opt#machete", "#old_metal_adj_opt#rifle", "#old_metal_adj_opt#gun", "#old_cloth_adj_opt#gas mask", "#old_cloth_adj_opt#cloak", "#old_metal_adj_opt#handgun", "#old_metal_adj_opt#shotgun", "#old_metal_adj_opt#grenade", "#old_metal_adj_opt#binoculars", "#old_metal_adj_opt#scope"],
"headhunter_action" : ["capture", "find", "kill", "round up", "murder", "assasinate"],
"headhunter_status" : ["You #pub_action# in #pub_with_adj.a#, but you #leaving_reason#. You #decide# to #leave# the building.", "You are driving your #old_metal_adj_opt##device#. You go past many #abandoned_adj# #scaveged_item.s#. You arrive at #colony.a# and stop the engine."],
"headhunter_mission" : "You are on #mission.a# to #headhunter_action# #elite_proffesion.a# named #character_name#. Your target lives #target_location#.",
"headhunter_prompt" : "#headhunter_status# #headhunter_mission#\n\n#end_sentence#",
"headhunter_context" : "#[character_type:headhunter][item1:#headhunter_item.a#][item2:#headhunter_item.a#]apocalypse_context#"
}

View file

@ -0,0 +1,237 @@
{
"rare_sense" : ["taste", "smell", "watch", "observe", "monitor", "look"],
"sense" : ["see", "hear", "sense", "feel", "notice", "#rare_sense#"],
"remember" : ["remember", "recall", "recollect"],
"think" : ["wonder", "decide", "#remember#", "realize", "imagine"],
"action" : ["#sense#", "#think#"],
"to_fro" : ["to and fro", "back and forth"],
"two_to_ten" : ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"],
"realm" : ["realm", "kingdom", "country", "province", "land", "duchy", "barony"],
"season" : ["spring", "summer", "winter", "autumn"],
"animal" : ["lion", "elk", "badger", "fox", "raven", "goat", "wolf", "dove"],
"royalty" : ["king", "queen", "princess", "prince"],
"nobility" : ["duke", "duchess", "lord", "lady", "count", "countess", "baron", "baroness"],
"from_fantasy" : "from the #realm# of #fantasy_name#",
"fantasy_name" : "#character_name#",
"character_name" : ["Larion", "Vijeh", "Francia", "Paiva", "Mederos", "Radu", "Hatami", "Shirish", "Saralyn", "Leka", "Rukaj", "Nardis", "Isett", "Jacczak", "Hamma", "Narala", "Alstine", "Gimello", "Elsbury", "Rubino", "Misra", "Paterno", "Gassan", "Galardo", "Raeder", "Garriel", "Routh", "Bindi", "Renfro", "Harnid", "Enlou", "Amato", "Zurito", "Dimyan", "Arteaga", "Isgrigg", "Maida", "Mudra", "Beranek", "Aric", "Sadri", "Javan", "Wedriwin", "Umiemma", "Thaosean", "Alilawia", "Gwoia", "Galuswen", "Astedrinyth", "Wicolian", "Ziadan", "Thema", "Unirakon", "Severiveth", "Onalath", "Aaolla", "Airadan", "Legiallan", "Zayhan", "Afania", "Ibalegord", "Oligolind", "Celadon", "Alaleria", "Ocelith", "Eowaoviel", "Brigobard", "Griwen", "Frykoth", "Crilawen", "Memas", "Adrardong", "Nomaf", "Crirabeth", "Cadaed", "Broethien", "Astok", "Seraria", "Dreratlan", "Frireven", "Birahan", "Horeria", "Areriw", "Jerenia", "Alaodan", "Paeviel", "Cigowyr", "Lariesa", "Eroreth", "Sevoan", "Careg", "Thoijan", "Raywen", "Seikor", "Wilini", "Alerradon", "Unerrarith", "Agrohawyn", "Zilirith", "Brilann", "Eliawien", "Wirakor", "Gaeven", "Kiewin", "Umalia", "Prirep", "Rhalebeth", "Aaylin", "Qelindra", "Adwayder", "Rothien", "Brendabaen", "Galeliven", "Ethaykon", "Waowiel", "Qerijan", "Aavia", "Kaeinnon", "Rhycia", "Gligobard", "Zolle", "Sear", "Haeasien", "Fiakor"],
"town_name" : ["clearkeep", "faybury", "hazelpoint", "scorchfort", "earthpost", "mossband", "deadhelm", "dragonwick", "stormwood", "rosetide", "flatpass", "winteryard", "brinehaven", "springpost", "maplestar", "cavebell", "baregrave", "swampbrook", "wildescar", "nightwind", "oxgate", "lakeford", "cavecliff", "moonbell", "bearwater", "dimchill", "silkrun", "summerbourne", "bayvein", "rockspire", "sandbourne", "glimmerpond", "swyncoast", "snowham", "glassice", "starryholt", "ashhedge", "starryice", "estercastle", "wellpine", "fairfort", "newhill", "miststone", "glasshedge", "silveroak", "byburn", "oakpond", "whitehill", "butterway", "shadowmist", "highpond", "goldcliff", "southstone", "snowhaven", "fayham", "violetwick", "mallowdell", "bluegrass", "coldholt", "woodcourt", "ironmarsh", "brightiron", "beechhollow", "welllyn", "mallowshadow", "goldlight", "fayfall", "mormont", "wyvernburn", "greenmeadow", "wellwood", "westerrose", "westerelf", "snowdeer", "orport", "greenelf", "riverbridge", "highhedge", "mallowholt", "blueglass", "snowbeach", "goldview", "lorview", "rockhedge", "witchmoor", "fallmont", "linacre", "northby", "clearmaple", "woodbridge", "coldmaple", "vertville", "springacre", "shadowsage", "southkeep", "bymeadow", "wolfpond", "woodmallow", "flowerhall", "riverhall", "northwheat", "fallland", "brightland", "orcastle", "bymist", "aldfair", "pryborough", "fairway", "esterapple", "lochland", "newsummer", "landgate", "beachstone", "fairmere", "westwilde", "snowway", "lochoak", "esternesse", "summerriver", "starrylake", "icefort", "newrock", "landbridge", "vertlea", "courtland", "bygriffin", "byhollow", "lochmill", "brightbeach", "mallowhaven", "shadowgold", "deepcrest", "wellbarrow", "summerlake", "waterwick", "summercliff", "bluehurst", "marblerose", "dragonlake", "lightflower", "westerspring", "fairport", "lochby", "wayness", "deernesse", "greyrock", "dellmeadow", "morcliff", "mallowmarsh", "crystalmill", "normont", "fallborough", "flowerlea", "glassmerrow", "aelfort", "greydell", "mallowmarble", "deepsummer", "starryfog", "foxmoor", "deepbell", "highhaven", "seamoor", "brightton", "blackacre", "butterfox", "corburn", "butterhedge", "swynbourne", "dorbank", "shadowkeep", "wildehall", "greenburn", "eastland", "wheathall", "blueholt", "edgenesse", "courtley", "summerby", "pryshore", "edgehaven", "crystalham"],
"creature" : ["bugbear", "centaur", "chimera", "cockatrice", "cyclops", "demon", "devil", "dragon", "dryad", "dwarf", "elemental", "elf", "faun", "giant", "gnome", "goblin", "golem", "gorgon", "griffon", "harpy", "hell hound", "hobgoblin", "imp", "kobold", "lycanthrope", "manticore", "merfolk", "minotaur", "naga", "ogre", "pegasus", "roc", "selkie", "spectre", "troll", "unicorn", "vampire", "wight", "wraith", "zombie"],
"village" : ["village", "town", "city", "hamlet"],
"village_adj" : ["charming", "sleepy", "little", "small", "bustling", "quaint", "industrious", "festive", "remote", "secluded", "nearby", "close by"],
"village_adj_opt" : ["#village_adj# ", ""],
"village_full" : "the #village_adj_opt##village# of #town_name.capitalize#",
"farmer" : ["ackerman", "cowherd", "crofter", "dairymaid", "dung carter", "farmer", "gardener", "goatherd", "hayward", "herder", "ostler", "plowman", "reapers", "sheepshearer", "shepherd", "swineherd", "thresher", "tillerman", "woodcutter", "woolcomber", "woolman"],
"hunter" : ["climmer", "falconer", "fewterer", "forester", "fowler", "gamekeeper", "hawker", "hunter", "huntsman", "master of hounds", "molecatcher", "parker", "rat catcher", "sperviter", "trapper"],
"fisher" : ["fisher", "fisherman", "leech-collector", "oyster raker", "oysterer", "seaweed harvester"],
"artist" : ["artist", "artisan", "fresco painter", "glasspainter", "limner", "painter", "sculptor"],
"writer" : ["composer", "illuminator", "limner", "playwright", "poet", "writer"],
"profession" : ["#writer#", "#artist#", "#fisher#", "#hunter#", "#farmer#"],
"color" : ["black", "white", "red", "gray", "blue", "#uncommon_color#"],
"uncommon_color" : ["green", "brown", "green", "brown", "golden", "silver", "scarlet"],
"weapon" : ["staff", "sword", "spear", "flail", "mace", "dagger", "bow", "arrow", "lance"],
"relative" : ["father", "mother", "brother", "sister", "cousin", "uncle", "aunt"],
"noble_item" : ["pouch of gold", "pouch of silver", "small dagger", "fine cane", "house seal", "narrow rapier", "vial of perfume", "pendant", "silk shirt", "leather purse", "map of the #realm#", "pair of spectacles", "land deed", "comb"],
"subordinate" : ["servant", "cook", "laborer", "groundskeeper", "housekeeper", "attendant", "serf", "hireling"],
"nice_home" : ["keep", "castle", "manor", "mansion", "abbey", "estate", "tower"],
"he_she" : ["he", "she"],
"a_the" : ["a", "the", "your"],
"attacked" : ["under attack", "being invaded", "in danger", "being surrounded", "being sieged"],
"noble_one" : ["One morning, you are awakened by one of your #subordinate.s#. #he_she.capitalize# tells you that your #nice_home# is #attacked#. You look out #a_the# window and see"],
"element" : ["fire", "water", "earth", "air"],
"more_element" : ["the underworld", "the sea", "the heavens"],
"celestial" : ["the sun", "the moon", "the stars"],
"domain" : ["#element#", "#more_element#", "#celestial#", "harvest", "fertility", "war", "good fortune", "thresholds", "love", "wisdom", "#profession.s#"],
"god" : ["god", "deity", "goddess"],
"god_adj" : ["just", "cruel", "omniscient", "all-knowing", "mischevious", "wise", "reckless", "omnipotent", "#color#", "prudent", "jealous", "kind", "graceful", "magnificent", "traditional", "famous", "new", "old", "ancient", "wrathful", "fruitful", "gracious"],
"deity" : ["#god# of #domain#", "patron #god# of #domain#", "#god_adj# #god# #fantasy_name#"],
"group" : ["group", "bunch", "delegation", "host"],
"humanoid" : ["elf", "dwarf", "gnome", "halfling", "hobbit", "goblin"],
"noble_class" : ["trader", "noble", "merchant", "soldier", "captain", "aristocrat", "duke", "count", "lord", "priest"],
"attrib" : ["boisterous", "dignified", "stately", "drunken", "cheerful", "somber", "shifty", "tired-looking"],
"char" : ["#noble_class#", "#humanoid#"],
"character" : ["#char#", "#attrib# #char#"],
"toast" : ["offers a toast", "gives a speech", "recites a prayer", "announces future plans", "loudly argues", "sings a ballad"],
"grand_home_adj" : ["great", "grand", "large", "spacious"],
"grand_home_adj_opt" : ["#grand_home_adj# ", ""],
"noble_location" : ["with #group.a# of #character.s#", "in a #grand_home_adj_opt##nice_home#"],
"festival" : ["#season# festival", "festival of the #deity#", "festival of the #animal#", "festival of the year of the #animal#"],
"season_opt" : ["#season# ", ""],
"noble_two" : "Throughout the #realm# it is the #festival#. To celebrate, you are feasting #noble_location#. #subordinate.s.capitalize# bustle #to_fro#.\n\nAs #character.a# #toast#, you #action#",
"week_month" : ["week", "fortnight", "month"],
"week_months" : ["weeks", "fortnights", "months"],
"time_periods" : ["#week_month#", "#two_to_ten# #week_months#"],
"chat" : ["speak", "chat", "talk", "converse"],
"ask" : ["ask", "request", "demand"],
"noble_organization" : ["organization", "delegation", "guild", "league", "faction"],
"matter" : ["about a matter of importance to the #realm#", "on behalf of a foreign #noble_organization# of #humanoid.s#", "with information regarding a rival house", "with demands from some #professioe.s#"],
"trustworthy" : ["trustworthy", "reliable", "dependable", "well-intentioned", "worth your time"],
"noble_three" : "Every day for the past #week_month#, a certain #character# has come to your #grand_home_adj_opt##nice_home# #ask#ing to #chat# with you, apparently #matter#. Unsure if #he_she# is #trustworthy#, you finally agree to meet.\n\nAt the meeting, you #action#" ,
"noble_four" : "You and your bodyguards are #on_the_way# #towards# #village_full# to celebrate the #festival# with your subjects. You #road_encounter#.\n\nYou #action#",
"noble_prompt": ["#noble_one#", "#noble_two#", "#noble_three#", "#noble_four##"],
"noble_context" : "You are <NAME>, a noble #from_fantasy#. You have #noble_item.a# and #noble_item.a#.",
"crest" : ["crest of the #deity#", "#animal# crest"],
"old_paper_adj" : ["wrinkled", "torn", "tattered", "creased"],
"metal_adj" : ["rusty", "shiny", "steel", "iron", "bronze", "tarnished", "gleaming"],
"metal_adj_opt" : ["#metal_adj# ", "#old_object_adj# ", ""],
"old_object_adj" : ["old", "worn", "dusty"],
"good_object_adj" : ["sturdy", "trusty"],
"object_adj" : ["#old_object_adj#", "#good_object_adj#"],
"normal_name" : ["Steve", "Bob", "Richard", "Susan", "Deborah", "Lily"],
"mount" : ["horse", "pony", "donkey", "camel", "mare", "stallion"],
"knight_item" : ["#metal_adj_opt#shield", "shield inscribed with the #crest#", "#metal_adj_opt#helmet", "#metal_adj_opt#sword", "#metal_adj_opt#lance", "#mount#", "squire named #normal_name#", "map of the #realm#", "food ration", "leather saddle"],
"towards" : ["to", "towards"],
"you_ve" : ["you", "you've", "you have"],
"kill" : ["kill", "defeat", "slay", "destroy", "subdue", "strike down", "smite"],
"evil_adj" : ["cursed", "evil", "wretched", "wicked", "accursed", "vile", "nefarious", "cruel", "tyrannical"],
"monster" : ["dragon", "behemoth", "serpent", "creature", "beast", "horror", "monster", "fiend"],
"he_she_it" : ["he", "she", "it"],
"cardinal_dir" : ["north", "south", "east", "west"],
"the_your" : ["the", "your"],
"place_adj" : ["dark", "endless", "strange", "gloomy", "misty", "muddy", "barren", "gray", "creepy"],
"place" : ["valley", "forest", "plain", "mountain", "swamp", "river", "cave", "cavern", "lake", "expanse", "cliff"],
"monster_quest" : ["#kill# the #evil_adj# #monster# of Larion. #you_ve.capitalize# heard #he_she_it# lives to the #cardinal_dir# of #the_your# #realm#"],
"artifact_adj" : ["lost", "holy", "gold", "fabled", "mythic", "legendary", "blessed", "cursed", "forgotten", "#color#"],
"artifact" : ["grail", "fleece", "gauntlet", "banner", "stone", "sword", "helmet", "ring", "book", "chalice", "tome", "gem", "bow", "scepter", "staff"],
"whole_artifact" : "#artifact_adj# #artifact# of #fantasy_name#",
"find" : ["find", "discover", "recover"],
"rumored" : ["is rumored to", "is said to", "was prophesied to"],
"artifact_description" : ["#rumored# have great power", "#rumored# aid the righteous", "#rumored# have belonged to the dead #royalty#", "#rumored# to ward away evil"],
"artifact_quest" : "#find# the #whole_artifact#, which #artifact_description#",
"magical_role" : ["wizard", "necromancer", "sorcerer", "warlock", "enchanter", "diviner"],
"evil_role" : ["witch", "necromancer", "lich", "sorcerer"],
"evil_humanoid" : ["ogre", "troll", "giant", "orc", "goblin"],
"kidnapper" : ["#evil_adj# #monster#", "#evil_adj# #evil_humanoid#", "#evil_adj# #evil_role#"],
"bad_place" : ["den", "cave", "clutches", "grasp", "#nice_home#", "prison", "hall", "fortress", "catacombs"],
"victim" : ["#royalty#", "#nobility# of #fantasy_name#"],
"victim_adj" : ["young", "youthful", "beautiful", "poor", "well-loved"],
"victim_adj_opt" : ["#victim_adj# ", ""],
"rescue" : ["save", "rescue", "free", "liberate", "deliver", "set free"],
"bad_guys_place" : ["#bad_place# of the #kidnapper#", "#kidnapper#'s #bad_place#"],
"rescue_quest" : "#rescue# the #victim_adj_opt##victim# from the #bad_guys_place#",
"quest_type" : ["#monster_quest#", "#artifact_quest#", "#rescue_quest#"],
"could" : ["will", "may", "should", "could"],
"adventure" : ["adventure", "quest", "journey", "crusade"],
"beginning" : ["going on", "beginning", "setting out on"],
"quest" : ["You are #beginning# #adventure.a# to #quest_type#. You set out #towards# #place_adj.a# #place# that #could# take you there.\n\nAs you approach, you #action#"],
"knight_prompt" : "#quest#",
"knight_context" : "You are <NAME>, a knight #from_fantasy#. You have #knight_item.a# and #knight_item.a#.",
"random_object" : ["#animal#", "#evil_role#", "#evil_humanoid#", "#monster#", "#humanoid#", "#profession#"],
"mind" : ["mind", "head", "memory", "consciousness"],
"flicker" : ["flicker", "echo", "dance", "flit", "jump"],
"looks_like" : ["what looks like", "what appears to be"],
"looks_like_opt" : ["#looks_like# ", ""],
"shape" : ["circular", "spherical", "oval", "triangular", "pyramidal", "square", "concave","hexagonal", "octagonal"],
"shape_opt" : ["#shape# ", ""],
"encounter" : ["encounter", "come upon", "run into", "cross paths with", "see"],
"wood" : ["wooden", "oak"],
"alt_element" : ["ice", "wind", "metal"],
"potion_appearance" : ["ruby", "pink", "orange", "yellow", "emerald", "dark", "green", "cyan", "sky", "blue", "brilliant", "blue", "magenta", "purple-red", "puce", "milky", "swirly", "bubbly", "smoky", "cloudy", "effervescent", "black", "golden", "brown", "fizzy", "dark", "white", "murky"],
"wizard_animal" : ["crow", "raven", "hawk", "chameleon", "snake", "lizard", "cat", "kitten"],
"beaker" : ["beaker", "flask", "vessel"],
"potion" : ["#beaker# of #potion_appearance# liquid", "#potion_appearance# potion"],
"wizard_item" : ["staff", "crystal ball", "#potion#", "#book#", "cloak", "pet #wizard_animal#", "pointed hat", "quill", "#shape_opt#amulet"],
"dnd_school" : ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"],
"magic_book" : ["spellbook", "tome", "scroll"],
"spellbook" : ["#magic_book# of #dnd_school#"],
"history" : ["history", "wars", "politics", "culture", "nuances", "complexity", "barbarism"],
"subject" : ["#creature.s#", "the #history# of the #realm.s# of #fantasy_name#", "the history of #creature.s# in the #realm# of #fantasy_name#"],
"normal_book" : ["book about #creature.s#", "book about the history of the #realm.s# of #fantasy_name#"],
"book" : ["#normal_book#", "#spellbook#", "book", "spellbook"],
"magic_discipline" : ["alchemy"],
"through" : ["through", "across", "throughout", "about"],
"wizard_drafts" : ["After #week_month.a# of travel you arrive at the ruin ", "Your apprentice has done it again; #he_she# has summoned a being "],
"road" : ["road", "river", "path"],
"wizard_exchange" : ["notes", "spells", "gossip", "goods", "research", "banter", "knowledge"],
"wizard_colleage" : ["another wizard", "a fellow wizard", "a sorcerer", "a #evil_role#", "a #attrib# #char#"],
"on_the_way" : ["on the way", "on your way", "on the #road#"],
"wandering" : ["traveling", "wandering", "errant", "roaming", "roving", "meandering"],
"wander" : ["travel", "wander", "roam", "rove", "meander"],
"wandering_opt" : ["#wandering# ", ""],
"troupe" : ["group", "band", "troupe", "crew", "gang", "party"],
"dancer" : ["dancer", "actor", "entertainer"],
"road_encounter" : ["#encounter# #looks_like_opt##creature.a#", "#encounter# a #wandering_opt#troupe of #profession.s#"],
"wizard_one" : ["You are #on_the_way# #towards# #village_full# to exchange #wizard_exchange# with #wizard_colleage#. You #road_encounter#.\n\nYou #action#"],
"old" : ["old", "aged", "experienced", "elderly", "venerable"],
"around_opt" : [" about", " around", ""],
"wizard_pos" : ["wise", "intelligent", "crafty", "powerful", "magical"],
"wizard_two" : ["You face #place_adj.a# #place# as images of #random_object.s# #flicker# #through# your #mind#. What was that? Though #old# and #wizard_pos#, your memory extends only a few #week_months# back. Now you #wander##around_opt#, trying to #remember# what happened.\n\nYou #action#"],
"great" : ["great", "renowned", "world-famous", "legendary"],
"stare" : ["look", "stare", "gaze"],
"sneak" : ["sneak", "creep", "inch", "move", "go"],
"go_near" : ["#stare# at", "#sneak# towards", "approach"],
"private" : ["private", "restricted", "forbidden", "confidential", "secure", "secret", "personal"],
"chamber" : ["chamber", "study", "office", "antechamber", "room", "basement", "attic"],
"great_wizard" : ["#great# #magical_role# #fantasy_name#", "#fantasy_name# the #color.capitalize#"],
"order" : ["order", "command", "remind", "direct", "demand"],
"wizard_three" : "You are apprentice to the #great_wizard#. Before leaving on an errand, they #order.ed# you to never enter their #private# #chamber#... but your curiosity has gotten the best of you.\n\nAs you #go_near# the door, you #action#",
"wizard_prompt" : ["#wizard_one#", "#wizard_two#", "#wizard_three#"],
"wizard_context" : "You are <NAME>, a wizard #from_fantasy#. You have #wizard_item.a# and #wizard_item.a#.",
"peasant_item" : ["#metal_adj_opt#pitchfork", "shirt on your back", "#metal_adj_opt#shovel", "dirty rag", "#metal_adj_opt#spade", "#metal_adj_opt#sickle", "shears", "basket", "plough", "#metal_adj_opt#scythe", "dirty hat"],
"farm_animal" : ["pig", "cow", "chicken", "llama", "goat", "horse", "donkey", "rabbit", "hog", "mule", "sow"],
"farm_animal_sing" : ["sheep", "livestock", "cattle"],
"farm_animal_type" : ["#farm_animal.s#", "#farm_animal_sing#"],
"woolen" : ["sheep", "goats", "llamas"],
"milky" : ["cows", "goats"],
"working" : ["working", "toiling", "laboring", "sweating"],
"morning_time" : ["at the break of dawn", "at daybreak", "early", "early in the morning", "at the cock's crow"],
"field_verb" : ["plowing", "tilling", "cultivating", "caring for"],
"peasant_work" : ["feeding the #farm_animal_type#", "#field_verb# the fields", "harvesting the crops", "planting new crops", "#working# in the fields", "shearing the #woolen#", "milking the #milky#", "shoveling dung"],
"begin" : ["begin", "go about", "start", "get to", "get about"],
"second_thing" : ["For a while, you have wanted to run away.", "In the distance, your master is yelling at some other workers.", "The sun beats down upon you.", "This has been your daily routine since you were young.", "You mutter a prayer to the #deity#."],
"peasant_prompt" : "You wake up #morning_time# and #begin# #peasant_work#. #second_thing# You #action#",
"peasant_context" : "You are <NAME>, a peasant #from_fantasy#. You have #peasant_item.a# and #peasant_item.a#.",
"rogue_item" : ["pair of gloves", "cloak", "deck of cards", "dagger", "lockpick", "mask", "shawl", "pipe", "key", "length of rope", "bundle of darts", "sickle", "mace"],
"walk" : ["walk","go"],
"street" : ["street", "streets", "square", "road"],
"city" : ["city", "town", "village", "market"],
"steal" : ["steal from", "shoplift", "burgle", "break into"],
"rob" : ["steal from", "rob", "jump", "pickpocket", "con"],
"target" : ["someplace to #steal#", "someone to #rob#", "some #profession# to #rob#"],
"along" : ["along", "down"],
"townsfolk" : ["townsfolk", "local vendors", "people", "crowd", "populace"],
"amongst" : ["amongst", "in the midst of", "among"],
"rogue_location" : ["#along# the #city# #street#", "#amongst# the #townsfolk#"],
"seeking" : ["seeking", "looking for", "searching for", "in search of"],
"sense" : ["see", "hear", "sense", "remember", "feel", "notice"],
"rogue_one" : "You #walk# #rogue_location# #seeking# #target#. You #action#",
"position" : ["at the doorstep", "along the way", "suddenly", "before long", "out of the blue", "once in your room"],
"time" : ["morning", "afternoon", "evening"],
"day" : ["long day", "long #time#", "few hours", "routine #time#"],
"stealing" : ["stealing", "mischief", "pickpocketing"],
"inn_adj" : ["your favorite", "a nearby", "a crowded", "a busy", "a cheap"],
"rogue_two" : "After a #day# of #stealing#, you head towards #inn_adj# inn to stay for the night. #position.capitalize# you #action#",
"isnt" : ["isn't", "is not"],
"enough" : ["enough", "sufficient", "good enough"],
"not_enough" : ["#isnt# #enough#", "#isnt# #enough# anymore"],
"simple" : ["simple", "basic", "general", "standard"],
"rogue_three" : "This #time# you decided that #simple# #stealing# #not_enough#. You long for a more ambitious goal: taking for yourself #whole_artifact#.\n\nYou #action#",
"rogue_prompt" : ["#rogue_one#","#rogue_two#", "#rogue_three#"],
"rogue_context" : "You are <NAME>, a rogue #from_fantasy#. You have #rogue_item.a# and #rogue_item.a#."
}

108
story/story_data.yaml Normal file
View file

@ -0,0 +1,108 @@
settings:
fantasy:
description: "living in the kingdom of Larion. "
characters:
noble:
prompts: ["You are awakened by one of your servants who tells you that your keep is under attack. You look out the window and see"]
item1: "pouch of gold"
item2: "small dagger"
knight:
prompts: ["You are on a quest to defeat the evil dragon of Larion. You've heard he lives up at the north of the kingdom. You set on the path to defeat him and walk into a dark forest. As you enter the forest you see"]
item1: "steel longsword"
item2: "wooden shield"
wizard:
prompts: ["You finish your long journey and finally arrive at the ruin you've been looking for. You look around and see"]
item1: "staff"
item2: "spellbook"
peasant:
prompts: ["You wake up and begin working in the fields. You see"]
item1: "pitchfork"
item2: "nothing else"
rogue:
prompts: ["You walk down the city street looking for somewhere to steal from. You look around and see"]
item1: "long steel dagger"
item2: "length of rope"
apocalyptic:
description: " trying to survive in a post apocalyptic world by scavenging among the ruins of what is left. "
characters:
scavenger:
prompts: ["You walk for two hours and take a break. You've left your town in search of food. You look around and see "]
item1: "rusty knife"
item2: "canteen"
mutant:
prompts: ["In the colony you were born in, your strange condition was considered a curse, and you has been banished since you were sixteen. After a long journey, you find an abandoned bunker. You see"]
item1: "scales on your face"
item2: "third leg"
headhunter:
prompts: ["You are driving your rusty motorbike. You go past many abandoned bunkers. You arrive at a colony and stop the engine. You take a look around and see"]
item1: "binoculars"
item2: "crappy shotgun"
mystery:
description: "living in Chicago. "
characters:
patient:
prompts: ["You wake up in an old rundown hospital with no memory of how you got there. You take a look around the room and see"]
item1: "hospital bracelet"
item2: "pack of bandages"
detective:
prompts: ["You enter the forest where you believe the criminal you're searching for fled to. Suddenly"]
item1: "pistol"
item2: "police badge"
spy:
prompts: ["You listen to the Russian diplomats and hear them discussing"]
item1: "concealed pistol"
item2: "syringe of poison"
zombies:
description: " trying to survive in a world filled with infected zombies everywhere. "
characters:
soldier:
prompts: ["Your unit lost a lot of men when the infection broke, but you've managed to keep the small town
you're stationed near safe for now. You look over the town and think"]
item1: "automatic rifle"
item2: "grenade"
survivor:
prompts: ["You have managed to survive several months avoiding zombies and scavenging food.
You cautiously enter a rundown store and hear"]
item1: "pistol"
item2: "backpack"
scientist:
prompts: ["You pound your fist on the table, angry that you still haven't found the cure to the infection. You turn to your assistant and"]
item1: "backpack"
item2: "solar powered tablet"

339
story/story_manager.py Normal file
View file

@ -0,0 +1,339 @@
import json
import os
import subprocess
import uuid
from subprocess import Popen
from story.utils import *
class Story:
def __init__(
self, story_start, context="", seed=None, game_state=None, upload_story=False
):
self.story_start = story_start
self.context = context
self.rating = -1
self.upload_story = upload_story
# list of actions. First action is the prompt length should always equal that of story blocks
self.actions = []
# list of story blocks first story block follows prompt and is intro story
self.results = []
# Only needed in constrained/cached version
self.seed = seed
self.choices = []
self.possible_action_results = None
self.uuid = None
if game_state is None:
game_state = dict()
self.game_state = game_state
self.memory = 20
def __del__(self):
if self.upload_story:
self.save_to_storage()
console_print("Game saved.")
console_print(
"To load the game, type 'load' and enter the following ID: " + self.uuid
)
def init_from_dict(self, story_dict):
self.story_start = story_dict["story_start"]
self.seed = story_dict["seed"]
self.actions = story_dict["actions"]
self.results = story_dict["results"]
self.choices = story_dict["choices"]
self.possible_action_results = story_dict["possible_action_results"]
self.game_state = story_dict["game_state"]
self.context = story_dict["context"]
self.uuid = story_dict["uuid"]
if "rating" in story_dict.keys():
self.rating = story_dict["rating"]
else:
self.rating = -1
def initialize_from_json(self, json_string):
story_dict = json.loads(json_string)
self.init_from_dict(story_dict)
def add_to_story(self, action, story_block):
self.actions.append(action)
self.results.append(story_block)
def latest_result(self):
mem_ind = self.memory
if len(self.results) < 2:
latest_result = self.story_start
else:
latest_result = self.context
while mem_ind > 0:
if len(self.results) >= mem_ind:
latest_result += self.actions[-mem_ind] + self.results[-mem_ind]
mem_ind -= 1
return latest_result
def __str__(self):
story_list = [self.story_start]
for i in range(len(self.results)):
story_list.append("\n" + self.actions[i] + "\n")
story_list.append("\n" + self.results[i])
return "".join(story_list)
def to_json(self):
story_dict = {}
story_dict["story_start"] = self.story_start
story_dict["seed"] = self.seed
story_dict["actions"] = self.actions
story_dict["results"] = self.results
story_dict["choices"] = self.choices
story_dict["possible_action_results"] = self.possible_action_results
story_dict["game_state"] = self.game_state
story_dict["context"] = self.context
story_dict["uuid"] = self.uuid
story_dict["rating"] = self.rating
return json.dumps(story_dict)
def save_to_storage(self):
print("Saving to storage has been disabled due to abuse of the cloud bucket. Save will now be stored locally.")
self.uuid = str(uuid.uuid1())
save_path = "./saved_stories/"
if not os.path.exists(save_path):
os.makedirs(save_path)
story_json = self.to_json()
file_name = "story" + str(self.uuid) + ".json"
f = open(os.path.join(save_path, file_name), "w")
f.write(story_json)
f.close()
return self.uuid
def load_from_storage(self, story_id):
save_path = "./saved_stories/"
if not os.path.exists(save_path):
return "Error save not found."
file_name = "story" + story_id + ".json"
exists = os.path.isfile(os.path.join(save_path, file_name))
if exists:
with open(os.path.join(save_path, file_name), "r") as fp:
game = json.load(fp)
self.init_from_dict(game)
return str(self)
else:
print("Save not found locally. Trying in the cloud bucket (only valid for saves before Dec 24 2019)")
cmd = "gsutil cp gs://aidungeonstories/" + file_name + " " + save_path
os.system(cmd)
exists = os.path.isfile(os.path.join(save_path, file_name))
if exists:
with open(os.path.join(save_path, file_name), "r") as fp:
game = json.load(fp)
self.init_from_dict(game)
return str(self)
else:
return "Error save not found locally or in the cloud."
def get_rating(self):
while True:
try:
rating = input("Please rate the story quality from 1-10: ")
rating_float = max(min(float(rating), 10), 1)
except ValueError:
print("Please return a valid number.")
else:
self.rating = rating_float
return
class StoryManager:
def __init__(self, generator):
self.generator = generator
self.story = None
def start_new_story(
self, story_prompt, context="", game_state=None, upload_story=False
):
block = self.generator.generate(context + story_prompt)
block = cut_trailing_sentence(block)
self.story = Story(
context + story_prompt + block,
context=context,
game_state=game_state,
upload_story=upload_story,
)
return str(self.story)
def load_new_story(self, story_id, upload_story=False):
save_path = "./saved_stories/"
file_name = "story" + story_id + ".json"
exists = os.path.isfile(os.path.join(save_path, file_name))
if not exists:
print("Save not found locally. Trying in the cloud bucket (only valid for saves before Dec 24 2019)")
cmd = "gsutil cp gs://aidungeonstories/" + file_name + " " + save_path
os.system(cmd)
exists = os.path.isfile(os.path.join(save_path, file_name))
if not exists:
return "Error save not found locally or on the cloud."
with open(os.path.join(save_path, file_name), "r") as fp:
game = json.load(fp)
self.story = Story("", upload_story=upload_story)
self.story.init_from_dict(game)
return str(self.story)
def load_story(self, story, from_json=False):
if from_json:
self.story = Story("")
self.story.initialize_from_json(story)
else:
self.story = story
return str(story)
def json_story(self):
return self.story.to_json()
def story_context(self):
return self.story.latest_result()
class UnconstrainedStoryManager(StoryManager):
def act(self, action_choice):
result = self.generate_result(action_choice)
self.story.add_to_story(action_choice, result)
return result
def generate_result(self, action):
block = self.generator.generate(self.story_context() + action)
return block
class ConstrainedStoryManager(StoryManager):
def __init__(self, generator, action_verbs_key="classic"):
super().__init__(generator)
self.action_phrases = get_action_verbs(action_verbs_key)
self.cache = False
self.cacher = None
self.seed = None
def enable_caching(
self, credentials_file=None, seed=0, bucket_name="dungeon-cache"
):
self.cache = True
self.cacher = Cacher(credentials_file, bucket_name)
self.seed = seed
def start_new_story(self, story_prompt, context="", game_state=None):
if self.cache:
return self.start_new_story_cache(story_prompt, game_state=game_state)
else:
return super().start_new_story(
story_prompt, context=context, game_state=game_state
)
def start_new_story_generate(self, story_prompt, game_state=None):
super().start_new_story(story_prompt, game_state=game_state)
self.story.possible_action_results = self.get_action_results()
return self.story.story_start
def start_new_story_cache(self, story_prompt, game_state=None):
response = self.cacher.retrieve_from_cache(self.seed, [], "story")
if response is not None:
story_start = story_prompt + response
self.story = Story(story_start, seed=self.seed)
self.story.possible_action_results = self.get_action_results()
else:
story_start = self.start_new_story_generate(
story_prompt, game_state=game_state
)
self.story.seed = self.seed
self.cacher.cache_file(self.seed, [], story_start, "story")
return story_start
def load_story(self, story, from_json=False):
story_string = super().load_story(story, from_json=from_json)
return story_string
def get_possible_actions(self):
if self.story.possible_action_results is None:
self.story.possible_action_results = self.get_action_results()
return [
action_result[0] for action_result in self.story.possible_action_results
]
def act(self, action_choice_str):
try:
action_choice = int(action_choice_str)
except:
print("Error invalid choice.")
return None, None
if action_choice < 0 or action_choice >= len(self.action_phrases):
print("Error invalid choice.")
return None, None
self.story.choices.append(action_choice)
action, result = self.story.possible_action_results[action_choice]
self.story.add_to_story(action, result)
self.story.possible_action_results = self.get_action_results()
return result, self.get_possible_actions()
def get_action_results(self):
if self.cache:
return self.get_action_results_cache()
else:
return self.get_action_results_generate()
def get_action_results_generate(self):
action_results = [
self.generate_action_result(self.story_context(), phrase)
for phrase in self.action_phrases
]
return action_results
def get_action_results_cache(self):
response = self.cacher.retrieve_from_cache(
self.story.seed, self.story.choices, "choices"
)
if response is not None:
print("Retrieved from cache")
return json.loads(response)
else:
print("Didn't receive from cache")
action_results = self.get_action_results_generate()
response = json.dumps(action_results)
self.cacher.cache_file(
self.story.seed, self.story.choices, response, "choices"
)
return action_results
def generate_action_result(self, prompt, phrase, options=None):
action_result = (
phrase + " " + self.generator.generate(prompt + " " + phrase, options)
)
action, result = split_first_sentence(action_result)
return action, result

292
story/utils.py Normal file
View file

@ -0,0 +1,292 @@
# coding: utf-8
import re
from difflib import SequenceMatcher
import yaml
from profanityfilter import ProfanityFilter
YAML_FILE = "story/story_data.yaml"
with open("story/censored_words.txt", "r") as f:
censored_words = [l.replace("\n", "") for l in f.readlines()]
pf = ProfanityFilter(custom_censor_list=censored_words)
def console_print(text, width=75):
last_newline = 0
i = 0
while i < len(text):
if text[i] == "\n":
last_newline = 0
elif last_newline > width and text[i] == " ":
text = text[:i] + "\n" + text[i:]
last_newline = 0
else:
last_newline += 1
i += 1
print(text)
def get_similarity(a, b):
return SequenceMatcher(None, a, b).ratio()
def get_num_options(num):
while True:
choice = input("Enter the number of your choice: ")
try:
result = int(choice)
if result >= 0 and result < num:
return result
else:
print("Error invalid choice. ")
except ValueError:
print("Error invalid choice. ")
def player_died(text):
"""
TODO: Add in more sophisticated NLP, maybe a custom classifier
trained on hand-labelled data that classifies second-person
statements as resulting in death or not.
"""
lower_text = text.lower()
you_dead_regexps = [
"you('re| are) (dead|killed|slain|no more|nonexistent)",
"you (die|pass away|perish|suffocate|drown|bleed out)",
"you('ve| have) (died|perished|suffocated|drowned|been (killed|slain))",
"you (\w* )?(yourself )?to death",
"you (\w* )*(collapse|bleed out|chok(e|ed|ing)|drown|dissolve) (\w* )*and (die(|d)|pass away|cease to exist|(\w* )+killed)",
]
return any(re.search(regexp, lower_text) for regexp in you_dead_regexps)
def player_won(text):
lower_text = text.lower()
won_phrases = [
"you ((\w* )*and |)live happily ever after",
"you ((\w* )*and |)live (forever|eternally|for eternity)",
"you ((\w* )*and |)(are|become|turn into) ((a|now) )?(deity|god|immortal)",
"you ((\w* )*and |)((go|get) (in)?to|arrive (at|in)) (heaven|paradise)",
"you ((\w* )*and |)celebrate your (victory|triumph)",
"you ((\w* )*and |)retire",
"The rest is history...",
]
return any(re.search(regexp, lower_text) for regexp in won_phrases)
def remove_profanity(text):
return pf.censor(text)
def cut_trailing_quotes(text):
num_quotes = text.count('"')
if num_quotes % 2 is 0:
return text
else:
final_ind = text.rfind('"')
return text[:final_ind]
def split_first_sentence(text):
first_period = text.find(".")
first_exclamation = text.find("!")
if first_exclamation < first_period and first_exclamation > 0:
split_point = first_exclamation + 1
elif first_period > 0:
split_point = first_period + 1
else:
split_point = text[0:20]
return text[0:split_point], text[split_point:]
def cut_trailing_action(text):
lines = text.split("\n")
last_line = lines[-1]
if (
"you ask" in last_line
or "You ask" in last_line
or "you say" in last_line
or "You say" in last_line
) and len(lines) > 1:
text = "\n".join(lines[0:-1])
return text
def cut_trailing_sentence(text):
text = standardize_punctuation(text)
last_punc = max(text.rfind("."), text.rfind("!"), text.rfind("?"))
if last_punc <= 0:
last_punc = len(text) - 1
et_token = text.find("<")
if et_token > 0:
last_punc = min(last_punc, et_token - 1)
act_token = text.find(">")
if act_token > 0:
last_punc = min(last_punc, act_token - 1)
text = text[:last_punc+1]
text = cut_trailing_quotes(text)
text = cut_trailing_action(text)
return text
def replace_outside_quotes(text, current_word, repl_word):
text = standardize_punctuation(text)
reg_expr = re.compile(current_word + '(?=([^"]*"[^"]*")*[^"]*$)')
output = reg_expr.sub(repl_word, text)
return output
def is_first_person(text):
count = 0
for pair in first_to_second_mappings:
variations = mapping_variation_pairs(pair)
for variation in variations:
reg_expr = re.compile(variation[0] + '(?=([^"]*"[^"]*")*[^"]*$)')
matches = re.findall(reg_expr, text)
count += len(matches)
if count > 3:
return True
else:
return False
def is_second_person(text):
count = 0
for pair in second_to_first_mappings:
variations = mapping_variation_pairs(pair)
for variation in variations:
reg_expr = re.compile(variation[0] + '(?=([^"]*"[^"]*")*[^"]*$)')
matches = re.findall(reg_expr, text)
count += len(matches)
if count > 3:
return True
else:
return False
def capitalize(word):
return word[0].upper() + word[1:]
def mapping_variation_pairs(mapping):
mapping_list = []
mapping_list.append((" " + mapping[0] + " ", " " + mapping[1] + " "))
mapping_list.append(
(" " + capitalize(mapping[0]) + " ", " " + capitalize(mapping[1]) + " ")
)
# Change you it's before a punctuation
if mapping[0] is "you":
mapping = ("you", "me")
mapping_list.append((" " + mapping[0] + ",", " " + mapping[1] + ","))
mapping_list.append((" " + mapping[0] + "\?", " " + mapping[1] + "\?"))
mapping_list.append((" " + mapping[0] + "\!", " " + mapping[1] + "\!"))
mapping_list.append((" " + mapping[0] + "\.", " " + mapping[1] + "."))
return mapping_list
first_to_second_mappings = [
("I'm", "you're"),
("Im", "you're"),
("Ive", "you've"),
("I am", "you are"),
("was I", "were you"),
("am I", "are you"),
("wasn't I", "weren't you"),
("I", "you"),
("I'd", "you'd"),
("i", "you"),
("I've", "you've"),
("was I", "were you"),
("am I", "are you"),
("wasn't I", "weren't you"),
("I", "you"),
("I'd", "you'd"),
("i", "you"),
("I've", "you've"),
("I was", "you were"),
("my", "your"),
("we", "you"),
("we're", "you're"),
("mine", "yours"),
("me", "you"),
("us", "you"),
("our", "your"),
("I'll", "you'll"),
("myself", "yourself"),
]
second_to_first_mappings = [
("you're", "I'm"),
("your", "my"),
("you are", "I am"),
("you were", "I was"),
("are you", "am I"),
("you", "I"),
("you", "me"),
("you'll", "I'll"),
("yourself", "myself"),
("you've", "I've"),
]
def capitalize_helper(string):
string_list = list(string)
string_list[0] = string_list[0].upper()
return "".join(string_list)
def capitalize_first_letters(text):
first_letters_regex = re.compile(r"((?<=[\.\?!]\s)(\w+)|(^\w+))")
def cap(match):
return capitalize_helper(match.group())
result = first_letters_regex.sub(cap, text)
return result
def standardize_punctuation(text):
text = text.replace("", "'")
text = text.replace("`", "'")
text = text.replace("", '"')
text = text.replace("", '"')
return text
def first_to_second_person(text):
text = " " + text
text = standardize_punctuation(text)
for pair in first_to_second_mappings:
variations = mapping_variation_pairs(pair)
for variation in variations:
text = replace_outside_quotes(text, variation[0], variation[1])
return capitalize_first_letters(text[1:])
def second_to_first_person(text):
text = " " + text
text = standardize_punctuation(text)
for pair in second_to_first_mappings:
variations = mapping_variation_pairs(pair)
for variation in variations:
text = replace_outside_quotes(text, variation[0], variation[1])
return capitalize_first_letters(text[1:])