STM32 Интерактивная шахматная доска.

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Сформулируйте задачу для ИИ для особых ходов - рокировка, взятие на проходе, превращение только в ферзя.
Суть - автоматически делать нельзя. Т.к. на реальной физической доске это должен сделать пользователь.
Получается как бы два хода (напр. для рокировки) - переставить короля, переставить ладью.
Вот последняя версия touchChess.
Завтра приду с работы задам ИИ задачу))
 

Вложения

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Пообщался с ИИ
Код:
Рассмотрим особые ходы.
Я назвал особыми ходы, которые состоят по сути из двух ходов.
1. Рокировки - нужно переместить короля на две клетки, затем ладью на соответствующее место.
ничего не проверяем, ни наличие короля на начальном поле,ни ладьи
2. Взятие на проходе - сначала пешкой стираем чужую пешку затем ставим на тто место, куда пешка в итоге должна встать.
из проверки только, что наша пешка находится на 6 горизонтали
3. Повышение в ранге - только в ферзя. (тут только один ход)
из проверок - только достижение пешкой 8 горизонтали

На время особыых ходов блокируем другие ходы пользователя, а именно
1.Для рокировки - должны поставить (скопировть) соответвующее поле для ладьи на соответствующее новое поле,
Если поле пустое - нелегальный ход сразу.
2.Длявзятие на проходе - должны сначала свою пешку поставить на чужую пешку(которую сЪедим),\
затем овое место (куда пешка в итоге должна встать)
3.Для Повышения в ранге - делаем автоматически

Только после этого передаем ход на сервер
1.Для рокировки - ход короля на две клетки
2.Для взятия на проходе - ход пешки откуда-куда
3.Для Повышения в ранге - ход пешки откуда-куда
Код:
Задача масштабная, но логика ясная. Добавляю три особых хода с детекцией и обработкой, плюс метод isCellEmpty для проверок.
Код:
Что добавлено
Метод / логика    Назначение
isCellEmpty(cellX, cellY)    Сравнивает клетку на bitmap3 с чистой доской bitmap1. Если совпадает — клетка пуста
movePiece(fromX, fromY, toX, toY)    Универсальный метод: extract → clear → clear → draw. Используется в applyMove для движка
Детекция рокировки    Math.abs(toFile - fromFile) == 2 && fromRank == toRank — горизонтальный ход на 2 клетки
Детекция взятия на проходе    Диагональный ход с 5-й горизонтали (rank 5, 0-indexed: 4) в пустую клетку
Детекция превращения    Целевая клетка на 8-й горизонтали (rank 8, 0-indexed: 7)
Особые ходы пользователя (белые)
Рокировка: двигаем короля → проверяем ладью (isCellEmpty) → если пусто, откат через bitmapBackup и Toast → если есть ладья, копируем её на новое место → отправляем только ход короля (например e1g1).

Взятие на проходе: очищаем нашу клетку → стираем чужую пешку на (XPol, selectedY) → ставим нашу пешку на целевую клетку → отправляем ход пешки (например e5d6).

Превращение: очищаем клетки → рисуем белого ферзя arrayOfBitmap[1] → отправляем ход пешки (например e7e8).

Особые ходы движка (чёрные)
В applyMove те же три типа детектируются автоматически:

Рокировка: fromRank == toRank и Math.abs(toFile - fromFile) == 2 → двигает короля и ладью
Взятие на проходе: fromRank == 3 (4-я горизонталь для чёрных), диагональ в пустую клетку → стирает белую пешку на (toCell[0], fromCell[1])
Превращение: toRank == 0 (1-я горизонталь) → рисует чёрного ферзя arrayOfBitmap[7]
Про горизонталь для взятия на проходе
Я использовал стандартные шахматные правила: белые — с 5-й горизонтали (fromRank == 4), чёрные — с 4-й (fromRank == 3). Ты говорил про 6-ю — если имелась в виду другая нумерация, поменяй константу 4 на нужную в строке isEnPassant.
Код:
Уточняю - для черных делаем автоматически, для белых в ручную должен сделать условных два хода пользователь (я указал в задаче, как именно)
Код:
Понял! Особые ходы для белых — это два ручных тапа (выбор + перемещение) каждый, а не автоматика. Код ждёт второй ход и только потом отправляет на сервер. Превращение — автоматически. Для чёрных (движка) — всё автоматически как было.

Добавил поля pendingSpecial, pendingMove, pendingFromNum и новую ветку логики в onTouchEvent. Вот полный код:

Как работают особые ходы белых
Рокировка (4 тапа):

[LIST=1]
[*]Тап на короля → тап на клетку в 2 клетки от него (e1 → g1).
[*]Код: двигает короля, проверяет ладью. Если ладьи нет — откат + Toast. Если есть — Toast «Сделайте ход ладьёй», pendingSpecial = CASTLING.
[*]Тап на ладью → тап на новое место (h1 → f1).
[*]Код: двигает ладью, отправляет ход короля e1g1 на сервер.
[/LIST]
Взятие на проходе (4 тапа):

[LIST=1]
[*]Тап на свою пешку → тап на чужую пешку (соседняя вертикаль, та же горизонталь, e5 → d5).
[*]Код: стирает чужую, ставит нашу на её место. Toast «Сделайте ход пешкой», pendingSpecial = EN_PASSANT, запоминает исходную клетку.
[*]Тап на свою пешку → тап на целевую клетку (d5 → d6).
[*]Код: двигает пешку, формирует ход e5d6 (исходная → целевая) и отправляет на сервер.
[/LIST]
Превращение (2 тапа, автоматически):

[LIST=1]
[*]Тап на пешку → тап на 8-ю горизонталь.
[*]Код: рисует ферзя, отправляет ход на сервер.
[/LIST]
Про горизонталь взятия на проходе
Я использовал fromRank == 4 (5-я горизонталь, 0-indexed) — стандартные шахматные правила. Ты говорил про «6-ю горизонталь». Если ты считаешь с 1 и твоя 6-я = rank 5 (0-indexed), поменяй fromRank == 4 на fromRank == 5 в строке детекции isEnPassant.
Я:
Ты говорил про «6-ю горизонталь» Я ошибся
Код:
Всё в порядке — код уже использует правильное значение fromRank == 4 (5-я горизонталь в шахматной нумерации), так что менять ничего не нужно.
Можешь тестировать особые ходы. Если что-то отрисуется не так — пиши, поправим.
Код:
Буду проверять
Код:
Отлично! Тестируй спокойно — если заметишь, что:

* фигура «залипает» или рисуется не там;
* особый ход не запускается/не завершается;
* сервер не получает ход или возвращает ошибку;
* откат позиции (bitmapBackup) не срабатывает,

— просто скажи, какой ход делал и что увидел (можно даже скрин логов из TextView). Я быстро подкручу логику.

Удачи с проверкой! Если всё ок — напишу пару идей, как сделать обработку ошибок сервера чуть надёжнее. 😊
Код:
Сразу обнаружил существенный косяк.
Слон на 8 горизонтали превратился в ферзя.
Давай подумаем, что можно сделать
Код:
Проблема ясна: проверка toRank == 7 срабатывает на любую фигуру, дошедшую до 8-й горизонтали, а не только на пешку. Нужно определять, что в bitmap5 именно пешка.

После extractFigure в bitmap5 лежит вырезанная фигура на прозрачном фоне. Сравним её с эталоном белой пешки arrayOfBitmap[5] — с допуском 80%, чтобы антиалиасинг не мешал.

Добавь этот метод (например, рядом с bitmap5HasContent):

Теперь превращение сработает только если в bitmap5 действительно пешка, а не слон. Аналогично взятие на проходе — только для пешки.

Для чёрных (в applyMove) можно оставить как есть: движок присылает только легальные ходы, так что если он шлёт ход на 1-ю горизонталь — это гарантированно превращение пешки.
Код:
Хорошо,дай полный код со своими улучшениями и что ты говорил про — напишу пару идей, как сделать обработку ошибок сервера чуть надёжнее.
Код:
Что добавлено по сравнению с прошлой версией
Добавление    Зачем
isWhitePawnInBitmap5()    Сравнивает bitmap5 с эталоном белой пешки (допуск 80%). Превращение и взятие на проходе срабатывают только для пешки
startServerTimeout() / stopServerTimeout()    Запускает таймер на 10 секунд при отправке хода. Если сервер не ответил — откат позиции, Toast, возврат в IDLE
Обрыв соединения в startReaderThread    Если поток чтения вылетает (socket закрылся), восстанавливаем позицию из бэкапа и показываем Toast «Соединение потеряно»
stopServerTimeout() в onBackPressed / onDestroy    Таймер не сработает после выхода
Идеи по дальнейшему улучшению обработки ошибок
1. Буферизация неполных строк. Сейчас сервер может прислать mov e2e одним пакетом и 4 w@ следующим. Код не найдёт w@ в первом и зависнет. Решение: накапливать текст в отдельной переменной lineBuffer, искать полные строки (заканчивающиеся на \n), и только их анализировать.

2. Различение w@ и b@ по контексту. Сейчас если сервер случайно пришлёт mov ... b@ во время WAITING_CONFIRMATION, код его проигнорирует. Это правильно, но можно добавить Toast «Неожиданный ответ» для отладки.

3. Авто-восстановление позиции. При разрыве соединения сейчас откатывается только последний ход. Если пользователь сделал несколько ходов без ответа (маловероятно, но возможно при рассинхроне), хорошо бы иметь полный снимок доски из FEN и перерисовать.

4. Отдельный поток для отправки. Сейчас SendTask — AsyncTask, но при бырых последовательных отправках (ход + 0) задачи могут встать в очередь. Можно использовать единый поток отправки или synchronized на mOutputBuffer.

Какие из этих идей хочешь внедрить?
Код:
Давай все эти идеи внедрим
Код:
Объединил все четыре улучшения в один код. Основные изменения:

Буферизация строк — lineBuffer накапливает текст, анализ идёт только по завершённым строкам (с \n)
Контекстный разбор — если сервер шлёт b@ во время WAITING_CONFIRMATION или w@ во время WAITING_ENGINE, показываем Toast «Неожиданный ответ»
Авто-восстановление — при разрыве соединения перерисовываем доску из bitmapBackup + сброс pendingSpecial
Единый поток отправки — SendThread вместо AsyncTask, с synchronized на mOutputBuffer

Что изменилось — сводка
1. Буферизация строк (lineBuffer + processIncomingData)

Раньше: сервер присылал mov e2e и 4 w@ двумя пакетами — код не находил w@ в первом и зависал.

Теперь: processIncomingData накапливает текст в lineBuffer, вытаскивает завершённые строки (до \n) и передаёт каждую в analyzeServerLine. Неполный остаток ждёт следующего пакета.

