#include "boardwidget.h" #include #include #include namespace { constexpr int kMargin = 8; // внешний отступ поля constexpr int kGap = 4; // зазор между клетками constexpr int kMinCell = 36; // минимальный размер клетки (пикс.) constexpr int kMaxCell = 72; // максимальный размер клетки (пикс.) } BoardWidget::BoardWidget(QWidget *parent) : QWidget(parent) { setMinimumSize(220, 220); } // Создаёт новую партию: сбрасывает поле, затем случайными одиночными // переключениями формирует решаемую стартовую позицию. void BoardWidget::newGame(int size) { m_size = size; m_moves = 0; m_real.fill(0, m_size * m_size); // Позиция строится переключением случайных клеток из нулевого состояния — // обратная последовательность ходов гарантированно решает её. const int scrambles = m_size * m_size * 3; for (int i = 0; i < scrambles; ++i) { const int idx = QRandomGenerator::global()->bounded(m_size * m_size); m_real[idx] ^= 1; } update(); emit stateChanged(isSolved()); } // Включает/выключает показ подсказки. void BoardWidget::setHintEnabled(bool enabled) { if (m_hintEnabled == enabled) { return; } m_hintEnabled = enabled; update(); } // Отображаемое значение клетки — XOR её самой с четырьмя соседями. int BoardWidget::displayValue(int row, int col) const { int v = m_real[row * m_size + col]; if (row > 0) v ^= m_real[(row - 1) * m_size + col]; if (row < m_size - 1) v ^= m_real[(row + 1) * m_size + col]; if (col > 0) v ^= m_real[row * m_size + (col - 1)]; if (col < m_size - 1) v ^= m_real[row * m_size + (col + 1)]; return v; } // Переключает клетку по клику и проверяет победу. void BoardWidget::toggleCell(int row, int col) { m_real[row * m_size + col] ^= 1; ++m_moves; update(); emit stateChanged(isSolved()); } // Победа — все отображаемые клетки погашены. bool BoardWidget::isSolved() const { for (int r = 0; r < m_size; ++r) { for (int c = 0; c < m_size; ++c) { if (displayValue(r, c) != 0) { return false; } } } return true; } void BoardWidget::mousePressEvent(QMouseEvent *event) { if (m_size <= 0 || event->button() != Qt::LeftButton) { QWidget::mousePressEvent(event); return; } const QRectF board = boardRect(); if (!board.contains(event->pos())) { return; } const double cell = (board.width() - (m_size - 1) * kGap) / m_size; const int col = static_cast((event->pos().x() - board.left()) / (cell + kGap)); const int row = static_cast((event->pos().y() - board.top()) / (cell + kGap)); if (row >= 0 && row < m_size && col >= 0 && col < m_size) { toggleCell(row, col); } } void BoardWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); if (m_size <= 0) { return; } const QRectF board = boardRect(); const double cell = (board.width() - (m_size - 1) * kGap) / m_size; const QColor onColor(255, 214, 92); // «включена» — тёплый жёлтый const QColor offColor(44, 48, 56); // «выключена» — тёмно-серый const QColor hintColor(120, 220, 120); // подсказка — зелёная точка for (int r = 0; r < m_size; ++r) { for (int c = 0; c < m_size; ++c) { const QRectF rect( board.left() + c * (cell + kGap), board.top() + r * (cell + kGap), cell, cell); painter.setPen(QPen(QColor(20, 22, 26), 1.5)); painter.setBrush(displayValue(r, c) ? onColor : offColor); painter.drawRoundedRect(rect, 6, 6); // Подсказка: точка на клетках, чьё истинное значение равно 0. if (m_hintEnabled && m_real[r * m_size + c] == 0) { painter.setPen(Qt::NoPen); painter.setBrush(hintColor); const double d = cell * 0.22; painter.drawEllipse(rect.center(), d / 2, d / 2); } } } } // Прямоугольник, занимаемый сеткой клеток (центрированный квадрат). QRectF BoardWidget::boardRect() const { const int cell = kMinCell; const double side = cell * m_size + kGap * (m_size - 1); const double x = (width() - side) / 2.0; const double y = (height() - side) / 2.0; return QRectF(x, y, side, side); }