diff --git a/cross-world.crossword-generator/pom.xml b/cross-world.crossword-generator/pom.xml index 4303f08..7acec4a 100644 --- a/cross-world.crossword-generator/pom.xml +++ b/cross-world.crossword-generator/pom.xml @@ -1,37 +1,42 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - cross-world.crossword-generator - jar + cross-world.crossword-generator + jar - cross-world.crossword-generator + cross-world.crossword-generator - - UTF-8 - + + UTF-8 + - - - junit - junit - 3.8.1 - test - - - team-rocket - cross-world.commons - 1.0.0 - - - org.mongodb - mongo-java-driver - 2.7.2 - - - - team-rocket - cross-world.parent - 1.0.0 - + + + junit + junit + 3.8.1 + test + + + log4j + log4j + 1.2.16 + + + team-rocket + cross-world.commons + 1.0.0 + + + org.mongodb + mongo-java-driver + 2.7.2 + + + + team-rocket + cross-world.parent + 1.0.0 + diff --git a/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrossWorldCrosswordGenerator.java b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrossWorldCrosswordGenerator.java index 33155f6..ab52358 100644 --- a/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrossWorldCrosswordGenerator.java +++ b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrossWorldCrosswordGenerator.java @@ -1,6 +1,7 @@ package team_rocket.cross_world.crossword_generator; import java.net.UnknownHostException; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; @@ -9,7 +10,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Map.Entry; import java.util.Random; import java.util.Set; @@ -20,17 +20,45 @@ public class CrossWorldCrosswordGenerator implements CrosswordGenerator { + // private static Logger logger = Logger + // .getLogger(CrossWorldCrosswordGenerator.class); + private WordProvider wordProvider; public static void main(String[] args) throws Exception { + // BasicConfigurator.configure(); + // logger.addAppender(new FileAppender(new SimpleLayout(), + // "/crossword-generator.log")); + // logger.addAppender(new ConsoleAppender(new SimpleLayout())); + CrossWorldCrosswordGenerator cwcg = new CrossWorldCrosswordGenerator( new WordProvider(new WordDictionaryCreator())); // cwcg.generateCrossword(3, 5, new int[] { 3, 4, 5, 6, 10, 11, }); // cwcg.generateCrossword(3, 3, new int[] {}); - //cwcg.generateCrossword(5, 5, new int[] {}); - cwcg.generateCrossword(9, 9, new int[] { 0, 1, 2, 6, 7, 8, 9, 10, 16, - 17, 18, 26, 31, 39, 40, 41, 49, 54, 62, 63, 64, 70, 71, 72, 73, - 74, 78, 79, 80 }); + // cwcg.generateCrossword(5, 5, new int[] {}); +// cwcg.generateCrossword(9, 9, new int[] { 0, 1, 2, 6, 7, 8, 9, 10, 16, +// 17, 18, 26, 31, 39, 40, 41, 49, 54, 62, 63, 64, 70, 71, 72, 73, +// 74, 78, 79, 80 }); +// cwcg.generateCrossword(13, 13, new int[] { +// 4, 17, 30, 134, 147, 160, 8, 21, 34, 138, 151, 164, +// 52, 53, 54, 62, 63, 64, 104, 105, 106, 114, 115, 116, +// 45, +// 58, +// 70, 71, 72, +// 81, 82, 83, 84, 85, 86, 87, +// 96, 97, 98, +// 110, +// 123 +// }); + cwcg.generateCrossword(13, 13, new int[] { + 4, 17, 30, 134, 147, 160, 8, 21, 34, 138, 151, 164, + 52, 53, 54, 62, 63, 64, 104, 105, 106, 114, 115, 116, + 58, + 70, 71, 72, + 82, 83, 84, 85, 86, + 96, 97, 98, + 110 + }); } @@ -66,7 +94,6 @@ private void printState(Map state) + wordProvider.getWord( wordEntry.getValue().getWordLength(), chosenWordIndex)); - } } @@ -117,6 +144,7 @@ private WordState getMostConstrainedWordState( private int getBestWordMatch(WordState wordState, Map state, boolean[] preconditions) throws UnknownHostException, MongoException { + //TODO: extract 10 int wordCount = Math.min(wordState.getNumberOfChoice(), 10); boolean[] avalableWords = wordState.getAvailableWords(); if (preconditions != null) { @@ -225,10 +253,64 @@ private Set getRandomAvailableWords(boolean[] availableWords, } return chosenWordIndexes; } + + /** + * Creates a starting grid and lists for the starting positions of the words. + * crosswordField must contain 0 in every cell that is considered free and -1 for + * every unavailable("blank") cell. + * Modifies crosswordField to contain the starting grid, acrossWords and + * downWords to contain the start index and position of the across words and down + * words. + * + * @param crosswordField + * @param acrossWords + * @param downWords + */ + public static void createStartingGrid(int[][] crosswordField, List acrossWords, + List downWords) { + int wordIndex = 1; + int rows = crosswordField.length; + int cols = crosswordField[0].length; + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + if (crosswordField[i][j] != -1) { + boolean isWordStart = false; + if ((i == 0) || (crosswordField[i - 1][j] == -1)) { + int k = 0; + while ((k + i) < rows && crosswordField[i + k][j] != -1) { + k++; + } + if (k > 2) { + downWords.add(new int[] { wordIndex, i, j }); + isWordStart = true; + } + } + if ((j == 0) || (crosswordField[i][j - 1] == -1)) { + int k = 0; + while ((j + k) < cols && crosswordField[i][j + k] != -1) { + k++; + } + if (k > 2) { + acrossWords.add(new int[] { wordIndex, i, j }); + isWordStart = true; + } + } + + if (isWordStart) { + crosswordField[i][j] = wordIndex; + wordIndex++; + } + } + } + } + } + private boolean getFinalState(Map wordStateMap) throws UnknownHostException, MongoException { WordState wordState = null; + Map> backtracksPerWordstate = new HashMap>(); Deque crosswordStates = new LinkedList(); boolean[] preconditions = null; do { @@ -240,9 +322,7 @@ private boolean getFinalState(Map wordStateMap) return true; } - System.out.println("Processing " - + wordState.getId().getWordNumber() + ", " - + (wordState.getId().isAcross() ? "across" : "down")); + System.out.println("Processing " + getIdentifierString(wordState)); // get best word match (intelligent instantiation) int bestWordIndex = getBestWordMatch(wordState, wordStateMap, @@ -252,25 +332,73 @@ private boolean getFinalState(Map wordStateMap) String word = wordProvider.getWord(wordState.getWordLength(), bestWordIndex); CrosswordState crosswordState = getCurrentState(wordStateMap, - wordState, word); + wordState, word, bestWordIndex); crosswordStates.addLast(crosswordState); - + changeCurrentState(wordStateMap, wordState, bestWordIndex, word); wordState = null; } else { + backtracksPerWordstate + .put(wordState, new LinkedList()); CrosswordState crosswordState; + List backtracksTries; List backtrackedStates = new LinkedList(); do { System.out.println("Backtracking"); crosswordState = crosswordStates.removeLast(); backtrackedStates.add(crosswordState); revertState(wordStateMap, crosswordState); + backtracksTries = backtracksPerWordstate + .put(crosswordState.wordState, + new LinkedList()); } while (getCrossedIndex(wordState, crosswordState.wordState) == -1); - preconditions = getPreconditions(backtrackedStates, wordState); + + if (backtracksTries == null) { + backtracksTries = new LinkedList(); + } + + boolean calculatePreconditions = true; + while (backtracksTries.size() == 5) { + calculatePreconditions = false; + backtracksPerWordstate.put(crosswordState.wordState, + new LinkedList()); + crosswordState = crosswordStates.removeLast(); + revertState(wordStateMap, crosswordState); + backtracksTries = backtracksPerWordstate + .get(crosswordState.wordState); + if (backtracksTries == null) { + backtracksTries = new LinkedList(); + } + System.out + .println("Backtrack count exceeded. Backtracking to " + + getIdentifierString(crosswordState.wordState) + + " - number of backtracks: " + + backtracksTries); + } + backtracksTries.add(crosswordState.wordIndex); + backtracksPerWordstate.put(crosswordState.wordState, + backtracksTries); + // backtracksPerWordstate.put(crosswordState.wordState, + // backtracksTries + 1); + if (calculatePreconditions) { + preconditions = getPreconditions(backtrackedStates, + wordState); + } else { + preconditions = new boolean[crosswordState.wordState + .getAvailableWords().length]; + for (int i = 0; i < backtracksTries.size(); i++) { + preconditions[backtracksTries.get(i)] = true; + } + } wordState = crosswordState.wordState; } - } while (!crosswordStates.isEmpty()); - return false; + } while (true); + // return false; + } + + private String getIdentifierString(WordState wordState) { + return wordState.getId().getWordNumber() + ", " + + (wordState.getId().isAcross() ? "across" : "down"); } private boolean[] getPreconditions(List backtrackedStates, @@ -343,7 +471,8 @@ private void revertState(Map wordStateMap, private void changeCurrentState( Map wordStateMap, WordState wordState, - int bestWordIndex, String word) throws UnknownHostException, MongoException { + int bestWordIndex, String word) throws UnknownHostException, + MongoException { System.out.println("Chose : " + word); @@ -355,16 +484,20 @@ private void changeCurrentState( } wordState.setAvailableWords(availableWords); wordState.setWord(word); - + // change crossed states for (int i = 0; i < wordState.getCrossedWords().size(); i++) { Entry crossedWordEntry = wordState .getCrossedWords().get(i); if (crossedWordEntry == null) { continue; - }// TODO if is processed dont do that + } WordState crossedWordState = wordStateMap.get(crossedWordEntry .getKey()); +// TODO if is processed dont do that +// if(crossedWordState.isProcessed()) { +// continue; +// } boolean[] crossedState = crossedWordState.getAvailableWords(); wordProvider.intersect(crossedState, crossedWordState.getWordLength(), @@ -380,7 +513,8 @@ private void changeCurrentState( } private CrosswordState getCurrentState( - Map wordStateMap, WordState wordState, String word) { + Map wordStateMap, WordState wordState, + String word, int wordIndex) { boolean[] oldAvailableWords = wordState.getAvailableWords().clone(); boolean[][] oldCrossedWordStates = new boolean[wordState .getWordLength()][wordState.getAvailableWords().length]; @@ -401,6 +535,7 @@ private CrosswordState getCurrentState( crosswordState.oldCrossedWordStates = oldCrossedWordStates; crosswordState.wordState = wordState; crosswordState.word = word; + crosswordState.wordIndex = wordIndex; return crosswordState; } @@ -413,45 +548,47 @@ private Map generateStartingState(int cols, printCrossword(cols, rows, crosswordField); - // Fills array with word indexes. - // Calculates on which index there is a word's start. - int wordIndex = 1; - // gridnum, row, col List acrossWords = new ArrayList(); List downWords = new ArrayList(); - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - if (crosswordField[i][j] != -1) { - boolean isWordStart = false; - if ((i == 0) || (crosswordField[i - 1][j] == -1)) { - int k = 0; - while ((k + i) < rows && crosswordField[i + k][j] != -1) { - k++; - } - if (k > 2) { - downWords.add(new int[] { wordIndex, i, j }); - isWordStart = true; - } - } - - if ((j == 0) || (crosswordField[i][j - 1] == -1)) { - int k = 0; - while ((j + k) < cols && crosswordField[i][j + k] != -1) { - k++; - } - if (k > 2) { - acrossWords.add(new int[] { wordIndex, i, j }); - isWordStart = true; - } - } - - if (isWordStart) { - crosswordField[i][j] = wordIndex; - wordIndex++; - } - } - } - } + createStartingGrid(crosswordField, acrossWords, downWords); + +// // Fills array with word indexes. +// // Calculates on which index there is a word's start. +// int wordIndex = 1; +// // gridnum, row, col +// for (int i = 0; i < rows; i++) { +// for (int j = 0; j < cols; j++) { +// if (crosswordField[i][j] != -1) { +// boolean isWordStart = false; +// if ((i == 0) || (crosswordField[i - 1][j] == -1)) { +// int k = 0; +// while ((k + i) < rows && crosswordField[i + k][j] != -1) { +// k++; +// } +// if (k > 2) { +// downWords.add(new int[] { wordIndex, i, j }); +// isWordStart = true; +// } +// } +// +// if ((j == 0) || (crosswordField[i][j - 1] == -1)) { +// int k = 0; +// while ((j + k) < cols && crosswordField[i][j + k] != -1) { +// k++; +// } +// if (k > 2) { +// acrossWords.add(new int[] { wordIndex, i, j }); +// isWordStart = true; +// } +// } +// +// if (isWordStart) { +// crosswordField[i][j] = wordIndex; +// wordIndex++; +// } +// } +// } +// } printCrossword(cols, rows, crosswordField); @@ -536,10 +673,15 @@ private Map generateStartingState(int cols, private void printCrossword(int cols, int rows, int[][] crosswordField) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { - System.out.print(crosswordField[i][j] + " "); + // logger.info(crosswordField[i][j] + " "); + System.out.print((crosswordField[i][j] >= 0 ? " " : "") + + (crosswordField[i][j] < 10 ? " " : "") + + crosswordField[i][j] + " "); } + // logger.info("\n"); System.out.println(); } + // logger.info("\n"); System.out.println(); } diff --git a/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordConverter.java b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordConverter.java new file mode 100644 index 0000000..b2757eb --- /dev/null +++ b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordConverter.java @@ -0,0 +1,119 @@ +package team_rocket.cross_world.crossword_generator; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.MongoException; +import com.mongodb.Mongo; + +import static team_rocket.cross_world.commons.constants.Constants.Mongo.*; +import team_rocket.cross_world.commons.data.Crossword; + +public class CrosswordConverter { + + private static DBCollection mWordsCollection; + private static Random random = new Random(); + + public static Crossword convertCrossword(WordProvider wordProvider, int[] blanks, int rows, + int cols, Map crossword) + throws UnknownHostException, MongoException { + int[][] crosswordField = new int[rows][cols]; + for (int blank : blanks) { + crosswordField[blank / cols][blank % cols] = -1; + } + + List acrossWords = new ArrayList(); + List downWords = new ArrayList(); + + CrossWorldCrosswordGenerator.createStartingGrid(crosswordField, acrossWords, downWords); + + Map acrossWordsPositions = + createWordsIndexGridPositionsMapping(acrossWords); + Map downWordsPositions = + createWordsIndexGridPositionsMapping(downWords); + + int[] gridNums = createGridNumbers(rows, cols, crosswordField); + + initializaDB(); + + char[] grid = new char[rows * cols]; + List cluesAcross = new ArrayList(acrossWordsPositions.size()); + List cluesDown = new ArrayList(downWordsPositions.size()); + for (Map.Entry entry : crossword.entrySet()) { + WordState state = entry.getValue(); + WordIdentifier identifier = entry.getKey(); + + int wordIndex = 0; + while (!state.getAvailableWords()[wordIndex]) { + wordIndex++; + } + String word = wordProvider.getWord(state.getWordLength(), wordIndex); + + int indexInCrossword = identifier.getWordNumber(); + int[] wordPositionInCrossword = (identifier.isAcross() ? acrossWordsPositions : + downWordsPositions).get(indexInCrossword); + int wordPositionInGrid = wordPositionInCrossword[0] * cols + wordPositionInCrossword[1]; + for (int i = 0; i < word.length(); i++) { + grid[wordPositionInGrid + (identifier.isAcross() ? i : i * cols)] = word.charAt(i); + } + + String[] clues = (String[])mWordsCollection.findOne(new BasicDBObject(FIELD_WORDS_WORD, + word)).get(FIELD_WORDS_CLUES); + String clue = indexInCrossword + ". " + clues[random.nextInt(clues.length)]; + (identifier.isAcross() ? cluesAcross : cluesDown).add(clue); + } + + Crossword resultingCrossword = new Crossword(); + resultingCrossword.setRows(rows); + resultingCrossword.setCols(cols); + resultingCrossword.setGridNums(gridNums); + resultingCrossword.setGrid(grid); + + String[] resultingCluesAcross = new String[cluesAcross.size()]; + cluesAcross.toArray(resultingCluesAcross); + resultingCrossword.setCluesAcross(resultingCluesAcross); + + String[] resultingCluesDown = new String[cluesAcross.size()]; + cluesDown.toArray(resultingCluesDown); + resultingCrossword.setCluesDown(resultingCluesDown); + + return resultingCrossword; + } + + private static int[] createGridNumbers(int rows, int cols, + int[][] crosswordField) { + int[] gridNums = new int[rows * cols]; + for (int i = 0; i < crosswordField.length; i++) { + for (int j = 0; j < crosswordField[i].length; j++) { + gridNums[i + j] = crosswordField[i][j] <= 0 ? 0 : crosswordField[i][j]; + } + } + return gridNums; + } + + private static Map createWordsIndexGridPositionsMapping( + List acrossWords) { + Map acrossWordsPositions = new HashMap(); + for (Iterator iterator = acrossWords.iterator(); iterator.hasNext();) { + int[] is = (int[]) iterator.next(); + acrossWordsPositions.put(is[0], new int[] {is[1], is[2]}); + } + return acrossWordsPositions; + } + + private static void initializaDB() throws UnknownHostException, MongoException { + if (mWordsCollection != null) { + Mongo mongo = new Mongo(); + DB crossWorld = mongo.getDB(DB_NAME); + mWordsCollection = crossWorld.getCollection(DB_COLLECTION_WORDS); + } + } +} diff --git a/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordState.java b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordState.java index ea6cc1c..c9f6773 100644 --- a/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordState.java +++ b/cross-world.crossword-generator/src/main/java/team_rocket/cross_world/crossword_generator/CrosswordState.java @@ -4,5 +4,6 @@ public class CrosswordState { public boolean[] oldAvailableWords; public boolean[][] oldCrossedWordStates; public String word; + public int wordIndex; public WordState wordState; } diff --git a/cross-world.crossword-generator/src/main/resources/log4j.properties b/cross-world.crossword-generator/src/main/resources/log4j.properties new file mode 100644 index 0000000..e4fcb01 --- /dev/null +++ b/cross-world.crossword-generator/src/main/resources/log4j.properties @@ -0,0 +1,9 @@ +# Set root logger level to DEBUG and its only appender to A1. +log4j.rootLogger=DEBUG, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n \ No newline at end of file