Предисловие


В общем, собрался я как-то для восстановления программисткой формы начать писать мини-проектики разных игр и задач, чтобы восстановить и улучшить эту самую программистскую форму. Первый выбор пал на шахматные задачи и на математическую игру Дж. Конве «Life».

Сегодня я расскажу о том, как реализовывал симулятор игры «Life» на базе кроссплатформенной библиотеки Qt. Делал всё на Qt Widgets без использования QML.

Идея


Первым делом в голову приходит выбор использовать готовый виджет типа QTableView или делать свой. Я решил сделать свой виджет, для упрощения всей работы он будет реализован без скролла и будет отображать столько ячеек поля игры «Жизнь», сколько на нём поместится. Это не совсем удобно, и классически, в профессиональных версиях симулятора «Жизни» сделано наоборот: там размер универсума константен, а меняется только масштаб и скролл у виджета отображения этого самого унивесума. У меня же будет сделано по-простому: размер универсума будет определятся размером самого виджета, сколько поместится клеток на виджете — столько и клеток будет в сетке унивесума. Дополнительно будет сделан интересный сервис: возможность просматривать список заранее составленных конфигураций-популяций с возможностью их размещени в произвольном месте сетки симулятора.

Архитектура


Архитектура будет простая. Один объект будет инкапсулировать сетку симулятора и отвечать за все аспекты симуляции, в том числе просчёт популяций по сигналу таймера QTimer. Другой объект — это классический виджет с переопределенным paintEvent, который отрисовывает текущую популяцию на экране средствами Qt. Третий объект — объект, инкапсулирующий работу с сэмплами (готовыми конфигурациями-популяциями хранящимися в файлах) и позволяющий загружать их из файлов для дальнейшего размещения на экране. Кроме того, нужно будет реализовать объекты окон программы и прописать в них логику работы с юзером.

Реализация


Сам универсум решено было сделать в виде обычной динамической матрицы, то есть массива размерности 2. Динамичность нужна для более гибкой работы с объектом. Итак, основной объект симулятора должен обеспечивать просчёт матрицы универсума и остальные связанные с этим функции. Матрица составлена из структуры вида:

struct Cell{   // Ячейка матрицы для симуляции игры
    bool current;
    bool next;
} ;

Где current представляет текущую ячейку, которая показывается в интерфейсе и которая модифицируется из интерфейса, а next приставляет временную ячейку, служащую для просчета следующей популяции в главном цикле симулятора.

Объект, хранящий матрицу из этой структуры, называется LifeObject. Приведу определение объекта:

/// Объект содержащий основной код симулятора игры
class LifeObject : public QObject
{
    Q_OBJECT
public:
    explicit LifeObject(QObject *parent = 0);
    ~LifeObject();

signals:
    void signal_on_timer();
public slots:
    void slot_on_timer();
public:
    Cell ** matrix; // матрица симулятора
    QTimer* timer; // объект таймера для симуляции
    uint col_count,row_count;// размерность матрицы
    uint timer_duration; // 
    void init_matrix(uint row_count, uint col_count);
    void reinit_matix(uint row_count,uint col_count ); 
    void deinit_matix();
    void start_simulation();
    void stop_simulation();
    void process_population();
    uint get_neighbor_count(uint row, uint col); 
    void random_population();
    void clean_population();

    void test_population();
    void qdebug_matrix();
};

За отображение картинки отвечает виджет, унаследованный от QWidget с переопределенным методом paintEvent, этот виджет сам отрисовывает на себе текущее состояние симуляции в виде сетки с квадратиками:



Определение объекта LifeWidget такое:

/// Объект, представляющий собой виджет для отображения ячеек сетки игры
class LifeWidget : public QWidget
{
    Q_OBJECT
public:
    explicit LifeWidget(QWidget *parent = 0);
    ~LifeWidget();
    void paintEvent( QPaintEvent *event );
signals:

public slots:
    void slot_on_timer();
public:
    uint row_height,col_width;
    uint cell_padding;
    Sample* sample;
    bool draw_numbers;
    void load_sample(QString path,QString filename);
    bool putting_sample;
    uint sample_row,sample_col;
    void  put_sample();
protected:
    virtual void resizeEvent(QResizeEvent *);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
public:
    LifeObject* life_object;
    void autoresize_widget();
};

Картинки и ссылки


На последок приведу скриншоты и ссылки на исходный код и дистрибутив с симулятором.



Окно выбора сэмпла:



Исходный код можно получить из репозитория на битбакете.

Скачать скомпилированную версию симулятора можно отсюда.

Комментарии (10)


  1. barker
    26.04.2016 13:05

    Тоже когда-то писал очень похожее, под дос ещё)


  1. silvansky
    26.04.2016 13:13

    Жизнь — Hello World для GUI. Писал реализации несколько раз на Delphi, Qt, WinForms, OS X, iOS. ;)

    Очень красиво выходит, если сделать тороидальное замыкание игрового пространства. Тогда глайдеры летают бесконечно.

    Ещё была когда-то идея сделать эту игру для двух пользователей: «красные» против «синих», каждый игрок на своей половине поля, по достижении определённого количества итераций посчитать у кого больше осталось. При этом клетки ещё бои должны вести, там можно разных правил навыдумывать.


  1. MaksVasilev
    26.04.2016 14:01

    Небольшое замечание: зачем вы в гите с исходником храните виндовый бинарник? .gitignore


    1. ryzkot
      26.04.2016 14:06

      это для тех кто хочет потыкать кнопочки а компилировать лень или Qt не стоит на компьютере.


      1. MaksVasilev
        26.04.2016 14:16
        +1

        Это очень дурная манера, класть бинарники в исходники.


        1. AllexIn
          27.04.2016 09:42

          И почему же?


          1. BelBES
            27.04.2016 10:00

            Ну как минимум потому, что исходники весят ~200Кб, а "дистрибутив" ~10Мб… и теперь либо удалять и переписывать историю гита, либо все кто клонит репозиторий будут выкачивать это ненужные 10Мб трафика.


            1. AllexIn
              27.04.2016 10:01

              Ну так и надо говорить: «В гите класть бинарники в исходники — дурная манера».
              Потому что у некоторых VCS такой проблемы нет и можно работать с частью репозитория.
              Зачем обобщать проблемы конкретной VCS на всю систему?


  1. dmrt
    27.04.2016 11:28

    Тоже люблю эту игру, только классические правила мне кажутся почему-то не интересными.
    Я пытаюсь сделать, чтобы более было похоже на жизнь: т.е. объекты двигаются, объединяются в группы, размножаются умирают, хищыники поедают не хищников. Здесь огромные возможности для программиста, можно задейсвтовать наверное львиную долю паттернов и технологий и все испытать в одной игре.
    Никто не пытался придумать свои правила «жизни»?


    1. AllexIn
      27.04.2016 17:09

      Жизнь очень проста и логично.
      А вы предлагаете из шахмат C&C запилить.