Проект 12. Часы Фишера (шахматные часы)

Устройство для любителей играть в шахматы по настоящим правилам. Суть таких шахмат заключается в правильном отсчете времени, предложенное американским шахматистом Робертом Фишером, а именно каждый матч делился на три этапа. В третьем этапе, в оставшиеся 10 минут за каждый ход игрок получал дополнительные 20 секунд. Поэтому для нашего устройство мы взяли правила третьего этапа и игра с самого начала за каждый ход игрока будет добавлять ему по 20 секунд.

Описание работы:

Для начала работы подключите питание к Arduino. На индикаторе загорится количество времени каждого игрока. По умолчанию: 30 минут. Для начала игры необходимо нажать одну из кнопок: красную либо синюю. Загорится так кнопка которая была нажата. Игрок нажавший ее ходит первым. На индикаторе высветятся два числа, разделенные точкой по центру. Левая часть индикатора показывает количество времени игрока нажимающий красную кнопку, правая часть индикатора показывает количество времени игрока нажимающий синюю кнопку. После нажатия кнопки время относящееся к тому или иному игроку будет уменьшаться, а время соперника будет неподвижно пока к нему не перейдет ход. Если уменьшаются минуты, то на индикаторе меняются минуты, если уменьшаются секунды, то индикатор будет показывать изменения каждую секунду. После сделанного хода игрок обязан нажать еще раз свою кнопку, чтобы его время остановилось, зажглась кнопка соперника, и пошел отсчет времени соперника. По истечению времени у одного из игроков начнет мигать светодиод на кнопке и пищать зуммер, оповещая о проигрыше данного игрока.

Нам понадобится:

Для реализации проекта нам необходимо установить следующие библиотеки:

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Схема сборки:

  • Полученные результат представлен ниже на рисунке.

Код программы:

#include <iarduino_4LED.h>                                       // Подключаем библиотеку iarduino_4LED.
iarduino_4LED dispLED(1, A0);                                    // Объявляем объект для работы с функциями библиотеки iarduino_4LED, с указанием выводов дисплея ( CLK , DIO ).
                                                                 //
#include <iarduino_RTC.h>                                        // Подключаем библиотеку.
iarduino_RTC time(RTC_DS3231);                                   // Объявляем объект time для модуля на базе чипа DS3231.
                                                                 //
const uint8_t pinKeyRed = A3;                                    // Объявляем пин для работы с красной кнопкой.
const uint8_t pinKeyBlue = 11;                                   // Объявляем пин для работы с зеленой кнопкой.
const uint8_t pinKeyLedRed = 5;                                  // Объявляем пин для работы со светодиодом красной кнопки.
const uint8_t pinKeyLedBlue = 13;                                // Объявляем пин для работы со светодиодом зеленой кнопки.
const uint8_t pinBuzzer = 2;                                     // Определяем № вывода к которому подключён зуммер со встроенным генератором.
                                                                 //
const uint8_t fisherTime = 20;                                   // Константа определяющая количество времени, которое прибавляется после каждого нажатия.
const uint8_t startGameTime = 30;                                // Константа определяющая общее количество времени в начале игры.
                                                                 //
int menu;                                                        // Переменная события.
uint8_t timeLeft;                                                // Переменная секунд для левого игрока.
uint8_t timeRight;                                               // Переменная секунд для правого игрока.
uint8_t minLeft;                                                 // Переменная минут для левого игрока.
uint8_t minRight;                                                // Переменная минут для правого игрока.
String timesMin;                                                 // переменная вывода времени в строковом формате.
String minLeftChar;                                              // Переменная времени для левого игрока в строковом формате.
String minRightChar;                                             // Переменная времени для правого игрока в строковом формате.
bool eventLeft = true;                                           // Переменная события разрешает/запрещает уменьшение минут для левого игрока.
bool eventRight = true;                                          // Переменная события разрешает/запрещает уменьшение минут для правого игрока.
bool eventLeftZero = false;                                      // Переменная события разрешает/запрещает показ секунд для левого игрока.
bool eventRightZero = false;                                     // Переменная события разрешает/запрещает показ секунд для правого игрока.
                                                                 //
bool counter = false;                                            // Переменная события разрешает/запрещает вывод на индикатор.
long prevmicros;                                                 // Переменная для хранения предыдущего значения таймера.
                                                                 //
void StartGame();                                                // Функция начала игры.
void ShowTime();                                                 // Функция показа времени.
void ConversionTimeLeft();                                       // Функция добавления времени Фишера для левого игрока.
void ConversionTimeRight();                                      // Функция добавления времени Фишера для правого игрока.
                                                                 //
void setup()                                                     //
{                                                                //
  pinMode(pinKeyBlue, INPUT);                                    // Переводим вывод pinKeyGreen в режим входа.
  pinMode(pinKeyRed, INPUT);                                     // Переводим вывод pinKeyRed в режим входа.
  pinMode(pinBuzzer,  OUTPUT  );                                 // Переводим вывод pinBuzzer в режим выхода.
  digitalWrite(pinBuzzer, LOW );                                 // Устанавливаем уровень логического «0» на выводе pinBuzzer.
  dispLED.begin();                                               // Инициируем LED дисплей.
  dispLED.light(7);                                              // Устанавливаем максимальную яркость свечения LED индикатора.
  time.begin();                                                  // Инициируем RTC модуль.
  menu = 1;                                                      // Задаем событие 1.
}                                                                //
                                                                 //
void loop()                                                      //
{                                                                //
time.gettime();                                                  // Читаем время, обновляя значения всех переменных.
  switch(menu)                                                   // Выбор события.
  {                                                              //
    case 1:                                                      // Событие 1.
    dispLED.print( startGameTime, 0, POS3 );                     // Выводим на индикатор заданное время матча. 
    if(digitalRead(pinKeyBlue) && digitalRead(!pinKeyRed))       // Проверяем нажатие синей кнопки и запрещаем нажатие красной кнопки.
    {                                                            //
      StartGame();                                               // Функция начала игры.
      menu = 3;                                                  // Переходим к событию 3.
    }                                                            //
    if(digitalRead(pinKeyRed) && digitalRead(!pinKeyBlue))       // Проверяем нажатие красной кнопки и запрещаем нажатие синей кнопки.
    {                                                            //
      StartGame();                                               // Функция начала игры.
      menu = 2;                                                  // Переходим к событию 2.
    }                                                            //
    break;                                                       // Выходим из оператора case.
                                                                 //
    case 2:                                                      // Событие 2.
    digitalWrite(pinBuzzer, LOW);                                // Выключаем зуммер.
    digitalWrite(pinKeyLedBlue, LOW);                            // Выключаем светодиод на синей кнопке, у соперника.
    digitalWrite(pinKeyLedRed, HIGH);                            // Включаем светодиод на красной кнопке.
    timeLeft = 59 - time.seconds;                                // Уменьшаем время у левого игрока на секунду.
                                                                 //
    if (timeLeft == 58)                  {eventLeft = true;}     // Разрешаем уменьшение минут у левого игрока на 58 секунде.
    if (timeLeft == 59 && eventLeft)                             // Уменьшаем минуты у левого игрока на 59 секунде, если разрешено уменьшение минут.
    {                                                            //
      minLeft--;                                                 // Уменьшаем минуты у левого игрока.
      eventLeft = false;                                         // Запрещаем уменьшение минут у левого игрока.
    }                                                            //
    if (minLeft <= 0)                    {eventLeftZero = true;} // Если минут меньше или равно нулю, то разрешаем вывод на дисплей секунд у левого игрока.
    if (eventLeftZero)                   {minLeft = timeLeft;}   // Если разрешен вывод на дисплей секунд, сохраняем значение секунд в переменную минут для левого игрока.
                                                                 //
    if (timeLeft == 0 && minLeft == 0)   {menu = 4;}             // Если минуты и секунды равны нулю, то переходим к событию 4. Левый игрок проиграл.
    if (digitalRead(pinKeyRed) && time.seconds >= 1)             // Проверка нажатия красной кнопки, но не раньше, чем через одну секунду с момента начала хода левого игрока.
    {                                                            //
      eventLeft = true;                                          // Разрешаем уменьшение минут у левого игрока.
      digitalWrite(pinBuzzer, HIGH);                             // Включаем зуммер.
      if (eventLeftZero) {minLeft = timeLeft;}                   // Если разрешен вывод секунд на индикатор, то сохраняем значение секунд в переменную минут для левого игрока.
      timeLeft = time.seconds;                                   // Сохраняем количество секунд, прошедших за ход у левого игрока.
      time.settime(timeRight);                                   // Устанавливаем время на котором остановился ход соперника. Правый игрок.
      ConversionTimeLeft();                                      // Функция добавления времени Фишера для левого игрока.
      menu = 3;                                                  // Переходим к событию 3. Ход соперника, правый игрок.
    }                                                            //
    ShowTime();                                                  // Функция показа времени.
    break;                                                       // Выходим из оператора case.
                                                                 //
    case 3:                                                      // Событие 3.
    digitalWrite(pinBuzzer, LOW);                                // Выключаем зуммер.
    digitalWrite(pinKeyLedRed, LOW);                             // Выключаем светодиод на красной кнопке, у соперника.
    digitalWrite(pinKeyLedBlue, HIGH);                           // Включаем светодиод на синей кнопки.
    timeRight = 59 - time.seconds;                               // Уменьшаем время у правого игрока на секунду.
                                                                 //
    if (timeRight == 58)                 {eventRight = true;}    // Разрешаем уменьшение минут у правого игрока на 58 секунде.
    if (timeRight == 59 && eventRight)                           // Уменьшаем минуты у правого игрока на 59 секунде, если разрешено уменьшение минут.
    {                                                            //
      minRight--;                                                // Уменьшаем минуты у правого игрока.
      eventRight = false;                                        // Запрещаем уменьшение минут у правого игрока.
    }                                                            //
    if (minRight <= 0)                   {eventRightZero = true;}// Если минут меньше или равно нулю, то разрешаем вывод на дисплей секунд у правого игрока.
    if (eventRightZero)                  {minRight = timeRight;} // Если разрешен вывод на дисплей секунд, сохраняем значение секунд в переменную минут для правого игрока.
                                                                 //
    if (timeRight == 0 && minRight == 0) {menu = 5;}             // Если минуты и секунды равны нулю, то переходим к событию 4. Правый игрок проиграл.
    if (digitalRead(pinKeyBlue) && time.seconds >= 1)            // Проверка нажатия синей кнопки, но не раньше, чем через одну секунду с момента начала хода правого игрока.
    {                                                            //
      eventRight = true;                                         // Разрешаем уменьшение минут у правого игрока.
      digitalWrite(pinBuzzer, HIGH);                             // Включаем зуммер.
      if (eventRightZero) {minRight = timeRight;}                // Если разрешен вывод секунд на индикатор, то сохраняем значение секунд в переменную минут для правого игрока.
      timeRight = time.seconds;                                  // Сохраняем количество секунд, прошедших за ход у правого игрока.
      time.settime(timeLeft);                                    // Устанавливаем время на котором остановился ход соперника. Левый игрок.
      ConversionTimeRight();                                     // Функция добавления времени Фишера для правого игрока.
      menu = 2;                                                  // Переходим к событию 2. Ход соперника, левый игрок.
    }                                                            //
    ShowTime();                                                  // Функция показа времени.
    break;                                                       // Выходим из оператора case.
                                                                 //
    case 4:                                                      // Событие 4.
    ShowTime();                                                  // Функция показа времени.
    if(digitalRead(pinKeyBlue) || digitalRead(pinKeyRed))        // Проверка нажатия синей либо красной кнопки.
    {menu = 1;}                                                  // Возвращаемся в событие 1.
    digitalWrite(pinBuzzer, HIGH);                               // Включаем зуммер.
    digitalWrite(pinKeyLedRed, HIGH);                            // Включаем светодиод на красной кнопке.
    delay(100);                                                  // Задержка 100 мс.
    digitalWrite(pinBuzzer, LOW);                                // Выключаем зуммер.
    digitalWrite(pinKeyLedRed, LOW);                             // Выключаем светодиод на красной кнопке.
    delay(100);                                                  // Задержка 100 мс. 
    break;                                                       // Выходим из оператора case.
                                                                 //
    case 5:                                                      // Событие 5.
    ShowTime();                                                  // Функция показа времени.
    if(digitalRead(pinKeyBlue) || digitalRead(pinKeyRed))        // Проверка нажатия синей либо красной кнопки.
    {menu = 1;}                                                  // Возвращаемся в событие 1.
    digitalWrite(pinBuzzer, HIGH);                               // Включаем зуммер.
    digitalWrite(pinKeyLedBlue, HIGH);                           // Включаем светодиод на синей кнопке.
    delay(100);                                                  // Задержка 100 мс.
    digitalWrite(pinBuzzer, LOW);                                // Выключаем зуммер.
    digitalWrite(pinKeyLedBlue, LOW);                            // Выключаем светодиод на синей кнопке.
    delay(100);                                                  // Задержка 100 мс.
    break;                                                       // Выходим из оператора case.
  }                                                              //
}                                                                //
                                                                 //
void StartGame()                                                 // Функция начала игры.
{                                                                //
  time.settime(0);                                               // Задаем начальное время равное 0.
  minLeft = startGameTime;                                       // Присваиваем заданное количество минут левому игроку.
  minRight = startGameTime;                                      // Присваиваем заданное количество минут правому игроку.
}                                                                //
                                                                 //
void ShowTime()                                                  // Функция показа времени.
{                                                                //
  if (minLeft>=0 && minLeft<10)                                  // Если количество времени у левого игрока от 0 до 9.
  {minLeftChar = (String) "0" + minLeft;}                        // Добавляем спереди ноль и переводим в строковое значение.
  else{minLeftChar = (String) minLeft;}                          // Иначе просто переводим количество времени в строковое значение.
                                                                 //
  if (minRight>=0 && minRight<10)                                // Если количество времени у правого игрока от 0 до 9.
  {minRightChar = (String) "0" + minRight;}                      // Добавляем спереди ноль и переводим в строковое значение.
  else{minRightChar = (String) minRight;}                        // Иначе просто переводим количество времени в строковое значение.
                                                                 //
  timesMin = minLeftChar + minRightChar;                         // Присваиваем общей переменной количество времени левого и правого игрока.
                                                                 //
  if (micros() - prevmicros > 500000)                            // Каждые пол секунды.
  {                                                              //
    prevmicros = micros();                                       // Сохраняем предыдущее значение таймера.
    counter=!counter;                                            // Изменяем переменную события вывода значений на индикатор.
    if (!counter)                                                // Если переменная события false.
    {                                                            //
      dispLED.print(timesMin);                                   // Выводим значение на индикатор.
    }                                                            //
    else                                                         // Иначе.
    {                                                            //
      dispLED.point(2, true);                                    // Включаем вторую точку.
    }                                                            //
  }                                                              //
}                                                                //
                                                                 //
void ConversionTimeLeft()                                        // Функция добавления времени Фишера для левого игрока.
{                                                                //
  timeLeft = 59 - timeLeft;                                      // Определяем количество времени оставшееся левому игроку.
  timeLeft = timeLeft + fisherTime;                              // Добавляем время Фишера левому игроку.
  if (timeLeft > 59)                                             // Если суммарное время больше 59 у левого игрока.
  {                                                              //
    timeLeft = timeLeft - 59;                                    // Вычитаем из суммарного времени 59 левого игрока.
    if (eventLeftZero) {minLeft = 0; eventLeftZero = false;}     // Если разрешен вывод секунд на индикатор для левого игрока, то очищаем переменную минут, запрещаем вывод секунд на индикатор.
    minLeft++;                                                   // Увеличиваем количество минут у левого игрока.
  }                                                              //
  if (eventLeftZero){minLeft = timeLeft;}                        // Если разрешен вывод секунд на индикатор для левого игрока, то сохраняем количество оставшегося времени левого игрока в переменную минут.
  timeLeft = 59 - timeLeft;                                      // Определяем обратное количество времени, чтобы узнать оставшееся время для левого игрока.
}                                                                //
                                                                 //
void ConversionTimeRight()                                       // Функция добавления времени Фишера для правого игрока.
{                                                                //
  timeRight = 59 - timeRight;                                    // Определяем количество времени оставшееся правому игроку.
  timeRight = timeRight + fisherTime;                            // Добавляем время Фишера правому игроку.
  if (timeRight > 59)                                            // Если суммарное время больше 59 у правого игрока.
  {                                                              //
    timeRight = timeRight - 59;                                  // Вычитаем из суммарного времени 59 правого игрока.
    if (eventRightZero) {minRight = 0; eventRightZero = false;}  // Если разрешен вывод секунд на индикатор для правого игрока, то очищаем переменную минут, запрещаем вывод секунд на индикатор.
    minRight++;                                                  // Увеличиваем количество минут у правого игрока.
  }                                                              //
  if (eventRightZero){minRight = timeRight;}                     // Если разрешен вывод секунд на индикатор для правого игрока, то сохраняем количество оставшегося времени правого игрока в переменную минут.
  timeRight = 59 - timeRight;                                    // Определяем обратное количество времени, чтобы узнать оставшееся время для правого игрока.
}                                                                //

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

В начале скетча (до кода setup) выполняются следующие действия:

