Игровой автомат «Капитан Крюк»

Любите доставать игрушки из автоматов, но устали от проделок лохотронщиков? «Капитан Крюк» точно придется вам по душе.

В основе проекта принцип дельта кинематики, широко применяющийся в 3D-принтерах. Захват передвигается внутри игрового поля по трём осям за счет изменения длины трёх тросов.

В роли захвата — крюк, отсюда и пиратское имя аппарата. Арррр!

Были конечно и другие варианты названия, но это совсем другая история…

Видеообзор

Как устроен «Капитан Крюк»

Из чего же мы собрали нашего Капитана Крюка?

Посмотрим на принципиальную схему игрового автомата

Что понадобится

Корпус автомата

Основание и крыша

Игровое поле аппарата состоит из двух равносторонних треугольников. Мы взяли в строительном магазине несколько листов 12 мм фанеры и вырезали из них основание и крышу. В основании просверлили отверстие для стоек. В крыше наметили сверлом углубления. Стойки зафиксированы, но не проходят сквозь крышу.

Стойки

Для стоек взяли двухметровые хромированные трубы диаметром 25 мм из системы мебельных конструкций Joker.

Стенки корпуса

Стенки корпуса выпилили из 6 мм фанеры. Три прямоугольника со сторонами 80/75 см.

Оргстекло

Игровое поле оградили стенками из 3 мм оргстекла. Вырезали три прямоугольника 110/80 см.

Крепление Joker

С помощью крепления системы Joker скрепляем детали на стойках.

Механика и электроника

Привод оси и блок с кольцевиками

За изменение длины каждого дельта луча отвечает отдельный привод, состоящий из мотора с редуктором и катушки. Для ограничения наматывания троса на катушку установили концевой выключатель. Когда трос размотается на максимальную длину, концевик активируется и наматывание троса прекратится.

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

Электроника

Для управления приводами мы использовали 2 Motor Shield которые установлены на управляющую плату Iskra Neo.

Панель управления

Мы установили Troyka Shield для подключения блока управления автоматом: потенциометров, кнопок-кольцевиков, дисплея и кнопки старта.

Алгоритм работы

Ручки потенциометра работают в трёх режимах. Когда ручка потенциометра в среднем положении, привод не перемещает крюк.

Если повернуть ручку влево, привод начнет наматывать трос, подтягивая крюк к себе. Скорость наматывания зависит от угла поворота. Привод крутится пока повернута ручка или пока фиксатор на тросе на активирует кольцевик на приводе.

Если повернуть ручку вправо, привод начнет разматывать трос. С увеличением угла поворота ручки возрастает скорость разматывания. Привод перестанет разматывать трос, когда из-за провисания на верхней системе блоков кнопка-концевик разомкнётся.

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

Исходный код

Скетч, который мы использовали в проекте,написан для Iskra Neo и Arduino Leonardo. Если вы хотите повторить проект на других платформах, потребуется внести изменения.

В проекте используется библиотека для работы с четырёхразрядным индикатором. Не забудьте скачать и установить её.

gameHook.ino
// подколючаем библиотеку для работы с дисплеем
#include <QuadDisplay2.h>
// создаём объект для работы с дисплем и передаем ему пин CS
QuadDisplay qd(3);
// пин кнопки старта
#define BUTTON_START 13
 
#define EN_PIN1       5
#define H_PIN1        4
#define EN_PIN2       6
#define H_PIN2        7
#define EN_PIN3       9
#define H_PIN3        8
 
// пины подколючения потенциометров
#define POT1_PIN      A3
#define POT2_PIN      A4
#define POT3_PIN      A5
 
// пины нижних концевиков
#define DOWN1         12
#define DOWN2         11
#define DOWN3         10
 
// пины верхних концевиков
#define TOP1          A1
#define TOP2          A2
#define TOP3          A0
 
// минимальная скорость моторов
int miniSpeed = 50;
// состояние игры
bool gameState = false;
// время игры
int gametime = 60;
// текущее время
unsigned long lastMillis;
// защита от дребезга нижних концевиков
bool firstPressButton1 = false;
bool firstPressButton2 = false;
bool firstPressButton3 = false;
 
void setup()
{
  // открываем монитор Serial-порта
  Serial.begin(9600);
  // начинаем работу с дисплеем
  qd.begin();
  // назнчаем пины кнопок в режим входы
  pinMode(DOWN1, INPUT);
  pinMode(DOWN2, INPUT);
  pinMode(DOWN3, INPUT);
  pinMode(TOP1, INPUT); 
  pinMode(TOP2, INPUT);
  pinMode(TOP3, INPUT);
  pinMode(BUTTON_START, INPUT);
  // назначаем пины управления моторов в режим выхода
  pinMode(EN_PIN1, OUTPUT);
  pinMode(EN_PIN2, OUTPUT);
  pinMode(EN_PIN3, OUTPUT);
  pinMode(H_PIN1, OUTPUT);
  pinMode(H_PIN2, OUTPUT);
  pinMode(H_PIN3, OUTPUT);
  // запоминает текущее время
  lastMillis = millis();
}
 
