Содержание

Часы Фишера для быстрых шахмат

Проекты на Arduino и Slot Shield

Обычные шахматные часы — два таймера отсчёта, работающие поочерёдно. Простой механизм привёл к появлению нового вида спорта — быстрых шахмат.

Но всем, кто играл в быстрые шахматы знакома ситуация: потерянные секунды в дебюте или миттельшпиле приводят к тому, что доигрывать партию приходится «под висящим флажком». До красивой победы порой не хватает считанных мгновений. В 1989 году Роберт Фишер придумал, как уравнять шансы в цейтноте. Чемпион мира запатентовал устройство, которое поощряет игрока за сделанный ход увеличением лимита времени. Сделал ход — получил дополнительные 20 секунд на обдумывание следующего.

Алгоритм прост на бумаге, но практически не реализуем в механических часах. А с электронными часами сделать такое устройство совсем не сложно. Понадобятся две светодиодные кнопки разных цветов, четырёхразрядный индикатор (сначала он будет показывать оставшееся время в минутах, а последнюю — отсчитывать посекундно) и пьезопищалку, которая сообщит о конце игры.

Что потребуется

Полный сет компонентов проекта. В сет входят:

Видеоинструкция

Как собрать

Установите Troyka Slot Shield на Iskra Neo

Перед следующими шагами переверните Slot Shield на 180 градусов

Вставьте светодиодные кнопки в левый и средний верхние слоты.

Встаьте зуммер в оставшимся свободным слот верхнего ряда.

Переверните четырёхразрядный индикатор вверх ногами и вставьте в слоты, расположенные прямо под светодиодными кнопками.

Скетч

Прошейте контроллер скетчем через Arduino IDE.

digital-chess-clock.ino
// ноты для мелодии
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
 
// библиотека для работы с дисплеем
#include <QuadDisplay2.h>
// создаём объект класса QuadDisplay и передаём номера пинов CS, DI и ⎍
QuadDisplay qd(3, 2, 6);
 
// номера цифровых пинов кнопок
#define BUTTON_ONE    12
#define BUTTON_TWO    13
 
// номера цифровых пинов светодиодов
#define LED_ONE       A1
#define LED_TWO       A0
 
// номер пина пищалки
#define BUZZER        A3
// таймер обратного отчёта
#define TIME_START    30
 
// 
int timePlayerOne = TIME_START;
int timePlayerTwo = TIME_START;
 
// состояния игры
enum State
{
    OFF,
    PLAYER_ONE,
    PLAYER_TWO,
};
// объявлеем переменную state
State state;
 
// массив мелодии при загрузке программы
int startTune[] = {NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4,
                   NOTE_F4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_F4, NOTE_G4};
// массив длительности нот для мелодии при старте
int durationStartTune[] = {100, 200, 100, 200, 100, 400, 100, 100, 100, 100, 200, 100, 500};
 
// запоминаем текущее время
unsigned long startMillis = millis();
 
void setup()
{
  // открываем монитор Serial-порта
  Serial.begin(9600);
  // инициализация дисплея
  qd.begin();
  // кнопки в режим входа
  pinMode(BUTTON_ONE, INPUT);
  pinMode(BUTTON_TWO, INPUT);
  // светодиоды в режим выхода
  pinMode(LED_ONE, OUTPUT);
  pinMode(LED_TWO, OUTPUT);
  // сброс игры
  resetGame();
}
 
