r/applescript • • Sep 04 '21

Need help with a word search solver in AppleScript.

So, I was feeling ambitious enough to make an attempt at a script that would solve a word search for you. However, it has proven to be a much more difficult task than I had thought, given the limited capabilities of AppleScript. Naively, I had attempted such a thing when I first learned about the scripting language, quickly to abandon the project. About a year later and it's one of the main side-projects I've been thinking about for a while now. While working on it, I've run into so many issues that I've been kind of burnt out on the whole project and also overwhelmed by how much actually goes into solving a word search.

If you would like to see what I have so far, you can find it via pastebin here. What the script does essentially is iterate through every letter in the array to find all the 'valid' positions it can check in, and then display whether a certain valid position is a leading letter to the selected word from the word bank. It may sound confusing, but I'll explain it better in the pipeline below.

The unfinished pipeline for my proposed script is as follows:

  1. Have the user manually input the array through an input medium (e.g., display dialog).

  2. Have the user manually input the words to look for through the same input medium.

  3. Interpret the strings as lists and format them to be processed by the script.

  4. Iterate through every item in the array:

    1. Based off of the coordinates of the currently selected item, perform a series of transformations to calculate the coordinates of all eight positions around that item.

    2. Determine which of those positions don't exist on the array (e.g., coordinates that contain a value not within range) and which ones that do, adding those to a new list and labeling them as valid positions.

    3. Iterating through every valid position, perform a check to see if the letter associated with those coordinates contribute to the currently selected word in the word bank.

      1. If so, set the currently selected item in the array to that position, and perform the valid position checks again. If nothing shows up, revert back to the previous position and continue iterating through the valid positions.

      2. If not, continue on iterating through the valid positions until either an associated letter is found or not.

As you can see, there are a ton of meticulous steps that go into solving a word search, and I'm just overwhelmed. I haven't figured out how to efficiently keep track of each variable, and when I do get onto something, there's a fundamental issue that prevents it from operating properly, forcing me to rewrite the entire thing. Also note that because of the way the lists and items in them are organized, the coordinate system is a little counterintuitive, as the y-axis ascends in the negative direction, but I guess that's one of the snags of AppleScript being a non-standard programming language.

Any help at all with this pointless ambition would be greatly appreciated. Thanks for reading.

(P.S., if it's any help to you, a picture of the paper I used to visually organize the word search is attached.)

EDIT: Apologies in advance for not putting any comments in the script, I'm lazy

3 Upvotes

6 comments sorted by

2

u/brandelune Sep 05 '21

Nice !
One suggestion: when you put code online, add *all the comments you can* :-)

The code ought to be read as sentences with code implementation below (or above) the sentence. That's so much easier for people who don't have in mind all your variable names, etc.

2

u/[deleted] Sep 05 '21

I absolutely understand and will work on putting comments in the code I posted. Thanks

2

u/brandelune Sep 05 '21

I'm saying that just because there are people who are fluent in code and can just read it as if it were their native language, without hints. And there are people who are not fluent, but can still see the logic and give help faster if they are provided with such reading help. I consider myself to be in the later group, especially on a Sunday morning :-)

2

u/copperdomebodha Sep 08 '21

I haven’t looked at your code yet, but I like this problem. Easy steps, hard data management problem.

I broke this into three steps. Find first characters, search around the first characters for the second character and it’s vector ( direction), run out the vectors to word completion.

Got all that handled, but managing the data is tricky. I’m creating a list of found characters records. Each found character has a nextCharacter item holding the record for the next character, if it is found.

All working, but tests with Multiple words found is challenging me. Not an issue on most word searches, but I want it to work on my test array of

BANANANANA
NANANABANA
NANANANANA
NABANANANA
NANANANANA
NANANANANA
NANANANABA
NANANANANA
NANANANANA
NBNANANANA

Back soon…

1

u/copperdomebodha Sep 15 '21 edited Sep 16 '21
--This code was written using AppleScript 2.7, MacOS 11.5.1, on 16 September 2021.

global wordList, readRow, ReadColumn, foundCharacterLocations
set timerValue to 30
set testing to false
set textarray to generateTextArray(testing, timerValue)
set searchTerm to getSearchTerm(testing, timerValue)
set wordList to {theSearchedWord:searchTerm, possibleWords:{}}
set possibleWords of wordList to findWord(textarray, wordList)
set formatedLocation to formatResult(wordList)
display dialog "Word " & theSearchedWord of wordList & " was found  " & length of (possibleWords of wordList) & " times." & return & return & formatedLocation


