生命游戏 - 结构单元的2D数组,结构的最佳方式是什么?

时间:2017-07-17 12:36:04

标签: c arrays

在我的生活游戏实现中,我创建了一个由struct cell s数组组成的数据板:

struct cell **board;

我的struct cell看起来像这样:

struct cell{
    int pop;    //0 if dead, 1 if alive.
    int x;      //x coordinate
    int y;      //y coordinate
};

这意味着如果我想要更改单元格的pop字段,我会这样做:

board[x][y].pop = 1;

有更好的方法吗?什么是“最佳实践”?

2 个答案:

答案 0 :(得分:1)

这取决于您希望细胞描述的内容。

例如,如果您正在努力实现内存效率高的实现,则可以使用

#include <stdlib.h>
#include <string.h>
#include <limits.h>

#define  ULONG_BITS  (CHAR_BIT * sizeof (unsigned long))

typedef struct {
    int            rows;
    int            cols;
    size_t         rowstride;
    unsigned long  data[];
} board;

static inline int get_cell(board *b, int row, int col, int alive)
{
    /* Periodic boundaries! */
    if (row < 0)
        row = (b->rows - ((-row) % b->rows)) % b->rows;
    else
        row = row % b->rows;
    if (col < 0)
        col = (b->cols - ((-col) % b->cols)) % b->cols;
    else
        col = col % b->cols;

    {
        const size_t  w = (size_t)col / ULONG_BITS;
        const size_t  b = (size_t)col % ULONG_BITS;

        /* (!!x) == 0 if x == 0,
           (!!x) == 1 if x != 0. */
        return !!(b->word[w + (size_t)row * b->rowstride] & (1ul << b));
    }
}

static inline void set_cell(board *b, int row, int col, int alive)
{
    /* Periodic boundaries! */
    if (row < 0)
        row = (b->rows - ((-row) % b->rows)) % b->rows;
    else
        row = row % b->rows;
    if (col < 0)
        col = (b->cols - ((-col) % b->cols)) % b->cols;
    else
        col = col % b->cols;

    {
        const size_t  w = (size_t)col / ULONG_BITS;
        const size_t  b = (size_t)col % ULONG_BITS;

        if (alive)
            b->word[w + (size_t)row * b->rowstride] |= 1ul << b;
        else
            b->word[w + (size_t)row * b->rowstride] &= ~(1ul << b);
    }
}

static board *new_board(int rows, int cols)
{
    board *b;

    /* rowsize = ceil( (double)cols / ULONG_BITS ) */
    size_t  rowsize = (size_t)cols / ULONG_BITS + !!(cols % ULONG_BITS);
    size_t  words = rowsize * (size_t)rows;
    size_t  bytes = words * sizeof (unsigned long);

    if (rows < 1 || cols < 1)
        return NULL;

    /* Overflow check. */
    if (bytes / words != sizeof (unsigned long) ||
        words / (size_t)rows != rowsize ||
        rowsize * ULONG_BITS <= (size_t)cols)
        return NULL;

    b = malloc(sizeof (board) + bytes);
    if (!b)
        return NULL;

    b->rows = rows;
    b->cols = cols;
    b->rowstride = rowsize;
    memset(b->data, 0, bytes);

    return b;
}

使用unsigned long“words”来保存位是很常见的,因为大多数当前的C实现使用unsigned long作为最大的本机(处理器寄存器范围)无符号整数类型。

您还可以创建一个有效的函数来计算活动邻居的数量。您需要分别处理单元格本身位于无符号长度中最左侧或最右侧的情况,但在大多数情况下,您只需要三个单词查找和位掩码,然后是popcount(或六个移位和八个加法)

基本上,你会使用两块板,每次计算另一块板上的下一代(类似乒乓球)。 (你不能使用单个板,因为那时你会混合当前和下一代。但是你可以使用只有三行数据的滑动缓存。)

但是,如果您希望为每个单元格使用无符号字符,那么最低位显示当前状态,前一个状态具有更高位(例如,如果您想要为不同颜色的最近已故单元格着色) ,你可以使用

#include <stdlib.h>
#include <string.h>

typedef struct {
    int            rows;
    int            cols;
    size_t         rowstride;
    unsigned char  data[];
} board;

