import java.util.ArrayList; // import java.util.Random; wird nicht mehr verwendet /** * Diese Klasse implementiert den Harten Ki Spieler. * @author Florian Alexy und Florian Hantzschel * */ public class SpecificAiPlayerHard extends AiPlayer{ private int gridSize; private boolean[][] shotsFired; private final ArrayList hitQueue; //private final Random random; wird nicht mehr verwendet private int nextChessRow; private int nextChessCol; // Enum für die Orientierung private enum Orientierung { UNBEKANNT, HORIZONTAL, VERTIKAL } private Orientierung orientierung; // Speichert den ersten Treffer zur Bestimmung der Orientierung private Point firstHit; /** * Eltern-Klasse wird initialisiert und alle lokalen variablen, * die gesetzt werden können, werden initialisiert. * @author Florian Alexy und Florian Hantzschel */ public SpecificAiPlayerHard() { super(); this.setName("AI Player Hard"); /*this.gridSize = super.board.getSize(); this.shotsFired = new boolean[gridSize][gridSize];*/ this.gridSize = 0; this.hitQueue = new ArrayList<>(); //this.random = new Random(); wird nicht mehr verwendet this.nextChessRow = 0; this.nextChessCol = 0; this.orientierung = Orientierung.UNBEKANNT; this.firstHit = null; } /** * Prüft, ob auf den punkt schonmal geschossen wurde. * @param p zu prüfender Punkt * @return boolean * @author Florian Alexy und Florian Hantzschel */ public boolean alreadyShot(Point p) { return shotsFired[p.getX()][p.getY()]; } /** * Bestimmt den nächsten punkt, der beschossen werden soll. * @return Position * @author Florian Alexy und Florian Hantzschel */ public Point getNextMove() { if(gridSize == 0) { this.gridSize = super.board.getSize(); this.shotsFired = new boolean[gridSize][gridSize]; } // Wenn wir noch Treffer in der Queue haben, diese priorisieren while (!hitQueue.isEmpty()) { Point target = hitQueue.remove(0); if (!alreadyShot(target)) { shotsFired[target.getX()][target.getY()] = true; return target; } } // Ansonsten weiterhin "Schachbrettmuster" int row = nextChessRow; int col = nextChessCol; while (alreadyShot(new Point(row, col))) { advanceChessPattern(); row = nextChessRow; col = nextChessCol; } shotsFired[row][col] = true; advanceChessPattern(); return new Point(row, col); } /** * Nachdem receiveShoot beim gegner den schuss verarbeitet hat, * wird diese Methode mit der antwort aufgerufen. * @param hitResponse the hitresponse * @author Florian Alexy und Florian Hantzschel */ @Override public synchronized void receiveHit(HitResponse hitResponse) { super.receiveHit(hitResponse); // Wenn es ein Treffer ist, Adjacent-Punkte hinzufügen if (hitResponse.getHitResponse() == HitResponseType.HIT) { Point hitPoint = hitResponse.getPoint(); // Wenn wir noch keinen ersten Treffer haben, speicher ihn if (firstHit == null) { firstHit = hitPoint; // Orientierung noch unbekannt: alle möglichen Richtungen hinzufügen addAdjacentPoints(hitPoint); } else { // Wenn Orientierung noch nicht bestimmt, jetzt prüfen if (this.orientierung == Orientierung.UNBEKANNT) { // Prüfen, ob der zweite Treffer horizontal oder vertikal liegt if (firstHit.getY() == hitPoint.getY()) { this.orientierung = Orientierung.VERTIKAL; } else if (firstHit.getX() == hitPoint.getX()) { this.orientierung = Orientierung.HORIZONTAL; } // Sobald die Orientierung erkannt wurde, entferne alle „unpassenden“ Punkte if (this.orientierung != Orientierung.UNBEKANNT) { cleanUpHitQueue(); } } // Für den aktuellen Treffer nur in passender Orientierung erweitern addPointsByOrientation(hitPoint); } } else if (hitResponse.getHitResponse() == HitResponseType.SUNK) { this.orientierung = Orientierung.UNBEKANNT; firstHit = null; hitQueue.clear(); } } /** * Entfernt aus der hitQueue alle Punkte, die nicht der erkannten Orientierung entsprechen. * @author Florian Alexy und Florian Hantzschel */ private void cleanUpHitQueue() { if (firstHit == null || this.orientierung == Orientierung.UNBEKANNT) { return; } ArrayList toRemove = new ArrayList<>(); for (Point p : hitQueue) { if (this.orientierung == Orientierung.HORIZONTAL) { // HORIZONTAL => gleiche Zeile wie firstHit if (p.getX() != firstHit.getX()) { toRemove.add(p); } } else if (this.orientierung == Orientierung.VERTIKAL) { // VERTICAL => gleiche Spalte wie firstHit if (p.getY() != firstHit.getY()) { toRemove.add(p); } } } hitQueue.removeAll(toRemove); } /** * Fügt benachbarte Felder in der erkannten Orientierung hinzu. * Ist die Orientierung HORIZONTAL, so werden nur links/rechts hinzugefügt. * Ist sie VERTICAL, so werden nur oben/unten hinzugefügt. * @author Florian Alexy und Florian Hantzschel */ private void addPointsByOrientation(Point point) { if (this.orientierung == Orientierung.UNBEKANNT) { // Fallback: füge alle benachbarten Punkte hinzu addAdjacentPoints(point); return; } int x = point.getX(); int y = point.getY(); if (this.orientierung == Orientierung.HORIZONTAL) { // Gleiche Zeile => links und rechts vom Point Point left = new Point(x, y - 1); Point right = new Point(x, y + 1); if (isValidPoint(left) && !alreadyShot(left) && !hitQueue.contains(left)) { hitQueue.add(left); } if (isValidPoint(right) && !alreadyShot(right) && !hitQueue.contains(right)) { hitQueue.add(right); } } else if (this.orientierung == Orientierung.VERTIKAL) { // Gleiche Spalte => oben und unten Point up = new Point(x - 1, y); Point down = new Point(x + 1, y); if (isValidPoint(up) && !alreadyShot(up) && !hitQueue.contains(up)) { hitQueue.add(up); } if (isValidPoint(down) && !alreadyShot(down) && !hitQueue.contains(down)) { hitQueue.add(down); } } } /** * Diese Methode erweitert die hitsQueue um die umliegenden Punkte die Schiffe seien könnten. * @param point * @author Florian Alexy und Florian Hantzschel */ private void addAdjacentPoints(Point point) { int x = point.getX(); int y = point.getY(); // Possible adjacent positions (up, down, left, right) Point[] adjacentPoints = { new Point(x, y - 1), new Point(x, y + 1), new Point(x - 1, y), new Point(x + 1, y) }; for (Point p : adjacentPoints) { if (isValidPoint(p) && !alreadyShot(p) && !hitQueue.contains(p)) { hitQueue.add(p); } } } /** * Die Methode gibt zurück, ob eine Position auf dem Board ist. (Boolean) * @param point Punkt der geprüft werden soll * @return Ist auf dem Board oder nicht. * @author Florian Alexy und Florian Hantzschel */ private boolean isValidPoint(Point point) { return point.getX() >= 0 && point.getX() < gridSize && point.getY() >= 0 && point.getY() < gridSize; } /** * Ki Methode um zu schießen. * @author Florian Alexy und Florian Hantzschel */ @Override public void aiShoot() { this.enemy.receiveShoot(getNextMove()); } /** * Die Zeilen und spalten variable wird hier angepasst, sodass beim nächsten schuss im Muster geschossen wird. * @author Florian Alexy und Florian Hantzschel */ private void advanceChessPattern() { nextChessCol += 2; if (nextChessCol >= gridSize) { nextChessRow += 1; nextChessCol = (nextChessRow % 2 == 0) ? 0 : 1; // Alternate starting points for chess pattern } if (nextChessRow >= gridSize) { nextChessRow = 0; nextChessCol = 0; // Reset if pattern wraps around } } // Adds adjacent cells to the hit queue /* public void processHit(int row, int col) { if (row > 0 && !alreadyShot(row - 1, col)) { hitQueue.add(new int[]{row - 1, col}); } if (row < gridSize - 1 && !alreadyShot(row + 1, col)) { hitQueue.add(new int[]{row + 1, col}); } if (col > 0 && !alreadyShot(row, col - 1)) { hitQueue.add(new int[]{row, col - 1}); } if (col < gridSize - 1 && !alreadyShot(row, col + 1)) { hitQueue.add(new int[]{row, col + 1}); } }*/ }