Mad Libs
Mad Libs is a phrasal template word game where one player prompts another for a list of words to substitute for blanks in a story, usually with funny results.
Write a program to create a Mad Libs like story. The program should read a multiline story from the input. The story will be terminated with a blank line. Then, find each replacement to be made within the story, ask the user for a word to replace it with, and make all the replacements. Stop when there are none left and print the final story.
The input should be in the form:
<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home.
It should then ask for a name, a he or she and a noun (<name> gets replaced both times with the same value.)
This page uses content from Wikipedia. The original article was at Mad Libs. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
Ada
The fun of Mad Libs is not knowing the story ahead of time, so the program reads the story template from a text file. The name of the text file is given as a command line argument.
<lang Ada>with Ada.Text_IO, Ada.Command_Line, String_Helper;
procedure Madlib is
use String_Helper;
Text: Vector := Get_Vector(Ada.Command_Line.Argument(1)); M, N: Natural;
begin
-- search for templates and modify the text accordingly for I in Text.First_Index .. Text.Last_Index loop loop Search_Brackets(Text.Element(I), "<", ">", M, N); exit when M=0; -- "M=0" means "not found" Ada.Text_IO.Put_Line("Replacement for " & Text.Element(I)(M .. N) & "?"); declare Old: String := Text.Element(I)(M .. N); New_Word: String := Ada.Text_IO.Get_Line; begin for J in I .. Text.Last_Index loop Text.Replace_Element(J, Replace(Text.Element(J), Old, New_Word)); end loop; end; end loop; end loop;
-- write the text for I in Text.First_Index .. Text.Last_Index loop Ada.Text_IO.Put_Line(Text.Element(I)); end loop;
end Madlib;</lang>
It uses an auxiliary package String_Helper for simple string functions;
<lang Ada>with Ada.Containers.Indefinite_Vectors;
package String_Helper is
function Index(Source: String; Pattern: String) return Natural;
procedure Search_Brackets(Source: String; Left_Bracket: String; Right_Bracket: String; First, Last: out Natural); -- returns indices of first pair of brackets in source -- indices are zero if no such brackets are found
function Replace(Source: String; Old_Word: String; New_Word: String) return String;
package String_Vec is new Ada.Containers.Indefinite_Vectors (Index_Type => Positive, Element_Type => String);
type Vector is new String_Vec.Vector with null record;
function Get_Vector(Filename: String) return Vector;
end String_Helper;</lang>
Here is the implementation of String_Helper:
<lang Ada>with Ada.Strings.Fixed, Ada.Text_IO;
package body String_Helper is
function Index(Source: String; Pattern: String) return Natural is begin return Ada.Strings.Fixed.Index(Source => Source, Pattern => Pattern); end Index;
procedure Search_Brackets(Source: String; Left_Bracket: String; Right_Bracket: String; First, Last: out Natural) is begin First := Index(Source, Left_Bracket); if First = 0 then Last := 0; -- not found else Last := Index(Source(First+1 .. Source'Last), Right_Bracket); if Last = 0 then First := 0; -- not found; end if; end if; end Search_Brackets;
function Replace(Source: String; Old_Word: String; New_Word: String) return String is L: Positive := Old_Word'Length; I: Natural := Index(Source, Old_Word); begin if I = 0 then return Source; else return Source(Source'First .. I-1) & New_Word & Replace(Source(I+L .. Source'Last), Old_Word, New_Word); end if; end Replace;
function Get_Vector(Filename: String) return Vector is F: Ada.Text_IO.File_Type; T: Vector; begin Ada.Text_IO.Open(F, Ada.Text_IO.In_File, Filename); while not Ada.Text_IO.End_Of_File(F) loop T.Append(Ada.Text_IO.Get_Line(F)); end loop; Ada.Text_IO.Close(F); return T; end Get_Vector;
end String_Helper;</lang>
A sample run (with the story template in t.txt):
./madlib t.txt Replacement for <name>? Hilla, the hypohondraic, Replacement for <he or she>? She Replacement for <noun>? headache Hilla, the hypohondraic, went for a walk in the park. She found a headache. Hilla, the hypohondraic, decided to take it home.
AutoHotkey
Like some other examples, this prompts the user for a text file template.
AutoHotkey is interestingly well suited for this task...
<lang AHK>FileSelectFile, filename, , %A_ScriptDir%, Select a Mad Libs template, *.txt
If ErrorLevel
ExitApp ; the user canceled the file selection
FileRead, contents, %filename%
pos := match := 0
While pos := RegExMatch(contents, "<[^>]+>", match, pos+strLen(match))
{
InputBox, repl, Mad Libs, Enter a replacement for %match%:
If ErrorLevel ; cancelled inputbox
ExitApp
StringReplace, contents, contents, %match%, %repl%, All
}
MsgBox % contents</lang>
- Sample Output
Han Solo went for a walk in the park. She found a flagpole. Han Solo decided to take it home.
C++
<lang cpp>#include <iostream>
- include <string>
using namespace std;
int main() {
string story, input;
//Loop while(true) { //Get a line from the user getline(cin, input);
//If it's blank, break this loop if(input == "\r") break;
//Add the line to the story story += input; }
//While there is a '<' in the story int begin; while((begin = story.find("<")) != string::npos) { //Get the category from between '<' and '>' int end = story.find(">"); string cat = story.substr(begin + 1, end - begin - 1);
//Ask the user for a replacement cout << "Give me a " << cat << ": "; cin >> input;
//While there's a matching category //in the story while((begin = story.find("<" + cat + ">")) != string::npos) { //Replace it with the user's replacement story.replace(begin, cat.length()+2, input); } }
//Output the final story cout << endl << story;
return 0;
}</lang>
D
<lang d>import std.stdio, std.regex, std.algorithm, std.string, std.array;
void main() {
writeln("Enter a story template, terminated by an empty line:"); string story; while (true) { auto line = stdin.readln().strip(); if (line.empty) break; story ~= line ~ "\n"; }
auto re = regex("<.+?>", "g"); auto fields = story.match(re).map!q{a.hit}().array().sort().uniq(); foreach (field; fields) { writef("Enter a value for '%s': ", field[1 .. $ - 1]); story = story.replace(field, stdin.readln().strip()); }
writeln("\nThe story becomes:\n", story);
}</lang>
- Output:
Enter a story template, terminated by an empty line: <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home. Enter a value for 'he or she': She Enter a value for 'name': Monica Enter a value for 'noun': cockerel The story becomes: Monica went for a walk in the park. She found a cockerel. Monica decided to take it home.
Go
Variance: The fun of Mad Libs is not knowing the story ahead of time, so instead of asking the player to enter the story template, my program asks the player to enter the file name of a story template (with contents presumably unknown to the player.) <lang go>package main
import (
"bufio" "fmt" "io/ioutil" "log" "os" "regexp" "strings"
)
func main() {
pat := regexp.MustCompile("<.+?>") if len(os.Args) != 2 { fmt.Println("usage: madlib <story template file>") return } b, err := ioutil.ReadFile(os.Args[1]) if err != nil { log.Fatal(err) } tmpl := string(b) s := []string{} // patterns in order of appearance m := map[string]string{} // mapping from patterns to replacements for _, p := range pat.FindAllString(tmpl, -1) { if _, ok := m[p]; !ok { m[p] = "" s = append(s, p) } } fmt.Println("Enter replacements:") br := bufio.NewReader(os.Stdin) for _, p := range s { for { fmt.Printf("%s: ", p[1:len(p)-1]) r, isPre, err := br.ReadLine() if err != nil { log.Fatal(err) } if isPre { log.Fatal("you're not playing right. :P") } s := strings.TrimSpace(string(r)) if s == "" { fmt.Println(" hmm?") continue } m[p] = s break } } fmt.Println("\nYour story:\n") fmt.Println(pat.ReplaceAllStringFunc(tmpl, func(p string) string { return m[p] }))
}</lang> Sample run:
Enter replacements: character name: Wonko the Sane third person pronoun for character: he noun: wild weasel Your story: Wonko the Sane went for a walk in the park. he found a wild weasel. Wonko the Sane decided to take it home.
Icon and Unicon
This just runs with the sample. It would be much more fun with a database of randomly selected story templates. <lang Icon>procedure main()
ml := "<name> went for a walk in the park. There <he or she> _ found a <noun>. <name> decided to take it home." # sample MadLib(ml) # run it
end
link strings
procedure MadLib(story)
write("Please provide words for the following:") V := [] story ? while ( tab(upto('<')), put(V,tab(upto('>')+1)) ) every writes(v := !set(V)," : ") do story := replace(story,v,read()) write("\nYour MadLib follows:\n",story)
end</lang>
Sample output:
Please provide words for the following: <noun> : keys <he or she> : she <name> : Goldilocks Your MadLib follows: Goldilocks went for a walk in the park. There she found a keys. Goldilocks decided to take it home.
Liberty BASIC
<lang lb>temp$="<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home." k = instr(temp$,"<") while k
replace$ = mid$(temp$,k,instr(temp$,">")-k + 1) print "Replace:";replace$;" with:"; :input with$ while k temp$ = left$(temp$,k-1) + with$ + mid$(temp$,k + len(replace$)) k = instr(temp$,replace$,k) wend
k = instr(temp$,"<") wend print temp$ wait </lang>
Perl 6
<lang perl6>my $story = slurp(@*ARGS.shift); my %words; $story.=subst(/ '<' (.*?) '>' /, { %words{$0} //= prompt "$0? " }, :g ); say $story;</lang> Sample run:
$ madlibs walk name? Phydeaux He or She? She noun? flea Phydeaux went for a walk in the park. She found a flea. Phydeaux decided to take it home.
Pike
this solution uses readline to make editing more convenient. <lang Pike>#!/usr/bin/pike
Stdio.Readline readln = Stdio.Readline();
void print_help() {
write(#"Write a Story.
Names or objects in the story can be made variable by referencing them as <person> <object>, etc. End the story with an empty line.
Type show to read the story. You will be asked to fill the variables, and the the story will be shown.
Type help to see this message again. Type exit to quit.
"); }
void add_line(string input) {
array variables = parse_for_variables(input); write("Found variables: %{\"%s\" %}\n", variables); story += input+"\n";
}
array parse_for_variables(string input) {
array vars = Array.flatten(array_sscanf(input, "%*[^<>]%{<%[^<>]>%*[^<>]%}%*[^<>]")); return Array.uniq(vars);
}
mapping fill_variables(string story) {
array vars = parse_for_variables(story); mapping variables = ([]); foreach(vars;; string name) { string value = readln->read(sprintf("Please name a%s %s: ", (<'a','e','i','o','u'>)[name[1]]?"":"n", name)); if (value != "") variables["<"+name+">"] = value; } return variables;
}
void show_story(string story) {
mapping variables = fill_variables(story); write("\n"+replace(story, variables));
}
void do_exit() {
exit(0);
}
mapping functions = ([ "help":print_help,
"show":show_story, "exit":do_exit, ]);
string story = "";
void main() {
Stdio.Readline.History readline_history = Stdio.Readline.History(512); readln->enable_history(readline_history); string prompt="> "; print_help(); while(1) { string input=readln->read(prompt); if(!input) exit(0); if(input == "") show_story(story); else if (functions[input]) functions[input](); else add_line(input); }
}</lang>
Sample output:
Write a Story. Names or objects in the story can be made variable by referencing them as <person> <object>, etc. End the story with an empty line. Type show to read the story. You will be asked to fill the variables, and the the story will be shown. Type help to see this message again. Type exit to quit. > <person> is a programmer. Found variables: "person" > <he or she> created <website> for all of us to enjoy. Found variables: "he or she" "website" > Please name a person: Michael Please name a he or she: he Please name a website: RosettaCode Michael is a programmer. he created RosettaCode for all of us to enjoy. > Please name a person: Guilaumme Please name a he or she: he Please name a website: PLEAC Guilaumme is a programmer. he created PLEAC for all of us to enjoy. > exit
PicoLisp
This function extends the syntax a bit to be able to express different words with the same description, the syntax is <name:description>, if the description is omitted the name is used instead, keeping backwards compatibility with the syntax used in the task description: <lang PicoLisp>(de madlib (Template)
(setq Template (split (chop Template) "<" ">")) (let (Reps () Text ()) (while Template (push 'Text (pop 'Template)) (let? Rep (mapcar pack (split (pop 'Template) ":")) (if (assoc (car Rep) Reps) (push 'Text (cdr @)) (until (and (prin "Gimme a(n) " (or (cadr Rep) (car Rep)) ": ") (clip (in NIL (line))) (push 'Text @) (push 'Reps (cons (car Rep) @)) ) (prinl "Huh? I got nothing.") ) ) ) ) (prinl (need 30 '-)) (prinl (flip Text)) ) )
</lang>
This runs the example:
(madlib "<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home." ) Gimme a(n) name: Mr. T Gimme a(n) he or she: he Gimme a(n) noun: fool ------------------------------ Mr. T went for a walk in the park. he found a fool. Mr. T decided to take it home.
Example with the extended syntax:
(madlib "<1:name> went for a walk in the park. <2:he or she> found a <3:noun (singular)>. <1> decided to take it home. On <4:his or her> way home, <2> found a <5:noun (singular)>, two <7:noun (plural)> and a <8:noun (singular)>." ) Gimme a(n) name: MC Hammer Gimme a(n) he or she: he Gimme a(n) noun (singular): pair of baggy pants Gimme a(n) his or her: his Gimme a(n) noun (singular): hammer Gimme a(n) noun (plural): STOP signs Gimme a(n) noun (singular): clock ------------------------------ MC Hammer went for a walk in the park. he found a pair of baggy pants. MC Hammer decided to take it home. On his way home, he found a hammer, two STOP signs and a clock.
Python
<lang python>import re
- Optional Python 2.x compatibility
- try: input = raw_input
- except: pass
template = <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home.
def madlibs(template):
print('The story template is:\n' + template) fields = sorted(set( re.findall('<[^>]+>', template) )) values = input('\nInput a comma-separated list of words to replace the following items' '\n %s: ' % ','.join(fields)).split(',') story = template for f,v in zip(fields, values): story = story.replace(f, v) print('\nThe story becomes:\n\n' + story)
madlibs(template)</lang>
- Sample output
The story template is: <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home. Input a comma-separated list of words to replace the following items <he or she>,<name>,<noun>: She,Monica L.,cockerel The story becomes: Monica L. went for a walk in the park. She found a cockerel. Monica L. decided to take it home.
Ruby
<lang ruby>puts "Enter a story, terminated by an empty line:" story = "" until (line = STDIN.gets).chomp.empty?
story << line
end
story.scan(/(?<=[<]).+?(?=[>])/).uniq.each do |var|
print "Enter a value for '#{var}': " story.gsub!(/<#{var}>/, STDIN.gets.chomp)
end
puts "" puts story</lang>
Example
Enter a story, terminated by an empty line: <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home. Enter a value for 'name': FOO Enter a value for 'he or she': BAR Enter a value for 'noun': BAZ FOO went for a walk in the park. BAR found a BAZ. FOO decided to take it home.
Run BASIC
<lang runbasic>temp$="<name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home." k = instr(temp$,"<") while k
replace$ = mid$(temp$,k,instr(temp$,">")-k + 1) print "Replace:";replace$;" with:"; :input with$ while k temp$ = left$(temp$,k-1) + with$ + mid$(temp$,k + len(replace$)) k = instr(temp$,replace$,k) wend
k = instr(temp$,"<") wend print temp$ wait</lang> Output:
Replace:<name> with:?Fred Replace:<he or she> with:?he Replace:<noun> with:?cat Fred went for a walk in the park. he found a cat. Fred decided to take it home.
Tcl
<lang tcl>package require Tcl 8.5
- Read the template...
puts [string repeat "-" 70] puts "Enter the story template, ending with a blank line" while {[gets stdin line] > 0} {
append content $line "\n"
}
- Read the mapping...
puts [string repeat "-" 70] set mapping {} foreach piece [regexp -all -inline {<[^>]+>} $content] {
if {[dict exists $mapping $piece]} continue puts -nonewline "Give me a $piece: " flush stdout dict set mapping $piece [gets stdin]
}
- Apply the mapping and print...
puts [string repeat "-" 70] puts -nonewline [string map $mapping $content] puts [string repeat "-" 70]</lang> Sample session:
---------------------------------------------------------------------- Enter the story template, ending with a blank line <name> went for a walk in the park. <he or she> found a <noun>. <name> decided to take it home. ---------------------------------------------------------------------- Give me a <name>: Wonko the Sane Give me a <he or she>: He Give me a <noun>: wild weasel ---------------------------------------------------------------------- Wonko the Sane went for a walk in the park. He found a wild weasel. Wonko the Sane decided to take it home. ----------------------------------------------------------------------