Arduino:Примеры/life

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Игра «Жизнь»[1]

Этот пример показывает при помощи Arduino и библиотеки GLCD воспроизвести на GLCD-панели клеточный автомат Джона Конуэя (John Conway) под названием «Игра “Жизнь”».

Код

 
/* 
 * Игра «Жизнь»
 * 
 * Игра «Жизнь» – клеточный автомат, придуманный Джоном Конуэем 
 * (John Conway) и адаптированный под Arduino GLCD.
 *
 */

#include <glcd.h>
#include "fonts/SystemFont5x7.h"       // системный шрифт
 
/********* пользовательские настройки ************/
#define CELL_SIZE 4        // высота и ширина каждой клетки
#define DELAY    50        // задержка (в миллисекундах) между показом поколений

/******* обработка пользовательских настроек ********/
#define ROWS    (DISPLAY_HEIGHT / CELL_SIZE)
#define COLUMNS  (DISPLAY_WIDTH / CELL_SIZE)

#define BYTES_PER_COLUMN ((COLUMNS + 7)/ 8)  //  это количество байт, необходимых для хранения столбца

#define MAX_ITERATIONS  500  // при достижении этого числа проходы обнуляются
#define STABLE_GENERATIONS 4  // должно быть как минимум «2», это количество старых поколений, которые проверяются для стабильности

#define MIN_OBJECTS 2     // минимальное количество объектов, которые будут посеяны случайным генератором 
#define MAX_OBJECTS 4     // максимальное количество объектов, которые будут посеяны случайным генератором 

#define MARGIN 4          // минимальная дистанция от края экрана для случайно посеянных объектов 


#define BITVAL(_pos) (1<< (7-(_pos % 8)))  // макрос для получения значения «bitfield»

#define ALIVE true
#define DEAD false

/* ========   Объекты, участвующие в «Игре жизни»   ========
 * Это объекты, которые будут участвовать в «Игре жизни»
 * (или, другими словами, которые могут быть отображаться на дисплее).
 * Чтобы добавить свой объект, создайте и инициализируйте структуру, 
 * следуя примерам ниже.
 * Добавьте в таблицу объектов обращение к своей структуре,
 * а также назовите свою структуру, добавив ее в список «enum».
 */

// шаблон структуры для живых объектов:
typedef struct {
         byte height ; 
         byte width ;  
         byte bitfield[];
     } object_t, *object_ptr_t;

// ниже создаются и инициализируются все объекты, которые будут 
// участвовать в «Игре жизни»:
struct {
         byte height ;
         byte width ;
         byte bitfield[2];
       } table = {2,4,  { 0b10010000 ,       //  *  *  
                          0b11110000 } };    //  **** 
                          
struct {
         byte height ; 
         byte width ;  
         byte bitfield[3];
       } glider  = {3,3, { 0b00100000 ,      //    *    
                           0b10100000 ,      //  * *
                           0b01100000 }};    //   **
                          
struct {
         byte height ; 
         byte width ;  
         byte bitfield[3];
       } glider2 = {3,3, { 0b10100000 ,      //  * *
                           0b11000000 ,      //  **
                           0b11110000 }};    //  ****                      
struct {
         byte height ; 
         byte width ;  
         byte bitfield[4];
       } loaf = {4,4,    { 0b01100000 ,     //   **
                           0b10010000 ,     //  *  *  
                           0b01010000 ,     //   * * 
                           0b00100000 }};   //    *                      
struct {
         byte height ; 
         byte width ;  
         byte bitfield[3];
       } ship = {3,3,    { 0b11000000 ,     //   **
                           0b10100000 ,     //   * *  
                           0b01100000 }};   //    **

struct {
         byte height ; 
         byte width ;  
         byte bitfield[4];
       } behive = {4,3,  { 0b01000000 ,     //   *    
                           0b10100000 ,     //  * *
                           0b10100000 ,     //  * * 
                           0b01000000}};    //   *
                           
