├── .gitignore ├── WordFillVer2.jar ├── ScreenRecording.gif ├── BufMap ├── WordInfo.java ├── README.md ├── Dictionary.java ├── LICENSE ├── Crossword.java └── Display.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | -------------------------------------------------------------------------------- /WordFillVer2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/CrosswordFiller/HEAD/WordFillVer2.jar -------------------------------------------------------------------------------- /ScreenRecording.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/CrosswordFiller/HEAD/ScreenRecording.gif -------------------------------------------------------------------------------- /BufMap: -------------------------------------------------------------------------------- 1 | 1 0 2 | 2 52 3 | 3 229 4 | 4 2661 5 | 5 12776 6 | 6 32000 7 | 7 64655 8 | 8 107735 9 | 9 158459 10 | 10 212409 11 | 11 260864 12 | 12 299648 13 | 13 328313 14 | 14 347899 15 | 15 358534 16 | 16 364438 17 | 17 367107 18 | 18 368421 19 | 19 368763 20 | 20 368983 21 | -------------------------------------------------------------------------------- /WordInfo.java: -------------------------------------------------------------------------------- 1 | public class WordInfo { 2 | public char[] word; 3 | int x; 4 | int y; 5 | char direction; 6 | 7 | WordInfo() { 8 | this.word = new char[0]; 9 | this.x = 0; 10 | this.y = 0; 11 | this.direction = ' '; 12 | } 13 | 14 | WordInfo(final char[] word, final int x, final int y, final char direction) { 15 | this.word = word.clone(); 16 | this.x = x; 17 | this.y = y; 18 | this.direction = direction; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crossword filler 2 | 3 | One of the first programs I ever wrote - a program to find valid English crossword fills given a grid pattern with optional partial completions. 4 | 5 | This project came to mind recently and I looked around on the Wayback Machine. 6 | Turns out I posted the jar on MediaFire and linked to it on an old blog on Jan 2, 2011, and it's [still there](https://www.mediafire.com/file/rwpl49xusm55s2a/WordFillVer2.jar). 7 | I downloaded and opened it on my 2023 MacBook Air, and it ran! 8 | Since it's Java, I'm guessing it runs on other computers, too :) 9 | 10 | I'm pretty sure I never put the source code online (looks like my first GH commit was almost a year later in [Dec 2011](https://github.com/khiner/AI-Challenge-2011/commit/9cd23268070eceb859ce34083a6f6ae25e9c7ac7)), but [David Walton](https://github.com/diggernet) used the [Enhanced Class Decompiler](https://marketplace.eclipse.org/content/enhanced-class-decompiler) Eclipse plugin, configured to use the Procyon decompiler, to recover something similar to the original source. (Thanks, David!) 11 | 12 | Here are my vague recollections from over 13 years ago: 13 | * It uses [beam search](https://en.wikipedia.org/wiki/Beam_search). 14 | * Written in Java. Looking at [the releases](https://www.java.com/releases/), version 6 was the latest at the time. 15 | * UI is Java Swing 16 | * If I remember correctly, it doesn't render _every_ guess on the screen, but rather every N guesses, where N < 10, since the vast majority of time was spend rendering compared to searching. 17 | * For some reason it is very, very yellow (ah simpler times 😅) 18 | 19 | ![](ScreenRecording.gif) 20 | 21 | [Download the jar file](https://github.com/khiner/CrosswordFiller/raw/main/WordFillVer2.jar) and have a blast all weekend long 🤪 22 | 23 | Or, to compile and run the decompiled source: 24 | ```shell 25 | $ javac Display.java 26 | $ java Display 27 | ``` 28 | -------------------------------------------------------------------------------- /Dictionary.java: -------------------------------------------------------------------------------- 1 | import java.io.BufferedWriter; 2 | import java.io.File; 3 | import java.io.FileReader; 4 | import java.util.Scanner; 5 | import java.io.FileWriter; 6 | import java.io.InputStream; 7 | import java.util.HashMap; 8 | import java.util.TreeMap; 9 | 10 | public class Dictionary { 11 | private final String dictFile = "/FinalDict"; 12 | final String bufFile = "/BufMap"; 13 | TreeMap freqMap; 14 | HashMap bufferMap; 15 | InputStream fis; 16 | FileWriter fos; 17 | Scanner scanner; 18 | FileReader fileReader; 19 | File file; 20 | BufferedWriter out; 21 | byte[] buf; 22 | int currBufPos; 23 | int currWordSize; 24 | int markedBufPos; 25 | int markedWordSize; 26 | int bufLen; 27 | 28 | public Dictionary() throws Exception { 29 | this.currBufPos = 0; 30 | this.currWordSize = 0; 31 | this.markedBufPos = 0; 32 | this.markedWordSize = 0; 33 | this.setupBuffer(); 34 | } 35 | 36 | private void setupFreqChart() { 37 | (this.freqMap = new TreeMap()).put("a", 0.08167); 38 | this.freqMap.put("b", 0.01492); 39 | this.freqMap.put("c", 0.02782); 40 | this.freqMap.put("d", 0.04253); 41 | this.freqMap.put("e", 0.12702); 42 | this.freqMap.put("f", 0.02228); 43 | this.freqMap.put("g", 0.02015); 44 | this.freqMap.put("h", 0.06094); 45 | this.freqMap.put("i", 0.06966); 46 | this.freqMap.put("j", 0.00153); 47 | this.freqMap.put("k", 0.00772); 48 | this.freqMap.put("l", 0.04025); 49 | this.freqMap.put("m", 0.02406); 50 | this.freqMap.put("n", 0.06749); 51 | this.freqMap.put("o", 0.07507); 52 | this.freqMap.put("p", 0.01929); 53 | this.freqMap.put("q", 9.5E-4); 54 | this.freqMap.put("r", 0.05987); 55 | this.freqMap.put("s", 0.06327); 56 | this.freqMap.put("t", 0.09056); 57 | this.freqMap.put("u", 0.02758); 58 | this.freqMap.put("v", 0.00987); 59 | this.freqMap.put("w", 0.0236); 60 | this.freqMap.put("x", 0.0015); 61 | this.freqMap.put("y", 0.01974); 62 | this.freqMap.put("z", 7.4E-4); 63 | } 64 | 65 | private double getFreqValue(final char[] s) { 66 | double value = 0.0; 67 | for (int i = 0; i < s.length; ++i) { 68 | value += this.freqMap.get(new StringBuilder().append(Character.toLowerCase(s[i])).toString()); 69 | } 70 | value /= s.length; 71 | return value; 72 | } 73 | 74 | private void setBufMap() throws Exception { 75 | this.bufferMap = new HashMap(); 76 | this.bufLen = (int) new File("/FinalDict").length(); 77 | this.fis = this.getClass().getResourceAsStream("/FinalDict"); 78 | this.buf = new byte[this.bufLen]; 79 | this.fis.read(this.buf); 80 | this.fis.close(); 81 | this.currBufPos = 0; 82 | this.bufferMap.put(1, 0); 83 | int currLength = 1; 84 | while (this.hasNext()) { 85 | final int bufferMark = this.currBufPos; 86 | final char[] string = this.next(); 87 | if (string.length > currLength) { 88 | currLength = string.length; 89 | this.bufferMap.put(currLength, bufferMark); 90 | } 91 | } 92 | this.fos = new FileWriter("/BufMap"); 93 | for (int i = 0; i <= 20; ++i) { 94 | this.fos.write(i + " " + this.bufferMap.get(i) + "\n"); 95 | } 96 | this.fos.close(); 97 | } 98 | 99 | private void setupBuffer() throws Exception { 100 | this.scanner = new Scanner(this.getClass().getResourceAsStream("/BufMap")); 101 | this.bufferMap = new HashMap(); 102 | while (this.scanner.hasNextInt()) { 103 | final int length = this.scanner.nextInt(); 104 | final int buffMark = this.scanner.nextInt(); 105 | this.bufferMap.put(length, buffMark); 106 | } 107 | this.bufLen = 369088; 108 | this.fis = this.getClass().getResourceAsStream("/FinalDict"); 109 | this.buf = new byte[this.bufLen]; 110 | this.fis.read(this.buf); 111 | this.fis.close(); 112 | } 113 | 114 | public void setWordLength(final int size) { 115 | this.currWordSize = size; 116 | this.currBufPos = this.bufferMap.get(size); 117 | } 118 | 119 | public boolean hasNext() { 120 | return this.currBufPos < this.bufferMap.get(this.currWordSize + 1); 121 | } 122 | 123 | public char[] next() { 124 | final char[] string = new char[this.currWordSize]; 125 | int index = 0; 126 | while (this.buf[this.currBufPos] != 10) { 127 | string[index] = (char) this.buf[this.currBufPos]; 128 | ++this.currBufPos; 129 | ++index; 130 | } 131 | ++this.currBufPos; 132 | return string; 133 | } 134 | 135 | public boolean contains(final char[] word) { 136 | this.setWordLength(word.length); 137 | while (this.hasNext()) { 138 | if (this.compareChars(word, this.next())) { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } 144 | 145 | public boolean containsIncomplete(final char[] word) { 146 | this.setWordLength(word.length); 147 | while (this.hasNext()) { 148 | if (this.compareUnfinished(word, this.next())) { 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | 155 | public int howManyMatches(final char[] word) { 156 | this.setWordLength(word.length); 157 | int matches = 0; 158 | while (this.hasNext()) { 159 | if (this.compareUnfinished(word, this.next())) { 160 | ++matches; 161 | } 162 | } 163 | return matches; 164 | } 165 | 166 | public int howManyWords(final int length) { 167 | return (this.bufferMap.get(length + 1) - this.bufferMap.get(length)) / (length + 1); 168 | } 169 | 170 | boolean compareChars(final char[] l, final char[] r) { 171 | if (l.length != r.length) { 172 | return false; 173 | } 174 | for (int i = 0; i < l.length; ++i) { 175 | if (l[i] != r[i]) { 176 | return false; 177 | } 178 | } 179 | return true; 180 | } 181 | 182 | boolean compareUnfinished(final char[] uf, final char[] f) { 183 | if (uf.length != f.length) { 184 | return false; 185 | } 186 | for (int i = 0; i < uf.length; ++i) { 187 | if (uf[i] != ' ' && uf[i] != f[i]) { 188 | return false; 189 | } 190 | } 191 | return true; 192 | } 193 | 194 | public void mark() { 195 | if (this.currBufPos != 0 && this.currWordSize != 0) { 196 | this.markedBufPos = this.currBufPos; 197 | this.markedWordSize = this.currWordSize; 198 | this.currBufPos = this.bufferMap.get(this.currWordSize); 199 | } 200 | } 201 | 202 | public void goBack() { 203 | this.currBufPos = this.markedBufPos; 204 | this.currWordSize = this.markedWordSize; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /Crossword.java: -------------------------------------------------------------------------------- 1 | import java.util.Iterator; 2 | import java.util.Arrays; 3 | import java.util.Stack; 4 | import java.util.ArrayList; 5 | 6 | public class Crossword { 7 | int depth; 8 | boolean stop; 9 | boolean done; 10 | boolean surrounded; 11 | char[][] crossword; 12 | char[][] startState; 13 | ArrayList acrossBlackSquares; 14 | ArrayList downBlackSquares; 15 | ArrayList unfinishedWords; 16 | ArrayList finishedWords; 17 | ArrayList blankWords; 18 | ArrayList nextWords; 19 | Stack lastWords; 20 | Stack tempLastWords; 21 | Stack nodeList; 22 | Dictionary dictionary; 23 | 24 | public Crossword(final char[][] crossword) throws Exception { 25 | this.dictionary = new Dictionary(); 26 | this.stop = false; 27 | this.done = false; 28 | this.crossword = this.copyChars(crossword); 29 | this.startState = this.copyChars(crossword); 30 | this.getBlackSquares(); 31 | this.lastWords = new Stack(); 32 | this.tempLastWords = new Stack(); 33 | this.updateWordLists(); 34 | this.depth = this.blankWords.size() + this.finishedWords.size() + this.unfinishedWords.size(); 35 | } 36 | 37 | public int size() { 38 | return this.crossword.length; 39 | } 40 | 41 | public int getDepth() { 42 | return this.depth; 43 | } 44 | 45 | public char[][] getChars() { 46 | return this.crossword; 47 | } 48 | 49 | public ArrayList getFinished() { 50 | return this.finishedWords; 51 | } 52 | 53 | public ArrayList getUnfinished() { 54 | return this.unfinishedWords; 55 | } 56 | 57 | private void getBlackSquares() { 58 | this.acrossBlackSquares = new ArrayList(); 59 | this.downBlackSquares = new ArrayList(); 60 | for (int x = 0; x < this.crossword.length - 1; ++x) { 61 | for (int y = -1; y < this.crossword.length; ++y) { 62 | if (y == -1 || this.crossword[x][y] == '\n') { 63 | this.acrossBlackSquares.add(new int[] { x, y }); 64 | } 65 | } 66 | } 67 | for (int y2 = 0; y2 < this.crossword.length - 1; ++y2) { 68 | for (int x2 = -1; x2 < this.crossword.length; ++x2) { 69 | if (x2 == -1 || this.crossword[x2][y2] == '\n') { 70 | this.downBlackSquares.add(new int[] { x2, y2 }); 71 | } 72 | } 73 | } 74 | } 75 | 76 | private void updateWordLists() { 77 | this.unfinishedWords = new ArrayList(); 78 | this.finishedWords = new ArrayList(); 79 | this.blankWords = new ArrayList(); 80 | this.nextWords = new ArrayList(); 81 | this.surrounded = false; 82 | for (int x = 0; x < this.acrossBlackSquares.size() - 1; ++x) { 83 | final int blackRow = this.acrossBlackSquares.get(x)[0]; 84 | final int blackColumn = this.acrossBlackSquares.get(x)[1]; 85 | final int nextBlackRow = this.acrossBlackSquares.get(x + 1)[0]; 86 | final int nextBlackColumn = this.acrossBlackSquares.get(x + 1)[1]; 87 | if (blackRow == nextBlackRow && blackColumn + 1 < nextBlackColumn) { 88 | final char[] word = Arrays.copyOfRange(this.crossword[blackRow], blackColumn + 1, nextBlackColumn); 89 | final WordInfo wi = new WordInfo(word, blackRow, blackColumn + 1, 'a'); 90 | int spaceCount = 0; 91 | for (int j = 0; j < word.length; ++j) { 92 | if (word[j] == ' ') { 93 | ++spaceCount; 94 | } 95 | } 96 | if (spaceCount == 0) { 97 | this.finishedWords.add(wi); 98 | } else if (spaceCount == word.length) { 99 | this.blankWords.add(wi); 100 | } else { 101 | this.unfinishedWords.add(wi); 102 | } 103 | } 104 | } 105 | for (int y = 0; y < this.downBlackSquares.size() - 1; ++y) { 106 | final int blackRow = this.downBlackSquares.get(y)[0]; 107 | final int blackColumn = this.downBlackSquares.get(y)[1]; 108 | final int nextBlackRow = this.downBlackSquares.get(y + 1)[0]; 109 | final int nextBlackColumn = this.downBlackSquares.get(y + 1)[1]; 110 | if (blackColumn == nextBlackColumn && blackRow + 1 < nextBlackRow) { 111 | final char[] word = new char[nextBlackRow - (blackRow + 1)]; 112 | for (int i = 0; i < word.length; ++i) { 113 | word[i] = this.crossword[blackRow + 1 + i][blackColumn]; 114 | } 115 | final WordInfo wi = new WordInfo(word, blackRow + 1, blackColumn, 'd'); 116 | int spaceCount = 0; 117 | for (int j = 0; j < word.length; ++j) { 118 | if (word[j] == ' ') { 119 | ++spaceCount; 120 | } 121 | } 122 | if (spaceCount == 0) { 123 | this.finishedWords.add(wi); 124 | } else if (spaceCount == word.length) { 125 | this.blankWords.add(wi); 126 | } else { 127 | this.unfinishedWords.add(wi); 128 | } 129 | } 130 | } 131 | this.getNextWords(); 132 | } 133 | 134 | private void getNextWords() { 135 | this.tempLastWords = (Stack) this.lastWords.clone(); 136 | while (!this.tempLastWords.isEmpty() && this.nextWords.isEmpty()) { 137 | for (final WordInfo uf : this.unfinishedWords) { 138 | if (this.intersects(uf, this.tempLastWords.peek())) { 139 | this.nextWords.add(uf); 140 | } 141 | } 142 | if (this.nextWords.isEmpty()) { 143 | this.tempLastWords.pop(); 144 | this.surrounded = true; 145 | } 146 | } 147 | } 148 | 149 | private WordInfo getNextWord() { 150 | WordInfo nextWord = new WordInfo(); 151 | int leastMatches = Integer.MAX_VALUE; 152 | if (!this.nextWords.isEmpty()) { 153 | for (final WordInfo wi : this.nextWords) { 154 | final int matches = this.dictionary.howManyMatches(wi.word); 155 | if (matches < leastMatches) { 156 | leastMatches = matches; 157 | nextWord = wi; 158 | } 159 | } 160 | } else if (!this.unfinishedWords.isEmpty()) { 161 | for (final WordInfo wi : this.unfinishedWords) { 162 | if (wi.word.length > nextWord.word.length) { 163 | nextWord = wi; 164 | } 165 | } 166 | } else { 167 | for (final WordInfo wi : this.blankWords) { 168 | final int matches = this.dictionary.howManyWords(wi.word.length); 169 | if (matches < leastMatches) { 170 | leastMatches = matches; 171 | nextWord = wi; 172 | } 173 | } 174 | } 175 | return nextWord; 176 | } 177 | 178 | private boolean intersects(final WordInfo l, final WordInfo r) { 179 | return l.direction != r.direction && ((l.direction == 'a' && l.x >= r.x && l.x <= r.x + r.word.length - 1 180 | && l.y <= r.y && r.y <= l.y + l.word.length - 1) 181 | || (l.direction == 'd' && l.y >= r.y && l.y <= r.y + r.word.length - 1 && l.x <= r.x 182 | && r.x <= l.x + l.word.length - 1)); 183 | } 184 | 185 | private void enterWord(final WordInfo wi) { 186 | if (wi.direction == 'a') { 187 | for (int y = wi.y; y < wi.y + wi.word.length; ++y) { 188 | this.crossword[wi.x][y] = wi.word[y - wi.y]; 189 | } 190 | } else if (wi.direction == 'd') { 191 | for (int x = wi.x; x < wi.x + wi.word.length; ++x) { 192 | this.crossword[x][wi.y] = wi.word[x - wi.x]; 193 | } 194 | } 195 | this.updateWordLists(); 196 | } 197 | 198 | void reset() { 199 | this.crossword = this.copyChars(this.startState); 200 | this.updateWordLists(); 201 | } 202 | 203 | private char[][] copyChars(final char[][] r) { 204 | final char[][] temp = new char[r.length][r.length]; 205 | for (int x = 0; x < r.length; ++x) { 206 | for (int y = 0; y < r.length; ++y) { 207 | temp[x][y] = r[x][y]; 208 | } 209 | } 210 | return temp; 211 | } 212 | 213 | private boolean compareFinished(final char[] l, final char[] r) { 214 | if (l.length != r.length) { 215 | return false; 216 | } 217 | for (int i = 0; i < l.length; ++i) { 218 | if (l[i] != r[i]) { 219 | return false; 220 | } 221 | } 222 | return true; 223 | } 224 | 225 | private boolean compareUnfinished(final char[] uf, final char[] f) { 226 | if (uf.length != f.length) { 227 | return false; 228 | } 229 | for (int i = 0; i < uf.length; ++i) { 230 | if (uf[i] != ' ' && uf[i] != f[i]) { 231 | return false; 232 | } 233 | } 234 | return true; 235 | } 236 | 237 | private boolean isOnBoard(final char[] s) { 238 | for (final WordInfo wi : this.finishedWords) { 239 | if (this.compareFinished(s, wi.word)) { 240 | return true; 241 | } 242 | } 243 | return false; 244 | } 245 | 246 | public boolean isValid() { 247 | int finishedCount = 0; 248 | int unfinishedCount = 0; 249 | this.dictionary.mark(); 250 | for (int i = 0; i < this.finishedWords.size(); ++i) { 251 | if (this.dictionary.contains(this.finishedWords.get(i).word)) { 252 | ++finishedCount; 253 | } 254 | } 255 | if (finishedCount < this.finishedWords.size()) { 256 | this.dictionary.goBack(); 257 | return false; 258 | } 259 | for (int i = 0; i < this.unfinishedWords.size(); ++i) { 260 | if (this.dictionary.containsIncomplete(this.unfinishedWords.get(i).word)) { 261 | ++unfinishedCount; 262 | } 263 | } 264 | if (unfinishedCount < this.unfinishedWords.size()) { 265 | this.dictionary.goBack(); 266 | return false; 267 | } 268 | this.dictionary.goBack(); 269 | return true; 270 | } 271 | 272 | public ArrayList getInvalidWords() { 273 | final ArrayList invalidWords = new ArrayList(); 274 | for (int i = 0; i < this.finishedWords.size(); ++i) { 275 | if (!this.dictionary.contains(this.finishedWords.get(i).word)) { 276 | invalidWords.add(this.finishedWords.get(i)); 277 | } 278 | } 279 | for (int i = 0; i < this.unfinishedWords.size(); ++i) { 280 | if (!this.dictionary.containsIncomplete(this.unfinishedWords.get(i).word)) { 281 | invalidWords.add(this.unfinishedWords.get(i)); 282 | } 283 | } 284 | return invalidWords; 285 | } 286 | 287 | private ArrayList getMatches(final WordInfo nextWord, final int c) throws Exception { 288 | final ArrayList matches = new ArrayList(); 289 | int count = 0; 290 | this.dictionary.setWordLength(nextWord.word.length); 291 | while (this.dictionary.hasNext() && count < c) { 292 | final char[] s = this.dictionary.next(); 293 | if (this.compareUnfinished(nextWord.word, s) && !this.isOnBoard(s)) { 294 | final WordInfo wi = new WordInfo(s.clone(), nextWord.x, nextWord.y, nextWord.direction); 295 | final char[][] tempCrossword = this.copyChars(this.crossword); 296 | this.lastWords.push(wi); 297 | this.enterWord(wi); 298 | if (this.isValid()) { 299 | matches.add(new node(this.copyChars(this.crossword), (Stack) this.lastWords.clone())); 300 | if (this.surrounded) { 301 | return matches; 302 | } 303 | if (++count == c) { 304 | continue; 305 | } 306 | this.lastWords.pop(); 307 | } else { 308 | this.crossword = this.copyChars(tempCrossword); 309 | this.lastWords.pop(); 310 | this.updateWordLists(); 311 | } 312 | } 313 | } 314 | return matches; 315 | } 316 | 317 | public void itBroad(int c) throws Exception { 318 | this.nodeList = new Stack(); 319 | this.lastWords = new Stack(); 320 | while (this.finishedWords.size() < this.depth && !this.stop) { 321 | for (final node match : this.getMatches(this.getNextWord(), c)) { 322 | this.nodeList.push(match); 323 | } 324 | if (this.nodeList.isEmpty()) { 325 | this.reset(); 326 | this.itBroad(++c); 327 | } else { 328 | this.crossword = this.copyChars(this.nodeList.peek().crossword); 329 | this.lastWords = (Stack) this.nodeList.pop().path.clone(); 330 | this.updateWordLists(); 331 | Display.displayWords(this.crossword); 332 | } 333 | } 334 | this.done = true; 335 | } 336 | 337 | public void stop() { 338 | this.stop = true; 339 | } 340 | 341 | public boolean isDone() { 342 | return this.done; 343 | } 344 | 345 | public void fill() throws Exception { 346 | this.itBroad(2); 347 | } 348 | 349 | private class node { 350 | char[][] crossword; 351 | Stack path; 352 | 353 | public node(final char[][] crossword, final Stack path) { 354 | this.crossword = Crossword.this.copyChars(crossword); 355 | this.path = path; 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /Display.java: -------------------------------------------------------------------------------- 1 | import java.awt.event.MouseEvent; 2 | import java.awt.event.FocusEvent; 3 | import java.awt.event.KeyEvent; 4 | import javax.swing.border.Border; 5 | import javax.swing.border.LineBorder; 6 | import java.awt.event.MouseListener; 7 | import java.awt.event.FocusListener; 8 | import java.awt.event.KeyListener; 9 | import java.text.DecimalFormat; 10 | import javax.swing.SwingWorker; 11 | import java.util.Iterator; 12 | import java.util.ArrayList; 13 | import java.awt.event.ActionEvent; 14 | import javax.swing.event.ChangeEvent; 15 | import java.util.Random; 16 | import java.awt.Component; 17 | import javax.swing.JLabel; 18 | import java.awt.Font; 19 | import java.awt.LayoutManager; 20 | import java.util.concurrent.Executors; 21 | import javax.swing.JTextField; 22 | import java.awt.GridLayout; 23 | import javax.swing.JTextArea; 24 | import javax.swing.JButton; 25 | import javax.swing.JSlider; 26 | import javax.swing.JPanel; 27 | import javax.swing.JFrame; 28 | import java.awt.Color; 29 | import java.util.concurrent.Executor; 30 | import java.awt.event.ActionListener; 31 | import javax.swing.event.ChangeListener; 32 | 33 | public class Display implements ChangeListener, ActionListener { 34 | private final Executor executor; 35 | static final Color highlightColor; 36 | JFrame mainWindow; 37 | JPanel board; 38 | JPanel mainPanel; 39 | JPanel sizePanel; 40 | JPanel densityPanel; 41 | JSlider densitySlider; 42 | JSlider sizeSlider; 43 | JButton stopGo; 44 | JButton reset; 45 | JTextArea display; 46 | GridLayout gl; 47 | static Square[][] grid; 48 | static JTextField currentSquare; 49 | static int size; 50 | static int density; 51 | static int halfway; 52 | static double startTime; 53 | static double time; 54 | Crossword crossword; 55 | boolean across; 56 | String startInfo; 57 | String endInfo; 58 | 59 | static { 60 | highlightColor = Color.cyan; 61 | } 62 | 63 | public Display() { 64 | this.executor = Executors.newCachedThreadPool(); 65 | this.startInfo = "Click on the board to type in words.\nUse arrows to navigate the board.\nDouble-clicking alternates between across & down."; 66 | this.endInfo = "Click on the board or press 'Reset' to clear the board"; 67 | Display.size = 8; 68 | Display.density = 30; 69 | this.across = true; 70 | Display.halfway = (int) Math.ceil(Display.size / 2.0); 71 | (this.mainWindow = new JFrame()).setLayout(new GridLayout(0, 2)); 72 | this.board = new JPanel(); 73 | (this.stopGo = new JButton("FILL")).setFont(new Font("Dialog", 1, 40)); 74 | this.stopGo.setBackground(Color.ORANGE); 75 | this.stopGo.setFocusPainted(false); 76 | this.stopGo.addActionListener(this); 77 | (this.reset = new JButton("RESET")).setFont(new Font("Dialog", 1, 40)); 78 | this.reset.setBackground(Color.ORANGE); 79 | this.reset.setFocusPainted(false); 80 | this.reset.addActionListener(this); 81 | (this.densitySlider = new JSlider(0, 0, 45, Display.density)).setMajorTickSpacing(5); 82 | this.densitySlider.setPaintTicks(true); 83 | this.densitySlider.setPaintLabels(true); 84 | this.densitySlider.setSnapToTicks(true); 85 | this.densitySlider.setBackground(Color.YELLOW); 86 | this.densitySlider.addChangeListener(this); 87 | this.densityPanel = new JPanel(new GridLayout(2, 0)); 88 | JLabel label = new JLabel(); 89 | label.setFont(new Font("Dialog", 1, 20)); 90 | label.setText("% of Black Squares"); 91 | this.densityPanel.add(label); 92 | this.densityPanel.add(this.densitySlider); 93 | this.densityPanel.setBackground(Color.YELLOW); 94 | this.densityPanel 95 | .setToolTipText("This is roughly the percentage of black squares that will appear on the board."); 96 | (this.sizeSlider = new JSlider(0, 2, 14, Display.size)).setMajorTickSpacing(1); 97 | this.sizeSlider.setPaintLabels(true); 98 | this.sizeSlider.setSnapToTicks(true); 99 | this.sizeSlider.setBackground(Color.YELLOW); 100 | this.sizeSlider.addChangeListener(this); 101 | this.sizePanel = new JPanel(new GridLayout(2, 0)); 102 | label = new JLabel(); 103 | label.setFont(new Font("Dialog", 1, 20)); 104 | label.setText("Size of Crossword"); 105 | this.sizePanel.add(label); 106 | this.sizePanel.add(this.sizeSlider); 107 | this.sizePanel.setBackground(Color.YELLOW); 108 | this.sizePanel.setToolTipText("This is the number of tiles on a side of the crossword square"); 109 | (this.display = new JTextArea()).setFont(new Font("Dialog", 1, 18)); 110 | this.display.setOpaque(true); 111 | this.display.setBackground(Color.YELLOW); 112 | this.display.setText(this.startInfo); 113 | (this.mainPanel = new JPanel(new GridLayout(5, 0))).add(this.densityPanel); 114 | this.mainPanel.add(this.sizePanel); 115 | this.mainPanel.add(this.stopGo); 116 | this.mainPanel.add(this.reset); 117 | this.mainPanel.add(this.display); 118 | this.mainWindow.getContentPane().add(this.mainPanel); 119 | this.mainWindow.getContentPane().add(this.board); 120 | this.mainWindow.pack(); 121 | this.mainWindow.setDefaultCloseOperation(3); 122 | this.mainWindow.setExtendedState(6); 123 | this.populateBoard(); 124 | } 125 | 126 | private void grayAll() { 127 | for (int x = 0; x < Display.grid.length; ++x) { 128 | for (int y = 0; y < Display.grid.length; ++y) { 129 | if (Display.grid[x][y].getText().isEmpty() && !Display.grid[x][y].isLocked()) { 130 | Display.grid[x][y].setBackground(Color.lightGray); 131 | } 132 | } 133 | } 134 | } 135 | 136 | private void redHighlight(final WordInfo word) { 137 | if (word.direction == 'a') { 138 | for (int i = 0; i < word.word.length; ++i) { 139 | Display.grid[word.x][word.y + i].setBackground(Color.pink); 140 | } 141 | } else if (word.direction == 'd') { 142 | for (int i = 0; i < word.word.length; ++i) { 143 | Display.grid[word.x + i][word.y].setBackground(Color.pink); 144 | } 145 | } 146 | } 147 | 148 | private void lockAll() { 149 | for (int x = 0; x < Display.size; ++x) { 150 | for (int y = 0; y < Display.size; ++y) { 151 | Display.grid[x][y].setLock(true); 152 | } 153 | } 154 | } 155 | 156 | private String getAlpha(final String key) { 157 | if (key.length() != 1) { 158 | return ""; 159 | } 160 | try { 161 | Integer.parseInt(key); 162 | } catch (final NumberFormatException e) { 163 | return key; 164 | } 165 | return ""; 166 | } 167 | 168 | public void populateBoard() { 169 | Display.grid = new Square[Display.size][Display.size]; 170 | this.gl = new GridLayout(Display.size, Display.size); 171 | this.board.removeAll(); 172 | this.board.setLayout(this.gl); 173 | final Random rand = new Random(); 174 | final int[][] seeds = new int[Display.size][Display.halfway]; 175 | for (int x = 0; x < Display.size; ++x) { 176 | for (int y = 0; y < Display.halfway; ++y) { 177 | seeds[x][y] = rand.nextInt(100); 178 | } 179 | } 180 | int x2 = Display.size; 181 | for (int x3 = 0; x3 < Display.size; ++x3) { 182 | --x2; 183 | int y2 = (Display.size % 2 == 0) ? Display.halfway : (Display.halfway - 1); 184 | for (int y3 = 0; y3 < Display.size; ++y3) { 185 | Display.grid[x3][y3] = new Square(x3, y3); 186 | boolean setBlack; 187 | if (y3 < Display.halfway) { 188 | setBlack = (seeds[x3][y3] < Display.density); 189 | } else { 190 | --y2; 191 | setBlack = (seeds[x2][y2] < Display.density); 192 | } 193 | if (setBlack) { 194 | Display.grid[x3][y3].setBackground(Color.black); 195 | Display.grid[x3][y3].setLock(true); 196 | Display.grid[x3][y3].setFocusable(false); 197 | } else { 198 | Display.grid[x3][y3].setBackground(Color.lightGray); 199 | } 200 | this.board.add(Display.grid[x3][y3]); 201 | } 202 | } 203 | this.board.repaint(); 204 | this.mainWindow.setVisible(true); 205 | } 206 | 207 | public char[][] getChars() { 208 | final char[][] crossword = new char[Display.size + 1][Display.size + 1]; 209 | for (int x = 0; x < Display.size; ++x) { 210 | for (int y = 0; y < Display.size; ++y) { 211 | if (Display.grid[x][y].getBackground() == Color.black) { 212 | crossword[x][y] = '\n'; 213 | } else if (!Display.grid[x][y].getText().isEmpty()) { 214 | crossword[x][y] = Display.grid[x][y].getText().charAt(0); 215 | } else { 216 | crossword[x][y] = ' '; 217 | } 218 | } 219 | } 220 | for (int x = 0; x < Display.size; ++x) { 221 | crossword[x][Display.size] = '\n'; 222 | } 223 | for (int y2 = 0; y2 < Display.size; ++y2) { 224 | crossword[Display.size][y2] = '\n'; 225 | } 226 | return crossword; 227 | } 228 | 229 | static void displayWords(final char[][] crossword) { 230 | for (int x = 0; x < crossword.length - 1; ++x) { 231 | for (int y = 0; y < crossword.length - 1; ++y) { 232 | if (crossword[x][y] != '\n') { 233 | Display.grid[x][y].setText(new StringBuilder().append(crossword[x][y]).toString()); 234 | } 235 | } 236 | } 237 | } 238 | 239 | private void ungray() { 240 | for (int x = 0; x < Display.size; ++x) { 241 | for (int y = 0; y < Display.size; ++y) { 242 | Display.grid[x][y].setLock(true); 243 | if (Display.grid[x][y].getText().isEmpty() && Display.grid[x][y].getBackground() != Color.black) { 244 | Display.grid[x][y].setBackground(Color.white); 245 | } 246 | } 247 | } 248 | } 249 | 250 | private void reset() { 251 | for (int x = 0; x < Display.size; ++x) { 252 | for (int y = 0; y < Display.size; ++y) { 253 | if (Display.grid[x][y].getBackground() != Color.black) { 254 | Display.grid[x][y].setText(""); 255 | Display.grid[x][y].setBackground(Color.lightGray); 256 | Display.grid[x][y].setLock(false); 257 | } 258 | } 259 | } 260 | this.display.setText(this.startInfo); 261 | } 262 | 263 | @Override 264 | public void stateChanged(final ChangeEvent e) { 265 | this.stop(); 266 | final JSlider source = (JSlider) e.getSource(); 267 | if (source.equals(this.sizeSlider) && source.getValue() != Display.size) { 268 | Display.size = source.getValue(); 269 | Display.halfway = (int) Math.ceil(Display.size / 2.0); 270 | this.populateBoard(); 271 | } else if (source.equals(this.densitySlider) 272 | && (source.getValue() <= Display.density - 5 || source.getValue() >= Display.density + 5)) { 273 | Display.density = source.getValue(); 274 | this.populateBoard(); 275 | } 276 | } 277 | 278 | @Override 279 | public void actionPerformed(final ActionEvent e) { 280 | final JButton source = (JButton) e.getSource(); 281 | if (source.equals(this.stopGo) && this.stopGo.getText() == "FILL") { 282 | this.start(); 283 | } else if (source.equals(this.stopGo) && this.stopGo.getText() == "STOP") { 284 | this.stop(); 285 | } else if (source.equals(this.reset)) { 286 | this.stop(); 287 | this.reset(); 288 | } 289 | } 290 | 291 | private void start() { 292 | try { 293 | this.crossword = new Crossword(this.getChars()); 294 | final ArrayList invalidWords = this.crossword.getInvalidWords(); 295 | if (invalidWords.isEmpty()) { 296 | this.ungray(); 297 | this.stopGo.setText("STOP"); 298 | this.executor.execute(new crosswordWorker()); 299 | this.executor.execute(new timeWorker()); 300 | } else { 301 | this.lockAll(); 302 | final StringBuffer sb = new StringBuffer(); 303 | boolean incomplete = false; 304 | sb.append("Unfortunate news:\n"); 305 | for (final WordInfo word : invalidWords) { 306 | for (int i = 0; i < word.word.length; ++i) { 307 | if (word.word[i] == ' ') { 308 | word.word[i] = '_'; 309 | incomplete = true; 310 | } 311 | } 312 | if (incomplete) { 313 | sb.append("None of my " + word.word.length + " letter words complete "); 314 | sb.append(word.word); 315 | sb.append(".\n"); 316 | } else { 317 | sb.append(word.word); 318 | sb.append(" is not in my dictionary.\n"); 319 | } 320 | this.redHighlight(word); 321 | this.display.setText(String.valueOf(sb.toString()) + this.endInfo); 322 | } 323 | } 324 | } catch (final Exception e1) { 325 | this.display.setText(e1.toString()); 326 | } 327 | } 328 | 329 | private void stop() { 330 | if (this.crossword != null) { 331 | this.crossword.stop(); 332 | this.stopGo.setText("FILL"); 333 | } 334 | } 335 | 336 | public static void main(final String[] args) throws Exception { 337 | new Display(); 338 | } 339 | 340 | private class crosswordWorker extends SwingWorker { 341 | public Void doInBackground() throws Exception { 342 | try { 343 | Display.this.crossword.fill(); 344 | Display.this.stopGo.setText("FILL"); 345 | } catch (final Exception e1) { 346 | e1.printStackTrace(); 347 | } 348 | return ((SwingWorker) this).get(); 349 | } 350 | } 351 | 352 | private class timeWorker extends SwingWorker { 353 | public Void doInBackground() throws Exception { 354 | final DecimalFormat format = new DecimalFormat("#.###"); 355 | Display.startTime = (double) System.currentTimeMillis(); 356 | while (!Display.this.crossword.isDone()) { 357 | Display.time = (System.currentTimeMillis() - Display.startTime) / 1000.0; 358 | final String message = (Display.time >= 60.0) 359 | ? ((int) (Display.time / 60.0) + " minutes " + format.format(Display.time % 60.0) + " seconds") 360 | : (format.format(Display.time) + " seconds"); 361 | Display.this.display.setText(message); 362 | } 363 | return ((SwingWorker) this).get(); 364 | } 365 | } 366 | 367 | class Square extends JTextField implements KeyListener, FocusListener, MouseListener { 368 | int x; 369 | int y; 370 | int count; 371 | boolean locked; 372 | boolean clicked; 373 | 374 | public Square(final int x, final int y) { 375 | this.x = x; 376 | this.y = y; 377 | this.locked = false; 378 | this.clicked = false; 379 | this.setFont(new Font("Dialog", 0, (int) (500.0 / Display.size))); 380 | this.setForeground(Color.black); 381 | this.setDisabledTextColor(Color.black); 382 | this.setHorizontalAlignment(0); 383 | this.setEnabled(true); 384 | this.setBorder(new LineBorder(Color.black, 1)); 385 | this.addKeyListener(this); 386 | this.addFocusListener(this); 387 | this.addMouseListener(this); 388 | this.setEditable(false); 389 | } 390 | 391 | public void setLock(final boolean bool) { 392 | this.locked = bool; 393 | } 394 | 395 | public boolean isLocked() { 396 | return this.locked; 397 | } 398 | 399 | private void highlightAcross() { 400 | Display.this.across = true; 401 | Display.this.grayAll(); 402 | for (int count = 0; this.y - count >= 0 && Display.grid[this.x][this.y - count].isFocusable(); ++count) { 403 | Display.grid[this.x][this.y - count].setBackground(Display.highlightColor); 404 | } 405 | for (int count = 0; this.y + count < Display.size 406 | && Display.grid[this.x][this.y + count].isFocusable(); ++count) { 407 | Display.grid[this.x][this.y + count].setBackground(Display.highlightColor); 408 | } 409 | } 410 | 411 | private void highlightDown() { 412 | Display.this.across = false; 413 | Display.this.grayAll(); 414 | for (int count = 0; this.x - count >= 0 && Display.grid[this.x - count][this.y].isFocusable(); ++count) { 415 | Display.grid[this.x - count][this.y].setBackground(Display.highlightColor); 416 | } 417 | for (int count = 0; this.x + count < Display.size 418 | && Display.grid[this.x + count][this.y].isFocusable(); ++count) { 419 | Display.grid[this.x + count][this.y].setBackground(Display.highlightColor); 420 | } 421 | } 422 | 423 | private boolean isAcrossSquare() { 424 | return ((this.squareToLeft() != null && !this.squareToLeft().isBlack()) 425 | || (this.squareToRight() != null && !this.squareToRight().isBlack()) || this.squareBelow() == null 426 | || this.squareBelow().isBlack() || this.squareAbove() == null || this.squareAbove().isBlack()) 427 | && (this.squareAbove() == null || this.squareAbove().isBlack() 428 | || (this.squareBelow() != null && !this.squareBelow().isBlack())) 429 | && ((this.squareAbove() != null && !this.squareAbove().isBlack()) || this.squareBelow() == null 430 | || this.squareBelow().isBlack()); 431 | } 432 | 433 | public boolean isBlack() { 434 | return this.getBackground() == Color.black; 435 | } 436 | 437 | private Square squareToLeft() { 438 | if (this.y - 1 >= 0) { 439 | return Display.grid[this.x][this.y - 1]; 440 | } 441 | return null; 442 | } 443 | 444 | private Square squareToRight() { 445 | if (this.y + 1 < Display.size) { 446 | return Display.grid[this.x][this.y + 1]; 447 | } 448 | return null; 449 | } 450 | 451 | private Square squareAbove() { 452 | if (this.x - 1 >= 0) { 453 | return Display.grid[this.x - 1][this.y]; 454 | } 455 | return null; 456 | } 457 | 458 | private Square squareBelow() { 459 | if (this.x + 1 < Display.size) { 460 | return Display.grid[this.x + 1][this.y]; 461 | } 462 | return null; 463 | } 464 | 465 | @Override 466 | public void keyPressed(final KeyEvent e) { 467 | if (this.locked) { 468 | return; 469 | } 470 | switch (e.getKeyCode()) { 471 | case 40: { 472 | Display.this.across = false; 473 | this.count = 1; 474 | while (this.x + this.count < Display.size) { 475 | if (Display.grid[this.x + this.count][this.y].isFocusable()) { 476 | Display.grid[this.x + this.count][this.y].requestFocus(); 477 | break; 478 | } 479 | ++this.count; 480 | } 481 | break; 482 | } 483 | case 39: { 484 | Display.this.across = true; 485 | this.count = 1; 486 | while (this.y + this.count < Display.size) { 487 | if (Display.grid[this.x][this.y + this.count].isFocusable()) { 488 | Display.grid[this.x][this.y + this.count].requestFocus(); 489 | break; 490 | } 491 | ++this.count; 492 | } 493 | break; 494 | } 495 | case 38: { 496 | this.count = 1; 497 | Display.this.across = false; 498 | this.highlightDown(); 499 | while (this.x - this.count < Display.size) { 500 | if (Display.grid[this.x - this.count][this.y].isFocusable()) { 501 | Display.grid[this.x - this.count][this.y].requestFocus(); 502 | break; 503 | } 504 | ++this.count; 505 | } 506 | break; 507 | } 508 | case 37: { 509 | Display.this.across = true; 510 | this.highlightAcross(); 511 | this.count = 1; 512 | while (this.y - this.count < Display.size) { 513 | if (Display.grid[this.x][this.y - this.count].isFocusable()) { 514 | Display.grid[this.x][this.y - this.count].requestFocus(); 515 | break; 516 | } 517 | ++this.count; 518 | } 519 | break; 520 | } 521 | case 8: { 522 | this.count = 1; 523 | if (!this.getText().isEmpty()) { 524 | this.setText(""); 525 | break; 526 | } 527 | if (Display.this.across) { 528 | while (this.y - this.count < Display.size) { 529 | if (Display.grid[this.x][this.y - this.count].isFocusable()) { 530 | Display.grid[this.x][this.y - this.count].requestFocus(); 531 | Display.grid[this.x][this.y - this.count].setText(""); 532 | break; 533 | } 534 | ++this.count; 535 | } 536 | break; 537 | } 538 | while (this.x - this.count < Display.size) { 539 | if (Display.grid[this.x - this.count][this.y].isFocusable()) { 540 | Display.grid[this.x - this.count][this.y].requestFocus(); 541 | Display.grid[this.x - this.count][this.y].setText(""); 542 | break; 543 | } 544 | ++this.count; 545 | } 546 | break; 547 | } 548 | case 10: { 549 | Display.this.start(); 550 | break; 551 | } 552 | default: { 553 | final String key = Display.this.getAlpha(KeyEvent.getKeyText(e.getKeyCode())); 554 | if (key == "") { 555 | break; 556 | } 557 | this.setText(String.valueOf(key)); 558 | this.count = 1; 559 | if (Display.this.across) { 560 | while (this.y + this.count < Display.size) { 561 | if (Display.grid[this.x][this.y + this.count].isFocusable()) { 562 | Display.grid[this.x][this.y + this.count].requestFocus(); 563 | break; 564 | } 565 | ++this.count; 566 | } 567 | break; 568 | } 569 | while (this.x + this.count < Display.size) { 570 | if (Display.grid[this.x + this.count][this.y].isFocusable()) { 571 | Display.grid[this.x + this.count][this.y].requestFocus(); 572 | break; 573 | } 574 | ++this.count; 575 | } 576 | break; 577 | } 578 | } 579 | } 580 | 581 | @Override 582 | public void keyReleased(final KeyEvent e) { 583 | } 584 | 585 | @Override 586 | public void keyTyped(final KeyEvent e) { 587 | } 588 | 589 | @Override 590 | public void focusGained(final FocusEvent e) { 591 | if (!this.locked) { 592 | this.getCaret().setVisible(true); 593 | if (Display.this.across) { 594 | this.highlightAcross(); 595 | } else if (!Display.this.across) { 596 | this.highlightDown(); 597 | } 598 | } 599 | } 600 | 601 | @Override 602 | public void focusLost(final FocusEvent e) { 603 | this.getCaret().setVisible(false); 604 | this.clicked = false; 605 | } 606 | 607 | @Override 608 | public void mouseClicked(final MouseEvent arg0) { 609 | if (this.locked) { 610 | if (Display.this.stopGo.getText() == "STOP") { 611 | Display.this.stop(); 612 | } else if (Display.this.stopGo.getText() == "FILL") { 613 | Display.this.reset(); 614 | } 615 | } else if (this.clicked) { 616 | if (Display.this.across) { 617 | Display.this.across = false; 618 | this.highlightDown(); 619 | } else { 620 | Display.this.across = true; 621 | this.highlightAcross(); 622 | } 623 | } else { 624 | this.clicked = true; 625 | } 626 | } 627 | 628 | @Override 629 | public void mouseEntered(final MouseEvent arg0) { 630 | if (this.locked) { 631 | return; 632 | } 633 | if (this.isAcrossSquare()) { 634 | Display.this.across = true; 635 | this.highlightAcross(); 636 | } else { 637 | Display.this.across = false; 638 | this.highlightDown(); 639 | } 640 | } 641 | 642 | @Override 643 | public void mouseExited(final MouseEvent arg0) { 644 | Display.this.grayAll(); 645 | } 646 | 647 | @Override 648 | public void mousePressed(final MouseEvent arg0) { 649 | } 650 | 651 | @Override 652 | public void mouseReleased(final MouseEvent arg0) { 653 | } 654 | } 655 | } 656 | --------------------------------------------------------------------------------