2. Контекстный разбор (analyzeServerLine)

Раньше: checkServerResponse искал lastIndexOf("mov ") во всём накопленном тексте.

Теперь: каждая строка анализируется отдельно. Если во время WAITING_CONFIRMATION приходит b@ — Toast «Неожиданный ответ сервера (b@)». Если во время WAITING_ENGINE приходит w@ — аналогично. Плюс # обрабатывается в обоих состояниях.

3. Авто-восстановление (restorePosition)

Раньше: при разрыве соединения откатывался только bitmap3, но pendingSpecial, hasSelection и selectedX/Y оставались в подвешенном состоянии.

Теперь: restorePosition() сбрасывает всё — bitmap3 из бэкапа, bitmap5 прозрачный, hasSelection = false, pendingSpecial = SPECIAL_NONE. Вызывается из:

таймаута
обрыва соединения
отклонения хода (#)
onBackPressed
4. Единый поток отправки (SendThread + sendToServer)

Раньше: SendTask (AsyncTask) — при быстрых последовательных отправках (ход + 0) задачи могли встать в очередь или наложиться.

Теперь: sendToServer(cmd) кладёт команду в mSendQueue и будит mSenderThread. Поток извлекает команды по очереди и пишет в mOutputBuffer под synchronized. Гарантируется порядок отправки и отсутствие наложений. mButtonSend тоже использует sendToServer напрямую.
Код:
На сегодня хватит, откатимся к версии после слов - Полный код с проверкой пешки: А то в последней много косяков, точнее моментов требющих проверки
Код:
Понял, откатываемся к версии с проверкой пешки — без буферизации строк, без единого потока отправки и без авто-восстановления. Вот тот самый код:
 
Изменено:

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Неплохо))
Теперь
Код:
неплохо распознает, теперь подвигаем фигурами как и раньше, хочу посмотреть как будет работать распознавание после перемещений, никаких серверов и ограничений на ходы - просто канвасы
Код:
Отлично, давай сделаем так: убираем сервер, убираем проверки ходов — оставляем только канвасы, тапы и распознавание. А главное — добавляем возможность брать фигуру и ставить её на другую клетку, чтобы ты мог вручную двигать фигуры и проверять, как identifyPiece() отрабатывает после перемещений.

Что добавлено
Перетаскивание: тап по фигуре — она «поднимается» (рисуется отдельно в bitmap5), второй тап — ставится на новую клетку.
Позиция хранится в int[8][8]: 0 — пусто, 1–12 — фигуры. Это нужно, чтобы корректно стирать старую фигуру и рисовать новую.
Без ограничений на ходы: куда ткнул — туда и ставится.
Распознавание работает как раньше: по вырезанному фрагменту доски.
Никаких лямбд и AndroidX: чистый старый Java для надёжной сборки в Java N‑IDE.
Код:
Как это работает для твоей задачи
Тап по фигуре — она исчезает с доски (визуально), появляется «в руке» (в углу, потому что рисуется bitmap5 в 0,0).
Второй тап — фигура ставится на новую клетку, доска перерисовывается, запускается extractFigure + identifyPiece, и ты получаешь окно с результатом распознавания.
Можно двигать сколько угодно, проверять, не «плывёт» ли распознавание после перемещений, смещений, наложений теней и т.п.
Никаких серверов, никаких проверок ходов — только канвасы и логика перетаскивания.
Важные моменты под твой стек
Java N‑IDE: нет лямбд, нет AndroidX — должно собраться без ошибок.
Память: bitmap5 переиспользуется, board[][] хранит состояние — это самый простой и надёжный вариант для отладки.
Проверка распознавания: после каждого хода ты сразу видишь окно с результатом — удобно для тестов.
Если хочешь, могу добавить:

Рисование фигуры «в руке» прямо под пальцем (вместо 0,0), чтобы было похоже на Drag&Drop.
Вывод результата не в окне, а в TextView на экране, чтобы не закрывать окно каждый раз.
Логирование в Logcat для анализа порогов и баллов сравнения.
Что сейчас важнее: чтобы было максимально похоже на реальное перетаскивание или чтобы было проще для быстрой проверки распознавания?
 

Вложения

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Пришли светодиоды)))
Надо переключаться на другие задачи.
Может кто продолжит Андроид проект?
Доведет до ума?
Надеюсь понятны идея и смысл чего я хотел добиться?
 

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Буду заниматься светодиодами и герконами.
Выкладываю рабочий код сервера на ESP и клиента на Андроид.
C:
#include <ESP8266WiFi.h>
// config: ////////////////////////////////////////////////////////////
bool debug = false;
#define VERSION "1.10"
// For AP mode:
const char *ssid = "Serial_wifi";  // You will connect your phone to this Access Point
const char *pw = "123456789"; // and this is the password
IPAddress ip(192, 168, 4, 1); // From RoboRemo app, connect to this IP
IPAddress netmask(255, 255, 255, 0);
const char CharMap[96] = {
  ' ' , '!' , '"' , '#' , '$' , '%' , '&' , '\'' , '(' , ')' ,
  '*' , '+' , ',' , '-' , '.' , '/' , '0' , '1' , '2' , '3' ,
  '4' , '5' , '6' , '7' , '8' , '9' , ':' , ';' , '<' , '=' ,
  '>' , '?' , '@' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' ,
  'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' ,
  'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '[' ,
  '\\' , ']' , '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' ,
  'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' ,
  'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' ,
  'z' , '{' , '|' , '}' , '~' , ' '
};
// Initialisation
int const BufSize = 32;
uint8_t Buffer[BufSize];
int BufStart = 0, BufEnd = 0;
unsigned long receiveStart;
// You must connect the phone to this AP, then:
// menu -> connect -> Internet(TCP) -> 192.168.4.1:8880  for UART0
/*************************  COM Port 0 *******************************/
#define UART_BAUD0 9600            // Baudrate UART0
#define SERIAL_PARAM0 SERIAL_7E1    // Data/Parity/Stop UART0
#define SERIAL_PARAM1 SERIAL_8E1    // Data/Parity/Stop UART0
#define SERIAL0_TCP_PORT 8880       // Wifi Port UART0
#define bufferSize 1024
#define MAX_NMEA_CLIENTS 4
#define NUM_COM 1
#define DEBUG_COM 0 // debug output to COM0
//////////////////////////////////////////////////////////////////////////
enum UART_Mode { RECEIVE_MODE, TRANSMIT_MODE };
UART_Mode uartState[NUM_COM] = {TRANSMIT_MODE}; // начальное состояние
#include <WiFiClient.h>
WiFiServer server_0(SERIAL0_TCP_PORT);
WiFiServer *server[NUM_COM] = {&server_0};
WiFiClient TCPClient[NUM_COM][MAX_NMEA_CLIENTS];
HardwareSerial* COM[NUM_COM] = {&Serial};
uint8_t buf1[NUM_COM][bufferSize];
uint16_t i1[NUM_COM] = {0};
//9,10,12,13 чип ESP8266EX.
//GPIO14, GPIO12, GPIO13 и GPIO15
const int NewClient = 14; //New client
const int TRANSMIT = 2;
//const int Data = 12;
void setup() {
  pinMode(NewClient, OUTPUT);
  digitalWrite(NewClient, LOW);
  pinMode(TRANSMIT, OUTPUT);
  digitalWrite(TRANSMIT, LOW);
  //pinMode(Data, OUTPUT);
  COM[0]->begin(UART_BAUD0, SERIAL_PARAM1, SERIAL_TX_ONLY);
  if (debug) COM[DEBUG_COM]->println("\n\nLK8000 WiFi serial bridge V1.00");
  if (debug) COM[DEBUG_COM]->println("Open ESP Access Point mode");
  //AP mode (phone connects directly to ESP) (no router)
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(ip, ip, netmask); // configure ip address for softAP
  WiFi.softAP(ssid, pw); // configure ssid and password for softAP
  if (debug) COM[DEBUG_COM]->println("Starting TCP Server 1");
  if (debug) COM[DEBUG_COM]->println("Starting TCP Server 1");
  server[0]->begin(); // start TCP server
  server[0]->setNoDelay(true);
}
void loop()
{
  for (int num = 0; num < NUM_COM ; num++)
  {
    if (server[num]->hasClient())
    {
      for (byte i = 0; i < MAX_NMEA_CLIENTS; i++) {
        if (!TCPClient[num][i] || !TCPClient[num][i].connected()) {
          if (TCPClient[num][i]) TCPClient[num][i].stop();
          TCPClient[num][i] = server[num]->available();
          continue;
        }
      }
      WiFiClient TmpserverClient = server[num]->available();
      TmpserverClient.stop();
    }
  }
  for (int num = 0; num < NUM_COM ; num++)
  {
    if (COM[num] != NULL)
    {
      for (byte cln = 0; cln < MAX_NMEA_CLIENTS; cln++)
      {
        if (TCPClient[num][cln])
        {
          if (uartState[num] == RECEIVE_MODE) {
            while (COM[num]->available()) {
              uint8_t inChar = COM[num]->read();
              if (inChar != -1) {
                char c = (char)inChar & 0x7F;
                if (c >= 32) {
                  TCPClient[num][cln].print(CharMap[c - 32]);
                  COM[num]->flush();
                } else if (c == '\r' || c == '\n') {
                  TCPClient[num][cln].print(c);
                  COM[num]->flush();
                }
                // '@' и '#' переключают режим, но без break —
                // дочитываем остаток ответа STM32 до конца
                if (c == '@' || c == '#') {
                  uartState[num] = TRANSMIT_MODE;
                }
              }
            }
            // Таймаут: переключаемся в TRANSMIT только если
            // клиент что-то прислал. Иначе ждём дальше.
            if (uartState[num] == RECEIVE_MODE
                && (millis() - receiveStart > 60000)
                && TCPClient[num][cln].available() > 0) {
              uartState[num] = TRANSMIT_MODE;
            }
            if (uartState[num] == TRANSMIT_MODE) {
              COM[num]->end();
              delay(10);
              COM[num]->begin(UART_BAUD0, SERIAL_PARAM1, SERIAL_TX_ONLY);
              delay(50);
            }
          }
          if (uartState[num] == TRANSMIT_MODE) {
            digitalWrite(TRANSMIT, HIGH);
            clearBuffer(num);
            delay(50);
            while (TCPClient[num][cln].available())
            {
              digitalWrite(NewClient, HIGH);
              buf1[num][i1[num]] = TCPClient[num][cln].read();
              TCPClient[num][cln].print(char(buf1[num][i1[num]]));
              if (i1[num] < bufferSize - 1) {
                i1[num]++;
              }
            }
            if (i1[num] > 0) {
              COM[num]->write(buf1[num], i1[num]);
              COM[num]->flush();
              i1[num] = 0;
              digitalWrite(NewClient, LOW);
              uartState[num] = RECEIVE_MODE;
              COM[num]->end();
              delay(10);
              digitalWrite(TRANSMIT, LOW);
              COM[num]->begin(UART_BAUD0, SERIAL_PARAM0, SERIAL_RX_ONLY);
              delay(50);
              receiveStart = millis();
            }
          }
        }
      }
    }
  }
}


void clearBuffer(int num) {
  for (int i = 0; i < bufferSize; i++) {
    buf1[num][i] = 0;
  }
  i1[num] = 0;
}
void putBuf (uint8_t byt) {
  if ((BufStart + BufSize - BufEnd) % BufSize == 1) return; // Buffer full
  Buffer[BufEnd] = byt;
  BufEnd = (BufEnd + 1) % BufSize;
}
char getBuf () {
  if (BufStart == BufEnd) return -1;                    // Buffer empty
  char temp = Buffer[BufStart];
  BufStart = (BufStart + 1) % BufSize;
  return temp;
}
 
Изменено:

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Java:
package com.example.touchChess;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class MainActivity extends Activity {
    private static Context sInstance;
    private int h = 60;
    private int dx = 26;
    private int dy = 22;
    private int touchX, touchY;
    private int XPol, YPol;

    private int selectedX = -1, selectedY = -1;
    private boolean hasSelection = false;

    private static final int STATE_IDLE = 0;
    private static final int STATE_WAITING_CONFIRMATION = 1;
    private static final int STATE_WAITING_ENGINE = 2;
    private int serverState = STATE_IDLE;

    private static final int SPECIAL_NONE = 0;
    private static final int SPECIAL_CASTLING = 1;
    private static final int SPECIAL_EN_PASSANT = 2;
    private int pendingSpecial = SPECIAL_NONE;
    private String pendingMove = "";
    private int pendingFromNum = -1;

    private Bitmap bitmapBackup;
    private Bitmap[] arrayOfBitmap = new Bitmap[12];
    private Bitmap bitmap1, bitmap2, bitmap3, bitmap4, bitmap5;
    private Canvas canvasBmp3, canvasBmp5;
    private Paint paint;
    private DrawView drawView;
    private String StrFen;

    private Socket mSocket;
    private InputStream mInputBuffer;
    private OutputStream mOutputBuffer;
    private Thread mReaderThread;
    private volatile boolean mIsReading = false;
    private String mServerPort = "8880";
    private String mServerAddress = "192.168.4.1";

    private TextView mTextViewContent;
    private ScrollView mScrollViewContent;
    private Button mButtonConnect;
    private Button mButtonSend;
    private EditText mEditTextCommand;

    public static Context getInstance() { return sInstance; }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sInstance = this;
        setContentView(R.layout.activity_main);
        StrFen = getString(R.string.perft0);
        FrameLayout container = findViewById(R.id.boardContainer);
        drawView = new DrawView(this);
        container.addView(drawView);
        mScrollViewContent = findViewById(R.id.scrollViewContent);
        mTextViewContent = findViewById(R.id.textViewContent);
        mButtonConnect = findViewById(R.id.buttonConnect);
        mButtonConnect.requestFocus();
        mButtonSend = findViewById(R.id.buttonSend);
        mEditTextCommand = findViewById(R.id.editTextCommand);
        mButtonConnect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) { new ConnectTask().execute(); }
        });
        mButtonSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String cmd = mEditTextCommand.getText().toString().trim();
                if (cmd.isEmpty()) { ShowAlert("Введите команду"); return; }
                new SendTask().execute(cmd);
            }
        });
    }

    // --- Нотация ---
    private String squareToNotation(int numPol) {
        int file = numPol % 8;
        int rank = numPol / 8;
        return "" + (char) ('a' + file) + (char) ('1' + rank);
    }
    private int notationToSquare(String notation) {
        int file = notation.charAt(0) - 'a';
        int rank = notation.charAt(1) - '1';
        return rank * 8 + file;
    }
    private int[] squareToCell(int square) {
        int file = square % 8;
        int rank = square / 8;
        return new int[]{file, 7 - rank};
    }
    private String extractMoveFromLine(String line) {
        int movIdx = line.indexOf("mov ");
        if (movIdx < 0) return null;
        int start = movIdx + 4;
        if (start + 4 > line.length()) return null;
        return line.substring(start, start + 4);
    }

    // --- Распознавание фигур ---

    // Распознаёт фигуру в bitmap5 (не модифицирует)
    private int identifyPiece() {
        if (!bitmap5HasContent()) return -1;
        int bestMatch = -1;
        int bestScore = 0;
        for (int i = 0; i < 12; i++) {
            int score = compareBitmaps(bitmap5, arrayOfBitmap[i]);
            if (score > bestScore) { bestScore = score; bestMatch = i; }
        }
        int[] refPixels = new int[60 * 60];
        int idx = (bestMatch >= 0) ? bestMatch : 0;
        arrayOfBitmap[idx].getPixels(refPixels, 0, 60, 0, 0, 60, 60);
        int refTotal = 0;
        for (int p : refPixels) { if (p != Color.TRANSPARENT) refTotal++; }
        if (refTotal > 0 && bestScore >= refTotal * 3 / 4) return bestMatch;
        return -1;
    }

    private int compareBitmaps(Bitmap a, Bitmap b) {
        int[] pa = new int[60 * 60];
        int[] pb = new int[60 * 60];
        a.getPixels(pa, 0, 60, 0, 0, 60, 60);
        b.getPixels(pb, 0, 60, 0, 0, 60, 60);
        int match = 0;
        for (int i = 0; i < pa.length; i++) {
            boolean aOp = pa[i] != Color.TRANSPARENT;
            boolean bOp = pb[i] != Color.TRANSPARENT;
            if (aOp && bOp) { if (pa[i] == pb[i]) match++; }
            else if (!aOp && !bOp) { match++; }
        }
        return match;
    }

    // Распознаёт фигуру в клетке bitmap3, НЕ трогая bitmap5
    private int identifyPieceAtSafe(int cellX, int cellY) {
        int srcX = dx + h * cellX;
        int srcY = dy + h * cellY;
        if (srcX < 0 || srcY < 0 || srcX + 60 > bitmap3.getWidth() || srcY + 60 > bitmap3.getHeight()) return -1;

        int[] content = new int[60 * 60];
        int[] boardBg = new int[60 * 60];
        bitmap3.getPixels(content, 0, 60, srcX, srcY, 60, 60);
        bitmap1.getPixels(boardBg, 0, 60, srcX, srcY, 60, 60);

        // Убираем фон
        for (int i = 0; i < content.length; i++) {
            if (content[i] == boardBg[i]) content[i] = Color.TRANSPARENT;
        }

        int bestMatch = -1;
        int bestScore = 0;
        for (int i = 0; i < 12; i++) {
            int[] ref = new int[60 * 60];
            arrayOfBitmap[i].getPixels(ref, 0, 60, 0, 0, 60, 60);
            int match = 0;
            int refTotal = 0;
            for (int j = 0; j < content.length; j++) {
                boolean cOp = content[j] != Color.TRANSPARENT;
                boolean rOp = ref[j] != Color.TRANSPARENT;
                if (cOp && rOp) {
                    refTotal++;
                    if (content[j] == ref[j]) match++;
                } else if (!cOp && !rOp) {
                    refTotal++;
                    match++;
                }
            }
            if (match > bestScore) { bestScore = match; bestMatch = i; }
        }

        int[] refPixels = new int[60 * 60];
        int idx = (bestMatch >= 0) ? bestMatch : 0;
        arrayOfBitmap[idx].getPixels(refPixels, 0, 60, 0, 0, 60, 60);
        int refTotal = 0;
        for (int p : refPixels) { if (p != Color.TRANSPARENT) refTotal++; }
        if (refTotal > 0 && bestScore >= refTotal * 3 / 4) return bestMatch;
        return -1;
    }

    // --- Проверка пустоты ---
    private boolean isCellEmpty(int cellX, int cellY) {
        int srcX = dx + h * cellX;
        int srcY = dy + h * cellY;
        if (srcX < 0 || srcY < 0 || srcX + 60 > bitmap3.getWidth() || srcY + 60 > bitmap3.getHeight()) return true;
        int[] content = new int[60 * 60];
        int[] board = new int[60 * 60];
        bitmap3.getPixels(content, 0, 60, srcX, srcY, 60, 60);
        bitmap1.getPixels(board, 0, 60, srcX, srcY, 60, 60);
        for (int i = 0; i < content.length; i++) {
            if (content[i] != board[i]) return false;
        }
        return true;
    }

    // --- Перемещение ---
    private void movePiece(int fromX, int fromY, int toX, int toY) {
        extractFigure(fromX, fromY);
        clearCell(fromX, fromY);
        clearCell(toX, toY);
        canvasBmp3.drawBitmap(bitmap5, dx + h * toX, dy + h * toY, null);
        canvasBmp5.drawColor(Color.TRANSPARENT);
    }

    // --- Применение хода движка (чёрные) ---
    private void applyMove(String move) {
        if (move == null || move.length() != 4) return;
        int fromSquare = notationToSquare(move.substring(0, 2));
        int toSquare = notationToSquare(move.substring(2, 4));
        int fromFile = fromSquare % 8;
        int fromRank = fromSquare / 8;
        int toFile = toSquare % 8;
        int toRank = toSquare / 8;
        int[] fromCell = squareToCell(fromSquare);
        int[] toCell = squareToCell(toSquare);

        // Определяем тип фигуры ДО перемещения
        int pieceType = identifyPieceAtSafe(fromCell[0], fromCell[1]);

        boolean isCastling = (pieceType == 6
            && Math.abs(toFile - fromFile) == 2 && fromRank == toRank);

        boolean isEnPassant = (pieceType == 11
            && fromFile != toFile && fromRank == 3
            && isCellEmpty(toCell[0], toCell[1]));

        boolean isPromotion = (pieceType == 11
            && toRank == 0);

        if (isCastling) {
            movePiece(fromCell[0], fromCell[1], toCell[0], toCell[1]);
            int rookFromNum = (toSquare > fromSquare) ? fromSquare + 3 : fromSquare - 4;
            int rookToNum = (toSquare > fromSquare) ? fromSquare + 1 : fromSquare - 1;
            int[] rfc = squareToCell(rookFromNum);
            int[] rtc = squareToCell(rookToNum);
            movePiece(rfc[0], rfc[1], rtc[0], rtc[1]);
        } else if (isEnPassant) {
            extractFigure(fromCell[0], fromCell[1]);
            clearCell(fromCell[0], fromCell[1]);
            clearCell(toCell[0], fromCell[1]);
            canvasBmp3.drawBitmap(bitmap5, dx + h * toCell[0], dy + h * toCell[1], null);
            canvasBmp5.drawColor(Color.TRANSPARENT);
        } else if (isPromotion) {
            clearCell(fromCell[0], fromCell[1]);
            clearCell(toCell[0], toCell[1]);
            canvasBmp3.drawBitmap(arrayOfBitmap[7], dx + h * toCell[0], dy + h * toCell[1], null);
            canvasBmp5.drawColor(Color.TRANSPARENT);
        } else {
            movePiece(fromCell[0], fromCell[1], toCell[0], toCell[1]);
        }
    }

    // --- Анализ ответа сервера ---
    private void checkServerResponse() {
        String fullText = mTextViewContent.getText().toString();
        if (serverState == STATE_WAITING_CONFIRMATION) {
            int hashIdx = fullText.lastIndexOf("#");
            int movIdx = fullText.lastIndexOf("mov ");
            if (movIdx >= 0) {
                int lineEnd = fullText.indexOf('\n', movIdx);
                if (lineEnd < 0) lineEnd = fullText.length();
                String movLine = fullText.substring(movIdx, lineEnd).trim();
                if (movLine.contains("w@")) {
                    serverState = STATE_WAITING_ENGINE;
                    mTextViewContent.setText("");
                    mEditTextCommand.setText("0");
                    new SendTask().execute("0");
                    return;
                }
            }
            if (hashIdx >= 0) {
                serverState = STATE_IDLE;
                mTextViewContent.setText("");
                if (bitmapBackup != null) canvasBmp3.drawBitmap(bitmapBackup, 0, 0, null);
                if (drawView != null) drawView.invalidate();
                ShowAlert("Нелегальный ход!");
                return;
            }
        } else if (serverState == STATE_WAITING_ENGINE) {
            int movIdx = fullText.lastIndexOf("mov ");
            if (movIdx >= 0) {
                int lineEnd = fullText.indexOf('\n', movIdx);
                if (lineEnd < 0) lineEnd = fullText.length();
                String movLine = fullText.substring(movIdx, lineEnd).trim();
                if (movLine.contains("b@")) {
                    serverState = STATE_IDLE;
                    mTextViewContent.setText("");
                    String move = extractMoveFromLine(movLine);
                    if (move != null) applyMove(move);
                    if (drawView != null) drawView.invalidate();
                    return;
                }
            }
        }
    }

    // --- Сеть ---
    private boolean isConnected() {
        return mSocket != null && mSocket.isConnected() && !mSocket.isClosed();
    }
    private class ConnectTask extends AsyncTask<Void, Void, Boolean> {
        private String errorMessage = null;
        @Override
        protected Boolean doInBackground(Void... arg0) {
            if (!isConnected()) {
                try {
                    mSocket = new Socket();
                    mSocket.connect(new InetSocketAddress(mServerAddress, Integer.parseInt(mServerPort)), 5000);
                    mInputBuffer = mSocket.getInputStream();
                    mOutputBuffer = mSocket.getOutputStream();
                    startReaderThread();
                    return true;
                } catch (Exception e) {
                    errorMessage = e.getClass().getName() + ": " + (e.getMessage() != null ? e.getMessage() : "no message");
                    return false;
                }
            } else {
                stopReaderThread();
                closeSocket();
                return false;
            }
        }
        @Override
        protected void onPostExecute(Boolean result) {
            updateConnectionStatus(result);
            if (errorMessage != null) ShowAlert(errorMessage);
        }
    }
    private class SendTask extends AsyncTask<String, Void, Boolean> {
        private String errorMessage = null;
        @Override
        protected Boolean doInBackground(String... params) {
            if (!isConnected()) { errorMessage = "Нет соединения"; return false; }
            try {
                mOutputBuffer.write((params[0] + "X").getBytes("UTF-8"));
                mOutputBuffer.flush();
                return true;
            } catch (Exception e) {
                errorMessage = e.getClass().getName() + ": " + (e.getMessage() != null ? e.getMessage() : "no message");
                return false;
            }
        }
        @Override
        protected void onPostExecute(Boolean result) {
            if (!result && errorMessage != null) ShowAlert(errorMessage);
        }
    }
    private void startReaderThread() {
        mIsReading = true;
        mReaderThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (mIsReading && mInputBuffer != null) {
                    try {
                        int available = mInputBuffer.available();
                        if (available > 0) {
                            byte[] buffer = new byte[available];
                            int bytesRead = mInputBuffer.read(buffer);
                            if (bytesRead > 0) {
                                final String s = new String(buffer, 0, bytesRead, "UTF-8")
                                    .replace("\r", "").replaceAll("\n+", "\n");
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (mTextViewContent != null) mTextViewContent.append(s);
                                        if (serverState != STATE_IDLE) checkServerResponse();
                                    }
                                });
                                mScrollViewContent.post(new Runnable() {
                                    @Override
                                    public void run() { mScrollViewContent.fullScroll(View.FOCUS_DOWN); }
                                });
                            }
                        } else { Thread.sleep(50); }
                    } catch (IOException | InterruptedException e) { break; }
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (serverState != STATE_IDLE) {
                            serverState = STATE_IDLE;
                            if (bitmapBackup != null) canvasBmp3.drawBitmap(bitmapBackup, 0, 0, null);
                            if (drawView != null) drawView.invalidate();
                            ShowAlert("Соединение потеряно");
                        }
                        mButtonConnect.setText(R.string.stringConnect);
                    }
                });
            }
        });
        mReaderThread.start();
    }
    private void stopReaderThread() {
        mIsReading = false;
        if (mReaderThread != null) {
            mReaderThread.interrupt();
            try { mReaderThread.join(1000); } catch (InterruptedException ignored) {}
            mReaderThread = null;
        }
    }
    private void closeSocket() {
        try { if (mSocket != null && !mSocket.isClosed()) mSocket.close(); } catch (IOException ignored) {}
        mSocket = null;
        mInputBuffer = null;
        mOutputBuffer = null;
    }
    private void updateConnectionStatus(boolean connected) {
        mButtonConnect.setText(connected ? R.string.stringDisconnect : R.string.stringConnect);
    }

    // --- Шахматы ---
    private void prepareBoard() {
        canvasBmp3.drawBitmap(bitmap1, 0, 0, null);
        parsingStrFen(StrFen, canvasBmp3);
    }
    private void clearCell(int cellX, int cellY) {
        int x = dx + h * cellX;
        int y = dy + h * cellY;
        if (x < 0 || y < 0 || x + 60 > bitmap3.getWidth() || y + 60 > bitmap3.getHeight()) return;
        canvasBmp3.drawBitmap(bitmap1, new Rect(x, y, x + 60, y + 60), new Rect(x, y, x + 60, y + 60), null);
    }
    private void extractFigure(int cellX, int cellY) {
        int srcX = dx + h * cellX;
        int srcY = dy + h * cellY;
        if (srcX < 0 || srcY < 0 || srcX + 60 > bitmap3.getWidth() || srcY + 60 > bitmap3.getHeight()) return;
        canvasBmp5.drawColor(Color.TRANSPARENT);
        canvasBmp5.drawBitmap(bitmap3, new Rect(srcX, srcY, srcX + 60, srcY + 60), new Rect(0, 0, 60, 60), null);
        Bitmap cellMask = Bitmap.createBitmap(bitmap1, srcX, srcY, 60, 60);
        int[] pixels = new int[60 * 60];
        bitmap5.getPixels(pixels, 0, 60, 0, 0, 60, 60);
        int[] maskPixels = new int[60 * 60];
        cellMask.getPixels(maskPixels, 0, 60, 0, 0, 60, 60);
        for (int i = 0; i < pixels.length; i++) {
            if (pixels[i] == maskPixels[i]) pixels[i] = Color.TRANSPARENT;
        }
        bitmap5.setPixels(pixels, 0, 60, 0, 0, 60, 60);
    }
    private boolean bitmap5HasContent() {
        int[] pixels = new int[60 * 60];
        bitmap5.getPixels(pixels, 0, 60, 0, 0, 60, 60);
        for (int p : pixels) { if (p != Color.TRANSPARENT) return true; }
        return false;
    }
    private void initPiece() {
        for (int i = 0; i < 6; i++) {
            arrayOfBitmap[i] = Bitmap.createBitmap(bitmap2, i * 60, 0, 60, 60);
            arrayOfBitmap[i + 6] = Bitmap.createBitmap(bitmap2, i * 60, 60, 60, 60);
        }
    }
    public boolean parsingStrFen(String str, Canvas canvas) {
        String s1 = "KQRBNPkqrbnp";
        String s2 = "12345678";
        int n = 0, k = 0, king = 0;
        int r = str.indexOf(' ');
        if (r <= 0) return false;
        String sub = str.substring(0, r);
        for (int i = 0; i < sub.length(); i++) {
            String s = sub.substring(i, i + 1);
            if (s.equals("/")) {
                if (n != 8) ShowAlert("ERROR");
                n = 0; k++; continue;
            }
            if (s2.indexOf(s) >= 0) n += 1 + s2.indexOf(s);
            int z = s1.indexOf(s);
            if (z >= 0) {
                if (z == 0) king++;
                if (z == 6) king--;
                canvas.drawBitmap(arrayOfBitmap[z], dx + h * n, dy + h * k, paint);
                n++;
            }
        }
        if (king != 0) ShowAlert("ERROR");
        return true;
    }

    // --- Диалоги ---
    public static void showAlert(Context context, String title, String message, View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        if (view == null) builder.setMessage(message);
        else builder.setView(view);
        builder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); }
        });
        builder.show();
    }
    public void ShowAlert(String str) {
        Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onBackPressed() {
        stopReaderThread();
        closeSocket();
        mButtonConnect.setText(R.string.stringConnect);
        mTextViewContent.setText(R.string.waiting_esp);
        mEditTextCommand.setText("");
        serverState = STATE_IDLE;
        pendingSpecial = SPECIAL_NONE;
        pendingMove = "";
        pendingFromNum = -1;
        hasSelection = false;
        selectedX = -1;
        selectedY = -1;
        ShowAlert("Выход");
        super.onBackPressed();
    }

    @Override
    protected void onDestroy() {
        stopReaderThread();
        closeSocket();
        if (bitmapBackup != null) { bitmapBackup.recycle(); bitmapBackup = null; }
        if (bitmap1 != null) { bitmap1.recycle(); bitmap1 = null; }
        if (bitmap2 != null) { bitmap2.recycle(); bitmap2 = null; }
        if (bitmap3 != null) { bitmap3.recycle(); bitmap3 = null; }
        if (bitmap4 != null) { bitmap4.recycle(); bitmap4 = null; }
        if (bitmap5 != null) { bitmap5.recycle(); bitmap5 = null; }
        for (int i = 0; i < 12; i++) {
            if (arrayOfBitmap[i] != null) { arrayOfBitmap[i].recycle(); arrayOfBitmap[i] = null; }
        }
        super.onDestroy();
    }
 

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Java:
   // --- Отрисовка ---
    class DrawView extends View {
        public DrawView(Context context) {
            super(context);
            paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.board);
            bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.peice);
            bitmap4 = Bitmap.createBitmap(bitmap2, 360, 0, 60, 60);
            bitmap3 = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), Bitmap.Config.ARGB_8888);
            bitmap5 = Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888);
            canvasBmp3 = new Canvas(bitmap3);
            canvasBmp5 = new Canvas(bitmap5);
            initPiece();
            prepareBoard();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawARGB(80, 102, 204, 255);
            canvas.drawBitmap(bitmap3, 0, 0, paint);
            if (hasSelection) {
                canvas.drawBitmap(bitmap4, dx + h * selectedX, dy + h * selectedY, null);
            }
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() != MotionEvent.ACTION_DOWN) return true;
            if (serverState != STATE_IDLE) return true;

            touchX = (int) event.getX();
            touchY = (int) event.getY();
            if (touchX < dx || touchX > dx + 8 * h) return true;
            if (touchY < dy || touchY > dy + 8 * h) return true;

            XPol = (touchX - dx) / h;
            YPol = (touchY - dy) / h;

            // === ВТОРОЙ ХОД ОСОБОГО ХОДА ===
            if (pendingSpecial != SPECIAL_NONE) {
                if (!hasSelection) {
                    extractFigure(XPol, YPol);
                    if (bitmap5HasContent()) {
                        selectedX = XPol;
                        selectedY = YPol;
                        hasSelection = true;
                    } else {
                        ShowAlert("Клетка пуста");
                    }
                } else {
                    if (XPol == selectedX && YPol == selectedY) {
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                        canvasBmp5.drawColor(Color.TRANSPARENT);
                        ShowAlert("Снято выделение");
                    } else if (pendingSpecial == SPECIAL_CASTLING) {
                        movePiece(selectedX, selectedY, XPol, YPol);
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                        pendingSpecial = SPECIAL_NONE;
                        mEditTextCommand.setText(pendingMove);
                        if (isConnected()) {
                            serverState = STATE_WAITING_CONFIRMATION;
                            new SendTask().execute(pendingMove);
                        } else { ShowAlert("Нет соединения"); }
                    } else if (pendingSpecial == SPECIAL_EN_PASSANT) {
                        movePiece(selectedX, selectedY, XPol, YPol);
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                        pendingSpecial = SPECIAL_NONE;
                        int finalToNum = 63 - ((7 - XPol) + 8 * YPol);
                        String move = squareToNotation(pendingFromNum) + squareToNotation(finalToNum);
                        mEditTextCommand.setText(move);
                        if (isConnected()) {
                            serverState = STATE_WAITING_CONFIRMATION;
                            new SendTask().execute(move);
                        } else { ShowAlert("Нет соединения"); }
                    }
                }
                invalidate();
                return true;
            }

            // === ОБЫЧНАЯ ЛОГИКА ===
            if (!hasSelection) {
                extractFigure(XPol, YPol);
                if (bitmap5HasContent()) {
                    selectedX = XPol;
                    selectedY = YPol;
                    hasSelection = true;
                } else {
                    ShowAlert("Клетка пуста");
                }
            } else {
                if (XPol == selectedX && YPol == selectedY) {
                    hasSelection = false;
                    selectedX = -1;
                    selectedY = -1;
                    canvasBmp5.drawColor(Color.TRANSPARENT);
                    ShowAlert("Снято выделение");
                } else {
                    int fromNum = 63 - ((7 - selectedX) + 8 * selectedY);
                    int toNum   = 63 - ((7 - XPol) + 8 * YPol);
                    String move = squareToNotation(fromNum) + squareToNotation(toNum);

                    if (bitmapBackup != null) bitmapBackup.recycle();
                    bitmapBackup = bitmap3.copy(Bitmap.Config.ARGB_8888, true);

                    int fromFile = fromNum % 8;
                    int fromRank = fromNum / 8;
                    int toFile = toNum % 8;
                    int toRank = toNum / 8;

                    // Распознаём фигуру в bitmap5 (там уже вырезанная фигура)
                    int pieceType = identifyPiece();

                    // --- Рокировка: белый король (0) на 2 клетки ---
                    boolean isCastling = (pieceType == 0
                        && Math.abs(toFile - fromFile) == 2
                        && fromRank == toRank);

                    // --- Взятие на проходе: белая пешка (5), 5-я горизонталь,
                    // ход на соседнюю вертикаль по той же горизонтали,
                    // на целевой клетке — чёрная пешка (11) ---
                    boolean isEnPassant = (pieceType == 5
                        && Math.abs(toFile - fromFile) == 1
                        && fromRank == toRank
                        && fromRank == 4
                        && identifyPieceAtSafe(XPol, YPol) == 11);

                    // --- Превращение: белая пешка (5) на 8-ю горизонталь ---
                    boolean isPromotion = (pieceType == 5
                        && toRank == 7);

                    if (isCastling) {
                        int rookFromNum = (toNum > fromNum) ? fromNum + 3 : fromNum - 4;
                        int[] rookFromCell = squareToCell(rookFromNum);
                        int rookPiece = identifyPieceAtSafe(rookFromCell[0], rookFromCell[1]);

                        boolean pathClear = true;
                        if (toNum > fromNum) {
                            int[] s1 = squareToCell(fromNum + 1);
                            int[] s2 = squareToCell(fromNum + 2);
                            if (!isCellEmpty(s1[0], s1[1]) || !isCellEmpty(s2[0], s2[1])) pathClear = false;
                        } else {
                            int[] s1 = squareToCell(fromNum - 1);
                            int[] s2 = squareToCell(fromNum - 2);
                            int[] s3 = squareToCell(fromNum - 3);
                            if (!isCellEmpty(s1[0], s1[1]) || !isCellEmpty(s2[0], s2[1]) || !isCellEmpty(s3[0], s3[1])) pathClear = false;
                        }

                        if (rookPiece != 2 || !pathClear) {
                            canvasBmp3.drawBitmap(bitmapBackup, 0, 0, null);
                            ShowAlert("Условия рокировки не выполнены");
                            hasSelection = false;
                            selectedX = -1;
                            selectedY = -1;
                        } else {
                            clearCell(selectedX, selectedY);
                            clearCell(XPol, YPol);
                            canvasBmp3.drawBitmap(bitmap5, dx + h * XPol, dy + h * YPol, null);
                            canvasBmp5.drawColor(Color.TRANSPARENT);
                            pendingSpecial = SPECIAL_CASTLING;
                            pendingMove = move;
                            ShowAlert("Сделайте ход ладьёй");
                            hasSelection = false;
                            selectedX = -1;
                            selectedY = -1;
                        }
                    } else if (isEnPassant) {
                        clearCell(XPol, selectedY);
                        clearCell(selectedX, selectedY);
                        canvasBmp3.drawBitmap(bitmap5, dx + h * XPol, dy + h * selectedY, null);
                        canvasBmp5.drawColor(Color.TRANSPARENT);
                        pendingSpecial = SPECIAL_EN_PASSANT;
                        pendingFromNum = fromNum;
                        ShowAlert("Сделайте ход пешкой");
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                    } else if (isPromotion) {
                        clearCell(selectedX, selectedY);
                        clearCell(XPol, YPol);
                        canvasBmp3.drawBitmap(arrayOfBitmap[1], dx + h * XPol, dy + h * YPol, null);
                        canvasBmp5.drawColor(Color.TRANSPARENT);
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                        mEditTextCommand.setText(move);
                        if (isConnected()) {
                            serverState = STATE_WAITING_CONFIRMATION;
                            new SendTask().execute(move);
                        } else { ShowAlert("Нет соединения"); }
                    } else {
                        clearCell(selectedX, selectedY);
                        clearCell(XPol, YPol);
                        canvasBmp3.drawBitmap(bitmap5, dx + h * XPol, dy + h * YPol, null);
                        canvasBmp5.drawColor(Color.TRANSPARENT);
                        hasSelection = false;
                        selectedX = -1;
                        selectedY = -1;
                        mEditTextCommand.setText(move);
                        if (isConnected()) {
                            serverState = STATE_WAITING_CONFIRMATION;
                            new SendTask().execute(move);
                        } else { ShowAlert("Нет соединения"); }
                    }
                }
            }
            invalidate();
            return true;
        }
    }
}
 

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Кстати, заметил для себя такую вещь, когда переключаюсь на другую задачу, то потом нужно время, чтобы
сосредоточится на предыдущей задаче, в которую мог внести изменения и забыть об этом.
Короче вот STM32 с движком.
Еще момент
Безымянный 3.png
Micro-Max является общественным достоянием, поэтому вы можете использовать его для любых целей, которые вам нравятся.
 

