diff --git a/robots/src/application/Main.java b/robots/src/application/Main.java new file mode 100644 index 000000000..ee65706a6 --- /dev/null +++ b/robots/src/application/Main.java @@ -0,0 +1,35 @@ +package application; + +import application.view.GameView; +import application.view.GameWindow; +import application.model.GameModel; +import application.viewModel.ViewModel; + +import javax.swing.*; +import java.awt.*; + +public class Main +{ + public static void main(String[] args) + { + try + { + UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf"); + } + catch (Exception e) + { + e.printStackTrace(); + } + GameModel gameModel = new GameModel(); + GameView gameView = new GameView(gameModel); + GameWindow gameWindow = new GameWindow(gameView); + ViewModel viewModel = new ViewModel(gameModel, gameWindow); + + SwingUtilities.invokeLater(() -> { + MainApplicationFrame frame = new MainApplicationFrame(viewModel); + frame.pack(); + frame.setVisible(true); + frame.setExtendedState(Frame.MAXIMIZED_BOTH); + }); + } +} diff --git a/robots/src/application/MainApplicationFrame.java b/robots/src/application/MainApplicationFrame.java new file mode 100644 index 000000000..f8c8ae293 --- /dev/null +++ b/robots/src/application/MainApplicationFrame.java @@ -0,0 +1,114 @@ +package application; + +import application.view.GameWindow; +import application.log.LogWindow; +import application.log.Logger; +import application.viewModel.ViewModel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; + +public class MainApplicationFrame extends JFrame +{ + private final JDesktopPane desktopPane = new JDesktopPane(); + + public MainApplicationFrame(ViewModel gameViewModel) + { + + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset * 2, + screenSize.height - inset * 2); + + setContentPane(desktopPane); + + LogWindow logWindow = createLogWindow(); + addWindow(logWindow); + + GameWindow gameWindow = gameViewModel.getGameWindow(); + addWindow(gameWindow); + + setJMenuBar(generateMenuBar()); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + + protected LogWindow createLogWindow() + { + LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); + logWindow.setLocation(10, 10); + logWindow.setSize(300, 800); + setMinimumSize(logWindow.getSize()); + logWindow.pack(); + Logger.debug("Протокол работает"); + return logWindow; + } + + protected void addWindow(JInternalFrame frame) + { + desktopPane.add(frame); + frame.setVisible(true); + } + + private JMenu createMenu(String nameOfMenu, String description) + { + JMenu lookAndFeelMenu = new JMenu(nameOfMenu); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + description); + return lookAndFeelMenu; + } + + private JMenuItem createMenuItem(String name, ActionListener l) + { + JMenuItem systemLookAndFeel = new JMenuItem(name, KeyEvent.VK_S); + systemLookAndFeel.addActionListener(l); + + return systemLookAndFeel; + } + + private JMenuBar generateMenuBar() + { + JMenuBar menuBar = new JMenuBar(); + + JMenu lookAndFeelMenu = createMenu("Режим отображения", + "Управление режимом отображения приложения"); + JMenuItem systemLookAndFeel = createMenuItem("Системная схема", (event) -> { + setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + this.invalidate(); + }); + lookAndFeelMenu.add(systemLookAndFeel); + + JMenuItem crossplatformLookAndFeel = createMenuItem("Универсальная схема", (event) -> { + setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + this.invalidate(); + }); + lookAndFeelMenu.add(crossplatformLookAndFeel); + + JMenu testMenu = createMenu("Тесты", "Тестовые команды"); + + JMenuItem addLogMessageItem = createMenuItem("Сообщение в лог", + (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) + { + // skip + } + } +} diff --git a/robots/src/application/log/LogChangeListener.java b/robots/src/application/log/LogChangeListener.java new file mode 100644 index 000000000..d08b21815 --- /dev/null +++ b/robots/src/application/log/LogChangeListener.java @@ -0,0 +1,6 @@ +package application.log; + +public interface LogChangeListener +{ + void onLogChanged(); +} diff --git a/robots/src/log/LogEntry.java b/robots/src/application/log/LogEntry.java similarity index 74% rename from robots/src/log/LogEntry.java rename to robots/src/application/log/LogEntry.java index 3d9147107..0c652f53d 100644 --- a/robots/src/log/LogEntry.java +++ b/robots/src/application/log/LogEntry.java @@ -1,24 +1,23 @@ -package log; +package application.log; public class LogEntry { - private LogLevel m_logLevel; - private String m_strMessage; - + private final LogLevel m_logLevel; + private final String m_strMessage; + public LogEntry(LogLevel logLevel, String strMessage) { - m_strMessage = strMessage; m_logLevel = logLevel; + m_strMessage = strMessage; } - + public String getMessage() { return m_strMessage; } - + public LogLevel getLevel() { return m_logLevel; } } - diff --git a/robots/src/log/LogLevel.java b/robots/src/application/log/LogLevel.java similarity index 80% rename from robots/src/log/LogLevel.java rename to robots/src/application/log/LogLevel.java index 582d010cc..b1e85462c 100644 --- a/robots/src/log/LogLevel.java +++ b/robots/src/application/log/LogLevel.java @@ -1,4 +1,4 @@ -package log; +package application.log; public enum LogLevel { @@ -8,17 +8,16 @@ public enum LogLevel Warning(3), Error(4), Fatal(5); - - private int m_iLevel; - + + private final int m_iLevel; + private LogLevel(int iLevel) { m_iLevel = iLevel; } - + public int level() { return m_iLevel; } } - diff --git a/robots/src/gui/LogWindow.java b/robots/src/application/log/LogWindow.java similarity index 70% rename from robots/src/gui/LogWindow.java rename to robots/src/application/log/LogWindow.java index 723d3e2fc..95dff53ff 100644 --- a/robots/src/gui/LogWindow.java +++ b/robots/src/application/log/LogWindow.java @@ -1,29 +1,22 @@ -package gui; - +package application.log; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.TextArea; - import javax.swing.JInternalFrame; import javax.swing.JPanel; -import log.LogChangeListener; -import log.LogEntry; -import log.LogWindowSource; - public class LogWindow extends JInternalFrame implements LogChangeListener { - private LogWindowSource m_logSource; - private TextArea m_logContent; + private final LogWindowSource m_logSource; + private final TextArea m_logContent; - public LogWindow(LogWindowSource logSource) + public LogWindow(LogWindowSource logSource) { - super("Протокол работы", true, true, true, true); + super("work protocol", true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); m_logContent.setSize(200, 500); - JPanel panel = new JPanel(new BorderLayout()); panel.add(m_logContent, BorderLayout.CENTER); getContentPane().add(panel); @@ -35,13 +28,11 @@ private void updateLogContent() { StringBuilder content = new StringBuilder(); for (LogEntry entry : m_logSource.all()) - { - content.append(entry.getMessage()).append("\n"); - } + content.append(entry.getMessage()); m_logContent.setText(content.toString()); m_logContent.invalidate(); } - + @Override public void onLogChanged() { diff --git a/robots/src/log/LogWindowSource.java b/robots/src/application/log/LogWindowSource.java similarity index 64% rename from robots/src/log/LogWindowSource.java rename to robots/src/application/log/LogWindowSource.java index ca0ce4426..d05c67285 100644 --- a/robots/src/log/LogWindowSource.java +++ b/robots/src/application/log/LogWindowSource.java @@ -1,62 +1,56 @@ -package log; +package application.log; import java.util.ArrayList; import java.util.Collections; -/** - * Что починить: - * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются - * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) - */ public class LogWindowSource { - private int m_iQueueLength; - - private ArrayList m_messages; + private final int m_iQueueLength; + + private final ArrayList m_messages; private final ArrayList m_listeners; private volatile LogChangeListener[] m_activeListeners; - - public LogWindowSource(int iQueueLength) + + public LogWindowSource(int iQueueLength) { m_iQueueLength = iQueueLength; m_messages = new ArrayList(iQueueLength); m_listeners = new ArrayList(); } - + public void registerListener(LogChangeListener listener) { - synchronized(m_listeners) + synchronized (m_listeners) { m_listeners.add(listener); m_activeListeners = null; } } - + public void unregisterListener(LogChangeListener listener) { - synchronized(m_listeners) + synchronized (m_listeners) { m_listeners.remove(listener); m_activeListeners = null; } } - + public void append(LogLevel logLevel, String strMessage) { LogEntry entry = new LogEntry(logLevel, strMessage); + if (size() >= m_iQueueLength) + m_messages.remove(0); + m_messages.add(entry); - LogChangeListener [] activeListeners = m_activeListeners; + LogChangeListener[] activeListeners = m_activeListeners; if (activeListeners == null) { synchronized (m_listeners) { if (m_activeListeners == null) { - activeListeners = m_listeners.toArray(new LogChangeListener [0]); + activeListeners = m_listeners.toArray(new LogChangeListener[0]); m_activeListeners = activeListeners; } } @@ -66,7 +60,7 @@ public void append(LogLevel logLevel, String strMessage) listener.onLogChanged(); } } - + public int size() { return m_messages.size(); @@ -75,9 +69,7 @@ public int size() public Iterable range(int startFrom, int count) { if (startFrom < 0 || startFrom >= m_messages.size()) - { return Collections.emptyList(); - } int indexTo = Math.min(startFrom + count, m_messages.size()); return m_messages.subList(startFrom, indexTo); } diff --git a/robots/src/log/Logger.java b/robots/src/application/log/Logger.java similarity index 74% rename from robots/src/log/Logger.java rename to robots/src/application/log/Logger.java index b008a5d01..e44b85dc7 100644 --- a/robots/src/log/Logger.java +++ b/robots/src/application/log/Logger.java @@ -1,21 +1,20 @@ -package log; +package application.log; -public final class Logger -{ +public final class Logger { private static final LogWindowSource defaultLogSource; - static { - defaultLogSource = new LogWindowSource(100); - } - - private Logger() + + static { + defaultLogSource = new LogWindowSource(3); } + private Logger() {} + public static void debug(String strMessage) { defaultLogSource.append(LogLevel.Debug, strMessage); } - + public static void error(String strMessage) { defaultLogSource.append(LogLevel.Error, strMessage); diff --git a/robots/src/application/model/Bot.java b/robots/src/application/model/Bot.java new file mode 100644 index 000000000..93d3ad98f --- /dev/null +++ b/robots/src/application/model/Bot.java @@ -0,0 +1,268 @@ +package application.model; + + +import application.model.additionals.Condition; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeSupport; + +public class Bot implements Entity +{ + private double positionX; + private double positionY; + private volatile double botDirection; + private Food foodGoal; + private int lifeTime; + public static double maxVelocity = 0.05; + public static final double maxAngularVelocity = 0.005; + private Dimension dimension; + private Condition condition; + private boolean isAlive; + private static final int START_LIFETIME = 40; + private static final int FULL_LIFETIME = 90; + + public Bot(double startX, double startY, int lifeTime) + { + this.positionX = startX; + this.positionY = startY; + this.dimension = new Dimension(400, 400); + this.foodGoal = new Food(); + this.botDirection = Math.random() * 10; + this.lifeTime = lifeTime; + this.condition = Condition.randomCondition(); + this.isAlive = true; + } + + public void setDimension(Dimension dimension) + { + this.dimension = dimension; + if (!foodGoal.isPositionCorrect(dimension)) + { + foodGoal.setX((int) (Math.random() * dimension.width)); + foodGoal.setY((int) (Math.random() * dimension.height)); + } + } + + private void setRandomCondition() + { + this.condition = Condition.randomCondition(); + } + + public Dimension getDimension() + { + return this.dimension; + } + + public double getPositionX() + { + return positionX; + } + + public void setPositionX(double positionX) + { + this.positionX = positionX; + } + + public double getPositionY() + { + return positionY; + } + + public void setPositionY(double positionY) + { + this.positionY = positionY; + } + + public double getBotDirection() + { + return botDirection; + } + + public void setBotDirection(double botDirection) + { + this.botDirection = botDirection; + } + + 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)); + } + + 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 double normalizedPositionX(double x) + { + if (x < 0) + return 0; + if (x > dimension.width) + return dimension.width; + return x; + } + + private double normalizedPositionY(double y) + { + if (y < 0) + return 0; + if (y > dimension.height) + return dimension.height; + return y; + } + + private static double applyLimits(double value, double min, double max) + { + if (value < min) + return min; + return Math.min(value, max); + } + + public Food getFoodGoal() + { + return foodGoal; + } + + public int getLifeTime() + { + return lifeTime; + } + + public void changeLifeTime(int lifeTmeDif) + { + this.lifeTime += lifeTmeDif; + } + + public void setFoodGoal(Point point) + { + if (point.x > dimension.width || point.y > dimension.height) + { + this.foodGoal.setFoodPosition(new Point(point.x / dimension.width, point.y / dimension.height)); + } + this.foodGoal.setFoodPosition(point); + } + + public void setFoodGoal(Food food){ + this.foodGoal = food; + } + + private void moveBot(double velocity, double angularVelocity, double duration) + { + velocity = applyLimits(velocity, 0, Bot.maxVelocity); + angularVelocity = applyLimits(angularVelocity, -Bot.maxAngularVelocity, Bot.maxAngularVelocity); + double newX = getPositionX() + velocity / angularVelocity * + (Math.sin(getBotDirection() + angularVelocity * duration) - + Math.sin(getBotDirection())); + if (!Double.isFinite(newX)) + newX = getPositionX() + velocity * duration * Math.cos(getBotDirection()); + + double newY = getPositionY() - velocity / angularVelocity * + (Math.cos(getBotDirection() + angularVelocity * duration) - + Math.cos(getBotDirection())); + if (!Double.isFinite(newY)) + newY = getPositionY() + velocity * duration * Math.sin(getBotDirection()); + + setPositionX(normalizedPositionX(newX)); + setPositionY(normalizedPositionY(newY)); + double newDirection = asNormalizedRadians(getBotDirection() + angularVelocity * duration); + setBotDirection(newDirection); + } + + public Condition getCondition() + { + return this.condition; + } + + @Override + public void update() + { + if (!this.isAlive) + { + this.setCondition(Condition.DEAD); + + return; + } + if (this.lifeTime < 50) + { + if (this.lifeTime < 0) + { + isAlive = false; + } + this.setCondition(Condition.HUNGRY); + } + + double distance = distance(foodGoal.getX(), foodGoal.getY(), + getPositionX(), getPositionY()); + + if (distance < 0.5) + { + this.foodGoalAchieved(); + return; + } + double angleToFoodGoal = angleTo(getPositionX(), getPositionY(), + foodGoal.getX(), foodGoal.getY()); + double angularVelocity = 0; + if (angleToFoodGoal > getBotDirection()) + angularVelocity = Bot.maxAngularVelocity; + if (angleToFoodGoal < getBotDirection()) + angularVelocity = -Bot.maxAngularVelocity; + + moveBot(Bot.maxVelocity, angularVelocity, 10); + } + + private void setCondition(Condition condition) + { + this.condition = condition; + } + + @Override + public void onStart(PropertyChangeSupport publisher) + { + publisher.addPropertyChangeListener(this); + } + + @Override + public void onFinish(PropertyChangeSupport publisher) + { + publisher.removePropertyChangeListener(this); + } + + private void foodGoalAchieved() + { + //this.setFoodGoal(new Point((int) (Math.random() * dimension.width), (int) (Math.random() * dimension.height))); + this.foodGoal=new Food(); + this.lifeTime += 20; + if (this.lifeTime > 50) + { + this.setRandomCondition(); + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + if (evt.getPropertyName().equals("new point")) + setFoodGoal((Point) evt.getNewValue()); + if (evt.getPropertyName().equals("change life time")) + changeLifeTime((int) evt.getNewValue()); + if (evt.getPropertyName().equals("set dimension")) + setDimension((Dimension) evt.getNewValue()); + } +} \ No newline at end of file diff --git a/robots/src/application/model/BotHandler.java b/robots/src/application/model/BotHandler.java new file mode 100644 index 000000000..0261f2f72 --- /dev/null +++ b/robots/src/application/model/BotHandler.java @@ -0,0 +1,133 @@ +package application.model; + +import java.awt.*; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static java.awt.geom.Point2D.distance; + +public class BotHandler +{ + private final Dimension dimension; + private final java.util.List bots; + private final List foods; + private final PropertyChangeSupport support; + private static final int BOT_COUNT = 5; + private static final int FOOD_COUNT = 5; + private static final int START_LIFETIME = 40; + private static final int FULL_LIFETIME = 90; + + public BotHandler() + { + this.bots = new ArrayList<>(); + this.foods = new ArrayList<>(); + this.dimension = new Dimension(400, 400); + this.support = new PropertyChangeSupport(this); + + for (int i = 0; i < FOOD_COUNT; i++) + { + Food food = new Food((int) (Math.random() * 400), (int) (Math.random() * 400)); + foods.add(food); + } + + for (int i = 0; i < BOT_COUNT; i++) + { + Bot bot = new Bot(Math.random() * 400, Math.random() * 400, (int) (START_LIFETIME + Math.random() * (FULL_LIFETIME - START_LIFETIME))); + bot.setFoodGoal(findFood(bot)); + bots.add(bot); + bot.onStart(support); + } + } + + private Food findFood(Bot bot) + { + double minDistance = Double.MAX_VALUE; + Food nearestFood = null; + + for (Food food : foods) + { + double distance = distance(bot.getPositionX(), bot.getPositionY(), food.getX(), food.getY()); + + if (distance < minDistance) + { + minDistance = distance; + nearestFood = food; + } + } + + if (nearestFood != null) + { + foods.remove(nearestFood); + } + + return nearestFood; + } + + public void suddenBotClone() + { + if (Math.random() * 6 > 2) + { + Random rand = new Random(); + Bot bot = bots.get(rand.nextInt(bots.size())); + double x = bot.getPositionX(); + double y = bot.getPositionY(); + bot.onFinish(support); + bots.remove(bot); + for (int i = 0; i < 2; i++) + { + int xx = (int) (Math.random() * dimension.width); + int yy = (int) (Math.random() * dimension.height); + Food food = new Food(xx, yy); + foods.add(food); + Bot b = new Bot(x, y, bot.getLifeTime() / 2); + b.setFoodGoal(findFood(b)); + bots.add(b); + } + } + } + + public void changeLifeTime() + { + support.firePropertyChange("change life time", null, -10); + } + + public List getBotList() + { + return bots; + } + + public List getFoodList() + { + return foods; + } + + public void setFoodGoal(Point point) + { + support.firePropertyChange("new point", null, point); + } + + public void update() + { + for (Food food : foods){ + if (!food.isPositionCorrect(dimension)) + { + food.setX((int) (Math.random() * dimension.width)); + food.setY((int) (Math.random() * dimension.height)); + } + } + for (Bot bot : bots){ + Food f = bot.getFoodGoal(); + if (!f.spawn) + { + int xx = (int) (Math.random() * dimension.width); + int yy = (int) (Math.random() * dimension.height); + Food food = new Food(xx, yy); + foods.add(food); + bot.setFoodGoal(findFood(bot)); + } + bot.update(); + } + } +} diff --git a/robots/src/application/model/Entity.java b/robots/src/application/model/Entity.java new file mode 100644 index 000000000..7d9f595ee --- /dev/null +++ b/robots/src/application/model/Entity.java @@ -0,0 +1,13 @@ +package application.model; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +public interface Entity extends PropertyChangeListener +{ + void update(); + + void onStart(PropertyChangeSupport publisher); + + void onFinish(PropertyChangeSupport publisher); +} diff --git a/robots/src/application/model/Food.java b/robots/src/application/model/Food.java new file mode 100644 index 000000000..b23f5664b --- /dev/null +++ b/robots/src/application/model/Food.java @@ -0,0 +1,81 @@ +package application.model; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeSupport; + +public class Food implements Entity{ + private volatile int x; + private volatile int y; + boolean spawn; + + public Food(int x, int y) + { + this.x = x; + this.y = y; + this.spawn=true; + } + + public Food() + { + this.x = 100; + this.y = 100; + this.spawn=false; + } + + public void setX(int x) + { + this.x = x; + } + + public void setY(int y) + { + this.y = y; + } + + public int getX() + { + return x; + } + + public int getY() + { + return y; + } + + public void setFoodPosition(Point p) + { + setX(p.x); + setY(p.y); + } + + protected Point getFoodPosition() + { + return new Point(getX(), getY()); + } + + public boolean isPositionCorrect(Dimension dimension) + { + return this.x <= dimension.width && this.y <= dimension.height; + } + + @Override + public void update() { + + } + + @Override + public void onStart(PropertyChangeSupport publisher) { + + } + + @Override + public void onFinish(PropertyChangeSupport publisher) { + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + } +} diff --git a/robots/src/application/model/GameModel.java b/robots/src/application/model/GameModel.java new file mode 100644 index 000000000..aba7e5d27 --- /dev/null +++ b/robots/src/application/model/GameModel.java @@ -0,0 +1,63 @@ +package application.model; + +import java.awt.*; +import java.util.*; +import java.util.List; + +public class GameModel +{ + private final BotHandler botHandler; + + public GameModel() + { + this.botHandler = new BotHandler(); + + Timer timer = initTimer(); + timer.schedule(new TimerTask() + { + @Override + public void run() + { + System.out.println("timer"); + botHandler.changeLifeTime(); + botHandler.suddenBotClone(); + } + }, 0, 3000); + } + + private static java.util.Timer initTimer() + { + return new Timer("events generator", true); + } + + public void setDimension(Dimension dimension) + { + for (Bot bot : botHandler.getBotList()) + bot.setDimension(dimension); + } + + public Dimension getDimension() + { + return botHandler.getBotList().get(0).getDimension(); + } + + public void updateModel() + { + botHandler.update(); + } + + public List getRobots() + { + return botHandler.getBotList(); + } + + public List getFoods() + { + return botHandler.getFoodList(); + } + + public void setFoodGoal(Point point) + { + botHandler.setFoodGoal(point); + } +} diff --git a/robots/src/application/model/additionals/Condition.java b/robots/src/application/model/additionals/Condition.java new file mode 100644 index 000000000..4947c1512 --- /dev/null +++ b/robots/src/application/model/additionals/Condition.java @@ -0,0 +1,30 @@ +package application.model.additionals; + +import java.awt.*; +import java.util.List; +import java.util.Random; + +public enum Condition +{ + CALM(Color.GREEN), + HUNGRY(Color.RED), + DEAD(Color.BLACK); + private final Color color; + private static final Random RANDOM = new Random(); + private static final List values = List.of(values()); + + public static Condition randomCondition() + { + return values.get(RANDOM.nextInt(values().length - 2)); + } + + Condition(Color color) + { + this.color = color; + } + + + public Color getColor() { + return color; + } +} diff --git a/robots/src/application/view/GameView.java b/robots/src/application/view/GameView.java new file mode 100644 index 000000000..b12c4d97d --- /dev/null +++ b/robots/src/application/view/GameView.java @@ -0,0 +1,57 @@ +package application.view; + +import application.model.Bot; +import application.model.Food; +import application.model.GameModel; +import application.view.draw.BotDrawer; +import application.view.draw.FoodDrawer; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +public class GameView extends JPanel +{ + private final GameModel gameModel; + BotDrawer botDrawer; + FoodDrawer foodDrawer; + + public GameView(GameModel gameModel) + { + botDrawer = new BotDrawer(); + foodDrawer = new FoodDrawer(); + this.gameModel = gameModel; + setDoubleBuffered(true); + } + + public void updateView() + { + onRedrawEvent(); + } + + protected void onRedrawEvent() + { + EventQueue.invokeLater(this::repaint); + } + + @Override + public void paint(Graphics g) + { + super.paint(g); + Graphics2D g2d = (Graphics2D) g; + ArrayList bots = (ArrayList) gameModel.getRobots(); + ArrayList foods = (ArrayList) gameModel.getFoods(); + for (Bot bot : bots) + { + botDrawer.draw(g2d, bot); + if (bot.getFoodGoal() != null) + { + foodDrawer.draw(g2d, bot.getFoodGoal()); + } + } + for (Food food : foods) + { + foodDrawer.draw(g2d,food); + } + } +} diff --git a/robots/src/application/view/GameWindow.java b/robots/src/application/view/GameWindow.java new file mode 100644 index 000000000..5b5682b8e --- /dev/null +++ b/robots/src/application/view/GameWindow.java @@ -0,0 +1,25 @@ +package application.view; + + +import javax.swing.*; +import java.awt.*; + +public class GameWindow extends JInternalFrame +{ + private final GameView gameView; + + public GameWindow(GameView gameView) + { + super("Игровое поле", true, true, true, true); + this.gameView = gameView; + JPanel panel = new JPanel(new BorderLayout()); + panel.add(this.gameView, BorderLayout.CENTER); + getContentPane().add(panel); + pack(); + setSize(400, 400); + } + + public GameView getGameView() { + return this.gameView; + } +} diff --git a/robots/src/application/view/draw/BotDrawer.java b/robots/src/application/view/draw/BotDrawer.java new file mode 100644 index 000000000..cc73769c1 --- /dev/null +++ b/robots/src/application/view/draw/BotDrawer.java @@ -0,0 +1,50 @@ +package application.view.draw; + +import application.model.Bot; +import application.model.additionals.Condition; + +import java.awt.*; +import java.awt.geom.AffineTransform; + +public class BotDrawer extends Drawer { + public void draw(Graphics2D g, Bot bot) + { + AffineTransform oldTransform = g.getTransform(); + int robotCenterX = (int) Math.round(bot.getPositionX()); + int robotCenterY = (int) Math.round(bot.getPositionY()); + AffineTransform l = new AffineTransform(oldTransform); + AffineTransform t = AffineTransform.getRotateInstance(bot.getBotDirection(), robotCenterX, robotCenterY); + l.concatenate(t); + g.setTransform(l); + g.setColor(bot.getCondition().getColor()); + + if (bot.getCondition().equals(Condition.HUNGRY)) + { + fillOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.YELLOW); + } + + if (bot.getCondition().equals(Condition.CALM)) + { + fillOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.WHITE); + } + + if (bot.getCondition().equals(Condition.DEAD)) + { + fillOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX, robotCenterY, 20, 10); + g.setColor(Color.MAGENTA); + } + + fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); + g.setColor(Color.BLACK); + drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); + g.setTransform(oldTransform); + } +} diff --git a/robots/src/application/view/draw/Drawer.java b/robots/src/application/view/draw/Drawer.java new file mode 100644 index 000000000..788ebf35a --- /dev/null +++ b/robots/src/application/view/draw/Drawer.java @@ -0,0 +1,20 @@ +package application.view.draw; + +import application.model.Bot; +import application.model.Entity; +import application.model.Food; + +import java.awt.*; + +public abstract class Drawer +{ + protected static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) + { + g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + } + + protected static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) + { + g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); + } +} diff --git a/robots/src/application/view/draw/FoodDrawer.java b/robots/src/application/view/draw/FoodDrawer.java new file mode 100644 index 000000000..f04bd8335 --- /dev/null +++ b/robots/src/application/view/draw/FoodDrawer.java @@ -0,0 +1,19 @@ +package application.view.draw; + +import application.model.Food; + +import java.awt.*; +import java.awt.geom.AffineTransform; + +public class FoodDrawer extends Drawer +{ + public void draw(Graphics2D g, Food food) + { + AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); + g.setTransform(t); + g.setColor(Color.GREEN); + fillOval(g, food.getX(), food.getY(), 5, 5); + g.setColor(Color.BLACK); + drawOval(g, food.getX(), food.getY(), 5, 5); + } +} diff --git a/robots/src/application/viewModel/ViewModel.java b/robots/src/application/viewModel/ViewModel.java new file mode 100644 index 000000000..2905333fc --- /dev/null +++ b/robots/src/application/viewModel/ViewModel.java @@ -0,0 +1,87 @@ +package application.viewModel; + +import application.view.GameView; +import application.view.GameWindow; +import application.model.GameModel; + +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Timer; +import java.util.TimerTask; + + +public class ViewModel +{ + private final GameModel gameModel; + private final GameWindow gameWindow; + private final java.util.Timer timer = initTimer(); + + private static java.util.Timer initTimer() + { + return new Timer("events generator", true); + } + + public ViewModel(GameModel gameModel, GameWindow gameWindow) + { + this.gameModel = gameModel; + this.gameWindow = gameWindow; + initListeners(); + } + + private void initListeners() + { + timer.schedule(new TimerTask() + { + @Override + public void run() + { + gameModel.setDimension(gameWindow.getSize()); + getGameView().updateView(); + } + }, 0, 10); + + timer.schedule(new TimerTask() + { + @Override + public void run() + { + gameModel.updateModel(); + } + }, 0, 10); + + gameWindow.getGameView().addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + System.out.println(e.getPoint()); + gameModel.setFoodGoal(e.getPoint()); + getGameView().repaint(); + } + }); + + gameWindow.addComponentListener(new ComponentAdapter() + { + @Override + public void componentResized(final ComponentEvent e) + { + super.componentResized(e); + System.out.println("resize"); + gameModel.setDimension((gameWindow.getSize())); + System.out.println(gameModel.getDimension()); + } + }); + } + + public GameView getGameView() + { + return gameWindow.getGameView(); + } + + public GameWindow getGameWindow() + { + return gameWindow; + } +} diff --git a/robots/src/gui/GameVisualizer.java b/robots/src/gui/GameVisualizer.java deleted file mode 100644 index f82cfd8f8..000000000 --- a/robots/src/gui/GameVisualizer.java +++ /dev/null @@ -1,210 +0,0 @@ -package gui; - -import java.awt.Color; -import java.awt.EventQueue; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.geom.AffineTransform; -import java.util.Timer; -import java.util.TimerTask; - -import javax.swing.JPanel; - -public class GameVisualizer extends JPanel -{ - private final Timer m_timer = initTimer(); - - private static Timer initTimer() - { - Timer timer = new Timer("events generator", true); - return timer; - } - - private volatile double m_robotPositionX = 100; - private volatile double m_robotPositionY = 100; - private volatile double m_robotDirection = 0; - - private volatile int m_targetPositionX = 150; - private volatile int m_targetPositionY = 100; - - private static final double maxVelocity = 0.1; - private static final double maxAngularVelocity = 0.001; - - public GameVisualizer() - { - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onRedrawEvent(); - } - }, 0, 50); - m_timer.schedule(new TimerTask() - { - @Override - public void run() - { - onModelUpdateEvent(); - } - }, 0, 10); - addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - setTargetPosition(e.getPoint()); - repaint(); - } - }); - setDoubleBuffered(true); - } - - protected void setTargetPosition(Point p) - { - m_targetPositionX = p.x; - m_targetPositionY = p.y; - } - - protected void onRedrawEvent() - { - EventQueue.invokeLater(this::repaint); - } - - private static double distance(double x1, double y1, double x2, double y2) - { - double diffX = x1 - x2; - double diffY = y1 - y2; - return Math.sqrt(diffX * diffX + diffY * diffY); - } - - private static double angleTo(double fromX, double fromY, double toX, double toY) - { - double diffX = toX - fromX; - double diffY = toY - fromY; - - return asNormalizedRadians(Math.atan2(diffY, diffX)); - } - - protected void onModelUpdateEvent() - { - double distance = distance(m_targetPositionX, m_targetPositionY, - m_robotPositionX, m_robotPositionY); - if (distance < 0.5) - { - return; - } - double velocity = maxVelocity; - double angleToTarget = angleTo(m_robotPositionX, m_robotPositionY, m_targetPositionX, m_targetPositionY); - double angularVelocity = 0; - if (angleToTarget > m_robotDirection) - { - angularVelocity = maxAngularVelocity; - } - if (angleToTarget < m_robotDirection) - { - angularVelocity = -maxAngularVelocity; - } - - moveRobot(velocity, angularVelocity, 10); - } - - private static double applyLimits(double value, double min, double max) - { - if (value < min) - return min; - if (value > max) - return max; - return value; - } - - private void moveRobot(double velocity, double angularVelocity, double duration) - { - velocity = applyLimits(velocity, 0, maxVelocity); - angularVelocity = applyLimits(angularVelocity, -maxAngularVelocity, maxAngularVelocity); - double newX = m_robotPositionX + velocity / angularVelocity * - (Math.sin(m_robotDirection + angularVelocity * duration) - - Math.sin(m_robotDirection)); - if (!Double.isFinite(newX)) - { - newX = m_robotPositionX + velocity * duration * Math.cos(m_robotDirection); - } - double newY = m_robotPositionY - velocity / angularVelocity * - (Math.cos(m_robotDirection + angularVelocity * duration) - - Math.cos(m_robotDirection)); - if (!Double.isFinite(newY)) - { - newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); - } - m_robotPositionX = newX; - m_robotPositionY = newY; - double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); - m_robotDirection = newDirection; - } - - private static double asNormalizedRadians(double angle) - { - while (angle < 0) - { - angle += 2*Math.PI; - } - while (angle >= 2*Math.PI) - { - angle -= 2*Math.PI; - } - return angle; - } - - private static int round(double value) - { - return (int)(value + 0.5); - } - - @Override - public void paint(Graphics g) - { - super.paint(g); - Graphics2D g2d = (Graphics2D)g; - drawRobot(g2d, round(m_robotPositionX), round(m_robotPositionY), m_robotDirection); - drawTarget(g2d, m_targetPositionX, m_targetPositionY); - } - - private static void fillOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.fillOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private static void drawOval(Graphics g, int centerX, int centerY, int diam1, int diam2) - { - g.drawOval(centerX - diam1 / 2, centerY - diam2 / 2, diam1, diam2); - } - - private void drawRobot(Graphics2D g, int x, int y, double direction) - { - int robotCenterX = round(m_robotPositionX); - int robotCenterY = round(m_robotPositionY); - AffineTransform t = AffineTransform.getRotateInstance(direction, robotCenterX, robotCenterY); - g.setTransform(t); - g.setColor(Color.MAGENTA); - fillOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX, robotCenterY, 30, 10); - g.setColor(Color.WHITE); - fillOval(g, robotCenterX + 10, robotCenterY, 5, 5); - g.setColor(Color.BLACK); - drawOval(g, robotCenterX + 10, robotCenterY, 5, 5); - } - - private void drawTarget(Graphics2D g, int x, int y) - { - AffineTransform t = AffineTransform.getRotateInstance(0, 0, 0); - g.setTransform(t); - g.setColor(Color.GREEN); - fillOval(g, x, y, 5, 5); - g.setColor(Color.BLACK); - drawOval(g, x, y, 5, 5); - } -} diff --git a/robots/src/gui/GameWindow.java b/robots/src/gui/GameWindow.java deleted file mode 100644 index ecb63c00f..000000000 --- a/robots/src/gui/GameWindow.java +++ /dev/null @@ -1,20 +0,0 @@ -package gui; - -import java.awt.BorderLayout; - -import javax.swing.JInternalFrame; -import javax.swing.JPanel; - -public class GameWindow extends JInternalFrame -{ - private final GameVisualizer m_visualizer; - public GameWindow() - { - super("Игровое поле", true, true, true, true); - m_visualizer = new GameVisualizer(); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(m_visualizer, BorderLayout.CENTER); - getContentPane().add(panel); - pack(); - } -} diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java deleted file mode 100644 index 62e943ee1..000000000 --- a/robots/src/gui/MainApplicationFrame.java +++ /dev/null @@ -1,156 +0,0 @@ -package gui; - -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import log.Logger; - -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ - private final JDesktopPane desktopPane = new JDesktopPane(); - - public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); - - setContentPane(desktopPane); - - - LogWindow logWindow = createLogWindow(); - addWindow(logWindow); - - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); - addWindow(gameWindow); - - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); - } - - protected LogWindow createLogWindow() - { - LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); - logWindow.setSize(300, 800); - setMinimumSize(logWindow.getSize()); - logWindow.pack(); - Logger.debug("Протокол работает"); - return logWindow; - } - - protected void addWindow(JInternalFrame frame) - { - desktopPane.add(frame); - frame.setVisible(true); - } - -// protected JMenuBar createMenuBar() { -// JMenuBar menuBar = new JMenuBar(); -// -// //Set up the lone menu. -// JMenu menu = new JMenu("Document"); -// menu.setMnemonic(KeyEvent.VK_D); -// menuBar.add(menu); -// -// //Set up the first menu item. -// JMenuItem menuItem = new JMenuItem("New"); -// menuItem.setMnemonic(KeyEvent.VK_N); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_N, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("new"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// //Set up the second menu item. -// menuItem = new JMenuItem("Quit"); -// menuItem.setMnemonic(KeyEvent.VK_Q); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("quit"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// return menuBar; -// } - - private JMenuBar generateMenuBar() - { - JMenuBar menuBar = new JMenuBar(); - - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(systemLookAndFeel); - } - - { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(crossplatformLookAndFeel); - } - - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); - - { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - testMenu.add(addLogMessageItem); - } - - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; - } - - private void setLookAndFeel(String className) - { - try - { - UIManager.setLookAndFeel(className); - SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore - } - } -} diff --git a/robots/src/gui/RobotsProgram.java b/robots/src/gui/RobotsProgram.java deleted file mode 100644 index ae0930a8b..000000000 --- a/robots/src/gui/RobotsProgram.java +++ /dev/null @@ -1,25 +0,0 @@ -package gui; - -import java.awt.Frame; - -import javax.swing.SwingUtilities; -import javax.swing.UIManager; - -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); - }); - }} diff --git a/robots/src/log/LogChangeListener.java b/robots/src/log/LogChangeListener.java deleted file mode 100644 index 0b0fb85dd..000000000 --- a/robots/src/log/LogChangeListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package log; - -public interface LogChangeListener -{ - public void onLogChanged(); -}