В коде setup выполняются следующие действия:

  • Переводим выводы pinKeyBlue для синей кнопки, pinKeyRed для красной кнопки в режим выхода. Переводим вывод pinBuzzer в режим входа и выключаем зуммер.
  • Инициируем LED дисплей.
  • Устанавливаем максимальную яркость свечения индикатора.
  • Инициируем RTC модуль.
  • Задаем событие 1.

В коде loop выполняются следующие действия:

  • Читаем время, с модуля часов реального времени.
  • Событие 1.
  • Выводим на экран количество заданных минут.
  • Ожидаем нажатие одной из кнопок. При нажатии переходим к функции "StartGame()" в которой обнуляем начальное время отсчета, сохраняем заданное время матча для левого игра и для правого игрока. При нажатии красной кнопки разрешаем событие 2. При нажатии синей кнопки разрешаем событие 3.
  • Событие 2 и 3. Одинаковые события для разных кнопок.
  • Выключаем зуммер. Гасим светодиод на кнопке соперника. Включаем светодиод на своей кнопке.
  • Вычитаем реальное время из минуты и присваиваем переменной времени.
  • Если 58 секунд разрешаем уменьшать минуты. Если 59 секунд, уменьшаем минуты на один, запрещаем уменьшать минуты.
  • Если количество минут меньше или равно нулю, разрешаем вывод на экран оставшихся секунд.
  • Если разрешен вывод количества оставшихся секунд, сохраняем секунды в переменную минут, для дальнейшего их вывода.
  • Если и минуты и секунды равны нулю, то переходим к событию 4 или 5 в зависимости от кнопки. Для красной кнопки переход к событию 4, для синей переход к событию 5.
  • Проверка нажатия своей кнопки для завершения своего хода, но не раньше 1 секунды после совершению хода. Разрешаем уменьшение минут. Включаем зуммер. Если разрешен вывод количества оставшихся секунд, сохраняем секунды в переменную минут, для дальнейшей с ними работой и выводом. Сохраняем реальное время в переменную времени. Устанавливаем реальное время для соперника. В начале игры оно равно нулю. После хода оно равно количеству реального времени сохраненного за предыдущий ход. Добавляем время Фишера функцией "ConversionTimeLeft()" для игрока с красной кнопкой или "ConversionTimeRight()" для игрока с синей кнопкой.
  • Эти функции идентичны по принципу действия, но для разных кнопок. В них определяем оставшееся количество времени игроку, добавляем время Фишера игроку, проверка полученное время больше минуты, если больше, то отсекаем часть минуты и сохраняем оставшееся время: минуты сохраняем в переменную минут, секунды сохраняем в переменную секунд. Если же разрешен вывод секунд на дисплей, значит минуты равны нулю, поэтому вводим проверку минут и так же преобразуем время в минуты и секунды, если количество секунд превышают минуту. Определяем реальное время и выходим из функций "ConversionTimeLeft()" или "ConversionTimeRight()".
  • Дальше разрешаем переходим к событию в зависимости от нажатой кнопки. Если нажата красная кнопка, то переходим к событию 2, если нажата синяя кнопка, то переходим к событию 3, то есть наступает ход второго игрока и переходим к противоположному событию.
  • Выводим на индикатор время с помощью функции "ShowTime()". В ней преобразуем время целочисленного значения в строковое, добавляя ноль, где это необходимо. А так же выводим раз в пол секунды общее время и мигающую точку по центру.
  • Событие 4. Событие, когда игрок за красной кнопкой проиграл. Здесь включается и выключается зуммер и светодиод красной кнопки, оповещая о проигрыше игрока, а также ожидание нажатии любой из кнопок, чтобы вернуться в событие 1, то есть начать игру заново.
  • Событие 5. Событие, когда игрок за синей кнопкой проиграл. Здесь включается и выключается зуммер и светодиод сеней кнопки, оповещая о проигрыше игрока, а также ожидание нажатии любой из кнопок, чтобы вернуться в событие 1, то есть начать игру заново.

Особенности:

  • Время Фишера можно изменять в коде программы. По умолчанию 20 секунд. Для этого необходимо открыть скетч в начале программы и там изменить константу "fisherTime" с 20 на любое удобное для вас число.
  • Так же можно изменить общее время матча. По умолчанию 30 минут. Для этого измените константу "startGameTime" на любое время, так же в начале скетча.

Ссылки:

Обсуждение