Шахматные часы самодельные2

Материал из WikiPrometheus.ru
(перенаправлено с «Трёхрежимные шахматные часы»)
Перейти к навигацииПерейти к поиску

Общие сведения

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

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

Краткая историческая справка

В XIX веке шахматные партии могли длиться бесконечно, и первые попытки контроля времени были кустарными — использовали песочные или карманные часы, которые судья запускал вручную. В 1883 году появились механические часы с двумя циферблатами и перекидным рычагом, а в 1950-х к ним добавили «падающий флажок». Однако такие часы были сложны в производстве и практически не подлежали ремонту — сломавшееся устройство проще выбрасывали. В конце 1980-х фирма DGT выпустила первые цифровые часы с высокой точностью и поддержкой инкремента, но их цена (от 12 000 рублей) сделала их малодоступными для массового пользователя, а дешёвые китайские аналоги (2 000–3 000 рублей) оказались неремонтопригодными.

Наш проект продолжает эту историю, но делает шаг в сторону доступности и открытости. Мы используем современную элементную базу — микроконтроллер, LED-дисплей, энкодер и две кнопки — и собираем часы, которые по функционалу приближаются к профессиональным, а по цене и ремонтопригодности — к самодельным. В отличие от заводских аналогов, наше устройство можно разобрать, заменить любую деталь и починить своими руками.

Основные требования к современным электронным шахматным часам

Точность хода — без неё невозможно объективно определить победителя при истечении времени. Эргономика — кнопка должна давать чёткий тактильный отклик, чтобы игрок чувствовал переключение хода даже не глядя на часы. Видимость — экран должен быть читаем под любым углом и не бликовать, чтобы игрок мгновенно считывал время боковым зрением. Интуитивная настройка — интерфейс должен быть понятен без инструкции, чтобы судья мог быстро восстановить время при сбое. Устойчивость на столе — часы должны иметь достаточный вес и нескользящее основание, чтобы не сдвигаться при ударах. Ремонтопригодность — конструкция должна позволять замену любой детали (кнопки, дисплея, платы) без специального инструмента. Надёжность — программа должна корректно обрабатывать дребезг контактов и исключать зависания от случайных нажатий. Автономность — часы должны долго работать от доступных источников (AA/AAA или USB), чтобы не отвлекать игроков частой заменой батарей. Компактность — часы должны быть удобны для перевозки в сумке с шахматным набором и не иметь хрупких выступающих элементов. Доступная цена — стоимость не должна превышать 3000 рублей, чтобы часы были доступны массовому пользователю. Минимализм в управлении — управление должно быть сведено к минимуму (старт, стоп, сброс), чтобы часы мог использовать любой человек без инструкции.

Технические характеристики

Микроконтроллер Arduino Nano Дисплеи 2× TM1637 (4 разряда) Управление Энкодер Кнопки игроков 2 кнопки Звук Пьезоизлучатель (зуммер) Питание от встроенного аккумулятора Потребление от 50 мА до 250 мА (зависит от яркости) Время непрерывной работы: около 18 часов

Примечания

· При быстром вращении энкодера скорость изменения значений автоматически увеличивается · Время сохраняется только во время работы устройства, при отключении питания сбрасывается · Максимальное время партии: 999 минут (~16.5 часов)

Правила игры в шахматы на время

Функциональная схема

Электрическая схема

Топология печатной платы

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

