package com.iambookmaster.server.logic; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.logging.Logger; import com.iambookmaster.client.beans.ObjectBean; import com.iambookmaster.client.beans.Paragraph; import com.iambookmaster.client.beans.ParagraphConnection; import com.iambookmaster.client.exceptions.TimeoutException; import com.iambookmaster.client.model.Model; import com.iambookmaster.client.paragraph.BookCreator; import com.iambookmaster.client.paragraph.BookCreatorListener; public class ExtendedBookCreator extends BookCreator { private static final Logger log = Logger.getLogger(ExtendedBookCreator.class.getName()); public ExtendedBookCreator(Model model) { super(model); } private HashMap<Paragraph, HashSet<Paragraph>> closest; private HashMap<Paragraph, ArrayList<ParagraphConnection>> conditionalConnections; private ArrayList<ObjectBean> usageQanity; public void generateBook(BookCreatorListener listener) throws TimeoutException { attempt = 0; conditionalConnections = new HashMap<Paragraph, ArrayList<ParagraphConnection>>(); closest = new HashMap<Paragraph, HashSet<Paragraph>>(model.getParagraphs().size()); final LinkedHashMap<ObjectBean, int[]> qanity = new LinkedHashMap<ObjectBean, int[]>(); ArrayList<ParagraphConnection> connections = model.getParagraphConnections(); for (int i = 0; i < connections.size(); i++) { ParagraphConnection connection = connections.get(i); addClosest(closest,connection.getFrom(),connection.getTo()); addClosest(closest,connection.getTo(),connection.getFrom()); if (connection.getObject() != null) { //connection with conditions ArrayList<ParagraphConnection> list = conditionalConnections.get(connection.getFrom()); if (list==null) { list = new ArrayList<ParagraphConnection>(); conditionalConnections.put(connection.getFrom(), list); } if (qanity.containsKey(connection.getObject())) { qanity.get(connection.getObject())[0]++; } else { qanity.put(connection.getObject(), new int[]{1}); } list.add(connection); } } ArrayList<ObjectBean> usage = new ArrayList<ObjectBean>(); usage.addAll(qanity.keySet()); Collections.sort(usage, new Comparator<ObjectBean>(){ public int compare(ObjectBean o1, ObjectBean o2) { int s1= qanity.get(o1)[0]; int s2= qanity.get(o2)[0]; if (s1>s2) { return 1; } else if (s1<s2) { return -1; } else { return 0; } } }); usageQanity = new ArrayList<ObjectBean>(); for (ObjectBean objectBean : usage) { if (qanity.get(objectBean)[0]>1) { usageQanity.add(objectBean); } } dispersion(listener); } private int attempt; private StringBuffer randomHistory; /** * find place for each paragraph * @param listener * @return */ private void dispersion(BookCreatorListener listener) throws TimeoutException{ //create list of paragraphs, which cannot be together //start dispersion ArrayList<Paragraph> all = model.getParagraphs(); int size = all.size(); Paragraph[] book = new Paragraph[size]; outer: // for (;attempt<model.getSettings().getMaxAttemptCount();attempt++) { for (;attempt<10;attempt++) { if (attempt>0) { //clear log.info(model.getGameId()+", randomHistory="+randomHistory.toString()); System.out.println("randomHistory="+randomHistory.toString()); for (int i = 0; i < book.length; i++) { book[i]=null; } } log.info(model.getGameId()+", new attempt to create book"); System.out.println("new attempt to create book"); randomHistory = new StringBuffer(); HashSet<Paragraph> located = new HashSet<Paragraph>(size); if (listener.checkTimiout()) { throw new TimeoutException(); } HashMap<ObjectBean,Integer> keys = null; if (model.getSettings().isHiddenUsingObjects()) { //generate secret keys ArrayList<Paragraph> conditinals = new ArrayList<Paragraph>(); for (Paragraph paragraph:all) { if (conditionalConnections.containsKey(paragraph)==false) { //simple paragraph - find place later continue; } conditinals.add(paragraph); } if (conditinals.size()>0) { //we have paragraphs with conditional objects Collections.sort(conditinals, new Comparator<Paragraph>(){ public int compare(Paragraph o1, Paragraph o2) { int s1= conditionalConnections.get(o1).size(); int s2= conditionalConnections.get(o2).size(); if (s1>s2) { return 1; } else if (s1<s2) { return -1; } else { return 0; } } }); keys = new HashMap<ObjectBean,Integer>(model.getObjects().size()); //set secret keys for multiply-used objects int keyValue=model.getSettings().getMinimalSeparation(); boolean negative=false; for (ObjectBean object : usageQanity) { if (negative) { //just assign negative value keys.put(object,0-keyValue); negative = false; } else { //find next value while (true) { keyValue++; switch (model.getSettings().getFineSecretKeys()) { case 5: if (keyValue % 5 >0) { continue; } break; case 10: if (keyValue % 10 >0) { continue; } break; } if (keyValue>book.length) { //too many secret objects listener.tooManyObjects(); listener.allIterationsFailed(); return; } //find next key keys.put(object,keyValue); negative = true; break; } } } PlaceValidator validator = new ConditionalPlaceValidator(keys,located); //generate keys for each object for (Paragraph paragraph:conditinals) { if (located.contains(paragraph)) { //we cannot be here due to chain is not allowed, but reserve for future continue; } located.add(paragraph); int pos = placeFound(book, paragraph, validator); if (pos<0) { //cannot be located listener.iterationFailed(located.size(),size); continue outer; } if (book[pos] == null) { book[pos] = paragraph; log.info(model.getGameId()+", 1.book["+pos+"]="+paragraph.getName()); System.out.println("1.book["+pos+"]="+paragraph.getName()); } else if (book[pos] != paragraph) { listener.algorithmError(6); continue outer; } } } } for (int pi = 0; pi < size; pi++) { Paragraph paragraph = all.get(pi); if (located.contains(paragraph)) { //already placed continue; } int candidate = placeFound(book,paragraph,null); if (candidate<0) { //we cannot find place for this paragraph, try again for the beginning listener.iterationFailed(pi,size); continue outer; } if (book[candidate]==null) { book[candidate] = paragraph; log.info(model.getGameId()+", 2.book["+candidate+"]="+paragraph.getName()); System.out.println("2.book["+candidate+"]="+paragraph.getName()); } else if (book[candidate] != paragraph){ listener.algorithmError(5); continue outer; } } log.info(model.getGameId()+", randomHistory="+randomHistory.toString()); System.out.println("randomHistory="+randomHistory.toString()); for (int i = 0; i < book.length; i++) { if (book[i]==null) { //ops!!!, error listener.algorithmError(4); continue outer; } else { book[i].setNumber(i+1); } } //YES!!!! if (keys != null) { //remember secret keys for (ObjectBean object : keys.keySet()) { object.setKey(keys.get(object)); } } return; } //we cannot create book listener.allIterationsFailed(); } interface PlaceValidator { boolean accept(Paragraph[] book, Paragraph paragraph,int candidatePosition); } public class ConditionalPlaceValidator implements PlaceValidator { public class PostConditionalPlaceValidator implements PlaceValidator { public boolean accept(Paragraph[] book, Paragraph paragraph, int candidatePosition) { int keyVal = candidatePosition-currentParagraph; switch (model.getSettings().getFineSecretKeys()) { case 5: if (keyVal % 5 != 0) { return false; } break; case 10: if (keyVal % 10 != 0) { return false; } break; } //look for the same key in global for (Integer key:keys.values()) { if (key.intValue()==keyVal) { return false; } } //look for the same key in local if (localKeys.size()>0) { for (Integer key:localKeys.values()) { if (key.intValue()==keyVal) { return false; } } } //can be used return true; // return canBePlaced(book,paragraph,candidatePosition); } } private PostConditionalPlaceValidator postValiadtor; private HashMap<ObjectBean,Integer> keys; private HashMap<ObjectBean,Integer> localKeys; private HashSet<Paragraph> located; public ConditionalPlaceValidator(HashMap<ObjectBean, Integer> keys, HashSet<Paragraph> located) { this.keys = keys; this.located = located; postValiadtor = new PostConditionalPlaceValidator(); localKeys = new HashMap<ObjectBean, Integer>(model.getObjects().size()); } private int[] mess; private int messCounter; private int currentParagraph; /** * Check that this Paragraph can be places in candidatePosition of the book * @param book * @param paragraph * @param candidatePosition * @return */ public boolean accept(Paragraph[] book, Paragraph paragraph,int candidatePosition) { //take it place book[candidatePosition] = paragraph; log.info(model.getGameId()+", 3.book["+candidatePosition+"]="+paragraph.getName()); System.out.println("3.book["+candidatePosition+"]="+paragraph.getName()); ArrayList<ParagraphConnection> connections = conditionalConnections.get(paragraph); if (mess==null || mess.length<connections.size()) { //initialize or extend mess = new int[connections.size()]; } for (int i = 0; i < mess.length; i++) { mess[i]=-1; } messCounter = 0; if (_accept(book,paragraph,candidatePosition,connections)) { if (localKeys.size()>0) { //add new keys to global keys.putAll(localKeys); } for (int i = 0; i < messCounter; i++) { //mark all used paragraphs as placed located.add(book[mess[i]]); } return true; } //remove our mess book[candidatePosition] = null; log.info(model.getGameId()+", 4.book["+candidatePosition+"]=null"); System.out.println("4.book["+candidatePosition+"]=null"); //clean our mess for (int i = 0; i < messCounter; i++) { book[mess[i]] = null; log.info(model.getGameId()+", 5.book["+mess[i]+"]=null"); System.out.println("5.book["+mess[i]+"]=null"); } return false; } public boolean _accept(Paragraph[] book, Paragraph paragraph,int candidatePosition, ArrayList<ParagraphConnection> connections) { if (localKeys.size()>0) { localKeys.clear(); } for (ParagraphConnection connection : connections) { Integer value = keys.get(connection.getObject()); if (value==null) { value = localKeys.get(connection.getObject()); } int childPos; if (value==null) { //no value currentParagraph = candidatePosition; childPos = placeFound(book,connection.getTo(),postValiadtor); if (childPos<0) { //not acceptable, clean our mess and leave return false; } else { //remember new value in local map localKeys.put(connection.getObject(), childPos-candidatePosition); } } else { //value already assigned childPos = candidatePosition+value.intValue(); if (childPos<0 || childPos>=book.length) { //not acceptable return false; } if (canBePlaced(book,connection.getTo(),childPos)==false) { //not acceptable, clean our mess and leave return false; } //ok, go next } //remember to clean addMess(childPos); book[childPos] = connection.getTo(); log.info(model.getGameId()+", 6.book["+childPos+"]="+connection.getTo().getName()); System.out.println("6.book["+childPos+"]="+connection.getTo().getName()); } return true; } private void addMess(int childPos) { mess[messCounter++]=childPos; } private boolean canBePlaced(Paragraph[] book, Paragraph paragraph, int candidatePosition) { //check that current paragraph can be placed here if (book[candidatePosition] != null) { //already taken return false; } for (int i = candidatePosition-model.getSettings().getMinimalSeparation(),l=candidatePosition+model.getSettings().getMinimalSeparation(); i <= l; i++) { if (i<0) { //before the first paragraph continue; } if (i>=book.length) { //out of range - it is OK return true; } if (book[i]==null) { //empty continue; } if (closest.get(book[i]).contains(paragraph)) { //ups...it cannot be here return false; } } return true; } } private int placeFound(Paragraph[] book, Paragraph paragraph, PlaceValidator validator) { int startPos = getStartPos(book); HashSet<Paragraph> close = closest.get(paragraph); int from = startPos+model.getSettings().getMinimalSeparation()+1; int to = from+model.getSettings().getMinimalSeparation()+1; int candidate = -1; for (int i = startPos; i < book.length; i++) { if (book[i]==null) { //unused paragraph if (i>=from && candidate<0) { //can be used for us candidate = i; to = i + model.getSettings().getMinimalSeparation(); } } else if (close.contains(book[i])) { //prohibited candidate = -1; from = i+model.getSettings().getMinimalSeparation()+1; continue; } if (i>to && candidate>=0) { //found !!! if (book[candidate] == null) { if (validator==null) { //no conditions return candidate; } else if (validator.accept(book,paragraph,candidate)){ //meets conditions return candidate; } } candidate++; to=candidate+model.getSettings().getMinimalSeparation(); } } //check for the end while (candidate>=0 && candidate<book.length) { //end of the book, can be used if (book[candidate] == null) { if (validator==null) { //no conditions return candidate; } else if (validator.accept(book,paragraph,candidate)){ //meets conditions return candidate; } } candidate++; } //scan from start candidate=-1; from = 0; to = model.getSettings().getMinimalSeparation(); for (int i = 0; i < startPos; i++) { if (book[i]==null) { if (i>=from && candidate<0) { candidate = i; to = i + model.getSettings().getMinimalSeparation(); } } else if (close.contains(book[i])) { //prohibited candidate = -1; from = i+model.getSettings().getMinimalSeparation()+1; to = from+model.getSettings().getMinimalSeparation(); continue; } if (i>to && candidate>=0) { if (book[candidate] == null) { //found !!! if (validator==null) { //no conditions return candidate; } else if (validator.accept(book,paragraph,candidate)){ //meets conditions return candidate; } } candidate++; to = candidate+model.getSettings().getMinimalSeparation(); } } //cannot be found return -1; } private String[] randomSource; // private String[] randomSource = "178,25,210".split(","); private int randomSourceConter; private int getStartPos(Paragraph[] book) { int res=-1; if (randomSource != null && randomSourceConter<randomSource.length) { try { res = Integer.parseInt(randomSource[randomSourceConter++]); } catch (NumberFormatException e) { } } if (res<0) { res = (int)Math.round(Math.random()*book.length); } if (randomHistory.length()>0) { randomHistory.append(','); } randomHistory.append(res); return res; } private void addClosest(HashMap<Paragraph, HashSet<Paragraph>> closest, Paragraph from, Paragraph to) { HashSet<Paragraph> list = closest.get(from); if (list==null) { list = new HashSet<Paragraph>(); closest.put(from, list); } list.add(to); } public void continueCreation(BookCreatorListener listener) throws TimeoutException{ dispersion(listener); } }