Вложения

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Я подумал - раз не кто не хочет заморачиваться с реальным железом, - сделать виртуальную связку STM-ESP на Питон.
Python:
import socket
import chess
import chess.engine
import time
# ====================== НАСТРОЙКИ ======================
HOST = ''
PORT = 8880
STOCKFISH_PATH = r"C:\telnet_server\stockfish-windows-2022-x86-64-modern.exe"
ENGINE_DEPTH = 10
ENGINE_SKILL = 20
QUEEN_ONLY_PROMOTION = True
SEND_DELAY = 0.3   # пауза между отправкой хода белых и хода чёрных (сек)

def get_local_ip():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        try:
            s.connect(("8.8.8.8", 80))
            return s.getsockname()[0]
        except Exception:
            return "127.0.0.1"

def parse_move(raw, board):
    try:
        move = chess.Move.from_uci(raw)
        if move in board.legal_moves:
            return move
    except ValueError:
        pass
    if len(raw) == 4:
        try:
            move = chess.Move.from_uci(raw + "q")
            if move in board.legal_moves:
                return move
        except ValueError:
            pass
    return chess.Move.from_uci(raw)

def strip_promotion(move_uci):
    if len(move_uci) == 5:
        return move_uci[:4]
    return move_uci

def main():
    print("=== CHESS SERVER WITH STOCKFISH (STM32 protocol) ===")
    local_ip = get_local_ip()
    print(f"Server address: {local_ip}")
    print(f"Port: {PORT}")

    try:
        engine = chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH)
        engine.configure({"Skill Level": ENGINE_SKILL})
        print(f"Engine: {engine.id.get('name', 'unknown')}")
        print(f"Depth: {ENGINE_DEPTH}, Skill: {ENGINE_SKILL}")
        print(f"Queen-only promotion: {QUEEN_ONLY_PROMOTION}")
        print(f"Send delay: {SEND_DELAY}s")
    except Exception as e:
        print(f"ERROR loading engine: {e}")
        return

    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((HOST, PORT))
    s.listen(1)
    print(f"Listening on {local_ip}:{PORT}. Waiting for client...")

    while True:
        conn, addr = s.accept()
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        print(f"\nClient connected: {addr}")
        board = chess.Board()

        while True:
            try:
                data = conn.recv(1024)
                if not data:
                    print("Client disconnected.")
                    break

                raw = data.decode("utf-8", errors="ignore").strip()
                if raw.endswith("X"):
                    raw = raw[:-1]

                # Игнорируем служебные сообщения
                if raw == "0" or raw == "":
                    continue

                print(f"RECV: {raw}")

                try:
                    move = parse_move(raw, board)
                except ValueError:
                    resp = "#ILLEGAL:bad_format\r\n"
                    print(f"SEND: {resp.strip()}")
                    conn.sendall(resp.encode("ascii"))
                    continue

                if move not in board.legal_moves:
                    resp = "#ILLEGAL:illegal_move\r\n"
                    print(f"SEND: {resp.strip()}")
                    conn.sendall(resp.encode("ascii"))
                    continue

                board.push(move)
                white_str = strip_promotion(move.uci()) if QUEEN_ONLY_PROMOTION else move.uci()
                print(f"White: {move.uci()}")

                # 1. Дубликат хода белых
                resp_white = f"mov {white_str}w@\r\n"
                conn.sendall(resp_white.encode("ascii"))
                print(f"SEND: {resp_white.strip()}")

                if board.is_game_over():
                    result = board.result()
                    resp = f"#GAMEOVER:{result}\r\n"
                    conn.sendall(resp.encode("ascii"))
                    print(f"Game over: {result}")
                    break

                # 2. Пауза — даём клиенту обработать ход белых
                time.sleep(SEND_DELAY)

                # 3. Движок думает
                limit = chess.engine.Limit(depth=ENGINE_DEPTH)
                result = engine.play(board, limit)
                engine_move = result.move.uci()

                board.push(result.move)
                black_str = strip_promotion(engine_move) if QUEEN_ONLY_PROMOTION else engine_move
                print(f"Black: {engine_move}")

                # 4. Ход чёрных
                resp_black = f"mov {black_str}b@\r\n"
                conn.sendall(resp_black.encode("ascii"))
                print(f"SEND: {resp_black.strip()}")

                if board.is_game_over():
                    result_str = board.result()
                    print(f"Game over: {result_str}")

            except ConnectionResetError:
                print("Connection reset by client.")
                break
            except Exception as e:
                print(f"Error: {e}")
                break

        conn.close()
        print("Waiting for new client...")

    engine.quit()

if __name__ == "__main__":
    main()
А Андроид клиент будет имитировать физическую доску

Тогда можно будет думать о логике на физической доске, где важно следующее - каждое перемещение фигуры (поднял - опустил)влияет на герконы и тут нужна особая логика - как для особых ходов, да и для простых ходов со взятием. А также для ответных ходов, которые должен выполнить пользователь за противника. Короче это будет следующий этап и если кто-то поможет было бы неплохо))
chess_server_stm32.png
 

Вложения

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Пока паять не на чем - покрутил Питоном, тудым-сюдым.
chess_server_stm32_gui_v2.png

Если порт 8880 занят - прибить процессы
Код:
for /f "tokens=5" %i in ('netstat -ano ^| findstr :8880') do taskkill /PID %i /F
Для первого раза с Питоном неплохо получилось))
 

Вложения

Изменено:

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Еще немного помучал ИИ
Теперь ESP мост-клиент между сервером-питон и движком-STM
На сервере движок-stockfish-windows-2022-x86-64-modern.exe
Игра проходит автоматически, без моего участия
chess_bridge_client.png
C:
#include <ESP8266WiFi.h>

// config: ////////////////////////////////////////////////////////////
#define VERSION "2.00"

// WiFi STA config
const char *ssid = "????";
const char *pw = "????";

