Четверг, 18 Апреля 2024, 17:13

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Форум игроделов » Программирование » C/C++ » [Урок]Игра "Жизнь"
[Урок]Игра "Жизнь"
SaiteiДата: Суббота, 25 Октября 2014, 20:41 | Сообщение # 1
старожил
Сейчас нет на сайте
Сегодня я покажу как написать игру "Жизнь".
Сразу хочу обратить внимание на то, что я не буду использовать ООП. Так же по совету опытных программистов я решил, что не стоит игру нагружать потоками, синглтонами и прочей ерундой. Если будет большое желание - я сделаю отдельный урок по разработке маленького параллельного игрового движка.

Игра "Жизнь"
Суть игры элементарна! Есть массив клеток. Каждая клетка может находиться в состоянии "1" или "0".
  • Если возле пустой клетки находится три живых клетки - зарождается жизнь.
  • Если же у живой клетки есть 2-3 соседа, то она продолжает жить (в противном случае погибает либо от одиночества, либо от "тесноты").
    Вот и всё.

    Код
    Игру можно было легко сделать и в консоли, но мне показалось, что получится "круче" если будет графика.
    Я использовал SFML, дабы не зацикливать ваше внимание на нюансах OpenGL/DirectX. Всё, что нас сейчас интересует, - это игра.
    Перед тем, как взглянуть на мой код, попробуйте самостоятельно написать такую игру. Если у вас получится, то можете гордиться собой.

    На этапе настройки SFML я не буду останавливаться, ведь всё написано здесь.
    Итак. Имеем следующее:

    Код
    #include <SFML/Graphics.hpp>
    #include <time.h>
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    #define WINDOW_TITLE "Life Game"
    #define CELL_SIZE 5
    #define MAP_WIDTH WINDOW_WIDTH/CELL_SIZE
    #define MAP_HEIGHT WINDOW_HEIGHT/CELL_SIZE
    #define FPS 22

    int main()
    {
             srand(time(NULL)); //Для рандома
             sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), WINDOW_TITLE); //Создаём окно
             window.setFramerateLimit(FPS);
             while (window.isOpen())
             {
                 sf::Event event;
                 while (window.pollEvent(event))
                 {
                     if (event.type == sf::Event::Closed)
                         window.close();
                 }
                 window.clear();
    ...
                 window.display();
             }
          return 0;
    }


    По условию игры есть массив клеток, где каждая клетка может становится либо "0", либо "1". Ничего не напоминает? Да-да! Это тоже самое, что true или false. Следовательно у нас есть массив булевых значений:
    Код
    bool Generation[MAP_WIDTH][MAP_HEIGHT];

    Но нам одного массива недостаточно, потому что мы каким-то образом должны "запоминать" это поколение клеток, чтобы его нормально обновлять:
    Код
    bool OldGeneration[MAP_WIDTH][MAP_HEIGHT];

    Что-то в этом есть... Не так ли?
    Но наши поколения пусты! Что же нам делать?
    Верно! Нам нужно его создать. Следовательно создадим функцию Generate:
    Код
    void Generate()
    {
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            if(rand()%10 == 1)
            {
             Generation[x][y] = OldGeneration[x][y] = true;
            }
            else
            {
             Generation[x][y] = OldGeneration[x][y] = false;
            }
           }
          }
    }

    Может быть кто-то из вас испугался циклов for... Но ничего здесь страшного нет! Мы всего лишь "пообщаемся" с каждой ячейкой массива =)
    Что касаемо rand()%10 - здесь мы генерируем число в диапазоне от 0 до 9. Следовательно rand()%N вернет число в диапазоне от 0 до (N-1).
    Если сгенерирована единица, то мы ставим в ячейку клетку. В противном случае в ячейке никого нет.
    В OldGeneration мы зафиксировали поколение, чтобы нормально обновлять Generation.
    Теперь нужно написать функцию обновления поколения:
    Код
    void Update()
    {
          UpdateOldGeneration();
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            char cellsCount = GetCellsCount(x,y);
            if(OldGeneration[x][y] == false)
            {
             if(cellsCount == 3)
             {
              Generation[x][y] = true;
             }
            }
            else
            {
             if((cellsCount < 2)||(cellsCount > 3))
             {
              Generation[x][y] = false;
             }
            }
           }
          }
    }

    Описание функций UpdateOldGeneration и GetCellsCount(x,y) я приведу ниже. Могу лишь сказать, что UpdateOldGeneration копирует Generation в OldGeneration, а GetCellsCount возвращает количество живых клеток возле OldGeneration[x][y] (с проверками на выход из массива).
    В функции Update мы всего лишь закодировали правила игры:
    Цитата
    Если возле пустой клетки находится три живых клетки - зарождается жизнь.
    Если же у живой клетки есть 2-3 соседа она продолжает жить (в противном случае погибает либо от одиночества, либо от "тесноты").

    Вот описание UpdateOldGeneration:
    Цитата
    void UpdateOldGeneration()
    {
    for(int y = 0; y < MAP_HEIGHT; y++)
    {
    for(int x = 0; x < MAP_WIDTH; x++)
    {
    OldGeneration[x][y] = Generation[x][y];
    }
    }
    }

    А вот и GetCellsCount:
    Код
    char GetCellsCount(char x, char y)
    {
          return GetCell(x-1,y)+GetCell(x-1,y-1)+GetCell(x,y-1)+GetCell(x+1,y-1)+
           GetCell(x+1,y)+GetCell(x+1,y+1)+GetCell(x,y+1)+GetCell(x-1,y+1);
    }

    "Ну вот! Опять какая-то функция!" - скажите вы. Но не беспокойтесь, мы скоро закончим : )
    GetCell всего лишь возвращает значение определенной клетки, если её координаты находятся в диапазоне массива:
    Код
    char GetCell(char x, char y)
    {
          if((x < 0)||(x > MAP_WIDTH - 1)||(y < 0)||(y > MAP_HEIGHT - 1))
          {
           return 0;
          }
          return (char)OldGeneration[x][y];
    }  


    Что ж! Логика написана. Теперь попытайтесь всё вывести на консоль.
    А вот мой код с выводом и прочей чепухой:
    Код
    #include <SFML/Graphics.hpp>
    #include <time.h>
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    #define WINDOW_TITLE "Life Game"
    #define CELL_SIZE 5
    #define MAP_WIDTH WINDOW_WIDTH/CELL_SIZE
    #define MAP_HEIGHT WINDOW_HEIGHT/CELL_SIZE
    #define FPS 22

    bool Generation[MAP_WIDTH][MAP_HEIGHT];
    bool OldGeneration[MAP_WIDTH][MAP_HEIGHT];

    void Generate()
    {
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            if(rand()%10 == 1)
            {
             Generation[x][y] = OldGeneration[x][y] = true;
            }
            else
            {
             Generation[x][y] = OldGeneration[x][y] = false;
            }
           }
          }
    }

    void UpdateOldGeneration()
    {
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            OldGeneration[x][y] = Generation[x][y];
           }
          }
    }

    char GetCell(char x, char y)
    {
          if((x < 0)||(x > MAP_WIDTH - 1)||(y < 0)||(y > MAP_HEIGHT - 1))
          {
           return 0;
          }
          return (char)OldGeneration[x][y];
    }

    char GetCellsCount(char x, char y)
    {
          return GetCell(x-1,y)+GetCell(x-1,y-1)+GetCell(x,y-1)+GetCell(x+1,y-1)+
           GetCell(x+1,y)+GetCell(x+1,y+1)+GetCell(x,y+1)+GetCell(x-1,y+1);
    }

    void Update()
    {
          UpdateOldGeneration();
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            char cellsCount = GetCellsCount(x,y);
            if(OldGeneration[x][y] == false)
            {
             if(cellsCount == 3)
             {
              Generation[x][y] = true;
             }
            }
            else
            {
             if((cellsCount < 2)||(cellsCount > 3))
             {
              Generation[x][y] = false;
             }
            }
           }
          }
    }

    void Draw(sf::RenderWindow* window, sf::RectangleShape* rectangle)
    {
          for(int y = 0; y < MAP_HEIGHT; y++)
          {
           for(int x = 0; x < MAP_WIDTH; x++)
           {
            if(Generation[x][y])
            {
             rectangle->setPosition(x*CELL_SIZE, y*CELL_SIZE);
             window->draw(*rectangle);
            }
           }
          }
    }

    int main()
    {
          srand(time(NULL));
          sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), WINDOW_TITLE);
          window.setFramerateLimit(FPS);
          sf::RectangleShape rectangle;
          rectangle.setFillColor(sf::Color(250,50,50));
          rectangle.setSize(sf::Vector2f(CELL_SIZE,CELL_SIZE));
          Generate();
             while (window.isOpen())
             {
                 sf::Event event;
                 while (window.pollEvent(event))
                 {
                     if (event.type == sf::Event::Closed)
                         window.close();
                 }
           if(sf::Keyboard::isKeyPressed(sf::Keyboard::R))
           {
            Generate();
           }
           Update();
                 window.clear();
           Draw(&window,&rectangle);
                 window.display();
             }
          return 0;
    }


    That's all.

    Вот скриншот:

    Добавлено (25.10.2014, 20:41)
    ---------------------------------------------
    *facepalm* что-то мне стыдно стало за этот код...

    Сообщение отредактировал Saitei - Пятница, 15 Августа 2014, 18:17
  • TreeLoysДата: Суббота, 24 Января 2015, 17:06 | Сообщение # 2
    частый гость
    Сейчас нет на сайте
    Можно я использую ваш материал на своем сайте с ссылкой на эту статью? http://kfni.ho.ua/sfml.html
    И да, извини что влезу, но #define не желательно уже использовать const int WINDOW_WIDTH = 640;


    http://kfni.ho.ua/ - мой сайт по урокам SFML

    Сообщение отредактировал TreeLoys - Суббота, 24 Января 2015, 17:47
    SaiteiДата: Воскресенье, 01 Февраля 2015, 17:20 | Сообщение # 3
    старожил
    Сейчас нет на сайте
    Цитата TreeLoys ()
    Можно я использую ваш материал на своем сайте с ссылкой на эту статью? http://kfni.ho.ua/sfml.html
    И да, извини что влезу, но #define не желательно уже использовать const int WINDOW_WIDTH = 640;

    Можешь
    Ozzy659Дата: Суббота, 14 Февраля 2015, 01:34 | Сообщение # 4
    уже был
    Сейчас нет на сайте
    Здравствуйте, мне очень понравился ваш урок, в связи с чем у меня возник вопрос:
    Возможно у вас есть схожие уроки/примеры простых игр? Или не совсем простых, но снабжённых комментариями наподобие данного урока?

    Вот мой вариант "Жизни", дабы отличаться сделан в консоли:

    Код

    #include <windows.h>
    #include <iostream>
    #include <array>
    #include <ctime>
    #include <conio.h>

    typedef unsigned char BYTE;

    using std::array;

    template<size_t I, size_t J, typename T>
    void Generate(array<array<T, J>, I> &arr);

    template<size_t I, size_t J, typename T>
    void UpdateGeneration(array<array<T, J>, I> &arr, array<array<T, J>, I> &oldarr);

    template<size_t I, size_t J, typename T>
    BYTE GetCell(BYTE x, BYTE y, array<array<T, J>, I> const &arr);

    template<size_t I, size_t J, typename T>
    BYTE GetCellsCount(BYTE x, BYTE y, array<array<T, J>, I> const &arr);

    template<size_t I, size_t J, typename T>
    int sumOrganism(array<array<T, J>, I> const &arr);

    template<size_t I, size_t J, typename T>
    void print(const HANDLE &StdOut, const COORD &Coord, array<array<T, J>, I> const &arr);

    int main(void) {
      srand(time(NULL));
      HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
      COORD coord {0, 0};

      array<array<bool, 60>, 20> Generation {false};
      array<array<bool, 60>, 20> oldGeneration {false};
      int sum {};

      Generate(Generation);

      do {
       UpdateGeneration(Generation, oldGeneration);
       print(hStdOut, coord, Generation);
       sum = sumOrganism(Generation);
       if(_kbhit() && (_getch() == 27)) return 0;
       if(Generation == oldGeneration) break;
       Sleep(100);
      } while(sum);

      if(!sum) {
       system("cls");
       std::cout << "All organisms were lost!" << std::endl;
      } else {
       std::cout << "\nFinish!" << std::endl;
      }
      system("pause");
      return 0;
    }

    template<size_t I, size_t J, typename T>
    void Generate(array<array<T, J>, I> &arr) {
      for(auto &i : arr) {
       for(auto &j : i) {
        j = rand()%2;
       }
      }
    }

    template<size_t I, size_t J, typename T>
    void UpdateGeneration(array<array<T, J>, I> &arr, array<array<T, J>, I> &oldarr) {
      BYTE cellsCount {};
      oldarr = arr;
      for(int i {}; i < I; i++) {
       for(int j {}; j < J; j++) {
        cellsCount = GetCellsCount(i, j, oldarr);
        if(!oldarr[i][j]) {
         if(cellsCount == static_cast<BYTE>(3)) arr[i][j] = true;
        } else {
         if((cellsCount < static_cast<BYTE>(2)) || (cellsCount > static_cast<BYTE>(3))) arr[i][j] = false;
        }
       }
      }
    }

    template<size_t I, size_t J, typename T>
    BYTE GetCell(BYTE x, BYTE y, array<array<T, J>, I> const &arr) {
      if((x > I - 1) || (y > J - 1)) {
       return 0;
      }
      return static_cast<BYTE>(arr[x][y]);
    }

    template<size_t I, size_t J, typename T>
    BYTE GetCellsCount(BYTE x, BYTE y, array<array<T, J>, I> const &arr) {
      BYTE Result {};
      Result += GetCell(x-1, y, arr);
      Result += GetCell(x-1, y-1, arr);
      Result += GetCell(x, y-1, arr);
      Result += GetCell(x+1, y-1, arr);
      Result += GetCell(x+1, y, arr);
      Result += GetCell(x+1, y+1, arr);
      Result += GetCell(x, y+1, arr);
      Result += GetCell(x-1, y+1, arr);
      return Result;
    }

    template<size_t I, size_t J, typename T>
    int sumOrganism(array<array<T, J>, I> const &arr) {
      int Result {};
      for(auto const i: arr) {
       for(auto const j: i) {
        Result += j;
       }
      }
      return Result;
    }

    template<size_t I, size_t J, typename T>
    void print(const HANDLE &StdOut, const COORD &Coord, array<array<T, J>, I> const &arr) {
      SetConsoleCursorPosition(StdOut, Coord);
      SetConsoleTextAttribute(StdOut, FOREGROUND_RED);
      for(auto const i: arr) {
       for(auto const j: i) {
        if(!j) {
         std::cout << " ";
        } else {
         std::cout << "X";
        }
       }
       std::cout << std::endl;
      }
      SetConsoleTextAttribute(StdOut, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
      std::cout << "\nESC = Exit" << std::endl;
    }


    Не могли бы вы при наличии существенных недостатков в моем коде указать на них (не как исправить, а в сторону чего копать).

    С уважением.


    Сообщение отредактировал Ozzy659 - Суббота, 14 Февраля 2015, 12:39
    SaiteiДата: Воскресенье, 15 Февраля 2015, 17:52 | Сообщение # 5
    старожил
    Сейчас нет на сайте
    Ozzy659, уроки больше не пишу, потому что они никому не нужны)
    По коду: зачем использовать шаблоны там, где они не нужны? Нужно бороться за читабельность кода...

    Цитата Ozzy659 ()
    #include <windows.h>

    os_specific_shit. Никакой кроссплатформенности... Sleep можно сделать с помощью фишек из стандарта С++11 (усыпить текущий поток на N мили\нано\...секунд)


    Сообщение отредактировал Saitei - Воскресенье, 15 Февраля 2015, 17:52
    Ozzy659Дата: Воскресенье, 15 Февраля 2015, 18:56 | Сообщение # 6
    уже был
    Сейчас нет на сайте
    Цитата Saitei ()
    уроки больше не пишу, потому что они никому не нужны)
    Очень жаль.

    Форум игроделов » Программирование » C/C++ » [Урок]Игра "Жизнь"
    • Страница 1 из 1
    • 1
    Поиск:

    Все права сохранены. GcUp.ru © 2008-2024 Рейтинг