Skip to content

Instantly share code, notes, and snippets.

@dmberry
Created January 20, 2026 13:46
Show Gist options
  • Select an option

  • Save dmberry/b8481e6f58ea39969cf90fe40c1fb5da to your computer and use it in GitHub Desktop.

Select an option

Save dmberry/b8481e6f58ea39969cf90fe40c1fb5da to your computer and use it in GitHub Desktop.
Visible Love Generator (after Strachey, 1952) by David M. Berry
"""
Visible Love Generator (after Strachey, 1952)
Author: David M. Berry
Date: 20 January 2025
Version: 1.3
A reimplementation that foregrounds the generator's status as literary theory
made executable. The program displays the template structure with numbered
blank slots, then populates each slot as you press Enter, allowing you to
experience the combinatorial process as it unfolds.
Run: python3 love_letter_generator.py
"""
import os
from random import choice, random
# Word lists derived from Roget's Thesaurus categories, as Strachey used.
SALUTATIONS = ['DARLING', 'DEAREST', 'BELOVED', 'MY OWN', 'SWEETHEART', 'MY DEAR']
ADJECTIVES = [
'tender', 'precious', 'wistful', 'passionate', 'anxious', 'avid',
'burning', 'sweet', 'devoted', 'cherished', 'fond', 'eager',
'ardent', 'fervent', 'sympathetic', 'loving', 'curious', 'breathless'
]
NOUNS = [
'heart', 'affection', 'love', 'longing', 'desire', 'devotion',
'sympathy', 'feeling', 'passion', 'wish', 'hope', 'soul',
'adoration', 'hunger', 'thirst', 'liking', 'fancy', 'yearning'
]
VERBS = [
'yearns for', 'clings to', 'sighs for', 'longs for', 'hopes for',
'burns for', 'thirsts for', 'hungers for', 'pines for', 'aches for',
'craves', 'treasures', 'cherishes', 'holds dear', 'prizes'
]
ADVERBS = [
'anxiously', 'tenderly', 'wistfully', 'passionately', 'curiously',
'ardently', 'fondly', 'devotedly', 'eagerly', 'beautifully'
]
CLOSINGS = ['YOURS', 'YOURS ALWAYS', 'FOREVER YOURS', 'EVER YOURS', 'YOUR OWN']
def clear():
os.system('cls' if os.name == 'nt' else 'clear')
def show(lines, prompt=''):
"""Display the letter and wait for Enter."""
clear()
print('=' * 55)
print(' VISIBLE LOVE GENERATOR (after Strachey, 1952)')
print(' David M. Berry, 20 January 2025, v1.3')
print('=' * 55)
print()
for line in lines:
print(' ', line)
print()
print('-' * 55)
if prompt:
input(f' {prompt}')
else:
input(' [Enter to continue]')
def maybe(lst):
"""50% chance to pick a word, otherwise empty."""
return choice(lst) if random() < 0.5 else ''
def slot(n):
"""Return a numbered blank slot."""
return f'[{n}]___'
def run():
# How many body sentences
num_sentences = choice([3, 4])
# Pre-generate all words and track slot numbers
slot_num = 1
slots = {} # slot_num -> word
# Salutation slots
slots[slot_num] = choice(SALUTATIONS)
sal1_slot = slot_num
slot_num += 1
slots[slot_num] = choice(SALUTATIONS)
sal2_slot = slot_num
slot_num += 1
# Sentence slots
sentence_slots = [] # list of lists of (slot_num, word) for each sentence
for _ in range(num_sentences):
sent = []
# adj1 (optional)
adj1 = maybe(ADJECTIVES)
if adj1:
slots[slot_num] = adj1
sent.append((slot_num, 'adj1'))
slot_num += 1
# noun1
slots[slot_num] = choice(NOUNS)
sent.append((slot_num, 'noun1'))
slot_num += 1
# adv (optional)
adv = choice(ADVERBS) if random() < 0.3 else ''
if adv:
slots[slot_num] = adv
sent.append((slot_num, 'adv'))
slot_num += 1
# verb
slots[slot_num] = choice(VERBS)
sent.append((slot_num, 'verb'))
slot_num += 1
# adj2 (optional)
adj2 = maybe(ADJECTIVES)
if adj2:
slots[slot_num] = adj2
sent.append((slot_num, 'adj2'))
slot_num += 1
# noun2
slots[slot_num] = choice(NOUNS)
sent.append((slot_num, 'noun2'))
slot_num += 1
sentence_slots.append(sent)
# Closing slots
slots[slot_num] = choice(CLOSINGS)
close1_slot = slot_num
slot_num += 1
slots[slot_num] = choice(ADVERBS).upper()
close2_slot = slot_num
# Track which slots are filled
filled = set()
def render_slot(n):
if n in filled:
return slots[n]
else:
return f'[{n}]___'
def render_salutation():
return f'{render_slot(sal1_slot)} {render_slot(sal2_slot)}'
def render_sentence(sent_slots):
parts = ['My']
for sn, stype in sent_slots:
if stype == 'noun2':
parts.append(render_slot(sn) + '.')
elif stype == 'verb':
parts.append(render_slot(sn))
parts.append('your')
else:
parts.append(render_slot(sn))
return ' '.join(parts)
def render_closing():
return f'{render_slot(close1_slot)} {render_slot(close2_slot)}'
def build_lines():
lines = [render_salutation(), '']
for sent in sentence_slots:
lines.append(render_sentence(sent))
lines.extend(['', render_closing(), '', 'M. U. C.'])
return lines
# Show initial template
show(build_lines(), 'Press Enter to begin filling slots...')
# Fill salutation
filled.add(sal1_slot)
show(build_lines(), f'Slot {sal1_slot}: {slots[sal1_slot]}')
filled.add(sal2_slot)
show(build_lines(), f'Slot {sal2_slot}: {slots[sal2_slot]}')
# Fill sentences word by word
for i, sent in enumerate(sentence_slots):
for sn, stype in sent:
filled.add(sn)
show(build_lines(), f'Slot {sn}: {slots[sn]}')
# Fill closing
filled.add(close1_slot)
show(build_lines(), f'Slot {close1_slot}: {slots[close1_slot]}')
filled.add(close2_slot)
show(build_lines(), f'Slot {close2_slot}: {slots[close2_slot]}')
# Final
show(build_lines(), 'Letter complete. Enter for another, Ctrl+C to exit.')
print('\n\n')
run()
if __name__ == '__main__':
try:
run()
except KeyboardInterrupt:
print('\n Goodbye.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment