Refactoring
This commit is contained in:
parent
661452d747
commit
472106ba3a
10 changed files with 429 additions and 106 deletions
|
@ -11,7 +11,7 @@ package eu.m724.crossword;
|
|||
*/
|
||||
public record Crossword(
|
||||
int width, int height,
|
||||
String solution,
|
||||
Solution solution,
|
||||
PlacedWord[] words
|
||||
) {
|
||||
/**
|
||||
|
|
|
@ -10,22 +10,25 @@ import java.util.Set;
|
|||
*/
|
||||
public class CrosswordBuilder {
|
||||
private final int width, height;
|
||||
private final String solution;
|
||||
private final String solutionText;
|
||||
|
||||
private final Set<Word> words = new HashSet<>();
|
||||
private final Set<PlacedWord> placedWords = new HashSet<>();
|
||||
|
||||
private Solution solution = null;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CrosswordBuilder}
|
||||
*
|
||||
* @param width The width of the crossword
|
||||
* @param height The height of the crossword
|
||||
* @param solution The solution
|
||||
* @param solutionText The solution text
|
||||
*/
|
||||
public CrosswordBuilder(int width, int height, String solution) {
|
||||
public CrosswordBuilder(int width, int height, String solutionText) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.solution = solution.toLowerCase();
|
||||
System.out.printf("Initialized builder of %dx%d with solution \"%s\"\n", width, height, solution);
|
||||
this.solutionText = solutionText.toLowerCase();
|
||||
System.out.printf("Initialized builder of %dx%d with solution \"%s\"\n", width, height, solutionText);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,8 +49,11 @@ public class CrosswordBuilder {
|
|||
* @return This exact {@link CrosswordBuilder}
|
||||
*/
|
||||
public CrosswordBuilder addWord(Word word) {
|
||||
this.words.add(word);
|
||||
System.out.println("Word added: " + word.word());
|
||||
System.out.println("Adding word: " + word.text());
|
||||
if (word.length() >= Math.max(width, height))
|
||||
System.out.printf("A word is too long and can't be added. Increase crossword size or shorten this word. (%d > %d) %s\n", word.length(), Math.min(width, height) - 1, word.text());
|
||||
else
|
||||
this.words.add(word);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -58,15 +64,43 @@ public class CrosswordBuilder {
|
|||
* @return This exact {@link CrosswordBuilder}
|
||||
*/
|
||||
public CrosswordBuilder addWords(Word... words) {
|
||||
this.words.addAll(Set.of(words));
|
||||
System.out.println("Multiple words added: " + String.join(", ", Arrays.stream(words).map(Word::word).toList()));
|
||||
System.out.println("Adding multiple words: " + String.join(", ", Arrays.stream(words).map(Word::text).toList()));
|
||||
for (Word word : words) {
|
||||
if (word.length() >= Math.min(width, height))
|
||||
System.out.println("A word is too long and can't be added. Increase crossword size or shorten this word. " + word.text());
|
||||
else
|
||||
this.words.add(word);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Generator getGenerator() {
|
||||
return new Generator(width, height, solutionText, words.toArray(Word[]::new));
|
||||
}
|
||||
|
||||
public CrosswordBuilder generate(long seed) {
|
||||
return generate(getGenerator().seed(seed));
|
||||
}
|
||||
|
||||
public CrosswordBuilder generate() {
|
||||
return generate(getGenerator());
|
||||
}
|
||||
|
||||
private CrosswordBuilder generate(Generator generator) {
|
||||
System.out.println("Generator invoked");
|
||||
if (generator == null)
|
||||
generator = getGenerator();
|
||||
|
||||
// TODO perhaps making this an assignment and making placedWords not final is better
|
||||
placedWords.addAll(Generator.generate(width, height, solution, words.toArray(Word[]::new)));
|
||||
Set<PlacedWord> g = generator.generate();
|
||||
if (g == null) {
|
||||
throw new RuntimeException("Unable to find solution. Perhaps the crossword is too small for the words?");
|
||||
}
|
||||
|
||||
placedWords.addAll(g);
|
||||
// TODO improve this too
|
||||
this.solution = generator.solutionFinder(placedWords);
|
||||
|
||||
System.out.println("Generator done");
|
||||
return this;
|
||||
}
|
||||
|
@ -75,23 +109,35 @@ public class CrosswordBuilder {
|
|||
* Builds a {@link Crossword}
|
||||
*
|
||||
* @throws SizeMismatchException When not all words have been placed. Or too many, for some reason.
|
||||
* @throws NoSolutionException if no solution
|
||||
* @return The {@link Crossword}
|
||||
*/
|
||||
public Crossword build() throws SizeMismatchException { // TODO maybe it should be unchecked
|
||||
public Crossword build() {
|
||||
System.out.println("Building");
|
||||
// TODO
|
||||
|
||||
if (words.size() != placedWords.size()) {
|
||||
throw new SizeMismatchException("Words: %d, placed: %d".formatted(words.size(), placedWords.size()));
|
||||
}
|
||||
|
||||
if (solution == null) {
|
||||
throw new NoSolutionException();
|
||||
}
|
||||
|
||||
return new Crossword(width, height, solution, placedWords.toArray(PlacedWord[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* When not all words have been placed. Or too many, for some reason.
|
||||
*/
|
||||
public static class SizeMismatchException extends Exception {
|
||||
public static class SizeMismatchException extends RuntimeException {
|
||||
public SizeMismatchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a solution hasn't been found;
|
||||
*/
|
||||
public static class NoSolutionException extends RuntimeException { }
|
||||
}
|
||||
|
|
|
@ -1,88 +1,202 @@
|
|||
package eu.m724.crossword;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class Generator {
|
||||
private final int width, height;
|
||||
private final String solution;
|
||||
private final String solutionText;
|
||||
private final Word[] words;
|
||||
|
||||
private final char[][] charArray;
|
||||
private final Set<PlacedWord> placedWords;
|
||||
//private final char[][] charArray;
|
||||
//private final Set<PlacedWord> placedWords;
|
||||
|
||||
private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
//private final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
private long seed = ThreadLocalRandom.current().nextLong();
|
||||
private final Random random = new Random(seed);
|
||||
|
||||
private Generator(int width, int height, String solution, Word[] words) {
|
||||
private final List<Vec> possiblePlacements = new ArrayList<>(); // TODO does this belong here
|
||||
private int peakDepth; // TODO useless?
|
||||
|
||||
public Generator(int width, int height, String solutionText, Word[] words) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.solution = solution;
|
||||
this.solutionText = solutionText;
|
||||
this.words = words;
|
||||
|
||||
this.charArray = new char[width][height];
|
||||
this.placedWords = new HashSet<>(words.length);
|
||||
//this.charArray = new char[width][height];
|
||||
//this.placedWords = new HashSet<>(words.length);
|
||||
}
|
||||
|
||||
public static Set<PlacedWord> generate(int width, int height, String solution, Word[] words) {
|
||||
return new Generator(width, height, solution, words).generate();
|
||||
public Generator seed(long seed) {
|
||||
this.seed = seed;
|
||||
random.setSeed(seed);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Set<PlacedWord> generate() {
|
||||
public Solution solutionFinder(Set<PlacedWord> placedWords) {
|
||||
System.out.printf("Solution finder running with %d placed words\n", placedWords.size());
|
||||
long start = System.nanoTime();
|
||||
|
||||
List<Vec> coordinatesList = new ArrayList<>(solutionText.length());
|
||||
|
||||
List<PlacedWord> placedWordList;
|
||||
|
||||
charLoop:
|
||||
for (int i=0; i<solutionText.length(); i++) {
|
||||
char wantedChar = solutionText.charAt(i);
|
||||
|
||||
placedWordList = new ArrayList<>(placedWords);
|
||||
shuffle(placedWordList);
|
||||
|
||||
for (PlacedWord placedWord : placedWordList) {
|
||||
String wordText = placedWord.word().text();
|
||||
int charIndex = wordText.indexOf(wantedChar);
|
||||
Vec coordinates = placedWord.coordinatesOf(charIndex);
|
||||
|
||||
if (charIndex != -1 && !coordinatesList.contains(coordinates)) {
|
||||
coordinatesList.add(coordinates);
|
||||
continue charLoop;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No word has character %c".formatted(wantedChar));
|
||||
}
|
||||
|
||||
System.out.printf("Solution found in %fms\n", (System.nanoTime() - start) / 1000000.0);
|
||||
|
||||
return new Solution(solutionText, coordinatesList.toArray(Vec[]::new));
|
||||
}
|
||||
|
||||
public Set<PlacedWord> generate() {
|
||||
System.out.printf("Generator running with %d words\n", words.length);
|
||||
long start = System.nanoTime();
|
||||
|
||||
boolean vertical = random.nextBoolean();
|
||||
for (Word word : words) {
|
||||
PlacedWord placedWord;
|
||||
for (int x=0; x<width; x++) {
|
||||
for (int y=0; y<height; y++) {
|
||||
possiblePlacements.add(new Vec(x, y));
|
||||
}
|
||||
}
|
||||
System.out.println("Possible placements: " + possiblePlacements.size() + " * 2");
|
||||
|
||||
do {
|
||||
int x = random.nextInt(1, width - (!vertical ? word.length() : 0));
|
||||
int y = random.nextInt(1, height - (vertical ? word.length() : 0));
|
||||
placedWord = new PlacedWord(x, y, vertical, word);
|
||||
} while (!canPlace(placedWord));
|
||||
/*for (Word word : words) {
|
||||
PlacedWord placedWord = null;
|
||||
|
||||
List<Vec> currentPossiblePlacements = new ArrayList<>(possiblePlacements);
|
||||
Collections.shuffle(currentPossiblePlacements);
|
||||
|
||||
pickLoop:
|
||||
for (int i=0; i<2; i++) { // repeating twice to pass horizontal and vertical
|
||||
vertical = !vertical;
|
||||
|
||||
for (Vec placement : currentPossiblePlacements) {
|
||||
|
||||
if (vertical) {
|
||||
int y = placement.y();
|
||||
if (y == 0 || y + word.length() >= height) continue;
|
||||
} else {
|
||||
int x = placement.x();
|
||||
if (x == 0 || x + word.length() >= width) continue;
|
||||
}
|
||||
|
||||
PlacedWord candidate = new PlacedWord(placement, vertical, word);
|
||||
if (canPlace(candidate)) {
|
||||
placedWord = candidate;
|
||||
break pickLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (placedWord == null)
|
||||
return null; // TODO remove problems
|
||||
|
||||
placeWord(placedWord); // TODO rename
|
||||
}*/
|
||||
|
||||
vertical = !vertical;
|
||||
Set<PlacedWord> placedWords = follow(new Grid(width, height), new HashSet<>(), 0);
|
||||
System.out.println("Explored depth: " + peakDepth + " / " + words.length);
|
||||
|
||||
System.out.println("Seed: " + seed);
|
||||
|
||||
double ms = (System.nanoTime() - start) / 1000000.0;
|
||||
System.out.printf("Generation took %fms", ms);
|
||||
|
||||
if (ms > 30000) {
|
||||
System.out.println(". That's a very long time!");
|
||||
} else {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.printf("Generation took %fms\n", (System.nanoTime() - start) / 1000000.0);
|
||||
|
||||
return placedWords;
|
||||
}
|
||||
|
||||
private void placeWord(PlacedWord word) {
|
||||
int x = word.x();
|
||||
int y = word.y();
|
||||
private Set<PlacedWord> follow(Grid grid, Set<PlacedWord> placedChain, int wordIndex) {
|
||||
System.out.println("Depth " + wordIndex);
|
||||
|
||||
placedWords.add(word);
|
||||
if (wordIndex > peakDepth)
|
||||
peakDepth = wordIndex;
|
||||
|
||||
for (int i=0; i<word.length(); i++) {
|
||||
if (word.vertical()) { y++; } else { x++; }
|
||||
if (wordIndex == words.length) {
|
||||
System.out.println(" Completed");
|
||||
return placedChain;
|
||||
}
|
||||
|
||||
char c = word.word().word().charAt(i); // TODO
|
||||
List<Vec> currentPossiblePlacements = new ArrayList<>(possiblePlacements);
|
||||
shuffle(currentPossiblePlacements);
|
||||
//Collections.shuffle(currentPossiblePlacements);
|
||||
|
||||
if (charArray[x][y] == 0 || charArray[x][y] == c) {
|
||||
charArray[x][y] = c;
|
||||
boolean vertical = random.nextBoolean();
|
||||
Word word = words[wordIndex];
|
||||
|
||||
for (int i=0; i<2; i++) { // repeating twice to pass horizontal and vertical
|
||||
vertical = !vertical;
|
||||
|
||||
for (Vec placement : currentPossiblePlacements) {
|
||||
if (vertical) {
|
||||
int y = placement.y();
|
||||
if (y == 0 || y + word.length() >= height) continue;
|
||||
} else {
|
||||
int x = placement.x();
|
||||
if (x == 0 || x + word.length() >= width) continue;
|
||||
}
|
||||
|
||||
PlacedWord candidate = new PlacedWord(placement, vertical, word);
|
||||
|
||||
if (grid.canPlace(candidate)) {
|
||||
System.out.println(" Found candidate");
|
||||
Set<PlacedWord> potentialChain = new HashSet<>(placedChain);
|
||||
potentialChain.add(candidate);
|
||||
|
||||
Grid newGrid = grid.clone();
|
||||
newGrid.placeWord(candidate);
|
||||
|
||||
Set<PlacedWord> newChain = follow(newGrid, potentialChain, wordIndex + 1);
|
||||
System.out.println("Back to depth " + wordIndex);
|
||||
|
||||
// if it's null it means there's no good placement, and we should continue searching at this depth
|
||||
if (newChain != null) {
|
||||
System.out.println(" Unfolding"); // is that a correct word?
|
||||
return newChain;
|
||||
} else {
|
||||
System.out.println(" Looking further");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(" No solution");
|
||||
// no placement at this depth
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean canPlace(PlacedWord word) {
|
||||
int x = word.x();
|
||||
int y = word.y();
|
||||
|
||||
for (int i=0; i<word.length(); i++) {
|
||||
if (word.vertical()) { y++; } else { x++; }
|
||||
|
||||
char c = word.word().word().charAt(i); // TODO
|
||||
|
||||
if (charArray[x][y] != 0 && charArray[x][y] != c) {
|
||||
return false;
|
||||
}
|
||||
private <T> void shuffle(List<T> list) {
|
||||
for (int i = list.size() - 1; i > 0; i--) {
|
||||
int index = random.nextInt(i + 1);
|
||||
// Swap elements
|
||||
T temp = list.get(index);
|
||||
list.set(index, list.get(i));
|
||||
list.set(i, temp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
93
src/main/java/eu/m724/crossword/Grid.java
Normal file
93
src/main/java/eu/m724/crossword/Grid.java
Normal file
|
@ -0,0 +1,93 @@
|
|||
package eu.m724.crossword;
|
||||
|
||||
public class Grid implements Cloneable {
|
||||
private char[][] charArray;
|
||||
|
||||
public Grid(int width, int height) {
|
||||
this.charArray = new char[width][height];
|
||||
}
|
||||
|
||||
public char getCharAt(int x, int y) {
|
||||
return charArray[x][y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a word.<br>
|
||||
* Whether it can be placed is not checked.
|
||||
*
|
||||
* @param placedWord The word to place
|
||||
*/
|
||||
public void placeWord(PlacedWord placedWord) {
|
||||
int x = placedWord.pos().x();
|
||||
int y = placedWord.pos().y();
|
||||
|
||||
int _x = x;
|
||||
int _y = y;
|
||||
|
||||
for (int i=-1; i< placedWord.length(); i++) { // -1 for hint
|
||||
if (placedWord.vertical()) {
|
||||
_y = y + i;
|
||||
} else {
|
||||
_x = x + i;
|
||||
}
|
||||
|
||||
char c = Character.MAX_VALUE - 1; // it would be a bad idea to assign a different id to every hint
|
||||
if (i != -1) {
|
||||
c = placedWord.word().text().charAt(i);
|
||||
}
|
||||
|
||||
if (charArray[_x][_y] == 0 || charArray[_x][_y] == c) {
|
||||
charArray[_x][_y] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a word be placed, will it not collide
|
||||
*
|
||||
* @param word The word
|
||||
* @return can it be placed
|
||||
*/
|
||||
public boolean canPlace(PlacedWord word) {
|
||||
int x = word.pos().x();
|
||||
int y = word.pos().y();
|
||||
|
||||
int _x = x;
|
||||
int _y = y;
|
||||
|
||||
for (int i=-1; i<word.length(); i++) { // -1 for hint
|
||||
if (word.vertical()) {
|
||||
_y = y + i;
|
||||
} else {
|
||||
_x = x + i;
|
||||
}
|
||||
|
||||
char c = Character.MAX_VALUE; // hint
|
||||
if (i != -1) {
|
||||
c = word.word().text().charAt(i);
|
||||
}
|
||||
|
||||
if (charArray[_x][_y] != 0 && charArray[_x][_y] != c) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Grid clone() {
|
||||
try {
|
||||
Grid grid = (Grid) super.clone();
|
||||
|
||||
grid.charArray = charArray.clone();
|
||||
for (int i=0; i<charArray.length; i++) {
|
||||
grid.charArray[i] = charArray[i].clone();
|
||||
} // so when I .clone() a 2d array the objects inside it are not cloned so that's a problem. I struggled with it for several hours. Claude found the solution.
|
||||
|
||||
return grid;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +1,24 @@
|
|||
package eu.m724.crossword;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws CrosswordBuilder.SizeMismatchException {
|
||||
CrosswordBuilder builder = new CrosswordBuilder(20, 20, "ardour")
|
||||
public static void main(String[] args) {
|
||||
CrosswordBuilder builder = new CrosswordBuilder(12, 12, "crossword")
|
||||
.addWord("cat", "Furry feline pet")
|
||||
.addWord("sun", "Bright star in our sky")
|
||||
.addWord("book", "Bound pages with a story")
|
||||
.addWord("tree", "Woody plant with branches")
|
||||
.addWord("blue", "Color of a clear sky")
|
||||
.addWord("door", "Entrance to a room")
|
||||
.addWord("ocean", "Vast body of saltwater")
|
||||
.addWord("mountain", "Large natural elevation of Earth's surface")
|
||||
.addWord("computer", "Electronic device for processing data")
|
||||
.addWord("music", "Art of arranging sounds in time")
|
||||
.addWord("bicycle", "Two-wheeled vehicle powered by pedaling")
|
||||
.addWord("flower", "Reproductive structure of flowering plants")
|
||||
.addWord("rain", "Water droplets falling from the sky")
|
||||
.addWord("camera", "Device for capturing images or videos")
|
||||
.addWord("pizza", "Flat bread topped with sauce and cheese")
|
||||
.addWord("telescope", "Optical instrument for viewing distant objects")
|
||||
.addWord("volcano", "Mountain that erupts molten rock")
|
||||
.addWord("galaxy", "Vast collection of stars and cosmic matter")
|
||||
.addWord("umbrella", "Device for protection against rain or sun")
|
||||
.addWord("microscope", "Instrument for viewing tiny objects")
|
||||
.addWord("butterfly", "Insect with large, colorful wings")
|
||||
.addWord("saxophone", "Brass musical instrument with a reed")
|
||||
.addWord("democracy", "System of government by the people")
|
||||
.addWord("calendar", "System for organizing days and dates")
|
||||
.addWord("tornado", "Violently rotating column of air")
|
||||
.addWord("origami", "Japanese art of paper folding")
|
||||
.addWord("kangaroo", "Large hopping marsupial from Australia")
|
||||
.addWord("lighthouse", "Tower with light to guide ships")
|
||||
.addWord("cactus", "Desert plant with spines")
|
||||
.addWord("ballet", "Classical form of dance")
|
||||
.addWord("pyramid", "Ancient structure with triangular sides")
|
||||
.addWord("owl", "Nocturnal bird of prey")
|
||||
.addWord("kite", "Flying toy on a string")
|
||||
.addWord("moon", "Earth's natural satellite")
|
||||
.addWord("train", "Rail transport vehicle")
|
||||
.addWord("cloud", "Visible mass of water droplets")
|
||||
.addWord("camera", "Device for taking photos")
|
||||
.addWord("locket", "Small ornamental case")
|
||||
.addWord("rainbow", "Multicolored arc in the sky")
|
||||
.addWord("volcano", "Erupting mountain")
|
||||
.addWord("elevator", "Vertical transport device")
|
||||
.addWord("pineapple", "Tropical fruit with spiky skin")
|
||||
.addWord("submarine", "Underwater military vessel")
|
||||
.addWord("sunflower", "Tall yellow-petaled plant")
|
||||
.addWord("helicopter", "Rotary-wing aircraft")
|
||||
.addWord("rhinoceros", "Large horned African mammal")
|
||||
.generate();
|
||||
|
||||
Crossword crossword = builder.build();
|
||||
|
|
|
@ -3,18 +3,30 @@ package eu.m724.crossword;
|
|||
/**
|
||||
* Represents a placement of a word
|
||||
*
|
||||
* @param x x of the first letter
|
||||
* @param y y of the first letter
|
||||
* @param vertical is the word, from the first letter, downwards (true), or rightwards (false)
|
||||
* @param word the word
|
||||
* @param pos Position of the first letter
|
||||
* @param vertical Is the word, from the first letter, going downwards (true), or rightwards (false)
|
||||
* @param word The word
|
||||
*/
|
||||
public record PlacedWord(
|
||||
int x,
|
||||
int y,
|
||||
Vec pos,
|
||||
boolean vertical,
|
||||
Word word
|
||||
) {
|
||||
public int length() {
|
||||
return word.word().length();
|
||||
return word.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates of nth character
|
||||
*
|
||||
* @param index character index, starting from 0
|
||||
* @return the coordinates of the character
|
||||
*/
|
||||
public Vec coordinatesOf(int index) {
|
||||
if (vertical) {
|
||||
return pos.plus(0, index);
|
||||
} else {
|
||||
return pos.plus(index, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import org.jfree.svg.SVGGraphics2D;
|
|||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Renderer {
|
||||
private final int tileSize;
|
||||
|
@ -13,26 +15,63 @@ public class Renderer {
|
|||
this.tileSize = tileSize;
|
||||
}
|
||||
|
||||
public String render(Crossword crossword) {
|
||||
public String render(Crossword crossword) { // TODO split this
|
||||
SVGGraphics2D g2 = new SVGGraphics2D(crossword.width() * tileSize, crossword.height() * tileSize);
|
||||
g2.setPaint(Color.BLACK);
|
||||
|
||||
List<Vec> solutionCoordinates = Arrays.asList(crossword.solution().coordinates());
|
||||
|
||||
for (PlacedWord word : crossword.words()) {
|
||||
int x = word.x();
|
||||
int y = word.y();
|
||||
int x = word.pos().x();
|
||||
int y = word.pos().y();
|
||||
|
||||
String hint = word.word().hint();
|
||||
|
||||
if (word.vertical()) {
|
||||
if (word.vertical()) { // TODO squash that
|
||||
drawStringFit(g2, x * tileSize, (y - 1) * tileSize, hint);
|
||||
|
||||
int[] xPoints = new int[] {
|
||||
toPos(x + 0.25),
|
||||
toPos(x + 0.75),
|
||||
toPos(x + 0.5)
|
||||
};
|
||||
int[] yPoints = new int[] {
|
||||
toPos(y - 1 + 0.7),
|
||||
toPos(y - 1 + 0.7),
|
||||
toPos(y - 1 + 0.9)
|
||||
};
|
||||
g2.drawPolygon(xPoints, yPoints, 3);
|
||||
|
||||
for (int i=0; i<word.length(); i++) {
|
||||
int solIndex;
|
||||
if ((solIndex = solutionCoordinates.indexOf(new Vec(x, y + i))) != -1) {
|
||||
solutionCoordinates.set(solIndex, null);
|
||||
g2.drawString(Integer.toString(solIndex + 1), x * tileSize + 5, (y + i) * tileSize + 15); // TODO align
|
||||
}
|
||||
g2.draw(new Rectangle(x * tileSize, (y + i) * tileSize, tileSize, tileSize));
|
||||
}
|
||||
} else {
|
||||
drawStringFit(g2, (x - 1) * tileSize, y * tileSize, hint);
|
||||
|
||||
int[] xPoints = new int[] {
|
||||
toPos(x - 1 + 0.7),
|
||||
toPos(x - 1 + 0.7),
|
||||
toPos(x - 1 + 0.9)
|
||||
};
|
||||
int[] yPoints = new int[] {
|
||||
toPos(y + 0.25),
|
||||
toPos(y + 0.75),
|
||||
toPos(y + 0.5)
|
||||
};
|
||||
g2.drawPolygon(xPoints, yPoints, 3);
|
||||
|
||||
for (int i=0; i<word.length(); i++) {
|
||||
int solIndex;
|
||||
if ((solIndex = solutionCoordinates.indexOf(new Vec(x + i, y))) != -1) {
|
||||
solutionCoordinates.set(solIndex, null);
|
||||
g2.drawString(Integer.toString(solIndex + 1), (x + i) * tileSize + 5, y * tileSize + 15);
|
||||
}
|
||||
|
||||
g2.draw(new Rectangle((x + i) * tileSize, y * tileSize, tileSize, tileSize));
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +125,8 @@ public class Renderer {
|
|||
graphics2D.drawString(line, x + (int)padding, y + (textHeight * i));
|
||||
}
|
||||
}
|
||||
|
||||
private int toPos(double u) {
|
||||
return (int) (u * tileSize);
|
||||
}
|
||||
}
|
||||
|
|
6
src/main/java/eu/m724/crossword/Solution.java
Normal file
6
src/main/java/eu/m724/crossword/Solution.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
package eu.m724.crossword;
|
||||
|
||||
public record Solution(
|
||||
String text,
|
||||
Vec[] coordinates
|
||||
) { }
|
24
src/main/java/eu/m724/crossword/Vec.java
Normal file
24
src/main/java/eu/m724/crossword/Vec.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package eu.m724.crossword;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Vec( // TODO use this
|
||||
int x,
|
||||
int y
|
||||
) {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Vec vec)) return false;
|
||||
return x == vec.x && y == vec.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(x, y);
|
||||
}
|
||||
|
||||
public Vec plus(int x, int y) {
|
||||
return new Vec(this.x + x, this.y + y);
|
||||
}
|
||||
}
|
|
@ -4,14 +4,14 @@ package eu.m724.crossword;
|
|||
* Represents a word and a hint.<br>
|
||||
* The word should be lowercase. TODO make that not a requirement
|
||||
*
|
||||
* @param word the word
|
||||
* @param text the word text
|
||||
* @param hint the hint / clue
|
||||
*/
|
||||
public record Word(
|
||||
String word,
|
||||
String text,
|
||||
String hint
|
||||
) {
|
||||
public int length() {
|
||||
return word.length();
|
||||
return text.length();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue