madSetupChess

Поехал как-то в Тихвин с подругой к одному тату-мастеру Сереге. Он такой взрослый прикольный мужик, художник и в каком-то смысле музыкант, там у него тусит местная металл-группа Very Sireous BallScratchers. Очень душевный делают треш-метал, без соплей и нюнь - все как я люблю. А между делом пообщались и он предложил сделать новые шахматы. Потому что старые давно всем настопиздели... А в новых весь прикол в том, что игкроки сначала спавнят королей, а затем либо ходят уже выставленными фигурами, либо спавнят новые. Прикольная тема оказалась и я решил заняться написанием прогарммы под Андроид. Естественно. тут с ходу обнаружилось куча подводных камней... Я не говорю сейчас про серверную часть проекта, всякие там авторизации, чатики, шифрование, работу с базами mySQL - это все относительно понятно... У меня сложности начались сразу с QML! Раньше-то я делал интерфейсики во встроенном визуальном дизайнере, а тут декларативный язык со своими приколами. И до сих пор не понимаю как сделать иконку приложения... Но за то уже получилось сделать первый набросок интерфейса с рабочими правилами хода фигур. По идее, на одном экране уже можно играть вдвоем. 

По ходу пьесы тут у нас нарисовались две вспомогательные программы. Одна нарезает один большой файл png с набором фигур на отдельные квадраты, а другая делает иконку для Андроид, вместе с необходимым деревом каталогов и Андроид-манифест файлом.

 

 

Первую, я, наверно, пока и показывать не буду, потому что она уж очень специфическая и, вероятно, мало кому понадобится, а вот вторая может быть использована кем угодно, кому надо делать иконки под Андроид:

// mad_android_iconset.cpp
// ------------------------------------------------------------
// Генератор набора иконок для Android из одного PNG исходника.
// Работает внутри корневой папки Qt Android проекта.
// Автоматически:
// * создаёт все mipmap-* директории и кладёт иконки нужных размеров;
// * создаёт adaptive foreground/background и XML;
// * правит или создаёт AndroidManifest.xml с нужной записью об иконке.
//
// Требуемые пакеты MSYS2:
// pacman -S --needed \
// mingw-w64-x86_64-toolchain \
// mingw-w64-x86_64-imagemagick \
// mingw-w64-x86_64-icu \
// mingw-w64-x86_64-libxml2
//
// Компиляция:
// g++ -std=c++17 -O2 mad_android_iconset.cpp -o iconset \
// -I/C/msys64/mingw64/include/ImageMagick-7 \
// -L/mingw64/lib -lMagick++-7.Q16HDRI -lMagickWand-7.Q16HDRI -lMagickCore-7.Q16HDRI
//
// Запуск:
// ./iconset [options] [input.png]
//
// Опции:
// --basename <name> Имя базового ресурса (по умолчанию: ic_launcher)
// --bg <color> Цвет фона (auto, white или #RRGGBB/#AARRGGBB, по умолчанию: auto)
// --fg-scale <scale> Масштаб foreground (0.0-1.0, по умолчанию: 0.85)
// --bg-blur <radius> Размытие фона (по умолчанию: 0.0)
// --no-legacy Не генерировать legacy иконки
// --no-round Не генерировать round версии
// --no-adaptive Не генерировать adaptive иконки
// --project-root <dir> Путь к корню проекта (по умолчанию: текущая директория)
// --verbose Подробный вывод
// --help Показать справку
// ------------------------------------------------------------

#define MAGICKCORE_HDRI_ENABLE 1
#define MAGICKCORE_QUANTUM_DEPTH 16

#include <Magick++.h>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <optional>
#include <cctype>
#include <regex>
#include <getopt.h>
#include <iomanip>

namespace fs = std::filesystem;
using namespace Magick;

namespace mad {
struct Options {
std::string input;
fs::path projectRoot = fs::current_path();
std::string baseName = "ic_launcher";
bool doLegacy = true;
bool doRound = true;
bool doAdaptive = true;
std::string bg = "auto";
double bgBlur = 0.0;
double fgScale = 0.85;
bool verbose = false;
};
}

static void print_help(const char* prog) {
std::cout << "Использование: " << prog << " [options] [input.png]\n\n"
<< "Опции:\n"
<< " --basename <name> Имя базового ресурса (по умолчанию: ic_launcher)\n"
<< " --bg <color> Цвет фона (auto, white или #RRGGBB/#AARRGGBB, по умолчанию: auto)\n"
<< " --fg-scale <scale> Масштаб foreground (0.0-1.0, по умолчанию: 0.85)\n"
<< " --bg-blur <radius> Размытие фона (по умолчанию: 0.0)\n"
<< " --no-legacy Не генерировать legacy иконки\n"
<< " --no-round Не генерировать round версии\n"
<< " --no-adaptive Не генерировать adaptive иконки\n"
<< " --project-root <dir> Путь к корню проекта (по умолчанию: текущая директория)\n"
<< " --verbose Подробный вывод\n"
<< " --help Показать эту справку\n\n"
<< "Если input.png не указан, будет предложен выбор из PNG-файлов в текущей директории.\n";
}

static bool parse_args(int argc, char** argv, mad::Options& opt) {
struct option long_options[] = {
{"basename", required_argument, 0, 'b'},
{"bg", required_argument, 0, 'g'},
{"fg-scale", required_argument, 0, 'f'},
{"bg-blur", required_argument, 0, 'u'},
{"no-legacy", no_argument, 0, 'l'},
{"no-round", no_argument, 0, 'r'},
{"no-adaptive", no_argument, 0, 'a'},
{"project-root", required_argument, 0, 'p'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int c;
while ((c = getopt_long(argc, argv, "b:g:f:u:lrp:vh", long_options, nullptr)) != -1) {
switch (c) {
case 'b': opt.baseName = optarg; break;
case 'g': opt.bg = optarg; break;
case 'f': opt.fgScale = std::stod(optarg); break;
case 'u': opt.bgBlur = std::stod(optarg); break;
case 'l': opt.doLegacy = false; break;
case 'r': opt.doRound = false; break;
case 'a': opt.doAdaptive = false; break;
case 'p': opt.projectRoot = fs::path(optarg); break;
case 'v': opt.verbose = true; break;
case 'h': print_help(argv[0]); return false;
default: std::cerr << "Неизвестная опция. Используйте --help для справки.\n"; return false;
}
}
if (optind < argc) {
opt.input = argv[optind];
}
return true;
}

static std::vector<fs::path> find_png_files(const fs::path& dir) {
std::vector<fs::path> pngs;
try {
for (const auto& entry : fs::directory_iterator(dir)) {
if (entry.is_regular_file() && entry.path().extension() == ".png") {
pngs.push_back(entry.path());
}
}
std::sort(pngs.begin(), pngs.end());
} catch (const fs::filesystem_error& e) {
std::cerr << "Ошибка при сканировании директории: " << e.what() << "\n";
}
return pngs;
}

static bool select_input(mad::Options& opt) {
auto pngs = find_png_files(opt.projectRoot);
if (pngs.empty()) {
std::cerr << "Ошибка: Нет PNG-файлов в директории " << opt.projectRoot << "\n";
return false;
}
std::cout << "Доступные PNG-файлы:\n";
for (size_t i = 0; i < pngs.size(); ++i) {
std::cout << i << ": " << pngs[i].filename().string() << "\n";
}
std::cout << "Введите номер файла (0-" << pngs.size() - 1 << "): ";
size_t num;
std::cin >> num;
if (num >= pngs.size()) {
std::cerr << "Ошибка: Неверный номер.\n";
return false;
}
opt.input = pngs[num].string();
return true;
}

static std::optional<Color> parse_hex_color(std::string s) {
auto hex2int = [](char c) -> int {
if (c >= '0' && c <= '9') return c - '0';
c = std::tolower(static_cast<unsigned char>(c));
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
return -1;
};
if (!s.empty() && s[0] == '#') s.erase(s.begin());
if (s.size() != 6 && s.size() != 8) return std::nullopt;
auto get = [&](int i) { return (hex2int(s[2 * i]) << 4) + hex2int(s[2 * i + 1]); };
for (char c : s) if (hex2int(c) < 0) return std::nullopt;
unsigned r, g, b, a = 255;
if (s.size() == 6) { r = get(0); g = get(1); b = get(2); }
else { a = get(0); r = get(1); g = get(2); b = get(3); }
return Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}

static std::string color_to_hex(const Color& color) {
double r = color.quantumRed() / QuantumRange;
double g = color.quantumGreen() / QuantumRange;
double b = color.quantumBlue() / QuantumRange;
double a = color.quantumAlpha() / QuantumRange;
std::stringstream ss;
ss << "#" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(a * 255)
<< std::setw(2) << std::setfill('0') << static_cast<int>(r * 255)
<< std::setw(2) << std::setfill('0') << static_cast<int>(g * 255)
<< std::setw(2) << std::setfill('0') << static_cast<int>(b * 255);
return ss.str();
}

static Image make_canvas(int size, const Color& color, bool withAlpha = false) {
Image img(Geometry(size, size), color);
if (withAlpha) img.alpha(true);
img.colorSpace(sRGBColorspace);
img.type(TrueColorAlphaType);
return img;
}

static void save_png(const Image& img, const fs::path& path, bool verbose = false) {
try {
fs::create_directories(path.parent_path());
Image copy = img;
copy.magick("PNG32");
copy.write(path.string());
if (verbose) std::cout << "Сохранено: " << path << "\n";
} catch (const std::exception& e) {
std::cerr << "Ошибка при сохранении " << path << ": " << e.what() << "\n";
}
}

static Color average_color(const Image& src) {
Image tmp = src;
tmp.resize(Geometry(1, 1));
return tmp.pixelColor(0, 0);
}

static Image scale_to_fit(const Image& src, int canvas, double ratio) {
size_t sw = src.columns();
size_t sh = src.rows();
double target = canvas * ratio;
double scale = (sw > sh) ? (target / sw) : (target / sh);
size_t nw = static_cast<size_t>(std::max(1.0, sw * scale));
size_t nh = static_cast<size_t>(std::max(1.0, sh * scale));
Image scaled = src;
scaled.filterType(LanczosFilter);
scaled.resize(Geometry(nw, nh));
Image canvasImg = make_canvas(canvas, Color("transparent"), true);
ssize_t x = static_cast<ssize_t>((canvas - nw) / 2);
ssize_t y = static_cast<ssize_t>(canvas - nh) / 2;
canvasImg.composite(scaled, x, y, OverCompositeOp);
return canvasImg;
}

static void ensure_manifest(const fs::path& projectRoot, const std::string& baseName, bool verbose = false) {
fs::path manifestPath = projectRoot / "AndroidManifest.xml";
try {
if (!fs::exists(manifestPath)) {
std::ofstream f(manifestPath);
f << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
f << "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"org.qtproject.example\">\n";
f << " <application android:icon=\"@mipmap/" << baseName << "\"/>\n";
f << "</manifest>\n";
if (verbose) std::cout << "Создан новый AndroidManifest.xml\n";
return;
}
// Создаём резервную копию
fs::path bakPath = manifestPath.string() + ".bak";
fs::copy(manifestPath, bakPath, fs::copy_options::overwrite_existing);
if (verbose) std::cout << "Создана резервная копия: " << bakPath << "\n";

std::string text;
{
std::ifstream in(manifestPath);
text.assign((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
}
if (text.find("android:icon") == std::string::npos) {
std::regex re("<application(.*?)>");
text = std::regex_replace(text, re, "<application$1 android:icon=\\\"@mipmap/" + baseName + "\\\">");
} else {
std::regex re("android:icon=\\\"[^\\\"]*\\\"");
text = std::regex_replace(text, re, "android:icon=\\\"@mipmap/" + baseName + "\\\"");
}
std::ofstream out(manifestPath);
out << text;
if (verbose) std::cout << "AndroidManifest.xml обновлён.\n";
} catch (const std::exception& e) {
std::cerr << "Ошибка при обработке манифеста: " << e.what() << "\n";
}
}

int main(int argc, char** argv) {
InitializeMagick(nullptr);
mad::Options opt;
if (!parse_args(argc, argv, opt)) return 1;

if (opt.input.empty()) {
if (!select_input(opt)) return 1;
}

if (!fs::exists(opt.input)) {
std::cerr << "Ошибка: Входной файл " << opt.input << " не существует.\n";
return 1;
}

fs::path resRoot = opt.projectRoot / "android" / "res";
if (!fs::exists(resRoot)) {
if (opt.verbose) std::cout << "Папка android/res не найдена. Создаётся.\n";
fs::create_directories(resRoot);
}

try {
Image src(opt.input);
src.colorSpace(sRGBColorspace);
src.type(TrueColorAlphaType);

if (opt.verbose) std::cout << "Обработка изображения: " << opt.input << "\n";

const std::vector<std::pair<std::string, int>> legacySizes = {
{"mdpi", 48}, {"hdpi", 72}, {"xhdpi", 96}, {"xxhdpi", 144}, {"xxxhdpi", 192}
};
const std::vector<std::pair<std::string, int>> adaptiveSizes = {
{"mdpi", 108}, {"hdpi", 162}, {"xhdpi", 216}, {"xxhdpi", 324}, {"xxxhdpi", 432}
};

Color bgColor("white");
if (opt.bg == "auto") {
bgColor = average_color(src);
if (opt.verbose) std::cout << "Автоматический цвет фона: " << color_to_hex(bgColor) << "\n";
} else if (auto c = parse_hex_color(opt.bg)) {
bgColor = *c;
} else if (opt.bg == "white") {
bgColor = Color("white");
} else {
std::cerr << "Ошибка: Некорректный цвет фона: " << opt.bg << "\n";
return 1;
}

if (opt.doLegacy) {
if (opt.verbose) std::cout << "Генерация legacy иконок...\n";
for (const auto& [bucket, px] : legacySizes) {
Image im = src;
im.filterType(LanczosFilter);
im.resize(Geometry(px, px));
fs::path p = resRoot / ("mipmap-" + bucket) / (opt.baseName + ".png");
save_png(im, p, opt.verbose);
if (opt.doRound) {
fs::path pr = resRoot / ("mipmap-" + bucket) / (opt.baseName + "_round.png");
save_png(im, pr, opt.verbose);
}
}
}

if (opt.doAdaptive) {
if (opt.verbose) std::cout << "Генерация adaptive иконок...\n";
for (const auto& [bucket, px] : adaptiveSizes) {
Image bg = make_canvas(px, bgColor, false);
if (opt.bgBlur > 0.0) {
bg.blur(opt.bgBlur, 3.0);
}
fs::path pbg = resRoot / ("mipmap-" + bucket) / (opt.baseName + "_background.png");
save_png(bg, pbg, opt.verbose);

Image fg = scale_to_fit(src, px, opt.fgScale);
fs::path pfg = resRoot / ("mipmap-" + bucket) / (opt.baseName + "_foreground.png");
save_png(fg, pfg, opt.verbose);
}
fs::path anydpi = resRoot / "mipmap-anydpi-v26";
fs::create_directories(anydpi);
auto write_xml = [&](const std::string& fname) {
fs::path x = anydpi / fname;
std::ofstream f(x);
f << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
<< "<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
<< " <background android:drawable=\"@mipmap/" << opt.baseName << "_background\"/>\n"
<< " <foreground android:drawable=\"@mipmap/" << opt.baseName << "_foreground\"/>\n"
<< "</adaptive-icon>\n";
if (opt.verbose) std::cout << "Сохранено: " << x << "\n";
};
write_xml(opt.baseName + ".xml");
if (opt.doRound) write_xml(opt.baseName + "_round.xml");
}

ensure_manifest(opt.projectRoot, opt.baseName, opt.verbose);

std::cout << "Готово. Ресурсы сохранены в android/res и манифест обновлён.\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "Ошибка: " << e.what() << "\n";
return 3;
}
}

 На данном этапе моя шахматная программа содержит 2 QML файла. Следующий малость поломан... Когда починю - обновлю.

QML-файл с правилами setupChess

import QtQuick

QtObject {
id: chessRules

function isSquareUnderAttack(board, row, col, attackingColor, excludeEnd) {
console.log("Checking if square (" + row + "," + col + ") is under attack by " + attackingColor)
for (var i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var piece = cell.children[3]
if (piece.pieceColor === attackingColor) {
var fromRow = cell.row
var fromCol = cell.col
var isCapture = piece.pieceType === "pawn" ? true : (board.children[row * 8 + col].children.length > 3)
if (isValidMove(piece.pieceType, piece.pieceColor, fromRow, fromCol, row, col, false, excludeEnd, isCapture, board)) {
console.log("Square (" + row + "," + col + ") is under attack by " + piece.pieceType +
" at (" + fromRow + "," + fromCol + ")")
return true
}
}
}
}
console.log("Square (" + row + "," + col + ") is not under attack")
return false
}

function isCheck(board, color) {
console.log("Checking if " + color + " is in check")
for (var i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var piece = cell.children[3]
if (piece.pieceType === "king" && piece.pieceColor === color) {
var kingRow = cell.row
var kingCol = cell.col
if (isSquareUnderAttack(board, kingRow, kingCol, color === "white" ? "black" : "white", false)) {
console.log(color + " king is in check at (" + kingRow + "," + kingCol + ")")
return true
}
}
}
}
console.log(color + " is not in check")
return false
}

function isCheckmate(board, color, whiteReserveGrid, blackReserveGrid) {
console.log("Checking checkmate for " + color)
if (!isCheck(board, color)) {
console.log("No check detected for " + color + ", not checkmate")
return false
}

var kingRow = -1
var kingCol = -1
var kingPiece = null
for (var i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var piece = cell.children[3]
if (piece.pieceType === "king" && piece.pieceColor === color) {
kingRow = cell.row
kingCol = cell.col
kingPiece = piece
break
}
}
}
if (kingRow === -1) {
console.log("No king found for " + color + ", cannot be checkmate")
return false
}
console.log("Found " + color + " king at (" + kingRow + "," + kingCol + ")")

for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue
var newRow = kingRow + dr
var newCol = kingCol + dc
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8) {
console.log("Checking king move to (" + newRow + "," + newCol + ")")
var targetCell = board.children[newRow * 8 + newCol]
var canMove = (targetCell.children.length <= 3 || targetCell.children[3].pieceColor !== color) &&
isValidMove("king", color, kingRow, kingCol, newRow, newCol, false, false, false, board) &&
!isSquareUnderAttack(board, newRow, newCol, color === "white" ? "black" : "white", false) &&
!isKingTooClose(board, newRow, newCol, color)
if (canMove) {
var tempPiece = targetCell.children.length > 3 ? targetCell.children[3] : null
if (tempPiece) tempPiece.parent = null
kingPiece.parent = targetCell
kingPiece.sourceSlot = targetCell
var stillInCheck = isCheck(board, color)
kingPiece.parent = board.children[kingRow * 8 + kingCol]
kingPiece.sourceSlot = board.children[kingRow * 8 + kingCol]
if (tempPiece) tempPiece.parent = targetCell
if (!stillInCheck) {
console.log("King can escape to (" + newRow + "," + newCol + ")")
return false
}
}
}
}
}