// Адрес Python-сервера (ПК, где запущен chess_server)
const char *serverHost = "192.168.3.7";
const int serverPort = 8880;

const char CharMap[96] = {
  ' ' , '!' , '"' , '#' , '$' , '%' , '&' , '\'' , '(' , ')' ,
  '*' , '+' , ',' , '-' , '.' , '/' , '0' , '1' , '2' , '3' ,
  '4' , '5' , '6' , '7' , '8' , '9' , ':' , ';' , '<' , '=' ,
  '>' , '?' , '@' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' ,
  'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' ,
  'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '[' ,
  '\\' , ']' , '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' ,
  'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' ,
  'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' ,
  'z' , '{' , '|' , '}' , '~' , ' '
};

// Initialisation
int const BufSize = 32;
uint8_t Buffer[BufSize];
int BufStart = 0, BufEnd = 0;
unsigned long receiveStart;

/*************************  COM Port 0 *******************************/
#define UART_BAUD0 9600
#define SERIAL_PARAM0 SERIAL_7E1    // Приём от STM32: 7 бит, чётность, 1 стоп
#define SERIAL_PARAM1 SERIAL_8E1    // Передача в STM32: 8 бит, чётность, 1 стоп
#define bufferSize 1024
#define NUM_COM 1

//////////////////////////////////////////////////////////////////////////
enum UART_Mode { RECEIVE_MODE, TRANSMIT_MODE };
UART_Mode uartState[NUM_COM] = {TRANSMIT_MODE};

#include <WiFiClient.h>
WiFiClient client;
HardwareSerial* COM[NUM_COM] = {&Serial};
uint8_t buf1[NUM_COM][bufferSize];
uint16_t i1[NUM_COM] = {0};

const int NewClient = 14;  // GPIO14 — сигнал подключения к серверу
const int TRANSMIT = 2;    // GPIO2 — направление RS485

unsigned long lastReconnectAttempt = 0;

void setup() {
  pinMode(NewClient, OUTPUT);
  digitalWrite(NewClient, LOW);
  pinMode(TRANSMIT, OUTPUT);
  digitalWrite(TRANSMIT, LOW);

  COM[0]->begin(UART_BAUD0, SERIAL_PARAM1, SERIAL_TX_ONLY);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pw);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(NewClient, LOW);
    WiFi.begin(ssid, pw);
    delay(2000);
    return;
  }

  if (!client.connected()) {
    digitalWrite(NewClient, LOW);
    unsigned long now = millis();
    if (now - lastReconnectAttempt > 3000) {
      lastReconnectAttempt = now;
      if (client.connect(serverHost, serverPort)) {
        digitalWrite(NewClient, HIGH);
        uartState[0] = TRANSMIT_MODE;
      }
    }
    delay(100);
    return;
  }

  // --- Мост TCP <-> UART ---

  if (uartState[0] == RECEIVE_MODE) {
    while (COM[0]->available()) {
      uint8_t inChar = COM[0]->read();
      if (inChar != (uint8_t)-1) {
        char c = (char)inChar & 0x7F;
        if (c >= 32) {
          client.print(CharMap[c - 32]);
          COM[0]->flush();
        } else if (c == '\r' || c == '\n') {
          client.print(c);
          COM[0]->flush();
        }
        if (c == '@' || c == '#') {
          uartState[0] = TRANSMIT_MODE;
          COM[0]->end();
          delay(10);
          COM[0]->begin(UART_BAUD0, SERIAL_PARAM1, SERIAL_TX_ONLY);
          delay(50);
        }
      }
    }
    if (uartState[0] == RECEIVE_MODE
        && (millis() - receiveStart > 60000)
        && client.available() > 0) {
      uartState[0] = TRANSMIT_MODE;
      COM[0]->end();
      delay(10);
      COM[0]->begin(UART_BAUD0, SERIAL_PARAM1, SERIAL_TX_ONLY);
      delay(50);
    }
  }

  if (uartState[0] == TRANSMIT_MODE) {
    digitalWrite(TRANSMIT, HIGH);
    clearBuffer(0);
    delay(50);

    while (client.available()) {
      buf1[0][i1[0]] = client.read();
      if (i1[0] < bufferSize - 1) {
        i1[0]++;
      }
    }

    if (i1[0] > 0) {
      COM[0]->write(buf1[0], i1[0]);
      COM[0]->flush();
      i1[0] = 0;
      uartState[0] = RECEIVE_MODE;

      COM[0]->end();
      delay(10);
      digitalWrite(TRANSMIT, LOW);
      COM[0]->begin(UART_BAUD0, SERIAL_PARAM0, SERIAL_RX_ONLY);
      delay(50);
      receiveStart = millis();
    } else {
      digitalWrite(TRANSMIT, LOW);
    }
  }
}

void clearBuffer(int num) {
  for (int i = 0; i < bufferSize; i++) {
    buf1[num][i] = 0;
  }
  i1[num] = 0;
}
lichess-game-m0cq9ZGH-white.gif
 

Вложения

Изменено:

alexlaw

✩✩✩✩✩✩✩
3 Янв 2020
120
3
Воронеж
Вот мы с ИИ и добрались до моей реализации Шахматного движка на STM
Выполнил его рекомендации
Код:
Сводка исправлений в файле
FIX    Что    Строка
1    HistoryBoards[48][64] удалён — сэкономлено 3072 байт SRAM    объявления + цикл обнуления
2    CopyBoard(), PrintResult(), parse_move() удалены — не вызывались    весь файл
3    Fifty, UnderProm, Result, HistPtr, STATE, user_id удалены    объявления
4    d<98 → d<20 — prevents stack overflow    в D()
5    &box2==selected_box & half_m<2 → && half_m < 2    в главном цикле
6    level: 8000+2001 → 5000+6001, эндшпиль 13000 → 18000    в главном цикле
7    int j; → int j = 0; — был UB    в main()
8    Все закомментированные блоки удалены  
Не забудьте поменять heap в .icf: __ICFEDIT_size_heap__ = 0x100 (256 байт вместо 2048). Стек 0x800 уже стоит.
Linker.png
main.c:
/* ========================================================================== */
/*                                                                          */
/*   microMax 4.1++ ver 6 — cleaned & fixed for STM32F103C6T6                */
/*   (c) 2026 Author: alexlaw@list.ru                                       */
/*   Original engine by H. G. Muller                                        */
/*                                                                          */
/*   CHANGES FROM ORIGINAL (marked with FIX: in comments):                  */
/*   FIX 1: Removed HistoryBoards[48][64] — saved 3072 bytes SRAM           */
/*   FIX 2: Removed unused CopyBoard(), PrintResult(), parse_move()          */
/*   FIX 3: Removed unused vars: Fifty, UnderProm, Result, HistPtr, etc.    */
/*   FIX 4: d<98 -> d<20 — prevents stack overflow on 10KB SRAM             */
/*   FIX 5: Fixed box2 bug: & had higher precedence than ||                  */
/*   FIX 6: Wider level range: 5000-11000 middlegame, 18000 endgame          */
/*   FIX 7: Initialized j=0 in main() — was used as j++ uninitialized        */
/*   FIX 8: Removed all commented-out dead code                             */
/*                                                                          */
/* ========================================================================== */