struct {
         byte height ; 
         byte width ;  
         byte bitfield[1];
       } blinker = {1,3, { 0b11100000 }};   //  *** 
                          
                         
struct {
         byte height ; 
         byte width ;  
         byte bitfield[2];
       } block = {2,2, { 0b11000000 ,      //  **
                         0b11000000}};     //  **                     

// помещаем все эти объекты в таблицу объектов:                         
 object_ptr_t objectTable[] = {
             (object_ptr_t)&table,
             (object_ptr_t)&glider,
             (object_ptr_t)&loaf,
             (object_ptr_t)&ship,
             (object_ptr_t)&behive,
             (object_ptr_t)&blinker,
             (object_ptr_t)&block
 };   

#define OBJECT_COUNT (sizeof( objectTable/ sizeof(object_ptr_t)))

// Составляем список объектов. Новые объекты добавляем в конец списка.
// Код «Игры жизни» будет обращаться к объектам через названия в этом списке.
 enum  {Table,Glider,Loaf,Ship,Behive,Blinker,Block};

byte generations[STABLE_GENERATIONS][ROWS][BYTES_PER_COLUMN]; // массив, используемый для вычисления, рисования и проверки, изменилось ли количество проходов

void setup(){     
  GLCD.Init();   // инициализируем библиотеку в режиме рисования
  GLCD.SelectFont(System5x7);
  seed(0);
}

void loop (void){     
  GLCD.ClearScreen();
  unsigned int i = generate();  
  GLCD.CursorTo(0,0); 
  GLCD.print(i);
  GLCD.print(" iterations");  
  delay(1500);   
  seed(1); // используем случайное семя
}   

unsigned int generate(){
  unsigned int iteration = 0;
  byte thisGeneration,nextGeneration, previousGeneration;  
  thisGeneration = nextGeneration  = 0;
  // показываем начальный массив:
  for(int row = 0; row < ROWS; row++)
  {
      for(int column = 0; column < COLUMNS; column++)
      {
        if(isAlive(thisGeneration,row,column))     
           GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE,  CELL_SIZE-1, CELL_SIZE-1, CELL_SIZE/2, BLACK) ; 
        else
           GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE,  CELL_SIZE-1, CELL_SIZE-1, CELL_SIZE/2, WHITE) ; 
      }
  }

  delay(1500); // задержка для показа условий посева 
  do{    
    thisGeneration  = iteration % STABLE_GENERATIONS;
    nextGeneration =  (thisGeneration+1) % STABLE_GENERATIONS;
    previousGeneration  =  (thisGeneration-1) % STABLE_GENERATIONS;
    delay(DELAY);     
    memset(generations[nextGeneration],0,sizeof(generations[0])); // очищаем массив для следующего поколения
    // смотрим, кто жив, а кто умер:
    for(int row = 0; row < ROWS; row++){       
      for(int column = 0; column < COLUMNS; column++){
        boolean cell =  isAlive(thisGeneration,row,column);
        byte n = getNeighborCount(thisGeneration,row,column);
        if(cell == DEAD){
          if(n==3) {
            setLife(nextGeneration,row,column,ALIVE); // рождение     
            GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE,  CELL_SIZE-1, CELL_SIZE-1, CELL_SIZE/2, BLACK) ; 
          }
        }
        else{
          if(n==2 || n==3) {
            setLife(nextGeneration,row,column,ALIVE);  // выживание
            // рисовать клетку не нужно, поскольку она уже есть:
          } else { 
            setLife(nextGeneration,row,column,DEAD); // смерть 
            GLCD.DrawRoundRect(column * CELL_SIZE, row * CELL_SIZE,  CELL_SIZE-1, CELL_SIZE-1, CELL_SIZE/2, WHITE) ; 
          }
        }
      }
    } 
  }
  while(isStable(thisGeneration) == false  && ++iteration < MAX_ITERATIONS ) ;
  return iteration;
}