for (i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var piece = cell.children[3]
if (piece.pieceColor === color && piece.pieceType !== "king") {
var fromRow = cell.row
var fromCol = cell.col
for (var toRow = 0; toRow < 8; toRow++) {
for (var toCol = 0; toCol < 8; toCol++) {
var targetCell = board.children[toRow * 8 + toCol]
var isCapture = targetCell.children.length > 3 && targetCell.children[3].pieceColor !== color
if ((targetCell.children.length <= 3 || isCapture) &&
isValidMove(piece.pieceType, color, fromRow, fromCol, toRow, toCol, false, false, isCapture, board)) {
console.log("Checking move for " + piece.pieceType + " from (" + fromRow + "," + fromCol + ") to (" + toRow + "," + toCol + ")")
var tempPiece = targetCell.children.length > 3 ? targetCell.children[3] : null
if (tempPiece) tempPiece.parent = null
piece.parent = targetCell
piece.sourceSlot = targetCell
var stillInCheck = isCheck(board, color)
piece.parent = cell
piece.sourceSlot = cell
if (tempPiece) tempPiece.parent = targetCell
if (!stillInCheck) {
console.log("Move by " + piece.pieceType + " to (" + toRow + "," + toCol + ") prevents checkmate")
return false
}
}
}
}
}
}
}

