Python Activities#
The first group of collaborative activities give students opportunities to connect secondary data structures and control structures in Python for a variety of creative text applications. The first focuses on text-adventure games, while the second focuses on creative text generation.
Text-Adventure Games#
Earlier in the semester, we used one of our Thursday collaborative work sessions to explore command-line text games.
Bashcrawl (version of the 1970s game Colossal Cave Adventure)
Andrew Petro’s BashVenture (a few different room-based scenarios)
We can use Python’s control structures to implement similar kinds of game experiences. A few preliminary examples:
Activity Steps#
Step #1: Explore some of the sample Python game programs.
You’ll want to run the entire program as a script. One option is download the source file and opening in a console IDE. The code below will download the game files and run the script in a notebook.
!wget https://raw.githubusercontent.com/sheena1010/creativecodinginpython/master/AdventureGame.py
!python3 AdventureGame.py
!wget https://raw.githubusercontent.com/makeuseofcode/python-adventure-game/main/AdventureGame.py
!python3 AdventureGame.py.2
!wget https://raw.githubusercontent.com/mrshareware/MojaveSpringsAdventure/main/MojaveSpringsAdv.py
!python3 MojaveSpringsAdv.py
Step #2: Brainstorm some ideas for something you could build in Python! Given our time constraints, don’t worry about actually building out an entire program. Think more conceptually about a pseudocode outline. You’re welcome to use the sample programs or other documentation as a starting point.
Step #3: A good place to start would be with pseudocode. What do you want your program to do? How can we map that onto Python control structures?
Start with a word problem, or a narrative description of the task
What is the goal
What are the parameters or constraints
Any particular commands or syntax you’re asked to use
Start breaking down the tasks into steps, thinking about how programming languages (specifically Python) approach logic
Input/output, variables, operators (arithmetic, comparison)
What are the programming building blocks that connect to aspects of the task
BUT still write things out in plain language (not programming syntax)
Continue breaking the task into steps
The goal is one programming step (or statement) per line
Step #4: See where you can get with building out a program.
Step #5: Continue to work on debugging and testing your program, switching up group roles as needed.
Step #5: Spend a few minutes as a group discussing and reflecting on the experience. What challenges did your group encounter, and how did you solve them? Other comments or observations?
Poetry Generation#
We’ve seen a few different examples of Python text generation programs, from madlibs to Dadaist poem construction to other computational approaches to poetry.
Past Examples#
Byron Madlib#
import random # import random module
terms = {
'verbs': ['runs', 'crawls', 'begs', 'rides', 'sings'], # list of verbs
'prepositions': ['in', 'on', 'under', 'over', 'through'], # list of prepositions
'adjectives' : ['small', 'warm', 'angry', 'happy'], # list of adjectives
'nouns' : ['cloud', 'terrace', 'castle', 'woods']
}
# poem madlib
print("She " + random.choice(terms['verbs']) + " in beauty, like the " + random.choice(terms['nouns']) + "\n Of " +
random.choice(terms['adjectives']) + " " + random.choice(terms['nouns']) + " and " + random.choice(terms['adjectives']) + " " + random.choice(terms['nouns']))
Dadaist Poem#
Avant-garde 20th century Poet Tristan Tzara developed a framework for making a Dadaist poem (circa 1920). We can implement a similar approach in Python using list methods. The program below is based on Allison Parrish’s implementation, but you can modify the original text and output format to make your own creation!
# based on: https://www.writing.upenn.edu/~afilreis/88v/tzara.html
import random, textwrap # import Python modules
# paragraph of text
newspaper = """
Dada was an informal international movement, with
participants in Europe and North America. The
beginnings of Dada correspond to the outbreak of
World War I. For many participants, the movement
was a protest against the bourgeois nationalist
and colonialist interests, which many Dadaists
believed were the root cause of the war, and
against the cultural and intellectual
conformity — in art and more broadly in
society — that corresponded to the war."""
words = newspaper.split() # split newspaper variable into list of words
random.shuffle(words) # shuffle list of words
print(textwrap.fill(" ".join(words), 60)) # recreate paragraph from shuffled list
New Forms#
Let’s look at some other creative works that connect concepts we’ve seen in recent chapters.
# based on: https://github.com/aparrish/rwet/blob/master/some-poetry-generators.ipynb & https://nickm.com/memslam/the_house_of_dust.html
import random # import random
materials = [ # list of materials
'brick',
'broken dishes',
'discarded clothing',
'dust',
'glass',
'leaves',
'mud',
'paper',
'plastic',
'roots',
'sand',
'steel',
'stone',
'straw',
'tin',
'weeds',
'wood'
]
locations = [ # list of locations
'among high mountains',
'among other houses',
'among small hills',
'by a river',
'by an abandoned lake',
'by the sea',
'in a cold, windy climate',
'in a deserted airport',
'in a deserted church',
'in a deserted factory',
'in a green, mossy terrain',
'in a hot climate',
'in a metropolis',
'in a place with both heavy rain and bright sun',
'in an overpopulated area',
'in dense woods',
'in heavy jungle undergrowth',
'in japan',
'in michigan',
'in southern france',
'inside a mountain',
'on an island',
'on the sea',
'underwater'
]
lights = [ # list of lighting options
'all available lighting',
'candles',
'electricity',
'natural light'
]
inhabitants = [ # list of inhabitant options
'all races of men represented wearing predominantly red clothing',
'children and old people',
'collectors of all types',
'fishermen and families',
'french and german speaking people',
'friends',
'friends and enemies',
'horses and birds',
'little boys',
'lovers',
'people from many walks of life',
'people speaking many languages wearing little or no clothing',
'people who eat a great deal',
'people who enjoy eating together',
'people who love to read',
'people who sleep almost all the time',
'people who sleep very little',
'various birds and fish',
'vegetarians',
'very tall people'
]
stanza_count = 7 # number of stanzas for the poem
for i in range(stanza_count): # create number of stanzas
print()
print("A house of " + random.choice(materials))
print(" " + random.choice(locations))
print(" using " + random.choice(lights))
print(" inhabited by " + random.choice(inhabitants))
Taroko Gorge#
Nick Montfort’s “Taroko Gorge,” as described by the author in The Electronic Literature Collection Volume Three:
“Taroko Gorge originated as a Python program that I developed at Taroko Gorge National Park in Taiwan. If others could go to a place of natural beauty and write a poem about that place, why couldn't I write a poetry generator, instead? Less esoteric than much of my other work, and aimed at evocative description rather than provocation or parody, this generator forms strophes that begin and end with a "path" line and may have one or more more static "site" lines in between. Between each pair of such strophes is a "cave" line that trails off, as if into darkness, like the tunnels in the park that were carved by Chiang Kai-shek's Nationalist army. The generated text, which is produced limitlessly, is pleasing to read, perhaps, but it seems at least as enjoyable to carve into the code and shape the program to represent different experiences and ideas.”
Montfort’s Python program uses a combination of lists, functions, some conditional statements, and for loops (along with a lot of concatenation and string work). The program below is based on Allison Parrish’s implementation. Folks can also explore the variety of electronic literature works that have remixed “Taroko Gorge.”
# source: https://github.com/aparrish/rwet/blob/master/some-poetry-generators.ipynb
import random
# create various term lists
above = ['brow', 'mist', 'shape', 'layer', 'the crag', 'stone', 'forest', 'height']
below = ['flow', 'basin', 'shape', 'vein', 'rippling', 'stone', 'cove', 'rock']
transitive = ['command', 'pace', 'roam', 'trail', 'frame', 'sweep', 'exercise', 'range']
imperative = ['track', 'shade', 'translate', 'stamp', 'progress through', 'direct', 'run', 'enter']
intransitive = ['linger', 'dwell', 'rest', 'relax', 'hold', 'dream', 'hum']
texture = ['rough', 'fine']
adjectives = ['encompassing', 'sinuous', 'straight', 'objective', 'arched', 'cool', 'clear', 'dim', 'driven']
# code to get the path
plural = random.sample(["s", ""], k=2)
words = random.choice(above)
if words == "forest" and random.randrange(4) == 0:
words = "monkeys" + " " + random.choice(transitive)
else:
words += plural[0] + " " + random.choice(transitive) + plural[1]
words += " the " + random.choice(below) + random.choice(["s", ""]) + "."
paths = words.capitalize()
# code to determine the interior setting
adjs = adjectives[:] + random.sample(texture, 1)
setting = " " + random.choice(imperative) + " " + " ".join(random.sample(adjs, random.randrange(1, 4))) + " —"
# code to determine the landscape setting
if random.randrange(2) == 0:
words = random.choice(above)
else:
words = random.choice(below)
words += "s " + random.choice(intransitive) + "."
site = words.capitalize()
stanza_count = 10 # number of stanzas
for repeat in range(stanza_count): # create each stanza
line_count = random.randrange(3, 6)
for i in range(line_count):
if i == 0:
print(paths)
elif i == line_count - 2:
print(paths)
elif i == line_count - 1:
print()
print(setting)
print()
else:
print(site)
Strachey Love Letter#
In 1952, British computer scientist Christopher Strachey developed one of the first works of electronic literature, a love letter generator (originally written for the Manchester Mark I computer). We can create his algorithm in Python using list methods, a few different flavors of iteration, and conditional statements (along with a lot of concatenation and work with strings). The program below is based on Allison Parrish’s implementation, but you can modify the original text and output format to make your own creation!
# source: https://github.com/aparrish/rwet/blob/master/some-poetry-generators.ipynb
sal_adjs = [ # salutatory adjectives
"Beloved",
"Darling",
"Dear",
"Dearest",
"Fanciful",
"Honey"]
sal_nouns = [ # salutory nouns
"Chickpea",
"Dear",
"Duck",
"Jewel",
"Love",
"Moppet",
"Sweetheart"
]
adjs = [ # adjectives
'affectionate',
'amorous',
'anxious',
'avid',
'beautiful',
'breathless',
'burning',
'covetous',
'craving',
'curious',
'eager',
'fervent',
'fondest',
'loveable',
'lovesick',
'loving',
'passionate',
'precious',
'seductive',
'sweet',
'sympathetic',
'tender',
'unsatisfied',
'winning',
'wistful'
]
nouns = [ # nouns
'adoration',
'affection',
'ambition',
'appetite',
'ardour',
'being',
'burning',
'charm',
'craving',
'desire',
'devotion',
'eagerness',
'enchantment',
'enthusiasm',
'fancy',
'fellow feeling',
'fervour',
'fondness',
'heart',
'hunger',
'infatuation',
'little liking',
'longing',
'love',
'lust',
'passion',
'rapture',
'sympathy',
'thirst',
'wish',
'yearning'
]
advs = [ # adverbs
'affectionately',
'ardently',
'anxiously',
'beautifully',
'burningly',
'covetously',
'curiously',
'eagerly',
'fervently',
'fondly',
'impatiently',
'keenly',
'lovingly',
'passionately',
'seductively',
'tenderly',
'wistfully'
]
verbs = [ # verbs
'adores',
'attracts',
'clings to',
'holds dear',
'hopes for',
'hungers for',
'likes',
'longs for',
'loves',
'lusts after',
'pants for',
'pines for',
'sighs for',
'tempts',
'thirsts for',
'treasures',
'yearns for',
'woos'
]
import textwrap, random # import library
output = random.choice(sal_adjs) + " " + random.choice(sal_nouns) + ",\n" # start with a salutation
output += "\n"
# inside this loop, build the phrases. strachey implemented "short" phrases
# and "long" phrases; two or more "short" phrases in a row have special
# formatting rules, so we need to know what the last phrase kind was in
# order to generate the output.
history = [] # empty list to note whether the previous phrase was short or long
body = "" # start the body of the letter
for i in range(5): # create the number of paragraphs
kind = random.choice(["short", "long"]) # alternate short and long phrases
if kind == "long": # long phrases
line = " ".join([
"My",
random.choice([random.choice(adjs), ""]),
random.choice(nouns),
random.choice([random.choice(advs), ""]),
random.choice(verbs),
"your",
random.choice([random.choice(adjs), ""]),
random.choice(nouns)])
body += line
else: # short phrases
adj_noun = random.choice(adjs) + " " + random.choice(nouns)
if len(history) > 0 and history[-1] == "short":
body += ": my " + adj_noun
else:
body += "You are my " + adj_noun
body += ". " # end the body of the letter
history.append(kind) # note the phrase type
body = body.replace(" ", " ") # deal with whitespace
body = body.replace(". :", ":")
output += textwrap.fill(body, 60) # create letter format
output += "\n\nYours " + random.choice(advs) + ",\n" # closing sign-off
output += "M.U.C."
print(output) # show full letter
Other Computational Creative Text Forms#
We can also introduce a few other text generation workflows
Acrostics#
{admonition} Acknowledgement :class: tip, dropdown *Programs in this section are from Allison Parrish’s “Poetics of Grouping” activity, from the NYU “Reading & Writing With Electronic Text” course.
What is an acrostic?
“An acrostic is a poem or other word composition in which the first letter (or syllable, or word) of each new line (or paragraph, or other recurring feature in the text) spells out a word, message or the alphabet” (Wikipedia)
Let’s imagine we want to take the letters in Shakespeare’s name and use them as the “seed” for each line of an acrostic that uses text from one of Shakespeare’s sonnets.
We can use “Sonnet 116” as an example:
Let me not to the marriage of true minds
Admit impediments. Love is not love
Which alters when it alteration finds,
Or bends with the remover to remove.
O no! it is an ever-fixed mark
That looks on tempests and is never shaken;
It is the star to every wand'ring bark,
Whose worth's unknown, although his height be taken.
Love's not Time's fool, though rosy lips and cheeks
Within his bending sickle's compass come;
Love alters not with his brief hours and weeks,
But bears it out even to the edge of doom.
If this be error and upon me prov'd,
I never writ, nor no man ever lov'd. <\blockquote>Our first step would be to get the lines of the sonnet in Python.
# load poem text = """ Let me not to the marriage of true minds Admit impediments. Love is not love Which alters when it alteration finds, Or bends with the remover to remove. O no! it is an ever-fixed mark That looks on tempests and is never shaken; It is the star to every wand'ring bark, Whose worth's unknown, although his height be taken. Love's not Time's fool, though rosy lips and cheeks Within his bending sickle's compass come; Love alters not with his brief hours and weeks, But bears it out even to the edge of doom. If this be error and upon me prov'd, I never writ, nor no man ever lov'd. """ # create list with poem lines lines = [y for y in (x.strip() for x in text.splitlines()) if y]Then, we would need to organize the sonnet’s text into groups of terms based on initial letters. Since doing this manually would be a pain, we can use Python’s dictionary data structure (and other functionality) to help us out.
terms = {} # empty dictionary for line in lines: # iterate over each line in the poem words = line.split() # split each line into a list of words for word in words: # go over each word in the line first = word[0] # gets first character in each word if first.casefold() not in terms: # checks if first character is a key in our dictionary terms[first.casefold()] = [] # creates an empty list with the first character as the key terms[first.casefold()].append(word) # adds words that start with the same letter to the empty list terms.keys() # show list of keysIn the previous program, the
.casefold()argument let us ignore capitalization when organizing terms and creating our dictionary. Now that we have our seed string (“shakespeare”) and our dictionary from “Sonnet 116,” we can create our acrostic!for character in "shakespeare": print(random.choice(terms[character]).capitalize())Grrr…“
k” is a letter in “shakespeare”, but doesn’t appear in the sonnet. We can at least modify the program so it doesn’t stop when it hits “k”.for character in "shakespeare": if character in terms: # checks if a character is in our dictionary print(random.choice(terms[character]).capitalize()) # selects a random term with a matching first character else: print(">>> WARNING! ACROSTIC FAILURE! WARNING")Putting that all together:
import random # import modules # load poem text = """ Let me not to the marriage of true minds Admit impediments. Love is not love Which alters when it alteration finds, Or bends with the remover to remove. O no! it is an ever-fixed mark That looks on tempests and is never shaken; It is the star to every wand'ring bark, Whose worth's unknown, although his height be taken. Love's not Time's fool, though rosy lips and cheeks Within his bending sickle's compass come; Love alters not with his brief hours and weeks, But bears it out even to the edge of doom. If this be error and upon me prov'd, I never writ, nor no man ever lov'd. """ # create list with poem lines lines = [y for y in (x.strip() for x in frost.splitlines()) if y] terms = {} # empty dictionary for line in lines: # iterate over each line in the poem words = line.split() # split each line into a list of words for word in words: # go over each word in the line first = word[0] # gets first character in each word if first.casefold() not in terms: # checks if first character is a key in our dictionary terms[first.casefold()] = [] # creates an empty list with the first character as the key terms[first.casefold()].append(word) # adds words that start with the same letter to the empty list for character in "shakespeare": if character in terms: # checks if a character is in our dictionary print(random.choice(terms[character]).capitalize()) # selects a random term with a matching first character else: print(">>> WARNING! ACROSTIC FAILURE! WARNING")
Lexical Swaps#
Let’s imagine a scenario in which we want to replace words in an original text with different words that start with the same letter. The dictionary we created in the previous task already has text organized for this task.
First, we would need to determine the starting character for each word in the Sonnet.
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
for word in words: # go over each word in the line
print(word[0]) # print the word's first character
Then, we would need to connect the first character in each word to our dictionary.
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
for word in words: # go over each word in the line
print(word[0].casefold()) # print the word's first character
print(terms[word[0].casefold()]) # show dictionary values that match the first character
Then, we would randomly select one of the matching character words for the swap.
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
for word in words: # go over each word in the line
print(word, random.choice(terms[word[0].casefold()])) # show the original word and the replacement word
Then, we can implement the lexical swap.
newWords = [] # empty list for new terms
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
for word in words: # go over each word in the line
newWords.append(random.choice(terms[word[0].casefold()]))
print(" ".join(newWords)) # ues join function to combine list elements
If we wanted the output format to mirror the original sonnet structure:
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
print(' '.join(random.choice(terms[word[0].casefold()]) for word in words)) # reconfigured version of the swap program
Putting that all together:
import requests, random # import modules
page = requests.get("https://poetrydb.org/random") # get poem
poem = page.json()[0] # load poem in python
lines = poem['lines'] # isolate poem lines
terms = {} # empty dictionary
for line in lines: # iterate over each line in the poem
words = line.split() # split each line into a list of words
for word in words: # go over each word in the line
first = word[0] # gets first character in each word
if first.casefold() not in terms: # checks if first character is a key in our dictionary
terms[first.casefold()] = [] # creates an empty list with the first character as the key
terms[first.casefold()].append(word) # adds words that start with the same letter to the empty list
for line in lines: # go over each line in the sonnet
words = line.split() # split the line into list of words
print(' '.join(random.choice(terms[word[0].casefold()]) for word in words)) # reconfigured version of the swap program
Natural Language Processing#
We’ll cover some preliminary natural language processing workflows later in the semester, but Prof. Walden has a Jupyter Notebook from a different class if you’re eager to dig in now.
Activity Steps#
Step #1: As a group, decide on what kind of creative template you want to modify or design.
Step #2: Once you have a framework or template, start to develop a pseudocode outline for your program
Start with a word problem, or a narrative description of the task
What is the goal
What are the parameters or constraints
Any particular commands or syntax you’re asked to use
Start breaking down the tasks into steps, thinking about how programming languages (specifically Python) approach logic
Input/output, variables, operators (arithmetic, comparison)
What are the programming building blocks that connect to aspects of the task
BUT still write things out in plain language (not programming syntax)
Continue breaking the task into steps
The goal is one programming step (or statement) per line
Step #3: Now we’re ready to start writing the Python program! Leverage the group roles to start translating your pseudocode outline into a Python program.
Step #4: Continue to work on debugging and testing your program, switching up group roles as needed.
Step #5: Spend a few minutes as a group discussing and reflecting on the experience. What challenges did your group encounter, and how did you solve them? Other comments or observations?