ShakkiAly.java
package datastructureproject.luokat;
import chess.bot.ChessBot;
import chess.engine.GameState;
import chess.model.Side;
import datastructureproject.luokat.apulaiset.Matikka;
import datastructureproject.luokat.apulaiset.ShakkiApu;
import datastructureproject.luokat.evaluointi.Evaluaattori;
import datastructureproject.luokat.evaluointi.OmaEvaluaattori;
import datastructureproject.luokat.tietorakenteet.*;
import datastructureproject.luokat.nappulat.Kuningas;
public class ShakkiAly implements ChessBot {
public static final String LUOVUTUS = null;
private Evaluaattori evaluaattori;
private int syvyysMax;
private boolean alphaBeta;
private Pelilauta pelilauta;
private long haarat = 0;
public ShakkiAly(boolean alphaBeta) {
alusta(alphaBeta, 4);
}
public ShakkiAly(boolean alphaBeta, int syvyysMax) {
alusta(alphaBeta, syvyysMax);
}
private void alusta(boolean alphaBeta, int syvyysMax) {
evaluaattori = new OmaEvaluaattori();
this.pelilauta = new Pelilauta();
this.alphaBeta = alphaBeta;
this.syvyysMax = syvyysMax;
}
/**
* Kertoo käyttääkö tämä instanssi AlphaBeta-karsintaa vai ei.
* @return onko käytössä AlphaBeta vai ei
*/
public boolean onAlphaBeta() {
return alphaBeta;
}
/**
* Kertoo MiniMaxille asetetun syvyyden
* @return MiniMaxille asetettu syvyys
*/
public int getSyvyysMax() {
return syvyysMax;
}
/**
* Viime siirron aikana tarkasteltujen haarojen(siirtojen) määrä.
* Hyödyllinen tehokkuustestauksessa
* @return tarkasteltujen siirtojen määrä
*/
public long getHaarat() {
return haarat;
}
/**
* Keksii aiemman pelitilanteen pohjalta uuden liikkeen.
*
* @return Nappulan siirtämisen tiedot UCI-formaatissa
*/
@Override
public String nextMove(GameState gamestate) {
//Luetaan koko pelitilanne laudalle, esim. keskeytyneen pelin vuoksi
this.pelilauta = new Pelilauta();
for (String siirto: gamestate.moves) {
pelilauta.siirto(new Siirto(siirto));
}
haarat = 0;
// Pistetään MiniMax algoritmi pyörimään
Tupla<Double, Siirto> arvoSiirto = negaMax(pelilauta, gamestate.playing);
if (arvoSiirto.getToka() == null) {
return LUOVUTUS;
}
//Palautetaan siirto rajapinnan mukaan
return arvoSiirto.getToka().getUCIString();
}
/**
* Filtteröi pois peliliikkeet jotka säilyttävät / aiheuttavat pelaajan oman kuninkaan shakkiin joutumisen.
* @param lauta tämän hetkinen pelitilanne
* @param kaikkiSiirrot kaikki siirrot jota pelaaja jonka vuoro on, pystyy tekemään
* @param puoli pelaaja jonka vuoro on
* @return listan siirroista josta on filtteröity pois shakkitilanteen säilyttävät / aiheuttavat siirrot
*/
public Lista<Siirto> vainSallitut(Pelilauta lauta, Lista<Siirto> kaikkiSiirrot, Side puoli) {
Lista<Siirto> siirrot = new Lista<>();
for (int i = 0; i < kaikkiSiirrot.size(); i++) {
Siirto siirto = kaikkiSiirrot.get(i);
lauta.siirto(siirto);
Kuningas k = lauta.etsiKuningas(puoli);
if (k != null && !(k.olenUhattuna(lauta))) {
siirrot.add(siirto);
}
lauta.peruutaSiirto();
}
return siirrot;
}
public static final double MAX = 1_000_000d;
public static final double MIN = -MAX;
public static final double DELTA = 0.0001d;
/**
* Aloitetaan rekursiivinen negaMax algoritmi
*
* @param lauta tamanhetkinen pelitilanne
* @param vuoro kumman pelaajan siirto halutaan etsiä
* @return
*/
public Tupla<Double, Siirto> negaMax(Pelilauta lauta, Side vuoro) {
return negaMax(lauta, vuoro, MIN, MAX, syvyysMax);
}
/**
* MiniMaxin yksi rekursio
*
* @param lauta tamanhetkinen pelitilanne
* @param vuoro kumman vuoro
* @param alpha alphabeta karsinnan alpha-arvo
* @param beta alphabeta karsinnan beta-arvo
* @param syvyys syvyys jota tarkastellaan pelipuussa
* @return
*/
private Tupla<Double, Siirto> negaMax(Pelilauta lauta, Side vuoro, double alpha, double beta, int syvyys) {
Lista<Siirto> siirrot = lauta.generoiSiirrot(vuoro);
siirrot = vainSallitut(lauta, siirrot, vuoro);
if (siirrot.size() == 0) {
return new Tupla<>(lauta.onShakissa(vuoro) ? MIN - syvyys : 0, null);
}
if (syvyys <= 0) {
double laudanArvo = evaluaattori.getLaudanArvo(lauta, vuoro);
return new Tupla<>(laudanArvo, null);
}
Tupla<Double, Siirto> parasSiirto = new Tupla<>(MIN, null);
for (int i = 0; i < siirrot.size(); i++) {
haarat++;
Siirto siirto = siirrot.get(i);
lauta.siirto(siirto);
Tupla<Double, Siirto> vastustajanSiirto = negaMax(lauta, ShakkiApu.vastustaja(vuoro), -beta, -alpha, syvyys - 1);
//Päivitetään tämän pelaajan parasta siirtoa,
//jos tämä haara on johtanut toiselle pelaajalle huonomman arvon kuin aiemmassa
if (-vastustajanSiirto.getEka() > parasSiirto.getEka()) {
parasSiirto.set(-vastustajanSiirto.getEka(), siirto);
}
lauta.peruutaSiirto();
alpha = Matikka.maksimi(alpha, parasSiirto.getEka());
if (alphaBeta && alpha >= beta) {
break;
}
}
return parasSiirto;
}
}