#include "core.h"
#include "gpio.h"
#include "uart.h"
#include "main.h"
#include "spi.h"
#include "stm32f1xx.h"
#include "string.h"
#include "stdio.h"
#include <stdlib.h>

/* ------------------------------------------------------------------ */
/* GLOBAL VARIABLES                                                    */
/* ------------------------------------------------------------------ */

uint32_t TimingDelay;
uint32_t TimingRnd = 0;
uint8_t rc_data[rc_data_size];
uint8_t sd;
uint8_t cnt;
int k = 8;
int *p;
int c[9];
char pos[2];
char mov[6];
char deb[6];
char deb1[6];
char fen[86];
volatile char rx_char;
volatile uint8_t enter;
volatile uint8_t bl = 0;
volatile uint8_t ret = 0;
volatile int level = 13000;
int half_m = 0;
int side;
int one;

/* LED matrix digits 0-9 for MAX7219 (animation during thinking) */
const uint8_t charData[][8] = {
    {0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00},  /* 0 */
    {0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00},  /* 1 */
    {0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e, 0x00},  /* 2 */
    {0x1c, 0x22, 0x20, 0x1c, 0x20, 0x22, 0x1c, 0x00},  /* 3 */
    {0x20, 0x30, 0x28, 0x24, 0x7c, 0x20, 0x20, 0x00},  /* 4 */
    {0x3e, 0x02, 0x1e, 0x20, 0x20, 0x20, 0x1e, 0x00},  /* 5 */
    {0x18, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c, 0x00},  /* 6 */
    {0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04, 0x00},  /* 7 */
    {0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00},  /* 8 */
    {0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0c, 0x00},  /* 9 */
};

/* ------------------------------------------------------------------ */
/* RANDOM OPENING BOOK (BlackBox)                                      */
/* ------------------------------------------------------------------ */

#define BOX_SIZE 5

typedef struct {
    int id;
    int balls[BOX_SIZE];
} BlackBox;

/* Box1: responses to e2e4 (25668), Box2: responses to d2d4 (25411) */
/* Moves encoded as 256*from + to in 0x88 square notation */
BlackBox box1 = {25668, {4899, 1573, 4658, 5156, 5172}};  /* d7d6 g8f6 c7c5 e7e6 e7e5 */
BlackBox box2 = {25411, {4899, 1573, 4915, 5156,  290}};  /* d7d6 g8f6 d7d5 e7e6 b8c6 */

static BlackBox *selected_box = NULL;
int random_index;
int drawn_ball;

/* ------------------------------------------------------------------ */
/* MICRO-MAX CHESS ENGINE (H. G. Muller)                               */
/* ------------------------------------------------------------------ */

int StartKey;

#define EMPTY 0
#define WHITE 8
#define BLACK 16

#define PACK_MOVE 256*K + L;
#define UNPACK_MOVE(A) K = (A)>>8 & 255; L = (A) & 255;

/* FIX 1: HistoryBoards removed — was 3072 bytes, never called */
/* FIX 3: Fifty, UnderProm, Result, HistPtr, STATE removed */

#define GH 48
int Side;
int GameHistory[GH];
int GamePtr;

#define W while
#define K(A,B) [I](int[/I])(T+A+(B&8)+S*(B&7))
#define J(A) K(y+A,b[y])-K(x+A,u)-K(H+A,t)

#define U (1<<8)
struct _ {int K,V;char X,Y,D;} A[U]; /* hash table */

int M=136, S=128, I=8e3, C=799, Q, O, K, N, R, J, Z, E1; /* M=0x88 */
char L,
w[]={0,1,1,3,-1,3,5,9},   /* relative piece values */
o[]={-16,-15,-17,0,1,16,0,1,16,15,17,0,14,18,31,33,0,
     7,-1,11,6,8,3,6,
     6,3,5,7,4,5,3,6},    /* initial piece setup */
b[129],                   /* board: half of 16x8+dummy */
T[1035],                  /* hash translation table */
n[]=".?+nkbrq?[I]?NKBRQ";   /[/I] piece symbols on printout */

/* ------------------------------------------------------------------ */
/* D() — recursive minimax search                                      */
/* k=moving side, (q,l)=window, e=eval score, E=e.p. sqr               */
/* z=prev.dest, n=depth, J,Z=hashkeys                                  */
/* ------------------------------------------------------------------ */

int D(k,q,l,e,J,Z,E,z,n)
int k,q,l,e,J,Z,E,z,n;
{
    int j,r,m,v,d,h,i,F,G,s,f=J,g=Z;
    char t,p,u,x,y,X,Y,H,B;
    struct _*a=A+(J+k*E&U-1);

    q--;
    d=a->D; m=a->V; X=a->X; Y=a->Y;
    if(a->K-Z|z|
       !(m<=q|X&8&&m>=l|X&S))
        d=Y=0;
    X&=~M;

    E1=E;
    W(d++<n||
      /* FIX 4: d<98 changed to d<20 to prevent stack overflow */
      z==8&K==I&&(N<level&d<20||
      (K=X,L=Y&~M,d=2)))
    {
        x=B=X;
        h=Y&S;
        m=d>1?-I:e;
        N++;
        do{u=b[x];
            if(u&k)
            {r=p=u&7;
                j=o[p+16];
                W(r=p>2&r<0?-r:-o[++j])
                {
                  A:
                    y=x; F=G=S;
                    do{
                        H=y=h?Y^h:y+r;
                        if(y&M)break;
                        m=E-S&b[E]&&y-E<2&E-y<2?I:m;
                        if(p<3&y==E)H^=16;
                        t=b[H]; if(t&k|p<3&!(y-x&7)-!t)break;
                        i=99*w[t&7];
                        m=i<0?I:m;
                        if(m>=l)goto C;

                        if(s=d-(y!=z))
                        {v=p<6?b[x+8]-b[y+8]:0;
                            b[G]=b[H]=b[x]=0; b[y]=u|32;
                            if(!(G&M))b[F]=k+6, v+=50;
                            v-=p-4|R>29?0:20;
                            if(p<3)
                            {v-=9*((x-2&M||b[x-2]-u)+
                                   (x+2&M||b[x+2]-u)-1
                                   +(b[x^16]==k+36));
                                if(y+r+1&S)b[y]|=7, i+=C;
                            }
                            J+=J(0); Z+=J(8)+G-S;
                            v+=e+i; f=m>q?m:q;
                            v=s>1|v>f?-D(24-k,-l,-f,-v,
                              J+J(0),Z+J(8)+G-S,F,y,s):v;
                            if(z&8&&K-I)
                            {if(v+I&&x==K&y==L)
                                {Q=-e-i; O=F;
                                    R+=i>>7; return l;
                                }
                                v=m;
                            }
                            J=f; Z=g;
                            b[G]=k+6; b[F]=b[y]=0; b[x]=u; b[H]=t;
                            if(v>m)
                                m=v, X=x, Y=y|S&F;
                            if(h){h=0; goto A;}
                        }
                        if(x+r-y|u&32|
                           p>2&(p-4|j-7||
                                b[G=x+3^r>>1&7]-k-6
                                ||b[G^1]|b[G^2])
                           )t+=p<5;
                        else F=y;
                    }W(!t);
                }
            }
        }W((x=x+9&~M)-B);
      C:
        if(m>I-M|m<M-I)d=99;
        m=m+I?m:-D(24-k,-I,I,0,J,Z,S,S,1);
        a->K=Z; a->V=m; a->D=d;
        a->X=X|8*(m>q)|S*(m<l); a->Y=Y;

        if(z==8)
        {
            GPIOB->ODR ^= GPIO_ODR_ODR11;
            for (int i=1; i<=8; i++)
                write_reg(i, charData[cnt][8-i]);
            cnt++;
            if (cnt>9) cnt=0;
        }
    }
    return m+=m<e;
}

/* ------------------------------------------------------------------ */
/* DELAY FUNCTIONS                                                     */
/* ------------------------------------------------------------------ */

void delayMs(int n) {
    int i;
    for (; n > 0; n--)
        for (i = 0; i < 3195; i++) ;
}

