Disclaimer

Имея на руках экран Nextion NX32f024 захотел сделать игру Memory силами Nextion, без обвязки МК или ещё чего мощнее. Почему? Просто захотел.

Начинаем

Открываем Nextion Editor, делаем новый проект.

Выбираем нашу плату (у меня NX32F024 разрешение 320x240 пикселей). Учитывая современные реалии, ставлю вертикальную ориентацию 0 градусов, чтобы кабели эстетично торчали вверх, как у старых телефонов с audio jack.

Мне приятно, что кабели не упираются мне в ладонь
Мне приятно, что кабели не упираются мне в ладонь

Из панели Toolbox перетаскиваем 6 элементов Button. Интерфейс неинтуитивен (IMHO) надо клацнуть, а потом уже редактировать компонент. Выбрал размеры 80 на 80 (редактируется через вкладку Атрибуты Attributies). Размножаю через ctrl-c ctrl-v, чтобы получить 6 кнопок. Имена b0b5.

Теперь берем заранее подготовленные картинки (я взял 5 для игры и 1 для обложки). Добавляем через панель Picture, Add. Все картинки размером 80 на 80 пикселей. (Использовал обычный Paint.)

В данном случае у меня используются картинки с ID0 – ID6 (исключаем ID3 – это иконка задника, так вышло, а менять я не захотел), которые хранят 5 уникальных изображений. Вообще их можно использовать столько, на сколько хватит памяти у контроллера и терпения у создателя.

Теперь на все кнопки в поле pic и pic2 пишем 3 (обложка).

Мне приглянулись фрукты. Предполагал, что так будет веселее
Мне приглянулись фрукты. Предполагал, что так будет веселее

Создаю паттерны (уровни) из возможных комбинаций (необходимо чтобы было именно 3 пары). Выглядит это как-то вот так

va0.val=1
va1.val=2
va2.val=4
va3.val=1
va4.val=2
va5.val=4

Уровни и «рандом» в Nextion

Теперь нам нужно реализовать функцию для перемешивания наших паттернов, чтобы игра шла хоть сколько-нибудь нелинейно. Для имитации рандома (в Nextion нет никаких random функций) я использую таймер tm_randи переменную is_start, а также переменная seed. У таймера устанавливаем tim (time in ms) равным 50 (это доступный минимум).

Суть: таймер каждые 50 миллисекунд инкрементирует переменную seed до максимального числа (количество наших паттернов-уровней). Получение этого «рандомного» значения происходит в момент первого нажатия на любую из кнопок карточек.

Вот код обработчика таймера

seed.val+=1
if(seed.val>11)
{
  seed.val=0
}

Здесь мы инкрементируем значение seed, и, если оно больше чем количество паттернов (у меня их 12), то просто обнуляем, чтобы начать инкрементировать заново.

Кусок кода для кнопки

