package kakuro.server.tablefactory;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import kakuro.server.ISkeletonTable;
import kakuro.server.ITableFactory;
import kakuro.table.ITable;
import kakuro.table.Position;
/** Tábla kitöltő osztály. A játéktáblát max. kilenc fokszámú páros gráffá alakítja, ahol a tábla egy szava
* felel meg egy csúcsnak a gráfban, és él fut két csúcs között, ha a két szó metszi egymást a táblában.
* A gráf páros, mert egy szó vagy vízszintes, vagy függőleges, és két vízszintes szó nem tudja metszeni egymást.
* A max. fokszám pedig a szavak max. hosszából következik.
* <p>
* Páros gráfok élszínezésére vonatkozik a Kőnig tétel, aminek a bizonyítása szerencsére konstruktív. Ezt a
* javító utas algoritmust használja a kód. Lassú implementáció, léteznek sokkal gyorsabb módszerek, de
* ekkor gráf méretekben gyakorlatilag mindegy a gyorsasága.
* */
public class BipartiteTableFactory implements ITableFactory {
/** Singleton példány */
private static final BipartiteTableFactory _instance = new BipartiteTableFactory();
/** Random generátor, az aktuális idő a seed */
private Random _random = new Random(System.currentTimeMillis());
/** Privát egyetlen konstruktor */
private BipartiteTableFactory() {
}
/** Singleton példány getter */
public static ITableFactory getInstance() {
return _instance;
}
/** Nem használt fv, tesztelésre csak! */
public ITable generateTable(int skillLevel) {
if (skillLevel == ITable.DIFFICULTY_EASY) {
return populateSkeleton(new SimpleSkeletonTable());
}
return populateSkeleton(new FixedSkeletonTable());
}
/** Adott nehézségi szinthez Skeletont generál, a {@link SkeletonGenerator} segítségével */
public ISkeletonTable generateSkeleton(int skillLevel) {
return new SkeletonGenerator().generateSkeleton(skillLevel);
}
/** Adott Skeletont kitölt, és visszaadja a kész táblát. */
public ITable populateSkeleton(ISkeletonTable skeleton) {
int sorokszama = skeleton.getRowCount();
int oszlopokszama = skeleton.getColumnCount();
System.out.println("sorok.sz: "+sorokszama+" # oszlopok.sz: "+oszlopokszama);
int[][] bwMatrix = new int[oszlopokszama][sorokszama];
int[][] horizontalWords = new int[oszlopokszama][sorokszama];
int[][] verticalWords = new int[oszlopokszama][sorokszama];
int[][] result = new int[oszlopokszama][sorokszama];
/* Eloszor osszegyujtom egy matrixba a fekete-feher ertekeket, 1, ha fekete,
* ebbol varazsolom ki a graf csomopontjait majd */
for (int y=0; y<sorokszama; y++) {
for (int x=0; x<oszlopokszama; x++) {
int celltipus = skeleton.getType(new Position(x, y));
bwMatrix[x][y] = (celltipus==ISkeletonTable.CELL_BLACK?1:0);
}
}
print(bwMatrix, "bwMatrix");
/* Eloallitom a vizszintes szavakat tartalmazo matrixot, a szavakat megszamozom 1-tol */
int hcounter = 1;
boolean inword = false;
HashMap<Integer, Integer> hCount = new HashMap<Integer, Integer>();
for (int y=0; y<sorokszama; y++) {
for (int x=0; x<oszlopokszama; x++) {
if (bwMatrix[x][y]==0) {
horizontalWords[x][y] = hcounter;
inword = true;
hCount.put(hcounter, hcounter);
} else {
if (inword) hcounter++;
inword = false;
}
}
}
print(horizontalWords, "horizontal words");
/* Ugyanez, csak fuggolegesre is. itt meg annyit csinalok, hogy lementem egy mapbe azt, hogy
* egy adott fuggoleges es vizszintes szo metszetehez, ami ugye valojaban egy cella az eredeti
* tablaban, pontosan milyen Position is tartozik. igy tehat a graf egy elehez majd siman meg tudom
* mondani, hogy az valojaban melyik cella volt. */
int vcounter = 1;
inword = false;
HashMap<Integer, Integer> vCount = new HashMap<Integer, Integer>();
HashMap<Position, Position> wordToCellMap = new HashMap<Position, Position>();
for (int x=0; x<oszlopokszama; x++) {
for (int y=0; y<sorokszama; y++) {
if (bwMatrix[x][y]==0) {
verticalWords[x][y] = vcounter;
inword = true;
vCount.put(vcounter, vcounter);
wordToCellMap.put(new Position(horizontalWords[x][y], vcounter), new Position(x, y));
} else {
if (inword) vcounter++;
inword = false;
}
}
}
print(verticalWords, "vertical words");
/* Fo ciklus. Az innerCycle fuggveny egy maximalis (maximum!) parositast ad vissza. Ezt elmentem, kiosztom az
* eleknek egy szamot, es a kovetkezo korben kiveszem a generalasbol ezeket az eleket - erre szolgal az exclusions
* map. */
ArrayList<Position> maximumMatching = null;
HashMap<Position, Boolean> exclusions = new HashMap<Position, Boolean>();
int run = 0;
int[] statistics = new int[10];
do {
maximumMatching = innerCycle(sorokszama, oszlopokszama, hCount.size(), vCount.size(), bwMatrix, horizontalWords, verticalWords, wordToCellMap, exclusions, run);
statistics[run] = maximumMatching.size();
run++;
for (int i=0; i<maximumMatching.size(); i++) {
exclusions.put(maximumMatching.get(i), true);
Position p = wordToCellMap.get(maximumMatching.get(i));
result[p.x][p.y] = run;
}
} while ((maximumMatching.size()>0) && (run<9));
HashMap<Position, Boolean> problematicEdgesMap = new HashMap<Position, Boolean>();
Iterator<Position> it = wordToCellMap.keySet().iterator();
while (it.hasNext()) {
Position e = it.next();
if (!exclusions.containsKey(e)) problematicEdgesMap.put(e, true);
}
/* problemas elek a 9-dik futas utan: ezek azok az elek a grafban, ahol nem tudott a moho algoritmus szint kiosztani
* ezekre javito ut kell */
Iterator<Position> problematicEdges = problematicEdgesMap.keySet().iterator();
while (problematicEdges.hasNext()) {
statistics[9]++;
Position edge = problematicEdges.next();
int[] freeColors = getFreeColors(edge, wordToCellMap, hCount.size(), vCount.size(), result);
System.out.println("Left out: "+edge+", free colors: "+freeColors[0]+" - "+freeColors[1]);
Position p = wordToCellMap.get(edge);
result[p.x][p.y] = freeColors[0];
Position next = edge;
HashMap<Position, Boolean> visited = new HashMap<Position, Boolean>();
boolean horizontalToVertical = false;
while ((next = getNextEnRoute(next, freeColors[0], freeColors[1], wordToCellMap, result, hCount.size(), vCount.size(), horizontalToVertical, visited))!=null) {
//invertColors(next, freeColors[0], freeColors[1], wordToCellMap, result);
horizontalToVertical ^= true;
}
}
try {
String s = "";
for (int i=0; i<statistics.length; i++) {
s += (i==0?"":"\t")+statistics[i];
}
FileWriter writer = new FileWriter("statistics.txt", true);
writer.append(s+"\r\n");
writer.close();
} catch (Throwable t) {
t.printStackTrace();
}
boolean[] digitsVisited = new boolean[9];
int[] digits = new int[10];
for (int i=1; i<10; i++) {
digits[i] = nextFree(digitsVisited)+1;
}
return new GeneralTable(skeleton, getPermuted(result, digits));
}
/** Megpermutálja a kapott kétdimenziós mátrix számait a segédtömb alapján
*
* @param mx permutálandó mátrix, itt cserélgeti meg a számjegyeket
* @param digits számjegyek, kevert sorrendben, 1-9 között
* @return új mátrix
* */
protected int[][] getPermuted(int[][] mx, int[] digits) {
int[][] result = new int[mx.length][mx[0].length];
for (int j=0; j<mx[0].length; j++) {
for (int i=0; i<mx.length; i++) {
result[i][j] = digits[mx[i][j]];
}
}
return result;
}
/** Adott élből elindul, és keresi a következő invertálandó élet. Javító-alternáló út.
*
* @param current kiinduló él
* @param color1 első szín
* @param color2 második szín
* @param wordToCellMap map, melyik vízszintes és függőleges szavak metszetéhez melyik pozíció tartozik
* az eredeti táblában
* @param colors aktális színek az összes táblabeli cellához
* @param hCount vízszintes szavak száma
* @param vCount függőleges szavak száma
* @param horizontalToVertical vízszintes vagy függőlegesből jövünk
* @param visited már meglátogatott cellák, hogy ne mehessünk visszafele
* @return következő invertálandó él
* */
private Position getNextEnRoute(Position current, int color1, int color2, HashMap<Position, Position> wordToCellMap, int[][] colors, int hCount, int vCount, boolean horizontalToVertical, HashMap<Position, Boolean> visited) {
System.out.println("keresem a kovetkezojet: "+current+" irany: "+horizontalToVertical+", szinek: "+color1+"-"+color2);
visited.put(current, true);
if (!horizontalToVertical) {
int vWord = current.y;
for (int i=1; i<=hCount; i++) {
if (visited.containsKey(new Position(i, vWord))) continue;
if (wordToCellMap.containsKey(new Position(i, vWord))) {
Position actual = wordToCellMap.get(new Position(i, vWord));
int color = colors[actual.x][actual.y];
System.out.println("\ttalaltam elet "+new Position(i, vWord)+" -> "+actual+", szine: "+color);
if (color==color1) {
colors[actual.x][actual.y] = color2;
return new Position(i, vWord);
}
}
}
} else {
int hWord = current.x;
for (int i=1; i<=vCount; i++) {
if (visited.containsKey(new Position(hWord, i))) continue;
if (wordToCellMap.containsKey(new Position(hWord, i))) {
Position actual = wordToCellMap.get(new Position(hWord, i));
int color = colors[actual.x][actual.y];
if (color==color2) {
colors[actual.x][actual.y] = color1;
return new Position(hWord, i);
}
}
}
}
return null;
}
/**
*
* @param e vízszintes és függőleges szaval sorszáma, ahol épp vagyunk (őket összekötő élen vagyunk most)
* @param wordToCellMap map, melyik vízszintes és függőleges szavak metszetéhez melyik pozíció tartozik
* az eredeti táblában
* @param hCount vízszintes szavak listája
* @param vCount függőleges szavak listája
* @param colors aktális színek az összes táblabeli cellához
* @return egy színpár, egy-egy szabad szín az él két végéről, ilyenek biztosan vannak, hiszen az él
* színezetlen
* */
private int[] getFreeColors(Position e, HashMap<Position, Position> wordToCellMap, int hCount, int vCount, int[][] colors) {
int hWord = e.x;
int vWord = e.y;
int[] result = new int[2];
result[0] = result[1] = -1;
boolean[] used = new boolean[10];
for (int i=1; i<=vCount; i++) {
if (wordToCellMap.containsKey(new Position(hWord, i))) {
Position actual = wordToCellMap.get(new Position(hWord, i));
if (colors[actual.x][actual.y]>0) used[colors[actual.x][actual.y]] = true;
}
}
result[0] = getFirst(used);
used = new boolean[10];
for (int i=1; i<=hCount; i++) {
if (wordToCellMap.containsKey(new Position(i, vWord))) {
Position actual = wordToCellMap.get(new Position(i, vWord));
if (colors[actual.x][actual.y]>0) used[colors[actual.x][actual.y]] = true;
}
}
result[1] = getFirst(used);
return result;
}
/** A kapott bool tömbből visszaadja az első false helyét, -1 különben */
private int getFirst(boolean[] used) {
for (int i=1; i<used.length; i++) { //szandekosan 1
if (!used[i]) {
return i;
}
}
return -1;
}
/** Belső ciklus, fedő élhalmazt ad vissza.
*
* @param sorokszama sorok száma
* @param oszlopokszama oszlopok száma
* @param hCount vízszintes szavak száma
* @param vCount függőleges szavak száma
* @param bwMatrix fekete-fehér eredeti tábla mátrixa
* @param horizontalWords eredeti tábla mátrixa, beszámozva a vízszintes szavak
* @param verticalWords eredeti tábla mátrixa, beszámozva a függőleges szavak
* @param wordToCellMap map, melyik vízszintes és függőleges szavak metszetéhez melyik pozíció tartozik
* az eredeti táblában
* @param exclusions az előző körök éleit már nem vizsgáljuk
* @param run hanyadik kör (max. 9)
* @return minimális fedő élek
* */
private ArrayList<Position> innerCycle(int sorokszama, int oszlopokszama, int hCount, int vCount, int[][] bwMatrix, int[][] horizontalWords, int[][] verticalWords, HashMap<Position, Position> wordToCellMap, HashMap<Position, Boolean> exclusions, int run) {
ArrayList<Position> result = new ArrayList<Position>();
Vector<Vector<Integer>> neighbours = new Vector<Vector<Integer>>(150);
/* Felepitem a szomszedossagi listakat, kiveve a mar feldolgozott eleket */
for (int i=0; i<=hCount; i++) neighbours.add(new Vector<Integer>());
for (int y=0; y<sorokszama; y++) {
for (int x=0; x<oszlopokszama; x++) {
if (bwMatrix[x][y]==1) continue;
int hWordId = horizontalWords[x][y];
int vWordId = verticalWords[x][y];
if (exclusions.containsKey(new Position(hWordId, vWordId))) {
continue;
}
neighbours.get(hWordId).add(vWordId);
}
}
for (int i=0; i<neighbours.size(); i++) Collections.shuffle(neighbours.get(i));
System.out.println("vCount: "+vCount+", hCount: "+hCount);
/* minden vizszintes szora lefuttatom a melysegi keresest */
int[] markedNodes = new int[Math.max(vCount, hCount)+1];
int[] visitedNodes = new int[Math.max(vCount, hCount)+1];
boolean[] visited = new boolean[hCount];
for (int i=1; i<=hCount; i++) {
int j = nextFree(visited)+1;
//System.out.println("Doing run j "+j);
depthFirstSearch(neighbours, markedNodes, visitedNodes, j, j);
}
/* es visszaadom az eredmenyt */
int all = 0;
for (int i=1; i<=vCount; i++) {
all += (markedNodes[i] != 0)?1:0;
if (markedNodes[i]!=0) {
System.out.println("Run "+run+", found edge "+markedNodes[i]+"--->"+i+" : "+wordToCellMap.get(new Position(markedNodes[i], i)));
result.add(new Position(markedNodes[i], i));
}
}
System.out.println("all: "+all);
return result;
}
/** A kapott visited tömbből véletlenszerűen választ egy szabadot. */
protected int nextFree(boolean[] visited) {
int rnd = _random.nextInt(visited.length);
for (int i=0; i<visited.length; i++) {
int j = (i+rnd)%visited.length;
if (!visited[j]) {
visited[j] = true;
return j;
}
}
return -1;
}
/** Trükkös depth first search, rekurzív. Inkább lassú, de a BFS implementálása sokkal genyóbb
*
* @param neighbours szomszédok
* @param markedNodes itt azt jelöljük, hogy eljutottunk-e már a jelölt node-okba az mostani körben
* @param visitedNodes ahol már jártunk
* @param currentNode aktuális node
* @param startingNode kiinduló node
* */
private boolean depthFirstSearch(Vector<Vector<Integer>> neighbours, int[] markedNodes, int[] visitedNodes, int currentNode, int startingNode) {
if (visitedNodes[currentNode]!=startingNode) {
visitedNodes[currentNode] = startingNode;
for (int j=0; j<neighbours.get(currentNode).size(); j++) {
if (markedNodes[neighbours.get(currentNode).get(j)]==0 || depthFirstSearch(neighbours, markedNodes, visitedNodes, markedNodes[neighbours.get(currentNode).get(j)], startingNode)) {
markedNodes[neighbours.get(currentNode).get(j)] = currentNode;
return true;
}
}
}
return false;
}
/** Statikus debug függvény, a kapott mátrixot iratja ki a képernyőre olvashatóan */
private static void print(int[][] m, String title) {
System.out.println("matrix "+title+":");
for (int j=0; j<m[0].length; j++) {
for (int i=0; i<m.length; i++) {
System.out.print("\t"+m[i][j]);
}
System.out.println("");
}
}
}