воскресенье, 7 июля 2019 г.

Sudoku Hunter

В последнее время наш блог молчит и может показаться, что «Карандаш и Самоделкин» все, сдулись. На самом деле работа кипела, а отсутствие публикуемых материалов было связано с тем, что проекты раз за разом становятся все сложнее и требуют все больше времени на реализацию. Последние наши разработки были так или иначе связаны с нейросетями, поэтому логично было за это время подтянуть скиллы по этой теме, что и было сделано – был пройден ряд онлайн курсов по и даже пара оффлайновых, из которых особенно понравилась «Зимняя школа машинного обучения» (Machine Learning Winter School).


Проект, работа над которым шла больше 4 месяцев, мы назвали Sudoku Hunter. Изначально была идея собрать робота по мотивам NXTSudokuSolver, однако постепенно она трансформировалось в нечто иное, на наш взгляд не менее интересное.
Итак, Sudoku Hunter – это пистолет с EV3, веб-камерой и Raspberry Pi на борту, из которого можно «стрелять» по математическим головоломкам судоку, получая на экране EV3-блока их решение. В алгоритмической части используются технологии машинного зрения и машинного обучения, веб-технологии для создания отладочной видеоконсоли.


Если кто не помнит, судоку - головоломка на бумаге, игровое поле которой представляет собой квадрат размером 9×9, разделённый на меньшие квадраты со стороной в 3 клетки. Таким образом всё игровое поле состоит из 81 клетки. В них уже в начале игры стоят некоторые числа (от 1 до 9), называемые подсказками. От игрока требуется заполнить свободные клетки цифрами от 1 до 9 так, чтобы в каждой строке, в каждом столбце и в каждом малом квадрате 3×3 каждая цифра встречалась бы только один раз.


Конструкция нашего робота не особенно замысловата, поэтому инструкцию по сборке мы решили не делать – вы легко соберете что-то подобное просто глядя на фото. Мы использовали следующие компоненты:
  • Блок Lego Mindstroms EV3 и строительные детали из этого набора
  • Raspberry Pi
  • Веб-камера Sony Playstation Eye
  • Wi-fi адаптер для связи EV3 с RPi
  • Powerbank для питания RPi

Программ, которые необходимы для работы робота - несколько, все они написаны на Python:
  • Программа для EV3 - отвечает за вывод информации на экран блока, связь с Raspberry Pi, передачу на RPi состояния датчика-кнопки
  • Программа для Raspberry Pi - отвечает за обработку кадров с камеры, распознаванием цифр с использованием обученной нейросети, последующим решением распознанного судоку, связь с EV3, вывод вспомогательной и отладочной информации на веб-консоль,
  • Программа для Raspberry Pi, предназначенная для создания дата сета для обучения нейронной сети.
  • Программа для ПК, используется для создания и обучения нейросети на основе подготовленного дата сета

Основная программа для Raspberry Pi работает по следующему алгоритму: 
  • Подключение библиотеки машинного зрения OpenCV
  • Подключение библиотек машинного обучения keras(tensorflow), sklearn
Процесс 1:
  • Взятие кадра с камеры
  • Преобразование изображения из BGR в HSV 
  • Наложение маски для фокусировки на значимых элементах изображения
  • Запись результирующего кадра в объект в памяти
  • Соединение с EV3 с помощью протокола socket 
  • Основной цикл:
  • Чтение кадра из объекта в памяти
  • Поиск в изображении с наложенной маской объектов, напоминающих квадрат 
  • Передача данных о предполагаемом расположении квадрата на EV3, для вывода на экран блока
  • Приём данных с EV3 о состоянии кнопки 
  • Если кнопка нажата:
Процесс 3:
  • Запуск веб-сервера (используется модуль Flask)
  • Соединение с Raspberry Pi с помощью протокола socket
  • Получение данных о предполагаемом расположении квадрата 
  • Подключение библиотеки машинного зрения OpenCV
  • Подключение библиотек машинного обучения keras(tensorflow), sklearn
Процесс 1:
  • Взятие кадра с камеры
  • Преобразование изображения из BGR в HSV 
  • Наложение маски для фокусировки на значимых элементах изображения
  • Запись результирующего кадра в объект в памяти
  • Соединение с EV3 с помощью протокола socket
  • Чтение кадра из объекта в памяти
  • Поиск в изображении с наложенной маской объектов, напоминающих квадрат 
  • Передача данных о предполагаемом расположении квадрата на EV3, для вывода на экран блока
  • Приём данных с EV3 о состоянии кнопки 
  • Если кнопка нажата:
  • Запуск веб-сервера (используется модуль Flask)
  • Вывод информации на веб-сервер - видеоконсоль для удобства отладки
  • Подключение библиотек машинного обучения 
  • Загрузка дата сета изображений 
  • Разбиение дата сета на учебные и тренировочные данные
  • Создание нейронной сети с заданными параметрами 
  • Обучение нейронной сети
  • Сохранение модели 

Процесс 2:
    • Изображение обрабатывается и судоку «нарезается» на 81 квадрат в каждом из которых одна клетка головоломки 
    • Каждый из фрагментов проверяется на наличие в нём цифры и если цифра обнаружена, то клетка подаётся на распознавание нейронной сети 
    • Распознанная цифра заноситься в массив. Когда массив заполнен всеми цифрами, он подаётся в алгоритм, решающий судоку 
    • Массив содержащий решённую головоломку передаётся на EV3 для вывода на экран блока 
  • Вывод информации на веб-сервер - видеоконсоль для удобства отладки

Программа на EV3 работает по следующему алгоритму:
  • Вывод контура квадрата на экран для визуального контроля (размер квадрата, угол разворота, степень его трапециевидности)
  • Передача состояния датчика-кнопки на Rpi
  • Если от RPi получен массив данных с решенной головоломкой - вывод решённого судоку на экран блока

Программа создания дата сета для обучения нейронной сети на Raspberry Pi имеет следующий алгоритм:
Процесс 2: 
    • Изображение обрабатывается и судоку «нарезается» на 81 квадрат в каждом из которых одна клетка головоломки 
    • Каждый квадрат сохраняется в папку, формируя дата сет
Процесс 3:

Программа на ПК, предназначенная для создания и обучения нейросети на основе подготовленного дата сета работает так: 
Стандартный алгоритм обучения нейронной сети мы усовершенствовали путём запуска функции обучения не на заданное количество эпох, а по одной эпохе, с проверкой результата на проверочной выборке после каждой. Если результат ухудшается несколько эпох подряд — прекращаем обучения с ошибкой. Если результаты на тестовой выборке выше 95% - прекращаем обучения, проверяем модель на отдельной выборке (которую она ещё не видела ни при обучении, ни при проверках после каждой эпохи).
Исходные коды всех программ проекта мы выложили здесь: https://github.com/darkavengersmr/Sudoku-Hunter
Демонстрация работы робота:

вторник, 8 января 2019 г.

PiTanks Online

Нам очень нравятся игры для программистов, такие как например Colobot и Ceebot. Писать код, загружать его в своего бота и запускать его для выполнения миссий - что может быть интереснее? Поддержка сетевого режима, когда другие игроки запускают своих прокачанных кодом ботов против твоего? Несомненно. Однако еще более интересным нам показалось самим разработать игру для программистов и поиграть в нее с друзьями.
Серверный игровой движок, многопоточность, асинхронный обмен данными, объекты и классы - все это мы приправим поддержкой дополненной реальности с использованием машинного зрения c OpenCV.


Приставка Pi в названии проекта "PiTanks Online" однозначно намекает на то, что в качестве серверной платформы мы использовали Raspberry Pi. Игровое поле в нашем проекте нарисовано от руки на листе бумаги.


На него смотрит веб-камера, установленная на подставке, собранной из LEGO Technic. В качестве рисованного поля может использоваться также маркерная доска.


Каждый кадр, полученный с камеры, обрабатывается в реальном времени. Все нарисованные или размещенные на поле объекты становятся непроезжаемыми для ботов препятствиями. Игра полна интерактива - положенная на игровое поле спичка или монетка становится новым испытанием - с момента их появления на поле ботам игроков приходится думать как их объезжать. Игровое поле опубликовано на веб-сервере, любой игрок может наблюдать за ходом битвы в браузере.

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

Наши примеры ботов имеют следующие наборы "команд":
  • forward() - бот смещается на 1 единицу дистанции вперед (в сторону, в которую он в данный момент развернут)
  • backward() - бот смещается на 1 единицу дистанции назад
  • right() - бот поворачивается на месте на 1 градус вправо
  • left() - бот поворачивается на месте на 1 градус влево
  • radar(a) - запросить данные с радара по направлению a (угла в градусах 0..359 от текущего направления бота). Функция возвращает кортеж из символа, обозначающего объект, который обнаружил радар (t - живой бот противника, w - стена или мертвый бот противника, n - "ничего нет") и числа - расстояния до обнаруженного объекта (0 в случае объекта "ничего нет") 
  • fire() - бот производит выстрел, снаряд летит по направлению разворота бота
  • energy() - возвращает количество доступных очков действий. Очки действий тратятся на движение, стрельбу и работу радара. Пополняются каждую секунду. Введены, чтобы никто из игроков не мог получить приоритета в обработке его бота сервером. 
  • exit() - отключиться от сервера, выйти из боя
Каждая из функций forward, backward, left и right возвращает True или False давая понять боту, была ли отработана команда. Это может быть удобным, например для понимания не застрял ли бот при маневре, упершись в препятствие.

При желании можно не использовать готовые функции из примеров, а написать свои. Например, доработать функции поворота возможностью принимать параметр "угол поворота", а функции движения - параметр "дистанция". Логику управления ботом в целом можно накрутить довольно сложную. Вполне адекватно работают, например, ПИД-регуляторы по дальномерам ботов, можно использовать элементы SLAM для локализации на карте.

Написанный на Python бот может работать не только на ПК, подходит практически любое устройство с интерпретатором этого языка, например LEGO EV3, ESP8266 или смартфон:


Исходный код сервера и примеры ботов доступны по ссылке.


пятница, 30 ноября 2018 г.

Pong: Next

Читатели нашего блога вероятно помнят наш проект 2016 года Laser Pong, в котором мы создали робота для игры в одну из первых компьютерных игр – Pong. Этой осенью мы снова решили вернуться к теме двух ракеток и мяча, взглянуть на эту простую, но увлекательную игру несколько под иным углом. Причем взглянуть - в прямом смысле этого слова: робот с машинным зрением будет смотреть камерой на экран и нажимать кнопки на компьютерной клавиатуре, играя на эмуляторе классического игрового автомата. Никаких регуляторов, только хардкор в виде расчетов траекторий полета мяча и отскоков. Но и это еще не все - в рамках проекта мы разработали свой Pong, с нейросетями и прочим машинным обучением. Интересно кто победит - машинное зрение и четкая математика или нейронные сети и вероятностная логика?



Старший участник команды «Карандаш и Самоделкин» спроектировал, построил и запрограммировал:
  • Робота, использующего камеру и технологии машинного зрения для распознавания и анализа происходящего на экране, способного играть в Pong, нажимая кнопки на компьютерной клавиатуре
  • Алгоритм игры в Pong, основанный на математической модели движения мяча, учитывающий траекторию его движения, множественные столкновения со стенками и передающий роботу информацию о том, где должен оказаться мяч после серии отскоков
Робот собран на базе LEGO Mindstorms EV3 и использует мотор для нажатия 2-х кнопок на клавиатуре. Для полноценной обработки видеопотока с камеры в реальном времени вычислительные мощности блока EV3 усилены микрокомпьютером Raspberry Pi 2, объединенным с ним беспроводной сетью. EV3 выступает в роли контроллера двигателя, управляемого по сокету TCP/IP.



Все программы в проекте Pong:Next написаны на языке Python, на ПК используется ОС семейства Windows, на Raspberry Pi и EV3 – Linux.

Для робота разработаны три программы:

1. Программа для позиционирования камеры относительно монитора. Использует веб-сервер для вывода на него видеопотока с камеры.
2. Программа для блока EV3 содержит в себе socket-сервер и пропорциональный регулятор управления двигателем. Для их независимого функционирования используется модуль threading. Кроме этого робот выводит на экран EV3 текущий счет в игре.
3. Основная программа, запускаемая для Raspberry Pi, работает по следующему алгоритму:

  Однократно:
  • Устанавливаем соединение socket-клиентом с сервером на EV3
  • Берём пробный кадр с камеры и устанавливаем размеры игрового поля
  • Запускаем параллельный процесс, отвечающий за веб-сервер и вещание картинки с отладочной информацией
  Главный процесс:
  • Обновляем кадр с камеры
  • Производим контурный анализ кадра, выделяя на нем мяч и ракетки с использованием модуля OpenCV
  • По данным данного и одного из предыдущих кадров определяем направление движения мяча и рассчитываем точку его касания о линию движения ракетки. Для этого рекурсивно находим пересечения линий, образованных точками текущего положения мяча и его положением в предыдущий момент времени с боковыми частями поля до тех пор, пока не будет вычислена точка касания с линией, вдоль которой двигается ракетка.
  • Проверяем, был ли гол, основываясь на перескоке мяча в центр кадра после его пропадания около одной из ракеток
  • По данным разницы между фактическим положением ракетки в кадре и желаемым ее положением по расчетным данным, определяем, какие кнопки на клавиатуре нужно нажать
  • Отправляем на EV3 данным о кнопках, которые требуется нажать и о счете в игре для отображения на экране блока EV3

Младший участник команды «Карандаш и Самоделкин» в рамках проекта разработал:
Программа для игры в Pong для ПК воссоздает классический игровой автомат с квадратным пиксельным мячом. Реализованы «подкручивания» мяча в момент его отбивания ракеткой – для корректировки угла отскока. Алгоритм компьютерного игрока основывается на использовании простейшей однослойной нейронной сети. Мы разбиваем поле на прямоугольные зоны, в каждой из которых выделяем N направлений полета мяча. Кроме этого выделяем зоны, в которых можем находиться управляемая сетью ракетка. Такие сочетания образуют нейрон сети, выход нейрона – вероятность нажатия кнопок «вверх» и «вниз» (либо отсутствия нажатий кнопок вообще, если вероятность низка).

Алгоритм обучения сети мы провели в два этапа:

1. «Обучения с учителем», сеть учится базовым правилам игры, играя с игроком.
  • Поначалу вероятность нажатия кнопок «вверх», «вниз» и «не нажата» равны, управляемая сетью ракетка в каждый момент времени движется хаотично
  • Параллельно сеть наблюдает в какой зоне, при каком угле полета мяча и при каком начальном положении ракетки человека какие кнопки на клавиатуре он нажимает. Эту информацию она обрабатывается, накапливаясь в коэффициентах (весах) нейронов
  • Чем дольше человек обучает сеть, тем больше информации о игровых ситуациях и действиях в них будет аккумулировано в нейронной сети. Уже с первых секунд игры хаотичные движения ракетки начинают трансформироваться в более «разумные». Через час игры сеть почти полностью копирует стиль игры человека.
2. «Машинное обучение». Две копии обученной человеком сети играют друг с другом, улучшая навыки игры, разыгрывая в том числе ранее не встречавшиеся игровые ситуации:
  • Ведем историю последних N комбинаций положения мяча, угла его полета и положения управляемой сетью ракетки в этот момент
  • Если управляемая сетью ракетка отбила мяч, увеличиваем коэффициенты соответствующих нейронов, укрепляя желание сети сыграть таким же образом в схожей игровой ситуации
  • Если управляемая сетью ракетка пропустила мяч, уменьшаем коэффициенты нейронов для последних N/5 комбинаций в истории

Демонстрацию работы робота можно посмотреть на следующем видео:

суббота, 15 сентября 2018 г.

NXT for Speed

Похоже LEGO Mindstorms NXT уже всеми забыт и в мире пластиковой робототехники безраздельно правит EV3. Однако мы не спешим списывать его со счетов и в нашем сегодняшнем проекте он еще даст жару!


NXT for Speed - гоночный аркадный автомат с использованием руля и педалей. Основная цель проекта - изучение основных принципов работы с динамической векторной графикой, закрепление навыков программирования многопоточных приложений на С.


В нашем проекте мы реализовали:
  • динамический расчет конфигурации трассы
  • физическую модель, учитывающую силу нажатия на педаль газа, сопротивление покрышек, сопротивление воздуха. Автомобиль способен двигаться накатом, тормозить с учетом силы нажатия на педаль тормоза.
  • вид от третьего лица с изменением угла зрения на автомобиль
  • "плавающий" горизонт с задним фоном (горы)
  • дорожные знаки на обочинах дороги 
  • управление с использованием руля (контроль угла поворота) и педалей (газ-тормоз)
  • реализован автовозврат педалей - они жесткие и возвращаются в исходную позицию, если их отпустить
  • обратная связь от препятствий - при съезде на обочины руль трясет
  • обратная связь от скорости движения - чем выше скорость тем сложнее резко повернуть руль, на стоящей машине руль поворачивается легко
  • цифровой спидометр в углу экрана
  • подсчет очков

Инструкцию по сборке в формате LEGO Digital Designer можно скачать по ссылке.


Программа написана на языке NXC и выглядит следующим образом:

Код:
int ohki=0;
int trig=1;
int r=23;
int ry=0;
int rx=0;
int mx=0;
int my;
float rz=2;
int l=0;
int c=0;
long t;
float m=500;
int f=4;
float x1=25,x2=75,y1=0,y2=0;
int rt=0;
int o=-10000000000,p=100000000;
int u=0;
float k=0.90;
int tmp =0;
float turn[48];
int ch=0;
float a=50;
int b=0;
int e=0;
int Sign(int x, int y,int v,int c)
{
  if(c==0){
    LineOut(x,y,x,y+v/2,DRAW_OPT_NORMAL);
    RectOut(x-v/4,y+v/2,v/2,v/2,DRAW_OPT_NORMAL);
  }
  else{
    LineOut(x,y,x,y+v/2,DRAW_OPT_CLEAR);
    RectOut(x-v/4,y+v/2,v/2,v/2,DRAW_OPT_CLEAR);
  }
}

int lines(int x, int y,int color)
{
  mx=x;

  if(color==0){
    RectOut(x-6,y+4,11,3,DRAW_OPT_NORMAL);
    RectOut(x-5,y+2,2,2,DRAW_OPT_NORMAL);
    RectOut(x+2,y+2,2,2,DRAW_OPT_NORMAL);
    LineOut(x+5,y+7,x+3,y+10,DRAW_OPT_NORMAL);
    LineOut(x-6,y+7,x-4,y+10,DRAW_OPT_NORMAL);
    LineOut(x-4,y+10,x+3,y+10,DRAW_OPT_NORMAL);
    if(CurrentTick()-t>500){
      TextOut(0,0,"    ");

      e=(500-m)/5;
      NumOut(0,0,e);
      t=CurrentTick();
    }
    if(MotorRotationCount(OUT_A)  > 15){
      LineOut(x+5,y+6,x+9,y+9,DRAW_OPT_NORMAL);
      LineOut(x+3,y+10,x+7,y+12,DRAW_OPT_NORMAL);
      LineOut(x-4,y+10,x-2,y+12,DRAW_OPT_NORMAL);
      LineOut(x,y+12,x+7,y+12,DRAW_OPT_NORMAL);
      LineOut(x+7,y+12,x+10,y+5,DRAW_OPT_NORMAL);
      LineOut(x+6,y+3,x+10,y+5,DRAW_OPT_NORMAL);
      LineOut(x+10,y+4,x+10,y+3,DRAW_OPT_NORMAL);
      LineOut(x+9,y+3,x+10,y+3,DRAW_OPT_NORMAL);

      if(x+10>=p+10){
        TextOut(35,20,"Over");
        rt=1;
      }
    }
    else if(MotorRotationCount(OUT_A)  <-15){

      LineOut(x+3,y+10,x+1,y+12,DRAW_OPT_NORMAL);
      LineOut(x-4,y+10,x-7,y+12,DRAW_OPT_NORMAL);
      LineOut(x+1,y+12,x-8,y+12,DRAW_OPT_NORMAL);
      LineOut(x-6,y+7,x-9,y+10,DRAW_OPT_NORMAL);
      LineOut(x-8,y+11,x-11,y+7,DRAW_OPT_NORMAL);
      LineOut(x-11,y+7,x-7,y+4,DRAW_OPT_NORMAL);
      LineOut(x-11,y+7,x-11,y+4,DRAW_OPT_NORMAL);

      LineOut(x-11,y+4,x-9,y+4,DRAW_OPT_NORMAL);
      if(x-11<=o-10){
        TextOut(35,20,"Over");
        rt=1;
      }
    }
    else{
      LineOut(x+3,y+10,x+1,y+12,DRAW_OPT_NORMAL);
      LineOut(x-4,y+10,x-2,y+12,DRAW_OPT_NORMAL);
      LineOut(x-2,y+12,x+1,y+12,DRAW_OPT_NORMAL);
      LineOut(x-5,y+11,x-5,y+11,DRAW_OPT_NORMAL);
      LineOut(x+4,y+11,x+4,y+11,DRAW_OPT_NORMAL);
      //if(x+10<=p || x-6>=o){
      //  TextOut(0,0,"Over");
      //  rt=1;
      //}
    }
    Wait(10);
  }
  else{
    RectOut(x-6,y+4,11,3,DRAW_OPT_CLEAR);
    RectOut(x-5,y+2,2,2,DRAW_OPT_CLEAR);
    RectOut(x+2,y+2,2,2,DRAW_OPT_CLEAR);
    LineOut(x+5,y+7,x+3,y+10,DRAW_OPT_CLEAR);
    LineOut(x-6,y+7,x-4,y+10,DRAW_OPT_CLEAR);
    LineOut(x-4,y+10,x+3,y+10,DRAW_OPT_CLEAR);

    LineOut(x+5,y+6,x+9,y+9,DRAW_OPT_CLEAR);
    LineOut(x+3,y+10,x+7,y+12,DRAW_OPT_CLEAR);
    LineOut(x-4,y+10,x,y+12,DRAW_OPT_CLEAR);
    LineOut(x,y+12,x+7,y+12,DRAW_OPT_CLEAR);
    LineOut(x+7,y+12,x+10,y+5,DRAW_OPT_CLEAR);
    LineOut(x+6,y+3,x+10,y+5,DRAW_OPT_CLEAR);
    LineOut(x+10,y+4,x+10,y+3,DRAW_OPT_CLEAR);
    LineOut(x+9,y+3,x+10,y+3,DRAW_OPT_CLEAR);
    LineOut(x+3,y+10,x+1,y+12,DRAW_OPT_CLEAR);
    LineOut(x-4,y+10,x-7,y+12,DRAW_OPT_CLEAR);
    LineOut(x+1,y+12,x-8,y+12,DRAW_OPT_CLEAR);
    LineOut(x-6,y+7,x-9,y+10,DRAW_OPT_CLEAR);
    LineOut(x-8,y+11,x-11,y+7,DRAW_OPT_CLEAR);
    LineOut(x-11,y+7,x-7,y+4,DRAW_OPT_CLEAR);
    LineOut(x-11,y+7,x-11,y+4,DRAW_OPT_CLEAR);
    LineOut(x-11,y+4,x-9,y+4,DRAW_OPT_CLEAR);
  }
  return 0;
}