/*

 Шахматные часы на Arduino Nano
 Упрощенные режимы: Классика, С добавкой, С задержкой
 Исправлены проблемы с паузой и форматом времени
 Добавлена пасхалка "К Элизе" (исправленная версия)
  • /
  1. include <TM1637Display.h>
  2. include <GyverEncoder.h>

// ==================== ПИНЫ ====================

  1. define ENC_S1 11
  2. define ENC_S2 10
  3. define ENC_KEY 9
  4. define BTN_P1 7
  5. define BTN_P2 8
  6. define TM1637_CLK1 6
  7. define TM1637_DIO1 5
  8. define TM1637_CLK2 4
  9. define TM1637_DIO2 3
  10. define BUZZER 2

// ==================== НАСТРОЙКИ ====================

  1. define DEBOUNCE_DELAY 50
  2. define LONG_PRESS_TIME 2000
  3. define ENC_DELAY_NORMAL 150
  4. define ENC_DELAY_FAST 50
  5. define ENC_FAST_THRESHOLD 3

// ==================== ЗВУКОВЫЕ ЧАСТОТЫ ====================

  1. define BEEP_ENC 4000
  2. define BEEP_BUTTON 3000
  3. define BEEP_START 2500
  4. define BEEP_CLICK 2800
  5. define BEEP_RESET 2000
  6. define BEEP_GAME_OVER 1500
  7. define BEEP_LONG_PRESS 1800
  8. define BEEP_PAUSE 2200
  9. define BEEP_UNPAUSE 2400

// ==================== ПРОТОТИПЫ ==================== void beep(int duration, int frequency); void showTime(); void showMenu(); void showSetTime(); void showSetAdd(); void showSetDelay(); void showSetMode(); void showSetBrightness(); void showSetVolume(); void handleGame(); void startGame(int firstPlayer); void gameOver(int loser); void resetAll(); void playFurElise(); void checkVolumeEasterEgg();

// ==================== ПЕРЕМЕННЫЕ ==================== TM1637Display display1(TM1637_CLK1, TM1637_DIO1); TM1637Display display2(TM1637_CLK2, TM1637_DIO2); Encoder encoder(ENC_S1, ENC_S2, ENC_KEY);

int mode = 0; int gameMode = 0;

long timeP1 = 300; long timeP2 = 300; long addTime = 3; long delaySec = 5;

bool turn = 0; bool playing = false; bool paused = false; unsigned long lastTickTime = 0; unsigned long pauseStartTime = 0; bool gameStarted = false;

int movesP1 = 0; int movesP2 = 0; bool showStats = false; unsigned long statsShowTime = 0;

int setMinutes = 5; int setAdd = 3; int setDelay = 5; int setMode = 0; int setBrightness = 7; int setVolume = 100;

// Переменные для пасхалки int easterEggStep = 0; unsigned long lastEasterEggTime = 0;

bool lastP1 = HIGH, lastP2 = HIGH; unsigned long lastP1Time = 0, lastP2Time = 0; unsigned long bothStart = 0;

int menuPos = 0; unsigned long lastEncTime = 0;

int encFastCounter = 0; unsigned long lastEncTurnTime = 0; int lastEncDirection = 0;

// ==================== СЕГМЕНТНЫЕ МАСКИ ==================== const uint8_t MY_SEG_A = 0b01110111; const uint8_t MY_SEG_b = 0b01111100; const uint8_t MY_SEG_C = 0b00111001; const uint8_t MY_SEG_d = 0b01011110; const uint8_t MY_SEG_L = 0b00111000; const uint8_t MY_SEG_S = 0b01101101; const uint8_t MY_SEG_Y = 0b01101110; const uint8_t MY_SEG_r = 0b01010000; const uint8_t MY_SEG_I = 0b01101100; const uint8_t MY_SEG_U = 0b00111110; const uint8_t MY_SEG_o = 0b01011100;

const uint8_t MODE_CLS[] = { MY_SEG_C, MY_SEG_L, MY_SEG_S, 0x00 }; const uint8_t MODE_ADD[] = { MY_SEG_A, MY_SEG_d, MY_SEG_d, 0x00 }; const uint8_t MODE_DLY[] = { MY_SEG_d, MY_SEG_L, MY_SEG_Y, 0x00 }; const uint8_t MODE_bRI[] = { MY_SEG_b, MY_SEG_r, MY_SEG_I, 0x00 }; const uint8_t MODE_Vol[] = { MY_SEG_U, MY_SEG_o, MY_SEG_L, 0x00 }; const uint8_t PUSE[] = { 0b01110011, 0b01111100, 0b01101101, 0b01111001 }; const uint8_t ELISE_SEG[] = { 0b01111001, 0b00111000, 0b00110000, 0b01101101 }; // E L I S

// ==================== SETUP ==================== void setup() {

   pinMode(BTN_P1, INPUT_PULLUP);
   pinMode(BTN_P2, INPUT_PULLUP);
   pinMode(BUZZER, OUTPUT);
   display1.setBrightness(setBrightness);
   display2.setBrightness(setBrightness);
   display1.showNumberDec(8888);
   display2.showNumberDec(8888);
   delay(800);
   showTime();
   showMenu();
   beep(100, BEEP_START);

}

// ==================== LOOP ==================== void loop() {

   encoder.tick();
   if (mode == 0) {
       handleEncoderMenu();
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           if (menuPos == 0) {
               mode = 3;
               setMinutes = timeP1 / 60;
               showSetTime();
               lastEncTime = millis();
           }
           else if (menuPos == 1) {
               mode = 5;
               setMode = gameMode;
               showSetMode();
               lastEncTime = millis();
           }
           else if (menuPos == 2) {
               if (gameMode == 1) {
                   mode = 4;
                   setAdd = addTime;
                   showSetAdd();
               }
               else if (gameMode == 2) {
                   mode = 7;
                   setDelay = delaySec;
                   showSetDelay();
               }
               else {
                   mode = 4;
                   setAdd = addTime;
                   showSetAdd();
               }
               lastEncTime = millis();
           }
           else if (menuPos == 3) {
               mode = 1;
               timeP1 = (long)setMinutes * 60;
               timeP2 = timeP1;
               movesP1 = 0;
               movesP2 = 0;
               gameStarted = false;
               turn = 0;
               paused = false;
               showTime();
               beep(50, BEEP_START);
           }
           else if (menuPos == 4) {
               mode = 6;
               showSetBrightness();
               lastEncTime = millis();
           }
           else if (menuPos == 5) {
               mode = 8;
               showSetVolume();
               lastEncTime = millis();
               easterEggStep = 0; // Сброс пасхалки при входе
           }
       }
       if (encoder.isHolded()) {
           beep(100, BEEP_LONG_PRESS);
           resetAll();
       }
   }
   else if (mode == 1) {
       if (digitalRead(BTN_P1) == LOW && !gameStarted) {
           delay(DEBOUNCE_DELAY);
           startGame(0);
       }
       else if (digitalRead(BTN_P2) == LOW && !gameStarted) {
           delay(DEBOUNCE_DELAY);
           startGame(1);
       }
       if (encoder.isHolded()) {
           beep(100, BEEP_LONG_PRESS);
           mode = 0;
           menuPos = 0;
           showTime();
           showMenu();
       }
   }
   else if (mode == 2) {
       if (encoder.isClick() && playing) {
           if (!paused) {
               paused = true;
               pauseStartTime = millis();
               beep(100, BEEP_PAUSE);
               display1.setSegments(PUSE, 4, 0);
               display2.setSegments(PUSE, 4, 0);
           } else {
               paused = false;
               unsigned long pauseDuration = millis() - pauseStartTime;
               lastTickTime += pauseDuration;
               beep(100, BEEP_UNPAUSE);
               showTime();
           }
           delay(200);
       }
       if (encoder.isHolded() && !showStats) {
           showStats = true;
           statsShowTime = millis();
           display1.showNumberDec(movesP1);
           display2.showNumberDec(movesP2);
           beep(50, BEEP_CLICK);
       }
       if (showStats && millis() - statsShowTime > 2000) {
           showStats = false;
           showTime();
       }
       handleGame();
   }
   else if (mode == 3) {
       handleEncoderValueDynamic(setMinutes, 1, 999, showSetTime);
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           mode = 0;
           showMenu();
       }
   }
   else if (mode == 4) {
       handleEncoderValueDynamic(setAdd, 0, 60, showSetAdd);
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           addTime = setAdd;
           mode = 0;
           showMenu();
       }
   }
   else if (mode == 5) {
       handleEncoderValue(setMode, 0, 2, showSetMode);
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           gameMode = setMode;
           mode = 0;
           showMenu();
       }
   }
   else if (mode == 6) {
       handleEncoderValue(setBrightness, 0, 7, showSetBrightness);
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           display1.setBrightness(setBrightness);
           display2.setBrightness(setBrightness);
           mode = 0;
           showMenu();
       }
   }
   else if (mode == 7) {
       handleEncoderValueDynamic(setDelay, 1, 60, showSetDelay);
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           delaySec = setDelay;
           mode = 0;
           showMenu();
       }
   }
   else if (mode == 8) {
       int oldVolume = setVolume;
       handleEncoderValueFast(setVolume, 0, 100, showSetVolume, 10);
       
       // Проверяем изменение громкости для пасхалки
       if (oldVolume != setVolume) {
           checkVolumeEasterEgg();
       }
       
       if (encoder.isClick()) {
           beep(30, BEEP_CLICK);
           mode = 0;
           showMenu();
           easterEggStep = 0;
       }
   }
   delay(5);

}

// ==================== ПАСХАЛКА ==================== void checkVolumeEasterEgg() {

   unsigned long now = millis();
   
   // Если прошло больше 3 секунд, сбрасываем
   if (now - lastEasterEggTime > 3000) {
       easterEggStep = 0;
   }
   
   lastEasterEggTime = now;
   
   // Проверяем последовательность: 0 -> 100 -> 0 -> 100
   if (easterEggStep == 0 && setVolume == 0) {
       easterEggStep = 1;
   }
   else if (easterEggStep == 1 && setVolume == 100) {
       easterEggStep = 2;
   }
   else if (easterEggStep == 2 && setVolume == 0) {
       easterEggStep = 3;
   }
   else if (easterEggStep == 3 && setVolume == 100) {
       // Пасхалка активирована!
       easterEggStep = 0;
       
       // Показываем ELIS
       display1.setSegments(ELISE_SEG, 4, 0);
       display2.setSegments(ELISE_SEG, 4, 0);
       
       // Играем мелодию
       playFurElise();
       
       // Возвращаем отображение
       showSetVolume();
   }

}

void playFurElise() {

   // Частоты нот
   const int E5 = 659;
   const int DS5 = 622;
   const int E4 = 330;
   const int B4 = 494;
   const int D5 = 587;
   const int C5 = 523;
   const int A4 = 440;
   const int REST = 0;
   
   // Упрощенная мелодия "К Элизе"
   int melody[] = {
       E5, DS5, E5, DS5, E5, B4, D5, C5, A4,
       REST, C5, E4, A4, B4, REST, E4, DS5, E5, B4, D5, C5, A4,
       REST, C5, E4, A4, B4, REST, E4, C5, B4, A4
   };
   
   int durations[] = {
       150, 150, 150, 150, 150, 150, 150, 150, 300,
       50, 150, 150, 150, 300, 50, 150, 150, 150, 150, 150, 150, 300,
       50, 150, 150, 150, 300, 50, 150, 150, 150, 300
   };
   
   int notesCount = sizeof(melody) / sizeof(melody[0]);
   
   for (int i = 0; i < notesCount; i++) {
       if (melody[i] != REST) {
           // Используем громкость, но не меньше 20%
           int volumePercent = (setVolume > 20) ? setVolume : 50;
           int adjustedFreq = map(volumePercent, 0, 100, 500, melody[i]);
           tone(BUZZER, adjustedFreq, durations[i] * 0.9);
       }
       delay(durations[i]);
       noTone(BUZZER);
   }
   
   delay(300);

}

// ==================== ОБРАБОТКА ЭНКОДЕРА ==================== void handleEncoderMenu() {

   if (encoder.isRight()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == 1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = 1; }
       } else { encFastCounter = 0; lastEncDirection = 1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           menuPos++;
           if (menuPos > 5) menuPos = 0;
           showMenu();
           lastEncTime = now;
       }
   }
   if (encoder.isLeft()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == -1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = -1; }
       } else { encFastCounter = 0; lastEncDirection = -1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           menuPos--;
           if (menuPos < 0) menuPos = 5;
           showMenu();
           lastEncTime = now;
       }
   }

}

void handleEncoderValue(int &value, int minVal, int maxVal, void (*updateFunc)()) {

   if (encoder.isRight()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == 1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = 1; }
       } else { encFastCounter = 0; lastEncDirection = 1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value++;
           if (value > maxVal) value = maxVal;
           updateFunc();
           lastEncTime = now;
       }
   }
   if (encoder.isLeft()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == -1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = -1; }
       } else { encFastCounter = 0; lastEncDirection = -1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value--;
           if (value < minVal) value = minVal;
           updateFunc();
           lastEncTime = now;
       }
   }

}

void handleEncoderValueDynamic(int &value, int minVal, int maxVal, void (*updateFunc)()) {

   if (encoder.isRight()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == 1) { encFastCounter++; } 
           else { encFastCounter = 1; lastEncDirection = 1; }
       } else { encFastCounter = 0; lastEncDirection = 1; }
       lastEncTurnTime = now;
       int step = (encFastCounter > ENC_FAST_THRESHOLD) ? 5 : 1;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value += step;
           if (value > maxVal) value = maxVal;
           updateFunc();
           lastEncTime = now;
       }
   }
   if (encoder.isLeft()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == -1) { encFastCounter++; } 
           else { encFastCounter = 1; lastEncDirection = -1; }
       } else { encFastCounter = 0; lastEncDirection = -1; }
       lastEncTurnTime = now;
       int step = (encFastCounter > ENC_FAST_THRESHOLD) ? 5 : 1;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value -= step;
           if (value < minVal) value = minVal;
           updateFunc();
           lastEncTime = now;
       }
   }

}

void handleEncoderValueFast(int &value, int minVal, int maxVal, void (*updateFunc)(), int step) {

   if (encoder.isRight()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == 1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = 1; }
       } else { encFastCounter = 0; lastEncDirection = 1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value += step;
           if (value > maxVal) value = maxVal;
           updateFunc();
           beep(20, BEEP_CLICK);
           lastEncTime = now;
       }
   }
   if (encoder.isLeft()) {
       unsigned long now = millis();
       if (now - lastEncTurnTime < 150) {
           if (lastEncDirection == -1) encFastCounter++;
           else { encFastCounter = 1; lastEncDirection = -1; }
       } else { encFastCounter = 0; lastEncDirection = -1; }
       lastEncTurnTime = now;
       int currentDelay = (encFastCounter > ENC_FAST_THRESHOLD) ? ENC_DELAY_FAST : ENC_DELAY_NORMAL;
       if (now - lastEncTime > currentDelay) {
           beep(5, BEEP_ENC);
           value -= step;
           if (value < minVal) value = minVal;
           updateFunc();
           beep(20, BEEP_CLICK);
           lastEncTime = now;
       }
   }

}

// ==================== ЛОГИКА ИГРЫ ==================== void handleGame() {

   if (!playing) return;
   unsigned long now = millis();
   if (!paused) {
       bool p1Pressed = digitalRead(BTN_P1) == LOW;
       if (p1Pressed && !lastP1) {
           beep(20, BEEP_BUTTON);
           if (turn == 0) {
               long elapsed = now - lastTickTime;
               
               if (gameMode == 2) {
                   if (elapsed > delaySec * 1000L) {
                       long extraTime = elapsed - (delaySec * 1000L);
                       int secondsElapsed = extraTime / 1000;
                       if (secondsElapsed > 0) {
                           timeP1 -= secondsElapsed;
                           if (timeP1 <= 0) { gameOver(1); return; }
                           lastTickTime += secondsElapsed * 1000;
                       }
                   }
                   lastTickTime = now;
               } else {
                   int secondsElapsed = elapsed / 1000;
                   if (secondsElapsed > 0) {
                       timeP1 -= secondsElapsed;
                       if (timeP1 <= 0) { gameOver(1); return; }
                       lastTickTime += secondsElapsed * 1000;
                   }
               }
               
               if (gameMode == 1) timeP1 += addTime;
               movesP1++;
               turn = 1;
               showTime();
           }
       }
       lastP1 = p1Pressed;
       bool p2Pressed = digitalRead(BTN_P2) == LOW;
       if (p2Pressed && !lastP2) {
           beep(20, BEEP_BUTTON);
           if (turn == 1) {
               long elapsed = now - lastTickTime;
               
               if (gameMode == 2) {
                   if (elapsed > delaySec * 1000L) {
                       long extraTime = elapsed - (delaySec * 1000L);
                       int secondsElapsed = extraTime / 1000;
                       if (secondsElapsed > 0) {
                           timeP2 -= secondsElapsed;
                           if (timeP2 <= 0) { gameOver(2); return; }
                           lastTickTime += secondsElapsed * 1000;
                       }
                   }
                   lastTickTime = now;
               } else {
                   int secondsElapsed = elapsed / 1000;
                   if (secondsElapsed > 0) {
                       timeP2 -= secondsElapsed;
                       if (timeP2 <= 0) { gameOver(2); return; }
                       lastTickTime += secondsElapsed * 1000;
                   }
               }
               
               if (gameMode == 1) timeP2 += addTime;
               movesP2++;
               turn = 0;
               showTime();
           }
       }
       lastP2 = p2Pressed;
   }
   if (gameStarted && !paused) {
       long elapsed = millis() - lastTickTime;
       
       if (gameMode == 2) {
           if (elapsed > delaySec * 1000L) {
               long activeTime = elapsed - (delaySec * 1000L);
               int secondsElapsed = activeTime / 1000;
               if (secondsElapsed > 0) {
                   if (turn == 0) {
                       timeP1 -= secondsElapsed;
                       if (timeP1 <= 0) { gameOver(1); return; }
                   } else {
                       timeP2 -= secondsElapsed;
                       if (timeP2 <= 0) { gameOver(2); return; }
                   }
                   lastTickTime += secondsElapsed * 1000;
                   showTime();
               }
           }
       } else {
           int secondsElapsed = elapsed / 1000;
           if (secondsElapsed > 0) {
               if (turn == 0) {
                   timeP1 -= secondsElapsed;
                   if (timeP1 <= 0) { gameOver(1); return; }
               } else {
                   timeP2 -= secondsElapsed;
                   if (timeP2 <= 0) { gameOver(2); return; }
               }
               lastTickTime += secondsElapsed * 1000;
               showTime();
           }
       }
   }
   if (digitalRead(BTN_P1) == LOW && digitalRead(BTN_P2) == LOW) {
       if (bothStart == 0) bothStart = millis();
       if (millis() - bothStart > LONG_PRESS_TIME) {
           resetAll();
           bothStart = 0;
       }
   } else {
       bothStart = 0;
   }

}

// ==================== СТАРТ ==================== void startGame(int firstPlayer) {

   mode = 2;
   playing = true;
   paused = false;
   gameStarted = true;
   bothStart = 0;
   movesP1 = 0;
   movesP2 = 0;
   if (firstPlayer == 0) turn = 0;
   else turn = 1;
   lastTickTime = millis();
   showTime();
   beep(50, BEEP_START);

}

// ==================== ОТОБРАЖЕНИЕ ==================== void showTime() {

   bool p1Hours = (timeP1 >= 5940);
   bool p2Hours = (timeP2 >= 5940);
   
   if (p1Hours) {
       int hours = timeP1 / 3600;
       int minutes = (timeP1 % 3600) / 60;
       if (hours > 99) hours = 99;
       if (minutes > 59) minutes = 59;
       display1.showNumberDecEx(hours * 100 + minutes, 0b01000000, true, 4, 0);
   } else {
       int minutes = timeP1 / 60;
       int seconds = timeP1 % 60;
       if (minutes > 99) minutes = 99;
       if (seconds > 59) seconds = 59;
       display1.showNumberDecEx(minutes * 100 + seconds, 0b01000000, true, 4, 0);
   }
   
   if (p2Hours) {
       int hours = timeP2 / 3600;
       int minutes = (timeP2 % 3600) / 60;
       if (hours > 99) hours = 99;
       if (minutes > 59) minutes = 59;
       display2.showNumberDecEx(hours * 100 + minutes, 0b01000000, true, 4, 0);
   } else {
       int minutes = timeP2 / 60;
       int seconds = timeP2 % 60;
       if (minutes > 99) minutes = 99;
       if (seconds > 59) seconds = 59;
       display2.showNumberDecEx(minutes * 100 + seconds, 0b01000000, true, 4, 0);
   }

}

void showMenu() {

   display1.showNumberDec(menuPos + 1);
   display2.showNumberDec(menuPos + 1);

}

void showSetTime() {

   static int lastMinutes = -1;
   if (setMinutes < 100) {
       if (lastMinutes >= 100 && setMinutes < 100) display1.clear();
       display1.showNumberDecEx(setMinutes, 0b00000000, true, 2, 2);
   } else {
       if (lastMinutes < 100 && setMinutes >= 100) display1.clear();
       display1.showNumberDec(setMinutes);
   }
   lastMinutes = setMinutes;
   display2.showNumberDec(1);

}

void showSetAdd() {

   display1.showNumberDecEx(setAdd, 0b00000000, true, 2, 2);
   display2.setSegments(MODE_ADD, 4, 0);

}

void showSetDelay() {

   display1.showNumberDecEx(setDelay, 0b00000000, true, 2, 2);
   display2.setSegments(MODE_DLY, 4, 0);

}

void showSetMode() {

   if (setMode == 0) { display1.setSegments(MODE_CLS, 4, 0); display2.showNumberDec(1); }
   else if (setMode == 1) { display1.setSegments(MODE_ADD, 4, 0); display2.showNumberDec(2); }
   else if (setMode == 2) { display1.setSegments(MODE_DLY, 4, 0); display2.showNumberDec(3); }

}

void showSetBrightness() {

   display1.setSegments(MODE_bRI, 4, 0);
   display2.showNumberDec(setBrightness);

}

void showSetVolume() {

   display1.setSegments(MODE_Vol, 4, 0);
   display2.showNumberDec(setVolume / 10);

}

void gameOver(int loser) {

   playing = false;
   beep(1000, BEEP_GAME_OVER);
   for (int i = 0; i < 6; i++) {
       if (loser == 1) display1.clear();
       else display2.clear();
       delay(200);
       showTime();
       delay(200);
   }
   mode = 0;
   menuPos = 0;
   showMenu();

}

void resetAll() {

   timeP1 = (long)setMinutes * 60;
   timeP2 = timeP1;
   addTime = setAdd;
   delaySec = setDelay;
   gameMode = setMode;
   movesP1 = 0;
   movesP2 = 0;
   mode = 0;
   playing = false;
   paused = false;
   gameStarted = false;
   menuPos = 0;
   showTime();
   showMenu();
   beep(100, BEEP_RESET);

}

void beep(int duration, int frequency) {

   if (setVolume == 0) return;
   int adjustedFreq = map(setVolume, 0, 100, 500, frequency);
   if (adjustedFreq < 500) adjustedFreq = 500;
   if (adjustedFreq > frequency) adjustedFreq = frequency;
   tone(BUZZER, adjustedFreq, duration);
   delay(duration);
   noTone(BUZZER);

}

Примечание

Ссылки

Группа

  • Тельпухова Майя Максимовна
  • вввв