-
Notifications
You must be signed in to change notification settings - Fork 0
Start task 3 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Start task 3 #1
Changes from all commits
17510b6
e6b60ee
83d0262
33340f8
41ad220
069627a
a370a85
0b0863b
806a355
2c47c4c
75e4493
81cadba
ec0b6cc
50557b7
ebe5aca
1a72771
089875b
c4e425e
7a3f520
bb526be
9e19d4d
46378d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| /.metadata | ||
| /robots/.settings | ||
| /robots/bin | ||
| eclipse.bat | ||
| eclipse.bat | ||
| /out/ | ||
| /.idea/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package controllers; | ||
|
|
||
| import models.RobotModel; | ||
|
|
||
| import java.util.Timer; | ||
| import java.util.TimerTask; | ||
|
|
||
| public class RobotUpdateController { | ||
|
|
||
| private final Timer m_timer = initTimer(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Здесь не буду рассматривать как замечание, но обращу внимание, что в реальном коде так лучше не делать. Если в классе есть конструктор, то лучше все поля инициализировать именно там. Неудобно, когда часть полей инициализируется в конструкторе, а часть - вне него. Особенно, когда вне конструктора используется нетривиальная логика (как в данном случае - вызов даже отдельного метода для запуска таймера) |
||
| private RobotModel m_model; | ||
| private static Timer initTimer() { | ||
| Timer timer = new Timer("events generator", true); | ||
| return timer; | ||
| } | ||
| public RobotUpdateController(RobotModel model){ | ||
| m_model=model; | ||
| m_timer.schedule(new TimerTask() { | ||
| @Override | ||
| public void run() { | ||
| updateModel(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я бы прямо здесь вызывал метод m_mode.updatePos, без перевызова метода. |
||
| } | ||
| }, 0, 10); | ||
| } | ||
|
|
||
| public void updateModel(){ | ||
| m_model.updatePos(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package controllers; | ||
|
|
||
| import models.TargetModel; | ||
|
|
||
| import java.awt.*; | ||
|
|
||
| public class TargetPositionController { | ||
| private TargetModel m_model; | ||
|
|
||
|
|
||
| public TargetPositionController(TargetModel model){ | ||
| m_model=model; | ||
| } | ||
| public void setTargetPos(Point p){ | ||
| m_model.setPos(p.x,p.y); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,210 +1,78 @@ | ||
| package gui; | ||
|
|
||
| import java.awt.Color; | ||
| import java.awt.EventQueue; | ||
| import java.awt.Graphics; | ||
| import java.awt.Graphics2D; | ||
| import java.awt.Point; | ||
| import controllers.TargetPositionController; | ||
| import gui.drawModels.RobotRepresentation; | ||
| import gui.drawModels.TargetRepresentation; | ||
| import models.RobotModel; | ||
| import models.TargetModel; | ||
|
|
||
| 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 java.util.Observable; | ||
| import java.util.Observer; | ||
|
|
||
| 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() | ||
| { | ||
| public class GameVisualizer extends JPanel implements Observer { | ||
|
|
||
|
|
||
|
|
||
| private RobotRepresentation m_robotView; | ||
| private TargetRepresentation m_targetView; | ||
|
|
||
| private TargetPositionController m_targetController; | ||
| private RobotModel m_robot; | ||
|
|
||
|
|
||
|
|
||
|
|
||
| public GameVisualizer(TargetPositionController modelController, RobotModel model, TargetModel target) { | ||
|
|
||
| m_robot =model; | ||
| m_robotView = new RobotRepresentation(m_robot); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ну вот снова тот же нюанс, на который уже неоднократно я обращал внимание: слева - View, справа - Representation. Но схожие вещи надо называть одинаково! Читать код и проверять его правильность проще! |
||
| m_targetView = new TargetRepresentation(target); | ||
| m_targetController = modelController; | ||
|
|
||
| addMouseListener(new MouseAdapter() { | ||
| @Override | ||
| public void mouseClicked(MouseEvent e) | ||
| { | ||
| 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 setTargetPosition(Point p) { | ||
|
|
||
|
|
||
| m_targetController.setTargetPos(p); | ||
| } | ||
|
|
||
| protected void onRedrawEvent() | ||
| { | ||
|
|
||
| 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); | ||
| protected void onModelUpdateEvent() { | ||
| onRedrawEvent(); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| @Override | ||
| public void paint(Graphics g) | ||
| { | ||
| 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); | ||
| Graphics2D g2d = (Graphics2D) g; | ||
| m_robotView.draw(g2d); | ||
| m_targetView.draw(g2d); | ||
|
|
||
| } | ||
|
|
||
| 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); | ||
|
|
||
|
|
||
| @Override | ||
| public void update(Observable o, Object arg) { | ||
| EventQueue.invokeLater(this::onModelUpdateEvent); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Здесь реагируем на любые события, связанные с моделью. В частности, если, вдруг будет несколько разных событий или несколько разных моделей, то на все будет одна и та же реакция: перерисовать. Правильнее было бы при помощи кода фильтровать событий, на которые хочется реагировать. |
||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,10 @@ | ||
| package gui; | ||
|
|
||
| import controllers.TargetPositionController; | ||
| import models.RobotModel; | ||
| import models.TargetModel; | ||
|
|
||
|
|
||
| import java.awt.BorderLayout; | ||
|
|
||
| import javax.swing.JInternalFrame; | ||
|
|
@@ -8,10 +13,12 @@ | |
| public class GameWindow extends JInternalFrame | ||
| { | ||
| private final GameVisualizer m_visualizer; | ||
| public GameWindow() | ||
| public GameWindow(RobotModel model, TargetPositionController controller, TargetModel target) | ||
| { | ||
| super("Игровое поле", true, true, true, true); | ||
| m_visualizer = new GameVisualizer(); | ||
|
|
||
| m_visualizer = new GameVisualizer(controller,model,target); | ||
| model.addObserver(m_visualizer); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. И вот эту подписку тоже правильнее было бы сделать внутри кода инициализации визуализатора. Ведь это ему нужны оповещения от модели, а не окну. |
||
| JPanel panel = new JPanel(new BorderLayout()); | ||
| panel.add(m_visualizer, BorderLayout.CENTER); | ||
| getContentPane().add(panel); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,10 @@ | |
| import javax.swing.UIManager; | ||
| import javax.swing.UnsupportedLookAndFeelException; | ||
|
|
||
| import controllers.TargetPositionController; | ||
| import log.Logger; | ||
| import models.RobotModel; | ||
| import models.TargetModel; | ||
|
|
||
| /** | ||
| * Что требуется сделать: | ||
|
|
@@ -26,7 +29,7 @@ public class MainApplicationFrame extends JFrame | |
| { | ||
| private final JDesktopPane desktopPane = new JDesktopPane(); | ||
|
|
||
| public MainApplicationFrame() { | ||
| public MainApplicationFrame(RobotModel model, TargetPositionController controller, TargetModel target) { | ||
| //Make the big window be indented 50 pixels from each edge | ||
| //of the screen. | ||
| int inset = 50; | ||
|
|
@@ -36,13 +39,18 @@ public MainApplicationFrame() { | |
| screenSize.height - inset*2); | ||
|
|
||
| setContentPane(desktopPane); | ||
|
|
||
|
|
||
| LogWindow logWindow = createLogWindow(); | ||
| addWindow(logWindow); | ||
|
|
||
| GameWindow gameWindow = new GameWindow(); | ||
|
|
||
| GameWindow gameWindow = new GameWindow(model,controller,target); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. И снова код, где вместе идут модель, контроллер и модель. Это должно наводить на мысль, что эти сущности связаны и, вероятно, имеет смысл иметь какую-то более общую сущность, наподобие GameModel, агрегирующую эти отдельные модели. Или даже GameState, где смогли бы находиться и модели, и контроллеры. |
||
| gameWindow.setSize(400, 400); | ||
|
|
||
| PositionShowWindow coordWindow=new PositionShowWindow(model); | ||
| coordWindow.setSize(200,100); | ||
| addWindow(coordWindow); | ||
| addWindow(gameWindow); | ||
|
|
||
| setJMenuBar(generateMenuBar()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Код стал лучше, но все же продолжаем его улучшать с точки зрения читаемости.
Вот вопрос: а какая ответственность у этого класса?
У меня складывается такая формулировка (исходя из того, что в этом классе написано): этот класс формирует события, заставляющие робота обновить свое состояние. Так ведь?
Но тогда и название должно быть каким-то таким: RobotsMovementEventsGenerator (вместо Generator можно использовать слово Producer). Тогда из названия проще извлекается смысл кода. Либо надо этот смысл писать в комментариях, чтобы не было неоднозначности. И контроллером этот класс все же, как мне кажется, называть не очень уместно, так он не занимается какой-то трансляцией событий от пользователя в модель.
И если следовать моей идее, то в дальнейшем возможно следующее расширение: один такой класс может генерировать события для обновления всех роботов на игровом поле. То есть при формировании классов, планировании их ответственности желательно продумывать возможные пути развития кода.