void loop()
{
  // если нажаты обе кнопки
  if (!digitalRead(BUTTON_ONE) && !digitalRead(BUTTON_TWO)) {
    // сбрасываем игру
    resetGame();
  }
  // если нажата кнопка игрока «1»
  if (!digitalRead(BUTTON_ONE) && state == PLAYER_ONE) {
    // добовляем 20 секунд к времени игрока «1» 
    timePlayerOne = timePlayerOne + 20;
    // пищим бузером
    tone(BUZZER, NOTE_A3, 100);
    // ход игрока «2»
    state = PLAYER_TWO;
    // гасим светодиод игрока «1»
    digitalWrite(LED_ONE, LOW);
    // зажигаем светодиод игрока «2»
    digitalWrite(LED_TWO, HIGH);
  } else if (!digitalRead(BUTTON_TWO) && state == PLAYER_TWO) {
    // если нажата кнопка игрока «2»
    // добовляем 20 секунд к времени игрока «2»
    timePlayerTwo = timePlayerTwo + 20;
    // пищим бузером
    tone(BUZZER, NOTE_B3, 100);\
    // ход игрока «2»
    state = PLAYER_ONE;
    // гасим светодиод игрока «1»
    digitalWrite(LED_TWO, LOW);
    // зажигаем светодиод игрока «2»
    digitalWrite(LED_ONE, HIGH);
  }
 
  // если прошла 1 секунда
  if (millis() - startMillis > 1000) {
    // проверяем какой игрок ходит
    switch (state) {
      case PLAYER_ONE:
        // уменьшаем время игрока «1» на 1 секунду
        timePlayerOne--;
        break;
      case PLAYER_TWO:
        // уменьшаем время игрока «2» на 1 секунду
        timePlayerTwo--;
        break;
    }
    // выводим время обоих игроков в Serial-порт
    Serial.print(timePlayerOne);
    Serial.print("\t\t");
    Serial.println(timePlayerTwo);
    // запоминаем текущее время
    startMillis = millis();
  }
  // вывод оставшигося времени обоих игроков на дисплей
  // два первых сегмента время игрока «1», два следующих — игрока «2»
  qd.displayScore(timePlayerOne < 60 ? timePlayerOne : timePlayerOne / 60,
                  timePlayerTwo < 60 ? timePlayerTwo : timePlayerTwo / 60);
  // если вышло время хотя бы у одного из игроков
  if (timePlayerOne == 0 || timePlayerTwo == 0) {
    // завершаем игру
    endGame();
  }
}
 
// функция сброса игры
void resetGame() {
  // гасим светодиоды обоих игроков
  digitalWrite(LED_ONE, LOW);
  digitalWrite(LED_TWO, LOW);
  // выводим на дисплей надпись «PLAY»
  qd.displayDigits(QD_P, QD_L, QD_A, QD_Y);
  // играем привествующую мелодию
  melodyStart();
  // выставляем время обратного отчёта
  timePlayerOne = TIME_START;
  timePlayerTwo = TIME_START;
  // состояние игры в режим «OFF»
  state = OFF;
  // бесконечный цикл
  while (1) {
    // если нажата кнопка игрока «1»
    if (!digitalRead(BUTTON_ONE)) {
      // пищим бузером
      tone(BUZZER, NOTE_A3, 100);
      // ход игрока «1»
      state = PLAYER_ONE;
      // зажигаем светодиод игрока «1»
      digitalWrite(LED_ONE, HIGH);
      // гасим светодиод игрока «2»
      digitalWrite(LED_TWO, LOW);
      // выходим из бесконечного цикла
      break;
    } else if (!digitalRead(BUTTON_TWO)) {
      // если нажата кнопка игрока «2»
      // пищим бузером
      tone(BUZZER, NOTE_B3, 100);
      // ход игрока «2»
      state = PLAYER_TWO;
      // зажигаем светодиод игрока «2»
      digitalWrite(LED_TWO, HIGH);
      // гасим светодиод игрока «1»
      digitalWrite(LED_ONE, LOW);
      // выходим из бесконечного цикла
      break;
    }
  }
  // вывод оставшигося времени обоих игроков на дисплей
  // два первых сегмента время игрока «1», два следующих — игрока «2»
  qd.displayScore(timePlayerOne < 60 ? timePlayerOne : timePlayerOne / 60,
                  timePlayerTwo < 60 ? timePlayerTwo : timePlayerTwo / 60);
  delay(1000);
}
 
// функция окончания игры
void endGame() {
  // играем мелодию завершения игры
  melodyEnd();
  // гасим все свтодиоды
  digitalWrite(LED_ONE, LOW);
  digitalWrite(LED_TWO, LOW);
  // пока не нажата ни одна кнопка любого игрока
  while (digitalRead(BUTTON_ONE) && digitalRead(BUTTON_TWO)) {
  // крутимся в бесконечном цикле
  }
  // сбрасываем игру
  resetGame();
}
 