void loop()
{
  Serial.println(analogRead(POT1_PIN));
  // если нажата кнопка старта и статус игры неактивен
  if ((!digitalRead(BUTTON_START)) && !gameState){
    // меняем ситатус игры на активный
    gameState = true; 
  }
  // если в данные момент идёт игра
  if (gameState) {
    // выводим оставшиеся время на дисплей
    qd.displayInt(gametime);
    // каждую секунду вычитаем с текущего времени 1 секунду
    if ((millis() - lastMillis) > 1000) {
      // запомнинаем текущее время
      lastMillis = millis();
      gametime--;
    }
    // если время вышло
    if (gametime < 0){
      // переводим статус игры в неактивный
      gameState = false; 
      gametime = 60;
    }
    motionMotor1(analogRead(POT1_PIN));
    motionMotor2(analogRead(POT2_PIN));
    motionMotor3(analogRead(POT3_PIN));
  } else {
    // меняем состояние игры на неактивный
    gameState = false;
    // выводим на дисплей надпись «PLAY»
    qd.displayDigits(QD_P, QD_L, QD_A, QD_Y);
    motionMotor1(1);
    motionMotor2(1);
    motionMotor3(1);
  }
}
 
// функция движение «моротра 1»
void motionMotor1(int valuePot1) {
  // создаём переменную скорости мотора 
  int speedMotor1;
  // если потенциометр в положении от крайне левого до середины
  if (valuePot1 >= 0 && valuePot1 <= 490) {
    // если концевик не был нажат
    if (digitalRead(DOWN1) && !firstPressButton1) {
      // вычисляем скорость мотора в зависимости от потенциометра
      speedMotor1 = map(valuePot1, 0, 490, 255, miniSpeed);
      // мотор в режиме заматывания
      digitalWrite(H_PIN1, LOW);
    } else {
      firstPressButton1 = true;
      speedMotor1 = 0; 
    }
  } else if(valuePot1 >= 533 && valuePot1 <= 1023) {
    // если потенциометр в положении от середины до крайне правого
    firstPressButton1 = 0;
    if (!digitalRead(TOP1)) {
      speedMotor1 = map(valuePot1, 533, 1023, miniSpeed, 255);
      // мотор в режиме разматывания
      digitalWrite (H_PIN1, HIGH);
    } else {
      speedMotor1 = 0;
    }
  } else {
    speedMotor1 = 0;
  }
  analogWrite(EN_PIN1, speedMotor1); 
}
 
// функция движение «моротра 2»
void motionMotor2(int valuePot2) {
  // создаём переменную скорости мотора 
  int speedMotor2;
  // если потенциометр в положении от крайне левого до середины
  if (valuePot2 >= 0 && valuePot2 <= 490) {
    // если концевик не был нажат
    if (digitalRead(DOWN2) && !firstPressButton2) {
      // вычисляем скорость мотора в зависимости от потенциометра
      speedMotor2 = map(valuePot2, 0, 490, 255, miniSpeed);
      // мотор в режиме заматывания
      digitalWrite(H_PIN2, LOW);
    } else {
      firstPressButton2 = true;
      speedMotor2 = 0; 
    }
  } else if(valuePot2 >= 533 && valuePot2 <= 1023) {
    // если потенциометр в положении от середины до крайне правого
    firstPressButton2 = 0;
    if (!digitalRead(TOP2)) {
      speedMotor2 = map(valuePot2, 533, 1023, miniSpeed, 255);
      // мотор в режиме разматывания
      digitalWrite (H_PIN2, HIGH);
    } else {
      speedMotor2 = 0;
    }
  } else {
    speedMotor2 = 0;
  }
  analogWrite(EN_PIN2, speedMotor2); 
}
 
// функция движение «моротра 3»
void motionMotor3(int valuePot3) {
  // создаём переменную скорости мотора 
  int speedMotor3;
  // если потенциометр в положении от крайне левого до середины
  if (valuePot3 >= 0 && valuePot3 <= 490) {
    // если концевик не был нажат
    if (digitalRead(DOWN3) && !firstPressButton3) {
      // вычисляем скорость мотора в зависимости от потенциометра
      speedMotor3 = map(valuePot3, 0, 490, 255, miniSpeed);
      // мотор в режиме заматывания
      digitalWrite(H_PIN3, LOW);
    } else {
      firstPressButton3 = true;
      speedMotor3 = 0; 
    }
  } else if(valuePot3 >= 533 && valuePot3 <= 1023) {
    // если потенциометр в положении от середины до крайне правого
    firstPressButton3 = 0;
    if (!digitalRead(TOP3)) {
      speedMotor3 = map(valuePot3, 533, 1023, miniSpeed, 255);
      // мотор в режиме разматывания
      digitalWrite (H_PIN3, HIGH);
    } else {
      speedMotor3 = 0;
    }
  } else {
    speedMotor3 = 0;
  }
  analogWrite(EN_PIN3, speedMotor3); 
}

Что дальше

Добавьте WiFi модуль, чтобы вести статистику работы автомата on-line.

Возьмите Arduino Yún, добавьте web-камеру и устройте видеотрансляцию азартных игроков.