diff --git a/main/BotFloodIt.java b/main/BotFloodIt.java index 499246d..5074182 100644 --- a/main/BotFloodIt.java +++ b/main/BotFloodIt.java @@ -3,19 +3,19 @@ package main; import java.util.ArrayList; /** - * . + * Класс логики бота. * @author aNNiMON */ public class BotFloodIt { - /* */ + /* Количество цветов в игре */ private static final int MAX_COLORS = 6; - /* */ + /* На сколько шагов вперёд просчитывать ход */ private static final int FILL_STEPS = 4; - /* */ + /* Игровое поле */ private byte[][] table; - /* , ID */ + /* Цвета, соответствующие ID */ private int[] colors; public BotFloodIt(int[][] table) { @@ -27,16 +27,16 @@ public class BotFloodIt { } /** - * - * @return RGB + * Получить цвета клеток в палитре + * @return массив цветов RGB */ public int[] getColors() { return colors; } /** - * - * @return + * Получить последовательность заливки цветов + * @return массив с идентификаторами цветов для заливки */ public byte[] getFillSequence() { byte[][] copyTable = copyTable(table); @@ -52,28 +52,28 @@ public class BotFloodIt { } /* - * + * Получить индекс следующего цвета для заливки */ private byte getNextFillColor(byte[][] table) { - // + // Количество вариантов заливок int fillSize = (int) Math.pow(MAX_COLORS, FILL_STEPS); int[] fillRate = new int[fillSize]; - // + // Заполняем значениями степени заливки int[] fillPow = new int[FILL_STEPS]; for (int i = 0; i < FILL_STEPS; i++) { fillPow[i] = (int) Math.pow(MAX_COLORS, i); } - // FILL_STEPS MAX_COLORS + // Заливаем FILL_STEPS раз MAX_COLORS вариантов for (int i = 0; i < fillSize; i++) { byte[][] iteration = copyTable(table); for (int j = 0; j < FILL_STEPS; j++) { byte fillColor = (byte) (i / fillPow[j] % MAX_COLORS); fillTable(iteration, fillColor); } - // + // Подсчитываем число залитых ячеек fillRate[i] = getFillCount(iteration); } - // FILL_STEPS + // Теперь ищем максимально залитый участок из FILL_STEPS итераций заливки int maxArea = fillRate[0]; int maxColor = 0; for (int i = 1; i < fillSize; i++) { @@ -82,30 +82,30 @@ public class BotFloodIt { maxArea = fillRate[i]; } } - // + // Получаем цвет с наибольшей площадью дальнейшей заливки byte colorID = (byte) (maxColor % MAX_COLORS); fillTable(table, colorID); return colorID; } /* - * + * Преобразование массива с цветами в массив с идентификаторами */ private byte[][] colorsToIds(int[][] tableColor) { int size = tableColor.length; byte[][] out = new byte[size][size]; - int colorsReaded = 1; // + int colorsReaded = 1; // сколько цветов распознано for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { int color = tableColor[i][j]; for (byte k = 0; k < colorsReaded; k++) { - // + // Добавляем цвет в палитру if (colors[k] == -1) { colors[k] = color; colorsReaded++; if (colorsReaded > MAX_COLORS) colorsReaded = MAX_COLORS; } - // , ID + // Если цвет уже в палитре, то присваиваем ему ID if (color == colors[k]) { out[i][j] = k; break; @@ -117,9 +117,9 @@ public class BotFloodIt { } /** - * color - * @param table - * @param color + * Залить заданное поле цветом color + * @param table игровое поле для заливки + * @param color цвет заливки */ private void fillTable(byte[][] table, byte color) { if (table[0][0] == color) return; @@ -127,14 +127,14 @@ public class BotFloodIt { } /* - * + * Заливка поля по координатам */ private void fill(byte[][] table, int x, int y, byte prevColor, byte color) { - // + // Проверка на выход за границы игрового поля if ( (x < 0) || (y < 0) || (x >= table.length) || (y >= table.length) ) return; if (table[x][y] == prevColor) { table[x][y] = color; - // + // Заливаем смежные области fill(table, x-1, y, prevColor, color); fill(table, x+1, y, prevColor, color); fill(table, x, y-1, prevColor, color); @@ -143,24 +143,24 @@ public class BotFloodIt { } /** - * - * @param table + * Получить количество залитых ячеек + * @param table игровое поле */ private int getFillCount(byte[][] table) { return getCount(table, 0, 0, table[0][0]); } /* - * + * Подсчет залитых ячеек по координатам */ private int getCount(byte[][] table, int x, int y, byte color) { - // + // Проверка на выход за границы игрового поля if ( (x < 0) || (y < 0) || (x >= table.length) || (y >= table.length) ) return 0; int count = 0; if (table[x][y] == color) { table[x][y] = -1; count = 1; - // + // Считаем смежные ячейки count += getCount(table, x-1, y, color); count += getCount(table, x+1, y, color); count += getCount(table, x, y-1, color); @@ -170,7 +170,7 @@ public class BotFloodIt { } /* - * , + * Проверка, залита ли вся область одним цветом */ private boolean gameCompleted(byte[][] table) { byte color = table[0][0]; @@ -184,7 +184,7 @@ public class BotFloodIt { } /* - * + * Копирование массива игрового поля */ private byte[][] copyTable(byte[][] table) { int size = table.length; diff --git a/main/ImageUtils.java b/main/ImageUtils.java index 82902ad..9bd5ff8 100644 --- a/main/ImageUtils.java +++ b/main/ImageUtils.java @@ -5,32 +5,32 @@ import java.awt.image.BufferedImage; import java.util.Random; /** - * . + * Класс обработки изображений * @author aNNiMON */ public class ImageUtils { - /* */ + /* За сколько точек мы будем узнавать преобладающий фон */ private static final int MAX_COLOR_POINTS = 50; - /* */ + /* Чувствительность к поиску кнопок */ private static final int FIND_BUTTON_TOLERANCE = 20; - /* */ + /* Изображение окна */ private BufferedImage image; - /* */ + /* Размер изображения */ private int w, h; - /* */ + /* Размерность поля */ private int boardSize; - /* */ + /* Размер ячеек */ private int cellSize; - /* */ + /* Координата угла игрового поля */ private Point board; - /* */ + /* Монохромное представление изображения */ private boolean[] monochrome; /** - * + * Конструктор для определения настроек * @param image * @param boardSize */ @@ -42,7 +42,7 @@ public class ImageUtils { } /** - * + * Конструктор для проверки настроек * @param image * @param boardSize * @param cellSize @@ -59,7 +59,7 @@ public class ImageUtils { } /** - * + * Получить размер ячейки * @return */ public int getCellSize() { @@ -67,8 +67,8 @@ public class ImageUtils { } /** - * - * @return + * Получить координаты игрового поля + * @return точка с координатами левого верхнего угла поля */ public Point getBoardParameters() { int[] pixels = new int[w * h]; @@ -79,8 +79,8 @@ public class ImageUtils { } /** - * - * @return + * Получить изображение игрового поля + * @return картинка игрового поля */ public BufferedImage getBoardImage() { int size = cellSize * boardSize; @@ -92,34 +92,34 @@ public class ImageUtils { } /** - * - * @param colors , - * @return , null - + * Получить координаты кнопок для автоматического нажатия + * @param colors массив цветов, по которым будем искать кнопки + * @return массив координат с точками, или null - если не удалось найти */ public Point[] getButtons(int[] colors) { Point[] out = new Point[colors.length]; - // + // Размер игрового поля в пикселах int size = boardSize * cellSize; - // , + // Размеры частей изображения, на которых будем искать кнопки Rectangle[] partsOfImage = new Rectangle[] { - new Rectangle(0, board.y, board.x, size), // - new Rectangle(0, 0, w, board.y), // + new Rectangle(0, board.y, board.x, size), // слева от поля + new Rectangle(0, 0, w, board.y), // сверху от поля new Rectangle(board.x+size, board.y, - w-board.x-size, size), // + w-board.x-size, size), // справа от поля new Rectangle(0, board.y+size, - w, h-board.x-size) // + w, h-board.x-size) // снизу от поля }; for (int i = 0; i < partsOfImage.length; i++) { Rectangle rect = partsOfImage[i]; BufferedImage part = image.getSubimage(rect.x, rect.y, rect.width, rect.height); - // , + // Вырезаем часть изображения, в котором будем искать boolean found = true; for (int j = 0; j < colors.length; j++) { if (colors[i] == -1) continue; Point pt = findButton(part, colors[j]); if (pt != null) { - // + // Учитываем смещения относительно частей картинок pt.translate(rect.x, rect.y); out[j] = pt; } else { @@ -129,19 +129,19 @@ public class ImageUtils { } if (found) return out; } - // + // Не удалось найти все точки return null; } /** - * - * @param ids - * @param palette - * @return + * Преобразовать массив цветов в графический вид + * @param ids массив идентификаторов последовательности + * @param palette массив палитры цветов + * @return изображение последовательности цветов */ public BufferedImage sequenceToImage(byte[] ids, int[] palette) { - final int size = 20; // - // 10 + final int size = 20; // размер каждой ячейки + // Разбивать будем по 10 клеток на строку final int CELLS_IN_ROW = 10; int width = CELLS_IN_ROW * size; if (width == 0) width = size; @@ -160,13 +160,13 @@ public class ImageUtils { } /** - * . - * , - * , , - * . - * @param pixels - * @param value - * @return boolean, true - , false - + * Преобразовать цветное изображение в монохромное. + * Нужно также учесть, что если поле расположено на светлом + * фоне, то необходимо инвертировать изображение, чтобы + * получить сплошную белую область на месте поля. + * @param pixels массив пикселей изображения + * @param value разделяющее значение + * @return массив boolean, true - белый, false - чёрный */ private boolean[] threshold(int[] pixels, int value) { boolean inverse = isBackgroundLight(MAX_COLOR_POINTS); @@ -180,12 +180,12 @@ public class ImageUtils { } /** - * . - * @param numPoints . - * @return true - , false - + * Получение состояния яркости фона. + * @param numPoints сколько точек нужно для определения. + * @return true - фон светлый, false - тёмный */ private boolean isBackgroundLight(int numPoints) { - // numPoints + // Получаем numPoints случайных точек Random rnd = new Random(); int[] colors = new int[numPoints]; for (int i = 0; i < numPoints; i++) { @@ -193,7 +193,7 @@ public class ImageUtils { int y = rnd.nextInt(h); colors[i] = image.getRGB(x, y); } - // numPoints + // Находим среднюю яркость всех numPoints точек long sum = 0; for (int i = 0; i < numPoints; i++) { int brightness = getBrightness(colors[i]); @@ -204,13 +204,13 @@ public class ImageUtils { } /** - * . - * @param boardSize (10x10, 14x14 ..) - * @return + * Определить координаты левой верхней ячейки игрового поля. + * @param boardSize размерность поля (10x10, 14x14 и т.д.) + * @return координата левого верхнего прямоугольника */ private Point getBoardXY(int boardSize) { /* - * + * Сначала подсчитаем количество белых точек по горизонтали и вертикали */ int[] horizontal = new int[h]; for (int i = 0; i < h; i++) { @@ -231,15 +231,15 @@ public class ImageUtils { } /* - * "" : - * . + * Затем "отфильтруем" лишнее: подсчитаем среднее значение + * и на его основе уберём малозначимые строки и столбцы. */ horizontal = filterByMean(horizontal); vertical = filterByMean(vertical); /* - * . - * . + * Ищем наибольшую ненулевую последовательность. + * Индексы границ последовательности и будут граничными точками поля. */ int[] vParam = getParamsFromSequence(horizontal); int[] hParam = getParamsFromSequence(vertical); @@ -249,15 +249,15 @@ public class ImageUtils { int outY = vParam[0]; int outWidth = hParam[1]; int outHeight = vParam[1]; - // + // Подсчет размера ячейки cellSize = Math.max((outWidth / boardSize), (outHeight / boardSize)); return new Point(outX, outY); } /** - * . - * @param source - * @return 0 1 + * Фильтр последовательности от малозначимых значений. + * @param source последовательность вхождений цвета + * @return отфильтрованный массив со значениями 0 и 1 */ private int[] filterByMean(int[] source) { long mean = 0; @@ -272,9 +272,9 @@ public class ImageUtils { } /** - * . - * @param source - * @return - + * Поиск самой длинной последовательности в массиве. + * @param source входная последовательность из нулей и единиц + * @return массив параметров - индекс начала последовательности и её длина */ private int[] getParamsFromSequence(int[] source) { int maxStart = 0, start = 0; @@ -292,7 +292,7 @@ public class ImageUtils { maxLength = length; } } else { - // - + // Если предыдущий элемент был нулевым - начинаем новую последовательность start = i; } } @@ -300,14 +300,14 @@ public class ImageUtils { } /** - * template - * @param img , - * @param template - * @return X. Y, null + * Поиск координаты кнопки с цветом template + * @param img изображение, на котором будем искать + * @param template шаблон цвета + * @return координата X. Y, или null если не нашли */ private Point findButton(BufferedImage img, int template) { int h2 = img.getHeight() / 2; - // , + // Искать будем с середины по вертикали, так быстрее найдём for (int y = 0; y < h2; y++) { for (int x = 0; x < img.getWidth(); x++) { int color = img.getRGB(x, h2 - y); @@ -320,16 +320,16 @@ public class ImageUtils { } } } - // + // Не нашли return null; } /** - * - * @param color1 - * @param color2 - * @param tolerance - * @return true - , false - + * Проверка на соответствие цветов друг другу + * @param color1 первый цвет + * @param color2 второй цвет + * @param tolerance чувствительность + * @return true - соответствуют, false - нет */ private boolean isEquals(int color1, int color2, int tolerance) { if (tolerance < 2) return color1 == color2; @@ -346,9 +346,9 @@ public class ImageUtils { } /** - * - * @param color - * @return (0..255) + * Получение яркости цвета + * @param color исходный цвет + * @return яркость (0..255) */ private int getBrightness(int color) { int qr = (color >> 16) & 0xff; @@ -358,8 +358,8 @@ public class ImageUtils { } /* - * . - * return true - , false - + * Получение цвета из монохромного изображения. + * return true - белый, false - чёрный */ private boolean getBWPixel(int x, int y) { if ((x < 0) || (y < 0) || (x >= w) || (y >= h)) return false; diff --git a/main/RobotFrame.java b/main/RobotFrame.java index 49363ee..1adc0a9 100644 --- a/main/RobotFrame.java +++ b/main/RobotFrame.java @@ -6,12 +6,12 @@ import java.awt.image.BufferedImage; import javax.swing.*; /** - * . + * Окно приложения * @author aNNiMON */ public class RobotFrame extends JFrame { - /* */ + /* Статус работы приложения */ private boolean isRunning; private Thread robotAction; @@ -201,7 +201,7 @@ public class RobotFrame extends JFrame { int windowX, windowY, boardSize, cellSize; BufferedImage detectImage; RobotUtils robot; - // + // Получаем настройки try { robot = new RobotUtils(); detectImage = robot.getImage(-1, -1, -1, -1); @@ -214,9 +214,9 @@ public class RobotFrame extends JFrame { return; } ImageUtils iu = new ImageUtils(detectImage, boardSize, cellSize, windowX, windowY); - // + // Обрезаем картинку до вида игрового поля detectImage = iu.getBoardImage(); - // + // Получаем цвета из картинки int[][] table = new int[boardSize][boardSize]; int offset = cellSize / 2; for (int i = 0; i < boardSize; i++) { @@ -226,17 +226,17 @@ public class RobotFrame extends JFrame { } } BotFloodIt bfi = new BotFloodIt(table); - // + // Получаем результирующую последовательность цветов byte[] result = bfi.getFillSequence(); int[] colors = bfi.getColors(); - // + // Пытаемся получить координаты кнопок для автоматической игры Point[] buttons = iu.getButtons(colors); if (buttons == null) { - // , + // Если не удалось найти кнопки, то просто выводим последовательность в виде картинки BufferedImage out = iu.sequenceToImage(result, colors); showImageWindow("Result: "+result.length+" steps", out); } else { - // + // Запускаем автоигру robot.autoClick(buttons, result); } isRunning = false; @@ -249,7 +249,7 @@ public class RobotFrame extends JFrame { }//GEN-LAST:event_startStopActionPerformed private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed - if (isRunning) return; // + if (isRunning) return; // нельзя проверять настройки во время работы int windowX, windowY, boardSize, cellSize; BufferedImage detectImage; RobotUtils robot; @@ -269,7 +269,7 @@ public class RobotFrame extends JFrame { }//GEN-LAST:event_checkButtonActionPerformed private void detectButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_detectButtonActionPerformed - if (isRunning) return; // + if (isRunning) return; // нельзя определять настройки во время работы RobotUtils robot; try { robot = new RobotUtils(); @@ -287,9 +287,9 @@ public class RobotFrame extends JFrame { }//GEN-LAST:event_detectButtonActionPerformed /** - * - * @param title - * @param image + * Показать модальное окно с изображением + * @param title заголовок окна + * @param image изображение * @throws SecurityException */ private void showImageWindow(String title, BufferedImage image) throws SecurityException { diff --git a/main/RobotUtils.java b/main/RobotUtils.java index b11833d..39ad367 100644 --- a/main/RobotUtils.java +++ b/main/RobotUtils.java @@ -6,7 +6,7 @@ import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; /** - * Robot. + * Работа с классом Robot. * @author aNNiMON */ public class RobotUtils { @@ -15,16 +15,16 @@ public class RobotUtils { private Robot robot; /** - * - * @throws AWTException Robot + * Конструктор + * @throws AWTException ошибка инициализации Robot */ public RobotUtils() throws AWTException { robot = new Robot(); } /** - * - * @param click + * Кликнуть в нужную точку + * @param click точка по которой нужно кликнуть */ public void clickPoint(Point click) { robot.mouseMove(click.x, click.y); @@ -34,9 +34,9 @@ public class RobotUtils { } /** - * - * @param buttons , - * @param result id + * Автоматически воспроизвести заданную последовательность нажатий + * @param buttons координаты точек, куда следует нажимать + * @param result последовательность id для указания на нужную кнопку */ public void autoClick(Point[] buttons, byte[] result) { for (int i = 0; i < result.length; i++) { @@ -44,8 +44,8 @@ public class RobotUtils { } } /** - * - * @param text "" + * Автоматическое написание сообщения + * @param text "печатаемый" текст */ public void writeMessage(String text) { for (char symbol : text.toCharArray()) { @@ -64,8 +64,8 @@ public class RobotUtils { } /* - * [width x height] [x, y] - * width height -1, . + * Получение картинки размером [width x height] с экрана с позиции [x, y] + * Если width или height равны -1, то возвращаем весь экран. */ public BufferedImage getImage(int x, int y, int width, int height) { Rectangle area;