PtokaX forum

Lua 5.3/5.2/5.1 Scripts (for PtokaX 0.4.0.0 and newer) => Finished Scripts => Topic started by: Sudo on 22 March, 2012, 22:13:33

Title: Detective Spooner (just for fun)
Post by: Sudo on 22 March, 2012, 22:13:33
Confuse and amuse your hubizens with Detective Spooner
Variable subtlety!
Intelligent punctuation!
Optimisable word selection!

--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


This is my first Lua script, so be gentle :)
Has been -reasonably- thoroughly debugged on a PtokaX 4.2 server on default settings
REQUIRES CONFIGURATION!
Also once you have it working you should turn off logging as it is rather verbose
Enjoy :D

Sudo


EDIT
Updated to fix a punctuation bug
Also minor improvement to evaluation of good spoonerisms

Script: http://www.mediafire.com/?fpazg62npfe2fh7
Suitable dictionary file: http://www.mediafire.com/?b9685avxfie4wc8
Title: Re: Detective Spooner (just for fun)
Post by: Psycho_Chihuahua on 22 March, 2012, 22:39:49
Uploaded it here for you  ;D

Detective Spooner (http://forum.ptokax.org/index.php?action=tpmod;dl=item112)
Title: Re: Detective Spooner (just for fun)
Post by: Sudo on 22 March, 2012, 23:22:27
Quote from: Psycho_Chihuahua on 22 March, 2012, 22:39:49
Uploaded it here for you  ;D

Detective Spooner (http://forum.ptokax.org/index.php?action=tpmod;dl=item112)

Ah thank you, I get "Error: Upload directory not writeable", pretty sure it's not on my side so don't know what's going on there...

May be a 1.3 version of this script soon with improved detection of inferior spoonerisms + a fix for "a"/"an" when the spoonerism switches the first letters between consonant and vowel.
Title: Re: Detective Spooner (just for fun)
Post by: Psycho_Chihuahua on 22 March, 2012, 23:24:24
Did you try uploading it here (http://forum.ptokax.org/index.php?action=tpmod;dl=0) ?
Anyway, i'll check the Folder permissions for the Attachments as well


Edit: Attachments should work again now. Seems last Update changed Rights on more folders than i had noticed  ::)
Title: Re: Detective Spooner (just for fun)
Post by: bastya_elvtars on 07 April, 2012, 13:27:27
It's good to see that new scripts are still getting made. I've looked at the code, nice job. Thanks for sharing.
Title: Re: Detective Spooner (just for fun)
Post by: Sudo on 18 April, 2012, 05:28:53
Thank you very much  ;D