static void generation_shift(board *b)
{
    unsigned char *const q = b->data + b->rows * rowstride;
    unsigned char       *p = b->data;

    while (p < q)
        *(p++) <<= 1;
}

static inline int old_neighbors(board *b, int row, int col)
{
    if (row > 0 && row < b->rows - 1 &&
        col > 0 && col < b->cols - 1) {
        const size_t               r = b->rowstride;
        const unsigned char *const p = b->data + row*b->rowstride + col;
        return ( (p[-r-1] & 2) + (p[-r] & 2) + (p[-r+1] & 2) +
                 (p[-1] & 2)                 + (p[1] & 2) +
                 (p[r-1] & 2)  + (p[r] & 2)  + (p[r+1] & 2) ) >> 1;
    }

    if (row < 0)
        row = (b->rows - ((-row) % b->rows)) % b->rows;
    else
        row = row % b->rows;
    if (col < 0)
        col = (b->cols - ((-col) % b->cols)) % b->cols;
    else
        col = col % b->cols;

    {
        const size_t  prevrow = ((row + b->rows - 1) % b->rows) * b->rowstride;
        const size_t  currrow = row * b->rowstride;
        const size_t  nextrow = ((row + 1) % b->rows) * b->rowstride;
        const size_t  prevcol = (col + b->cols - 1) % b->cols;
        const size_t  currcol = col;
        const size_t  nextcol = (col + 1) % b->cols;
        const unsigned char *const p = b->data;

        return ( (p[prevrow+prevcol] & 2) +
                 (p[prevrow+currcol] & 2) +
                 (p[prevrow+nextcol] & 2) +
                 (p[currrow+prevcol] & 2) +
                 (p[currrow+nextcol] & 2) +
                 (p[nextrow+prevcol] & 2) +
                 (p[nextrow+currcol] & 2) +
                 (p[nextrow+nextcol] & 2) ) >> 1;
    }  
}

这样做的好处是每个单元实际上描述了当前一代的状态,并且至少有七代(除了在模拟开始时)。

一个有趣的选择是使用存储在unsigned long“字”中的位图,但在奇数代上,使用前一代的奇数位和下一代的偶数位;甚至几代人,反之亦然。这样你就不需要代换,虽然你需要使用不同的函数,这取决于代数是奇数还是偶数。

答案 1 :(得分:0)

这是一个非常紧凑和简单的版本。我假设是二次板。

#include <stdlib.h>
#include <memory.h>
#include <stdio.h>

int noNeighbours(const int* board, int x, int y, int size)
{
  int n=-board[x+y*size]; // Don't count a cell as it's own neighbour                                              

  for(int i=x-1; i<x+2; i++)
    for(int j=y-1; j<y+2; j++)
      n+=board[i+j*size];

  return n;
}

int next(int *board, int size)
{
  int *newBoard=&board[size*size];

  for(int i=1; i<size-1; i++)
    for(int j=1; j<size-1; j++) {
      int n=noNeighbours(board, i, j, size);

      if(3==n ||          // If a cell, dead or alive, has three living neighbours                                 
         (2==n && 1==board[i+j*size]) ) // If a living cell has two or three neighbours                            
        newBoard[i+j*size]=1;
    }

  memcpy(board, newBoard, size*size*sizeof(int));
}

这就是所有的游戏逻辑。以下是文件的其余部分,以提供完整的示例:

void printBoard(const int *board, int size)
{
  for(int i=0; i<size; i++)
    printf("-");
  printf("\n");

  for(int i=1; i<size-1; i++) {
    printf("|");
    for(int j=1; j<size-1; j++)
      if(board[i+j*size])
        printf("*");
      else
        printf(" ");
    printf("|\n");
  }

  for(int i=0; i<size; i++)
    printf("-");
  printf("\n");
}

int main()
{
  const int size=20;
  int *board=calloc(2*size*size, sizeof(*board));

  // Blinker                                                                                                       
  board[3+5*size]=1;
  board[3+6*size]=1;
  board[3+7*size]=1;

  // Glider                                                                                                        
  board[10+5*size]=1;
  board[10+6*size]=1;
  board[10+7*size]=1;
  board[11+7*size]=1;
  board[12+6*size]=1;

  while(1) {
    printBoard(board, size);
    next(board, size);
    getchar();
  }
  free(board);
}

这是一个好设计吗?那么,这取决于你如何定义“好的设计”。这很简单,这是件好事。