Let's now implement a complete, useful program that can help us solve crossword puzzles. You type in a pattern—a word, where some letters have been replaced by question marks— and it displays all the English words that match this pattern.
Here is what it should look like:
import scala.swing._
import scala.swing.event._
class Solver {
private val words = scala.io.Source.fromFile("words.txt").getLines().toSet
private def matches(pattern: String, word: String): Boolean = {
if (word.length != pattern.length)
return false
for (i <- 0 until word.length) {
val p = pattern(i)
if (p != '?' && p != word(i))
return false
}
true
}
def findWords(pattern: String): List[String] = {
var w = List[String]()
for (e <- words) {
if (matches(pattern, e))
w = e :: w
}
w
}
}
class UI(val solver: Solver) extends MainFrame {
private def restrictHeight(s: Component) {
s.maximumSize = new Dimension(Short.MaxValue, s.preferredSize.height)
}
title = "Crossword Puzzle Helper"
val searchField = new TextField { columns = 32 }
val searchButton = new Button("Search")
val searchLine = new BoxPanel(Orientation.Horizontal) {
contents += searchField
contents += Swing.HStrut(20)
contents += searchButton
}
val resultField = new TextArea {
rows = 10
lineWrap = true
wordWrap = true
editable = false
}
// make sure that resizing only changes the resultField:
restrictHeight(searchLine)
contents = new BoxPanel(Orientation.Vertical) {
contents += searchLine
contents += Swing.VStrut(10)
contents += new ScrollPane(resultField)
border = Swing.EmptyBorder(10, 10, 10, 10)
}
listenTo(searchField)
listenTo(searchButton)
reactions += {
case EditDone(`searchField`) => searchNow()
case ButtonClicked(`searchButton`) => searchNow()
}
def searchNow() {
val pattern = searchField.text.toLowerCase
val words = solver.findWords(pattern)
if (words.length == 0) {
resultField.text = "\n\nSorry, no words found."
} else {
resultField.text = words.sorted mkString "\n"
resultField.caret.position = 0
}
}
}
object Crossword {
def main(args: Array[String]) {
val solver = new Solver
val ui = new UI(solver)
ui.visible = true
}
}
Note that the output area (the resultField) is read-only: its contents cannot be changed by the user. This is achieved by setting its editable attribute.
After filling the output area with a long list of words, the cursor (called caret in the Swing documentation) is at the end of the text. I prefer the display to show the beginning of the list, so I move the caret to the beginning using the line:
resultField.caret.position = 0
Note that I have completely separated the logical part of the program (the Solver) from the user interface part (the UI). This is a good strategy for writing maintainable and reusable code.
There is a slightly improved version of this program in crossword2.scala. It has buttons to change the font size and a selector to switch between different word files.