java.lang.StackOverflowError vermeiden?

Hallo,
ich habe es geschafft, einen java.lang.StackOverflowError auszulösen.
Aber erst zur Vorgeschichte:
Ich will mir für ein gewisses Flashspiel ein wenig Code schreiben, der das Spiel für mich spielt.(müsste derzeit ewig grinden und das kann ein Computer einfach besser!)
Zum relevanten Part:
Ich habe so ein Spielfeld mit farbigen Steinen gegeben:

Ziel ist es auf die Gruppe an zusammenhängenden Steinen zu klicken, wo halt die meisten Steine sind (im Beispiel kämen also die grüne oder die blaue Gruppe unten rechts mit 4 Steinen in Frage).

Ich habe mir dieses Spielfeld in vereinfachter Form als 8x8 int Array „feld“ eingelesen, wo jedes Element halt einfahc einen der möglichen Farbwerte 0-5 entspricht (0 steht für „hier ist gar kein Stein“, im Bild nicht vorkommend).
gleichzeitig gibts ein weiteres 8x8 boolean array „unusedfeld“, dessen einträge eingangs auf true gesetzt sind.

Und nun will ich die Steine durchgehen und wenn einstein noch nicht „benutzt“ wurde in einer vorherigen gruppe, mit diesem einzelnen Stein eine neue gruppe bilden

dummyGroup = new Group(i, j, feld[i][j]);
builtGroup(i, j);

und dann davon ausgehend mit der Methode

    public void builtGroup(int i, int j) {
        if (!(unusedFeld[i][j])) {
            return;
        }
        if (feld[i][j] == dummyGroup.getFarbe()) {
            dummyGroup.add();
            unusedFeld[i][j] = false;
        }
        // feld rechts
        if (j < width - 1) {
            builtGroup(i, j + 1);
        }
        // feld unten
        if (i < height - 1) {
            builtGroup(i + 1, j);
        }
        // feld links
        if (j > 0) {
            builtGroup(i, j - 1);
        }
    }

so weit wie möglich in alle Richtungen "die Füler ausstrecken, also der startstein betrachtet sein Nachbarn, diese Nachbarn betrachtne wieder ihre eigenen Nachbarn, etc.

Nur beim Aufruf der builtGroup methode tritt auch gleich der besagte Error auf.
Die naheliegende Vermutung ist dass irgendwie „alte“ Steine wieder aufgesucht werden.

Weil von sich aus kann die Rekursionstiefe eigentlich gar nicht zu tief werden, selbst wenn man im Feld oben links beginnt, könnte im worst case die Rekursionstiefe nur 64 erreichen weil eben nur 64 Felder da sind.
Nichtsdestotrotz tritt der Error auf.
Der Errorlog sieht dann so aus:


Exception in thread "main" java.lang.StackOverflowError
	at a.Feld.builtGroup(Feld.java:98)
	at a.Feld.builtGroup(Feld.java:90)
	at a.Feld.builtGroup(Feld.java:98)
	at a.Feld.builtGroup(Feld.java:90)
	at a.Feld.builtGroup(Feld.java:98)
	at a.Feld.builtGroup(Feld.java:90)
	at a.Feld.builtGroup(Feld.java:98)
        ......
	at a.Feld.builtGroup(Feld.java:90)
	at a.Feld.builtGroup(Feld.java:98)
	at a.Feld.builtGroup(Feld.java:90)
	at a.Feld.builtGroup(Feld.java:98)
	at a.Feld.builtGroup(Feld.java:90)

wobei ich da hunderte ähnliche Zeilen nicht mit abgedruckt habe.
Ich sehe aktuell leider nicht wirklich wo der Fehler liegt.
Eigentlich wird ja ein Feld, sobald besucht. gleich als benutzt markiert.
Warum es dann doch wieder benutzt wird, ist mir ein Rätsel :-/

Kann mir da Jemand weiterhelfen?
Falls ich den ganzen Algorithmus irgendwie ohne Rekursion machen könnte, wäre mir das natürlich noch lieber! :slight_smile:

Achja, hier die Definition einer Group:


    class Group {
        int x = 0;
        int y = 0;
        int farbe = 0;
        int length = 0;

        public Group(int x, int y, int farbe) {
            this.x = x;
            this.y = y;
            this.farbe = farbe;
        }

        public void add() {
            length++;
        }

        public int getFarbe() {
            return farbe;
        }

        public int getLength() {
            return length;
        }

        public void printInfo() {
            System.out.println("Group is based at (i,j)=(" + x + "," + y + ") with farbe=" + farbe + " und                                                                               length="       + length + "!");
        }

    }