// функция проигрывания мелодии о начале игры 
void melodyStart() {
  for (int thisNote = 0; thisNote < 13; thisNote++) {
    // играем следующую ноту
    tone(BUZZER, startTune[thisNote], durationStartTune[thisNote]);
    delay(durationStartTune[thisNote]);
    // задержка между нотами
    delay(25);
  }
}
 
// функция проигрывания мелодии о завершении игры
void melodyEnd() {
  for (int y = 0; y <= 3; y++) {
    // издаём звуковой сигнал
    tone(BUZZER, NOTE_G3, 300);
    delay(200);
    // издаём звуковой сигнал
    tone(BUZZER, NOTE_C3, 300);
    delay(200);
  }
}

Обратите внимание Slot Shield v2 имеет другое расположение пинов. Если вы используете Slot Shield v2, то воспользуйтесь кодом ниже.

digital-chess-clock-v2.ino
// ноты для мелодии
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
 
// библиотека для работы с дисплеем
#include <QuadDisplay2.h>
// создаём объект класса QuadDisplay и передаём номера пинов CS, DI и ⎍
QuadDisplay qd(6, A4, 3);
 
// номера цифровых пинов кнопок
#define BUTTON_ONE    12
#define BUTTON_TWO    13
 
// номера цифровых пинов светодиодов
#define LED_ONE       A1
#define LED_TWO       A0
 
// номер пина пищалки
#define BUZZER        A3
// таймер обратного отчёта
#define TIME_START    30
 
// 
int timePlayerOne = TIME_START;
int timePlayerTwo = TIME_START;
 
// состояния игры
enum State
{
    OFF,
    PLAYER_ONE,
    PLAYER_TWO,
};
// объявлеем переменную state
State state;
 
// массив мелодии при загрузке программы
int startTune[] = {NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4,
                   NOTE_F4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_F4, NOTE_G4};
// массив длительности нот для мелодии при старте
int durationStartTune[] = {100, 200, 100, 200, 100, 400, 100, 100, 100, 100, 200, 100, 500};
 
// запоминаем текущее время
unsigned long startMillis = millis();
 
void setup()
{
  // открываем монитор Serial-порта
  Serial.begin(9600);
  // инициализация дисплея
  qd.begin();
  // кнопки в режим входа
  pinMode(BUTTON_ONE, INPUT);
  pinMode(BUTTON_TWO, INPUT);
  // светодиоды в режим выхода
  pinMode(LED_ONE, OUTPUT);
  pinMode(LED_TWO, OUTPUT);
  // сброс игры
  resetGame();
}
 
void loop()
{
  // если нажаты обе кнопки
  if (!digitalRead(BUTTON_ONE) && !digitalRead(BUTTON_TWO)) {
    // сбрасываем игру
    resetGame();
  }
  // если нажата кнопка игрока «1»
  if (!digitalRead(BUTTON_ONE) && state == PLAYER_ONE) {
    // добовляем 20 секунд к времени игрока «1» 
    timePlayerOne = timePlayerOne + 20;
    // пищим бузером
    tone(BUZZER, NOTE_A3, 100);
    // ход игрока «2»
    state = PLAYER_TWO;
    // гасим светодиод игрока «1»
    digitalWrite(LED_ONE, LOW);
    // зажигаем светодиод игрока «2»
    digitalWrite(LED_TWO, HIGH);
  } else if (!digitalRead(BUTTON_TWO) && state == PLAYER_TWO) {
    // если нажата кнопка игрока «2»
    // добовляем 20 секунд к времени игрока «2»
    timePlayerTwo = timePlayerTwo + 20;
    // пищим бузером
    tone(BUZZER, NOTE_B3, 100);\
    // ход игрока «2»
    state = PLAYER_ONE;
    // гасим светодиод игрока «1»
    digitalWrite(LED_TWO, LOW);
    // зажигаем светодиод игрока «2»
    digitalWrite(LED_ONE, HIGH);
  }
 
  // если прошла 1 секунда
  if (millis() - startMillis > 1000) {
    // проверяем какой игрок ходит
    switch (state) {
      case PLAYER_ONE:
        // уменьшаем время игрока «1» на 1 секунду
        timePlayerOne--;
        break;
      case PLAYER_TWO:
        // уменьшаем время игрока «2» на 1 секунду
        timePlayerTwo--;
        break;
    }
    // выводим время обоих игроков в Serial-порт
    Serial.print(timePlayerOne);
    Serial.print("\t\t");
    Serial.println(timePlayerTwo);
    // запоминаем текущее время
    startMillis = millis();
  }
  // вывод оставшигося времени обоих игроков на дисплей
  // два первых сегмента время игрока «1», два следующих — игрока «2»
  qd.displayScore(timePlayerOne < 60 ? timePlayerOne : timePlayerOne / 60,
                  timePlayerTwo < 60 ? timePlayerTwo : timePlayerTwo / 60);
  // если вышло время хотя бы у одного из игроков
  if (timePlayerOne == 0 || timePlayerTwo == 0) {
    // завершаем игру
    endGame();
  }
}
 