task mot()
{
  Wait(3000);
  float P=0.7;
  //P=0.3*e;
  float Kp=e/100;
  float P2=0.7;
  float P3=0.7;
  float ERR1=0;
  float ERR2=0;
  float ERR3=0;
  float u1=0;
  float u2=0;
  float u3=0;
  while(1){
    if(rt==1){
      break;
    }

    float Kp=e/100;
    ERR1=0-MotorRotationCount(OUT_A);
    u1=P*ERR1*Kp;
    ERR2=0-MotorRotationCount(OUT_B);
    u2=P2*ERR2;
    ERR3=0-MotorRotationCount(OUT_C);
    u3=P3*ERR3;
    if(mx-11<=o+3){
      u1=Random(70)-35;
    }
    if(mx+10>=p-3){
      u1=Random(70)-35;
    }

    if(u1>100)u1=100;
    if(u1<-100)u1=-100;
    if(u1<0){
      OnRev(OUT_A,-1*u1);
    }
    else{
      OnFwd(OUT_A,u1);
    }
    if(u2>100)u2=100;
    if(u2<-100)u2=-100;
    if(u2<0){
      OnRev(OUT_B,-1*u2);
    }
    else{
      OnFwd(OUT_B,u2);
    }
    if(u3>100)u3=100;
    if(u3<-100)u3=-100;
    if(u3<0){
      OnRev(OUT_C,-1*u3);
    }
    else{
      OnFwd(OUT_C,u2);
    }
    Wait(100);
  }
}

task road()
{
  for(int i =0;i<47;i++){
    turn[i]=0;
  }
  while(true){

    ch++;
    f--;
    if(f==0){
      f=4;
    }

    l+=1;

    for(int i =0;i<23;i+=1){

      LineOut(25+turn[i]+i*0.9,i*2,25+turn[i+1]+(i+1)*0.9,i*2+3,DRAW_OPT_NORMAL);
      LineOut(75+turn[i]-i*0.9,i*2,75+turn[i+1]-(i+1)*0.9,i*2+3,DRAW_OPT_NORMAL);
      if(i==r){
        if(trig==1){
          rx=25+turn[i]+i*0.9-5;
          ry=i*2;
        }
        else{
          rx=75+turn[i]-i*0.9+5;
          ry=i*2;
        }
      }
      if(i%4==f  && i>5){
        LineOut(50+turn[i],i*2,50+turn[i+1],i*2+3,DRAW_OPT_NORMAL );
      }
    }
    Sign(rx,ry,rz,0);
    m+=5;
    m+=m*0.2;
    if(m>500){
      m=500;
    }
    if(m<0){
      m=0;
    }
    if(abs(MotorRotationCount(OUT_B))> 5){
      if(m<=500){
        m-=1.7*abs(0-abs(MotorRotationCount(OUT_B))*2);

      }
      if(m>500){
        m=500;
      }
      if(m<0){
        m=0;
      }
    }
    if(abs(MotorRotationCount(OUT_C))> 5){
      if(m>=0){
        m+=1.7*abs(0-abs(MotorRotationCount(OUT_C)));

      }
      if(m>500){
        m=500;
      }
      if(m<0){
        m=0;
      }
    }


    Wait(m);

    for(int i =0;i<23;i+=1){
      LineOut(25+turn[i]+i*0.9,i*2,25+turn[i+1]+(i+1)*0.9,i*2+3,DRAW_OPT_CLEAR);
      LineOut(75+turn[i]-i*0.9,i*2,75+turn[i+1]-(i+1)*0.9,i*2+3,DRAW_OPT_CLEAR);

      if(i%4==f && i>5)
      {
        LineOut(50+turn[i],i*2,50+turn[i+1],i*2+3,DRAW_OPT_CLEAR);
      }
      if(i==3){
        o=25+turn[i];
        p=75+turn[i];
      }
    }
    Sign(rx,ry,rz,1);
    rz+=0.5;
    r--;

    if(rz>=13){
      rz=2;
      r=23;
      trig=trig*-1;
    }
    for(int i =0;i<47;i++){
      turn[i]=turn[i+1];
    }
    if(ch==23){
      ohki+=1;
      ch=0;
      u = Random(100);
      turn[47]=u-50;
      for(int i =23;i<47;i++){
        turn[i]=turn[i-1]*k+turn[47]*(1-k);
      }
    }

    if(rt==1){
      break;
    }
    if(l==1){
      LineOut(70,0,70,35,DRAW_OPT_NORMAL);
      LineOut(70,35,30,35,DRAW_OPT_NORMAL);
      LineOut(70,17,30,17,DRAW_OPT_NORMAL);
      LineOut(70,0,30,0,DRAW_OPT_NORMAL);
      Wait(1000);
      LineOut(70,0,70,35,DRAW_OPT_CLEAR);
      LineOut(70,35,30,35,DRAW_OPT_CLEAR);
      LineOut(70,17,30,17,DRAW_OPT_CLEAR);
      LineOut(70,0,30,0,DRAW_OPT_CLEAR);
      LineOut(30,0,70,35,DRAW_OPT_NORMAL);
      LineOut(30,35,70,35,DRAW_OPT_NORMAL);
      LineOut(70,0,30,0,DRAW_OPT_NORMAL);

      Wait(1000);
      LineOut(30,0,70,35,DRAW_OPT_CLEAR);
      LineOut(30,35,70,35,DRAW_OPT_CLEAR);
      LineOut(70,0,30,0,DRAW_OPT_CLEAR);
      LineOut(30,17,70,35,DRAW_OPT_NORMAL);
      LineOut(70,0,70,35,DRAW_OPT_NORMAL);

      Wait(1000);
      LineOut(30,17,70,35,DRAW_OPT_CLEAR);
      LineOut(70,0,70,35,DRAW_OPT_CLEAR);
    }
  }
}

