Detective Spooner (just for fun)
 

Detective Spooner (just for fun)

Started by Sudo, 22 March, 2012, 22:13:33

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Sudo

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

Psycho_Chihuahua

PtokaxWiki ?PtokaX Mirror + latest Libs

01100001011011000111001101101111001000000110101101101110011011110111011101101110001000000110000101110011001000000101010001101111011010110110111101101100011011110111001101101000

Sudo

Quote from: Psycho_Chihuahua on 22 March, 2012, 22:39:49
Uploaded it here for you  ;D

Detective Spooner

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.

Psycho_Chihuahua

#3
Did you try uploading it here ?
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  ::)
PtokaxWiki ?PtokaX Mirror + latest Libs

01100001011011000111001101101111001000000110101101101110011011110111011101101110001000000110000101110011001000000101010001101111011010110110111101101100011011110111001101101000

bastya_elvtars

It's good to see that new scripts are still getting made. I've looked at the code, nice job. Thanks for sharing.
Everything could have been anything else and it would have just as much meaning.

Sudo


SMF spam blocked by CleanTalk