var reserveGrid = (color === "white") ? whiteReserveGrid : blackReserveGrid
if (reserveGrid) {
for (i = 0; i < reserveGrid.children.length; i++) {
var slot = reserveGrid.children[i]
if (slot.children.length > 0) {
var reservePiece = slot.children[0]
for (var toRow = 0; toRow < 8; toRow++) {
for (var toCol = 0; toCol < 8; toCol++) {
var targetCell = board.children[toRow * 8 + toCol]
if (targetCell.children.length <= 3 &&
!(reservePiece.pieceType === "king" && targetCell.isCenter) &&
isValidMove(reservePiece.pieceType, color, -1, -1, toRow, toCol, true, false, false, board)) {
console.log("Checking reserve " + reservePiece.pieceType + " to (" + toRow + "," + toCol + ")")
reservePiece.parent = targetCell
reservePiece.sourceSlot = targetCell
reservePiece.fromReserve = false
var stillInCheck = isCheck(board, color)
reservePiece.parent = slot
reservePiece.sourceSlot = slot
reservePiece.fromReserve = true
if (!stillInCheck) {
console.log("Reserve " + reservePiece.pieceType + " to (" + toRow + "," + toCol + ") prevents checkmate")
return false
}
}
}
}
}
}
}

console.log("No escape for king at (" + kingRow + "," + kingCol + "). Checkmate confirmed for " + color)
return true
}