Wie man sieht hat diese nur 4 Attribute für die „Farbe“ der Gruppe ihre Länge sowie ihre Koordinaten (der Stein am weitesten oben links zählt als Positiond er Gruppe).
Einige getter Methoden und eine Methode, die die Länge einfach ums 1 erhöht.

Also nichts wirklich spektakuläres.

Edit: Ich habe mir gerade mal die „Kooordinaten“ mit ausgeben lassen, für die die builtMethode jeweils aufgerufen wird.

Da sieht man direkt dass dauernd zwischen (i,j)=(7,7) und (7,6) hin und hergewechselt wird

(Ich weiß, die Farberkennung und damit die "feld"einträge sind noch mies und falsch aktuell weil ich dank Betriebssystem Skalierung noch nicht die richtigen Koordinaten für die Felder getroffen habe. und man ja nie weiß bei java welche Klassen und Methoden jetzt die realen koordinaten oder irgendwelche skalierten Fantasiewerte nutzen… Das Grundproblem ist aber Dasselbe)

Tja, du darfst einfach nicht nochmal ein Feld besuchen, auf dem du schon warst. Dazu kannst du die „fertigen“ Felder in eine Liste (oder Set) packen, und bevor du irgendwas in deinen links/rechts/oben/unten-Zweigen tust, brichtst du ab, wenn das jeweilige Feld schon in dieser Liste (oder diesem Set) ist.

Naja, genau das soll ja das


        if (!(unusedFeld[i][j])) {
            return;
        }

in der builtGroup Methode eigentlich tun, direkt rausgehen wenn das Feld schon benutzt wurde :-/

Gut, ich könnte mal versuchen, vorm methodenaufruf das zu testen, vielleicht bringts was

Mal so frei aus der Hüfte:

import java.awt.*;
import java.util.*;
import java.util.List;

public class FindGroups {
    static int[][] field = new int[8][8];

    public static void main(String[] args) {
        initialize();
        List<Group> groups = findGroups();
        System.out.println(groups);
    }

    private static void initialize() {
        Random rnd = new Random();
        for (int x = 0; x < 8; x++) {
            for (int y = 0; y < 8; y++) {
                field[x][y] = rnd.nextInt(6);
            }
        }
    }

    public static List<Group> findGroups() {
        Set<Point> used = new HashSet<>();
        List<Group> groups = new ArrayList<>();
        for (int x = 0; x < 8; x++) {
            for (int y = 0; y < 8; y++) {
                Point p = new Point(x, y);
                if (!used.contains(p)) {
                    used.add(p);
                    Group g = new Group(x, y, field[x][y]);
                    List<Point> todo = new ArrayList<>(neighbors(p));
                    while(!todo.isEmpty()) {
                        Point current = todo.get(0);
                        todo.remove(0);
                        if (!used.contains(current) && field[current.x][current.y] == g.color) {
                            g.size++;
                            used.add(current);
                            todo.addAll(neighbors(current));
                        }
                    }
                    groups.add(g);
                }
            }
        }
        return groups;
    }

    static Set<Point> neighbors(Point p) {
        Set<Point> result = new HashSet<>();
        if(p.x > 0) {
            result.add(new Point(p.x-1, p.y));
        }
        if(p.y > 0) {
            result.add(new Point(p.x, p.y-1));
        }
        if(p.x < 7) {
            result.add(new Point(p.x+1, p.y));
        }
        if(p.y < 7) {
            result.add(new Point(p.x, p.y+1));
        }
        return result;
    }

    static class Group {
        int color;
        int size = 1;
        int x;
        int y;

        Group(int x, int y, int c) {
            this.x = x;
            this.y = y;
            this.color = c;
        }

        @Override
        public String toString() {
            return "Group{" +
                       "color=" + color +
                       ", size=" + size +
                       ", x=" + x +
                       ", y=" + y +
                       '}';
        }
    }
}

Ein compilierbares Beispiel wäre hilfreich. So weiß niemand, was die Zeile „98“ und „90“ aus der Fehlermeldung sind.

Hallo, die aktuelle Version meiner Klasse sieht aktuell so aus, den ursprünglichen Stackoverflow scheine ich umgangen zu haben indem ich den „Feld benutzt?“ check vor den Methodenaufruf geschaltet habe.
(es kommt zumindest mal keine Fehlermeldung mehr…)
Der Code aktuell:

package a;

import java.awt.Color;
import java.awt.Robot;
import java.awt.event.InputEvent;

public class Feld {
    Robot bot;

    int width = 8;
    int height = 8;
    
    int topleftx = 668;
    int toplefty = 267;
    int bottomrightx = 1248;
    int bottomrighty = 844;
    
    int deltax = (int) ((1.0 * bottomrightx - topleftx) / width);
    int deltay = (int) ((1.0 * bottomrighty - toplefty) / height);

    int[][] feld = new int[height][width];

