package gameTree;
import game.Move;
import components.Field;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import rating.PrimitivKI;
import rating.PrimitivRating;
import useful.*;
import util.ChessfigureConstants;
import alphaBeta.AlphaBetaSearch;
/**
* Klasse, die als Hauptklasse der AI dienen soll. Hier sind die Aufrufe für den
* Game-Coordinator zu finden! Diese Klasse muss instanziiert werden. Auf diese
* Weise können auch mehrere Klassen erstellt werden, um an schließend die KI
* trainiren zu können.
*
* @author tobi
*
*/
public class NextMove {
// #################################################################################
// Klassenvariable
// Instanz der MoveGenerator-Klasse
private MoveGenerator moveGen;
// Instanz einer LinkesList, in der jeweils die erste Kindgeneration
// gespeichert ist
private LinkedList<SituationWithRating> list;
// HashMaps zum Vergleich und zur Rückgabe
private HashMap<Integer, Byte> beforeField;
private HashMap<Integer, Byte> afterField;
private PrimitivKI ki;
private PrimitivRating prim;
private static int INFINITY = 2147483647;
// Anzahl parallel laufender Threads (2 ist zumindest auf meinem MAC optimal
private final int PARALLEL = 2;
// Suchtiefe, TODO: später automatisch an Situation anpassen lassen
private final int DEPTH = 3;
private final boolean TEACHINGMODE = true;
private final String PATH = "ki.ser";
// #################################################################################
// Konstruktor
/**
* Konstruktor der Klasse NextMove Hier werden die nötigen Instanzen
* initialisiert
*/
public NextMove() {
this.moveGen = new MoveGenerator();
}
private boolean isGameOver(HashMap<Integer, Byte> field, byte player)
{
LinkedList<HashMap<Integer, Byte>> childSit = moveGen.generateMoves(field, player);
return childSit.isEmpty();
}
/**
* Hauptmethode der Klasse, die vom Game-Coordinator aufgerufen werden
* sollte.
*
* @param field
* @param player
* @return
*/
public Move getNext(Field field, byte player)
{
File pfad = new File("");
System.out.println(pfad.getAbsolutePath() + "...");
// HashMap<Integer, Byte> zusammenbauen
beforeField = field.getCurrentFieldAsHashMapWithBytes();
HashMap<Integer,Byte> cloneMap = (HashMap<Integer,Byte>)beforeField.clone();
if(isGameOver(beforeField, ChessfigureConstants.WHITE) || isGameOver(beforeField, ChessfigureConstants.BLACK)) {
int position;
Iterator<Entry<Integer, Byte>> it = cloneMap.entrySet().iterator();
Entry<Integer,Byte> pair = it.next();
position = pair.getKey();
return new Move(ChessfigureConstants.WHITE, position, position, false, true, true);
}
ki = new PrimitivKI();
ki.deserialize(PATH);
prim = new PrimitivRating();
String fingerprintBeforeField = Fingerprint.getFingerprint(beforeField);
if (ki.getSituations(fingerprintBeforeField) != null && ki.getDepth(fingerprintBeforeField) >= DEPTH) {
list = ki.getSituations(fingerprintBeforeField);
findBestSituationWithPositionInListMax();
// afterField = list.get(rn.nextInt(list.size())).getMap();
} else if (TEACHINGMODE) {
ki.teachSituation(beforeField, DEPTH, player);
ki.serialize(PATH);
list = ki.getSituations(fingerprintBeforeField);
findBestSituationWithPositionInListMax();
// afterField = list.get(rn.nextInt(list.size())).getMap();
}
else {
doChildSituations(player);
// Spiel zu ende?
rateChildSituations(player == ChessfigureConstants.WHITE ? ChessfigureConstants.BLACK : ChessfigureConstants.WHITE);
findBestSituationInListMax();
prim.primPositionRating(list, player);
findBestSituationWithPositionInListMax();
// for (SituationWithRating sit : list) {
// System.out.println("fig " + sit.getFigureRating() + " pos " +
// sit.getPositionRating());
// }
}
return HashMapToMove(beforeField, afterField, player);
}
private void findBestSituationWithPositionInListMax()
{
// Stelle der am besten bewerteten Situation in der ArrayList
int figureHelp = -INFINITY;
Random rn = new Random();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getPositionRating() > figureHelp) {
figureHelp = list.get(i).getPositionRating();
}
}
for (int i = 0; i < list.size(); i += 0) {
if (list.get(i).getPositionRating() != figureHelp) {
list.remove(i);
i--;
}
i++;
}
afterField = list.get(rn.nextInt(list.size())).getMap();
}
/**
* Finde Situation im Feld, die am besten bewertet wurde
*/
private void findBestSituationInListMax()
{
// Stelle der am besten bewerteten Situation in der ArrayList
int figureHelp = -INFINITY;
Random rn = new Random();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getFigureRating() > figureHelp) {
figureHelp = list.get(i).getFigureRating();
}
}
for (int i = 0; i < list.size(); i += 0) {
if (list.get(i).getFigureRating() != figureHelp) {
list.remove(i);
i--;
}
i++;
}
// afterField = list.get(rn.nextInt(list.size())).getMap();
}
/**
* Bewerte nacheinander alle in der Liste befindlichen Situationen
*
* @param player
*/
private void rateChildSituations(byte player)
{
AlphaBetaSearch[] abThreads = new AlphaBetaSearch[list.size()];
for (int i = 0; i < list.size(); i++) {
// Thread erstellen mit SituationWithRating,depth,player
abThreads[i] = new AlphaBetaSearch(list.get(i), DEPTH, player);
abThreads[i].setName("" + i);
}
// Variablen zur Zeitmessung um das Optimum aus der nebenlaeufigkeit
// herauszuholen
long time = System.currentTimeMillis();
// startet eine bestimmte Anzahl an Threads
orderedThreadStart(abThreads, PARALLEL);
System.out.println("\n Zeit: " + (System.currentTimeMillis() - time) + " Anzahl paralleler Threads " + PARALLEL + "; Suchtiefe " + (DEPTH) + "\n");
LinkedList<SituationWithRating> helpList = new LinkedList<SituationWithRating>();
int i = 0;
for (AlphaBetaSearch ab : abThreads) {
helpList.add(ab.getSituationWithRating());
System.out.printf("%-3d %d ", i++, ab.getSituationWithRating().getFigureRating());
System.out.println("Zug: " + HashMapMoveToText(beforeField, ab.getSituationWithRating().getMap(), player) + " ");
}
this.list = helpList;
}
/*
* Es sollten nicht alle Suchen gleichzeitig als seperater Thread gestartet
* werden, da das Scheduling ansonsten die Sache extrem verlangsamt. Die
* Methode orderedThreadStart achtet darauf, dass immer nur eine zuvor
* bestimmte Anzahl an Threads gleichzeitig laufen. Wenn diese beendet
* werden, dann startet diese Methode automatisch die naechsten Threads aus
* dem Array AlphaBetaSearch.
*
* @param ab Array von Threads des Typ AlphaBetaSearch
*
* @param parallelValue Anzahl an gleichzeitig laufenden Threads
*/
public boolean orderedThreadStart(AlphaBetaSearch[] ab, int parallelValue)
{
System.out.println("Anzahl an Wurzeln " + ab.length);
/*
* wenn anzahl der möglichen Threads kleiner ist als maximale anzahl
* gleichzeitiger Threads dann gleich alle starten
*/
if (parallelValue >= ab.length) {
System.out.println("0");
for (int i = 0; i < ab.length; i++) {
ab[i].start();
}
} else {
int counter = ab.length - 1;
// solange Zuege da sind
while (counter >= 0) {
// duerfen noch Threads gestartet werden oder laufen genug
// parallel?
if (ab[0].getNumberOfThreads() < parallelValue) {
ab[counter].start();
counter--;
}
// ein wenig abwarten, damit schleife nicht komplett cpu
// auslastet
try {
Thread.sleep(1 * parallelValue);
} catch (Exception e) {
}
}
}
// letzten Threads die noch laufen beenden lassen
while (ab[0].getNumberOfThreads() != 0) {
try {
Thread.sleep(1 * parallelValue);
} catch (Exception e) {
}
}
return true;
}
/**
* Fülle die Liste mit allen möglichen Kindsituationen
*
* @param player
*/
private void doChildSituations(byte player)
{
LinkedList<HashMap<Integer, Byte>> childSit = moveGen.generateMoves(beforeField, player);
LinkedList<SituationWithRating> listConversion = new LinkedList<SituationWithRating>();
while (!childSit.isEmpty()) {
listConversion.add(new SituationWithRating(childSit.pollLast(), 0, 0));
}
list = listConversion;
}
/**
* Finde aus dem Unterschied zwischen "beforeMap" und afterMap" ein
* Move-Objekt
*
* @param map1
* @param map2
* @param color
* @return
*/
private Move HashMapToMove(HashMap<Integer, Byte> before, HashMap<Integer, Byte> after, byte player)
{
Iterator<Entry<Integer, Byte>> it1 = before.entrySet().iterator();
Iterator<Entry<Integer, Byte>> it2 = after.entrySet().iterator();
Integer[] posBefore = new Integer[before.size()];
Byte[] figBefore = new Byte[before.size()];
Integer[] posAfter = new Integer[after.size()];
Byte[] figAfter = new Byte[after.size()];
/*
* Variablen zur Erstellung des Move-Objektes
*/
int from = 0, to = 0;
byte colorOfPlayer = player;
boolean captured = false;
// TODO Rochade
int itCount = 0;
while (it1.hasNext()) {
Map.Entry<Integer, Byte> entry1 = (Map.Entry<Integer, Byte>) it1.next();
posBefore[itCount] = entry1.getKey();
figBefore[itCount] = entry1.getValue();
itCount++;
}// while
itCount = 0;
while (it2.hasNext()) {
Map.Entry<Integer, Byte> entry2 = (Map.Entry<Integer, Byte>) it2.next();
posAfter[itCount] = entry2.getKey();
figAfter[itCount] = entry2.getValue();
itCount++;
}// while
/*
* Auswertung der beiden Schachfelder, Unterscheidung nach
* "einfacher Zug" und "Schlag"
*/
if (posBefore.length == posAfter.length) {
/*
* Beide Felder gleiche Länge --> einfacher Zug, kein Schlag
*
* if figBefore in posBefore und nicht figAfter in posAfter folgt
* figBefore ist "from" if figAfter in posAfter und nicht figAfter
* in posBefore folgt figAfter ist "to"
*/
// TODO Bauernwumwandlung
captured = false;
boolean contains = false;
for (int i = 0; i < posBefore.length; i++) {
for (int j = 0; j < posAfter.length; j++) {
if (posBefore[i].equals(posAfter[j])) {
contains = true;
}
}
if (!contains) {
from = posBefore[i];
}
contains = false;
}
contains = false;
for (int j = 0; j < posAfter.length; j++) {
for (int i = 0; i < posBefore.length; i++) {
if (posBefore[i].equals(posAfter[j])) {
contains = true;
}
}
if (!contains) {
to = posAfter[j];
}
contains = false;
}
} else {
/*
* Beide Felder unterschiedliche Länge --> Zug und Schlag
*/
captured = true;
boolean contains = false;
for (int i = 0; i < posBefore.length; i++) {
for (int j = 0; j < posAfter.length; j++) {
if (posBefore[i].equals(posAfter[j])) {
contains = true;
}
}
if (!contains) {
from = posBefore[i];
}
contains = false;
}
for (int j = 0; j < posAfter.length; j++) {
for (int i = 0; i < posBefore.length; i++) {
if ((posBefore[i].equals(posAfter[j])) && (figBefore[i].equals(figAfter[j]) == false)) {
to = posBefore[i];
}
}
contains = false;
}
}
//Spielende (keine Unterscheidung Schachmatt/Patt)
if (isGameOver(afterField, ChessfigureConstants.WHITE) || isGameOver(afterField, ChessfigureConstants.BLACK)) {
return new Move(colorOfPlayer, from, to, captured, true, true);
}
else {
return new Move(colorOfPlayer, from, to, captured, false, false);
}
}
private String HashMapMoveToText(HashMap<Integer, Byte> map1, HashMap<Integer, Byte> map2, byte color)
{
Iterator<Entry<Integer, Byte>> it1 = map1.entrySet().iterator();
Iterator<Entry<Integer, Byte>> it2 = map2.entrySet().iterator();
Integer[] int1 = new Integer[map1.size()];
Byte[] byte1 = new Byte[map1.size()];
Integer[] int2 = new Integer[map2.size()];
Byte[] byte2 = new Byte[map2.size()];
/*
* Variablen zur Erstellung des Move-Objektes
*/
int from = 0, to = 0;
byte colorOfPlayer = color;
boolean captured = false;
// TODO Rochade
int itCount = 0;
while (it1.hasNext()) {
Map.Entry<Integer, Byte> entry1 = (Map.Entry<Integer, Byte>) it1.next();
int1[itCount] = entry1.getKey();
byte1[itCount] = entry1.getValue();
itCount++;
}// while
itCount = 0;
while (it2.hasNext()) {
Map.Entry<Integer, Byte> entry2 = (Map.Entry<Integer, Byte>) it2.next();
int2[itCount] = entry2.getKey();
byte2[itCount] = entry2.getValue();
itCount++;
}// while
/*
* Auswertung der beiden Schachfelder, Unterscheidung nach
* "einfacher Zug" und "Schlag"
*/
if (int1.length == int2.length) {
/*
* Beide Felder gleiche Länge --> einfacher Zug, kein Schlag
*
* iff key_1 in int1 und nicht key_1 in int2 folgt key_1 ist "from"
* iff key_2 in int2 und nicht key_2 in int1 folgt key_2 ist "to"
*/
// TODO Bauernwumwandlung
captured = false;
boolean contains = false;
for (int i = 0; i < int1.length; i++) {
for (int j = 0; j < int2.length; j++) {
if (int1[i].equals(int2[j])) {
contains = true;
}
}
if (!contains) {
from = int1[i];
}
contains = false;
}
contains = false;
for (int j = 0; j < int2.length; j++) {
for (int i = 0; i < int1.length; i++) {
if (int1[i].equals(int2[j])) {
contains = true;
}
}
if (!contains) {
to = int2[j];
}
contains = false;
}
} else {
/*
* Beide Felder unterschiedliche Länge --> Zug und Schlag
*/
captured = true;
boolean contains = false;
for (int i = 0; i < int1.length; i++) {
for (int j = 0; j < int2.length; j++) {
if (int1[i].equals(int2[j])) {
contains = true;
}
}
if (!contains) {
from = int1[i];
}
contains = false;
}
for (int j = 0; j < int2.length; j++) {
for (int i = 0; i < int1.length; i++) {
if ((int1[i].equals(int2[j])) && (byte1[i].equals(byte2[j]) == false)) {
to = int1[i];
}
}
contains = false;
}
}
String s = "";
// return new Move(colorOfPlayer, from, to, captured);
return s + from + " " + to;
}
// #################################################################################
// Getter/Setter
}