Визуализация «похождений» робота

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

  1. Robot — класс робота, который будет передвигаться и поворачиваться. Но что важно отметить — робот будет запоминать весь путь, который он проходил — т.е. будет вести список прямых, которые он проезжал. Для хранения списка мы используем стандартный класс Java ArrayList. Этот класс мы будем рассматривать более подробно позже при изучении коллекций, а сейчас мы просто будем его использовать «вслепую» — просто знайте, что у него есть метод add, который позволяет добавить в список объект. Также список объектов можно «просмотреть» с помощью конструкции for, которая приведена в классе RobotPathComponent.
  2. RobotLine — класс для хранения координат одного отрезка пути. Это простой класс, который включает 4 числа — координаты начальной точки (X1, Y1) и координаты конечной точки (X2, Y2).
  3. RobotPathComponent — этот класс наследуется от уже знакомого нам класса JComponent. Этому классу передается робот со своим списком отрезков пути. В методе paintComponent путем перебора всех отрезков мы получаем координаты каждого и рисуем линию вызовом метода drawLine класса Graphics.
  4. RobotFrame — класс для отображения формы. Мы уже встречались с подобной реализацией.
  5. RobotManager — класс для запуска всего приложения. В его методе main мы сначала создаем робота и указываем ему нарисовать 12-ти угольник. Вы можете задать другое количество, изменив значение переменной COUNT. После «прогулки» мы создаем форму и передаем ей нашего робота для отрисовки его пути. Если задать большое количество сторон, то из-за округлений наш многоугольник не замкнется. Можете попробовать это исправить.

Думаю, что комментариев должно быть достаточно — теперь вы можете посмотреть код наших классов и скачать проект для запуска — Robot4.

 

Класс Robot

Класс RobotLine

Класс RobotPathComponent

Здесь мы видим конструкцию, которая может вас озадачить — у нее даже комментарии такие Вот эта
for (RobotLine rl : robot.getLines()).
Давайте пока примем ее как есть. Чтобы не было совсем непонятно — эта конструкция перебирает каждый элемент внутри списка и помещает ее в переменную rl. Т.е. внутри цикла эта переменная указывает на элемент списка и тип этого элемента RobotLine. Можно себе представить как будто мы просматриваем кадры кинопленки. На каждом шаге мы перемещаем окошко просмотра на следующий кадр. Окошко просмотра — это переменная rl. С помощью этой переменной мы можем посмотреть параметры конкретного кадра — с нашем случае линии с координатами начала и конца (X1, Y1, X2, Y2).
Класс RobotFrame

Класс RobotManager

Можете поиграть с нашим роботом. Учтите, что координаты отсчитываются от верхнего левого угла. Хотелось бы заострить ваше внимание на следующем моменте — по коду уже можно видеть, что мы создавая объекты по сути «играем» с ними, указывая, что им делать и в каком порядке. Большинство программ именно так и создаются — просто классов больше, отношения более сложные. Но идея одна — создать объекты нужных классов и заставить их работать в кооперации. Это требует умения и мастерства, которое надо накапливать создавая программы — другого пути пока не придумали.
Попробуйте в качестве маршрута нарисовать какую-нибудь сложную фигуру — например пятиконечную звезду. Или сделать квадратную спираль от самых краев к центру — каждый квадрат все меньше и меньше.

49 comments to Визуализация робота

  • javaNoob  says:

    Ух!!! Сильная статья! С мощным примером!
    Исходя из очитанного и разобранного кода, я осмелюсь сделать вывод, что полиморфизм является краеугольным камнем ООП. Это свобода реализации различных ситуаций возникающих и могущих возникнуть..
    Огромное спасибо!!

  • Stitch  says:

    «Если задать большое количество сторон, то из-за округлений наш многоугольник не замкнется. Можете попробовать это исправить.»

    Исправлено. проблема была в том, что при делении 360/COUNТ в делении участвовали два числа типа int и результат округлялся, на чем мы и допускали погрешность, которая не позволяла замкнуться фигуре, прокастовав одно из чисел, получили результат без погрешности типа double). Повод к статье о вычислениях примитивов, кастовании и т.д. Спасибо Вам за Ваш труд, уроки более чем доступны, прочел с удовольствием, хотя материал мне знаком, но как говорили наши бабушки «повторение — мать учения»)

    robot.setCourse(robot.getCourse() + 360 / (double)COUNT);

    • Денис  says:

      Можно немного проще:
      robot.setCourse(robot.getCourse() + 360. / COUNT);

  • Олег  says:

    Добрый день!

    Наткнулся на Ваш сайт и понравился код по визуализации робота но не получается реализовать маршрут робота по квадратной спирали от самых краев к центру – каждый квадрат все меньше и меньше, если нетрудно с чего начать?

    • admin  says:

      Я бы начал с того, что нарисовал эту фигуру на листочке бумаги. Потом попробовал выяснить закономерность изменения линий и это как раз самая сложная часть. После решения этой задачи все будет проще.

    • Creed  says:

      Robotmanager c 14 по 20 строки замените на :
      Robot robot = new Robot(5, 5);
      int side = SIDE;
      int count = 0;
      while(side>0){
      count++;
      side -=5;
      robot.forward(side);
      robot.setCourse(count*90);
      }

  • Олег  says:

    Я так понимаю, надо копать здесь, но как он должен повернуть так как мне надо, не придумал

    x += distance * Math.cos(course / 180 * Math.PI);
    y += distance * Math.sin(course / 180 * Math.PI);

    • admin  says:

      Для поворота надо изменить курс — используйте setCourse

  • Олег  says:

    если запрограммировать кнопки вперед, назад это понятно, но с помощью setCourse
    не могу разобраться?

    • admin  says:

      Можно запрограммировать кнопки «Налево» и «Направо» — и тогда курс робота можно изменять.

  • Олег  says:

    а как же повернуть не с помощью кнопок?

  • Булат  says:

    Спиральку вот так сделал:

    for (int i = 0; i < COUNT; i++) {
    robot.forward(SIDE+SIDESTEP*i);
    // robot.setCourse(robot.getCourse() + 360 / COUNT);
    robot.setCourse(robot.getCourse() + 90);

    правильно?

    • admin  says:

      Я не проверял результат, но есть подозрение, что не совсем верно. В спирали больше одной стороны имеют одинаковую длину, а у Вас только одна. Сомневаюсь я в правильности. Вы проверяли работоспособность этого ?

  • Булат  says:

    Да. это пока единственное что понимаю из курса ))))
    Вот сделал корректнее:
    for (int i = 0; i < COUNT; i++) {
    robot.forward(SIDE*COUNT-SIDESTEP*i);
    robot.setCourse(robot.getCourse() + 90);

    }

    • admin  says:

      Это уже ближе. В принципе я когда-то написал так:

  • Александр  says:

    С какой целью были созданы два пакета? package edu.javacourse.robot.ui и package edu.javacourse.robot
    Если все классы «вписать» в один пакет всё будет работать

    • admin  says:

      Потому что мне показалось будет правильнее — разделить классы, которые отвечают за разные функции по разным пакетам. Это вопрос достаточно субъективный.

  • Pavel  says:

    Вариант спирали.
    Интересно кто как звезду рисовал.

    for (int i = 0; i < COUNT; i++) {
    for (int n = 0; n < 4; n++) {
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 90);
    SIDE *= 0.9;

    }
    }

    • Pavel  says:

      Вариант звезды:

      final int COUNT = 5;
      final int SIDE = 100;
      final int alfa = 360 / COUNT / 2;
      Robot robot = new Robot(100, 200);
      for (int i = 0; i < COUNT; i++) {
      robot.forward(SIDE);
      robot.setCourse(robot.getCourse() — 2 * alfa);
      robot.forward(SIDE);
      robot.setCourse(robot.getCourse() + 360 / (double)COUNT + 2 * alfa);
      }

  • Екатерина  says:

    public static void main(String[] args) {
    final int COUNT = 10;
    final int SIDE = 120;
    Robot robot = new Robot(100,200);
    for (int i = 0; i < COUNT/2; i++) {
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 2* 360 / COUNT);
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 4*360 / COUNT);
    }

  • Владимир  says:

    А есть ли какой то пример, чтобы изображение масштабировалось при изменении окна? Как в случае с овалом.

    • admin  says:

      В принципе интернет можно спросить о таком примере: http://stackoverflow.com/questions/15558202/how-to-resize-image-in-java

    • Nibbler  says:

      Тоже «залип» с этой идеей… В конце-концов реализовал — правда Мастер, вряд ли, одобрит. Пришлось сделать отдельный класс: DistanceModify.java в котором реализован статический метод setWH, принимающий в качестве параметров ширину, высоту и самого робота. Метод вызывается из тела paintComponent следующим образом: DistanceModify.setWH(getWidth(),getHeight(),robot); Только так можно получить ширину и высоту графического поля. Поскольку после масштабирования окна нужно заново пересчитывать массив линий — алгоритм перемещений тоже ушел в DistanceModify.java. В такой реализации идеально масштабируется прямоугольник:

      Если же играть константой COUNT и подставить формулу расчета курса, которую я приводил для звезды ниже — при растягивании окна получаются «метаморфозы», как в калейдоскопе.
      Да — массив линий приходится очищать перед каждым новым «перестроением»: robot.linesClear(); иначе полная каша получится.
      Наверное, можно было сделать все проще и красивее. Но пока еще знаю очень мало для этого.

  • Nibbler  says:

    Вот такой вариант «ленивой» звезды:
    final int COUNT = 10;
    robot.setCourse(robot.getCourse()+(180-360/COUNT));

    В детстве, вроде бы, так рисовали 🙂

    • Юрий  says:

      final int COUNT = 5;
      robot.setCourse(robot.getCourse() + (180-180/COUNT));

  • IntelligenceUniverse  says:

    Звезда
    public static void main(String[] args) {
    final int COUNT =5;
    final int COUNT2 = 10;
    final int SIDE = 100;

    Robot robot = new Robot(200, 100);
    for (int i = 0; i < COUNT; i++) {
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 360 / COUNT2);
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 180 — 360 / COUNT);
    }

  • Firefly  says:

    По-моему, спираль достаточно просто рисуется (вдруг кому пригодится):

    // Количество сторон многоугольника
    final int COUNT = 4;
    // Длина стороны
    final int SIDE = 100;

    Robot robot = new Robot(150, 100);
    // Создаем замкнутую фигуру с количеством сторон 12
    for (int i = 0; i < 12; i++) {
    // уменьшаем длину стороны на 20 каждые два шага
    robot.forward(SIDE — 20 * (i/2));
    robot.setCourse(robot.getCourse() + 360 / COUNT);
    }

  • Firefly  says:

    А вот у меня вопросик: а можно ли эту визуализацию сделать анимационной?

    • admin  says:

      Можно. Только для этого надо запускать поток и по таймеру рисовать.

      • Firefly  says:

        Спасибо, значит будет изучать дальше 🙂

  • Vladimir  says:

    линии с координатами начала и конца (X1, Y2, X2, Y2).

    • admin  says:

      Спасибо, исправил.

  • Антон  says:

    for (int i=0; i<count; i++){
    robot.forward(side-i);
    robot.setCourse(robot.getCourse()+90);
    }

    Квадратная спирать

    • v  says:

      можно модернизировать: robot.forward(side-i*indent); //где indent — отступ

  • Антон  says:

    Плюс в том, что вне зависимости от количества поворотов всё адекватно рисуется. Если сделать зависимость от размера экрана (самой длинной стороны) и величины side, то можно даже потягать размеры в любые стороны экран — всё будет отображаться правильно. Далее можно сделать count зависимой от максимального числа поворотов. Формула будет что-то из разряда count=(side-1)*4. Не скажу за достоверность, но это на вскидку.

  • v  says:

    треугольная спираль

    final int COUNT = 3;
    final int SIDE = 400;
    int k = 50;
    Robot robot = new Robot(50, 50);

    for (int i = 0; i < k; i++) {
    robot.forward(SIDE — i*6);
    robot.setCourse(robot.getCourse()+360/COUNT);
    }

  • v  says:

    многоугольная спираль, можно достигнуть классической спирали (также прикрутил printCoordinates() для информации из предыдущих уроков)

    final int COUNT = 10;
    final int SIDE = 5;
    int k = 50;
    int indent = 2;
    Robot robot = new Robot(150, 150);
    robot.printCoordinates();

    for (int i = 0; i < k; i++) {
    robot.forward(SIDE — i*indent);
    robot.setCourse(robot.getCourse()+360/COUNT);
    }

    • v  says:

      robot.printCoordinates(); также добавить в цикл надо

  • Паша  says:

    public class RobotManager {
    public static void main(String[] args){

    final int COUNT = 82;
    int SIDE = 400;

    Robot robot = new Robot(50,50);

    for (int i = 1; i 4 && i % 2 == 0){
    SIDE -= 10;
    }

    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + (COUNT * 90) / COUNT);
    }

    RobotFrame rf = new RobotFrame(robot);
    rf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    rf.setVisible(true);
    }
    }

  • Паша  says:

    public static void main(String[] args){

    final int COUNT = 82;
    int SIDE = 400;

    Robot robot = new Robot(50,50);

    for (int i = 1; i 4 && i % 2 == 0){
    SIDE -= 10;
    }

    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + (COUNT * 90) / COUNT);
    }

    RobotFrame rf = new RobotFrame(robot);
    rf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    rf.setVisible(true);
    }
    }

  • Таня  says:

    Звезда:

    public class RobotManager
    {
    public static void main(String[] args) {
    final int COUNT = 5;
    final int SIDE = 200;

    Robot robot = new Robot(200, 200);
    for (int i = 0; i < COUNT; i++) {
    robot.forward(SIDE);
    robot.setCourse(robot.getCourse() + 2*360 / COUNT);
    }

    RobotFrame rf = new RobotFrame(robot);
    rf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    rf.setVisible(true);
    }
    }

  • Таня  says:

    квадратная спираль:

    public class RobotManager
    {
    public static void main(String[] args) {
    final int COUNT = 8;
    final int SIDE = 200;

    Robot robot = new Robot(100, 50);
    for (int i = SIDE; i > 0; i = i — 10) {
    robot.forward(i);
    robot.setCourse(robot.getCourse() + 90);
    }

    RobotFrame rf = new RobotFrame(robot);
    rf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    rf.setVisible(true);
    }
    }

    • Таня  says:

      сорри, тут переменная COUNT не нужна

  • Владимир  says:

    Полностью повторил ваш код про рисующего робота. Всё работает.
    У меня только один вопрос — при запуске в Netbeans класс RobotPathComponent вызывается 2 или 3 раза. Как такое вообще может быть?!

    • admin  says:

      А каким образом Вы это обнаружили ?

  • Владимир  says:

    Случайно. Решил использовать связанный список для построения маршрута, и вместо метода get() использовал метод pull(). Программа постоянно зависала и выдавала Nullpointerexeption. Поэтому поставил после каждой итерации вывод значения переменных на консоль. И оказалось, что робот проходит маршрут один раз, а вот отрисовывает то два, а то и три раза, т.е. одна и та же программа выдает РАЗНЫЕ результаты. Ума не приложу как это получается.

    • admin  says:

      То, что прорисовывается не один раз, а несколько — это как раз можно принять.
      В принципе правильно прорисовывать форму надо в отдельном специальном потоке, но в данном случае это уже разговор про потоки исполнения, о которых я рассказываю позже.

      • Владимир  says:

        То есть это нормально? До потоков я еще не добрался…

        • admin  says:

          Штука в том, что каждый раз при перерисовке формы компонент будет себя перерисовывать.
          Как форма решает, когда ей себя перерисовывать — это уже зависит от системы Swing, которая весьма не простая.
          Я глубоко не копался внутри Swing, но судя по тому, что я знаю, такой вариант развития событий вполне возможен.
          Особенно в нашем не совсем правильном решении.

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.