// функция сброса игры
void resetGame() {
  // гасим светодиоды обоих игроков
  digitalWrite(LED_ONE, LOW);
  digitalWrite(LED_TWO, LOW);
  // выводим на дисплей надпись «PLAY»
  qd.displayDigits(QD_P, QD_L, QD_A, QD_Y);
  // играем привествующую мелодию
  melodyStart();
  // выставляем время обратного отчёта
  timePlayerOne = TIME_START;
  timePlayerTwo = TIME_START;
  // состояние игры в режим «OFF»
  state = OFF;
  // бесконечный цикл
  while (1) {
    // если нажата кнопка игрока «1»
    if (!digitalRead(BUTTON_ONE)) {
      // пищим бузером
      tone(BUZZER, NOTE_A3, 100);
      // ход игрока «1»
      state = PLAYER_ONE;
      // зажигаем светодиод игрока «1»
      digitalWrite(LED_ONE, HIGH);
      // гасим светодиод игрока «2»
      digitalWrite(LED_TWO, LOW);
      // выходим из бесконечного цикла
      break;
    } else if (!digitalRead(BUTTON_TWO)) {
      // если нажата кнопка игрока «2»
      // пищим бузером
      tone(BUZZER, NOTE_B3, 100);
      // ход игрока «2»
      state = PLAYER_TWO;
      // зажигаем светодиод игрока «2»
      digitalWrite(LED_TWO, HIGH);
      // гасим светодиод игрока «1»
      digitalWrite(LED_ONE, LOW);
      // выходим из бесконечного цикла
      break;
    }
  }
  // вывод оставшигося времени обоих игроков на дисплей
  // два первых сегмента время игрока «1», два следующих — игрока «2»
  qd.displayScore(timePlayerOne < 60 ? timePlayerOne : timePlayerOne / 60,
                  timePlayerTwo < 60 ? timePlayerTwo : timePlayerTwo / 60);
  delay(1000);
}
 
// функция окончания игры
void endGame() {
  // играем мелодию завершения игры
  melodyEnd();
  // гасим все свтодиоды
  digitalWrite(LED_ONE, LOW);
  digitalWrite(LED_TWO, LOW);
  // пока не нажата ни одна кнопка любого игрока
  while (digitalRead(BUTTON_ONE) && digitalRead(BUTTON_TWO)) {
  // крутимся в бесконечном цикле
  }
  // сбрасываем игру
  resetGame();
}
 
// функция проигрывания мелодии о начале игры 
void melodyStart() {
  for (int thisNote = 0; thisNote < 13; thisNote++) {
    // играем следующую ноту
    tone(BUZZER, startTune[thisNote], durationStartTune[thisNote]);
    delay(durationStartTune[thisNote]);
    // задержка между нотами
    delay(25);
  }
}
 
// функция проигрывания мелодии о завершении игры
void melodyEnd() {
  for (int y = 0; y <= 3; y++) {
    // издаём звуковой сигнал
    tone(BUZZER, NOTE_G3, 300);
    delay(200);
    // издаём звуковой сигнал
    tone(BUZZER, NOTE_C3, 300);
    delay(200);
  }
}

Что дальше?

Хотите собрать другой девайс? Выберите своё будущее устройство из списка проектов на Slot Shield.

Часто задаваемые вопросы

Где скачать необходимые библиотеки и как их установить?
У моего модуля QuadDisplay всего три ноги и расположены они слева. Можно ли использовать его в этом проекте?

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

Мы уже тестируем версию скетча для старого дисплея и разместим её на этой странице в ближайшие дни.