    boolean[][] unusedFeld = new boolean[height][width];
    Group maxGroup;
    Group dummyGroup;

    public Feld(Robot bot) {
        this.bot = bot;
        initFeld();
        // init unusedFeld
        for (int i = 0; i < unusedFeld.length; i++) {
            for (int j = 0; j < unusedFeld[i].length; j++) {
                unusedFeld[i][j] = true;
            }
        }

        System.out.println("unusedFeld initialized!");
        findAndClickMaxGroup();
    }

    public void findAndClickMaxGroup() {
        System.out.println("findAndClickMaxGroup started!");
        dummyGroup = new Group(0, 0, feld[0][0]);
        builtGroup(0, 0);
        maxGroup = dummyGroup;
        for (int i = 0; i < feld.length; i++) {
            for (int j = 0; j < feld[i].length; j++) {
                if ((i + j == 0) || (!(unusedFeld[i][j]))) {
                    continue;
                }
                // built dummyGroup and explore its lengths
                dummyGroup = new Group(i, j, feld[i][j]);
                builtGroup(i, j);
                System.out.println("New found group's info:");
                dummyGroup.printInfo();
                // change maxGroup if applicable
                if (dummyGroup.getLength() > maxGroup.getLength()) {
                    System.out.println("New maxGroup found!");
                    maxGroup = dummyGroup;
                }
            }
        }
        // Click maxGroup's position

        System.out.println("Now clicking on maxGroup!");
        click(maxGroup.x,maxGroup.y);
    }

    public void click(int Zeile, int Spalte) {
        int x = (int) (topleftx + deltax / 5.0 + Spalte * deltax);
        int y = (int) (toplefty + deltay / 5.0 + Zeile * deltay);

        bot.mouseMove(x, y); // ggbfls. mit custom funktion ersetzen
        bot.delay(100);
        bot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        bot.delay(100);
        bot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);

    }

    public void builtGroup(int i, int j) {
        System.out.println("Watching stone at i="+i+", j="+j+"!");
        if (!(unusedFeld[i][j])) {
            return;
        }
        if (feld[i][j] == dummyGroup.getFarbe()) {
            dummyGroup.add();
            unusedFeld[i][j] = false;
        }
        // feld rechts
        if ((j < width - 1)&& (!(unusedFeld[i][j]))){
            builtGroup(i, j + 1);
        }
        // feld unten
        if ((i < height - 1)&&(!(unusedFeld[i][j])) ){
            builtGroup(i + 1, j);
        }
        // feld links
        if ((j > 0)&&(!(unusedFeld[i][j]))) {
            builtGroup(i, j - 1);
        }
    }

    // ****Feldkram****

    public void initFeld() {
        System.out.println("initFeld()!");
        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                feld[x][y] = getElement(x, y);
                System.out.println("Color("+x+","+y+")="+feld[x][y]);
            }
        }
        System.out.println("initFeld() finished!");

    }

    public int getElement(int Zeile, int Spalte) {
        int x = (int) (topleftx + deltax / 5.0 + Spalte * deltax);
        int y = (int) (toplefty + deltay / 5.0 + Zeile * deltay);
        return readElement(x, y);
    }

    public int readElement(int p, int q) {
        // read color at (x,y)=(a,b)
        Color color = bot.getPixelColor((int)p,(int)q);

        int g = color.getGreen();
        int r = color.getRed();
        int b = color.getBlue();
        System.out.print("p,q="+p+","+q+"; r,g,b="+r+","+g+","+b+"; so color is ");
        // return certain integer based on said color
        // rot
        if (r > 180 && g < 100 && b > 100) {
            System.out.println("1!");
            return 1;
        }
        // grün
        else if (g > 180 && r < 100 && b < 100) {
            System.out.println("2!");
            return 2;
        }
        // blau
        else if (b > 180 && r < 120 && g < 120) {
            System.out.println("3!");
            return 3;
        }
        // gelb
        else if (g > 200 && r > 200 && g < 100) {
            System.out.println("4!");
            return 4;
        }
        // lila
        else if (r > 150 && b > 200 && g < 100) {
            System.out.println("5!");
            return 5;
        }
        // sonstiges
        else if(r<100&&g<100&&b<100){
            System.out.println("0!");
            return 0;
        }
        System.out.println("-1!");
        return -1;

    }

    class Group {
        int x = 0;
        int y = 0;
        int farbe = 0;
        int length = 0;

        public Group(int x, int y, int farbe) {
            this.x = x;
            this.y = y;
            this.farbe = farbe;
        }

        public void add() {
            length++;
        }

        public int getFarbe() {
            return farbe;
        }

        public int getLength() {
            return length;
        }

        public void printInfo() {
            System.out.println("Group is based at (i,j)=(" + x + "," + y + ") with farbe=" + farbe + " und length="
                    + length + "!");
        }

    }

}

