package kakuro.server.tablefactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import kakuro.server.ISkeletonTable;
import kakuro.table.ITable;
import kakuro.table.Position;
/** Skeleton generátor osztály. Alapvetően véletlenszerűen szórja ki a fekete mezőket,
* kicsit nagyobb valószínűséggel a tábla szélére, és feketéket egymás mellé. Ügyel arra, hogy
* 9-nél hosszabb szavak ne maradjanak. */
class SkeletonGenerator {
/** Random generátor, az aktális idő a seed. */
private Random _random = new Random(System.currentTimeMillis());
SkeletonGenerator() {
}
/** Az adott nehézségi szinthez generál egy üres táblavázat. */
public ISkeletonTable generateSkeleton(int skillLevel) {
int x;
int y;
switch (skillLevel) {
case ITable.DIFFICULTY_EASY:
x = y = 5;
break;
case ITable.DIFFICULTY_MEDIUM:
x = 8;
y = 10;
break;
case 3:
x = 42;
y = 33;
skillLevel = ITable.DIFFICULTY_HARD;
break;
default:
case ITable.DIFFICULTY_HARD:
x = 14;
y = 16;
break;
}
List<Position> result = makeSkeleton(x-1, y-1);
translate(result);
for (int i=0; i<x; i++) result.add(new Position(i, 0));
for (int i=0; i<y; i++) result.add(new Position(0, i));
return new MySkeletonImpl(x, y, result, skillLevel);
}
/** Eggyel eltolja a kapott pozíciókat jobbra és lefele. Azért van szükség erre, mert így egyszerűbben
* biztosítható az, hogy a bal és a felső sor mindig fekete maradjon. */
protected void translate(List<Position> list) {
for (int i=0; i<list.size(); i++) {
Position p = list.get(i);
list.set(i, new Position(p.x+1, p.y+1));
}
}
/** Skeleton implementáció. A fekete mezőket tárolja egy map-ben. */
public class MySkeletonImpl implements ISkeletonTable {
/** Nehézségi szint */
private int _diff;
/** tábla mérete */
private Position _bounds;
/** fekete cellák helye */
private HashMap<Position, Boolean> _blacks = new HashMap<Position, Boolean>();
/** Letárolja a kapott paramétereket, és feltölti a fekete-mapet. */
public MySkeletonImpl(int x, int y, List<Position> list, int diff) {
_diff = diff;
_bounds = new Position(x, y);
for (int i=0; i<list.size(); i++)
_blacks.put(list.get(i), true);
}
/** Oszlopok száma */
public int getColumnCount() {
return _bounds.x;
}
/** Nehézségi szint */
public int getDifficulty() {
return _diff;
}
/** azonosító */
public int getId() {
return -1;
}
/** Sorok száma */
public int getRowCount() {
return _bounds.y;
}
/** Cella típusa, fekete vagy fehér lehet. */
public int getType(Position position) {
return (_blacks.containsKey(position)?ISkeletonTable.CELL_BLACK:ISkeletonTable.CELL_WHITE);
}
}
/** Statikus teszteléshez segítő függvény, a kapott kétdimenziós int tömböt kiírja a képernyőre. */
private static void print(String title, int[][] mx) {
System.out.println("Matrix "+title+":");
for (int j=0; j<mx[0].length; j++) {
for (int i=0; i<mx.length; i++) {
System.out.print("\t"+mx[i][j]);
}
System.out.println();
}
}
/** Skeleton gyártó főfüggvény, megkapja a tábla kiterjedését, és visszaadja a feketék helyét listában. */
protected List<Position> makeSkeleton(int x, int y) {
LinkedHashSet<Position> blacks = new LinkedHashSet<Position>();
int[][] weights = fillWeights(blacks, x, y);
int counter = 0;
Position longPos = null;
int rollbackCounter = 0;
int[] rollbackStat = new int[3];
while ((counter<((x*y)/10)) || (longPos = checkLongs(weights))!=null) {
if (rollbackCounter>11) {
//System.out.println("Rollback3333");
rollbackCounter = 0;
rollbackStat[0]++;
Iterator<Position> it = blacks.iterator();
Position p1 = null;
Position p2 = null;
while (it.hasNext()) {
p1 = p2;
p2 = it.next();
}
blacks.remove(p1);
blacks.remove(p2);
weights = fillWeights(blacks, x, y);
continue;
}
if (counter>((x*y)/10)) {
blacks.add(longPos);
weights = fillWeights(blacks, x, y);
if (!checkTree(weights, blacks)) {
//System.out.println("rollback1!");
rollbackCounter++;
rollbackStat[1]++;
blacks.remove(longPos);
weights = fillWeights(blacks, x, y);
}
continue;
}
int rnd = _random.nextInt(getSum(weights));
Position nextBlack = getPos(weights, rnd);
blacks.add(nextBlack);
weights = fillWeights(blacks, x, y);
if (!checkTree(weights, blacks)) {
//System.out.println("rollback2!");
rollbackCounter++;
rollbackStat[2]++;
blacks.remove(nextBlack);
weights = fillWeights(blacks, x, y);
continue;
}
counter++;
}
int blackCount = blacks.size();
fillOneways(blacks, x, y);
System.out.println("Rollbacks: "+rollbackStat[0]+" for timeout, "+(rollbackStat[1]+rollbackStat[2])+" for connectness. "+(blacks.size()-blackCount)+" blacks added for oneway fields.");
ArrayList<Position> result = new ArrayList<Position>();
result.addAll(blacks);
return result;
}
/** Addig megy a kapott kétdimenziós tömbben, és adja össze a cellákban található számokat, amíg el
* nem ér a kért összegig. Ekkor visszaadja az elért pozíciót.
*
* @param mx kétdimenziós tömb súlyokkal
* @param sum elérni kívánt összeg
* @return pozíció, ahol elértük az összeget
* */
protected Position getPos(int[][] mx, int sum) {
for (int j=0; j<mx[0].length; j++) {
for (int i=0; i<mx.length; i++) {
if (mx[i][j]==0) continue;
sum -= mx[i][j];
if (sum<1) return new Position(i, j);
}
}
return null;
}
/** Összegzi a kapott kétdimenziós tömbben a súlyokat. */
protected int getSum(int[][] mx) {
int result = 0;
for (int j=0; j<mx[0].length; j++)
for (int i=0; i<mx.length; i++)
result += mx[i][j];
return result;
}
/** lekéri, hogy az adott pozíció körül hány darab fekete mező van. átlósan is számolja őket!
*
* @param blacks fekete cellák
* @param x nézett x pozíció
* @param y nézett y pozíció
* @param mx mátrix szélessége
* @param my mátrix magassága
* @return fekete szomszédok száma
* */
protected int getBlackNeighbourCount(HashSet<Position> blacks, int x, int y, int mx, int my) {
int result = 0;
for (int i=-1; i<2; i++) {
for (int j=-1; j<2; j++) {
if (x+i<0 || y+j<0 || x+i>=mx || y+j>=my) continue;
result += (blacks.contains(new Position(x+i, y+j))?1:0);
}
}
return result;
}
/** Visszaad egy súlyokkal feltöltött mátrixot. A feketék helyét és a tábla méretét kapja meg,
* fekete helye nulla lesz, a fehér mezőké pedig egy szám, hogy mekkora valószínűséggel essen oda a
* következő fekete mező.
* <p>
* Tábla széle nagyobb esélyt jelent, és a szomszédos feketék is megnöveli egy fehér mező esélyét.
*
* @param blacks fekete mezők
* @param x mátrix szélessége
* @param y mátrix magassága
* @return mátrix súlyokkal
* */
protected int[][] fillWeights(HashSet<Position> blacks, int x, int y) {
int[][] weights = new int[x][y];
for (int j=0; j<y; j++) {
for (int i=0; i<x; i++) {
if (i==0 || j==0 || i==x-1 || j==y-1) weights[i][j] += 100;
weights[i][j] += 25;
int n = getBlackNeighbourCount(blacks, i, j, x, y);
weights[i][j] += 10*n*n;
}
}
Iterator<Position> it = blacks.iterator();
it = blacks.iterator();
while (it.hasNext()) {
Position p = it.next();
weights[p.x][p.y] = 0;
}
return weights;
}
/** Túl hosszú szavak kiszűrése. Ha van túl hosszú szó, akkor visszaadja az első szó kilencedik pozícióját.
*
* @param mx kétdimenziós int tömb a súlyokkal
* @return első olyan cella helye, ami túl hosszú szóban található, vagy null, ha nincs ilyen
* */
protected Position checkLongs(int[][] mx) {
for (int j=0; j<mx[0].length; j++) {
for (int i=0, w=0; i<mx.length; i++) {
if (mx[i][j]!=0) w++;
else w = 0;
if (w>9) {
int[][] row = new int[9][1];
for (int l=0; l<9; l++) row[l][0] = mx[i-9+l][j];
int sum = getSum(row);
int rnd = _random.nextInt(sum);
Position p = getPos(row, rnd);
//System.out.println("RandomH: "+p);
return new Position(i-9+p.x, j);
}
}
}
for (int i=0; i<mx.length; i++) {
for (int j=0, w=0; j<mx[0].length; j++) {
if (mx[i][j]!=0) w++;
else w = 0;
if (w>9) {
//return new Position(i, j-_random.nextInt(9));
int[][] row = new int[9][1];
for (int l=0; l<9; l++) row[l][0] = mx[i][j-9+l];
int sum = getSum(row);
int rnd = _random.nextInt(sum);
Position p = getPos(row, rnd);
//System.out.println("RandomV: "+p);
return new Position(i, j-9+p.x);
}
}
}
return null;
}
/** Összefüggőségi vizsgálat.
*
* @param mx súlyok kétdimenziós mátrixa
* @param blacks fekete mezők
* @return true ha összefüggő a tábla, false egyébként
* */
protected boolean checkTree(int[][] mx, HashSet<Position> blacks) {
int total = mx[0].length*mx.length;
total -= blacks.size();
HashSet<Position> marked = new HashSet<Position>();
for (int j=0; j<mx[0].length; j++) {
for (int i=0; i<mx.length; i++) {
if (blacks.contains(new Position(i, j))) continue;
int visited = dfs(mx, marked, i, j);
if (visited!=total) return false;
return true;
}
}
return false;
}
/** Mélységi keresés a fehér mezőkre
*
* @param mx súlyok mátrixa
* @param marked már meglátogatott mezők
* @param x kiinduló x pozíció
* @param y kiinduló y pozíció
* @return meglátogatt mezők száma, beleértve saját magát is
* */
protected int dfs(int[][] mx, HashSet<Position> marked, int x, int y) {
Position p = new Position(x, y);
if (marked.contains(p)) return 0;
if (x<0 || y<0 || x>=mx.length || y>=mx[0].length) return 0;
if (mx[x][y]==0) return 0;
int result = 1;
marked.add(p);
result += dfs(mx, marked, x+1, y);
result += dfs(mx, marked, x-1, y);
result += dfs(mx, marked, x, y+1);
result += dfs(mx, marked, x, y-1);
return result;
}
/** Zsák fehérek kitöltése feketével. Zsák fehér: olyan fehér, akinek csak 1 darab fehér szomszédja van
* (átlós nem számít). Az összeset befeketíti!
*
* @param blacks fekete mezők
* @param x kiinduló x
* @param y kiinduló y koordináta
* */
protected void fillOneways(LinkedHashSet<Position> blacks, int x, int y) {
Position oneway = null;
while ((oneway = getFirstOneway(blacks, x, y))!=null) blacks.add(oneway);
}
/** Megkeresi az első zsákutca fehér mezőt, ha nincs ilyen, akkor null-t ad vissza.
*
* @param blacks feketék
* @param x kiinduló x
* @param y kiinduló y koordináta
* @return első zsákutca fehér mező helye, vagy null, ha nincs ilyen
* */
protected Position getFirstOneway(LinkedHashSet<Position> blacks, int x, int y) {
int c = 0;
for (int j=0; j<y; j++) {
for (int i=0; i<x; i++) {
if (blacks.contains(new Position(i, j))) continue;
c = 0;
if (blacks.contains(new Position(i, j+1))) c++;
if (blacks.contains(new Position(i, j-1))) c++;
if (blacks.contains(new Position(i+1, j))) c++;
if (blacks.contains(new Position(i-1, j))) c++;
if (c==3) return new Position(i, j);
if ((i==0 || j==0 || i==x-1 || j==y-1) && c==2) return new Position(i, j);
if (c==1 && (i+j==0 || i+j==x+y-2 || (i==0 && j==y-1) || (i==x-1 && j==0))) return new Position(i, j);
}
}
return null;
}
}