diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..ff55b3b49
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ com.example
+ robots
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.9
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.8.1
+ compile
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.36
+
+
+
+
\ No newline at end of file
diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java
index f82cfd8f8..43ecaee8b 100644
--- a/robots/src/gui/GameVisualizer.java
+++ b/robots/src/gui/GameVisualizer.java
@@ -1,210 +1,206 @@
-package gui;
+package src.gui;
-import java.awt.Color;
-import java.awt.EventQueue;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
+import java.awt.*;
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.*;
-import javax.swing.JPanel;
+public class GameVisualizer extends JPanel {
+
+ private static final double MAX_VELOCITY = 0.1;
+ private static final double MAX_ANGULAR_VELOCITY = 0.001;
-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 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()
- {
+
+ public GameVisualizer() {
+ initializeTimer();
+ addMouseListener(new MouseClickListener());
+ setDoubleBuffered(true);
+ setPreferredSize(new Dimension(400, 400));
+ setSize(500, 500);
+ }
+ public double getM_robotPositionX(){
+ return m_robotPositionX;
+ }
+
+ public double getM_robotPositionY() {
+ return m_robotPositionY;
+ }
+
+ private void initializeTimer() {
+ Timer timer = new Timer("events generator", true);
+ timer.schedule(new TimerTask() {
@Override
- public void run()
- {
+ public void run() {
onRedrawEvent();
}
}, 0, 50);
- m_timer.schedule(new TimerTask()
- {
+
+ timer.schedule(new TimerTask() {
@Override
- public void run()
- {
+ 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;
+ private class MouseClickListener extends MouseAdapter {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ setTargetPosition(e.getPoint());
+ repaint();
+ }
+ }
+
+ public void setTargetPosition(Point point) {
+ System.out.println("Click at: (" + point.x + ", " + point.y + ")");
+ m_targetPositionX = point.x;
+ m_targetPositionY = point.y;
+ System.out.println("Target set at: (" + m_targetPositionX + ", " + m_targetPositionY + ")");
+ repaint();
}
-
- protected void onRedrawEvent()
- {
+
+
+ private void onRedrawEvent() {
EventQueue.invokeLater(this::repaint);
}
- private static double distance(double x1, double y1, double x2, double y2)
- {
+ public void onModelUpdateEvent() {
+ double distanceToTarget = calculateDistance(m_targetPositionX, m_targetPositionY, m_robotPositionX, m_robotPositionY);
+ if (distanceToTarget < 0.5) {
+ return;
+ }
+
+ double angleToTarget = calculateAngleToTarget(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY);
+ double angularVelocity = calculateAngularVelocity(angleToTarget, m_robotDirection);
+
+ moveRobot(MAX_VELOCITY, angularVelocity, 10);
+ }
+
+ private double calculateDistance(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)
- {
+
+ private double calculateAngleToTarget(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;
+ return normalizeAngle(Math.atan2(diffY, diffX));
+ }
+
+ private double calculateAngularVelocity(double angleToTarget, double currentDirection) {
+ if (angleToTarget > currentDirection) {
+ return MAX_ANGULAR_VELOCITY;
+ } else if (angleToTarget < currentDirection) {
+ return -MAX_ANGULAR_VELOCITY;
}
-
- 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))
- {
+ return 0;
+ }
+
+ private void moveRobot(double velocity, double angularVelocity, double duration) {
+ velocity = applyLimits(velocity, 0, MAX_VELOCITY);
+ angularVelocity = applyLimits(angularVelocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);
+
+ 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))
- {
+
+ 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);
}
+
+ int width = getWidth();
+ int height = getHeight();
+
+ if (newX < 0) {
+ newX = width + newX;
+ } else if (newX > width) {
+ newX = newX - width;
+ }
+
+ if (newY < 0) {
+ newY = height + newY;
+ } else if (newY > height) {
+ newY = newY - height;
+ }
+
m_robotPositionX = newX;
m_robotPositionY = newY;
- double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration);
- m_robotDirection = newDirection;
+ m_robotDirection = normalizeAngle(m_robotDirection + angularVelocity * duration);
+ }
+
+ private double applyLimits(double value, double min, double max) {
+ return Math.max(min, Math.min(max, value));
}
- private static double asNormalizedRadians(double angle)
- {
- while (angle < 0)
- {
- angle += 2*Math.PI;
+ private double normalizeAngle(double angle) {
+ while (angle < 0) {
+ angle += 2 * Math.PI;
}
- while (angle >= 2*Math.PI)
- {
- 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);
+
+ private int round(double value) {
+ return (int) (value + 0.5);
}
-
+
@Override
- public void paint(Graphics g)
- {
+ public void paint(Graphics g) {
super.paint(g);
- Graphics2D g2d = (Graphics2D)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);
+
+ private void drawRobot(Graphics2D g, int x, int y, double direction) {
+ AffineTransform transform = AffineTransform.getRotateInstance(direction, x, y);
+ g.setTransform(transform);
+
g.setColor(Color.MAGENTA);
- fillOval(g, robotCenterX, robotCenterY, 30, 10);
+ fillOval(g, x, y, 30, 10);
+
g.setColor(Color.BLACK);
- drawOval(g, robotCenterX, robotCenterY, 30, 10);
+ drawOval(g, x, y, 30, 10);
+
g.setColor(Color.WHITE);
- fillOval(g, robotCenterX + 10, robotCenterY, 5, 5);
+ fillOval(g, x + 10, y, 5, 5);
+
g.setColor(Color.BLACK);
- drawOval(g, robotCenterX + 10, robotCenterY, 5, 5);
+ drawOval(g, x + 10, y, 5, 5);
}
-
- private void drawTarget(Graphics2D g, int x, int y)
- {
- AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0);
- g.setTransform(t);
+
+ private void drawTarget(Graphics2D g, int x, int y) {
+ AffineTransform transform = AffineTransform.getRotateInstance(0, 0, 0);
+ g.setTransform(transform);
+
g.setColor(Color.GREEN);
fillOval(g, x, y, 5, 5);
+
g.setColor(Color.BLACK);
drawOval(g, x, y, 5, 5);
}
-}
+
+ private void fillOval(Graphics g, int centerX, int centerY, int width, int height) {
+ g.fillOval(centerX - width / 2, centerY - height / 2, width, height);
+ }
+
+ private void drawOval(Graphics g, int centerX, int centerY, int width, int height) {
+ g.drawOval(centerX - width / 2, centerY - height / 2, width, height);
+ }
+}
\ No newline at end of file
diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java
index ecb63c00f..cfceb6284 100644
--- a/robots/src/gui/GameWindow.java
+++ b/robots/src/gui/GameWindow.java
@@ -1,20 +1,25 @@
-package gui;
+package src.gui;
-import java.awt.BorderLayout;
+import java.awt.*;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
-public class GameWindow extends JInternalFrame
-{
- private final GameVisualizer m_visualizer;
- public GameWindow()
- {
+public class GameWindow extends JInternalFrame {
+ public GameWindow() {
super("Игровое поле", true, true, true, true);
- m_visualizer = new GameVisualizer();
+ GameVisualizer m_visualizer = new GameVisualizer();
JPanel panel = new JPanel(new BorderLayout());
panel.add(m_visualizer, BorderLayout.CENTER);
getContentPane().add(panel);
pack();
+ setSize(500, 500);
+ System.out.println("GameWindow size: " + getSize());
+ System.out.println("GameVisualizer size: " + m_visualizer.getSize());
}
+
}
+
+
+
+
diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java
index 723d3e2fc..eac1cb236 100644
--- a/robots/src/gui/LogWindow.java
+++ b/robots/src/gui/LogWindow.java
@@ -1,50 +1,68 @@
-package gui;
+package src.gui;
import java.awt.BorderLayout;
+import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.TextArea;
-
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
-import log.LogChangeListener;
-import log.LogEntry;
-import log.LogWindowSource;
+import src.log.LogChangeListener;
+import src.log.LogEntry;
+import src.log.LogWindowSource;
-public class LogWindow extends JInternalFrame implements LogChangeListener
-{
- private LogWindowSource m_logSource;
- private TextArea m_logContent;
+public class LogWindow extends JInternalFrame implements LogChangeListener {
+ private final LogWindowSource m_logSource;
+ private final TextArea m_logContent;
- public LogWindow(LogWindowSource logSource)
- {
+ public LogWindow(LogWindowSource logSource) {
super("Протокол работы", true, true, true, true);
m_logSource = logSource;
- m_logSource.registerListener(this);
- m_logContent = new TextArea("");
- m_logContent.setSize(200, 500);
-
+ m_logSource.registerListener(this); // Подписка на изменения лога
+
+ m_logContent = new TextArea();
+ m_logContent.setEditable(false);
+ m_logContent.setPreferredSize(new Dimension(400, 300));
+
JPanel panel = new JPanel(new BorderLayout());
panel.add(m_logContent, BorderLayout.CENTER);
getContentPane().add(panel);
- pack();
+
+ addInternalFrameListener(new InternalFrameAdapter() {
+ @Override
+ public void internalFrameClosing(InternalFrameEvent e) {
+ dispose(); // закрываем окно корректно, чтобы слушатели не копились
+ }
+ });
+
updateLogContent();
+ pack();
+ setVisible(true);
}
- private void updateLogContent()
- {
+ private void updateLogContent() {
StringBuilder content = new StringBuilder();
- for (LogEntry entry : m_logSource.all())
- {
- content.append(entry.getMessage()).append("\n");
+ synchronized (m_logSource) {
+ for (LogEntry entry : m_logSource.all()) {
+ content.append(entry.getMessage()).append("\n");
+ }
}
m_logContent.setText(content.toString());
- m_logContent.invalidate();
+ m_logContent.repaint();
}
-
+
@Override
- public void onLogChanged()
- {
+ public void onLogChanged() {
EventQueue.invokeLater(this::updateLogContent);
}
+
+ @Override
+ public void dispose() {
+ // Удаляем LogWindow когда закрываем окно это чтобы память не утекала в трубу и метод
+ // переопределили потому что такой уже есть в JInternalFrame
+ m_logSource.unregisterListener(this);
+ super.dispose();
+ }
}
diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java
index 62e943ee1..26359affa 100644
--- a/robots/src/gui/MainApplicationFrame.java
+++ b/robots/src/gui/MainApplicationFrame.java
@@ -1,156 +1,59 @@
-package gui;
+package src.gui;
import java.awt.Dimension;
import java.awt.Toolkit;
-import java.awt.event.KeyEvent;
+import javax.swing.*;
-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 src.log.Logger;
-import log.Logger;
-
-/**
- * Что требуется сделать:
- * 1. Метод создания меню перегружен функционалом и трудно читается.
- * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс).
- *
- */
-public class MainApplicationFrame extends JFrame
-{
+public class MainApplicationFrame extends JFrame {
private final JDesktopPane desktopPane = new JDesktopPane();
-
+ private final MenuBuilder menuBuilder;
+
public MainApplicationFrame() {
- //Make the big window be indented 50 pixels from each edge
- //of the screen.
- int inset = 50;
+ int inset = 50;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
- setBounds(inset, inset,
- screenSize.width - inset*2,
- screenSize.height - inset*2);
+ 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);
+ gameWindow.setSize(400, 400);
addWindow(gameWindow);
- setJMenuBar(generateMenuBar());
- setDefaultCloseOperation(EXIT_ON_CLOSE);
+ this.menuBuilder = new MenuBuilder(this);
+ setJMenuBar(menuBuilder.buildMenuBar());
+
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ addWindowListener(new java.awt.event.WindowAdapter() {
+ @Override
+ public void windowClosing(java.awt.event.WindowEvent e) {
+ menuBuilder.confirmExit();
+ }
+ });
+
+ setVisible(true);
+ }
+
+ public JMenuBar getJMenuBar() {
+ return menuBuilder.buildMenuBar();
}
-
- protected LogWindow createLogWindow()
- {
+
+ protected LogWindow createLogWindow() {
LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource());
- logWindow.setLocation(10,10);
+ logWindow.setLocation(10, 10);
logWindow.setSize(300, 800);
setMinimumSize(logWindow.getSize());
logWindow.pack();
Logger.debug("Протокол работает");
return logWindow;
}
-
- protected void addWindow(JInternalFrame frame)
- {
+
+ 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/gui/MenuBuilder.java b/robots/src/gui/MenuBuilder.java
new file mode 100644
index 000000000..7ac6028cc
--- /dev/null
+++ b/robots/src/gui/MenuBuilder.java
@@ -0,0 +1,104 @@
+package src.gui;
+
+import javax.swing.*;
+import java.awt.event.KeyEvent;
+
+
+import src.log.Logger;
+
+public class MenuBuilder {
+
+ private final JMenuBar menuBar = new JMenuBar();
+ private final JFrame mainFrame;
+
+ public MenuBuilder(JFrame mainFrame) {
+ this.mainFrame = mainFrame;
+ }
+
+
+ public JMenuBar buildMenuBar() {
+ addFileMenu();
+ addLookAndFeelMenu();
+ addTestMenu();
+ return menuBar;
+ }
+
+ private void addFileMenu() {
+ JMenu fileMenu = new JMenu("Файл");
+ fileMenu.setMnemonic(KeyEvent.VK_F);
+ fileMenu.getAccessibleContext().setAccessibleDescription("Файл");
+
+ addMenuItem(fileMenu, "Выход", KeyEvent.VK_X, () -> {
+ int confirmed = JOptionPane.showOptionDialog(
+ null, "Вы уверены?", "подтверждение выхода",
+ JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ new Object[]{"Да", "Нет"}, "Нет");
+ if (confirmed == JOptionPane.YES_OPTION) {
+ System.exit(0);
+ }
+ });
+
+ menuBar.add(fileMenu);
+ }
+
+ private void addLookAndFeelMenu() {
+ JMenu lookAndFeelMenu = new JMenu("Режим отображения");
+ lookAndFeelMenu.setMnemonic(KeyEvent.VK_V);
+ lookAndFeelMenu.getAccessibleContext().setAccessibleDescription(
+ "Управление режимом отображения приложения");
+
+ addMenuItem(lookAndFeelMenu, "Системная схема", KeyEvent.VK_S, () -> {
+ setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ });
+
+ addMenuItem(lookAndFeelMenu, "Универсальная схема", KeyEvent.VK_U, () -> {
+ setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
+ });
+
+ menuBar.add(lookAndFeelMenu);
+ }
+
+ private void addTestMenu() {
+ JMenu testMenu = new JMenu("Тесты");
+ testMenu.setMnemonic(KeyEvent.VK_T);
+ testMenu.getAccessibleContext().setAccessibleDescription(
+ "Тестовые команды");
+
+ addMenuItem(testMenu, "Сообщение в лог", KeyEvent.VK_S, () -> {
+ Logger.debug("Новая строка");
+ });
+
+ menuBar.add(testMenu);
+ }
+
+ private void addMenuItem(JMenu menu, String text, int mnemonic, Runnable action) {
+ JMenuItem menuItem = new JMenuItem(text, mnemonic);
+ menuItem.addActionListener(e -> action.run());
+ menu.add(menuItem);
+ }
+
+ private void setLookAndFeel(String className) {
+ try {
+ UIManager.setLookAndFeel(className);
+ SwingUtilities.updateComponentTreeUI((JFrame) menuBar.getTopLevelAncestor());
+ } catch (Exception e) {
+ // Игнорируем ошибки
+ }
+ }
+
+ public void confirmExit() {
+ int confirmed = JOptionPane.showOptionDialog(
+ mainFrame,
+ "Вы уверены, что хотите выйти?",
+ "Подтверждение выхода",
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ new Object[]{"Да", "Нет"},
+ "Нет"
+ );
+ if (confirmed == JOptionPane.YES_OPTION) {
+ System.exit(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java
index ae0930a8b..dca8787ed 100644
--- a/robots/src/gui/RobotsProgram.java
+++ b/robots/src/gui/RobotsProgram.java
@@ -1,25 +1,23 @@
-package gui;
+package src.gui;
import java.awt.Frame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
-public class RobotsProgram
-{
+public class RobotsProgram {
public static void main(String[] args) {
- try {
- UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
-// UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
-// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-// UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
- } catch (Exception e) {
- e.printStackTrace();
- }
- SwingUtilities.invokeLater(() -> {
- MainApplicationFrame frame = new MainApplicationFrame();
- frame.pack();
- frame.setVisible(true);
- frame.setExtendedState(Frame.MAXIMIZED_BOTH);
- });
- }}
+ try {
+ UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ SwingUtilities.invokeLater(() -> {
+ MainApplicationFrame frame = new MainApplicationFrame();
+ frame.pack();
+ frame.setVisible(true);
+ frame.setExtendedState(Frame.MAXIMIZED_BOTH);
+ });
+ }
+}
diff --git a/robots/src/log/LogChangeListener.java b/robots/src/log/LogChangeListener.java
index 0b0fb85dd..12065e7de 100644
--- a/robots/src/log/LogChangeListener.java
+++ b/robots/src/log/LogChangeListener.java
@@ -1,6 +1,5 @@
-package log;
+package src.log;
-public interface LogChangeListener
-{
- public void onLogChanged();
+public interface LogChangeListener {
+ void onLogChanged();
}
diff --git a/robots/src/log/LogEntry.java b/robots/src/log/LogEntry.java
index 3d9147107..a58cb15ee 100644
--- a/robots/src/log/LogEntry.java
+++ b/robots/src/log/LogEntry.java
@@ -1,23 +1,19 @@
-package log;
+package src.log;
-public class LogEntry
-{
- private LogLevel m_logLevel;
- private String m_strMessage;
-
- public LogEntry(LogLevel logLevel, String strMessage)
- {
+public class LogEntry {
+ private final LogLevel m_logLevel;
+ private final String m_strMessage;
+
+ public LogEntry(LogLevel logLevel, String strMessage) {
m_strMessage = strMessage;
m_logLevel = logLevel;
}
-
- public String getMessage()
- {
+
+ public String getMessage() {
return m_strMessage;
}
-
- public LogLevel getLevel()
- {
+
+ public LogLevel getLevel() {
return m_logLevel;
}
}
diff --git a/robots/src/log/LogLevel.java b/robots/src/log/LogLevel.java
index 582d010cc..28b7db11b 100644
--- a/robots/src/log/LogLevel.java
+++ b/robots/src/log/LogLevel.java
@@ -1,23 +1,20 @@
-package log;
+package src.log;
-public enum LogLevel
-{
+public enum LogLevel {
Trace(0),
Debug(1),
Info(2),
Warning(3),
Error(4),
Fatal(5);
-
- private int m_iLevel;
-
- private LogLevel(int iLevel)
- {
+
+ private final int m_iLevel;
+
+ LogLevel(int iLevel) {
m_iLevel = iLevel;
}
-
- public int level()
- {
+
+ public int level() {
return m_iLevel;
}
}
diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java
index ca0ce4426..9d82a8ecb 100644
--- a/robots/src/log/LogWindowSource.java
+++ b/robots/src/log/LogWindowSource.java
@@ -1,89 +1,93 @@
-package log;
+package src.log;
-import java.util.ArrayList;
-import java.util.Collections;
+
+import java.lang.ref.WeakReference;
+import java.util.*;
/**
* Что починить:
* 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются
* удерживаемыми в памяти)
- * 2. Этот класс хранит активные сообщения лога, но в такой реализации он
- * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено
- * величиной m_iQueueLength (т.е. реально нужна очередь сообщений
- * ограниченного размера)
+ * 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)
- {
+public class LogWindowSource {
+ private final int m_iQueueLength;
+ private final LinkedList m_messages;
+ private final List> m_listeners;
+
+ public LogWindowSource(int iQueueLength) {
m_iQueueLength = iQueueLength;
- m_messages = new ArrayList(iQueueLength);
- m_listeners = new ArrayList();
+ m_messages = new LinkedList<>();
+ m_listeners = new ArrayList<>();
+ }
+
+ public void registerListener(LogChangeListener listener) {
+ synchronized (m_listeners) {
+ m_listeners.add(new WeakReference<>(listener));
+ }
}
-
- 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.removeIf(ref -> {
+ LogChangeListener l = ref.get();
+ return l == null || l == listener;
+ });
}
}
-
- public void unregisterListener(LogChangeListener listener)
- {
- synchronized(m_listeners)
- {
- m_listeners.remove(listener);
- m_activeListeners = null;
+
+ public void clearListeners() {
+ synchronized (m_listeners) {
+ m_listeners.clear();
}
}
-
- public void append(LogLevel logLevel, String strMessage)
- {
+
+ 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;
+ synchronized (m_messages) {
+ m_messages.addLast(entry);
+ if (m_messages.size() > m_iQueueLength) {
+ m_messages.removeFirst();
+ }
+ }
+
+
+ List activeListeners = new ArrayList<>();
+ synchronized (m_listeners) {
+ Iterator> iterator = m_listeners.iterator();
+ while (iterator.hasNext()) {
+ LogChangeListener listener = iterator.next().get();
+ if (listener != null) {
+ activeListeners.add(listener);
+ } else {
+ iterator.remove(); // Удаляем мёртвые ссылки
}
}
}
- for (LogChangeListener listener : activeListeners)
- {
+
+ for (LogChangeListener listener : activeListeners) {
listener.onLogChanged();
}
}
-
- public int size()
- {
+
+ public int size() {
return m_messages.size();
}
- public Iterable range(int startFrom, int count)
- {
- if (startFrom < 0 || startFrom >= m_messages.size())
- {
- return Collections.emptyList();
+ public Iterable range(int startFrom, int count) {
+ synchronized (m_messages) {
+ if (startFrom < 0 || startFrom >= m_messages.size()) {
+ return Collections.emptyList();
+ }
+ int indexTo = Math.min(startFrom + count, m_messages.size());
+ return new ArrayList<>(m_messages.subList(startFrom, indexTo));
}
- int indexTo = Math.min(startFrom + count, m_messages.size());
- return m_messages.subList(startFrom, indexTo);
}
- public Iterable all()
- {
+ public Iterable all() {
return m_messages;
}
-}
+}
\ No newline at end of file
diff --git a/robots/src/log/Logger.java b/robots/src/log/Logger.java
index b008a5d01..03db58e4f 100644
--- a/robots/src/log/Logger.java
+++ b/robots/src/log/Logger.java
@@ -1,28 +1,23 @@
-package log;
+package src.log;
-public final class Logger
-{
+public final class Logger {
private static final LogWindowSource defaultLogSource;
+
static {
defaultLogSource = new LogWindowSource(100);
}
-
- private Logger()
- {
- }
- public static void debug(String strMessage)
- {
+
+ public static void debug(String strMessage) {
defaultLogSource.append(LogLevel.Debug, strMessage);
}
-
- public static void error(String strMessage)
- {
+
+
+ public static void error(String strMessage) {
defaultLogSource.append(LogLevel.Error, strMessage);
}
- public static LogWindowSource getDefaultLogSource()
- {
+ public static LogWindowSource getDefaultLogSource() {
return defaultLogSource;
}
}
diff --git a/robots/tests/GameVisualizerTest.java b/robots/tests/GameVisualizerTest.java
new file mode 100644
index 000000000..24ac93b4c
--- /dev/null
+++ b/robots/tests/GameVisualizerTest.java
@@ -0,0 +1,36 @@
+package tests;
+
+import src.gui.GameVisualizer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.awt.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class GameVisualizerTest {
+
+ private GameVisualizer visualizer;
+
+ @BeforeEach
+ void setUp() {
+ visualizer = new GameVisualizer();
+ }
+
+ @Test
+ void testRobotMovesToTarget() {
+ visualizer.setTargetPosition(new Point(200, 200));
+ visualizer.onModelUpdateEvent();
+
+ assertNotEquals(100, visualizer.getM_robotPositionX());
+ assertNotEquals(100, visualizer.getM_robotPositionY());
+
+ for (int i = 0; i < 1000; i++) {
+ visualizer.onModelUpdateEvent();
+ }
+
+
+ assertEquals(200, visualizer.getM_robotPositionX(), 1.0);
+ assertEquals(200, visualizer.getM_robotPositionY(), 1.0);
+ }
+}
\ No newline at end of file
diff --git a/robots/tests/MainApplicationFrameTest.java b/robots/tests/MainApplicationFrameTest.java
new file mode 100644
index 000000000..d95b45ab4
--- /dev/null
+++ b/robots/tests/MainApplicationFrameTest.java
@@ -0,0 +1,32 @@
+package tests;
+
+import src.gui.MainApplicationFrame;
+import org.junit.jupiter.api.Test;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowEvent;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MainApplicationFrameTest {
+
+ @Test
+ void testExitConfirmation() {
+ MainApplicationFrame frame = new MainApplicationFrame();
+
+ frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
+
+ Window[] windows = Window.getWindows();
+ boolean confirmationDialogFound = false;
+ for (Window window : windows) {
+ if (window instanceof JDialog) {
+ JDialog dialog = (JDialog) window;
+ if (dialog.getTitle().equals("Подтверждение выхода")) {
+ confirmationDialogFound = true;
+ break;
+ }
+ }
+ }
+ assertTrue(confirmationDialogFound, "Диалог подтверждения выхода не найден");
+ }
+}
\ No newline at end of file
diff --git a/robots/tests/MenuBuilderTest.java b/robots/tests/MenuBuilderTest.java
new file mode 100644
index 000000000..cc23acd56
--- /dev/null
+++ b/robots/tests/MenuBuilderTest.java
@@ -0,0 +1,20 @@
+package tests;
+
+import src.gui.MainApplicationFrame;
+import org.junit.jupiter.api.Test;
+import javax.swing.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MenuBuilderTest {
+
+ @Test
+ void testFileMenuExists() {
+ MainApplicationFrame frame = new MainApplicationFrame();
+ JMenuBar menuBar = frame.getJMenuBar();
+
+ JMenu fileMenu = menuBar.getMenu(0);
+ assertNotNull(fileMenu);
+ assertEquals("Файл", fileMenu.getText());
+ }
+}
\ No newline at end of file