Сегодня мы напишем сетевую карточную игру на двух игроков. Какую игру написать? Давайте напишем популярную карточную игру "Дурак", цель которой — избавиться от всех карт. Подробнее о правилах Вы можете узнать здесь.  Рисунок 1. Последовательный порт — сленговое название интерфейса стандарта RS-232, которым массово оснащались персональные компьютеры, техника специального назначения, в том числе и принтеры. Порт называется "последовательным", так как информация через него передаётся по одному биту, последовательно бит за битом. Обычные персональные компьютеры снабжены RS-232, а микроконтроллеры и M5STACK в том числе использует TTL.  RS-232 для передачи "0" использует напряжение от -3 до -25 В, а для передачи "1" от +3 до +25 В. В отличии от RS-232 в TTL для передачи "0" используется напряжение близкое к 0 В, а для передачи "1" рабочее напряжение интегральной схемы, как правило 3.3 или 5 В (рис. 2). 
Рисунок 2. Разница напряжений между RS-232 и TTL Для обмена данными через последовательный порт M5STACK используют цифровые порты ввод/вывода R0, R2 (RX) и T0, T2 (TX) (рис. 3), а также USB порт. Важно учитывать, что если вы используете функции для работы с последовательным портом, то нельзя одновременно с этим использовать порты 0 и 1 для других целей. 
Рисунок 3. Правило подключения двух устройств через последовательный порт Для работы с данным портом используют стандартный набор функций Serial из Arduino IDE https://www.arduino.cc/reference/en/language/functions/communication/serial/ Ознакомьтесь с понятием таблицы ASCII, с помощью которой кодируются символы в вычислительной технике: https://en.wikipedia.org/wiki/ASCII Перечень компонентов для урока Давайте нарисуем логотип игры, который будет появляться на дисплее устройства при запуске игры. Для этого используем любой редактор, например MS Office Word и Paint (рис. 4).  
Рисунок 4. Рисуем логотип игры в MS Office Word и Paint :) Далее при помощи приложения из урока 1.2.1 http://forum.m5stack.com/topic/49/lesson-1-2-1-lcd-how-to-create-image-array конвертируем изображение в массив пикселей и получим файл logo.c, который подключим к скетчу. По правилам игры используется колода, содержащая только 36 карт. По 6 карт выдается каждому игроку. Игровое поле рассчитано на 12 карт. Игрок может взять 10 карт, после чего проиграет. В игре карта имеет своё значение и масть, например 7. В программе карта имеет своё место: (в руках у игрока — поле игрока; в игровом поле; в использованной колоде; в неиспользованной колоде) и назначение: свободная карта, недоступная карта, карта козырная, карта игрока, карта противника. Сделаем структуру, которая содержит порядковые номера параметров. Колода карт представляет собой массив структур (рис. 5). 
Рисунок 5. Структура карты и массив колоды карт
 Атака. Случай 1. Если в игровом поле нет ни одной карты, тогда игрок, который получил первое место в очереди может кинуть совершенно любую карту любой масти (рис. 5.1): 
Рисунок 5.1. Можно сделать ход любой картой Атака. Случай 2. Если соперник сделал ход на игрока, то игрок может отбиться картой такой же масти, но большего значения (рис. 5.2) или козырной картой любого значения при условии того, что последняя карта соперника в игровом поле не является козырной: 
Рисунок 5.1. Соперник должен отбить карту игрока Атака. Случай 3. Когда соперник отобьёт карту, то количество шагов станет равным и игрок сможет подкинуть ещё карты такого же значения, что имеется в игровом поле: Бито/взять. Случай 1. Если у игроков одинаковое количество шагов, то игрок может отправить карты в отбитую колоду: Бито/взять. Случай 2. Если у соперник сделал больше ходов, чем игрок, то игрок может забрать все карты с игрового поля себе: Благодаря прекрасным стандартным функциям для работы со встроенным дисплеем из библиотеки M5STACK, рисование карт займет доли секунд и практически не займёт памяти устройства. 
Рисунок 5.3 Функция отправки строки добавляет в конец строки полученной в качестве аргумента символ новой строки и отправляет её в последовательный порт. Потом пытается принять строку, содержащую в себе символ конца сеанса передачи данных. Если символ пришёл, то функция вернёт true иначе false. Функция приёма строки в течении timeout (3000 миллисекунд) пытается принять строку, при этом очищая от мусора в начале до символа начала пакета. В случае неудачи возвращает пустую строку. Сделаем так, что любая информация, передаваемая между устройствами упаковывалась в специальные пакеты (рис. 6). Напишем функцию, которая принимает, распаковывает и выполняет пакеты. А также напишем вспомогательную функцию (parseString), которая позволит извлекать определенный участок из строки, заключенный между специальных знаков разделителей (похожа на метод Split из JS). 
Рисунок 6. Структура пакета данных При включении устройства и соединении кабеля, каждое из устройств выдерживает случайный интервал времени, при этом слушает входящие пакеты, и, отсылает пакет "Я здесь!" (рис. 7). По-умолчанию оба устройства являются первыми игроками. То устройство, которое первым примет послание "Я здесь!" сразу назначит себе номер игрока 2. 
Рисунок 7. Принцип работы рукопожатия для получения номера игрока

void handshakePlayerId() {
long tpid = random(10, 1001) * 10;
long previousMillis = millis();
while (!serialRecivePacket())
{
unsigned long currentMillis = millis();
if (currentMillis — previousMillis > tpid) break;
}
while (!serialSendPlayerTId());
} Функция синхронизации по факту является ключевой в данном проекте. Целью функции синхронизации является реагирование при действиях игроков и обмен игровой информацией. Функция вызывается в ручном и автоматическом режиме из цикла loop(). Когда очередь игрока функция работает в ручном режиме и выполняется только после действий игрока. В то время эта же функция работает в автоматическом режиме на устройстве соперника. Функция whoWin() возвращает номер игрока, который выиграл или -1, если никто не выиграл.
По правилам игры считается, что если у игрока не осталось ни одной карты, то он выиграл. Если у игрока 10 (playerFieldAmount) и более карт, то он проиграл. Скачать скетч для Arduino IDE можно внизу урока в разделе Загрузки. Соединим устройства при помощи последовательного порта (рис. 5.1). И нажмём красные кнопки на обоих устройствах (рис. 8). Попробуем сыграть! (рис. 8.1). 
Рисунок 8. Первый запуск игры 
Рисунок 8.1. Играем! Приобрести M5Stack можно здесь
Скетч можно скачать здесь