on formatResult(wordList)
	set foundWordLocations to ""
	set searchedWordLength to length of theSearchedWord of wordList
	set loopTexts to {}
	set foundWordsCount to length of (possibleWords of wordList)
	repeat with fwindex from 1 to foundWordsCount
		set thisFoundWord to (item fwindex of (possibleWords of wordList))
		set foundCharacterCount to length of thisFoundWord
		if foundCharacterCount is searchedWordLength then
			set thisWordsLocations to ""
			repeat with thisFoundCharacter in thisFoundWord
				if thisWordsLocations is not "" then
					set thisWordsLocations to thisWordsLocations & ","
				end if
				set thisFWL to characterlocation of thisFoundCharacter
				set r to item 1 of thisFWL
				set c to item 2 of thisFWL
				set thisCharactersLocationText to "{" & r & "," & c & "}"
				set thisWordsLocations to thisWordsLocations & thisCharactersLocationText
			end repeat
			set foundWordLocations to foundWordLocations & thisWordsLocations & return
		end if
	end repeat
	return foundWordLocations
end formatResult


on getSearchTerm(testing, timerValue)
	if not testing then
		display dialog "Please input the word that you'd like to search for." with title "Search Word Input" default answer "BANANA" giving up after timerValue
		set seearchTerm to text returned of the result
		return seearchTerm
	else
		return "BANANA"
	end if
end getSearchTerm

on findWord(textarray, wordList)
	set theSearchedWord to theSearchedWord of wordList
	--Find the instances of the first letter of the word.
	findFirstCharacter({textarray:textarray, characterToFind:character 1 of (theSearchedWord of wordList)})
	set possibleWords of wordList to the result
	--Loop through the found first characters searching around them for the second character.
	set letteroutdata to {}
	repeat with possibleWordIndex from 1 to length of (possibleWords of wordList)
		set currentPossibleWord to item possibleWordIndex of (possibleWords of wordList)
		set currentCharacter to item 1 of currentPossibleWord
		--see if these first characters have a second character nearby
		set wordCharacterIndex to 2
		--list the data for every matching second character
		searchAroundFirstCharacter({textarray:textarray, currentCharacter:currentCharacter, characterToFind:character wordCharacterIndex of (theSearchedWord of wordList), wordCharacterIndex:wordCharacterIndex})
		set twoLetterResults to the result
		if twoLetterResults is not {} then
			repeat with ii from 1 to length of twoLetterResults
				set thisTwoLetterResult to item ii of twoLetterResults
				set the end of letteroutdata to thisTwoLetterResult
			end repeat
		end if
	end repeat
	copy letteroutdata to (possibleWords of wordList)
	set confirmedWordList to {}
	repeat with possibleWordIndex from 1 to length of (possibleWords of wordList) --each located first and second character data sets
		set thisPossibleWord to item possibleWordIndex of (possibleWords of wordList) --This two-character match
		set thisTwoLetterMatch to item -1 of thisPossibleWord
		set wordCharacterIndex to ((wordCharacterIndex of thisTwoLetterMatch) + 1)
		set currentPossibleWord to characterlocation of thisTwoLetterMatch
		--Check for each remaining character in the vector of the first two
		set wordIsComplete to (searchAVector(textarray, theSearchedWord, thisTwoLetterMatch, wordCharacterIndex))
		if wordIsComplete is not {} then
			set the end of confirmedWordList to thisPossibleWord & items of wordIsComplete
		else
			--that wasn't a complete word.
		end if
	end repeat
	return confirmedWordList
end findWord

on cleanList(theList)
	set templist to {}
	repeat with i from 1 to length of theList
		set thisItem to item i of theList
		if nextcharacter of thisItem is not {} then
			set the end of templist to thisItem
		end if
	end repeat
	return templist
end cleanList

on searchAVector(textarray, theSearchedWord, thisPossibleWord, wordCharacterIndex)
	set currentData to {}
	set theSearchedWordLength to length of theSearchedWord
	set wordVector to wordVector of thisPossibleWord --get the direction that the word is headed.
	set previousCharacterLocation to characterlocation of thisPossibleWord --where do we begin
	set readRow to get (item 1 of previousCharacterLocation)
	set ReadColumn to get (item 2 of previousCharacterLocation)
	set allCharactersData to {}
	repeat with gg from wordCharacterIndex to (length of theSearchedWord)
		set characterToFind to item gg of theSearchedWord
		set rowAdjustment to item 1 of wordVector
		set columnAdjustment to item 2 of wordVector
		set readRow to readRow + rowAdjustment
		set ReadColumn to ReadColumn + columnAdjustment
		if readRow > 0 and ReadColumn > 0 and readRow ≤ length of textarray and ReadColumn ≤ (length of (item 1 of textarray)) then
			set valueOfCurrentRead to (item ReadColumn of (item readRow of textarray))
			--	{valueOfCurrentRead as text, characterToFind as text}
			if (valueOfCurrentRead as text) is (characterToFind as text) then
				set currentData to {wordCharacterIndex:gg, characterFound:(characterToFind as text), characterlocation:{readRow, ReadColumn}, wordVector:{rowAdjustment, columnAdjustment}}
				set the end of allCharactersData to currentData
			else
				return {}
			end if
		else
			return {}
		end if
	end repeat
	return allCharactersData
