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
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); |
|
}
|
|
|