Nur weiß ich generell nicht was es tut und obs das Richtige ist weil ichs beim eigentlichen Feld mit den blöden Koordinaten einfach nicht auf die Reihe kriege.

Ob ich jetzt die normalen koordinaten (die ich für meine 1920x1080 Auflösung ablese mittels MouseInfo.getPointerInfo().getLocation()) so nehmen kann oder mit dem Skalierfaktor 1.25 multiplizieren oder dividieren muss.
Und wie sich da die bot.mouseMove() Methode von der bot.getPixelColor Methode unterscheidet oder nicht.

Ich habe gefühlt schon alle möglichen varianten durchprobiert aber irgendwie platziert er die Maus nie da, wo er soll bzw. liest nicht an der gewünschten Stelle ab :-/

Im prinzip müsste es einfach sein, oben links die ecke und unten rechts die vom Feld ablessen, die Differenzen durch 8 teilen.
und schon müsste ich die passende Steinlänge haben.
und dann eben so grob der bereich oben link im Stein mit
steinposx=steinobenlinksx+i*steinbreite, mit i aus 0-7.
analog für den y-wert des steins.

Ich habe etwas weiter rumgewerkelt, mittlerweile klappt sowohl das „erkennen“ der Feldfarben als auch das Finden der Gruppen bzw. der größten Gruppe.
Hier der aktuelle Code:

package a;

import java.awt.Color;
import java.awt.Robot;
import java.awt.event.InputEvent;

public class Feld {
    Robot bot;

    int width = 8;
    int height = 8;

    int topleftx = 668;
    int toplefty = 267;
    int bottomrightx = 1248;
    int bottomrighty = 844;

    int deltax = (int) ((1.0 * bottomrightx - topleftx) / width);
    int deltay = (int) ((1.0 * bottomrighty - toplefty) / height);

    int[][] feld = new int[height][width];

    boolean[][] unusedFeld = new boolean[height][width];
    Group maxGroup;
    Group dummyGroup;

    public Feld(Robot bot) {
        this.bot = bot;
        initFeld();
        // init unusedFeld
        for (int i = 0; i < unusedFeld.length; i++) {
            for (int j = 0; j < unusedFeld[i].length; j++) {
                unusedFeld[i][j] = true;
            }
        }

        System.out.println("unusedFeld initialized!");
        printFeld();
        findAndClickMaxGroup();

    }

    public void findAndClickMaxGroup() {
        System.out.println("findAndClickMaxGroup started!");
        dummyGroup = new Group(0, 0, feld[0][0]);
        builtGroup(0, 0);
        maxGroup = dummyGroup;
        System.out.println("New found group's info:");
        dummyGroup.printInfo();
        for (int i = 0; i < feld.length; i++) {
            for (int j = 0; j < feld[i].length; j++) {
                if ((i + j == 0) || (!(unusedFeld[i][j]))) {
                    continue;
                }
                // built dummyGroup and explore its lengths
                dummyGroup = new Group(i, j, feld[i][j]);
                builtGroup(i, j);
                System.out.println("New found group's info:");
                dummyGroup.printInfo();
                // change maxGroup if applicable
                if (dummyGroup.getLength() > maxGroup.getLength()) {
                    System.out.println("New maxGroup found!");
                    maxGroup = dummyGroup;
                }
                System.out.println("final maxGroup's info:");
                maxGroup.printInfo();
            }
        }
        // Click maxGroup's position

         System.out.println("Now clicking on maxGroup!");
         click(maxGroup.x, maxGroup.y);
    }

