diff --git a/.gitignore b/.gitignore index 2c6eb3893..5ff6309b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,38 @@ -/.metadata -/robots/.settings -/robots/bin -eclipse.bat \ No newline at end of file +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..aa00ffab7 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..9dc782bb2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index d79735aee..d0639160a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# Robots -The project to learn OO design concepts and MDI application development in Java +task1 +1. Научиться собирать проект с использованием Apache Maven. +2. Провести рефакторинг и исправить баг (см. комментарии в коде). +3. Исправить баг убегания робота за границу окна + +task2 +1. Добавить окно с подтверждением на закрытие любого из окон. +2. Добавить пункт меню ""Выйти"" (с обработкой аналогичной кнопке закрытия приложения). +3. Добавить локализацию приложения (пункты меню, кнопки и т.д.). +4. По умолчанию выбирается язык системы, но он может быть изменён через меню. diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..0e8c7fbcf --- /dev/null +++ b/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.example + Robots + 1.0-SNAPSHOT + + + 20 + 20 + UTF-8 + + + + + junit + junit + 4.12 + test + + + junit + junit + 4.13.2 + compile + + + org.junit.jupiter + junit-jupiter + RELEASE + compile + + + + \ No newline at end of file diff --git a/robots/.classpath b/robots/.classpath deleted file mode 100644 index fceb4801b..000000000 --- a/robots/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/robots/.gitignore b/robots/.gitignore deleted file mode 100644 index 2757ffa76..000000000 --- a/robots/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin/ -/.settings/ diff --git a/robots/.project b/robots/.project deleted file mode 100644 index 78e165663..000000000 --- a/robots/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - Robots - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java deleted file mode 100644 index f82cfd8f8..000000000 --- a/robots/src/gui/GameVisualizer.java +++ /dev/null @@ -1,210 +0,0 @@ -package gui; - -import java.awt.Color; -import java.awt.EventQueue; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.geom.AffineTransform; -import java.util.Timer; -import java.util.TimerTask; - -import javax.swing.JPanel; - -public class GameVisualizer extends JPanel -{ - private final Timer m_timer = initTimer(); - - private static Timer initTimer() - { - Timer timer = new Timer("events generator", true); - return timer; - } - - private volatile double m_robotPositionX = 100; - private volatile double m_robotPositionY = 100; - private volatile double m_robotDirection = 0; - - private volatile int m_targetPositionX = 150; - private volatile int m_targetPositionY = 100; - - private static final double maxVelocity = 0.1; - private static final double maxAngularVelocity = 0.001; - - public GameVisualizer() - { - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onRedrawEvent(); - } - }, 0, 50); - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onModelUpdateEvent(); - } - }, 0, 10); - addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - setTargetPosition(e.getPoint()); - repaint(); - } - }); - setDoubleBuffered(true); - } - - protected void setTargetPosition(Point p) - { - m_targetPositionX = p.x; - m_targetPositionY = p.y; - } - - protected void onRedrawEvent() - { - EventQueue.invokeLater(this::repaint); - } - - private static double distance(double x1, double y1, double x2, double y2) - { - double diffX = x1 - x2; - double diffY = y1 - y2; - return Math.sqrt(diffX * diffX + diffY * diffY); - } - - private static double angleTo(double fromX, double fromY, double toX, double toY) - { - double diffX = toX - fromX; - double diffY = toY - fromY; - - return asNormalizedRadians(Math.atan2(diffY, diffX)); - } - - protected void onModelUpdateEvent() - { - double distance = distance(m_targetPositionX, m_targetPositionY, - m_robotPositionX, m_robotPositionY); - if (distance < 0.5) - { - return; - } - double velocity = maxVelocity; - double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); - double angularVelocity = 0; - if (angleToTarget > m_robotDirection) - { - angularVelocity = maxAngularVelocity; - } - if (angleToTarget < m_robotDirection) - { - angularVelocity = -maxAngularVelocity; - } - - moveRobot(velocity, angularVelocity, 10); - } - - private static double applyLimits(double value, double min, double max) - { - if (value < min) - return min; - if (value > max) - return max; - return value; - } - - private void moveRobot(double velocity, double angularVelocity, double duration) - { - velocity = applyLimits(velocity, 0, maxVelocity); - angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); - double newX = m_robotPositionX + velocity / angularVelocity * - (Math.sin(m_robotDirection + angularVelocity * duration) - - Math.sin(m_robotDirection)); - if (!Double.isFinite(newX)) - { - newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); - } - double newY = m_robotPositionY - velocity / angularVelocity * - (Math.cos(m_robotDirection + angularVelocity * duration) - - Math.cos(m_robotDirection)); - if (!Double.isFinite(newY)) - { - newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); - } - m_robotPositionX = newX; - m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; - } - - private static double asNormalizedRadians(double angle) - { - while (angle < 0) - { - angle += 2*Math.PI; - } - while (angle >= 2*Math.PI) - { - angle -= 2*Math.PI; - } - return angle; - } - - private static int round(double value) - { - return (int)(value + 0.5); - } - - @Override - public void paint(Graphics g) - { - super.paint(g); - Graphics2D g2d = (Graphics2D)g; - drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); - drawTarget(g2d, m_targetPositionX, m_targetPositionY); - } - - private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private void drawRobot(Graphics2D g, int x, int y, double direction) - { - int robotCenterX = round(m_robotPositionX); - int robotCenterY = round(m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); - g.setTransform(t); - g.setColor(Color.MAGENTA); - fillOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.WHITE); - fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); - } - - private void drawTarget(Graphics2D g, int x, int y) - { - AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); - g.setTransform(t); - g.setColor(Color.GREEN); - fillOval(g, x, y, 5, 5); - g.setColor(Color.BLACK); - drawOval(g, x, y, 5, 5); - } -} diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java deleted file mode 100644 index ecb63c00f..000000000 --- a/robots/src/gui/GameWindow.java +++ /dev/null @@ -1,20 +0,0 @@ -package gui; - -import java.awt.BorderLayout; - -import javax.swing.JInternalFrame; -import javax.swing.JPanel; - -public class GameWindow extends JInternalFrame -{ - private final GameVisualizer m_visualizer; - public GameWindow() - { - super("Игровое поле", true, true, true, true); - m_visualizer = new GameVisualizer(); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(m_visualizer, BorderLayout.CENTER); - getContentPane().add(panel); - pack(); - } -} diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java deleted file mode 100644 index 62e943ee1..000000000 --- a/robots/src/gui/MainApplicationFrame.java +++ /dev/null @@ -1,156 +0,0 @@ -package gui; - -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import log.Logger; - -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ - private final JDesktopPane desktopPane = new JDesktopPane(); - - public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); - - setContentPane(desktopPane); - - - LogWindow logWindow = createLogWindow(); - addWindow(logWindow); - - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); - addWindow(gameWindow); - - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); - } - - protected LogWindow createLogWindow() - { - LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); - logWindow.setSize(300, 800); - setMinimumSize(logWindow.getSize()); - logWindow.pack(); - Logger.debug("Протокол работает"); - return logWindow; - } - - protected void addWindow(JInternalFrame frame) - { - desktopPane.add(frame); - frame.setVisible(true); - } - -// protected JMenuBar createMenuBar() { -// JMenuBar menuBar = new JMenuBar(); -// -// //Set up the lone menu. -// JMenu menu = new JMenu("Document"); -// menu.setMnemonic(KeyEvent.VK_D); -// menuBar.add(menu); -// -// //Set up the first menu item. -// JMenuItem menuItem = new JMenuItem("New"); -// menuItem.setMnemonic(KeyEvent.VK_N); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_N, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("new"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// //Set up the second menu item. -// menuItem = new JMenuItem("Quit"); -// menuItem.setMnemonic(KeyEvent.VK_Q); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("quit"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// return menuBar; -// } - - private JMenuBar generateMenuBar() - { - JMenuBar menuBar = new JMenuBar(); - - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(systemLookAndFeel); - } - - { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(crossplatformLookAndFeel); - } - - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); - - { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - testMenu.add(addLogMessageItem); - } - - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; - } - - private void setLookAndFeel(String className) - { - try - { - UIManager.setLookAndFeel(className); - SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore - } - } -} diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java deleted file mode 100644 index ca0ce4426..000000000 --- a/robots/src/log/LogWindowSource.java +++ /dev/null @@ -1,89 +0,0 @@ -package log; - -import java.util.ArrayList; -import java.util.Collections; - -/** - * Что починить: - * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются - * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) - */ -public class LogWindowSource -{ - private int m_iQueueLength; - - private ArrayList m_messages; - private final ArrayList m_listeners; - private volatile LogChangeListener[] m_activeListeners; - - public LogWindowSource(int iQueueLength) - { - m_iQueueLength = iQueueLength; - m_messages = new ArrayList(iQueueLength); - m_listeners = new ArrayList(); - } - - public void registerListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.add(listener); - m_activeListeners = null; - } - } - - public void unregisterListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.remove(listener); - m_activeListeners = null; - } - } - - public void append(LogLevel logLevel, String strMessage) - { - LogEntry entry = new LogEntry(logLevel, strMessage); - m_messages.add(entry); - LogChangeListener [] activeListeners = m_activeListeners; - if (activeListeners == null) - { - synchronized (m_listeners) - { - if (m_activeListeners == null) - { - activeListeners = m_listeners.toArray(new LogChangeListener [0]); - m_activeListeners = activeListeners; - } - } - } - for (LogChangeListener listener : activeListeners) - { - listener.onLogChanged(); - } - } - - public int size() - { - return m_messages.size(); - } - - public Iterable range(int startFrom, int count) - { - if (startFrom < 0 || startFrom >= m_messages.size()) - { - return Collections.emptyList(); - } - int indexTo = Math.min(startFrom + count, m_messages.size()); - return m_messages.subList(startFrom, indexTo); - } - - public Iterable all() - { - return m_messages; - } -} diff --git a/src/main/java/org/example/Test/LogWindowSourceTest.java b/src/main/java/org/example/Test/LogWindowSourceTest.java new file mode 100644 index 000000000..e0b3da1c5 --- /dev/null +++ b/src/main/java/org/example/Test/LogWindowSourceTest.java @@ -0,0 +1,63 @@ +package Test; +import log.LogWindowSource; +import log.LogLevel; +import log.LogEntry; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class LogWindowSourceTest { + + @Test + public void testLogAppending() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + + assertEquals(3, logWindow.size()); // Проверяем, что размер лога равен 3 после добавления 3 сообщений + } + + @Test + public void testLogLimit() { + LogWindowSource logWindow = new LogWindowSource(3); // Создаем лог с ограничением в 3 сообщения + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + logWindow.append(LogLevel.Debug, "Message 4"); // Превышаем лимит, первое сообщение должно быть удалено + + assertEquals(3, logWindow.size()); // Проверяем, что размер лога равен 3 после добавления 4 сообщений + assertEquals("Message 2", logWindow.all().iterator().next().getMessage()); // Проверяем, что первое сообщение было удалено + } + + @Test + public void testLogRange() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + logWindow.append(LogLevel.Debug, "Message 4"); + logWindow.append(LogLevel.Info, "Message 5"); + + Iterable range = logWindow.range(1, 3); // Получаем диапазон сообщений с индекса 1 до 3 + + int count = 0; + for (LogEntry entry : range) { + count++; + } + assertEquals(3, count); // Проверяем, что в полученном диапазоне содержится 3 сообщения + } + + @Test + public void testLogRangeOutOfBounds() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + + Iterable range = logWindow.range(2, 3); // Пытаемся получить диапазон с индекса 2, хотя в логе только 1 сообщение + + int count = 0; + for (LogEntry entry : range) { + count++; + } + assertEquals(0, count); // Проверяем, что полученный диапазон пустой + } +} \ No newline at end of file diff --git a/src/main/java/org/example/gui/GameVisualizer.java b/src/main/java/org/example/gui/GameVisualizer.java new file mode 100644 index 000000000..55fee1af1 --- /dev/null +++ b/src/main/java/org/example/gui/GameVisualizer.java @@ -0,0 +1,237 @@ +package gui; + +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; +import javax.imageio.ImageIO; +import javax.swing.JPanel; + +public class GameVisualizer extends JPanel +{ + private final Timer m_timer = initTimer(); + private Image background = ImageIO.read(new File("C:\\Users\\egori\\IdeaProjects\\Robots333\\src\\main\\java\\org\\example\\gui\\grasss.png")); + private Image treedr = ImageIO.read(new File("C:\\Users\\egori\\IdeaProjects\\Robots333\\src\\main\\java\\org\\example\\gui\\drytree.png")); + private Image trunk = ImageIO.read(new File("C:\\Users\\egori\\IdeaProjects\\Robots333\\src\\main\\java\\org\\example\\gui\\trunk.png")); + + private int score; + private static Timer initTimer() + { + Timer timer = new Timer("events generator", true); + return timer; + } + + public boolean m_collisionWithBorder; + + private volatile double m_robotPositionX = 100; + private volatile double m_robotPositionY = 100; + private volatile double m_robotDirection = 0; + private volatile int m_targetPositionX = 150; + private volatile int m_targetPositionY = 100; + + private static final double maxVelocity = 0.1; + private static final double maxAngularVelocity = 0.001; + + private ArrayList trees = new ArrayList<>(); + private static final int NUM_TREES = 20; + + public GameVisualizer() throws IOException { + for (int i = 0; i < NUM_TREES; i++) { + trees.add(new gui.Tree(Math.random() * 800, Math.random() * 600)); + } + + m_timer.schedule(new TimerTask() { + @Override + public void run() { + for (gui.Tree tree : trees) { + if (tree.isRobotOnTree(m_robotPositionX, m_robotPositionY)) { + tree.decreaseHealth(); + if (tree.getHealth() == 0) { + score++; + } + } + } + repaint(); + } + }, 0, 50); + + m_timer.schedule(new TimerTask() { + @Override + public void run() { + onRedrawEvent(); + } + }, 0, 50); + m_timer.schedule(new TimerTask() { + @Override + public void run() { + onModelUpdateEvent(); + } + }, 0, 7); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + setTargetPosition(e.getPoint()); + repaint(); + } + }); + setDoubleBuffered(true); + } + + public static final double BORDER_BUFFER = 3.5; + + protected void setTargetPosition(Point p) { + m_targetPositionX = p.x; + m_targetPositionY = p.y; + } + + protected void onRedrawEvent() { + EventQueue.invokeLater(this::repaint); + } + + private static double distance(double x1, double y1, double x2, double y2) { + double diffX = x1 - x2; + double diffY = y1 - y2; + return Math.sqrt(diffX * diffX + diffY * diffY); + } + + private static double angleTo(double fromX, double fromY, double toX, double toY) { + double diffX = toX - fromX; + double diffY = toY - fromY; + + return asNormalizedRadians(Math.atan2(diffY, diffX)); + } + + public void onModelUpdateEvent() { + m_targetPositionX = (int) applyLimits(m_targetPositionX, 0, this.getWidth()); + m_targetPositionY = (int) applyLimits(m_targetPositionY, 0, this.getHeight()); + + double distance = distance(m_targetPositionX, m_targetPositionY, + m_robotPositionX, m_robotPositionY); + if (distance < 7) { + return; + } + + double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); + double angularVelocity = 0; + + if (angleToTarget > m_robotDirection) { + angularVelocity = maxAngularVelocity; + } + if (angleToTarget < m_robotDirection) { + angularVelocity = -maxAngularVelocity; + } + + moveRobot(maxVelocity, angularVelocity, 10); + } + + private static double applyLimits(double value, double min, double max) { + if (value < min) + return min; + if (value > max) + return max; + return value; + } + + public void moveRobot(double velocity, double angularVelocity, double duration) { + velocity = applyLimits(velocity, 0, maxVelocity); + angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); + m_robotDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); + + double distanceToMove = velocity * duration; + double newX = m_robotPositionX + Math.cos(m_robotDirection) * distanceToMove; + double newY = m_robotPositionY + Math.sin(m_robotDirection) * distanceToMove; + + if (newX < BORDER_BUFFER) { + newX = BORDER_BUFFER; + m_robotDirection = Math.PI - m_robotDirection; + } else if (newX > getWidth() - BORDER_BUFFER) { + newX = getWidth() - BORDER_BUFFER; + m_robotDirection = Math.PI - m_robotDirection; + } + if (newY < BORDER_BUFFER) { + newY = BORDER_BUFFER; + m_robotDirection = Math.PI + m_robotDirection; + } else if (newY > getHeight() - BORDER_BUFFER) { + newY = getHeight() - BORDER_BUFFER; + m_robotDirection = Math.PI + m_robotDirection; + } + m_robotPositionX = newX; + m_robotPositionY = newY; + if (0 <= m_robotPositionX && m_robotPositionX <= this.getWidth() && 0 <= m_robotPositionY && m_robotPositionY <= getHeight()) { + m_collisionWithBorder = false; + } + } + + private static double asNormalizedRadians(double angle) { + while (angle < 0) { + angle += 2 * Math.PI; + } + while (angle >= 2 * Math.PI) { + angle -= 2 * Math.PI; + } + return angle; + } + + public double getPositionX() { + return m_robotPositionX; + } + + public double getPositionY() { + return m_robotPositionY; + } + + @Override + public void paint(Graphics g) { + super.paint(g); + Graphics2D g2d = (Graphics2D) g; + g2d.drawImage(background, 0, 0, this.getWidth(), this.getHeight(), null); + for (gui.Tree tree : trees) { + tree.paint(g2d, treedr, trunk); + } + drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); + drawTarget(g2d, m_targetPositionX, m_targetPositionY); + g2d.drawString("Score: " + score, 10, 15); + } + + private static int round(double value) { + return (int) (value + 0.5); + } + + private void drawRobot(Graphics2D g, int x, int y, double direction) { + int robotCenterX = round(m_robotPositionX); + int robotCenterY = round(m_robotPositionY); + AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); + g.setTransform(t); + g.setColor(Color.MAGENTA); + fillOval(g, robotCenterX, robotCenterY, 30, 10); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX, robotCenterY, 30, 10); + g.setColor(Color.WHITE); + fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + } + + private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { + g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + } + + private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) { + g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + } + + private void drawTarget(Graphics2D g, int x, int y) { + AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); + g.setTransform(t); + g.setColor(Color.GREEN); + fillOval(g, x, y, 5, 5); + g.setColor(Color.BLACK); + drawOval(g, x, y, 5, 5); + } +} diff --git a/src/main/java/org/example/gui/GameWindow.java b/src/main/java/org/example/gui/GameWindow.java new file mode 100644 index 000000000..38b200955 --- /dev/null +++ b/src/main/java/org/example/gui/GameWindow.java @@ -0,0 +1,37 @@ +package gui; + +import java.awt.BorderLayout; +import java.io.IOException; +import java.util.Locale; + +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; + +import static gui.MenuBar.getLocaleString; + +public class GameWindow extends inter +{ + private final GameVisualizer m_visualizer; + public GameWindow() + { + super(getLocaleString("gamePlane", Locale.getDefault()), true, true, true, true); + setTitle(getLocaleString("gamePlane", Locale.getDefault())); + + try { + m_visualizer = new GameVisualizer(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(m_visualizer, BorderLayout.CENTER); + getContentPane().add(panel); + pack(); + } + + public void updateWindow() { + setTitle(getLocaleString("gamePlane", Locale.getDefault())); + } + +} diff --git a/robots/src/gui/LogWindow.java b/src/main/java/org/example/gui/LogWindow.java similarity index 60% rename from robots/src/gui/LogWindow.java rename to src/main/java/org/example/gui/LogWindow.java index 723d3e2fc..0e956faf0 100644 --- a/robots/src/gui/LogWindow.java +++ b/src/main/java/org/example/gui/LogWindow.java @@ -1,29 +1,32 @@ package gui; -import java.awt.BorderLayout; -import java.awt.EventQueue; -import java.awt.TextArea; +import java.awt.*; +import java.awt.event.WindowListener; +import java.util.Locale; -import javax.swing.JInternalFrame; -import javax.swing.JPanel; +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; import log.LogChangeListener; import log.LogEntry; import log.LogWindowSource; -public class LogWindow extends JInternalFrame implements LogChangeListener +import static gui.MenuBar.getLocaleString; + +public class LogWindow extends inter implements LogChangeListener { private LogWindowSource m_logSource; private TextArea m_logContent; public LogWindow(LogWindowSource logSource) { - super("Протокол работы", true, true, true, true); + super(getLocaleString("workProtocol", Locale.getDefault()), true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); m_logContent.setSize(200, 500); - + JPanel panel = new JPanel(new BorderLayout()); panel.add(m_logContent, BorderLayout.CENTER); getContentPane().add(panel); @@ -41,10 +44,20 @@ private void updateLogContent() m_logContent.setText(content.toString()); m_logContent.invalidate(); } + + @Override + public void doDefaultCloseAction() { + m_logSource.unregisterListener(this); + super.doDefaultCloseAction(); + } @Override public void onLogChanged() { EventQueue.invokeLater(this::updateLogContent); } + + public void updateWindow() { + setTitle(getLocaleString("workProtocol", Locale.getDefault())); + } } diff --git a/src/main/java/org/example/gui/MainApplicationFrame.java b/src/main/java/org/example/gui/MainApplicationFrame.java new file mode 100644 index 000000000..6428e3e71 --- /dev/null +++ b/src/main/java/org/example/gui/MainApplicationFrame.java @@ -0,0 +1,135 @@ +package gui; + +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Locale; +import java.util.ResourceBundle; + +import javax.swing.*; + + +import log.Logger; + +import static gui.MenuBar.getLocaleString; + + + +public class MainApplicationFrame extends JFrame +{ + static ResourceBundle rb; + + private final JDesktopPane desktopPane = new JDesktopPane(); + + private String profileID; + public MainApplicationFrame() { + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset*2, + screenSize.height - inset*2); + + setContentPane(desktopPane); + + + LogWindow logWindow = createLogWindow(); + addWindow(logWindow); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + Object[] options = { getLocaleString("yes", Locale.getDefault()), getLocaleString("no", Locale.getDefault()) }; + int n = JOptionPane + .showOptionDialog(e.getWindow(), getLocaleString("closeWindow", Locale.getDefault()), + getLocaleString("accept", Locale.getDefault()), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (n == 0) { + e.getWindow().setVisible(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + } + }); + + GameWindow gameWindow = new GameWindow(); + gameWindow.setSize(400, 400); + addWindow(gameWindow); + + MenuBar menuBar = new MenuBar(this); + setJMenuBar(generateMenuBar()); + setDefaultCloseOperation(EXIT_ON_CLOSE); + selectingProfile(); + } + + private void selectingProfile(){ + boolean flagNewProfile = false; + while(profileID == null) { + int response = MenuStart.whatProfile(); +// if (response == 0) { +// profileID = MenuStart.selectingProfile(); +// } else + if (response == 1){ + flagNewProfile = true; + profileID = MenuStart.createProfile(); + } + else { + System.exit(0); + } + } +// if (!flagNewProfile) { +// setStates(); +// } + } + + protected LogWindow createLogWindow() + { + LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); + logWindow.setLocation(10,10); + logWindow.setSize(300, 800); + setMinimumSize(logWindow.getSize()); + logWindow.pack(); + Logger.debug(getLocaleString("protocolWorks", Locale.getDefault())); + return logWindow; + } + + protected void addWindow(JInternalFrame frame) + { + desktopPane.add(frame); + frame.setVisible(true); + } + + + public JMenuBar generateMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + menuBar.add(MenuBar.createLookAndFeelMenu()); + menuBar.add(MenuBar.createTestMenu()); + menuBar.add(MenuBar.createLanguage()); + menuBar.add(MenuBar.createExitButton()); + return menuBar; + } + + public void changeLocale(Locale newLocale) { + if (!(newLocale.equals(Locale.getDefault()))){ + Locale.setDefault(newLocale); + + setJMenuBar(generateMenuBar()); + setContentPane(desktopPane); + updateWindowsLocale(); + } + else{ + Locale.setDefault(newLocale); + } + } + + private void updateWindowsLocale() { + JInternalFrame[] frames = desktopPane.getAllFrames(); + for (JInternalFrame frame : frames) { + if (frame instanceof inter) { + ((inter) frame).updateWindow(); + } + } + } + + +} diff --git a/src/main/java/org/example/gui/MenuBar.java b/src/main/java/org/example/gui/MenuBar.java new file mode 100644 index 000000000..daf2bb879 --- /dev/null +++ b/src/main/java/org/example/gui/MenuBar.java @@ -0,0 +1,129 @@ +package gui; + + +import log.Logger; + +import javax.swing.*; +import java.awt.event.KeyEvent; +import java.util.Locale; +import java.util.ResourceBundle; + + + +public class MenuBar { + + static MainApplicationFrame mainFrame; + + public MenuBar(MainApplicationFrame mainFrame){ + this.mainFrame = mainFrame; + } + + + protected static JMenu createLookAndFeelMenu() { + + JMenu lookAndFeelMenu = new JMenu(getLocaleString("displayMode", Locale.getDefault())); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + getLocaleString("systemMap", Locale.getDefault())); + + { + JMenuItem systemLookAndFeel = new JMenuItem(getLocaleString("modeControl", Locale.getDefault()), KeyEvent.VK_S); + systemLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + mainFrame.invalidate(); + }); + lookAndFeelMenu.add(systemLookAndFeel); + } + + { + JMenuItem crossplatformLookAndFeel = new JMenuItem(getLocaleString("universityMap", Locale.getDefault()), KeyEvent.VK_S); + crossplatformLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + mainFrame.invalidate(); + }); + lookAndFeelMenu.add(crossplatformLookAndFeel); + } + + return lookAndFeelMenu; + } + + + protected static JMenu createTestMenu() { + JMenu testMenu = new JMenu(getLocaleString("test", Locale.getDefault())); + testMenu.setMnemonic(KeyEvent.VK_T); + testMenu.getAccessibleContext().setAccessibleDescription( + getLocaleString("testCommand", Locale.getDefault())); + + { + JMenuItem addLogMessageItem = new JMenuItem(getLocaleString("messageLog", Locale.getDefault()), KeyEvent.VK_S); + addLogMessageItem.addActionListener((event) -> { + Logger.debug(getLocaleString("newString", Locale.getDefault())); + }); + testMenu.add(addLogMessageItem); + } + return testMenu; + } + + protected static JMenu createLanguage() { + JMenu language = new JMenu(getLocaleString("language", Locale.getDefault())); + + + { + JMenuItem English = new JMenuItem(getLocaleString("englishLang", Locale.getDefault())); + English.addActionListener((event) -> { + mainFrame.changeLocale(new Locale("en", "US")); + }); + + JMenuItem Russian = new JMenuItem(getLocaleString("russianLang", Locale.getDefault())); + Russian.addActionListener((event) -> { + mainFrame.changeLocale(new Locale("ru", "RU")); + }); + language.add(English); + language.add(Russian); + } + + return language; + } + + + public static String getLocaleString(String key, Locale locale) { + ResourceBundle rb; + rb = ResourceBundle.getBundle("resources", locale); + return rb.getString(key); + } + + protected static JMenu createExitButton() { + JMenu menu = new JMenu(getLocaleString("exit", Locale.getDefault())); + menu.setMnemonic(KeyEvent.VK_T); + + { + JMenuItem exit1 = new JMenuItem(getLocaleString("exit", Locale.getDefault()), KeyEvent.VK_S); + exit1.setFocusable(false); + exit1.addActionListener((event) -> { + Object[] options = {getLocaleString("yes", Locale.getDefault()), getLocaleString( "no", Locale.getDefault())}; + int n = JOptionPane + .showOptionDialog(null , getLocaleString("closeWindow", Locale.getDefault()), + getLocaleString("accept", Locale.getDefault()), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (n == 0) { + System.exit(0); + } + }); + menu.add(exit1); + } + return menu; + } + + + private static void setLookAndFeel(String className) { + try { + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(mainFrame); + } catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) { + } + } + +} + diff --git a/src/main/java/org/example/gui/MenuStart.java b/src/main/java/org/example/gui/MenuStart.java new file mode 100644 index 000000000..22b652d00 --- /dev/null +++ b/src/main/java/org/example/gui/MenuStart.java @@ -0,0 +1,39 @@ +package gui; + +import javax.swing.*; +import java.util.Objects; + +public class MenuStart { + public static int whatProfile(){ + Object[] options = {"save", + "create"}; + return JOptionPane.showOptionDialog(null, + "select", + "confirmation", + JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, + null, options, options[0]); + } + public static String createProfile(){ + JOptionPane newProfile = new JOptionPane("Ok", JOptionPane.PLAIN_MESSAGE, + JOptionPane.DEFAULT_OPTION); + + newProfile.setWantsInput(true); + JDialog dialog = newProfile.createDialog(null); + + newProfile.selectInitialValue(); + dialog.show(); + dialog.dispose(); + + Object result2 = newProfile.getInputValue(); + + if (result2 == JOptionPane.UNINITIALIZED_VALUE) { + return null; + } + else if (Objects.equals(result2.toString(), "")){ + return createProfile(); + } + else{ + return result2.toString(); + } + } +} diff --git a/robots/src/gui/RobotsProgram.java b/src/main/java/org/example/gui/RobotsProgram.java similarity index 100% rename from robots/src/gui/RobotsProgram.java rename to src/main/java/org/example/gui/RobotsProgram.java diff --git a/src/main/java/org/example/gui/Tree.java b/src/main/java/org/example/gui/Tree.java new file mode 100644 index 000000000..351566f71 --- /dev/null +++ b/src/main/java/org/example/gui/Tree.java @@ -0,0 +1,45 @@ +package gui; + +import java.awt.*; +import java.awt.image.ImageObserver; + +public class Tree { + private double x; + private double y; + private int health; + private static final int MAX_HEALTH = 100; + + public Tree(double x, double y) { + this.x = x; + this.y = y; + this.health = MAX_HEALTH; + } + + public double getX() { + return x; + } + + public double getY() { + return y; + } + + public int getHealth() { + return health; + } + + public void decreaseHealth() { + if (health > 0) { + health--; + } + } + + public boolean isRobotOnTree(double robotX, double robotY) { + double distance = Math.sqrt(Math.pow(robotX - x, 2) + Math.pow(robotY - y, 2)); + return distance < 30; + } + + public void paint(Graphics2D g, Image treeImage, Image trunkImage) { + Image imageToDraw = (health > 0) ? treeImage : trunkImage; + g.drawImage(imageToDraw, (int)x, (int)y, 32, 32, null); + } +} diff --git a/src/main/java/org/example/gui/drytree.png b/src/main/java/org/example/gui/drytree.png new file mode 100644 index 000000000..ec4649bf4 Binary files /dev/null and b/src/main/java/org/example/gui/drytree.png differ diff --git a/src/main/java/org/example/gui/grass.png b/src/main/java/org/example/gui/grass.png new file mode 100644 index 000000000..aa2d995a2 Binary files /dev/null and b/src/main/java/org/example/gui/grass.png differ diff --git a/src/main/java/org/example/gui/grasss.png b/src/main/java/org/example/gui/grasss.png new file mode 100644 index 000000000..1155836a5 Binary files /dev/null and b/src/main/java/org/example/gui/grasss.png differ diff --git a/src/main/java/org/example/gui/inter.java b/src/main/java/org/example/gui/inter.java new file mode 100644 index 000000000..14d10870f --- /dev/null +++ b/src/main/java/org/example/gui/inter.java @@ -0,0 +1,65 @@ +package gui; + +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; +import java.util.Locale; + +import static gui.MenuBar.getLocaleString; + +public abstract class inter extends JInternalFrame{ + public abstract void updateWindow(); + public inter(String str, boolean b, boolean b1, boolean b2, boolean b3){ + super(str, b, b1, b2, b3); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addInternalFrameListener(new InternalFrameListener() { + + @Override + public void internalFrameOpened(InternalFrameEvent e) { + + } + + @Override + public void internalFrameClosing(InternalFrameEvent event) { + Object[] options = {getLocaleString("yes", Locale.getDefault()), getLocaleString( "no", Locale.getDefault())}; + int n = JOptionPane + .showOptionDialog(event.getInternalFrame(), getLocaleString("closeWindow", Locale.getDefault()), + getLocaleString("accept", Locale.getDefault()), JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (n == 0) { + event.getInternalFrame().setVisible(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + } + + @Override + public void internalFrameClosed(InternalFrameEvent e) { + + } + + public void internalFrameIconified(InternalFrameEvent e) { + + } + + @Override + public void internalFrameDeiconified(InternalFrameEvent e) { + + } + + + @Override + public void internalFrameActivated(InternalFrameEvent e) { + + } + + @Override + public void internalFrameDeactivated(InternalFrameEvent e) { + + } + + }); + } + + +} diff --git a/src/main/java/org/example/gui/tree.png b/src/main/java/org/example/gui/tree.png new file mode 100644 index 000000000..ffebd350c Binary files /dev/null and b/src/main/java/org/example/gui/tree.png differ diff --git a/src/main/java/org/example/gui/trunk.png b/src/main/java/org/example/gui/trunk.png new file mode 100644 index 000000000..ba9d5f904 Binary files /dev/null and b/src/main/java/org/example/gui/trunk.png differ diff --git a/robots/src/log/LogChangeListener.java b/src/main/java/org/example/log/LogChangeListener.java similarity index 100% rename from robots/src/log/LogChangeListener.java rename to src/main/java/org/example/log/LogChangeListener.java diff --git a/robots/src/log/LogEntry.java b/src/main/java/org/example/log/LogEntry.java similarity index 100% rename from robots/src/log/LogEntry.java rename to src/main/java/org/example/log/LogEntry.java diff --git a/robots/src/log/LogLevel.java b/src/main/java/org/example/log/LogLevel.java similarity index 100% rename from robots/src/log/LogLevel.java rename to src/main/java/org/example/log/LogLevel.java diff --git a/src/main/java/org/example/log/LogWindowSource.java b/src/main/java/org/example/log/LogWindowSource.java new file mode 100644 index 000000000..a54304894 --- /dev/null +++ b/src/main/java/org/example/log/LogWindowSource.java @@ -0,0 +1,93 @@ +package log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * пофикшены: + * 1. утечка ресурсов (связанные слушатели оказываются удерживаемыми в памяти) + * 2. ограничения; раньше класс хранил активные сообщения лога, но в такой реализации он + * их лишь накапливал. Сейчас количество сообщений в логе было ограничено + * величиной m_iQueueLength (т.е. реально нужна очередь сообщений + * ограниченного размера) + * + * изменения: + * Используется ArrayBlockingQueue для хранения сообщений лога, что позволяет избежать утечек ресурсов и ограничить размер лога величиной m_iQueueLength. + * Избавлены от необходимости хранить все слушатели в виде массива, который нужно было пересоздавать каждый раз при добавлении или удалении слушателя. Вместо этого используется обычный список m_listeners, синхронизированный на запись/удаление. + * Используется offer() вместо add() при добавлении сообщения в очередь, чтобы избежать исключения в случае, если очередь заполнена. + * Метод size() теперь возвращает размер m_queue. + * Метод range() теперь проверяет, что count неотрицательное число и возвращает пустой список, если startFrom выходит за пределы очереди или count равен нулю. + * Метод range() теперь использует метод ArrayList.subList() непосредственно над m_queue, а не создает новый список, что улучшает производительность и уменьшает использование памяти. + * Метод all() теперь создает новый список из m_queue, вместо создания списка итератором. + */ +public class LogWindowSource { + private final int m_iQueueLength; + private final ArrayBlockingQueue m_queue; + private final List m_listeners; + private volatile LogChangeListener[] m_activeListeners; + + public LogWindowSource(int iQueueLength) { + m_iQueueLength = iQueueLength; + m_queue = new ArrayBlockingQueue<>(iQueueLength); + m_listeners = new ArrayList<>(); + } + + public void registerListener(LogChangeListener listener) { + synchronized(m_listeners) { + m_listeners.add(listener); + m_activeListeners = null; + } + } + + public void unregisterListener(LogChangeListener listener) { + synchronized(m_listeners) { + m_listeners.remove(listener); + m_activeListeners = null; + } + } + + public void append(LogLevel logLevel, String strMessage) { + boolean added = false; + while (!added) { + LogEntry entry = new LogEntry(logLevel, strMessage); + if (m_queue.offer(entry)) { + added = true; + if (m_queue.size() > m_iQueueLength) { + m_queue.remove(); + } + LogChangeListener[] activeListeners = m_activeListeners; + if (activeListeners == null) { + synchronized (m_listeners) { + if (m_activeListeners == null) { + activeListeners = m_listeners.toArray(new LogChangeListener[0]); + m_activeListeners = activeListeners; + } + } + } + for (LogChangeListener listener : activeListeners) { + listener.onLogChanged(); + } + } else { + m_queue.remove(); + } + } + } + + public int size() { + return m_queue.size(); + } + + public Iterable range(int startFrom, int count) { + if (startFrom < 0 || startFrom >= m_queue.size() || count <= 0) { + return Collections.emptyList(); + } + int indexTo = Math.min(startFrom + count, m_queue.size()); + return new ArrayList<>(m_queue).subList(startFrom, indexTo); + } + + public Iterable all() { + return new ArrayList<>(m_queue); + } +} \ No newline at end of file diff --git a/robots/src/log/Logger.java b/src/main/java/org/example/log/Logger.java similarity index 100% rename from robots/src/log/Logger.java rename to src/main/java/org/example/log/Logger.java diff --git a/src/main/java/org/example/saveProfile/SaveProfile.java b/src/main/java/org/example/saveProfile/SaveProfile.java new file mode 100644 index 000000000..dd337f343 --- /dev/null +++ b/src/main/java/org/example/saveProfile/SaveProfile.java @@ -0,0 +1,8 @@ +package saveProfile; + +import java.io.Serializable; + +public class SaveProfile implements Serializable { + + +} diff --git a/src/main/resources/resources_en_US.properties b/src/main/resources/resources_en_US.properties new file mode 100644 index 000000000..f0d6c85f4 --- /dev/null +++ b/src/main/resources/resources_en_US.properties @@ -0,0 +1,20 @@ +language = Language +yes = Yes +no = No +closeWindow = Close a Window? +accept = Confirmation +gamePlane = Playing field +exit = Exit +englishLang = English language +russianLang = Russian language +newString = New line +messageLog = Message to log +testCommand = Test commands +test = Tests +universityMap = Universal scheme +systemMap = System diagram +modeControl = Controlling the Application Display Mode +displayMode = Display Mode +protocolWorks = The protocol works +workProtocol = Work protocol +rrr =dfjkhfgljhjdfgh \ No newline at end of file diff --git a/src/main/resources/resources_ru_RU.properties b/src/main/resources/resources_ru_RU.properties new file mode 100644 index 000000000..3bd90cbb5 --- /dev/null +++ b/src/main/resources/resources_ru_RU.properties @@ -0,0 +1,19 @@ +language = \u042f\u0437\u044b\u043a +yes = \u0020\u0020\u0414\u0430 +no = \u041d\u0435\u0442 +closeWindow = \u0417\u0430\u043a\u0440\u044b\u0442\u044c\u0020\u043e\u043a\u043d\u043e\u003f +accept = \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 +gamePlane = \u0418\u0433\u0440\u043e\u0432\u043e\u0435\u0020\u043f\u043e\u043b\u0435 +exit = \u0412\u044b\u0439\u0442\u0438 +englishLang = \u0410\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439\u0020\u044f\u0437\u044b\u043a +russianLang = \u0420\u0443\u0441\u0441\u043a\u0438\u0439\u0020\u044f\u0437\u044b\u043a +newString = \u041d\u043e\u0432\u0430\u044f\u0020\u0441\u0442\u0440\u043e\u043a\u0430 +messageLog = \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u0020\u0432\u0020\u043b\u043e\u0433 +testCommand = \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0435\u0020\u043a\u043e\u043c\u0430\u043d\u0434\u044b +test = \u0422\u0435\u0441\u0442\u044b +universityMap = \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0430\u044f\u0020\u0441\u0445\u0435\u043c\u0430 +systemMap = \u0421\u0438\u0441\u0442\u0435\u043c\u043d\u0430\u044f\u0020\u0441\u0445\u0435\u043c\u0430 +modeControl = \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0020\u0440\u0435\u0436\u0438\u043c\u043e\u043c\u0020\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0020\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f +displayMode = \u0420\u0435\u0436\u0438\u043c\u0020\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f +protocolWorks = \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0020\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 +workProtocol = \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0020\u0440\u0430\u0431\u043e\u0442\u044b \ No newline at end of file