package it.paspiz85.nanobot.platform;
import it.paspiz85.nanobot.exception.BotConfigurationException;
import it.paspiz85.nanobot.util.Area;
import it.paspiz85.nanobot.util.Pixel;
import it.paspiz85.nanobot.util.Point;
import it.paspiz85.nanobot.util.Size;
import it.paspiz85.nanobot.util.Utils;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
* Abstract implementation of {@link Platform}.
*
* @author paspiz85
*
*/
public abstract class AbstractPlatform implements Platform {
private static final Area FULLSCREEN = Area.bySize(new Point(0, 0), GAME_SIZE);
private static final String IMG_FOLDER = "img";
private static final Object WAIT_FOR_CLICK_SYNC = new Object();
protected final Logger logger = Logger.getLogger(getClass().getName());
@Override
public final boolean compareColor(final Color c1, final Color c2, final int var) {
return compareColor(c1.getRGB(), c2.getRGB(), var);
}
private boolean compareColor(final int c1, final int c2, final int var) {
final int r1 = c1 >> 16 & 0xFF;
final int r2 = c2 >> 16 & 0xFF;
final int g1 = c1 >> 8 & 0xFF;
final int g2 = c2 >> 8 & 0xFF;
final int b1 = c1 >> 0 & 0xFF;
final int b2 = c2 >> 0 & 0xFF;
return !(Math.abs(r1 - r2) > var || Math.abs(g1 - g2) > var || Math.abs(b1 - b2) > var);
}
protected void doActivate() {
}
protected abstract void doApplySize(Size size) throws BotConfigurationException;
protected abstract void doKeyPress(final int keyCode, final boolean shifted) throws InterruptedException;
/**
* Do a left click in the game.
*
* @param point
* coordinates of click.
* @throws InterruptedException
*/
protected abstract void doLeftClick(final Point point) throws InterruptedException;
/**
* Pick a screenshot from game.
*
* @param p1
* top left corner of screenshot.
* @param p2
* bottom right corner of screenshot.
* @return image containing the screenshot.
*/
protected abstract BufferedImage doScreenshot(Point p1, Point p2);
protected abstract void doSingleZoomUp() throws InterruptedException;
protected abstract void doWrite(String s) throws InterruptedException;
protected abstract Size getActualSize();
/**
* Given a point return the color of pixel.
*
* @param point
* coordinates of pixel.
* @return color of a pixel.
*/
protected abstract Color getColor(Point point);
@Override
public final BufferedImage getSubimage(final BufferedImage image, final Area area) {
return getSubimage(image, area.getEdge1(), area.getEdge2());
}
@Override
public final BufferedImage getSubimage(final BufferedImage image, final Point p1, final Point p2) {
final int x1 = p1.x();
final int y1 = p1.y();
final int x2 = p2.x();
final int y2 = p2.y();
return image.getSubimage(x1, y1, x2 - x1, y2 - y1);
}
@Override
public final void init() throws BotConfigurationException {
init(() -> false);
}
@Override
public final void init(final BooleanSupplier autoAdjustResolution) throws BotConfigurationException {
doActivate();
logger.log(Level.CONFIG, "Setting up platform...");
setup();
setupResolution(autoAdjustResolution);
logger.log(Level.CONFIG, "Setup is successful");
}
@Override
public final void keyPress(final int keyCode, final boolean shifted) throws InterruptedException {
doActivate();
doKeyPress(keyCode, shifted);
}
@Override
public final void leftClick(final Point point) throws InterruptedException {
leftClick(point, true);
}
@Override
public final void leftClick(final Point point, final boolean randomize) throws InterruptedException {
if (point == null) {
throw new IllegalArgumentException("point not provided");
}
doActivate();
Point p = point;
logger.log(Level.FINER, "Clicking " + p);
// randomize coordinates little bit
if (randomize) {
p = new Point(p.x() - 1 + Utils.RANDOM.nextInt(3), p.y() - 1 + Utils.RANDOM.nextInt(3));
}
doLeftClick(p);
}
@Override
public boolean matchColoredPoint(final Pixel point) {
if (point == null) {
throw new IllegalArgumentException("point not provided");
}
final Color actualColor = getColor(point);
final boolean result = compareColor(point.getColor(), actualColor, 5);
return result;
}
/**
* Register for a click on the game.
*
* @param clickConsumer
* listener that receive the click.
* @return true if registration is OK, false otherwise.
*/
protected abstract boolean registerForClick(Consumer<Point> clickConsumer);
@Override
public File saveImage(final BufferedImage img, final String... filePathRest) {
File result = null;
try {
result = saveImageInternal(img, IMG_FOLDER, filePathRest);
} catch (final IOException e1) {
logger.log(Level.SEVERE, e1.getMessage(), e1);
}
return result;
}
private File saveImageInternal(final BufferedImage img, final String filePathFirst, final String... filePathRest)
throws IOException {
final Path path = Paths.get(filePathFirst, filePathRest).toAbsolutePath();
String fileName = path.getFileName().toString();
if (!path.getFileName().toString().toLowerCase().endsWith(".png")) {
fileName = path.getFileName().toString() + ".png";
}
final File file = new File(path.getParent().toString(), fileName);
if (!file.getParentFile().isDirectory()) {
file.getParentFile().mkdirs();
}
logger.log(Level.FINE, "Saving image at " + file.getAbsolutePath());
ImageIO.write(img, "png", file);
logger.log(Level.INFO, "Saved image at " + file.getAbsolutePath());
return file;
}
@Override
public final File saveScreenshot(final Area area, final String... filePathRest) {
final BufferedImage img = screenshot(area);
return saveImage(img, filePathRest);
}
@Override
public final File saveScreenshot(final String... filePathRest) {
final BufferedImage img = screenshot();
return saveImage(img, filePathRest);
}
@Override
public final BufferedImage screenshot() {
return screenshot(null);
}
@Override
public final BufferedImage screenshot(final Area area) {
doActivate();
BufferedImage result;
if (area == null) {
result = doScreenshot(FULLSCREEN.getEdge1(), FULLSCREEN.getEdge2());
} else {
result = doScreenshot(area.getEdge1(), area.getEdge2());
}
return result;
}
protected abstract void setup() throws BotConfigurationException;
private void setupResolution(final BooleanSupplier autoAdjustResolution) throws BotConfigurationException {
logger.log(Level.INFO, "Checking plaftorm resolution...");
try {
final Size bsActualSize = getActualSize();
final Size bsExpectedSize = getExpectedSize();
if (!bsExpectedSize.equals(bsActualSize)) {
logger.warning(String.format("Platform resolution is %s", bsActualSize.toString()));
if (!autoAdjustResolution.getAsBoolean()) {
throw new BotConfigurationException("Re-run when resolution is fixed");
}
doApplySize(bsExpectedSize);
}
} catch (final BotConfigurationException e) {
throw e;
} catch (final Exception e) {
throw new BotConfigurationException("Unable to change resolution. Do it manually", e);
}
}
@Override
public final void sleepRandom(final int sleepInMs) throws InterruptedException {
final int time = sleepInMs + Utils.RANDOM.nextInt(sleepInMs);
logger.log(Level.FINER, "Sleeping for " + time + " ms");
Thread.sleep(time);
}
@Override
public final Point waitForClick() throws InterruptedException {
final Point[] result = new Point[1];
final boolean[] done = new boolean[1];
final boolean registered = registerForClick((point) -> {
result[0] = point;
done[0] = true;
synchronized (WAIT_FOR_CLICK_SYNC) {
WAIT_FOR_CLICK_SYNC.notify();
}
});
if (registered) {
logger.log(Level.INFO, "Waiting for user to click");
synchronized (WAIT_FOR_CLICK_SYNC) {
while (!done[0]) {
WAIT_FOR_CLICK_SYNC.wait();
}
}
}
return result[0];
}
@Override
public final void write(final String s) throws InterruptedException {
doActivate();
doWrite(s);
}
@Override
public final void zoomUp() throws InterruptedException {
doActivate();
logger.log(Level.CONFIG, "Zooming out...");
final int notch = 14;
for (int i = 0; i < notch; i++) {
doSingleZoomUp();
Thread.sleep(1000);
}
}
}