    public void click(int Zeile, int Spalte) {
        int x = (int) (topleftx + 15 + 1.0 * Spalte * deltax);
        int y = (int) (toplefty + 15 + 1.0 * Zeile * deltay);

        bot.mouseMove(x, y); // ggbfls. mit custom funktion ersetzen
        bot.delay(100);
        bot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        bot.delay(100);
        bot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);

    }

    public void builtGroup(int i, int j) {
        if (!(unusedFeld[i][j])) {
            return;
        }
        if (feld[i][j] == dummyGroup.getFarbe()) {
            dummyGroup.add();
            unusedFeld[i][j] = false;
        }
        // feld rechts
        if ((j < width - 1) && (!(unusedFeld[i][j]))) {
            builtGroup(i, j + 1);
        }
        // feld unten
        if ((i < height - 1) && (!(unusedFeld[i][j]))) {
            builtGroup(i + 1, j);
        }
        // feld links
        if ((j > 0) && (!(unusedFeld[i][j]))) {
            builtGroup(i, j - 1);
        }
    }

    // ****Feldkram****

    public void initFeld() {
        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                feld[x][y] = getElement(x, y);
            }
        }

    }

    public int getElement(int Zeile, int Spalte) {
        int x = (int) (topleftx + 15 + 1.0 * Spalte * deltax);
        int y = (int) (toplefty + 15 + 1.0 * Zeile * deltay);
        return readElement(x, y);
    }

    public void printFeld() {
        System.out.println("**********************************");
        for (int i = 0; i < feld.length; i++) {
            for (int j = 0; j < feld[i].length; j++) {
                System.out.print(feld[i][j] + " ");
            }
            System.out.println(".");
        }
        System.out.println("**********************************");
    }

    public int readElement(int p, int q) {
        // read color at (x,y)=(a,b)
        bot.delay(100);
        Color color = bot.getPixelColor(p, q);
        bot.mouseMove(p, q);

        int g = color.getGreen();
        int r = color.getRed();
        int b = color.getBlue();
        // return certain integer based on said color
        // rot
        if (r > 180 && g < 100 && b < 100) {
            return 1;
        }
        // grün
        else if (g > 180 && r < 100 && b < 100) {
            return 2;
        }
        // blau
        else if (b > 180 && r < 120 && g < 120) {
            return 3;
        }
        // gelb
        else if (g > 200 && r > 200 && b < 100) {
            return 4;
        }
        // lila
        else if (r > 150 && b > 200 && g < 100) {
            return 5;
        }
        // sonstiges
        else if (r < 100 && g < 100 && b < 100) {
            return 0;
        }
        return -1;

    }

    class Group {
        int x = 0;
        int y = 0;
        int farbe = 0;
        int length = 0;

        public Group(int x, int y, int farbe) {
            this.x = x;
            this.y = y;
            this.farbe = farbe;
        }

        public void add() {
            length++;
        }

        public int getFarbe() {
            return farbe;
        }

        public int getLength() {
            return length;
        }

        public void printInfo() {
            System.out.println("Group is based at (i,j)=(" + x + "," + y + ") with farbe=" + farbe + " und length="
                    + length + "!");
        }

    }

}

Nun bliebe einfahc nur noch die größte Gruppe anzuklicken aber praktisch Alles, was irgendwie mit bot.mouseMove() zu tun hat, klappt nicht.
Bzw. konkret setzt der Bot die Maus an alle möglichen orte, nur nicht dahin wo er soll.

So als Beispiel: Ich lasse zu Testzwecken bei jedem Farbablesen eines Steins die Maus auch mit auf die Position stellen an der geprüft wird.
Für die Steine, die sich in der 2. Spalte und weiter rechts befinden, klappt das auch.
Allerdings für die Steine in der ersten Spalte spinnt der Robot einfahc komplett.

Hei0t konkret:
Anfangs setzt der Bot die Maus irgendwo oben links jenseits des Feldes.
dann in der Nähe des Feldes (0,1).
Die folgebewegungen sind dann auf die steine (0,2)(0,3) usw.
in der 2. zeile dasselbe: für stein (1,0) ist der Cursor weit links des Feldes.
stein (1,1) und weitere in der zeile klappen wieder.
so geht das weiter, immer wenn er auf einen stein der ersten spalte gehen soll, hüpft er sonstwohin, nur nicht direkt auf den stein.

am ende von allem soll der bot ja auch den cursor auf die position der größten gruppe setzen.
da geht unlogischerwese der cursor auch auf ein pixel weit oberhalb des spielfeldes O_o

Also ich blicks nicht was für einen Mist der moveMouse() befehl da tut…

Edit: Ich habe es noch einmal in super SlowMo durchlaufen lassen:
für Steine der ersten Spalte geht er immer ein Stück links abseits des Feldes, wobei er bei (0,0) nahc linksoben statt nur nahc links ausbricht.
für steine in der 2. spalte ist er zwar auf dem stein aber nicht wie geplant so grob oben mitte des steines, sondern oben rechts.
für die steine 3,4,usw in einer zeile trifft er ziemlich genau oben-mittig auf den stein, also gleiche gewünschte position.

Nur irgendwas geht shceinbar richtig hart in die hose wenn die spalte 0 oder 1 betrachtet wird O_o

Schauen wir einfach einmal, was so alles auffällt:

  1. Unnötige double Berechnung:
    int deltax = (int) ((1.0 * bottomrightx - topleftx) / width);
    int deltay = (int) ((1.0 * bottomrighty - toplefty) / height);

da kannst Du einfach berechnen:

    int deltax = (bottomrightx - topleftx) / width;
    int deltay = (bottomrighty - toplefty) / height;

Ob Du nun erst eine Fließkommazahl Berechnung machst um auf 72,5 zu kommen um dann die 0,5 wegzuwerfen oder ob Du gleich eine Integer Division machst um direkt die 72 zu bekommen macht keinen Unterschied.

