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.