Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
17510b6
initial configure
As-pasa Apr 17, 2023
e6b60ee
drawing logic incapsulated in drawModels, removed service math functi…
As-pasa Apr 18, 2023
83d0262
robot movement logic incapsulated into RobotModel class, unused funct…
As-pasa Apr 18, 2023
33340f8
robot internal variable dublicates removed from GameVisualizer, repla…
As-pasa Apr 18, 2023
41ad220
model update timer incapsulated into controller class, model update c…
As-pasa Apr 18, 2023
069627a
created PositionShowWinow, representing position of model
As-pasa Apr 18, 2023
a370a85
start task 3: robot rotation bug fixed
As-pasa Apr 21, 2023
0b0863b
window redraw for GameVisualizer and PosotionShowWindow moved to daem…
As-pasa Apr 22, 2023
806a355
added dependency (model, controller) for MainApplicationFrame
As-pasa Apr 22, 2023
2c47c4c
Controller package and Controller class renamed
As-pasa May 4, 2023
75e4493
setting position logic separated from update timer
As-pasa May 4, 2023
81cadba
redraw timer removed from GameVisualizer. Now redraw is triggered by …
As-pasa May 4, 2023
ec0b6cc
target point incapsulated into separate model, changed dependency for…
As-pasa May 4, 2023
50557b7
Drawable interface cleared from static functions. Added utils package
As-pasa May 4, 2023
ebe5aca
target model is now dependency for visualisation
As-pasa May 4, 2023
1a72771
getters in models changed to global getPos method. Potentially fixing…
As-pasa May 4, 2023
089875b
RobotModel class cleared from static math function, that now placed i…
As-pasa May 4, 2023
c4e425e
TargetModel now extends Observable
As-pasa May 5, 2023
7a3f520
refactor: CoordPair class renamed into Vector
As-pasa May 5, 2023
bb526be
Naming changes, changed signature for RobotRepresentation constructor…
As-pasa May 11, 2023
9e19d4d
vector class removed, added state hierarchie
As-pasa May 11, 2023
46378d6
renamed getPos method to getState in RobotModel and TargetModel
As-pasa May 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
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/
29 changes: 29 additions & 0 deletions robots/src/controllers/RobotUpdateController.java
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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Код стал лучше, но все же продолжаем его улучшать с точки зрения читаемости.
Вот вопрос: а какая ответственность у этого класса?
У меня складывается такая формулировка (исходя из того, что в этом классе написано): этот класс формирует события, заставляющие робота обновить свое состояние. Так ведь?
Но тогда и название должно быть каким-то таким: RobotsMovementEventsGenerator (вместо Generator можно использовать слово Producer). Тогда из названия проще извлекается смысл кода. Либо надо этот смысл писать в комментариях, чтобы не было неоднозначности. И контроллером этот класс все же, как мне кажется, называть не очень уместно, так он не занимается какой-то трансляцией событий от пользователя в модель.
И если следовать моей идее, то в дальнейшем возможно следующее расширение: один такой класс может генерировать события для обновления всех роботов на игровом поле. То есть при формировании классов, планировании их ответственности желательно продумывать возможные пути развития кода.


private final Timer m_timer = initTimer();

Choose a reason for hiding this comment

The 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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы прямо здесь вызывал метод m_mode.updatePos, без перевызова метода.
А еще в подобном коде обычно делается связь через слабую ссылку (т.е. тип m_model следовало бы сделать WeakReference), чтобы избежать ситуации, что мы по каким-то причинам решили отказаться от конкретного робота (удалить из памяти), но он остается в памяти и продолжает получать события из этого таймера.
Соответственно, тогда перед вызовом updatePos сначала получаем жесткую ссылку, проверяем, что она отлична от null. Если не null, то вызываем updatePos. Если null, вызываем метод calcel для отмены задания таймера.
Предлагаю попробовать это реализовать, чтобы опробовать работу со слабыми ссылками.

}
}, 0, 10);
}

public void updateModel(){
m_model.updatePos();
}
}
17 changes: 17 additions & 0 deletions robots/src/controllers/TargetPositionController.java
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);
}
}
236 changes: 52 additions & 184 deletions robots/src/gui/GameVisualizer.java
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);

Choose a reason for hiding this comment

The 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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь реагируем на любые события, связанные с моделью. В частности, если, вдруг будет несколько разных событий или несколько разных моделей, то на все будет одна и та же реакция: перерисовать. Правильнее было бы при помощи кода фильтровать событий, на которые хочется реагировать.

}

}
11 changes: 9 additions & 2 deletions robots/src/gui/GameWindow.java
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;
Expand All @@ -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);

Choose a reason for hiding this comment

The 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);
Expand Down
14 changes: 11 additions & 3 deletions robots/src/gui/MainApplicationFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import controllers.TargetPositionController;
import log.Logger;
import models.RobotModel;
import models.TargetModel;

/**
* Что требуется сделать:
Expand All @@ -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;
Expand All @@ -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);

Choose a reason for hiding this comment

The 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());
Expand Down
Loading