Здравствуйте, {{username}}!
Сегодня я хочу показать вам, как реализовать всеми любимую игру.
Игра Жизни Джон Конвей на Qt. Будем писать на виджетах.
На примере этого проекта я покажу вам, как работать с QPainter, несколькими основными классами, макетами и графикой в целом в Qt Widgets. Всем, кто интересуется этой игрой или работает с графикой на Qt, пожалуйста, читайте дальше.
В целом статья ориентирована на новичков, но и продвинутым ребятам будет что почитать :).
Кому лень - вот оно здесь лежит источник проекта.
Можно собирать сразу, зависимости от ядра, gui. Идея Мы хотим реализовать Game Of Life Конвея со всеми правилами Qt GUI. Оно должно быть красивым и масштабируемым.
Должна быть возможность задать размер поля, интервал между поколениями и выбор цвета ячеек.
Вам также понадобятся кнопки «Пуск», «Стоп», «Очистить».
Вам необходимо иметь возможность сохранять и загружать игру со всеми конфигами.
Архитектура Давайте сделаем это следующим образом.
на QPaintEvent мы пересчитаем ширину ячейки в зависимости от ширины окна, нарисуем сетку и ячейки.
Сохраним все красиво и аккуратно в макетах и разберемся на этапе Дизайна.
Что касается настроек, то мы очень аккуратно все вынесем на небольшую панель.
Дизайн В общем, я решил не слишком заострять внимание на этом пункте.
Я не буду никого учить делать тривиальные вещи в UI Designer. Я просто опишу архитектуру.
Создайте менеджер горизонтального макета.
Разместите для него центральный виджет. Затем вставляем туда два вертикальных менеджера (в горизонтальный).
Слева будет окно с игрой, справа - настройки.
В дизайнере они выглядят одинакового размера, но коэффициент растяжения (коэффициент ширины относительно соседнего макета) мы зададим в коде.
В макет игры вставим QWidget - который, кстати, потом продвинем на наш игровой виджет, а в макет настроек - настройки)).
Рассказывать можно долго, но лучше показать:
Давайте начнем думать
Начнем с алгоритма.
Прошу прощения, но мне было лень думать и я решил реализовать простейший алгоритм моделирования «Жизни» — с полным расчетом.
Суть алгоритма предельно проста.
Для каждой клетки мы рассчитываем ее судьбу на основе предыдущего поколения и ее соседей.
Плюсы алгоритма в том, что он прост, как бутылка минеральной воды.
Минусы: довольно дорого.
Но, как это бывает со всеми, победила лень.
Я уже придумал несколько вещей.
Мы будем хранить две матрицы (вселенная, следующая) для текущего и следующего поколения.
Также нам понадобится переменная m_masterColor для хранения цвета ячейки, timer для таймера, UniverseSize — размер матрицы.
Давайте сделаем это просто:
- Вызывается startGame()
- Таймер начинает вращаться с заданным интервалом
- В timeout() мы запускаем newGeneration().
- Здесь мы заполняем следующую матрицу на основе bool isAlive(row, col)
- Перенаправление рядом со вселенной
- Перерисовка виджета с помощью update()
- PaintEvent() вызывает методы рендеринга сетки и ячеек.
- И так долго и нудно, пока таймер работает.
- И таймер работает до тех пор, пока не будет вызвана Universe == next или stopGame().
Немного теории.
В Qt QPainter в первую очередь отвечает за графику; он содержит методы для работы с графикой.
Вы можете рисовать с его помощью на виджете только в PaintEvent().
Кстати, вот как это выглядит:
Давайте пойдем глубже.void GameWidget::paintEvent(QPaintEvent *) { QPainter p(this); paintGrid(p); paintUniverse(p); }
Здесь мы создали экземпляр QPainter и передали его ссылку методам PaintGrid() и PaintUniverse().
Они занимаются исключительно рисованием модели (матрицы вселенной).
Все просто как часы.
Теперь давайте посмотрим на PaintGrid(): void GameWidget::paintGrid(QPainter &p)
{
QRect borders(0, 0, width()-1, height()-1); // borders of the universe
QColor gridColor = m_masterColor; // color of the grid
gridColor.setAlpha(10); // must be lighter than main color
p.setPen(gridColor);
double cellWidth = (double)width()/universeSize; // width of the widget / number of cells at one row
for(double k = cellWidth; k <= width(); k += cellWidth)
p.drawLine(k, 0, k, height());
double cellHeight = (double)height()/universeSize; // height of the widget / number of cells at one row
for(double k = cellHeight; k <= height(); k += cellHeight)
p.drawLine(0, k, width(), k);
p.drawRect(borders);
}
В комментариях описаны все моменты, которые могут быть не понятны.
Теперь посмотрим, как рисуется наша «вселенная»: void GameWidget::paintUniverse(QPainter &p)
{
double cellWidth = (double)width()/universeSize;
double cellHeight = (double)height()/universeSize;
for(int k=1; k <= universeSize; k++) {
for(int j=1; j <= universeSize; j++) {
if(universe[k][j] == true) { // if there is any sense to paint it
qreal left = (qreal)(cellWidth*j-cellWidth); // margin from left
qreal top = (qreal)(cellHeight*k-cellHeight); // margin from top
QRectF r(left, top, (qreal)cellWidth, (qreal)cellHeight);
p.fillRect(r, QBrush(m_masterColor)); // fill cell with brush of main color
}
}
}
}
Замечательно.
Можно считать, что мы научились рисовать.
Впрочем, я не буду слишком подробно останавливаться на QPainter - он даже очень хорошо описан в документации, скажу лишь, что в его основе лежат три слона - перо, кисть и фигура (QRect, QCircle.) .
Перо рисует контур фигуры, кисть — ее заливку.
В последнем листинге мы не указали ручку, так как нам не нужны были контуры квадрата, но мы указали кисть для заливки.
Но как дать пользователю возможность отмечать ячейки? Очевидно, давайте переопределим метод keyPressEvent() и что-нибудь с ним сделаем.
Кстати, вот его список: void GameWidget::mousePressEvent(QMouseEvent *e)
{
double cellWidth = (double)width()/universeSize;
double cellHeight = (double)height()/universeSize;
int k = floor(e->y()/cellHeight)+1;
int j = floor(e->x()/cellWidth)+1;
universe[k][j] = !universe[k][j];
update();
}
Сохранить/Открыть карту
Данный функционал реализован двумя кнопками – Сохранить/Загрузить.
Их задача — открывать и сохранять файлы с игровыми картами.
Файл содержит:
- Размер карты
- Карта дампа
- Цвет живых клеток
- Интервал генерации
[size]
[dump]
[red] [green] [blue]
[interval]
Размер карты реализуется с помощью GameWidget::cellNumber() и GameWidget::setCellNumber().
Дамп — GameWidget::dump() и GameWidget::setDump().
Цвет — GameWidget::masterColor() и GameWidget::setMasterColor().
Интервал — GameWidget::interval() и GameWidget::setInterval().
Все что остается на плечах MainWindow — правильно писать и читать.
Я перечислю функцию loadGame(): void MainWindow::loadGame()
{
QString filename = QFileDialog::getOpenFileName(this,
tr("Open saved game"),
QDir::homePath(),
tr("Conway's Game Of Life File (*.
life)"));
if(filename.length() < 1)
return;
QFile file(filename);
if(!file.open(QIODevice::ReadOnly))
return;
QTextStream in(&file);
int sv;
in >> sv;
ui->cellsControl->setValue(sv);
game->setCellNumber(sv);
QString dump="";
for(int k=0; k != sv; k++) {
QString t;
in >> t;
dump.append(t+"\n");
}
game->setDump(dump);
int r,g,b; // RGB color
in >> r >> g >> b;
currentColor = QColor(r,g,b);
game->setMasterColor(currentColor); // sets color of the dots
QPixmap icon(16, 16); // icon on the button
icon.fill(currentColor); // fill with new color
ui->colorButton->setIcon( QIcon(icon) ); // set icon for button
in >> r; // r will be interval number
ui->iterInterval->setValue(r);
game->setInterval(r);
}
Выбор цвета
Тут много рассказывать не буду — реализовано через QColorDialog и методы (перечислены выше) класса GameWidget. Кстати, слева от текста кнопки есть квадрат, залитый тем цветом, который был выбран.
Это делается через QIcon, который получает QPixmap размером 16x16, заполненный masterColor. На что я не хочу обращать внимание Я не буду рассказывать, как запустить таймер (timer-> start()) или перерисовать виджет (update()) — надеюсь, это и так понятно, ведь в Qt есть, не побоюсь сказать, одна из лучших документации в мире.
Пожалуйста, не пишите в комментариях, что ячейки получились прямоугольными, а не квадратными.
Это действительно мой косяк — надо было все это дело обернуть в QAbstractScrollArea — но оказалось, что я этого не сделал.
В конце концов, форки и пулл-реквесты приветствуются — я не зря размещаю на GitHub)).
Фотографии и примеры
«Пулемет» планеров на поле 50х50 синего цвета.
«Пулемет» планеров на поле 100х100
Тот же пулемет, только оранжевого цвета.
Спасибо за прочтение Спасибо всем, что нашли время прочитать эту статью.
Еще раз источники находятся на GitHub .
Карта с пулеметом на github: суть .
Удачи тебе, а главное – успешной девицы, Илья.
Теги: #Qt #qws #linux #ubuntu #qwidget #Nokia #meego #Harmattan #qml #github #life #game life #the life #life #programming #Qt
-
Тенденции Цифровой Рекламы В 2017 Году
19 Oct, 24 -
Де Роберти Евгений Валентинович
19 Oct, 24 -
Учеба – Не Лотерея, Показатели Врут
19 Oct, 24 -
Бумажный Самолетик Отправлен В Стратосферу
19 Oct, 24