I hadn’t written a program for other than microcontrollers in many years. Recently I have been playing the Quartiles game in Apple News, and realizing its similarity to the Jumble puzzle, I though that I could write a Quartiles solving program. My Jumble solver is here: https://almy.us/jelbum.html
In Quartiles there are 20 tiles with word segments of up to four characters each. The object is to find five words that can be made from four tiles each, as well as other shorter words to enhance the score.

Here is a solved puzzle, with enough shorter words to get the 100 point “expert” score:

I wrote a Quartiles solver in Java using the Netbeans IDE. I created the user interface:

The CLEAR button clears all the segment fields, allowing the user to type in the words. Once the user has entered all the words, pressing the SOLVE button lists all the found words. For development purposes, the segment fields were initialized to be that of the then current puzzle. Details of the code for the user interface aren’t shown here. I’m more concerned to show the action code. Here’s the solution provided by this app to the segments shown at the top of this article:

When the program is launched, a dictionary file is read into an array:
private Object[] wordList;
private boolean initializeWordList() {
java.util.ArrayList<String> theWordList =
new java.util.ArrayList<>();
BufferedReader myReader;
String thisLine;
int counter = 0;
myReader = new BufferedReader(new InputStreamReader(
getClass().getResourceAsStream(
"/us/almy/lazyquartiles/words.txt")));
try {
while ((thisLine = myReader.readLine()) != null) {
int lineLength = thisLine.length();
if (lineLength < 1 || lineLength > 16) {
continue;
}
theWordList.add(thisLine);
counter++;
}
myReader.close();
} catch (IOException io) {
return false;
}
wordList = theWordList.toArray();
return true;
}
private Object[] wordList;
Note that the words.txt file is in the program’s executable .jar archive, so is a “resource”.
When the SOLVE button is pressed, the filled in segments (note — it isn’t necessary to fill in all of them) are cleaned up and copied into another array of “snippets”. If I really wanted the program to be slick, I would have text fields set up as an array so that a simple for loop could be used to copy the text. Instead I just wrote out the code to copy each segment individually. More code, but easier to do, and we aren’t hurting for space. I also add the snippet then remove it if it was empty.
private final String[] snippets;
this.snippets = new String[20]; /* This is in the constructor for the class object */
private void initializeSnippetTable() {
snipCount = 0;
if (jTextField1.getText() != null) {
snippets[snipCount++] = jTextField1.getText().strip().toLowerCase();
if ("".equals(snippets[snipCount - 1])) {
snipCount--;
}
}
if (jTextField2.getText() != null) {
snippets[snipCount++] = jTextField2.getText().strip().toLowerCase();
if ("".equals(snippets[snipCount - 1])) {
snipCount--;
}
}
/* fields 3 through 19 checked here, not shown */
if (jTextField20.getText() != null) {
snippets[snipCount++] = jTextField20.getText().strip().toLowerCase();
if ("".equals(snippets[snipCount - 1])) {
snipCount--;
}
}
}
Then comes the actually process of finding the words. A nested loop tries every single segment, every double of segments, every triple of segments, and every quadruple of segments to see if they are words in the dictionary list. When a match is found, it is written to the output field with marker characters between the segments. The output is sorted in this process so that the words using the most segments appear first.
StringBuffer answers1, answers2, answers3, answers4;
private void testSingle(int i1) {
if (binarySearch(wordList, snippets[i1]) >= 0) {
answers1.append(snippets[i1]).append("\n");
}
}
private void testDouble(int i1, int i2) {
String testString = snippets[i1] + snippets[i2];
if (binarySearch(wordList, testString) >= 0) {
answers2.append(snippets[i1])
.append(“•”)
.append(snippets[i2]).append("\n");
}
}
private void testTriple(int i1, int i2, int i3) {
String testString = snippets[i1] + snippets[i2] + snippets[i3];
if (binarySearch(wordList, testString) >= 0) {
answers3.append(snippets[i1])
.append("•")
.append(snippets[i2])
.append("•")
.append(snippets[i3]).append("\n");
}
}
private void testQuad(int i1, int i2, int i3, int i4) {
String testString = snippets[i1] + snippets[i2] + snippets[i3] + snippets[i4];
if (binarySearch(wordList, testString) >= 0) {
answers4.append(snippets[i1])
.append("•")
.append(snippets[i2])
.append("•")
.append(snippets[i3])
.append("•")
.append(snippets[i4]).append("\n");
}
}
private void solveIt() {
answers1 = new StringBuffer();
answers2 = new StringBuffer();
answers3 = new StringBuffer();
answers4 = new StringBuffer();
for (int i1 = 0; i1 < snipCount; i1++) {
// test a single snippet
testSingle(i1);
for (int i2 = 0; i2 < snipCount; i2++) {
if (i1 == i2) {
continue;
}
// test a dual snippet
testDouble(i1, i2);
for (int i3 = 0; i3 < snipCount; i3++) {
if (i3 == i2 || i3 == i1) {
continue;
}
// test a triple snippet
testTriple(i1, i2, i3);
for (int i4 = 0; i4 < snipCount; i4++) {
if (i4 == i3 || i4 == i2 || i4 == i1) {
continue;
}
// test a quad snippet
testQuad(i1, i2, i3, i4);
}
}
}
}
answerArea.append(answers4.toString());
answerArea.append(answers3.toString());
answerArea.append(answers2.toString());
answerArea.append(answers1.toString());
solveButton.setText("SOLVE ");
solveButton.setEnabled(true);
clearButton.setEnabled(true);
}