task main()
{
  t=CurrentTick();
  SetSensorTouch(IN_4);
  start road;
  start mot;

  ClearScreen();
  LineOut(0,48,100,48,DRAW_OPT_NORMAL);

  lines(a,b,0);

  while(true){
    NumOut(80,0,ohki);
    c=30-((a-50)/2);
    LineOut(c-50,49,c-30,53,DRAW_OPT_NORMAL);
    LineOut(c-10,49,c-30,53,DRAW_OPT_NORMAL);
    LineOut(c-10,49,c,55,DRAW_OPT_NORMAL);
    LineOut(c+10,49,c,55,DRAW_OPT_NORMAL);
    LineOut(c+5,51,c+15,55,DRAW_OPT_NORMAL);
    LineOut(c+25,51,c+15,55,DRAW_OPT_NORMAL);
    LineOut(c+20,51,c+35,55,DRAW_OPT_NORMAL);
    LineOut(c+40,49,c+35,55,DRAW_OPT_NORMAL);
    LineOut(c+40,51,c+55,55,DRAW_OPT_NORMAL);
    LineOut(c+60,49,c+55,55,DRAW_OPT_NORMAL);
    LineOut(c+60,51,c+85,55,DRAW_OPT_NORMAL);
    LineOut(c+100,49,c+85,55,DRAW_OPT_NORMAL);
    lines(a,b,1);
    if(abs(MotorRotationCount(OUT_A))>=15){
      a+=(MotorRotationCount(OUT_A))/100.0;
    }
    LineOut(c-10,49,c,55,DRAW_OPT_CLEAR);
    LineOut(c+10,49,c,55,DRAW_OPT_CLEAR);
    LineOut(c+5,51,c+15,55,DRAW_OPT_CLEAR);
    LineOut(c+25,51,c+15,55,DRAW_OPT_CLEAR);
    LineOut(c+20,51,c+35,55,DRAW_OPT_CLEAR);
    LineOut(c+40,49,c+35,55,DRAW_OPT_CLEAR);
    LineOut(c+40,51,c+55,55,DRAW_OPT_CLEAR);
    LineOut(c+60,49,c+55,55,DRAW_OPT_CLEAR);
    LineOut(c+60,51,c+85,55,DRAW_OPT_CLEAR);
    LineOut(c+100,49,c+85,55,DRAW_OPT_CLEAR);
    LineOut(c-50,49,c-30,53,DRAW_OPT_CLEAR);
    LineOut(c-10,49,c-30,53,DRAW_OPT_CLEAR);
    lines(a,b,0);
    if(rt==1){
      break;
    }
  }
  Wait(1500);
}



Самое популярное