pgo-automation/src/main/java/com/pokemongo/PokemonGoAutomation.java
2025-12-14 20:05:39 +01:00

700 lines
26 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.pokemongo;
import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
/**
* Automatizační nástroj pro Pokémon GO.
* Najde běžící aplikaci, označí všechny Pokémony a stiskne tlačítko Transfer.
*/
public class PokemonGoAutomation {
private Robot robot;
private Rectangle windowBounds;
private BufferedImage t1Template; // TRANSFER button template
private BufferedImage t2Template; // Confirmation button template
private BufferedImage t3Template;
private BufferedImage pok1Template; // Pokemon detection template
private BufferedImage includeTemplate; // Include button template
private static final int DELAY_BETWEEN_CLICKS = 100; // ms
private static final int DELAY_AFTER_ACTION = 100; // ms
private int transferredPokemonCount = 0; // Počet transfernutých pokémonů
public PokemonGoAutomation() throws AWTException {
this.robot = new Robot();
this.robot.setAutoDelay(50);
this.robot.setAutoWaitForIdle(true);
loadButtonTemplates();
}
/**
* Vrátí počet dosud transfernutých pokémonů
*/
public int getTransferredCount() {
return transferredPokemonCount;
}
/**
* Resetuje počítadlo transfernutých pokémonů
*/
public void resetTransferredCount() {
transferredPokemonCount = 0;
}
/**
* Pořídí screenshot oblasti okna
*/
private BufferedImage captureScreen(Rectangle area) {
return robot.createScreenCapture(area);
}
/**
* Načte templaty pro tlačítka TRANSFER (t1.png) a confirmation (t2.png)
*/
private void loadButtonTemplates() {
try {
String t1Paths = "/t1.png";
t1Template = ImageIO.read(getClass().getResourceAsStream(t1Paths));
System.out.println("✅ Template TRANSFER načten z: " + t1Paths);
String t2Paths = "/t2.png";
t2Template = ImageIO.read(getClass().getResourceAsStream(t2Paths));
System.out.println("✅ Template Potvrzení načten z: " + t2Paths);
String t3Paths = "/t3.png";
t3Template = ImageIO.read(getClass().getResourceAsStream(t3Paths));
System.out.println("✅ Template Potvrzení načten z: " + t3Paths);
String pok1Paths = "/pok1.png";
pok1Template = ImageIO.read(getClass().getResourceAsStream(pok1Paths));
System.out.println("✅ Template Pokémon načten z: " + pok1Paths);
String includePaths = "/include.png";
includeTemplate = ImageIO.read(getClass().getResourceAsStream(includePaths));
System.out.println("✅ Template INCLUDE načten z: " + includePaths);
if (t1Template == null) {
System.out.println("⚠️ Template t1.png nebyl nalezen");
}
if (t2Template == null) {
System.out.println("⚠️ Template t2.png nebyl nalezen");
}
if (pok1Template == null) {
System.out.println("⚠️ Template pok1.png nebyl nalezen");
}
if (includeTemplate == null) {
System.out.println("⚠️ Template include.png nebyl nalezen");
}
} catch (IOException e) {
System.err.println("⚠️ Chyba při načítání templates: " + e.getMessage());
}
}
/**
* Porovnává template s oblastí v obrázku - jednoduché pixelové porovnání s
* optimalizací
*/
private double templateMatch(BufferedImage screenshot, int startX, int startY, BufferedImage template,
int tolerance) {
if (template == null)
return 0.0;
int tWidth = template.getWidth();
int tHeight = template.getHeight();
// Kontrola hranic
if (startX + tWidth > screenshot.getWidth() || startY + tHeight > screenshot.getHeight()) {
return 0.0;
}
if (startX < 0 || startY < 0) {
return 0.0;
}
int matchingPixels = 0;
int totalPixels = tWidth * tHeight;
int requiredMatch = (int) (totalPixels * 0.75); // 75% shody
for (int y = 0; y < tHeight; y++) {
for (int x = 0; x < tWidth; x++) {
int templateRGB = template.getRGB(x, y);
int screenRGB = screenshot.getRGB(startX + x, startY + y);
// Rozdělit RGB hodnoty
int tR = (templateRGB >> 16) & 0xFF;
int tG = (templateRGB >> 8) & 0xFF;
int tB = templateRGB & 0xFF;
int sR = (screenRGB >> 16) & 0xFF;
int sG = (screenRGB >> 8) & 0xFF;
int sB = screenRGB & 0xFF;
// Porovnat barvy - zvýšená tolerance pro lepší detekci
int rDiff = Math.abs(tR - sR);
int gDiff = Math.abs(tG - sG);
int bDiff = Math.abs(tB - sB);
if (rDiff < tolerance && gDiff < tolerance && bDiff < tolerance) {
matchingPixels++;
// Early exit - pokud jsme nedosáhli minima, skončit
if (matchingPixels < requiredMatch / 2 && (y * tWidth + x) > (totalPixels / 2)) {
return 0.0;
}
}
}
}
// Vrátit procentuální shodu (0.0-1.0)
return (double) matchingPixels / totalPixels;
}
/**
* Detekuje pozice Pokémonů pomocí template matchingu s pok1.png
* Hledá v oblasti od vrchu obrazovky do 75% výšky
* Může najít až 9 Pokémonů
*/
private List<Point> detectPokemonsByTemplateMatching2() {
List<Point> positions = new ArrayList<>();
System.out.println("\n=== Detekce Pokémonů pomocí template matchingu (pok1.png) ===");
if (pok1Template == null) {
System.out.println("⚠️ Template pok1.png není dostupný");
return positions;
}
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = pok1Template.getWidth();
int tHeight = pok1Template.getHeight();
System.out.println("Template velikost: " + tWidth + "x" + tHeight);
// Grid parametry - 3x3 (9 Pokémonů) - pevné pozice podle layoutu aplikace
int gridStartY = (int) (windowBounds.height * 0.15);
int gridEndY = (int) (windowBounds.height * 0.75);
int gridHeight = gridEndY - gridStartY;
int gridMargin = (int) (windowBounds.width * 0.05);
int gridStartX = gridMargin;
int gridWidth = windowBounds.width - (2 * gridMargin);
int columns = 3;
int rows = 3; // 3x3 grid = 9 Pokémonů
int columnWidth = gridWidth / columns;
int rowHeight = gridHeight / rows;
System.out.println("Hledám Pokémony v gridu 3x3 (9 pozic)");
// Pro každou buňku v gridu hledat template
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
int cellX = gridStartX + (col * columnWidth);
int cellY = gridStartY + (row * rowHeight);
double bestScore = 0.0;
int bestX = cellX + columnWidth / 2;
int bestY = cellY + rowHeight / 2;
// Skenovat buňku s jemností
for (int sy = cellY; sy < cellY + rowHeight - tHeight; sy += 3) {
for (int sx = cellX; sx < cellX + columnWidth - tWidth; sx += 3) {
double score = templateMatch(screenshot, sx, sy, pok1Template, 115); // Zvýšená tolerance
if (score > bestScore) {
bestScore = score;
bestX = sx + tWidth / 2;
bestY = sy + tHeight / 2;
}
}
}
// Pokud jsme našli match (skóre > 75%), považovat za Pokémona
if (bestScore > 0.98) {
int x = windowBounds.x + bestX;
int y = windowBounds.y + bestY;
positions.add(new Point(x, y));
System.out.println(" Detekován Pokémon na [" + row + "," + col + "] -> (" + x + ", " + y
+ ") skóre=" + String.format("%.1f", bestScore * 100) + "%");
}
}
}
System.out.println("Celkem detekováno: " + positions.size() + " Pokémonů");
System.out.println("=================================\n");
return positions;
}
/**
* Najde a vrátí pozici transferu tlačítka bez kliknutí
*/
private Point findTransferButtonPosition() {
System.out.println("Hledám tlačítko TRANSFER pomocí template matchingu (t1.png)...");
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = t1Template.getWidth();
int tHeight = t1Template.getHeight();
// Template je celý řádek - zkusíme matchovat od 75% do 95% (kde by měl být
// TRANSFER řádek)
int searchStartY = (int) (windowBounds.height * 0.75);
int searchEndY = (int) (windowBounds.height * 0.95);
double bestScore = 0.0;
int bestX = 0;
int bestY = 0;
// Skenovat Y-ové pozice s větším krokem - 15px místo 5px pro zrychlení
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
// Skenovat také X pozice pro nalezení středu shody
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
double score = templateMatch(screenshot, x, y, t1Template, 90);
if (score > bestScore) {
bestScore = score;
bestX = x;
bestY = y;
}
}
}
if (bestScore > 0.92) {
Point bestMatch = new Point(
windowBounds.x + bestX + (tWidth / 2),
windowBounds.y + bestY + (tHeight / 2));
System.out.println("✅ Nalezeno TRANSFER tlačítko (template match) na: " + bestMatch + " (shoda: "
+ String.format("%.1f", bestScore * 100) + "%)");
return bestMatch;
}
System.out.println("Template nenalezen");
return null;
}
/**
* Najde a vrátí pozici potvrzovacího tlačítka bez kliknutí
*/
private Point findConfirmTransferButtonPosition(BufferedImage template, int tolerance) {
System.out.println("Hledám potvrzovací TRANSFER tlačítko pomocí template matchingu...");
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = template.getWidth();
int tHeight = template.getHeight();
// Dialog je obvykle v horní polovině po TRANSFER kliknutí
int searchStartY = (int) (windowBounds.height * 0.40);
int searchEndY = (int) (windowBounds.height * 0.75);
double bestScore = 0.0;
int bestX = 0;
int bestY = 0;
// Skenovat Y-ové pozice s větším krokem - 15px místo 5px pro zrychlení
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
// Skenovat také X pozice pro nalezení středu shody
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
double score = templateMatch(screenshot, x, y, template, tolerance);
if (score > bestScore) {
bestScore = score;
bestX = x;
bestY = y;
}
}
}
if (bestScore > 0.85) {
Point bestMatch = new Point(
windowBounds.x + bestX + (tWidth / 2),
windowBounds.y + bestY + (tHeight / 2));
System.out.println("✅ Nalezeno potvrzovací tlačítko (template match) na: " + bestMatch + " (shoda: "
+ String.format("%.1f", bestScore * 100) + "%)");
return bestMatch;
}
System.out.println("Template nenalezen");
return null;
}
private Point findIncludeButtonPosition(int tolerance) {
System.out.println("Hledám tlačítko INCLUDE pomocí template matchingu (include.png)...");
BufferedImage screenshot = captureScreen(windowBounds);
int tWidth = includeTemplate.getWidth();
int tHeight = includeTemplate.getHeight();
// Dialog je obvykle v horní polovině po TRANSFER kliknutí
int searchStartY = (int) (windowBounds.height * 0.40);
int searchEndY = (int) (windowBounds.height * 0.75);
double bestScore = 0.0;
int bestX = 0;
int bestY = 0;
// Skenovat Y-ové pozice s větším krokem - 15px místo 5px pro zrychlení
for (int y = searchStartY; y <= searchEndY - tHeight; y += 15) {
// Skenovat také X pozice pro nalezení středu shody
for (int x = 0; x <= windowBounds.width - tWidth; x += 30) {
double score = templateMatch(screenshot, x, y, includeTemplate, tolerance);
if (score > bestScore) {
bestScore = score;
bestX = x;
bestY = y;
}
}
}
if (bestScore > 0.80) {
Point bestMatch = new Point(
windowBounds.x + bestX + (tWidth / 2),
windowBounds.y + bestY + (tHeight / 2));
System.out.println("✅ Nalezeno tlačítko INCLUDE (template match) na: " + bestMatch + " (shoda: "
+ String.format("%.1f", bestScore * 100) + "%)");
return bestMatch;
}
System.out.println("Template nenalezen");
return null;
}
/**
* Najde okno s názvem obsahujícím "Pokémon" nebo "Pokemon"
*/
public boolean findPokemonGoWindow() {
try {
// Pro Linux - hledání okna pomocí xdotool
// Nejdříve najít ID okna, pak získat jeho geometrii
Process searchProcess = Runtime.getRuntime().exec(
new String[] { "bash", "-c", "xdotool search --name \"Lenovo|L78032\" | head -1" });
java.io.BufferedReader searchReader = new java.io.BufferedReader(
new java.io.InputStreamReader(searchProcess.getInputStream()));
String windowId = searchReader.readLine();
searchProcess.waitFor();
if (windowId != null && !windowId.trim().isEmpty()) {
System.out.println("Nalezeno okno s ID: " + windowId);
// Získat geometrii okna
Process geomProcess = Runtime.getRuntime().exec(
new String[] { "bash", "-c", "xdotool getwindowgeometry " + windowId.trim() });
java.io.BufferedReader geomReader = new java.io.BufferedReader(
new java.io.InputStreamReader(geomProcess.getInputStream()));
String line;
int x = 0, y = 0, width = 0, height = 0;
while ((line = geomReader.readLine()) != null) {
System.out.println("xdotool výstup: " + line);
if (line.contains("Position:")) {
String[] parts = line.split("Position: ")[1].split(",");
x = Integer.parseInt(parts[0].trim());
y = Integer.parseInt(parts[1].trim().split(" ")[0]);
} else if (line.contains("Geometry:")) {
String[] parts = line.split("Geometry: ")[1].split("x");
width = Integer.parseInt(parts[0].trim());
height = Integer.parseInt(parts[1].trim());
}
}
geomProcess.waitFor();
if (width > 0 && height > 0) {
windowBounds = new Rectangle(x, y, width, height);
System.out.println("Nalezeno okno: " + windowBounds);
return true;
}
}
// Fallback - použití celé obrazovky
System.out.println("Okno nenalezeno, použiji celou obrazovku");
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
windowBounds = gd.getDefaultConfiguration().getBounds();
return true;
} catch (Exception e) {
System.err.println("Chyba při hledání okna: " + e.getMessage());
e.printStackTrace();
// Použití celé obrazovky jako záložní varianta
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
windowBounds = gd.getDefaultConfiguration().getBounds();
return true;
}
}
/**
* Aktivuje okno aplikace
*/
public void activateWindow() {
try {
// Kliknutí na horní panel okna (title bar) pro aktivaci
// Title bar je obvykle 30-40px vysoký v horní části okna
int centerX = windowBounds.x + windowBounds.width / 2;
int titleBarY = windowBounds.y + 15; // 15px od vrcholu okna = horní panel
System.out.println("Aktivuji okno kliknutím na title bar: (" + centerX + ", " + titleBarY + ")");
robot.mouseMove(centerX, titleBarY);
robot.delay(200);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(DELAY_AFTER_ACTION);
System.out.println("Okno aktivováno");
} catch (Exception e) {
System.err.println("Chyba při aktivaci okna: " + e.getMessage());
}
}
/**
* Stiskne tlačítko Transfer na spodní části obrazovky
*/
public void clickTransferButton() {
// Použít detekci tlačítka
// Point buttonPos = findTransferButtonPosition();
Point buttonPos = getAbsolutePoint(307, 1100);
if (buttonPos == null) {
System.err.println("Tlačítko TRANSFER nebylo nalezeno!");
return;
}
robot.mouseMove(buttonPos.x, buttonPos.y);
robot.delay(DELAY_AFTER_ACTION);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
System.out.println("Tlačítko TRANSFER stisknuto!");
robot.delay(DELAY_AFTER_ACTION);
}
/**
* Potvrdí transfer (pokud je potřeba potvrzovací dialog)
*/
public void clickConfirmTransferButton(BufferedImage template, int tolerance) {
System.out.println("Potvrzuji transfer - hledám confirmation button...");
robot.delay(100); // Počkat na zobrazení dialogu
// Najít zelené TRANSFER tlačítko v potvrzovacím dialogu
Point buttonPos = findConfirmTransferButtonPosition(template, tolerance);
if (buttonPos == null) {
System.out.println("Potvrzovací tlačítko nebylo nalezeno, přeskočím potvrzení.");
return;
}
robot.mouseMove(buttonPos.x, buttonPos.y);
robot.delay(DELAY_BETWEEN_CLICKS);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
System.out.println("Transfer finálně potvrzen!");
robot.delay(DELAY_AFTER_ACTION);
}
public boolean clickIncludeButton(int tolerance) {
System.out.println("Potvrzuji transfer - hledám include.png (confirmation button)...");
robot.delay(100); // Počkat na zobrazení dialogu
// Najít zelené INCLUDE tlačítko v potvrzovacím dialogu
Point buttonPos = findIncludeButtonPosition(tolerance);
if (buttonPos == null) {
System.out.println("INCLUDE tlačítko nebylo nalezeno, přeskočím potvrzení.");
return false;
}
robot.mouseMove(buttonPos.x, buttonPos.y);
robot.delay(DELAY_BETWEEN_CLICKS);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
System.out.println("Transfer finálně potvrzen!");
robot.delay(DELAY_AFTER_ACTION);
return true;
}
/**
* Spustí Transfer automatizaci s konkrétním počtem pokémonů
*
* @param totalPokemonCount Celkový počet pokémonů k transferu
* @param delaySeconds Čekání mezi iteracemi (v sekundách)
*/
public void runWithCount(int totalPokemonCount, int delaySeconds) {
System.out.println("Spouštím Transfer automatizaci - Počet pokémonů: " + totalPokemonCount + ", Čekání: "
+ delaySeconds + "s");
if (!findPokemonGoWindow()) {
System.err.println("Nepodařilo se najít okno Pokémon GO!");
return;
}
activateWindow();
System.out.println("Čekám 1 sekundu před začátkem...");
robot.delay(1000);
int transferredCount = 0;
// Transferovat dokud nedosáhneme požadovaného počtu
while (transferredCount < totalPokemonCount) {
// Kontrola přerušení
if (Thread.currentThread().isInterrupted()) {
System.out.println("\n⚠ Automatizace byla přerušena!");
return;
}
int pokemonThisRound = Math.min(12, totalPokemonCount - transferredCount);
System.out.println(
"\n=== Iterace " + (transferredCount / 9 + 1) + " - Transferuji maximálně " + pokemonThisRound
+ " pokémonů ===");
// Vybrat pokémony a získat skutečný počet vybraných
int actualTransferredCount = selectAllPokemonCount(pokemonThisRound);
System.out.println("Čekám na TRANSFER...");
clickTransferButton();
System.out.println("Čekám na INCLUDE dialog...");
robot.delay(100);
if (clickIncludeButton(110)) {
System.out.println("Čekám na potvrzovací dialog t3...");
robot.delay(100);
clickConfirmTransferButton(t3Template, 70);
} else {
System.out.println("Čekám na potvrzovací dialog t2...");
robot.delay(100);
clickConfirmTransferButton(t2Template, 70);
}
transferredCount += actualTransferredCount;
transferredPokemonCount += actualTransferredCount;
System.out.println("Transferováno v této iteraci: " + actualTransferredCount + " pokémonů (celkem: "
+ transferredCount + ")");
// Pokud ještě zbývá co transferovat, počkat
if (transferredCount < totalPokemonCount) {
System.out.println("Přestávka: " + delaySeconds + " sekund...");
// Čekat s kontrolou přerušení
long endTime = System.currentTimeMillis() + (delaySeconds * 1000L);
while (System.currentTimeMillis() < endTime) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("\n⚠ Automatizace byla přerušena během přestávky!");
return;
}
robot.delay(100);
}
}
}
System.out.println("\n✅ Transfer automatizace dokončena! Transferováno: " + transferredCount + " pokémonů");
}
/**
* Označí konkrétní počet pokémonů
*
* @return Skutečný počet vybraných pokémonů
*/
private int selectAllPokemonCount(int count) {
System.out.println("Začínám označovat " + count + " pokémonů (pouze template matching)...");
List<Point> positions = getPossitionsAbsolute();
int maxPokemon = Math.min(count, positions.size());
System.out.println("Budu označovat " + maxPokemon + " pokémonů...");
for (int i = 0; i < maxPokemon; i++) {
Point pos = positions.get(i);
System.out.println("Klikám na Pokémona " + (i + 1) + "/" + maxPokemon + " na pozici: " + pos);
robot.mouseMove(pos.x, pos.y);
if (i == 0) {
System.out.println(" -> Dlouhé podržení (aktivace multi-select)");
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(700);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
robot.delay(DELAY_AFTER_ACTION);
} else {
System.out.println(" -> Normální klik");
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
robot.delay(DELAY_BETWEEN_CLICKS);
}
System.out.println("Označeno " + maxPokemon + " pokémonů!");
System.out.println("Čekám před kliknutím na TRANSFER...");
robot.delay(800);
return maxPokemon;
}
private List<Point> getPossitionsAbsolute() {
List<Point> possitions = new ArrayList<>();
possitions.add(getAbsolutePoint(115, 240));
possitions.add(getAbsolutePoint(307, 240));
possitions.add(getAbsolutePoint(496, 240));
possitions.add(getAbsolutePoint(115, 460));
possitions.add(getAbsolutePoint(307, 460));
possitions.add(getAbsolutePoint(496, 460));
possitions.add(getAbsolutePoint(115, 680));
possitions.add(getAbsolutePoint(307, 680));
possitions.add(getAbsolutePoint(496, 680));
possitions.add(getAbsolutePoint(115, 900));
possitions.add(getAbsolutePoint(307, 900));
possitions.add(getAbsolutePoint(496, 900));
return possitions;
}
/**
* Převede relativní pozici v okně (0.0-1.0) na absolutní souřadnice obrazovky
*
* @param relativeX Relativní X pozice (0.0 = levý okraj, 1.0 = pravý okraj)
* @param relativeY Relativní Y pozice (0.0 = horní okraj, 1.0 = dolní okraj)
* @return Absolutní pozice na obrazovce
*/
private Point getAbsolutePoint(int relativeX, int relativeY) {
int absoluteX = windowBounds.x + relativeX;
int absoluteY = windowBounds.y + relativeY;
return new Point(absoluteX, absoluteY);
}
public static void main(String[] args) {
System.out.println("=== Pokémon GO Automatizační Nástroj ===");
System.out.println("Ujistěte se, že je aplikace Pokémon GO spuštěná...");
}
}