Das Gleiche gilt auch bei den anderen Berechnungen, wo das 1.0* einfach unnötig ist.

Das +15 als Versatz hat mich etwas gewundert - da hätte ich 0,5deltax bzw. 0,5deltay verwendet. Spielt aber keine Rolle - du hast einen Versatz von 15 und die Ungenauigkeiten sind deutlich geringer. (Du hast bei der Berechnung der deltyx/deltay Werte eine Ungenauigkeit von < 1 was beim höchsten Feld eine Ungenauigkeit von <8 Pixeln ausmacht, die Fehlen. Damit bist du mindestens 8 Pixel im jeweiligen Feld.)

Ansonsten sieht das erst einmal prinzipiell ok aus. Da wäre die Frage, was denn das genaue Vorgehen von Dir ist. Du hast da wirklich die Screen Koordinaten? Wenn es da ein Problem mit den Screen Koordinaten gibt, dann liegt die Vermutung durchaus nahe, dass es hier doch noch ein weitergehendes Problem gibt und ggf. auch Probleme beim Erfassen des Grids vorhanden sind.

Daher wäre es evtl. einmal interessant zu wissen, wie Du es aufgebaut hast. Und Du kannst ggf. ja auch erst einmal mit der Maus durch Klicks die Koordinaten ermitteln.

Generell ist halt wichtig, dass man da immer gut aufpasst, was für Koordinaten man nutzt. Beim Robot sind es Screen Koordinaten. Aber es gibt noch die Koordinaten im Fenster oder in Controls. Das kann also etwas problematisch sein. Es gibt aber auch Methoden, die dies umrechnen für einen.

Ansonsten schade, dass Du Deinen Thread im Java-Forum einfach so verlassen hast. (Ich nehme mal an, dass Du da den Thread auch geführt hattest.)

Hallo, Konrad, du bist aber auch überall :slight_smile:

Ja die 15 Pixel kommen einfach daher weil ich beim Farbablesen und so nicht will dass die Mitte eines Steins benutzt wird sondern ein Pixel so oben links im Stein (wie man ja sieht auf dem Bild sieht hat jeder Stein aussenrum einen dunklen Rahmen, in der unteren Hälfte einen dunklen und oben einen hellen Bereich sowie ein Symbol.
Zum Farbvergleich will ich bevorzugt die helle Farbe im oberen Steinbereich nehmen. daher auch so grob oben links auf dem Stein den Farbvergleich)
Hatte es anfangs durchaus mit sowas wie deltax/5.0 versucht aber irgendwie funktionierte das nicht so recht. Und da es mir mittlerweile zu nervig wurde, habe ich das +15 gehardcoded weil das so eine halbwegs passende Entfernung von der Steinecke oben links ist.

Ich benutze nur den Robot, ausschließlich.
Irgendwie scheint die Farberkennung so halbwegs zu passen positionsmässig (wobei manchmal komischerweise eine -1 als farbe bestimmt wird, also doch manchmal an eienr falschen Stelle geguckt wird.)

Nur wenn ich auf die praktisch selben koordinaten wie beim Farbablesen (Formeln sind ja Diesselben) mousemove, verhält es sich… komisch.
Ich versuche mal ein Video aufzunehmen wie genau sich die Maus bewegt.
Die Mausbewegungen ergeben teilweise gar keinen Sinn, innerhalb einer Zeile sollte es sich ja schließlich simpel bei gleicher y komponente um einen festen Abstand nach rechts bewegen.

Bin mir auch bis jetzt nicht sicher ob das Koordinatensystem des Robots nun von 0,0 bis 1919,1079 geht. Oder ob es skalierfaktorbedingt (skalierung 125%) etwas Anderes ist.

Wobei übrigens die 8 Pixel „Messfehler“ schon recht viel ausmachen können wenn man bedenkt dass ein Stein nur so 60x60 Pixel breit ist.

Video
Ich hab jetzt mal ein Video gemacht wie sich der Cursor durch das laufende Programm bewegt.
Ich habe ihn anfangs oben rechts positioniert,
Alle weiteren bewegungen kommen durch die Roboter.mouseMove() Befehle zustande.

Ich verstehe nicht wie der Robot da so „daneben“ liegen kann während aber gleichzeitig die Farbanalyse mittels getPixelColor mehr oder minder immer die Steine trifft. O_o

Hm, also jetzt blicke ich gar nichts mehr.
Die Eckkoordinaten des Feldes hatte ich mit dem Code

        while (true) {

            b = MouseInfo.getPointerInfo().getLocation();
            x = (int) b.getX();
            y = (int) b.getY();
            Color color = bot.getPixelColor(x, y);
            System.out.println("x=" + x + "; y=" + y + "; green=" + color.getGreen() + "; blue=" + color.getBlue()
                    + "; red=" + color.getRed());
            bot.delay(200);
        }

abgelesen, also namentlich mit MouseInfo.getPointerInfo().getLocation().

Jetzt habe ich aber mal just for fun diese abgelesenen Koordinaten (die ja auch hardcoded im Code stehen als topleftx usw.)
und mousemove damit benutzt.
die kordinaten sind oben links bzw. unten rechts jenseits des Feldes, heißt die von mouseinfo erhaltenen koordinaten nehme das Feld „größer“ war als es ist.
Ich würde jetzt noch verstehen wnen Alles nahc unten rechts gestreckt wäre oder so.
Aber kann das Feld als ein größeres Rechteck wahrgenommen werden als es real ist?
Da müsste ja , statt von oben links, vond er Bildschirmmitte aus gestreckt worden sein? O_o

Ich hasse das, warum kann Java kein braves programm sein und einfach die realen Koordinaten (0,0) bis (1920,1080) einfach so übernehmen wie sie sind?

Das liegt einfach daran, dass du mehr als einen Monitor hast. Für Java sind alle Monitore zusammen eine große Fläche, wenn du die Location auf einem Monitor haben willst, musst du das noch berechnen

Point location = MouseInfo.getPointerInfo()                
            .getLocation();
    
    
Rectangle bounds = MouseInfo.getPointerInfo()
            .getDevice()
            .getDefaultConfiguration()
            .getBounds();
    
    
Point wantedLocation = new Point(location.x - bounds.x, location.y - bounds.y);
    
System.out.println(location);
System.out.println(wantedLocation);

Also ich habe ehrlich gesagt noch immer ein paar Probleme, zu verstehen, was Du genau machst.

Mit der MouseInfo Zeile bekommst Du auch Screen Koordinaten - die sind identisch zu den Koordinaten des Roboters.

Also einfach mal ein kleines Test-Programm:

package de.kneitzel;

import javax.swing.*;
import java.awt.*;

public class ScreenPointerTest {
    public static void main(String[] args) throws Exception {
        JFrame frame = new JFrame("MouseTest");
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JTextArea area = new JTextArea();
        frame.add(area);
        frame.setVisible(true);

        Robot robot = new Robot();

        while(true) {
            Point posBeforeMove = MouseInfo.getPointerInfo().getLocation();
            robot.mouseMove(posBeforeMove.x, posBeforeMove.y);
            Point posAfterMove = MouseInfo.getPointerInfo().getLocation();
            SwingUtilities.invokeLater( () -> area.setText(posBeforeMove + " - " + posAfterMove));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {}
        }
    }
}

Das ist nicht schön, aber es hilft bei einem ersten Test. Wir haben ein Fenster, in dem wir was ausgeben. Das ist aber erst einmal egal. Der Thread außerhalb macht etwas anderes:

  • Die Position der Maus wird gelesen
  • Die Position der Maus wird dann gesetzt
  • Dann wird die Position erneut gelesen.

Da wirst Du dann sehen: Die Maus verändert die Position nicht.

Um zu sehen, ob es funktionieren würde, könnte man die Position verändern - also bei x und y jeweils noch ein +10 oder so.
==> Dann springt die Maus jede Sekunde ein kleines Stück und man sieht in der Ausgabe auch die veränderte Position.

Das sind so Tests, mit denen ich mir immer etwas erarbeiten würde. Dieses Programm kannst Du z.B. Nutzen um dann die Koordinaten auf dem Bildschirm zu bestätigen: Wo sind die einzelnen Eckpunkte?
Dann kannst Du schauen: Berechnest Du die korrekt? (Das wäre bei mir eine eigene Klasse. Die bekommt dann im Konstruktor die äußeren zwei Eckpunkte und berechnet dann alles:
→ Koordinaten der Grid in Screen Punkte (für Klick)
→ Screen Punkte in Grid Koordinaten (Brauchst Du evtl. nicht, aber das kann man nutzen für Tests. Also z.B. die Applikation oben angepasst um dann die zwei äußeren Eckpunkte zu setzen um dann bei der Maus immer die Grid Koordinaten angezeigt zu bekommen.

Wichtig ist halt, alles in Schritten zu entwickeln. Dabei Teile entwickeln, die man testen kann (Und deren Entwicklung erst abgeschlossen ist, wenn diese Tests komplett abgeschlossen sind. Diese tests sind in der Regel automatische Tests (sogenannte Unit Tests) aber auch so manuelle Tests sind am Anfang durchaus ok.

@Ebi:
Ich habe nicht mehrere Monitore.Höchstens mein notebook versteckt noch irgendwo einen Monitor vor mir, von dem ich ncihts weiß :wink:

@all:
Okay, ich habe glaube ich mein Problem auf eine recht unerwartete Art gelöst:
Sache war ich hatte die von der Uni vorgegebene Eclipse Version, Workspace, Eisntellungen und Co. benutzt.
Diese waren auf java 8 eingestellt und auch Eclipse war eine Asbach Uralt Version.

Ich habe also komplett frisch die neueste Eclipse Version sowie Java 18 installiert, meine Projekte da rein importiert, wieder getestet.
Diese komischen „Ausbrecher“ bei den Positionen hatte ich shcon mal nicht, nur war das Ganze einfach zu weit unten rechts angesiedelt.
Hier kam nun wieder gute alte ich könnte so kotzen wenn ich den Rotz sehe dpi Skalierfaktor ins Spiel, den Java in seiner Dämlichkeit einfahc nicht ignorieren kann.

Also überall ein schlichtes /1.25 rangebaut und schon wird überall da geklickt, Farben abgelesen und Co. wie es sollte :slight_smile:

TL;DR:
Alte, von der Uni so vorgegebene und vorkonfigurierte Eclipse und Java version waren für den ganzen Unsinn verantwortlich.
Und halt das Bullshit DPI Scaling, das man in java mit einem Faktor 1/skalierfaktor kompensieren muss.

Das kann ich nicht nachvollziehen. Auch mit Skalierungsfaktor sind die Koordinaten gleich.

Unter dem Strich bedeutet es nur, dass Deine Werte, die Du angenommen hast für die Eckpunkte falsch waren. Das waren dann keine Bildschirm-Koordinaten sondern irgend etwas anderes, das Du irgendwoher hattest.

Du musst also in Deinem Code nichts wild mit Skallierungsfaktor Rechnen. Du kannst überall mit Bildschirm-Koordinaten rechnen (Auch bei mehreren Bildschirmen!) - alles was Du nur brauchst sind eben die Bildschirmkoordinaten des Grids auf dem Bildschirm.

Naja, ich hbe wie gesagt immer mit Mouseinfo… die koordinaten abgelesen.
Die beziehen sich auch offensichtlichen auf die reale Auflösung von 1920x1080.

Der Robot hingegen geht davon aus dass der Screen nur (1920/1.25) mal (1080/1.25) lang ist.
Keine Ahnung warum, Java wurde halt mies programmiert,
aber während mouseinfo den Skalierungsfaktor ignoriert bzw. als 1.0 annimmt,
beachtet der Robot den Faktor.

Egak wie rum, wenn ich die mit mouseinfo gemessenen koordinaten nehme und durch 1.25 teile, kriege ich faktishc koordinaten im koordinatesystem des Robots.

Und wie geagt, ich habe immer noch keine mehreren Bildschirme.
Nur den, den man auch im Video sieht.
Höchstens mein notebook ist vielleicht ein Transformer mit anderen Bildshcirmen, aber ich weiß mal nur von Einem :slight_smile:

Warum das Alles so sit, weiß ich auch nicht.
Wobei ich irgendwo las dass bei Java mouseinfo die Windows Api Methode getCurrPos oder so benutzt.
Und Robot nutzt wohl irgendwie andere „dpi aware“ Koordinaten oder so

Also ich habe bei mir ja auch mit dem Test-Programm getestet und auch wenn ich z.B. auf 125% gegangen bin, haben Robot und MouseInfo mit den gleichen Koordinaten gearbeitet. Da war an keiner Stelle irgend etwas zu rechnen.

Hast Du denn mal diese kleine Test-Routine auch bei dir laufen lassen?

Mich würde also wirklich einmal der Test mit meinem Programm interessieren. Ebenso mehr Details zu dem System. Windows 10 Laptop ohne externen Monitor? Und Skalierung auf 125%

Was ich gefunden habe in der Doku:

The coordinate system used for the mouse position depends on whether or not the GraphicsDevice is part of a virtual screen device. For virtual screen devices, the coordinates are given in the virtual coordinate system, otherwise they are returned in the coordinate system of the GraphicsDevice

Damit kann es durchaus so sein, dass da irgendetwas ist. Da wäre dann aber auch interessant, wie da vorzugehen ist. So findet sich z.B. auch eine AffineTransform wenn man sich das Device und dessen Konfiguration anschaut. Evtl. kann man damit etwas machen.

Das mag für Dich evtl. nicht notwendig scheinen, so Du nur auf Deinem Rechner arbeiten willst. Aber Dein Rechner und mein Rechner verhalten sich anscheinend schon unterschiedlich - dein Code würde hier also nicht funktionieren. Daher die Frage, was hier zu tun wäre, damit es universell laufen würde.