Это продолжение публикации отрывков из готовящейся к изданию книги (предыдущая публикация здесь).
Предлагаемая глава представляет другое лицо книги: учебник программирования и инженерии.
Она посвящена следованию по линии - классической робототехнической задаче, перекочевавшей из промышленности в робототехнические соревнования для начинающих (но остающейся актуальной и в технике: недавно, например, фирма Рено Россия внедрила роботов-доставщиков, перевозящих постельное бельё и еду в больнице в Коммунарке).
С точки зрения алгоритма следования алгоритма следования по линии глава повторяет старую публикацию на эту тему. Вместе с тем, подача материала с учебной точки зрения существенно изменилась по ряду причин:
- в системе MakeCode добавилась возможность возращать значение функции, и это прогресс в системе нашёл, естественно, отражение в методике обучения программированию
- в системе также появился выход из цикла, что тоже нашло отражение в методике
- автор пересмотрел подачу ветвления с несколькими ветками в отсутствии конструкции switch (методически это, пожалуй, самая сложная тема в азах программирования)
- изложение стало более подробным и иллюстрированным, с большим креном к обучению программированию.
Итак, заключительная 19-ая глава, которая вынесена за скобки сказочного сюжета.
19. ЛЕКЦИЯ НА ФЕСТИВАЛЕ ФЕСТИВАЛЕЙ
'Следование по линии' — классическая задача робототехники. Система линий, ненасённых на полу или потолке производственного или складского помещения, может служить обозначением маршрута для робота, перевозящего грузы. Для слежения за линией у робота есть датчики. Робот передвигается строго вдоль линий. Достоинством такой системы является простота. С одной стороны, 'зрение' робота устроено очень просто: ему достаточно различать всего два цвета: цвет пола и цвет линии. С другой стороны — простота устройства 'дорог' для робота: робот движется по обычным полам, а нанесение линий является простой задачей и, главное, нансённые линии никому не мешают. Современные роботы зачастую используют более сложную систему навигации, обладают развитыми способностями ориентироваться и не нуждаются в 'подсказках' в виде дополнительно нанесённых линий.
При относительной простоте, задача следования по линии является в полном смысле слова инженерной задачей. 'Следование по линии' — это не просто программирование, когда робота программируют определённым образом, и на этом дело заканчивается. Решение задачи следования по линии — это циклический инженерный процесс, когда разрабатывается начальное решение, это решение испытывается, анализируются результаты этого испытания, по результатам анализа создаётся новая версия решения, и далее по кругу, с постепеннм улучшением создаваемого инженерного продукта. Братья Райт, первые в мире продемонстрировавшие пилотируемый полёт машины с двигателем, не могли начать сразу с современного нам Боинга — созданию современных самолётов предшествовало много оборотов инженерного цикла: идея — разработка — испытание — анализ результатов — новая идея...
Сочетание простоты с инженерной природой сделало задачу следования по динии популярнейшей темой роботехнических соревнований для начинающих.
Инженерная история 1
Братья Райт, коим принадлежит слава первого управляемого пилотом полёта летательного аппарата с двигателем, значительное время занимались изучением управляемости планёра.
Вывод: для решения сложной инженерной задачи нужна хорошая подготовка.
Теперь мы приступим к созданию нашего первого 'аэроплана'.
Описываемая дальше работа привязана к конкретному полю и конкретному роботу (Maqueen). Естественно, не все результаты будут приложимы к другим роботам и полям, наша цель - показать возможные подходы для решения этого класса задач. Подготовку к разработке алгоритма начнём со сбора начальной информации
Подготовка: сбор начальной информации. Как можно подготовиться к разработке алгоритма следования по линии, что нужно изучить? Естественно, изучить то, что для процесса существенно.
Немного подумав, можно прийти к выводу, что нужно
1. Понимать возможности робота
2. Понимать особенности трассы.
Возможности робота - это об источниках информации для робота (информация с датчиков следования линии). В нашей ситуации речь идёт о роботе Maqueen, оснащённом парой датчиков для следования линии (рис. 19.1).
Рис. 19.1. Датчики следования по линии робота Maqueen
Датчики могут возвращать одно из двух значений: 0 или 1 (двух цветов нам достачно, чтобы различать поле — белое — и нанесённую на поле чёрную линию для следования робота, рис. 19.2).
Рис. 19.2. Пример поля для следования по линии с замкнутой трассой (dfrobot)
Поскольку документации в коробке с роботом нет,
Задача 1: определить кодировку датчика.
Обращение к датчику выполняется с помощью овального блока 'датчик следования по линии ...'. Дальнейшее — несложная задача для самостоятельной работы, поскольку опыт работы с датчиками у нас уже есть.
Результат исследования — на роботе Maqueen белый цвет кодируется единичкой. Мы получили первые константы, пора начинать сбор информации в проект 'Следование по линии' (рис. 19.3).
Рис. 19.3. Кодирование цветов, получаемых с датчика
Задача 2: определить скорость робота.
Задача допускает разные решения (например, можно запустить робота на 10 секунд).
Результат эксперимента на солевых батарейках Flarx: скорость 19.5 см/c. Стоп, батарейки-то уже были какое-то время в работе. Меняем батарейки на новые: 23.5 см/c. Добавлю, что можно попробовать и с другими типами батареек, и результат наверняка будет другой.
Делаем вывод: нельзя жёстко опираться на скорость.
Линейная скорость - не единственная скорость, которая нас интересует. В ситуации, когда робот сбился с курса и должен поправить свой маршрут, он, естественно, не может продолжать прямолинейное движение.
Дополнительное задание: определить угловую скорость робота, когда одно колесо остановлено, а другое двигается.
Задача 3: определить расстояние между датчиками следования по линии.
У робота Maqueen расстояние между датчиками фиксировано и равно 15 мм.
Задача 4. Определить параметры поля для следования по линии.
Поле (рис. 19.2) воспроизводит по форме беговую дорожку вокруг футбольного поля: 2 параллельных отрезка, соединенных с обоих концов полуокружностями.
Размеры:
длина по продольной оси (включая ширину линии) - 86 см
ширина (включая ширину линии) 46 см
радиус закругления (включая ширину линии) - 23 см
ширина линии - 15 мм
Определение стратегии следования по линии. Прежде всего, нужно определиться, будем ли мы разрабатывать алгоритм для конкретной трассы, учитывая её параметры, или пытаться разработать универсальный алгоритм для любой замкнутой непересекающейся трассы.
Попытка сразу пытаться написать эффективный алгоритм под конкретную трассу может привести к эффекту 'За деревьями леса не видим'. Лучше, пожалуй, поработать над общим алгоритмом, а после решать, допускает ли он оптимизацию под конкретную трассу.
В общих чертах следование по линии выглядит так:
Считываются данные с датчиков
Если показания датчиков говорят о том, что робот находится на трассе с правильным курсом
То робот движется вперёд
Иначе робот выполняет маневры для возвращения на правильный курс
Отправная точка разработки — определение состояния робота, которое нас устраивает, то есть, не требует коррекции. Конечно, нам хочется, чтобы робот двигался вперёд.
А показания датчиков? Чтобы определиться с этим вопросом, нужно сначала сравнить ширину линии трассы и расстояние между датчиками:
— если расстояние между датчиками больше ширины линии, то можно попытаться держать датчики слева и справа от линии, то есть, ориентироваться на показания 'белый И белый'
— если расстояние между датчиками меньше ширины линии, то можно попытаться держать датчики над линией, то есть, ориентироваться на показания 'чёрный И чёрный'
А если расстояния равны?
В последнем случае (а это как раз наш случай), очевидно, придётся обеспечивать несимметричное положение робота относительно линии трассы: один датчик должен находиться вне линии ('белый'), а второй - на линии ('черный').
Вариант показания датчиков 'белый И чёрный' допускает два расположения робота относительно линии:
— расположение 'внутри', когда 'белый' датчик находится внутри трассы
— расположение 'снаружи, когда 'белый' датчик находится снаружи трассы
Что выбрать: прохождение трассы снаружи или внутри? Самоё простое соображение — внутри путь короче. Остановимся для начала на этом решении (но будем помнить, что выбору мы посвятили совсем немного времени, значит, надо оставить за собой право пересмотреть подход позже. Вообще, развилки проекта следует помнить и документировать, чтобы позже иметь возможность проверить альтернативные подходы).
Для определённости будем считать, что 'белым' будет левый датчик. Это означает, что испытания мы будем проводить с ездой против часовой стрелки.
Настал момент сформулировать желаемое состояние робота во время прохождения трассы:
— робот движется вперед
— левый датчик показывает 'белый', правый — 'чёрный'
Если робот находится в этом состоянии, то мы не предпринимаем никаких корректирующих действий и позволяем роботу двигаться дальше. На рисунке 19.4 это состояние помечено как основное.
Рис. 19.4. Варианты показаний датчиков следования по линии
Как только робот выходит из целевого состояния, необходимо выполнить корректирующие действия.
Мы подошли к важному моменту практически любой программистской задачи: определение, наукоообразно выражаясь, пространства событий, а проще говоря — всех возможных состояний робота. Заметим, что определение пространства событий — важный элемент культуры мышления: прежде чем бросаться на решение сложной задачи, не только учебной или научной, но и житейской, нужно прежде всего определить все варианты, которые следует рассмотреть.
Состояния робота, структура и данные программы. Состояния можно определить просто: 2 датчика, по два показания у каждого, итого 2 во второй степени, то есть 4. Эти 4 состояния показаны на рисунке 19.4.
На каждое из 4 состояний мы напишем функцию, и будем вызывать функцию в зависимости от текущей комбинации показаний датчиков:
'ЧёрныйЧерный' — функция, вызываемая, когда оба датчика показывают 'чёрный' (00)
'ЧёрныйБелый' — левый датчик — 'чёрный', правый — 'белый' (01)
'БелыйЧерный' — левый датчик — 'белый', правый — 'чёрный' (10)
'БелыйБелый' — оба датчика показывают 'белый' (11)
Для результатов опроса датчиков создадам переменные 'левый датчик' и 'правый датчик'.
Опрос датчиков и вызов подпрограмм поместим внутрь цикла 'пока' (папка Циклы).
Цикл 'пока' может исполняться бесконечно, если условие цикла, которое находится в шестиугольном блоке внутри блока 'пока', будет продолжать оставаться истинным.
Почему мы будем использовать цикл 'пока', а не блок 'постоянно'? Дело в том, что блок "постоянно" будет исполняться всегда, не оставляя возможности завершить программу в случае обнаружения неразрешимых проблем (а с такой ситуацией в инженерной задаче нужно считаться). Блок же 'пока' работает, пока условие в нем истинно, и это условие можно поменять, чтобы выйти из цикла: этим мы займёмся чуть позже.
Итак, у нас есть цикл 'пока', перед циклом мы запускаем моторы, после цикла - останавливаем их, внутри цикла опрашиваем датчики и, в зависимости от значений, полученных с датчиков, вызываем ту или иную процедуру.
Поскольку наша программа содержит развилку (нужно выбирать вызываемую функцию в зависимости от значений, полученных с датчиков), то нам нужно использовать блок 'если'. В блоке 'если' нужны 4 ветки — для вызова наших 4-х функций, но мы сделаем 5, по аналогии с программой работы с Радио. подробнее поясним чуть позже (рис. 19.5).
Рис. 19.5. Структура программы
Как написать условия в ветках цикла. Условия, по которым вызываются функции в ветках блока 'если', должны содержать две проверки: проверку на значение с левого датчика и проверку значения с правого датчика. Объединение двух проверок выполняется с помошью шестиугольного блока '... и ...', папка Логика, секция Логические значения (рис. 19.6).
Рис. 19.6. Проверка условий для вызова функции
Почему мы зарезервировали 5-ую ветку ('иначе') в блоке 'если'? По идее, последняя ветка в блоке 'если' никогда не будет исполняться, зачем же она нужна?
Во-первых, это делает программу нагляднее, поскольку для каждой исполняемой ветки видно условие, при котором ветка выполняется. Если бы последней исполняемой веткой была ветка 'иначе', то нужно было бы приложить определённые усилия, чтобы определить, при каком всё-таки условии ветка выполняется. А наглядность, ясность программы — это очень важно.
Во-вторых, наличие 'запасной' ветки позволяет контролировать полноту проверяемого набора условий. Если мы не учли какое-то состояние — допустим, мы насчитали всего 3 возможных состояния, а не 4, или одна из веток случайно была не написана или удалена, то программа, не найдя подходящего условия, попадёт в ветку 'иначе'. Попадание в ветку 'иначе' говорит о том, что в программе ошибка (рис. 19.7).
Рис. 19.7. Ловушка для Баги
Мы выбрали самое большое однозначное число (9) в качестве кода завершения программы при внутренней ошибке программы, оставляя другие числа для сообщений о других ситуациях. Выбор однозначного числа для сообщения вызван тем, что дисплей платы микро:бит мал, и длинное сообщение просто пробежит по экрану, и от него останется только последний символ.
Кстати, наша ловушка может помочь обнаружить и более тонкую ошибку в программе: если число веток будет правильным, но условия в каких-то ветках совпадут из-за 'описки' в программе (рис. 19.8).
Рис. 19.8. 'Описка' в программе
Здесь из-за ошибки при создании программы последнее условие 'потерялось' и совпало с 3-им условием. Это значит, что четвёртая ветка никогда не будет выполнена: ведь по такому же условию сработает 3-ья ветка, а если какая-то ветка сработала, то выполнение блока 'если' будет завершено, и оставшиеся проверки выполняться не будут. Логика программы сломана из-за маленькой, едва заметной ошибки. Такие ошибки, увы, подстерегают нас при создании нескольких похожих фрагментов, отличающихся лишь небольшими деталями (в нашем случае это похожие друг на друга условия). Тут нужно быть особенно внимательным.
Главная опасность таких малозаметных ошибок состоит в том, что мы думаем, что наша написана так, как мы её задумали, а на самом деле мы работаем с совсем другой программой. Это приводит к тому, что мы начинаем искать проблему с программой совсем не там, где она есть (например, внутри функций, если бы они уже были написаны), и даже ломать правильные части программы, пытаясь исправить нашу программу.
Что же произойдёт в нашей программе, если оба датчика покажут белый цвет? Условие это из-за ошибки утеряно, значит, выполняться будет ветка 'иначе'. Если бы ветка 'иначе' не была специально добавлена, то в ней уже был бы вызов какой-то функции. Значит, в ветку 'иначе' программа попадала бы в двух случаях: в том случае, для которого она была задумана, и в том случае, когда в программу вкралась лшибка. С помощью дополнительной ветки мы добились того, что в 'иначе' программа может попасть только в случае ошибки.
Конечно, не все ошибки можно поймать таким образом, но если есть возможность сделать свою программу более надёжной с помощью подобного внутреннего контроля — это нужно делать. Чем большего размера будут становиться наши программы, тем больше вероятность проникновения в них ошибок, и тем большее значение играет способность создавать программы, которые хорошо читаются и содержат средства внутреннего контроля. Большую программу, написанную хаотично и небрежно, невозможно 'довести до ума'. Поэтому так важно осваивать приёмы грамотного создания программ с самого начала.
Как выйти их цикла 'пока'. Цикл 'пока' представляет из себя комбинацию цикла с проверкой условия, как в блоке 'если'. Условие в цикле 'пока' проверяется перед каждым шагом цикла: если условие истинно, то выполняется очередной шаг цикла, а если нет, то выполнение цикла прекращается, и выполнение программы переходит на блок после цикла. Но мы пока не будем останавливаться подробнее на этом механизме выхода из цикла, поскольку есть более простой и чёткий путь — использование блока 'прервать'.
Мы использовали блок 'прервать' в 'Ловушке для Баги', посколько при обнарущении ошибки продолжать выполнение программы нет смысла: надо искать ошибку.
Мы выполнили очень важную часть работы: создали структуру программы под нашу задачу, это — здоровая основа для последующей работы. Но эту структуру нужно заполнить содержанием: нужно разработать функции, которые будут вызываться при тех или иных значениях датчиков.
'Схлопывание' блоков. Прежде чем начать работу над функциями, расчистим себе место. Блоки большого размера можно 'схлопнуть' в небольшое изображение, чтобы освободить пространство в поле сборки программы. Блок 'при начале' как раз достиг таких размеров, когда его лучше прятать, когда мы с ним не работаем. Находясь на блоке, откроем меню правой кнопки мыши и выберем 'Collapse Block' (рис. 19.9).
Рис. 19.9. Расчистка места в поле создания программы
Для того, чтобы развернуть блок в исходное состояние, нужно просто нажать на 'галочку ' на блоке.
Теперь приступим к функциям.
Функция БелыйЧёрный — делать ничего не надо.
Как мы договорились, это наше главное состояние - здесь делать ничего не надо, функция останется пустой (до поры: если мы сочтём позже нужным что-то делать в желаемом состоянии, — да хоть радостно посигналить, нам не нужно будет переделывать структуру программы).
Из состояния 'БелыйЧёрный' робот может перейти в состояние 'БелыйБелый', то есть, оказаться внутри трассы или начать движение наружу и перейти в состояние 'ЧёрныйЧерный'. Состояние 'ЧёрныйБелый' будем пока считать запрещённым: из соседнего состояния "ЧёрныйЧёрный" мы должны вернуться в базовое состояние 'БелыйЧёрный'.
Функция 'ЧёрныйБелый' — запретное состояние.
Итак, мы запретили это состояние, о чём надо сделать пометку в дневнике проекта, не забывая о возможности пересмотреть это решение в процессе развития программы.
Как функция может сообщить основной программе о том, что обнаружились обстоятельства, которые не позволяют продолжать выполнение программы? Для этого есть возможность передать вызывающей программе код завершения — число, которое говорит о том, по какой причине функция завершила своё исполнение. Код завершения передаётся с помощью блока 'return …', папка Функции.
Перенесём блок 'return …' внутрь функции 'ЧёрныйБелый' запишем в окне блока число 1 — наш код завершения (рис. 19.10).
Рис. 19.10. Использование блока 'return …'
Блок 'return …' осначает 'завершить выполнение функции и передать вызывающей программе код завершения ...'.
Заметим, что после того, как мы поместили блок 'return …' в функцию, в папке Функции появился овальный блок 'ЧёрныйБелый' (он отмечен на рисунке). Это означает, что мы можем обращаться к такой функции подобно тому, как мы ображаемся к датчикам: вызывать функцию, используя овальный блок как числовое значение. Значением функции и будет код завершения, который мы записали в блоке 'return …'.
Для единообразия, поставим блок 'return …' во все остальные функции со значением 0 (так мы обозначим нормальное завершение функции).
Создадим переменную 'код завершения' и будем в основной программе присваивать ей значение при вызове функций (рис. 19.11).
Рис. 19.11. Вызов функций с получением кода завершения
В результате на каждом шаге цикла в блоке 'если' та функцияя, которая будет вызвана, задаст значение переменной 'код завершения'. Если значение будет 0, то, как мы договорились, всё в порядке, а если не 0, то это значит, что вызванная на этом шаге цикла столкнулась с проблемой, после которой продолжать выполнение программы не имеет смысла.
Завершение цикла 'пока' по условию. Чтобы решить, продолжать нам выполнение цикла или завершить работу, мы могли бы поставить блоки 'если' для проверки результатов после каждого вызова функции. Но, поскольку на каждом шаге цикла выполняется только одна функция, можно поступить проще: поставить только одну проверку для шага цикла. В каком месте цикла поставить такую проверку? Тут пора вспомнить о том что цикл 'пока' содержит условие, которое проверяется перед каждым шагом цикла. Сейчас на месте проверки стоит блок 'истина,' что означает, что результат проверки всегда положительный. А нам нужно проверить, равен ли код завершения нулю. Эту проверку мы и поставим в условие цикла (рис. 19.12).
Рис. 19.12. Проверка условия в цикле 'пока'
Перед циклом мы задали значение переменной 'код завершения' равным 0: это необходимо, чтобы цикл выполнился первый раз, а в дальнейшем уже значение будут обновляться результатами вызова функций.
После цикла мы поставили блок 'показать число (код завершения)', чтобы вывести информацию о том, почему закончилась программа. Правда, теперь получилась небольшая нестыковка: если цикл завершится в ветке 'иначе', то код заверешения сначала будет показан внутри ветки 'иначе' (9), а после цикла будет выведено старое значение переменной 'код завершения' — 0. Одну и ту же, по сути, работу: вывод кода завершения, мы выполняем в разных местах, и это стало источником проблемы.
Программу целесообразно создавать так, чтобы какая-то значимая работа, потребность в которой может возникнуть в разных местах программы, выполнялась в одном месте или одной и той же функцией. Повторение каких-то частей программы удлинняет программу, затрудняет её восприятие и, главное, может в будущем привести к ошибкам, когда нам понадобится изменить способ выполнения этой работы, поскольку нужно будут отыскать все места, где работа выполняется, и исправить их одинаковым образом.
Вывод для нашего случая можно сделать простой: поступить в ветке 'иначе' так, как если бы это был вызов ещё одной функции, то есть, просто задать код завершения, позволяя программе обработать его так, как это будет нужно, а не брать на себя обязанность по выводу сообщения об ошибке (рис. 19.13).
Рис. 19.13. Вывод кода завершения, окончательный вариант
Это программу можно проверить на корректность завершения работы, поставив робота так, чтобы датчики сразу показали "ЧёрныйБелый".
Теперь приступаем к содержательной части задачи: коррекции курса робота.
Функция 'БелыйБелый' — выход из положения внутри трассы
Что делать, если мы оказались внутри трассы? Для начала надо вернуться к линии.
Как? Например, выполняя поворот направо, то есть, остановив правое колесо, продолжить движение до тех пор, пока линия не будет 'поймана' датчиками (не забудем занести наше решение в дневник).
Что дальше? Не будем спешить, для начала испытаем эту часть программы. Чтобы завершить выполнение программы после вращения вокруг правого колеса, поставим код завершения 3 (рис. 19.14).
Рис. 19.14. Функция 'БелыйБелый', отладочный вариант
В принципе можно было выбрать любой ещё не задействованный код завершения, просто 3 в двоичной системе — это 11, как показания датчиков.
Мы создали для этой функции новые переменные 'левый датчик ББ' и 'правый датчик ББ'. Подобно тому, как каждому нужны личная расчёска и личная зубная щётка, функциям нужны свои переменные. ББ в имени переменной мы использовали для обозначении принадлежности переменной функции 'БелыйБелый'.
Общее использование переменных основной программой и функциями может привести к непредсказуемым последствиям, в которых очень сложно разбираться. Такое использование допускается только в специальных случаях и обычно не предполагает изменения изменения значения переменной.
Для испытания программы поставим робота чуть внутрь трассы, чтобы оба датчика были на белом и включим питание. Робот немного повернётся вокруг правого колеса и остановится.
После испытания программы мы можем видеть, что курс робота идёт под существенным углом к трассе. Если в этот момент возобновить работу двух моторов, то робот через долю секунды выскочит за пределы трассы. Значит? Нужно скорректировать курс робота. Как? Например, останавливаем левый мотор, запускаем правый — робот будет разворачиваться к направлению трассы.
Как долго нужно корректировать курс? Примерно столько же, сколько робот 'ловил' линию.
А как отмерить 'примерно столько же'? Заведём счётчик числа обращений к датчикам, чтобы использовать его как меру времени (рис. 19.15).
Рис. 19.15. Функция 'БелыйБелый' с коррекцией курса
В первом цикле мы 'накручивали' счётчик — считали число обращений к датчикам. Во втором цикле идёт обратный счёт — счётчик уменьшается после каждого обращения к датчикам, пока он не станет нулём: так мы уравниваем число обращений к датчикам в первом и во втором циклах. В принципе можно было использовать цикл 'повторить', где счётчик задал бы число шагов цикла, но лучше всё-таки в явном виде изменять счётчик, поскольку именно так мы поступали в первом цикле.
Зачем мы вообще опрашиваем датчики во втором цикле, если мы не анализируем данные с них? Причина та же: просто для того, чтобы шаг второго цикла был максимально похож на шаг первого цикла и потреблял примерно такое же время.
Функции 'ЧёрныйЧерный' и 'ЧёрныйБелый'
После испытаний получившейся версии, пора приступить к коррекции в состоянии ЧёрныйЧёрный. В этой ситуации робот настроен выскочить наружу (вправо). Чтобы поправить курс, остановим на какое-то время левый мотор, чтобы робот повернул влево, а потом возобновим работу двух моторов. Время коррекции курса занесём в переменную 'коррекция ЧЧ' (рис. 19.16).
Рис. 19.16. Функция 'ЧёрныйЧёрный'
Попробуем подбором найти время коррекции. После нескольких опытов получилось, что вариант с 200 мс работает удовлетворительно.
Поскольку робот выскакивает наружу, стоит пересмотреть запрет состояния ЧёрныйБелый. Как нетрудно видеть, ситуация в состоянии ЧёрныйЧёрный схожа с ситуацией ЧёрныйБелый: надо немного подрулить влево. Для начала просто используем функцию 'ЧёрныйЧёрный' в состоянии ЧёрныйБелый.
Рис. 19.17. Функция 'ЧёрныйБелый'
Получившийся вариант заработал: робот достаточно стабильно ходит по трассе как против часовой стрелки (внутри трассы), так и по часовой стрелке (снаружи трассы), и это приятный бонус.
Заключение. Программу можно посмотреть по адресу https://makecode.microbit.org/_62F0wDa0RLW9
Это, конечно, 'Аэроплан братьев Райт'. Впереди — широкое поле совершенствования алгоритма, исследований приложения алгоритма к разным типам трасс, и так далее.
Инженерная история 2
Заслуженный и известный в своё время учёный и изобретатель Лэнгли (США) имел щедрый правительственный грант на разработку пилотируемого летательного аппарата. Но история помнит братьев Райт, начинавших в своём гараже.
Вывод: то, что в области поработало немало авторитетов, не значит, что там не осталось места для прорыва. Нужно дерзать!