Skip to content

Instantly share code, notes, and snippets.

@sudonhim
Created May 26, 2017 23:07
Show Gist options
  • Save sudonhim/2ab3447aefe331828c613e7622c3c12a to your computer and use it in GitHub Desktop.
Save sudonhim/2ab3447aefe331828c613e7622c3c12a to your computer and use it in GitHub Desktop.
--Detective Spooner--
--Author: Sudo (aka SudoNhim)
--Version: 1.1 - 22/03/2012
--Detective Spooner examines each significant pair of long words and tries
--to establish which pairs make good spoonerisms. If a pair is selected,
--the words are substituted for the spoonerism.
--Spoonerisms are recorded so they will not happen if the user tries to
--repeat them <trollface.jpg>
-------------------------------------------------------------------
--Configuration
--Filepaths are relative to the PtokaX exe
--Where to find the dictionary file, must be list of words one per line
DICT_FILE = "texts/dict.txt"
--Where/if to save logging data
LOGGING_FILE = "scripts/DetectiveSpoonersLog.txt"
LOGGING_ENABLED = true
----------------------
--Change these settings to make Detective Spooner more invasive/subtle
--Ignore words shorter than...
MIN_WORD_LENGTH = 4
--Skipped words; these are words that should not 'break' a spoonerism, for
--example in "nooks and crannies" we want to skip the "and" so we get
--"crooks and nannies". These should be shorter than MIN_WORD_LENGTH
SKIPPED_WORDS = {'and','or','a','the','so','to','be','he','she','is','for',
'my','in','can'}
--Starting words that should cause the message to be skipped eg user commands
SKIP_MESSAGE_WORDS = {'/me','!faq','!catchup'}
--Chance of spoonerising based on dictionary matches of resulting spoonerism
NO_MATCH_CHANCE = 0.02
ONE_MATCH_CHANCE = 0.9
TWO_MATCH_CHANCE = 1.0
--This function determines the likelyhood that the words will be spooned
--based on the length of the words
function LENGTH_CHANCE(word1,word2)
if (math.random(5,word1:len()+word2:len())>=7) then
return true
else
return false
end
end
-------------------------------------------------------------------
--Some 'helper' functions
function Set(t)
--For set operations, we want hashing rather enumeration
local s = {}
for _,v in pairs(t) do s[v] = true end
return s
end
function split(s,sep) --Bug; always splits at a '/'
local fields = {}
local s = s..sep
s:gsub("([^"..sep.."]*)"..sep, function(c) table.insert(fields, c) end)
return fields
end
function join(t,sep)
return table.concat(t, sep)
end
function strip(word)
--Remove leading and trailing punctuation
local out = ""
local char = "DUMMY"
local i = 1
while (i <= word:len()) do
char = word:sub(i,i)
if (letters[char:lower()]) then
out = out..char
end
i = i+1
end
return out
end
function unstrip(fromword,toword)
--Add fromword's leading and trailing puctuation to toword and
--return toword. (For undoing strip after modding word)
local out = ""
local char = fromword:sub(1,1)
local i = 1
while (not letters[char:lower()] and i < fromword:len()) do
out = out..char
i = i+1
char = fromword:sub(i,i)
end
out = out..toword
while (letters[char:lower()]) do
i = i+1
char = fromword:sub(i,i)
end
out = out..fromword:sub(i)
return out
end
function write_debug(message)
if LOGGING_ENABLED then
local debug_out = io.open(LOGGING_FILE,'a')
debug_out:write(message..'\n')
io.close(debug_out)
end
end
-------------------------------------------------------------------
--When the script is loaded
function OnStartup()
local dict_file = assert(io.input(DICT_FILE))
local wordtable = split(dict_file:read("*all"),'\n')
dictionary = Set(wordtable)
vowels = Set({'a','e','i','o','u'})
letters = Set({'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z'})
SKIPPED_WORDS = Set(SKIPPED_WORDS)
SKIP_MESSAGE_WORDS = Set(SKIP_MESSAGE_WORDS)
used_words = {}
--Auto-erase logging file
local debug_out = io.open(LOGGING_FILE,'w')
io.close(debug_out)
end
-------------------------------------------------------------------
--When a message arrives
function ChatArrival(user, data)
write_debug("Arrived: "..data)
local i = 1
local prev_word = false
local words = {}
--remove the name tag from the start of data
if (string.sub(data, 1, 1) == "<" ) then
data = split(data,'>')
table.remove(data,1)
data = join(data,'>')
data = data:sub(1,data:len()-1)
else
write_debug("Message ignored (did not start with '<')")
return false
end
words = split(data,' ')
--Seems to always split at '/' so for user commands:
if (words[1] == '') then
table.remove(words,1)
end
if (SKIP_MESSAGE_WORDS[words[1]]) then
write_debug("Message began with "..words[1]..", exiting")
return false
end
--Perform spoonerism function on valid word pairs
for _,word in pairs(words) do
word = strip(word)
if (word:len()>=MIN_WORD_LENGTH
and dictionary[string.lower(word)]) then
write_debug("Recieved valid word: "..word)
if (not prev_word == false) then
local spooned
spooned = spoonerise(strip(words[prev_word]),strip(words[i]))
words[prev_word] = unstrip(words[prev_word],spooned[1])
words[i] = unstrip(words[i],spooned[2])
prev_word = i
else
prev_word = i
end
else
if (not SKIPPED_WORDS[word]) then
prev_word = false
else write_debug("Word skipped: "..word)
end
end
i = i+1
end
local sMessage = "<"..user.sNick.."> "..join(words,' ').."|"
Core.SendToAll(sMessage)
write_debug("Sent: "..sMessage..'\n')
return true
end
-------------------------------------------------------------------
--Perform teh spooning magic
function spoonerise(word1,word2)
write_debug("Attempting spoonerise on: "..word1.." "..word2)
if (used_words[word1] and used_words[word2]) then
write_debug("Avoiding possible respooning")
return {word1,word2}
end
if(not LENGTH_CHANCE(word1,word2)) then
write_debug("Randomly decided not to spoon based on length")
return {word1,word2}
end
local bits = {} --The four segments of word to be recombined
for _,w in pairs({word1,word2}) do
local i = 1
local char = 'DUMMY'
while (not vowels[char] and i<5) do
char = w:sub(i,i):lower()
i = i+1
end
if (not vowels[char]) then
write_debug("Words not valid for spooning")
return {word1,word2}
end
if (char == 'u' and w:sub(i-1,i-1) == 'q') then
i = i+1
end
table.insert(bits,w:lower():sub(1,i-2))
table.insert(bits,w:lower():sub(i-1,w:len()))
end
local spooned = {bits[3]..bits[2], bits[1]..bits[4]}
--Keep case consistent
if (word1:sub(1,1):upper()==word1:sub(1,1)) then
spooned[1] = spooned[1]:sub(1,1):upper()..spooned[1]:sub(2)
else
spooned[1] = spooned[1]:sub(1,1):lower()..spooned[1]:sub(2)
end
if (word2:sub(1,1):upper()==word2:sub(1,1)) then
spooned[2] = spooned[2]:sub(1,1):upper()..spooned[2]:sub(2)
else
spooned[2] = spooned[2]:sub(1,1):lower()..spooned[2]:sub(2)
end
write_debug("Spoons created: "..join(spooned,' '))
--Check to see if spoonerisms are in the dictionary
--and decide whether to go through with it
local matches = 0
for _,word in pairs(spooned) do
if (dictionary[string.lower(word)]) then
matches = matches + 1
end
end
write_debug("Spoonerism contains "..matches.." dictionary words")
if((matches == 0 and math.random()<NO_MATCH_CHANCE)
or (matches == 1 and math.random()<ONE_MATCH_CHANCE)
or (matches == 2 and math.random()<TWO_MATCH_CHANCE)) then
used_words[word1] = true
used_words[word2] = true
return spooned
end
write_debug("Randomly decided not to spoonerise")
return {word1,word2}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment