From f1f96f62ecf7a664db96094b9eaf0e209865dc7d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 24 Feb 2025 01:34:50 +0500 Subject: [PATCH 1/3] Task1.1 --- robots/src/gui/GameVisualizer.java | 272 +++++++++++------------ robots/src/gui/GameWindow.java | 13 +- robots/src/gui/LogWindow.java | 60 +++-- robots/src/gui/MainApplicationFrame.java | 142 ++---------- robots/src/gui/MenuBuilder.java | 83 +++++++ robots/src/gui/RobotsProgram.java | 32 ++- robots/src/log/LogChangeListener.java | 5 +- robots/src/log/LogEntry.java | 22 +- robots/src/log/LogLevel.java | 17 +- robots/src/log/LogWindowSource.java | 124 ++++++----- robots/src/log/Logger.java | 21 +- 11 files changed, 383 insertions(+), 408 deletions(-) create mode 100644 robots/src/gui/MenuBuilder.java diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index f82cfd8f8..4086c5b54 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -10,201 +10,187 @@ 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); + } + + 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(); + } } - - protected void onRedrawEvent() - { + + private void setTargetPosition(Point point) { + m_targetPositionX = point.x; + m_targetPositionY = point.y; + } + + private void onRedrawEvent() { EventQueue.invokeLater(this::repaint); } - private static double distance(double x1, double y1, double x2, double y2) - { + private 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; + } else if (newX > width) { + newX = 0; + } + + if (newY < 0) { + newY = height; + } else if (newY > height) { + newY = 0; + } m_robotPositionX = newX; m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; + m_robotDirection = normalizeAngle(m_robotDirection + angularVelocity * duration); + } - private static double asNormalizedRadians(double angle) - { - while (angle < 0) - { - angle += 2*Math.PI; + private double applyLimits(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + 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..0b26898ab 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -5,16 +5,17 @@ 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(); } } + + + + diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java index 723d3e2fc..e9fbcc1d4 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/gui/LogWindow.java @@ -1,50 +1,68 @@ package 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; -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..3c144f752 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,155 +2,53 @@ 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 -{ +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; + + int inset = 50; Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); + 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()); + + setJMenuBar(new MenuBuilder().buildMenuBar()); + + setDefaultCloseOperation(EXIT_ON_CLOSE); + setVisible(true); } - - 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 - } - } -} +} \ No newline at end of file diff --git a/robots/src/gui/MenuBuilder.java b/robots/src/gui/MenuBuilder.java new file mode 100644 index 000000000..2628aaf9d --- /dev/null +++ b/robots/src/gui/MenuBuilder.java @@ -0,0 +1,83 @@ +package gui; + +import javax.swing.*; +import java.awt.event.KeyEvent; + + +import log.Logger; + +public class MenuBuilder { + + private final JMenuBar menuBar = new JMenuBar(); + + + 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) { + // Игнорируем ошибки + } + } +} \ No newline at end of file diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index ae0930a8b..e080c7266 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -5,21 +5,19 @@ 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..043418064 100644 --- a/robots/src/log/LogChangeListener.java +++ b/robots/src/log/LogChangeListener.java @@ -1,6 +1,5 @@ package 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..3c33cc36d 100644 --- a/robots/src/log/LogEntry.java +++ b/robots/src/log/LogEntry.java @@ -1,23 +1,19 @@ package 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..529a80974 100644 --- a/robots/src/log/LogLevel.java +++ b/robots/src/log/LogLevel.java @@ -1,23 +1,20 @@ package 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..cd4c46f95 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -1,89 +1,93 @@ package 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..ac9f034e2 100644 --- a/robots/src/log/Logger.java +++ b/robots/src/log/Logger.java @@ -1,28 +1,23 @@ package 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; } } From bc71bec914c7961f0a7b218c1a14a6473f690a1e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 3 Mar 2025 21:10:19 +0500 Subject: [PATCH 2/3] Task1.2 --- robots/src/gui/GameVisualizer.java | 34 ++++++++++++-------- robots/src/gui/GameWindow.java | 6 +++- robots/src/gui/MainApplicationFrame.java | 29 +++++++++-------- robots/src/gui/MenuBuilder.java | 21 +++++++++++++ robots/tests/GameVisualizerTest.java | 36 ++++++++++++++++++++++ robots/tests/MainApplicationFrameTest.java | 31 +++++++++++++++++++ robots/tests/MenuBuilderTest.java | 19 ++++++++++++ 7 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 robots/tests/GameVisualizerTest.java create mode 100644 robots/tests/MainApplicationFrameTest.java create mode 100644 robots/tests/MenuBuilderTest.java diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java index 4086c5b54..d5217ea65 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -1,10 +1,6 @@ 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.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; @@ -28,6 +24,15 @@ 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() { @@ -55,16 +60,20 @@ public void mouseClicked(MouseEvent e) { } } - private void setTargetPosition(Point point) { + 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(); } + private void onRedrawEvent() { EventQueue.invokeLater(this::repaint); } - private void onModelUpdateEvent() { + public void onModelUpdateEvent() { double distanceToTarget = calculateDistance(m_targetPositionX, m_targetPositionY, m_robotPositionX, m_robotPositionY); if (distanceToTarget < 0.5) { return; @@ -112,24 +121,25 @@ private void moveRobot(double velocity, double angularVelocity, double duration) 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 = width + newX; } else if (newX > width) { - newX = 0; + newX = newX - width; } if (newY < 0) { - newY = height; + newY = height + newY; } else if (newY > height) { - newY = 0; + newY = newY - height; } + m_robotPositionX = newX; m_robotPositionY = newY; m_robotDirection = normalizeAngle(m_robotDirection + angularVelocity * duration); - } private double applyLimits(double value, double min, double max) { diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index 0b26898ab..61f2748aa 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,6 +1,6 @@ package gui; -import java.awt.BorderLayout; +import java.awt.*; import javax.swing.JInternalFrame; import javax.swing.JPanel; @@ -13,7 +13,11 @@ public GameWindow() { 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/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 3c144f752..02ab125f5 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -2,26 +2,21 @@ import java.awt.Dimension; import java.awt.Toolkit; -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; +import javax.swing.*; import log.Logger; public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); + private final MenuBuilder menuBuilder; public MainApplicationFrame() { - 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); @@ -29,14 +24,24 @@ public MainApplicationFrame() { gameWindow.setSize(400, 400); addWindow(gameWindow); + this.menuBuilder = new MenuBuilder(this); + setJMenuBar(menuBuilder.buildMenuBar()); - setJMenuBar(new MenuBuilder().buildMenuBar()); - + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent e) { + menuBuilder.confirmExit(); + } + }); - setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } + public JMenuBar getJMenuBar() { + return menuBuilder.buildMenuBar(); + } + protected LogWindow createLogWindow() { LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); logWindow.setLocation(10, 10); @@ -51,4 +56,4 @@ protected void addWindow(JInternalFrame frame) { desktopPane.add(frame); frame.setVisible(true); } -} \ No newline at end of file +} diff --git a/robots/src/gui/MenuBuilder.java b/robots/src/gui/MenuBuilder.java index 2628aaf9d..b0f21c6fa 100644 --- a/robots/src/gui/MenuBuilder.java +++ b/robots/src/gui/MenuBuilder.java @@ -9,6 +9,11 @@ public class MenuBuilder { private final JMenuBar menuBar = new JMenuBar(); + private final JFrame mainFrame; + + public MenuBuilder(JFrame mainFrame) { + this.mainFrame = mainFrame; + } public JMenuBar buildMenuBar() { @@ -80,4 +85,20 @@ private void setLookAndFeel(String className) { // Игнорируем ошибки } } + + 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/tests/GameVisualizerTest.java b/robots/tests/GameVisualizerTest.java new file mode 100644 index 000000000..ca709210e --- /dev/null +++ b/robots/tests/GameVisualizerTest.java @@ -0,0 +1,36 @@ + + +import 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..f706f88ce --- /dev/null +++ b/robots/tests/MainApplicationFrameTest.java @@ -0,0 +1,31 @@ + +import 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..2fae62a11 --- /dev/null +++ b/robots/tests/MenuBuilderTest.java @@ -0,0 +1,19 @@ + +import 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 From 440f98e465d860d7f1258ae2055248be8c58c34d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 30 Mar 2025 17:55:28 +0500 Subject: [PATCH 3/3] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B5=D1=85=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BD=D0=B0=20pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 35 ++++++++++++++++++++++ robots/src/gui/GameVisualizer.java | 2 +- robots/src/gui/GameWindow.java | 2 +- robots/src/gui/LogWindow.java | 8 ++--- robots/src/gui/MainApplicationFrame.java | 4 +-- robots/src/gui/MenuBuilder.java | 4 +-- robots/src/gui/RobotsProgram.java | 2 +- robots/src/log/LogChangeListener.java | 2 +- robots/src/log/LogEntry.java | 2 +- robots/src/log/LogLevel.java | 2 +- robots/src/log/LogWindowSource.java | 2 +- robots/src/log/Logger.java | 2 +- robots/tests/GameVisualizerTest.java | 4 +-- robots/tests/MainApplicationFrameTest.java | 3 +- robots/tests/MenuBuilderTest.java | 3 +- 15 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 pom.xml 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 d5217ea65..43ecaee8b 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/robots/src/gui/GameVisualizer.java @@ -1,4 +1,4 @@ -package gui; +package src.gui; import java.awt.*; import java.awt.event.MouseAdapter; diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java index 61f2748aa..cfceb6284 100644 --- a/robots/src/gui/GameWindow.java +++ b/robots/src/gui/GameWindow.java @@ -1,4 +1,4 @@ -package gui; +package src.gui; import java.awt.*; diff --git a/robots/src/gui/LogWindow.java b/robots/src/gui/LogWindow.java index e9fbcc1d4..eac1cb236 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/gui/LogWindow.java @@ -1,4 +1,4 @@ -package gui; +package src.gui; import java.awt.BorderLayout; import java.awt.Dimension; @@ -9,9 +9,9 @@ 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 final LogWindowSource m_logSource; diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java index 02ab125f5..26359affa 100644 --- a/robots/src/gui/MainApplicationFrame.java +++ b/robots/src/gui/MainApplicationFrame.java @@ -1,10 +1,10 @@ -package gui; +package src.gui; import java.awt.Dimension; import java.awt.Toolkit; import javax.swing.*; -import log.Logger; +import src.log.Logger; public class MainApplicationFrame extends JFrame { private final JDesktopPane desktopPane = new JDesktopPane(); diff --git a/robots/src/gui/MenuBuilder.java b/robots/src/gui/MenuBuilder.java index b0f21c6fa..7ac6028cc 100644 --- a/robots/src/gui/MenuBuilder.java +++ b/robots/src/gui/MenuBuilder.java @@ -1,10 +1,10 @@ -package gui; +package src.gui; import javax.swing.*; import java.awt.event.KeyEvent; -import log.Logger; +import src.log.Logger; public class MenuBuilder { diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java index e080c7266..dca8787ed 100644 --- a/robots/src/gui/RobotsProgram.java +++ b/robots/src/gui/RobotsProgram.java @@ -1,4 +1,4 @@ -package gui; +package src.gui; import java.awt.Frame; diff --git a/robots/src/log/LogChangeListener.java b/robots/src/log/LogChangeListener.java index 043418064..12065e7de 100644 --- a/robots/src/log/LogChangeListener.java +++ b/robots/src/log/LogChangeListener.java @@ -1,4 +1,4 @@ -package log; +package src.log; public interface LogChangeListener { void onLogChanged(); diff --git a/robots/src/log/LogEntry.java b/robots/src/log/LogEntry.java index 3c33cc36d..a58cb15ee 100644 --- a/robots/src/log/LogEntry.java +++ b/robots/src/log/LogEntry.java @@ -1,4 +1,4 @@ -package log; +package src.log; public class LogEntry { private final LogLevel m_logLevel; diff --git a/robots/src/log/LogLevel.java b/robots/src/log/LogLevel.java index 529a80974..28b7db11b 100644 --- a/robots/src/log/LogLevel.java +++ b/robots/src/log/LogLevel.java @@ -1,4 +1,4 @@ -package log; +package src.log; public enum LogLevel { Trace(0), diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java index cd4c46f95..9d82a8ecb 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/log/LogWindowSource.java @@ -1,4 +1,4 @@ -package log; +package src.log; import java.lang.ref.WeakReference; diff --git a/robots/src/log/Logger.java b/robots/src/log/Logger.java index ac9f034e2..03db58e4f 100644 --- a/robots/src/log/Logger.java +++ b/robots/src/log/Logger.java @@ -1,4 +1,4 @@ -package log; +package src.log; public final class Logger { private static final LogWindowSource defaultLogSource; diff --git a/robots/tests/GameVisualizerTest.java b/robots/tests/GameVisualizerTest.java index ca709210e..24ac93b4c 100644 --- a/robots/tests/GameVisualizerTest.java +++ b/robots/tests/GameVisualizerTest.java @@ -1,6 +1,6 @@ +package tests; - -import gui.GameVisualizer; +import src.gui.GameVisualizer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/robots/tests/MainApplicationFrameTest.java b/robots/tests/MainApplicationFrameTest.java index f706f88ce..d95b45ab4 100644 --- a/robots/tests/MainApplicationFrameTest.java +++ b/robots/tests/MainApplicationFrameTest.java @@ -1,5 +1,6 @@ +package tests; -import gui.MainApplicationFrame; +import src.gui.MainApplicationFrame; import org.junit.jupiter.api.Test; import javax.swing.*; import java.awt.*; diff --git a/robots/tests/MenuBuilderTest.java b/robots/tests/MenuBuilderTest.java index 2fae62a11..cc23acd56 100644 --- a/robots/tests/MenuBuilderTest.java +++ b/robots/tests/MenuBuilderTest.java @@ -1,5 +1,6 @@ +package tests; -import gui.MainApplicationFrame; +import src.gui.MainApplicationFrame; import org.junit.jupiter.api.Test; import javax.swing.*;