function getActiveBishops(board, color) {
var lightBishops = 0
var darkBishops = 0
for (var i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var piece = cell.children[3]
if (piece.pieceType === "bishop" && piece.pieceColor === color) {
var isLightSquare = (cell.row + cell.col) % 2 === 0
if (isLightSquare) lightBishops++
else darkBishops++
}
}
}
console.log("Active bishops for " + color + ": light=" + lightBishops + ", dark=" + darkBishops)
return { light: lightBishops, dark: darkBishops }
}

function isValidMove(pieceType, pieceColor, fromRow, fromCol, toRow, toCol, fromReserve, excludeEnd, isCapture, board) {
console.log("isValidMove: " + pieceType + ", " + pieceColor + ", from: (" + fromRow + "," + fromCol +
"), to: (" + toRow + "," + toCol + "), fromReserve: " + fromReserve +
", excludeEnd: " + (excludeEnd || false) + ", isCapture: " + (isCapture || false))

if (!pieceType || !pieceColor) {
console.log("Invalid move: pieceType or pieceColor undefined")
return false
}

if (fromReserve) {
if (pieceType === "pawn" && (toRow === 0 || toRow === 7)) {
console.log("Cannot place pawn on row " + toRow)
return false
}
if (pieceType === "bishop") {
var bishops = getActiveBishops(board, pieceColor)
var isLightSquare = (toRow + toCol) % 2 === 0
if (bishops.light > 0 && isLightSquare) {
console.log("Cannot place bishop on light square: light bishop active")
return false
}
if (bishops.dark > 0 && !isLightSquare) {
console.log("Cannot place bishop on dark square: dark bishop active")
return false
}
}

// Проверка на шах королю противника при размещении из резерва (кроме короля)
if (pieceType !== "king") {
console.log("Checking if placing " + pieceType + " at (" + toRow + "," + toCol + ") causes check")
var targetCell = board.children[toRow * 8 + toCol]
var tempPiece = targetCell.children.length > 3 ? targetCell.children[3] : null
var originalParent = null
var originalSourceSlot = null
var originalFromReserve = null
var reservePiece = null
var reserveGrid = (pieceColor === "white") ? board.parent.parent.parent.whiteReserveLoader.item.grid : board.parent.parent.parent.blackReserveLoader.item.grid
for (var i = 0; i < reserveGrid.children.length; i++) {
var slot = reserveGrid.children[i]
if (slot.children.length > 0 && slot.children[0].pieceType === pieceType && slot.children[0].pieceColor === pieceColor) {
reservePiece = slot.children[0]
originalParent = reservePiece.parent
originalSourceSlot = reservePiece.sourceSlot
originalFromReserve = reservePiece.fromReserve
reservePiece.parent = targetCell
reservePiece.sourceSlot = targetCell
reservePiece.fromReserve = false
break
}
}
if (reservePiece) {
var opponentColor = pieceColor === "white" ? "black" : "white"
var causesCheck = isCheck(board, opponentColor)
reservePiece.parent = originalParent
reservePiece.sourceSlot = originalSourceSlot
reservePiece.fromReserve = originalFromReserve
if (tempPiece) tempPiece.parent = targetCell
if (causesCheck) {
console.log("Cannot place " + pieceType + " at (" + toRow + "," + toCol + "): causes check to opponent")
return false
}
} else {
console.log("No matching piece found in reserve for " + pieceType + ", " + pieceColor)
return false
}
} else {
console.log("Skipping check validation for king placement at (" + toRow + "," + toCol + ")")
}

console.log("Reserve move valid for " + pieceType + " to (" + toRow + "," + toCol + ")")
return true
}

var rowDiff = toRow - fromRow
var colDiff = toCol - fromCol
var absRowDiff = Math.abs(rowDiff)
var absColDiff = Math.abs(colDiff)

function isPathClear(fromRow, fromCol, toRow, toCol, excludeEnd, isCapture) {
var rowStep = (toRow === fromRow) ? 0 : (toRow > fromRow ? 1 : -1)
var colStep = (toCol === fromCol) ? 0 : (toCol > fromCol ? 1 : -1)
var steps = Math.max(Math.abs(toRow - fromRow), Math.abs(toCol - fromCol))
if (excludeEnd || isCapture) steps -= 1
for (var i = 1; i <= steps; i++) {
var checkRow = fromRow + rowStep * i
var checkCol = fromCol + colStep * i
var cellIndex = checkRow * 8 + checkCol
if (cellIndex >= 0 && cellIndex < 64) {
var cell = board.children[cellIndex]
if (cell.children.length > 3) {
console.log("Path blocked at (" + checkRow + "," + checkCol + ")")
return false
}
}
}
if (isCapture) {
var targetCell = board.children[toRow * 8 + toCol]
if (targetCell.children.length > 3 && targetCell.children[3].pieceColor !== pieceColor) {
console.log("Path clear for capture at (" + toRow + "," + toCol + ")")
return true
}
return false
}
console.log("Path clear from (" + fromRow + "," + fromCol + ") to (" + toRow + "," + toCol + ")")
return true
}

if (pieceType === "pawn") {
var forward = pieceColor === "white" ? -1 : 1
var startRow = pieceColor === "white" ? 6 : 1
var targetCell = board.children[toRow * 8 + toCol]
if (isCapture) {
if (absColDiff === 1 && rowDiff === forward && targetCell.children.length > 3 && targetCell.children[3].pieceColor !== pieceColor) {
console.log("Valid pawn capture")
return true
}
return false
} else {
if (colDiff === 0 && rowDiff === forward && targetCell.children.length <= 3) {
console.log("Valid pawn move forward")
return true
}
if (colDiff === 0 && rowDiff === 2 * forward && fromRow === startRow &&
targetCell.children.length <= 3 &&
board.children[(fromRow + forward) * 8 + fromCol].children.length <= 3) {
console.log("Valid pawn double move")
return true
}
console.log("Invalid pawn move: rowDiff=" + rowDiff + ", colDiff=" + colDiff)
return false
}
} else if (pieceType === "rook") {
if ((rowDiff === 0 || colDiff === 0) && (rowDiff !== 0 || colDiff !== 0)) {
return isPathClear(fromRow, fromCol, toRow, toCol, excludeEnd, isCapture)
}
return false
} else if (pieceType === "knight") {
if ((absRowDiff === 2 && absColDiff === 1) || (absRowDiff === 1 && absColDiff === 2)) {
return true
}
return false
} else if (pieceType === "bishop") {
if (absRowDiff === absColDiff && absRowDiff !== 0) {
return isPathClear(fromRow, fromCol, toRow, toCol, excludeEnd, isCapture)
}
return false
} else if (pieceType === "queen") {
if ((rowDiff === 0 || colDiff === 0 || absRowDiff === absColDiff) && (rowDiff !== 0 || colDiff !== 0)) {
return isPathClear(fromRow, fromCol, toRow, toCol, excludeEnd, isCapture)
}
return false
} else if (pieceType === "king") {
if (absRowDiff <= 1 && absColDiff <= 1 && (absRowDiff !== 0 || absColDiff !== 0)) {
return true
}
return false
}
console.log("Unknown piece type: " + pieceType)
return false
}

function isKingTooClose(board, toRow, toCol, newPieceColor) {
for (var i = 0; i < 64; i++) {
var cell = board.children[i]
if (cell.children.length > 3) {
var child = cell.children[3]
if (child.pieceType === "king" && child.pieceColor !== newPieceColor) {
var kingRow = cell.row
var kingCol = cell.col
if (Math.abs(toRow - kingRow) <= 1 && Math.abs(toCol - kingCol) <= 1) {
console.log("Kings too close: new king at (" + toRow + "," + toCol + "), other king at (" + kingRow + "," + kingCol + ")")
return true
}
}
}
}
return false
}
}