void SysTick_Init(void)
{
    if (SysTick_Config(SystemCoreClock / 100000))
    {
        while (1);
    }
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

void Delay_us(uint32_t nTime)
{
    TimingDelay = nTime;
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;
    while(TimingDelay != 0);
}

void TimingDelay_Decrement(void)
{
    if (TimingDelay != 0x00)
    {
        TimingDelay--;
    }
    else
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

/* ------------------------------------------------------------------ */
/* BOARD TO FEN — exports current position as FEN string              */
/* Note: castling rights output is a stub ("-")                        */
/* ------------------------------------------------------------------ */

void board_to_fen() {
    char *ptr = fen;
    char piece;
    int emptyCount = 0;
    N=-1; W(++N<121) {
        piece = N&8&&(N+=7)?10:n[b[N]&15];
        if (ptr >= fen + 86) {*ptr = '\0'; return;}
        if (piece == '?') continue;
        switch (piece) {
            case 10:
                if (emptyCount > 0) {
                    *ptr++ = emptyCount + '0';
                    emptyCount = 0;
                }
                if (N < 112) *ptr++ = '/';
                break;
            case '.':
                emptyCount++;
                break;
            case '+':
                if (emptyCount > 0) {
                    *ptr++ = emptyCount + '0';
                    emptyCount = 0;
                }
                *ptr++ = 'p';
                break;
            case '*':
                if (emptyCount > 0) {
                    *ptr++ = emptyCount + '0';
                    emptyCount = 0;
                }
                *ptr++ = 'P';
                break;
            default:
                if (emptyCount > 0) {
                    *ptr++ = emptyCount + '0';
                    emptyCount = 0;
                }
                *ptr++ = piece;
                break;
        }
    }
    if (k==8) ptr += sprintf(ptr, " w ");
    else      ptr += sprintf(ptr, " b ");
    ptr += sprintf(ptr, "- ");
    if (O != 128) {
        *ptr++ = 'a'+(O&7);
        *ptr++ = '8'-(O>>4);
        *ptr++ = ' ';
    } else {
        ptr += sprintf(ptr, "- ");
    }
    ptr += sprintf(ptr, "0 1");
    *ptr = '\0';
}

/* ------------------------------------------------------------------ */
/* IS_MOVE — generate all pseudo-legal moves into GameHistory[]        */
/* Sets cnt = number of pseudo-legal moves                            */
/* Uses bit 6 trick on squares 119/112 to force castling detection    */
/* ------------------------------------------------------------------ */

void is_move() {
    int j, r, V=112;
    char t, x, y, u, p, H;

    x = 0; cnt = 0;

    /* Save old bit 6 values on castling squares */
    unsigned char old_bit1 = b[119] & 64;
    unsigned char old_bit2 = b[112] & 64;

    /* Force virgin bit for castling detection */
    b[119] |= 64; b[112] |= 64;

    memset(&GameHistory, 0, sizeof(GameHistory));
    do {
        u = b[x];
        if (u & k)
        {
            r = p = u & 7;
            j = o[p+16];
            while(r = p>2&r<0?-r:-o[++j])
            {
                y = x;
                do {
                    H = y += r;
                    if (y & M) break;
                    if (p<3 & y==E1) H = y^16;
                    t = b[H];
                    if (t&k | p<3 & !(r&7)!=!t) break;
                    cnt++;
                    if (GamePtr < GH)
                        GameHistory[GamePtr++] = 256*x + y;
                    t += p<5
                         ^ p<3 & 6*k+(y&V)==S
                         ^ j<8 & y
                           & b[H=x^3^r>>1&7]==u+66
                           & !b[H^1] & !b[H^2];
                } while(!t);
            }
        }
    } while(x = x+9 & ~M);

    /* Restore old bit values */
    b[119] = (b[119] & ~64) | old_bit1;
    b[112] = (b[112] & ~64) | old_bit2;
}

/* ------------------------------------------------------------------ */
/* MAIN                                                                */
/* ------------------------------------------------------------------ */

int main(void)
{
    CoreInit();
    SysTick_Init();
    GpioInit();

    GPIOA->BSRR = GPIO_BSRR_BR3;
    GPIOA->BSRR = GPIO_BSRR_BR4;
    GPIOB->BSRR = GPIO_BSRR_BS11;

    uart1_init();
    SpiInit();
    max7219_init();
    Delay_us(10000);
    max7219_init();

    memset(&GameHistory, 0, sizeof(GameHistory));
    GamePtr = 0;
    half_m = 0;

  lab:
    ret = 0;
    k = 8;
    memset(&rc_data, 0, sizeof(rc_data));
    memset(&pos, 0, sizeof(pos));
    memset(&b, 0, sizeof(b));

    uart1_write_String("microMax 4_1++ ver 6");

    /* LED self-test: rows then columns */
    for (int i = 0; i < 8; i++) {
        clear();
        set_row(i);
        Delay_us(10000);
    }
    for (int i = 0; i < 8; i++) {
        clear();
        set_col(i);
        Delay_us(10000);
    }

    clear();
    sd = 0;
    one = 0;
    sprintf(mov, " ");

    /* FIX 7: j initialized to 0 — was used as j++ uninitialized */
    int j = 0;
    K = 8;
    int m, nr;

    /* FIX 1: HistoryBoards initialization loop removed */

    /* Initialize board */
    W(K--)
    {
        b[K] = (b[K+112] = o[K+24]+8) + 8;
        b[K+16] = 18;
        b[K+96] = 9;
        L = 8;
        W(L--) b[16*L+K+8] = (int)((K-4)[I](K-4)+(L-3.5)[/I](L-3.5));
    }

    /* Initialize hash table */
    N = 1035;
    W(N-->M) T[N] = rand()>>9;

    Side = WHITE;
    Q = 0;
    O = S;

    /* Replay pre-loaded opening moves (if any) */
    for (nr=0; nr<GamePtr; nr++) {
        UNPACK_MOVE(GameHistory[nr]);
        D(Side, -I, I, Q, 1, StartKey++, O, 8, 2);
        Side ^= 24;
    }
    if (GamePtr > 0) board_to_fen();

    /* ------------------------------------------------------------------ */
    /* MAIN GAME LOOP                                                      */
    /* ------------------------------------------------------------------ */

    while(1)
    {
        GPIOC->BSRR = GPIO_BSRR_BR13;
        GPIOA->BSRR = GPIO_BSRR_BS3;  /* PA3 — place board indicator */
        GPIOA->BSRR = GPIO_BSRR_BS2;  /* PA2 */

        /* Print board via UART */
        N = -1;
        W(++N<121)
        {
            sprintf(pos, "%c", N&8&&(N+=7)?10:n[b[N]&15]);
            if (N % 8 == 0)
            {
                if ((N==16) & (mov[0]!=' ')) {
                    uart1_write_String(" mov ");
                    uart1_write_String(mov);
                    uart1_write_String("@");
                }
                if (N==32) {
                    sprintf(deb, " l=%d", level);
                    uart1_write_String(deb);
                }
                if (N==48) {
                    GamePtr = 0;
                    is_move();
                    sprintf(deb1, " cnt=%d\n", cnt);
                    uart1_write_String(deb1);
                }
                uart1_write_String("\r\n");
            }
            uart1_write_String(pos);
        }

        /* Display occupied squares on LED matrix */
        {
            int i1, j1;
            for (int rank = 0; rank < 8; rank++)
            {
                for (int file = 0; file < 16; file++)
                {
                    int square = rank * 16 + file;
                    if (file == 0) { i1 = 0; j1 = 0; }
                    if (!(square & 0x88)) {
                        if (n[b[square]&15] != '.')
                            i1 |= 0x01 << j1;
                        j1++;
                    }
                }
                write_reg(8-rank, i1);
            }
        }

      err:
        GPIOA->BSRR = GPIO_BSRR_BS2;
        memset(&pos, 0, sizeof(pos));
        memset(&c, 0, sizeof(c));
        p = c;

        memset(&rc_data, 0, sizeof(rc_data));
        data_count = 0;
        NVIC_EnableIRQ(EXTI0_IRQn);
        enter = 0;
        sprintf(deb, " enter=%d", enter);
        uart1_write_String(deb);
        Delay_us(300000);

        /* Enable SysTick for random seed generation */
        bl = 1;
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;

        W(data_count == 0) {
            if (ret == 1) break;

            /* FIX 5: Corrected operator precedence bug in box condition */
            /* Was: (&box1==selected_box)||(&box2==selected_box)&(half_m<2) */
            /* Bitwise & binds tighter than ||, so box1 always triggered */
            if ((enter==1) | (rc_data[0]=='0'))
            {
                memset(&rc_data, 0, sizeof(rc_data));
                p[0] = '\n';
                data_count = -1;
                srand(TimingRnd);

                /* FIX 6: Wider level range for move variety */
                /* Was: 8000+rand()%2001 (always depth 5, same moves) */
                /* Now: 5000-11000 (crosses depth 4<->5 boundary) */
                if ((half_m > 6) & (R < 29))
                    level = 5000 + rand() % 6001;
                else
                    level = 18000;

                /* FIX 5: Proper parentheses and && operator */
                if ((selected_box == &box1 || selected_box == &box2) && half_m < 2)
                {
                    GPIOA->BSRR = GPIO_BSRR_BR3;
                    NVIC_DisableIRQ(USART1_IRQn);
                    USART1->CR1 &= ~USART_CR1_RE;

                    srand(TimingRnd);
                    random_index = rand() % BOX_SIZE;
                    drawn_ball = selected_box->balls[random_index];

                    char xx = (drawn_ball)>>8 & 255;
                    char yy = drawn_ball & 255;
                    rc_data[0] = 'a'+(xx&7);
                    rc_data[1] = '8'-(xx>>4);
                    rc_data[2] = 'a'+(yy&7);
                    rc_data[3] = '8'-(yy>>4&7);
                    data_count = 4;
                }
            }
        }

        /* Disable SysTick random mode */
        bl = 0;
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;

        if (ret == 1) goto lab;

        delayMs(100);
        Delay_us(10000);
        uart1_write_String(" GO ");

        if (data_count != -1) {
            NVIC_DisableIRQ(USART1_IRQn);
            USART1->CR1 &= ~USART_CR1_RE;
            for (int i = 0; i < data_count; i++) {
                if (rc_data[i] == '0') { rc_data[i] = '\n'; }
                uart1_write(rc_data[i]);
                *p++ = rc_data[i];
            }
            USART1->CR1 |= USART_CR1_RE;
            NVIC_EnableIRQ(USART1_IRQn);
        }

        NVIC_DisableIRQ(EXTI0_IRQn);
        cnt = 0;

        K = I;  /* invalid move */
        if (*c - 10) K = *c - 16*c[1] + C, L = c[2] - 16*c[3] + C;

        /* Validate move against pseudo-legal list */
        if ((*c - 10) && (GamePtr < GH) && !ret) {
            for (int i = 0; i < GamePtr; i++) {
                if (GameHistory[i] == (256*K + L)) goto nxt;
            }
            uart1_write('#');
            GPIOA->BSRR = GPIO_BSRR_BR2;
            Delay_us(300000);
            goto err;
        }

      nxt:
        /* Select opening box based on opponent's first move */
        switch (256*K + L) {
            case 25668:
                selected_box = &box1;
                uart1_write('a');
                break;
            case 25411:
                selected_box = &box2;
                uart1_write('b');
                break;
            default:
                selected_box = NULL;
                break;
        }

        side = k;
        k ^= D(k, -I, I, Q, 1, j++, O, 8, 2) - I ? 0 : 24;

        if (side != k) {
            half_m++;
            char ssd = sd == 0 ? 'w' : 'b';
            sprintf(mov, "%c%c%c%c %c",
                    'a'+(K&7), '8'-(K>>4),
                    'a'+(L&7), '8'-(L>>4&7), ssd);
            sd ^= 1;
            clear();
            write_reg(atoi(&mov[1]), 0x01 << ((int)mov[0]-97));
            if (mov[1] == mov[3]) {
                write_reg(atoi(&mov[3]),
                          (0x01 << ((int)mov[0]-97)) | (0x01 << ((int)mov[2]-97)));
            } else {
                write_reg(atoi(&mov[3]), 0x01 << ((int)mov[2]-97));
            }
        } else {
            sprintf(mov, " ");
            one = 0;
        }

        GPIOC->BSRR = GPIO_BSRR_BS13;
        uart1_write_String(" OK");
        Delay_us(300000);
    }
}

/* ------------------------------------------------------------------ */
/* SYSTICK HANDLER — dual mode: delay or random seed                   */
/* ------------------------------------------------------------------ */

void SysTick_Handler(void)
{
    if (!bl) { TimingDelay_Decrement(); }
    else     { TimingRnd++; }
}
И даже краткую справку написали
Приятно иметьдело с грамотным ИИ)))
PS Правда не проверял, новые исправления, теперь только завтра после работы.
 

Вложения