void seed(int seedValue){
  // если seedvalue равно «0», делаем запрограммированный посев; во всех остальных случаях делаем случайный посев
  memset(generations,0,sizeof(generations)); // очищаем все массивы для поколений
  if(seedValue == 0){
    // размещаем объекты по умолчанию; аргумента – это смещение от центра
    loadObject(Table,2,-9);    // размещаем объект «table» в двух рядах ниже и девяти столбцах левее центра 
    loadObject(Glider, -5,-9);   // размещаем объект «glider» в пяти рядах выше и в девяти столбцах левее центра 
  }
  else{  
  // загружаем несколько случайных объектов и размещаем в случайных позициях:
     byte nbrObjects = random(MIN_OBJECTS,MAX_OBJECTS+1);  // создаем случайное количество объектов
     for(byte i=0; i < nbrObjects;i++){
        byte obj = random(7); // OBJECT_COUNT);
        int column = random(MARGIN - (COLUMNS/2), (COLUMNS/2) - MARGIN); 
        int row = random(MARGIN - (ROWS/2)   , (ROWS/2) - MARGIN); 
        loadObject(obj,row,column);
     }  
  }  
}

void loadObject(void * object, char y, char x){
  // object_ptr – это загружаемый объект; 
  // X и Y – это координаты смещения от центра дисплея:
  object_ptr_t object_ptr = (object_ptr_t)object; 
  byte row = (ROWS/2) + y;
  byte column = (COLUMNS/2) + x;   
  for(byte ys = 0; ys < object_ptr->height; ys++ ){
    for(byte xs=0; xs < object_ptr->width; xs++ ){
      if( (row +ys < ROWS) && (column +xs < COLUMNS)){
        // этот код работает, только если в «width» содержится до 8 бит:
        boolean value = object_ptr->bitfield[ys] & BITVAL(xs) ;   
        setLife(0, row+ys, column+xs, value) ; // объекты всегда загружаются в первый массив
      }   
    }          
  }      
}

void loadObject(int object, char y, char x){
  loadObject(objectTable[object], y, x );
}

boolean isStable(byte thisGeneration){
  // возвращает «true», если два зарегистрированных поколения – одинаковые:
  for(byte i=0; i < STABLE_GENERATIONS; i++)
    if(i != thisGeneration) 
      if(memcmp(generations[thisGeneration], generations[i], sizeof(generations[0])) == 0)
        return true;
  return false;        
} 

boolean isAlive( byte generation, byte row, byte column){
  byte  b = generations[generation][row][column /8];
  return b &  BITVAL(column) ;   
}

void setLife( byte generation, byte row, byte column, boolean state){
  byte elem = column /8;  
  byte  b = generations[generation][row][elem];
  if(state != DEAD){
    b = b | BITVAL(column);
  }
  else{
    b &= ~(BITVAL(column)) ;  
  }
  generations[generation][row][elem] = b;
}

byte getNeighborCount( byte generation, byte row, byte column){
  byte count = 0;
  for(byte d = 0;d < 8; d++)
    if(isNeighborAlive(generation,row,column,d) != 0)
      count++;
  return count;
}

boolean isNeighborAlive( byte generation , byte row, byte column, byte dir){
  byte nrow=row;
  byte ncol=column;
  if(dir == 7 || dir == 0 || dir == 1) 
    nrow--;
  if(dir == 3 || dir == 4 || dir == 5) 
    nrow++;
  if(dir == 5 || dir == 6 || dir == 7) 
    ncol--;
  if(dir == 1 || dir == 2 || dir == 3) 
    ncol++;
  if(ncol==255) ncol = COLUMNS - 1;
  if(ncol==COLUMNS) ncol = 0;
  if(nrow==255) nrow = ROWS - 1;
  if(nrow==ROWS) nrow = 0;
  return isAlive(generation,nrow,ncol);
}

См.также

Внешние ссылки