if(is_start.val==0)
{
  is_start.val=1
  if(seed.val==0)
  {
    va0.val=1
    va1.val=2
    va2.val=4
    va3.val=1
    va4.val=2
    va5.val=4
  }
  if(seed.val==1)
    ...

Сначала проверяем, что у нас игра не запущена (is_start.val == 0)

Потом устанавливаем start в 1, говоря, что игра запущена и мы «загрузили» наш паттерн-уровень. Затем мы проверяем, какое случайное число в seed, и присваиваем соответствующие ID картинок каждой из 6 кнопок

Логика

Теперь переходим к логике. Для управления основной логикой работы игры используется переменная state. Может иметь четыре с��стояния:

  • 0 – игра еще не началась, ожидание открытия первой карты

  • 1 – игрок открыл первую карту, ждем вторую

  • 2 – нет совпадений! заблокировано управление на время показа ошибки

  • 3 – все пары найдены. Игра окончена

И также щепотка дополнительных переменных:

  • first_val — хранит ID картинки (из массива va), которая изображена на первой открытой карточке. Именно это число мы сравниваем со второй карточкой, чтобы понять, совпали они или нет.

  • first_id — хранит индекс первой нажатой кнопки. Необходимо чтобы «заморозить» кнопку, если пара совпала, или закрыть, если пара не совпала.

Теперь можно прописать код для начала кнопки. Используем Touch Release Event (чтобы попытаться минимизировать дрожащие пальцы и мисклики)

Код (код инициализации пропускаем)

if(timer.en==0)
{
  if(state.val<2)
  {
    b#.pic=va#.val
    if(state.val==0)
    {
      first_id.val=#
      first_val.val=va#.val
      state.val=1
      tsw b#,0
    }
else if(state.val==1)
    {
      tsw 255,0
      if(first_val.val==va#.val)
      {
        found.val+=1
        state.val=0
        tsw 255,1
        tsw b#,0
        if(first_id.val==0)
        {
          tsw b0,0
        }
/* опущены остальные для краткости*/
        if(found.val==3)
        {
          state.val=3
          timer.tim=1000
          timer.en=1
        }
      }
else
      {
        state.val=2
        second_id.val=#
        timer.en=1
      }
    }
/* здесь # - номер кнопки от 0 до 5 соответственно */

Сначала у нас идет проверка, что мы играем (timer.en==0), потом идет проверка, что у нас открыто не более 2 карт (state.val<2) потом мы показываем нашу картинку (b#.pic=va#.val). Если это первое нажатие (if(state.val==0)), то мы присваиваем значения в наши переменные для хранения first_id, first_val и state (для указывания, что мы открыли первую кнопку) ну и в конце замораживаем кнопку, чтобы игрок больше не мог на нее нажать (tsw b#,0).

Однако если мы открываем уже вторую карточку (state.val==1), мы должны заморозить весь экран (tsw 255,0). Проверяем совпали ли наши карточки (first_val.val==va#.val), если да то:

  • инкрементируем счетчик (found.val+=1)

  • переводим машину состояний в начальное положение (state.val=0)

  • размораживаем все (tsw 255,1)

  • замораживаем нашу кнопку (tsw b#,0)

  • через гору if ищем вторую кнопку и также ее замораживаем.

Если же мы ошиблись и не отгадали вторую карточку, то:

  • переводим машину состояний в состояние «нет совпадений»

  • в переменную second_id закидываем номер нажатой кнопки (Это нужно таймеру, чтобы он знал, какие именно две кнопки «закрыть» (вернуть рубашку) при ошибке, не перерисовывая всё поле целиком)

  • включаем таймер timer

Прибираемся в карточках. Таймер для переворачивания

Для того, чтобы карточки не были всегда перевернуты есть таймер timer (tim = 1000 (1 cекунда)). Вот код обработки Timer Event

timer.en=0
tsw 255,1
if(state.val==3)
{
  state.val=0
  page victory
}

Здесь мы прежде всего выключаем этот же таймер. Если (state.val==3), значит мы выиграли, переводим нашу машину состояний в начальное положение и прыгаем на страницу с новой игрой (победа). А если игра еще не закончена то, мы закрываем только две конкретные кнопки, которые не совпали. Тут очень большой текст который строится по следующему типу

if(first_id.val==#)
{
  b#.pic=3
}
if(second_id.val==#)
{
  b#.pic=3
}

Напомню, что наша обложка карточки - это картинка с ID = 3. В конце также переводим машину состояний в начальное положение state.val=0 (found не обнуляем).

Помимо основной страницы page0 у нас также есть страница victory. На последней у нас расположена только одна кнопка с кодом обнуления и перехода на страницу игры

page0.is_start.val=0
page0.found.val=0
page0.state.val=0
page page0

Необходимо чтобы is_start, found и state были Global. И несмотря на то, что они Global обращаться к ним нужно через имя страницы, иначе не сработает.

Видео на rutube

P.S.

Моему ребенку (мальчик 2,5г) это поделие не сильно понравилось. Поигрался без особого энтузиазма.