Created
May 26, 2017 23:07
-
-
Save sudonhim/2ab3447aefe331828c613e7622c3c12a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--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