Skip to content

Instantly share code, notes, and snippets.

@darthschmoo
Last active May 15, 2020 18:10
Show Gist options
  • Save darthschmoo/00b56bdee43347e794091a91732e1f18 to your computer and use it in GitHub Desktop.
Save darthschmoo/00b56bdee43347e794091a91732e1f18 to your computer and use it in GitHub Desktop.
Ruby Random Epic Fantasy Title Generator, Take II
#!/usr/bin/env ruby
# TODOS: move lists into configuration file, plurals, better finding/replacing of variables
class LineParser
def without_comments( line )
line.gsub( /#.*/, "" )
end
end
class ListParser < LineParser
def self.parse( text )
self.new.parse( text )
end
def parse( text )
return text if text.is_a?( Hash )
{}.tap do |lists|
current_list = nil
for line in text.split("\n")
case without_comments( line )
when /^\s+$/ # empty line
# do nothing
when /^\S/ # no space, move on to next key
current_list = line.strip
lists[current_list] ||= []
when /^\s+\S/
lists[current_list] ||= []
lists[current_list] << line.strip
end
end
end
end
end
class TemplateParser < LineParser
def self.parse( text )
self.new.parse( text )
end
# each line represents one template
def parse( text )
[].tap do |templates|
for line in text.split( "\n" )
case without_comments( line )
when /^\s*$/
# empty line, do nothing
else
templates << line
end
end
end
end
end
class TitleGenerator
VERSION = "0.0.2"
def initialize( opts = { templates: [], lists: {} } )
@templates = parse_templates( opts[:templates] )
@lists = parse_lists( opts[:lists] )
# @pluralizer = Pluralizer.new
end
def parse_templates( t )
TemplateParser.parse( t )
end
def parse_lists( l )
ListParser.parse( l )
end
def print_titles( n = 1 )
n.times do
puts self.title
end
end
def title
templ = pick( templates )
titl = post_fix( fill_in( templ ) )
if debugging?
puts "TITLE: #{ titl.inspect }"
puts "\n\n---------------------------------------"
end
titl
end
def variable_substitute( s )
tags = s.scan( /\<[A-Z0-9_|]+\>/ )
for tag in tags
list = lookup( tag )
choice = pick( list ) || "" # returns nil when the given list is empty
puts " #{s.inspect} ---> #{choice.inspect}" if debugging?
s = s.gsub( tag, choice )
end
s
end
def fill_in( template )
puts " TEMPLATE: #{template}" if debugging?
rval = template.split.map{ |s|
variable_substitute( s )
}.join( " " )
if rval.include?( "<" ) # Still have variables to fill, so send it through another round of replacements
fill_in( rval )
else
rval
end
end
def post_fix( t )
# fixing these the stupid way
t = t.gsub( " ,", "," )
t = t.gsub( " 's", "'s" )
a_to_an( t )
end
def a_to_an( t )
original_chunks = t.split( /\s+/ )
final_chunks = []
(original_chunks.length - 1).times do |i|
chunk = original_chunks[i]
if chunk.downcase == "a"
next_chunk = original_chunks[i + 1]
first_letter = next_chunk[0].downcase
vowel = %w(a e i o u).include?( first_letter )
final_chunks << (vowel ? chunk + "n" : chunk )
else
final_chunks << chunk
end
end
final_chunks << original_chunks.last
rval = final_chunks.join( " " )
rval
end
# Look up the items in the list(s) <THING|OTHER_THING|THIRD_THING>
def lookup( s )
key = s[1..-2]
keys_to_list( key.split( "|" ))
end
def keys_to_list( keys )
keys.map{|k|
if lists.has_key?( k )
lists[k]
else
warn( "Missing list: #{k.inspect}" )
[]
end
}.flatten
end
def templates
@templates
end
# Randomly pick a single element out of an array
def pick( list )
list[ rand( list.length) ]
end
def lists
@lists
end
def debugging?
false
end
end
templates = "<EPIC_FANTASY_TITLE>"
lists = "
EPIC_FANTASY_TITLE
<A|THE|NULL> <S0> of <S1> and <S1>
<A|THE|NULL> <ADJ0|NULL> <S0> of <ADJ0> <S1>
<S0> of the <ADJ0> <S1>
<ADJ0> <S1> and <ADJ0> <S1>
<A|THE> <PLACE|PERSON|SEASON> of <ADJ0> <ANIMALS>
<A|THE> <ADJ0> <PERSON>
<OBJECT_PHRASE> , <OBJECT_PHRASE>
<A|THE|NULL> <S0> of <S1> <SERIES>
<S0> of the <PLACE>
<PLACE|PERSON> of <ELEMENT|EMOTION|JEWEL>
<A|THE> <ADJ_ATTRIBUTE|ADJ_MISC> <S0>
The <PERSON|ITEM> in the <ENVIRONMENT|PLACE>
A <TIME> of <S1>
The <TIME> of the <ADJ0|NULL> <S0> <SERIES_PHRASE>
The <ADJ0> <S0> Trilogy
<ENVIRONMENT|ELEMENT|RACE><BRINGER> <SERIES_PHRASE>
The <EVENT> of <PLACE_PHRASE>
The <LORE> of the <COUNT2> <PLACE|RANK|MAGICIAN>
The <LORE> of the <COUNT> <PLACE|RANK|MAGICIAN>s
<S0> <SERIES_PHRASE>
TIME
Time
Day
Year
Hour
Century
Age
LORE
Tale
Saga
Legend
Scroll
NULL
# Empty
A
A
THE
The
GENERIC_PERSON
Man
Woman
Child
RELATION
Daughter
Son
Mother
Father
RANK
King
Queen
Prince
Duke
Knight
Earl
Apprentice
Master
OCCUPATION
Blacksmith
Courtesan
General
Innkeper
Lorekeeper
Pirate
Prophet
Soldier
Traveler
Thief
Warlord
RACE
Centaur
Demon
Dragon
Dwarf
Elf
God
Goddess
Gorgon
Ghost
Gryphon
Orc
Mermaid
Titan
Werewolf
Vampire
MAGICIAN
Adept
Lich
Necromancer
Oracle
Seer
Sorcerer
Sorceress
Witch
Wizard
Warlock
PERSON
<GENERIC_PERSON|RELATION|RANK|OCCUPATION|RACE|MAGICIAN>
ENVIRONMENT
Desert
Field
Forest
Mountain
Swamp
Jungle
River
Valley
PLACE
Abbey
Citadel
City
Court
Empire
Forge
Gate
Grave
Graveyard
# Home (not really working)
Isle
Keep
Kingdom
Land
Monastery
Palace
Path
Realm
Refuge
Ruins
Tavern
Tomb
Tower
Void
Wasteland
ITEM
Blade
Book
Crown
Curse
Dagger
Fire
Fires
Flames
Mirror
Relic
Robe
Scroll
Scepter
Star
Sword
Throne
Wish
EVENT
Peace
War
Battle
Council
Death
Siege
S0
<PERSON|PLACE|ENVIRONMENT|ITEM|ANIMAL|EVENT|TIME|LORE>
ANIMAL
Basilisk
Bear
Cormorant
Crow
Dragon
Fox
Hawk
Horse
Lion
Raven
Wolf
ANIMALS
Basilisks
Bears
Cormorants
Crows
Dragons
Foxes
Hawks
Horses
Lions
Ravens
Wolves
ELEMENT
Earth
Air
Fire
Flame
Water
Wind
Sky
Iron
Steel
Stone
Lightning
Storm
Ice
Cold
Blood
Smoke
Bone
Glass
Fog
Ash
Embers
Night
Light
Rain
EMOTION
Joy
Sadness
Dream
Nightmares
Rage
Sleep
Terror
Evil
Revenge
SEASON
Summer
Winter
DIRECTION
North
South
East
West
UNKNOWN
Culling
Fate
Darkness
Twilight
S1
<ANIMALS|ELEMENT|EMOTION|SEASON|DIRECTION>
PERSON_PHRASE
<PERSON>
<EMOTION|ELEMENT|ADJ0> <PERSON>
JEWEL
Amethyst
Diamond
Emerald
Obsidian
Opal
Ruby
ADJ_CHANGE
Falling
Rising
Brightening
Darkening
Waning
Ascending
Gathering
ADJ_ATTRIBUTE
Ancient
Barren
Beguiling
Blighted
Holy
Unholy
Corrupted
Consecrated
Sacred
Desolated
Living
Ethereal
Accursed
Cursed
Blessed
Royal
ADJ_MISC
Avenging
Burning
Coming
Endless
Unending
Encircling
Forgotten
Magic
Magical
Lost
Lunar
Towering
Windswept
ADJ_ELEMENT
Fiery
Watery
Earthen
Stony
Ashen
Stormy
Icy
Frozen
Bloody
Empty
Chaos
Smoky
ADJ_COLOR
Black
Cerulean
Crimson
Dark
Golden
Red
Sable
Silver
White
ADJ0
<ADJ_CHANGE|ADJ_ATTRIBUTE|ADJ_MISC|JEWEL|ADJ_ELEMENT|ADJ_COLOR|ELEMENT>
OBJECT_PHRASE
<PERSON_PHRASE>
<ADJ0> <S0>
<ADJ_COLOR> <PERSON>
<PERSON> 's <ADJ0> <PERSON>
BRINGER
born
bringer
cursed
marked
blade
seeker
slayer
sworn
thorn
wing
COUNT
Two
Three
Four
Five
Six
Seven
Eight
Nine
COUNT2
First
Second
Third
Fourth
Fifth
COUNT3
One
Two
Three
PLACE_PHRASE
<ADJ_ATTRIBUTE|ADJ_MISC|JEWEL|ADJ_ELEMENT|ADJ_COLOR|ELEMENT> <PLACE>
the <DIRECTION>ern <PLACE>
<ANIMAL|OCCUPATION|RACE>'s <PLACE>
SERIES
Trilogy
Cycle
Series
Epic
SERIES_NAME
<PERSON_PHRASE>
<PLACE_PHRASE>
<S0> of <ADJ0>
SERIES_PHRASE
(Book <COUNT3> of the <SERIES_NAME> Trilogy)
(a Novel of the <SERIES_NAME> <SERIES>)
(next in the <SERIES_NAME> <SERIES>)
<NULL>
<NULL>
<NULL>
"
TitleGenerator.new( templates: templates, lists: lists ).print_titles( (ARGV[0] || 10).to_i )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment