From 59fdc4a3c02a6a4a50f84c5212fba4feb2249319 Mon Sep 17 00:00:00 2001 From: Victor Melnik Date: Wed, 11 Jan 2012 16:38:00 +0200 Subject: [PATCH] Added sources --- main/BotFloodIt.java | 197 +++++++++++++++++++++++ main/ImageUtils.java | 369 +++++++++++++++++++++++++++++++++++++++++++ main/RobotFrame.form | 222 ++++++++++++++++++++++++++ main/RobotFrame.java | 362 ++++++++++++++++++++++++++++++++++++++++++ main/RobotUtils.java | 78 +++++++++ 5 files changed, 1228 insertions(+) create mode 100644 main/BotFloodIt.java create mode 100644 main/ImageUtils.java create mode 100644 main/RobotFrame.form create mode 100644 main/RobotFrame.java create mode 100644 main/RobotUtils.java diff --git a/main/BotFloodIt.java b/main/BotFloodIt.java new file mode 100644 index 0000000..499246d --- /dev/null +++ b/main/BotFloodIt.java @@ -0,0 +1,197 @@ +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 */ + private int[] colors; + + public BotFloodIt(int[][] table) { + colors = new int[MAX_COLORS]; + for (int i = 0; i < colors.length; i++) { + colors[i] = -1; + } + this.table = colorsToIds(table); + } + + /** + * Получить цвета клеток в палитре + * @return массив цветов RGB + */ + public int[] getColors() { + return colors; + } + + /** + * Получить последовательность заливки цветов + * @return массив с идентификаторами цветов для заливки + */ + public byte[] getFillSequence() { + byte[][] copyTable = copyTable(table); + ArrayList seq = new ArrayList(); + while(!gameCompleted(copyTable)) { + seq.add(getNextFillColor(copyTable)); + } + byte[] out = new byte[seq.size()]; + for (int i = 0; i < out.length; i++) { + out[i] = seq.get(i).byteValue(); + } + return out; + } + + /* + * Получить индекс следующего цвета для заливки + */ + 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 вариантов + 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 итераций заливки + int maxArea = fillRate[0]; + int maxColor = 0; + for (int i = 1; i < fillSize; i++) { + if (fillRate[i] > maxArea) { + maxColor = i; + 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; // сколько цветов распознано + 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 + if (color == colors[k]) { + out[i][j] = k; + break; + } + } + } + } + return out; + } + + /** + * Залить заданное поле цветом color + * @param table игровое поле для заливки + * @param color цвет заливки + */ + private void fillTable(byte[][] table, byte color) { + if (table[0][0] == color) return; + fill(table, 0, 0, table[0][0], color); + } + + /* + * Заливка поля по координатам + */ + 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); + fill(table, x, y+1, prevColor, color); + } + } + + /** + * Получить количество залитых ячеек + * @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); + count += getCount(table, x, y+1, color); + } + return count; + } + + /* + * Проверка, залита ли вся область одним цветом + */ + private boolean gameCompleted(byte[][] table) { + byte color = table[0][0]; + int size = table.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + if (table[i][j] != color) return false; + } + } + return true; + } + + /* + * Копирование массива игрового поля + */ + private byte[][] copyTable(byte[][] table) { + int size = table.length; + byte[][] out = new byte[size][size]; + for (int i = 0; i < size; i++) { + System.arraycopy(table[i], 0, out[i], 0, size); + } + return out; + } +} diff --git a/main/ImageUtils.java b/main/ImageUtils.java new file mode 100644 index 0000000..82902ad --- /dev/null +++ b/main/ImageUtils.java @@ -0,0 +1,369 @@ +package main; + +import java.awt.*; +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 + */ + public ImageUtils(BufferedImage image, int boardSize) { + this.image = image; + this.boardSize = boardSize; + w = image.getWidth(); + h = image.getHeight(); + } + + /** + * Конструктор для проверки настроек + * @param image + * @param boardSize + * @param cellSize + * @param x + * @param y + */ + public ImageUtils(BufferedImage image, int boardSize, int cellSize, int x, int y) { + this.image = image; + this.boardSize = boardSize; + this.cellSize = cellSize; + this.board = new Point(x, y); + w = image.getWidth(); + h = image.getHeight(); + } + + /** + * Получить размер ячейки + * @return + */ + public int getCellSize() { + return cellSize; + } + + /** + * Получить координаты игрового поля + * @return точка с координатами левого верхнего угла поля + */ + public Point getBoardParameters() { + int[] pixels = new int[w * h]; + image.getRGB(0, 0, w, h, pixels, 0, w); + monochrome = threshold(pixels, 64); + board = getBoardXY(boardSize); + return board; + } + + /** + * Получить изображение игрового поля + * @return картинка игрового поля + */ + public BufferedImage getBoardImage() { + int size = cellSize * boardSize; + try { + return image.getSubimage(board.x, board.y, size, size); + } catch (Exception e) { + return image; + } + } + + /** + * Получить координаты кнопок для автоматического нажатия + * @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(board.x+size, board.y, + w-board.x-size, size), // справа от поля + new Rectangle(0, board.y+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 { + found = false; + break; + } + } + if (found) return out; + } + // Не удалось найти все точки + return null; + } + + /** + * Преобразовать массив цветов в графический вид + * @param ids массив идентификаторов последовательности + * @param palette массив палитры цветов + * @return изображение последовательности цветов + */ + public BufferedImage sequenceToImage(byte[] ids, int[] palette) { + final int size = 20; // размер каждой ячейки + // Разбивать будем по 10 клеток на строку + final int CELLS_IN_ROW = 10; + int width = CELLS_IN_ROW * size; + if (width == 0) width = size; + int rows = ids.length / CELLS_IN_ROW; + + BufferedImage out = new BufferedImage(width, (rows*size)+size, BufferedImage.TYPE_INT_RGB); + Graphics G = out.getGraphics(); + for (int i = 0; i < ids.length; i++) { + G.setColor(new Color(palette[ids[i]])); + G.fillRect(i % CELLS_IN_ROW * size, + i / CELLS_IN_ROW * size, + size, size); + } + G.dispose(); + return out; + } + + /** + * Преобразовать цветное изображение в монохромное. + * Нужно также учесть, что если поле расположено на светлом + * фоне, то необходимо инвертировать изображение, чтобы + * получить сплошную белую область на месте поля. + * @param pixels массив пикселей изображения + * @param value разделяющее значение + * @return массив boolean, true - белый, false - чёрный + */ + private boolean[] threshold(int[] pixels, int value) { + boolean inverse = isBackgroundLight(MAX_COLOR_POINTS); + if (inverse) value = 255 - value; + boolean[] bw = new boolean[pixels.length]; + for (int i = 0; i < pixels.length; i++) { + int brightNess = getBrightness(pixels[i]); + bw[i] = (brightNess >= value) ^ inverse; + } + return bw; + } + + /** + * Получение состояния яркости фона. + * @param numPoints сколько точек нужно для определения. + * @return true - фон светлый, false - тёмный + */ + private boolean isBackgroundLight(int numPoints) { + // Получаем numPoints случайных точек + Random rnd = new Random(); + int[] colors = new int[numPoints]; + for (int i = 0; i < numPoints; i++) { + int x = rnd.nextInt(w); + int y = rnd.nextInt(h); + colors[i] = image.getRGB(x, y); + } + // Находим среднюю яркость всех numPoints точек + long sum = 0; + for (int i = 0; i < numPoints; i++) { + int brightness = getBrightness(colors[i]); + sum = sum + brightness; + } + sum = sum / numPoints; + return (sum > 128); + } + + /** + * Определить координаты левой верхней ячейки игрового поля. + * @param boardSize размерность поля (10x10, 14x14 и т.д.) + * @return координата левого верхнего прямоугольника + */ + private Point getBoardXY(int boardSize) { + /* + * Сначала подсчитаем количество белых точек по горизонтали и вертикали + */ + int[] horizontal = new int[h]; + for (int i = 0; i < h; i++) { + int count = 0; + for (int j = 0; j < w; j++) { + if (getBWPixel(j, i)) count++; + } + horizontal[i] = count; + } + + int[] vertical = new int[w]; + for (int i = 0; i < w; i++) { + int count = 0; + for (int j = 0; j < h; j++) { + if (getBWPixel(i, j)) count++; + } + vertical[i] = count; + } + + /* + * Затем "отфильтруем" лишнее: подсчитаем среднее значение + * и на его основе уберём малозначимые строки и столбцы. + */ + horizontal = filterByMean(horizontal); + vertical = filterByMean(vertical); + + /* + * Ищем наибольшую ненулевую последовательность. + * Индексы границ последовательности и будут граничными точками поля. + */ + int[] vParam = getParamsFromSequence(horizontal); + int[] hParam = getParamsFromSequence(vertical); + + + int outX = hParam[0]; + 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 + */ + private int[] filterByMean(int[] source) { + long mean = 0; + for (int i = 0; i < source.length; i++) { + mean += source[i]; + } + mean = mean / source.length; + for (int i = 0; i < source.length; i++) { + source[i] = (source[i] > mean) ? 1 : 0; + } + return source; + } + + /** + * Поиск самой длинной последовательности в массиве. + * @param source входная последовательность из нулей и единиц + * @return массив параметров - индекс начала последовательности и её длина + */ + private int[] getParamsFromSequence(int[] source) { + int maxStart = 0, start = 0; + int maxLength = 0, length = 0; + for (int i = 1; i < source.length; i++) { + if (source[i] == 0) { + start = 0; + length = 0; + continue; + } + if (source[i] == source[i-1]) { + length++; + if (maxLength < length) { + maxStart = start; + maxLength = length; + } + } else { + // Если предыдущий элемент был нулевым - начинаем новую последовательность + start = i; + } + } + return new int[] {maxStart, maxLength}; + } + + /** + * Поиск координаты кнопки с цветом 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); + if (isEquals(color, template, FIND_BUTTON_TOLERANCE)) { + return new Point(x, h2 - y); + } + color = img.getRGB(x, h2 + y); + if (isEquals(color, template, FIND_BUTTON_TOLERANCE)) { + return new Point(x, h2 + y); + } + } + } + // Не нашли + return null; + } + + /** + * Проверка на соответствие цветов друг другу + * @param color1 первый цвет + * @param color2 второй цвет + * @param tolerance чувствительность + * @return true - соответствуют, false - нет + */ + private boolean isEquals(int color1, int color2, int tolerance) { + if (tolerance < 2) return color1 == color2; + + int r1 = (color1 >> 16) & 0xff; + int g1 = (color1 >> 8) & 0xff; + int b1 = color1 & 0xff; + int r2 = (color2 >> 16) & 0xff; + int g2 = (color2 >> 8) & 0xff; + int b2 = color2 & 0xff; + return (Math.abs(r1 - r2) <= tolerance) && + (Math.abs(g1 - g2) <= tolerance) && + (Math.abs(b1 - b2) <= tolerance); + } + + /** + * Получение яркости цвета + * @param color исходный цвет + * @return яркость (0..255) + */ + private int getBrightness(int color) { + int qr = (color >> 16) & 0xff; + int qg = (color >> 8) & 0xff; + int qb = color & 0xff; + return (qr + qg + qb) / 3; + } + + /* + * Получение цвета из монохромного изображения. + * return true - белый, false - чёрный + */ + private boolean getBWPixel(int x, int y) { + if ((x < 0) || (y < 0) || (x >= w) || (y >= h)) return false; + return monochrome[y * w + x]; + } + +} \ No newline at end of file diff --git a/main/RobotFrame.form b/main/RobotFrame.form new file mode 100644 index 0000000..bba3bf1 --- /dev/null +++ b/main/RobotFrame.form @@ -0,0 +1,222 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/RobotFrame.java b/main/RobotFrame.java new file mode 100644 index 0000000..49363ee --- /dev/null +++ b/main/RobotFrame.java @@ -0,0 +1,362 @@ +package main; + +import java.awt.AWTException; +import java.awt.Point; +import java.awt.image.BufferedImage; +import javax.swing.*; + +/** + * Окно приложения. + * @author aNNiMON + */ +public class RobotFrame extends JFrame { + + /* Статус работы приложения */ + private boolean isRunning; + + private Thread robotAction; + + /** Creates new form RobotFrame */ + public RobotFrame() { + initComponents(); + isRunning = false; + setAlwaysOnTop(true); + boardSizeSpinner.setModel(new SpinnerNumberModel(14, 5, 40, 1)); + cellSizeSpinner.setModel(new SpinnerNumberModel(24, 5, 100, 1)); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + startStop = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + windowPanel = new javax.swing.JPanel(); + windowXTextField = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + windowYTextField = new javax.swing.JTextField(); + jLabel3 = new javax.swing.JLabel(); + jLabel5 = new javax.swing.JLabel(); + jLabel6 = new javax.swing.JLabel(); + checkButton = new javax.swing.JButton(); + boardSizeSpinner = new javax.swing.JSpinner(); + jLabel7 = new javax.swing.JLabel(); + cellSizeSpinner = new javax.swing.JSpinner(); + detectButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("FloodItBot"); + setResizable(false); + + startStop.setText("Start"); + startStop.setActionCommand(""); + startStop.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + startStopActionPerformed(evt); + } + }); + + jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel1.setText("Bot for FloodIt"); + jLabel1.setFocusable(false); + jLabel1.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + + windowPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Options")); + + windowXTextField.setText("0"); + + jLabel2.setText("x:"); + + windowYTextField.setText("0"); + + jLabel3.setText("y:"); + + jLabel5.setText("Window:"); + + jLabel6.setText("Cell size:"); + + checkButton.setText("Check"); + checkButton.setActionCommand(""); + checkButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + checkButtonActionPerformed(evt); + } + }); + + jLabel7.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N + jLabel7.setText("Field size:"); + + detectButton.setText("Detect"); + detectButton.setActionCommand(""); + detectButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + detectButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout windowPanelLayout = new javax.swing.GroupLayout(windowPanel); + windowPanel.setLayout(windowPanelLayout); + windowPanelLayout.setHorizontalGroup( + windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(windowPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(detectButton, javax.swing.GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE) + .addComponent(checkButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE) + .addGroup(windowPanelLayout.createSequentialGroup() + .addGroup(windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel5) + .addGroup(windowPanelLayout.createSequentialGroup() + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(windowXTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(windowYTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(windowPanelLayout.createSequentialGroup() + .addComponent(jLabel7) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(boardSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(windowPanelLayout.createSequentialGroup() + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cellSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + windowPanelLayout.setVerticalGroup( + windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(windowPanelLayout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel2) + .addComponent(windowXTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel3) + .addComponent(windowYTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel6) + .addComponent(cellSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(windowPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(boardSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(detectButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(checkButton)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(windowPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(startStop, javax.swing.GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGap(57, 57, 57) + .addComponent(jLabel1) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addGap(18, 18, 18) + .addComponent(windowPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(startStop) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void startStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startStopActionPerformed + if (isRunning) { + startStop.setText("Start"); + isRunning = false; + } else { + startStop.setText("Stop"); + isRunning = true; + robotAction = new Thread(new Runnable() { + + @Override + public void run() { + int windowX, windowY, boardSize, cellSize; + BufferedImage detectImage; + RobotUtils robot; + // Получаем настройки + try { + robot = new RobotUtils(); + detectImage = robot.getImage(-1, -1, -1, -1); + windowX = Integer.valueOf(windowXTextField.getText()); + windowY = Integer.valueOf(windowYTextField.getText()); + boardSize = (Integer) boardSizeSpinner.getValue(); + cellSize = (Integer) cellSizeSpinner.getValue(); + } catch (Exception ex) { + ex.printStackTrace(); + 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++) { + for (int j = 0; j < boardSize; j++) { + table[i][j] = detectImage.getRGB(j*cellSize + offset, + i*cellSize + offset); + } + } + 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; + startStop.setText("Start"); + } + + }); + robotAction.start(); + } + }//GEN-LAST:event_startStopActionPerformed + + private void checkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_checkButtonActionPerformed + if (isRunning) return; // нельзя проверять настройки во время работы + int windowX, windowY, boardSize, cellSize; + BufferedImage detectImage; + RobotUtils robot; + try { + robot = new RobotUtils(); + detectImage = robot.getImage(-1, -1, -1, -1); + windowX = Integer.valueOf(windowXTextField.getText()); + windowY = Integer.valueOf(windowYTextField.getText()); + boardSize = (Integer) boardSizeSpinner.getValue(); + cellSize = (Integer) cellSizeSpinner.getValue(); + } catch (Exception ex) { + ex.printStackTrace(); + return; + } + ImageUtils iu = new ImageUtils(detectImage, boardSize, cellSize, windowX, windowY); + showImageWindow("Checking", iu.getBoardImage()); + }//GEN-LAST:event_checkButtonActionPerformed + + private void detectButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_detectButtonActionPerformed + if (isRunning) return; // нельзя определять настройки во время работы + RobotUtils robot; + try { + robot = new RobotUtils(); + BufferedImage detectImage = robot.getImage(-1, -1, -1, -1); + int boardSize = (Integer) boardSizeSpinner.getValue(); + ImageUtils iu = new ImageUtils(detectImage, boardSize); + Point pt = iu.getBoardParameters(); + int cellSize = iu.getCellSize(); + windowXTextField.setText(String.valueOf(pt.x)); + windowYTextField.setText(String.valueOf(pt.y)); + cellSizeSpinner.setValue(cellSize); + } catch (AWTException ex) { + ex.printStackTrace(); + } + }//GEN-LAST:event_detectButtonActionPerformed + + /** + * Показать модальное окно с изображением + * @param title заголовок окна + * @param image изображение + * @throws SecurityException + */ + private void showImageWindow(String title, BufferedImage image) throws SecurityException { + ImageIcon icon = new ImageIcon(image); + JLabel backlabel = new JLabel(icon); + getLayeredPane().add(backlabel, new Integer(Integer.MIN_VALUE)); + backlabel.setBounds(0, 0, icon.getIconWidth(), icon.getIconHeight()); + JDialog dialog = new JDialog(this); + dialog.setAlwaysOnTop(true); + dialog.setLocationByPlatform(true); + dialog.setTitle(title); + dialog.setResizable(true); + dialog.add(backlabel); + dialog.pack(); + dialog.setVisible(true); + } + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + /* Set the Nimbus look and feel */ + // + /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. + * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html + */ + try { + for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + javax.swing.UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException ex) { + java.util.logging.Logger.getLogger(RobotFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (InstantiationException ex) { + java.util.logging.Logger.getLogger(RobotFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + java.util.logging.Logger.getLogger(RobotFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (javax.swing.UnsupportedLookAndFeelException ex) { + java.util.logging.Logger.getLogger(RobotFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } + // + + /* Create and display the form */ + java.awt.EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + new RobotFrame().setVisible(true); + } + }); + } + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JSpinner boardSizeSpinner; + private javax.swing.JSpinner cellSizeSpinner; + private javax.swing.JButton checkButton; + private javax.swing.JButton detectButton; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JButton startStop; + private javax.swing.JPanel windowPanel; + private javax.swing.JTextField windowXTextField; + private javax.swing.JTextField windowYTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/main/RobotUtils.java b/main/RobotUtils.java new file mode 100644 index 0000000..b11833d --- /dev/null +++ b/main/RobotUtils.java @@ -0,0 +1,78 @@ +package main; + +import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; + +/** + * Работа с классом Robot. + * @author aNNiMON + */ +public class RobotUtils { + + private static final int CLICK_DELAY = 300; + private Robot robot; + + /** + * Конструктор + * @throws AWTException ошибка инициализации Robot + */ + public RobotUtils() throws AWTException { + robot = new Robot(); + } + + /** + * Кликнуть в нужную точку + * @param click точка по которой нужно кликнуть + */ + public void clickPoint(Point click) { + robot.mouseMove(click.x, click.y); + robot.mousePress(InputEvent.BUTTON1_MASK); + robot.delay(CLICK_DELAY); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + } + + /** + * Автоматически воспроизвести заданную последовательность нажатий + * @param buttons координаты точек, куда следует нажимать + * @param result последовательность id для указания на нужную кнопку + */ + public void autoClick(Point[] buttons, byte[] result) { + for (int i = 0; i < result.length; i++) { + clickPoint(buttons[result[i]]); + } + } + /** + * Автоматическое написание сообщения + * @param text "печатаемый" текст + */ + public void writeMessage(String text) { + for (char symbol : text.toCharArray()) { + boolean needShiftPress = Character.isUpperCase(symbol) && Character.isLetter(symbol); + if(needShiftPress) { + robot.keyPress(KeyEvent.VK_SHIFT); + } + int event = KeyEvent.getExtendedKeyCodeForChar(symbol); + try { + robot.keyPress(event); + } catch (Exception e) {} + if(needShiftPress) { + robot.keyRelease(KeyEvent.VK_SHIFT); + } + } + } + + /* + * Получение картинки размером [width x height] с экрана с позиции [x, y] + * Если width или height равны -1, то возвращаем весь экран. + */ + public BufferedImage getImage(int x, int y, int width, int height) { + Rectangle area; + if ((width == -1) || (height == -1)) { + area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()); + } else area = new Rectangle(x, y, width, height); + return robot.createScreenCapture(area); + } + +}