You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

141 lines
5.0 KiB

#include "boardwidget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QRandomGenerator>
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<int>((event->pos().x() - board.left()) / (cell + kGap));
const int row = static_cast<int>((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);
}