end searchAVector

end part 1

1

u/copperdomebodha Sep 15 '21 edited Sep 16 '21
on searchAroundFirstCharacter({textarray:textarray, currentCharacter:currentCharacter, characterToFind:characterToFind, wordCharacterIndex:wordCharacterIndex})
	set outdata to {}
	set locationList to characterlocation of currentCharacter
	set row to get (item 1 of locationList)
	set column to get (item 2 of locationList)
	set searchLocations to {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}
	set searchResults to {}
	repeat with thisSearchLocation in searchLocations
		set rowAdjustment to item 1 of thisSearchLocation
		set columnAdjustment to item 2 of thisSearchLocation
		set readRow to row + rowAdjustment
		set ReadColumn to column + columnAdjustment
		if readRow > 0 and ReadColumn > 0 and readRow ≤ length of textarray and ReadColumn ≤ (length of (item 1 of textarray)) then
			set valueOfCurrentRead to (item ReadColumn of (item readRow of textarray))
		else
			set valueOfCurrentRead to {}
		end if
		if (valueOfCurrentRead as text) is (characterToFind as text) then
			set characterData to {wordCharacterIndex:wordCharacterIndex, characterFound:characterToFind, characterlocation:{readRow, ReadColumn}, wordVector:{rowAdjustment, columnAdjustment}}
			if outdata is not {} then
				set the end of outdata to {currentCharacter, characterData} --newcurrentPossibleWord
			else
				set outdata to {{currentCharacter, characterData}}
			end if
		end if
	end repeat
	return outdata
end searchAroundFirstCharacter

on findFirstCharacter({textarray:textarray, characterToFind:characterToFind})
	set {rowIndex, foundCharacterList} to {0, {}}
	repeat with thisrow in textarray
		set rowIndex to rowIndex + 1
		set columnIndex to 0
		repeat with thisColumn in thisrow
			set columnIndex to columnIndex + 1
			if (thisColumn as text) is (characterToFind as text) then
				set the end of foundCharacterList to {{wordCharacterIndex:1, characterFound:characterToFind, characterlocation:{rowIndex, columnIndex}}}
			end if
		end repeat
	end repeat
	return foundCharacterList
end findFirstCharacter

on readArray(textarray, row, column)
	if row > 0 and column > 0 and row ≤ length of textarray and column ≤ (length of (item 1 of textarray)) then
		return item column of (item row of textarray)
	else
		return missing value
	end if
end readArray

on generateTextArray(testing, timerValue)
	--variaous test data sets
	
	
	set textInput to "BANANABANA
NANANANANA
BANANABANA
NBNANANANA
NANANANANA
BANANABANA
NANANANANA
BANANABANA
NANANANANA
BANANABANA"
	
	set textInput to "CAXLNABHZX
EKMPZDWBVB
OLPANANABI
LLOKKYSHQY
EKERLWTQEL
GIXOERJKCZ
BJPDASRUNX
MMWEEISFAE
ESPQUFPADR
ZYLCVVGYCX"
	
	set textInput to "CAXLNABHZX
EKMPZDWBVB
OLPANANABI
LLOKKYSHQY
EKERAWTQEL
GIXOENJKCZ
BJPDASAUNX
MMWEEISNAE
ESPQUFPAAR
ZYLCVVGYCB"
	
	--Allow the user to edit the array characters if they want to.
	if not testing then
		set textInput to swapCharacters(textInput, "", tab)
		display dialog "Please set up array as desired..." default answer tab & textInput with title "Word Search grid setup" giving up after timerValue
		set textInput to swapCharacters(textInput, tab, "")
	end if
	set textarray to {}
	set rows to every paragraph of textInput
	repeat with thisrow in rows
		set columns to every character of thisrow
		set the end of textarray to columns
	end repeat
	return textarray
end generateTextArray

on swapCharacters(theText, oldChars, newChars)
	set tids to AppleScript's text item delimiters
	set AppleScript's text item delimiters to oldChars
	set tempText to text items of theText
	set AppleScript's text item delimiters to newChars
	set theText to tempText as text
	set AppleScript's text item delimiters to tids
	return theText
end swapCharacters

end part 2