This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git


The following commit(s) were added to refs/heads/master by this push:
     new 31d567e88 games/cgol: Add Conway's Game of Life
31d567e88 is described below

commit 31d567e88033f37190f6e972557f23fae7551f72
Author: Matteo Golin <[email protected]>
AuthorDate: Sun Nov 9 23:15:15 2025 -0500

    games/cgol: Add Conway's Game of Life
    
    This commit introduces a new application, Conway's Game of Life (or
    `cgol`). It is a simple frame buffer rendering application that makes
    for an interesting, animated visual.
    
    Signed-off-by: Matteo Golin <[email protected]>
---
 games/cgol/Kconfig     |   80 ++++
 games/cgol/Make.defs   |   25 ++
 games/cgol/Makefile    |   36 ++
 games/cgol/cgol_main.c | 1057 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1198 insertions(+)

diff --git a/games/cgol/Kconfig b/games/cgol/Kconfig
new file mode 100644
index 000000000..d07a71251
--- /dev/null
+++ b/games/cgol/Kconfig
@@ -0,0 +1,80 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config GAMES_CGOL
+       bool "Conway's Game of Life"
+       depends on VIDEO_FB
+       default n
+       ---help---
+               Enable Conway's Game of Life.
+
+if GAMES_CGOL
+
+config GAMES_CGOL_PROGNAME
+       string "Program name"
+       default "cgol"
+       ---help---
+               This is the name of the program that will be used when the NSH 
ELF
+               program is installed.
+
+config GAMES_CGOL_PRIORITY
+       int "CGOL task priority"
+       default 100
+
+config GAMES_CGOL_STACKSIZE
+       int "CGOL stack size"
+       default DEFAULT_TASK_STACKSIZE
+
+config GAMES_CGOL_FBDEV
+       string "CGOL frame buffer device path"
+       default "/dev/fb0"
+       ---help---
+               The frame buffer device path that will be used by default for 
rendering.
+               Device path can still be selected from first command line 
argument.
+
+config GAMES_CGOL_DBLBUF
+       bool "CGOL double buffered"
+       default n
+       ---help---
+               If enabled, CGOL will allocate enough RAM for a 'second frame 
buffer'.
+               Rendering will be done to this buffer and copied to the real 
frame
+               buffer once complete. This is necessary for some devices, but 
requires
+               a lot more memory.
+
+config GAMES_CGOL_MAPWIDTH
+       int "Map width"
+       range 1 500000
+       default 256
+       ---help---
+               This is the width of the game map. It must be greater than or 
equal to
+               the width of the frame buffer. It must also be a multiple of 
the target
+               device's `sizeof(unsigned int) * 8`.
+
+config GAMES_CGOL_MAPHEIGHT
+       int "Map height"
+       range 1 500000
+       default 256
+       ---help---
+               This is the height of the game map. It must be greater than or 
equal to
+               the height of the frame buffer.
+
+config GAMES_CGOL_FRAMEDELAY
+       int "Microseconds between frames"
+       range 0 10000000
+       default 0
+       ---help---
+               This adds a delay between frames in microseconds. If the render 
is too
+               fast for you to "enjoy", then you can slow it down with this.
+
+config GAMES_CGOL_DENSITY
+       int "Cell density"
+       range 1 10000
+       default 6
+       ---help---
+               The initial cell density to populate the map with. The map will 
be
+               randomly initialized with `TOTAL_CELLS * (1 / density)` cells, 
not
+               accounting for overlap.
+
+endif
diff --git a/games/cgol/Make.defs b/games/cgol/Make.defs
new file mode 100644
index 000000000..a41a0c995
--- /dev/null
+++ b/games/cgol/Make.defs
@@ -0,0 +1,25 @@
+############################################################################
+# apps/games/cgol/Make.defs
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+ifneq ($(CONFIG_GAMES_CGOL),)
+CONFIGURED_APPS += $(APPDIR)/games/cgol
+endif
diff --git a/games/cgol/Makefile b/games/cgol/Makefile
new file mode 100644
index 000000000..2aa5433cd
--- /dev/null
+++ b/games/cgol/Makefile
@@ -0,0 +1,36 @@
+############################################################################
+# apps/games/cgol/Makefile
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+# Snake game info
+
+PROGNAME = $(CONFIG_GAMES_CGOL_PROGNAME)
+PRIORITY = $(CONFIG_GAMES_CGOL_PRIORITY)
+STACKSIZE = $(CONFIG_GAMES_CGOL_STACKSIZE)
+MODULE = $(CONFIG_GAMES_CGOL)
+
+# CGOL application
+
+MAINSRC = cgol_main.c
+
+include $(APPDIR)/Application.mk
diff --git a/games/cgol/cgol_main.c b/games/cgol/cgol_main.c
new file mode 100644
index 000000000..88e26d2f7
--- /dev/null
+++ b/games/cgol/cgol_main.c
@@ -0,0 +1,1057 @@
+/****************************************************************************
+ * apps/games/cgol/cgol_main.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Author's note:
+ *
+ * There are several approaches that can be taken to make a CGOL
+ * implementation efficient for a resource-constrained device, such as the
+ * ones that Nuttx (impressively) can run on.
+ *
+ * I can make each of the cells a single bit, 1 for alive and 0 for dead, and
+ * pack a large game state into a single array of bit fields. I can take
+ * advantage of the game's "sparse" nature and only store the living cells as
+ * x, y coordinates.
+ *
+ * In terms of memory usage:
+ * A decently sized map of 100 x 100 cells with a single bit per cell would
+ * require a little over 1KB of memory. On the other hand, each living cell
+ * could be represented with 2 bytes (x, y), which allows 625 living cells in
+ * the same space (much less).
+ *
+ * As it turns out, people smarter than me have determined in the "still-Life
+ * Conjecture" that a still life cannot have a density greater than 1/2. [1]
+ * Although this doesn't tell me anything about the maximum living cells at
+ * any moment in time, it would be fair to say that on _average_ a 100 x 100
+ * map is upper-bounded by some 5000 living cells. That is a lot more than
+ * the 625 which can be implemented using 1250 bytes of memory.
+ *
+ * On the other hand, we also want to balance CPU usage. Nobody should be
+ * using this alongside important tasks, as it is just a curiosity/demo.
+ * However, lower CPU usage allows us to render frames more quickly and get
+ * a nicer video output.
+ *
+ * This implementation will restrict the size of the CGOL world to be less
+ * than or equal to the size of the frame buffer (i.e. minimum resolution of
+ * one pixel per cell). With this cap on the world size, it will likely
+ * always be more memory efficient to use full world map with 1 bit per cell
+ * than to represent living cells as (x, y) pairs.
+ *
+ * The implementation in [2] gives a fast method for computing CGOL with
+ * bit-fields in `uint64_t` types. However, that won't work as efficiently
+ * on machines with smaller word sizes. I want to use a bit-field that works
+ * well on all machines.
+ *
+ * In order to keep calculations of next states fast, this program enforces
+ * the map width to be a multiple of the machine word size.
+ *
+ * [1] Elkies, Noam D, “The still-Life density problem and its
+ * generalizations,” arXiv.org, 1999. https://arxiv.org/abs/math/9905194
+ * (accessed Nov. 10, 2025).
+ *
+ * [2] https://binary-banter.github.io/game-of-life/
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <nuttx/video/fb.h>
+#include <nuttx/video/rgbcolors.h>
+
+/****************************************************************************
+ * Preprocessor Definitions
+ ****************************************************************************/
+
+/* Foreground and background colours for all pixel sizes */
+
+#define ARGB_FULL_ALPHA (0xff << 24)
+
+#define BG32 (RGB24_BLACK | ARGB_FULL_ALPHA)
+#define FG32 (RGB24_WHITE | ARGB_FULL_ALPHA)
+
+#define BG24 RGB24_BLACK
+#define FG24 RGB24_WHITE
+
+#define BG16 RGB16_BLACK
+#define FG16 RGB16_WHITE
+
+#define BG8 RGB8_BLACK
+#define FG8 RGB8_WHITE
+
+/* Total cells in the map */
+
+#define TOTAL_CELLS (CONFIG_GAMES_CGOL_MAPWIDTH * CONFIG_GAMES_CGOL_MAPHEIGHT)
+
+/* The number of bits in one "natural" word on our machine */
+
+#ifdef UINT_WIDTH
+#define WORD_BITS UINT_WIDTH
+#else
+#define WORD_BITS ((uint8_t)(sizeof(unsigned int) * 8))
+#endif
+
+/* The number of words required to represent the map. This will always be a
+ * multiple of the word size since that is a restriction we put on MAPWIDTH.
+ */
+
+#define WORD_COUNT (TOTAL_CELLS / WORD_BITS)
+
+/* Map width in words */
+
+#define MAPWIDTH_WORDS (CONFIG_GAMES_CGOL_MAPWIDTH / WORD_BITS)
+
+/* Last bit of a word mask. Important for calculating next state */
+
+#define LAST_BIT_MASK (1 << (WORD_BITS - 1))
+
+/* Returns 1 if bit `n` is set, 0 if not. */
+
+#define BIT_N_ISSET(word, n) (((word) & (1 << (n))) >> (n))
+
+/* The map width must be a multiple of the word size in bits, otherwise
+ * the calculation of next states becomes much slower (not good on
+ * low-resource devices).
+ *
+ * We can only compile-time check this if the compiler includes a macro for
+ * the size of an unsigned int. Otherwise, we have to perform a run-time
+ * check.
+ */
+
+#ifdef UINT_WIDTH
+#if CONFIG_GAMES_CGOL_MAPWIDTH % WORD_BITS != 0
+#error "Please choose a map width which is a multiple of sizeof(unsigned int)"
+#endif
+#endif
+
+/* Short-hand for accessing the render buffer */
+
+#ifdef CONFIG_GAMES_CGOL_DBLBUF
+#define render_buf(state) ((state)->rambuf)
+#else
+#define render_buf(state) ((state)->fb)
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct fb_state_s
+{
+  struct fb_videoinfo_s vinfo;
+  struct fb_planeinfo_s pinfo;
+  unsigned int scale;
+  int fd;
+  void *fb; /* Real frame buffer */
+#ifdef CONFIG_GAMES_CGOL_DBLBUF
+  void *rambuf; /* RAM double buffer */
+#endif
+};
+
+/* Function which renders a single cell at `x`, `y` */
+
+typedef void (*cell_render_f)(const struct fb_state_s *, uint32_t x,
+                              uint32_t y);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: cgol_init
+ *
+ * Description:
+ *   Initializes the CGOL game with some random living cells. These are
+ *   chosen using `rand()` and are approximately uniformly distributed.
+ *   Overlap can happen and is not specially handled.
+ *
+ * Parameters:
+ *   map - The map to initialize with random living cells.
+ *   density - The map is initialized with `(1 / density) * TOTAL_CELLS` live
+ *             cells, not accounting for overlap.
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_init(unsigned int *map, unsigned int density)
+{
+  unsigned int idx;
+  uint8_t offset;
+
+  /* Wipe the map to all dead initially */
+
+  memset(map, 0, WORD_COUNT * sizeof(unsigned int));
+
+  /* Approximately half the total cells should start alive. */
+
+  for (unsigned int i = 0; i < TOTAL_CELLS / density; i++)
+    {
+      /* Choose a random word, and within that word, choose a random bit to
+       * start as a live cell.
+       */
+
+      idx = rand() % WORD_COUNT;
+      offset = rand() % WORD_BITS;
+      map[idx] |= (1 << offset);
+    }
+
+  return;
+}
+
+/****************************************************************************
+ * Name: cell_lives
+ *
+ * Description:
+ *   Calculates if a cell lives or dies in the next generation according to
+ *   the rules of CGOL.
+ *
+ *   Rules of cells in CGOL:
+ *   - Any live cell with < 2 neighbour dies by underpopulation
+ *   - Any live cell with 2-3 live neighbours lives
+ *   - Any live cell with > 3 live neighbours dies by overpopulation
+ *   - Any dead cell with exactly 3 live neighbours becomes alive
+ *
+ * Parameters:
+ *   alive - True if the cell is alive, false if the cell is dead
+ *   count - The cell's neighbour count
+ *
+ * Returned Value:
+ *   Returns 1 if the cell lives, 0 if the cell doesn't.
+ *
+ ****************************************************************************/
+
+static unsigned int cell_lives(unsigned int alive, uint8_t count)
+{
+  if (alive)
+    {
+      if (count < 2)
+        {
+          return 0;
+        }
+      else if (count > 3)
+        {
+          return 0;
+        }
+      else
+        {
+          return 1;
+        }
+    }
+
+  /* Cell is dead */
+
+  return count == 3 ? 1 : 0;
+}
+
+/****************************************************************************
+ * Name: cgol_advance
+ *
+ * Description:
+ *   Advances the state of the game to the next time step. The next state
+ *   overwrites the current one in `map`.
+ *
+ *   NOTE: This function expects both maps to have the same dimensions.
+ *
+ * Parameters:
+ *   map - A bit-field map representing all of the cells in the map
+ *
+ * Returned Value:
+ *   The pointer of the most updated map.
+ *
+ ****************************************************************************/
+
+static void cgol_advance(unsigned int *map)
+{
+  unsigned int *above;
+  unsigned int *cur;
+  unsigned int *below;
+  unsigned int prev_idx;
+  unsigned int next_idx;
+  unsigned int zerobuf[MAPWIDTH_WORDS];
+  uint8_t count;
+
+  unsigned int buffer[2][MAPWIDTH_WORDS];
+  unsigned int *readybuf = buffer[0];
+  unsigned int *workbuf = buffer[1];
+  unsigned int *temp;
+
+  /* We start with `cur` as row 1 of the map. Any part of our row scanner
+   * (which is three rows in height) that exceeds the map's y limits
+   * is made to point to a row of dead cells. This will prevent us from
+   * having to handle more special cases in the logic below.
+   */
+
+  memset(zerobuf, 0, sizeof(zerobuf));
+
+  above = zerobuf;
+  cur = map;
+  below = map + MAPWIDTH_WORDS;
+
+  /* For each row of the map (i.e. while the `cur` pointer hasn't looped back
+   * to the start of the map):
+   */
+
+  do
+    {
+      /* Working buffer starts zeroed so we don't need to explicitly do bit
+       * operations on dead cells.
+       */
+
+      memset(workbuf, 0, MAPWIDTH_WORDS * sizeof(unsigned int));
+
+      /* Iterate through each word in the rows. */
+
+      for (unsigned int i = 0; i < MAPWIDTH_WORDS; i++)
+        {
+          /* Get the previous and next indexes (considering wrap-around),
+           * since they will be useful later
+           */
+
+          if (i == 0)
+            {
+              prev_idx = MAPWIDTH_WORDS - 1;
+            }
+          else
+            {
+              prev_idx = i - 1;
+            }
+
+          if (i + 1 == MAPWIDTH_WORDS)
+            {
+              next_idx = 0;
+            }
+          else
+            {
+              next_idx = i + 1;
+            }
+
+          /* Iterate through each cell (bit) in the word */
+
+          for (uint8_t b = 0; b < WORD_BITS; b++)
+            {
+              count = 0;
+
+              /* Count the three '1' bits above and below our cell in the
+               * neighbourhood.
+               */
+
+              if (b == 0)
+                {
+                  /* Bit 0 also needs to check the last bit of the next word
+                   * above, below and immediately left.
+                   */
+
+                  count += popcount(above[i] & 0x3);
+                  count += popcount(below[i] & 0x3);
+                  count += BIT_N_ISSET(above[prev_idx], WORD_BITS - 1);
+                  count += BIT_N_ISSET(below[prev_idx], WORD_BITS - 1);
+                  count += BIT_N_ISSET(cur[prev_idx], WORD_BITS - 1);
+                }
+              else
+                {
+                  /* For all bits that aren't bit 0, we need to check the
+                   * left-hand side of the neighbourhood. We check top-left
+                   * and bottom-left for free already with above and below
+                   * words, so just check current left explicitly.
+                   */
+
+                  count += popcount(above[i] & (0x7 << (b - 1)));
+                  count += popcount(below[i] & (0x7 << (b - 1)));
+                  count += BIT_N_ISSET(cur[i], b - 1);
+                }
+
+              /* For all cases except the last bit, we can easily check the
+               * right-most neighbours.
+               */
+
+              if (b == WORD_BITS - 1)
+                {
+                  /* We need to fetch the next words and check if their
+                   * first bits are set.
+                   */
+
+                  count += BIT_N_ISSET(cur[next_idx], 0);
+                  count += BIT_N_ISSET(above[next_idx], 0);
+                  count += BIT_N_ISSET(below[next_idx], 0);
+                }
+              else
+                {
+                  /* We already checked top-right and bottom-right before,
+                   * check the current right only.
+                   */
+
+                  count += BIT_N_ISSET(cur[i], b + 1);
+                }
+
+              /* Now, we should have the complete neighbourhood count for the
+               * cell at word `i` and bit `b`.
+               *
+               * We always put the next state of neighbours into the last row
+               * of the buffer. Then, we shift up the rows by one index. Once
+               * a row has bubbled to the top of the buffer, it is used to
+               * overwrite the corresponding row of the map.
+               */
+
+              DEBUGASSERT(count <= 8); /* Only 8 neighbours to this cell */
+              workbuf[i] |= (cell_lives(cur[i] & (1 << b), count) << b);
+            }
+        }
+
+      if (cur - map >= MAPWIDTH_WORDS)
+        {
+          /* We can copy the portion of the next state that is ready to the
+           * `above` row. Once we hit the next iteration, the `above` row
+           * isn't used for calculation ever again. Since it will no longer
+           * have any impact on the following neighbourhoods, it's safe to
+           * overwrite in the map.
+           *
+           * NOTE: We only does this when `cur` is row 1 onward, since
+           * otherwise `readybuf` is uninitialized.
+           *
+           * NOTE: This is the clever trick that allows us to have only 2
+           * rows of buffer for the next state calculation, regardless of
+           * the map size. This is very advantageous for embedded contexts.
+           * Row buffers are even better because each word in the buffer is
+           * `WORD_BITS` cells, opposed to column buffering which would take
+           * as many words as the map height (a factor of `WORD_BITS` worse
+           * than row buffering).
+           */
+
+          memcpy(above, readybuf, MAPWIDTH_WORDS * sizeof(unsigned int));
+        }
+
+      /* Swap work buffer and temporary buffer */
+
+      temp = workbuf;
+      workbuf = readybuf;
+      readybuf = temp;
+
+      /* Now all the updating and whatnot is done for this row. Increment the
+       * row pointers to the next rows and continue!
+       */
+
+      above = cur;
+      cur = below;
+      below += MAPWIDTH_WORDS;
+
+      /* If we exceed the bottom of the map, the 'below' pointer gets to be
+       * the zero buffer.
+       */
+
+      if (below >= map + WORD_COUNT)
+        {
+          below = zerobuf;
+        }
+    }
+
+  /* Since we move up the pointers for `above`, `cur` and `below`, the `cur`
+   * pointer will be moved to the `zerobuf` once we hit the bottom of the
+   * map. This is the reason for this condition.
+   */
+
+  while (cur != zerobuf);
+
+  /* We perform one last copy from the `readybuf` at this stage, since the
+   * `above` row will never reach the bottom row during the regular loop
+   * iterations.
+   */
+
+  memcpy(above, readybuf, MAPWIDTH_WORDS * sizeof(unsigned int));
+}
+
+/****************************************************************************
+ * Name: cgol_render_update
+ *
+ * Description:
+ *   Updates the frame buffer with the current render.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_update(const struct fb_state_s *state)
+{
+#ifdef CONFIG_FB_UPDATE
+  struct fb_area_s *full_screen;
+
+  /* Create an area with the dimensions of the full screen for updating the
+   * frame buffer after render operations are complete.
+   */
+
+  full_screen.x = 0;
+  full_screen.y = 0;
+  full_screen.w = fb_state.vinfo.xres;
+  full_screen.h = fb_state.vinfo.yres;
+#endif
+
+  /* If double buffering, copy the RAM buffer to the frame buffer */
+
+#ifdef CONFIG_GAMES_CGOL_DBLBUF
+  memcpy(state->fb, state->rambuf, state->pinfo.fblen);
+#endif
+
+  /* If the frame buffer on this device needs explicit updates, do that */
+
+#ifdef CONFIG_FB_UPDATE
+  err = ioctl(fb_state.fd, FBIO_UPDATE, (uintptr_t)&full_screen);
+  if (err < 0)
+    {
+      fprintf(stderr, "Couldn't update screen: %d\n", errno);
+    }
+#endif
+}
+
+/****************************************************************************
+ * Name: cgol_render_clear
+ *
+ * Description:
+ *   Wipes the frame buffer clean to the background colour.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_clear(const struct fb_state_s *state)
+{
+  /*  TODO: we can do this more efficiently if we just blot out the pixels
+   * used for live cells last frame.
+   */
+
+  switch (state->pinfo.bpp)
+    {
+    case 32:
+      for (uint32_t y = 0; y < state->pinfo.yres_virtual; y++)
+        {
+          uint8_t *row = render_buf(state) + state->pinfo.stride * y;
+          for (uint32_t x = 0; x < state->pinfo.xres_virtual; x++)
+            {
+              ((uint32_t *)(row))[x] = BG32;
+            }
+        }
+      break;
+
+    case 24:
+      for (uint32_t y = 0; y < state->pinfo.yres_virtual; y++)
+        {
+          uint8_t *row = render_buf(state) + state->pinfo.stride * y;
+          for (uint32_t x = 0; x < state->pinfo.xres_virtual; x++)
+            {
+              *row++ = RGB24BLUE(BG24);
+              *row++ = RGB24GREEN(BG24);
+              *row++ = RGB24RED(BG24);
+            }
+        }
+      break;
+
+    case 16:
+      {
+        for (uint32_t y = 0; y < state->pinfo.yres_virtual; y++)
+          {
+            uint8_t *row = render_buf(state) + state->pinfo.stride * y;
+            for (uint32_t x = 0; x < state->pinfo.xres_virtual; x++)
+              {
+                ((uint16_t *)(row))[x] = BG16;
+              }
+          }
+      }
+      break;
+
+    case 8:
+      memset(render_buf(state), BG8, state->pinfo.fblen);
+      break;
+    }
+}
+
+/****************************************************************************
+ * Name: cgol_render_cell8
+ *
+ * Description:
+ *   Renders living cell with 8bpp.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_cell8(const struct fb_state_s *state, uint32_t x,
+                              uint32_t y)
+{
+  uint8_t *row;
+
+  /* Scale the (x, y) coordinates */
+
+  x *= state->scale;
+  y *= state->scale;
+
+  /* Starting at the (x, y) pair, we draw `scale` cells in each direction */
+
+  for (uint8_t yy = 0; yy < state->scale; yy++)
+    {
+      row = ((uint8_t *)render_buf(state)) + state->pinfo.stride * y;
+      for (uint8_t xx = 0; xx < state->scale; xx++)
+        {
+          row[x + xx] = FG8;
+        }
+
+      y++;
+    }
+}
+
+/****************************************************************************
+ * Name: cgol_render_cell16
+ *
+ * Description:
+ *   Renders living cell with 16bpp.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_cell16(const struct fb_state_s *state, uint32_t x,
+                               uint32_t y)
+{
+  uint8_t *row;
+
+  /* Scale the (x, y) coordinates */
+
+  x *= state->scale;
+  y *= state->scale;
+
+  /* Starting at the (x, y) pair, we draw `scale` cells in each direction */
+
+  for (uint8_t yy = 0; yy < state->scale; yy++)
+    {
+      row = ((uint8_t *)render_buf(state)) + state->pinfo.stride * y;
+      for (uint8_t xx = 0; xx < state->scale; xx++)
+        {
+          ((uint16_t *)(row))[x + xx] = FG16;
+        }
+
+      y++;
+    }
+}
+
+/****************************************************************************
+ * Name: cgol_render_cell24
+ *
+ * Description:
+ *   Renders living cell with 24bpp.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_cell24(const struct fb_state_s *state, uint32_t x,
+                               uint32_t y)
+{
+  uint8_t *row;
+
+  /* Scale the (x, y) coordinates */
+
+  x *= state->scale;
+  y *= state->scale;
+
+  /* Starting at the (x, y) pair, we draw `scale` cells in each direction */
+
+  for (uint8_t yy = 0; yy < state->scale; yy++)
+    {
+      row = ((uint8_t *)render_buf(state)) + state->pinfo.stride * y;
+      for (uint8_t xx = 0; xx < state->scale; xx++)
+        {
+          row[x + xx + 0] = RGB24BLUE(BG24);
+          row[x + xx + 1] = RGB24GREEN(BG24);
+          row[x + xx + 2] = RGB24RED(BG24);
+        }
+
+      y++;
+    }
+}
+
+/****************************************************************************
+ * Name: cgol_render_cell32
+ *
+ * Description:
+ *   Renders living cell with 32bpp.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_cell32(const struct fb_state_s *state, uint32_t x,
+                               uint32_t y)
+{
+  uint8_t *row;
+
+  /* Scale the (x, y) coordinates */
+
+  x *= state->scale;
+  y *= state->scale;
+
+  /* Starting at the (x, y) pair, we draw `scale` cells in each direction */
+
+  for (uint8_t yy = 0; yy < state->scale; yy++)
+    {
+      row = ((uint8_t *)render_buf(state)) + state->pinfo.stride * y;
+      for (uint8_t xx = 0; xx < state->scale; xx++)
+        {
+          ((uint32_t *)(row))[x + xx] = FG32;
+        }
+
+      y++;
+    }
+}
+
+/****************************************************************************
+ * Name: cgol_render_alive
+ *
+ * Description:
+ *   Renders living cells to the frame buffer.
+ *
+ * Parameters:
+ *   state - The frame buffer state information to use for rendering
+ *   map - The bit-field map where 1 represents living cells, 0 for dead
+ *         cells
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void cgol_render_alive(const struct fb_state_s *state,
+                              const unsigned int *map)
+{
+  cell_render_f render_cell = NULL;
+  uint32_t x;
+  uint32_t y;
+  uint32_t bit_index;
+  unsigned int word;
+  const unsigned int *start;
+  const unsigned int *end;
+
+  switch (state->pinfo.bpp)
+    {
+    case 32:
+      render_cell = cgol_render_cell32;
+      break;
+    case 24:
+      render_cell = cgol_render_cell24;
+      break;
+    case 16:
+      render_cell = cgol_render_cell16;
+      break;
+    case 8:
+      render_cell = cgol_render_cell8;
+      break;
+    }
+
+  DEBUGASSERT(render_cell != NULL);
+
+  /* Render only the living cells. */
+
+  start = map;
+  end = &map[WORD_COUNT];
+
+  for (; map < end; map++)
+    {
+      word = *map; /* Current word */
+
+      /* Determine where we are in the map in terms of (x, y) coords.
+       */
+
+      bit_index = (map - start) * WORD_BITS;
+      y = (bit_index) / CONFIG_GAMES_CGOL_MAPWIDTH;
+      x = (bit_index) % CONFIG_GAMES_CGOL_MAPWIDTH;
+
+      /* Stop trying to render cells if there are no more living cells in
+       * this word.
+       */
+
+      while (word)
+        {
+          /* Get the index of the first bit in the word.
+           *
+           * NOTE: reusing the bit_index variable here since it's no longer
+           * needed.
+           */
+
+          bit_index = ffs(word);
+          DEBUGASSERT(bit_index > 0);
+
+          /* Advance the word past the set bit so that next iteration we'll
+           * be looking for a new bit.
+           *
+           * NOTE: if the bit index is the same as the word size in bits,
+           * the C standard says that left-shift behaviour is undefined.
+           * For this case, we just set the word to 0 manually.
+           */
+
+          if (bit_index == WORD_BITS)
+            {
+              word = 0;
+            }
+          else
+            {
+              word = word >> bit_index;
+            }
+
+          /* Calculate the x, y coordinates of the bit we found that was set.
+           *
+           * NOTE: We can safely perform `- 1` since there is no way we would
+           * be here if there isn't a '1' bit in the word.
+           *
+           * NOTE: Since we're operating within a single word, there is no
+           * way that the x index can ever exceed the end of the word, and
+           * therefore no way that it can ever cross to another row.
+           */
+
+          x += (bit_index - 1);
+          DEBUGASSERT(x < CONFIG_GAMES_CGOL_MAPWIDTH);
+
+          /* Since we've rounded the map length to the nearest word, we
+           * may have up to WORD_BITS extra bits at the end of our map.
+           * These bits have invalid (x, y) coordinates. Since the end of
+           * our map always lands on the end of a row, this will manifest
+           * with a `y` coordinate that is greater than or equal to the
+           * map height. In this case, we skip rendering the cell. We've
+           * also reached the end of the map so it's safe to break out of
+           * the rendering loop.
+           */
+
+          if (y >= CONFIG_GAMES_CGOL_MAPHEIGHT)
+            {
+              break;
+            }
+
+          /* Render the cell at x, y */
+
+          render_cell(state, x, y);
+
+          /* Since our bit-index increase to x (`x += (bit_index - 1)`)
+           * doesn't account for the additional left-shift of the word, we
+           * have to increase x by one here.
+           */
+
+          x++;
+        }
+    }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * cgol_main
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+  int err;
+  char *fbdev = CONFIG_GAMES_CGOL_FBDEV;
+  struct fb_state_s fb_state;
+  unsigned int map[WORD_COUNT];
+  unsigned int yscale;
+  unsigned int xscale;
+
+  if (argc == 2)
+    {
+      fbdev = argv[1];
+    }
+
+  /* Access the frame buffer */
+
+  fb_state.fd = open(fbdev, O_RDWR);
+  if (fb_state.fd < 0)
+    {
+      fprintf(stderr, "Failed to open %s: %d\n", fbdev, errno);
+      return EXIT_FAILURE;
+    }
+
+  /* Get information about the frame buffer for rendering */
+
+  err = ioctl(fb_state.fd, FBIOGET_VIDEOINFO, (uintptr_t)&fb_state.vinfo);
+  if (err < 0)
+    {
+      fprintf(stderr, "Couldn't get frame buffer video information: %d\n",
+              errno);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+
+  err = ioctl(fb_state.fd, FBIOGET_PLANEINFO, (uintptr_t)&fb_state.pinfo);
+  if (err < 0)
+    {
+      fprintf(stderr, "Couldn't get frame buffer plane information: %d\n",
+              errno);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+
+  /* If the frame buffer resolution is too small to support our game at its
+   * lowest resolution (one pixel per cell), we can't play :(
+   */
+
+  if (fb_state.vinfo.xres < CONFIG_GAMES_CGOL_MAPWIDTH ||
+      fb_state.vinfo.yres < CONFIG_GAMES_CGOL_MAPHEIGHT)
+    {
+      fprintf(stderr,
+              "Needed at least %u x %u px resolution, but got %u x %u",
+              CONFIG_GAMES_CGOL_MAPWIDTH, CONFIG_GAMES_CGOL_MAPHEIGHT,
+              fb_state.vinfo.xres, fb_state.vinfo.yres);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+
+#ifndef UINT_WIDTH
+
+  /* If we haven't performed a compile-time check to guarantee that the map
+   * width is a multiple of the word size, we need to run-time check it.
+   */
+
+  if (CONFIG_GAMES_CGOL_MAPWIDTH % WORD_BITS != 0)
+    {
+      fprintf(
+          stderr,
+          "Map width of %u cells is not a multiple of %u bits (word size)\n",
+          CONFIG_GAMES_CGOL_MAPWIDTH, WORD_BITS);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+#endif
+
+  /* Get access to the frame buffer memory */
+
+  fb_state.fb = mmap(NULL, fb_state.pinfo.fblen, PROT_READ | PROT_WRITE,
+                     MAP_SHARED | MAP_FILE, fb_state.fd, 0);
+
+  if (fb_state.fb == MAP_FAILED)
+    {
+      fprintf(stderr, "Failed to map frame buffer memory: %d\n", errno);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+
+#ifdef CONFIG_GAMES_CGOL_DBLBUF
+
+  /* If double buffering, allocate the RAM buffer */
+
+  fb_state.rambuf = malloc(fb_state.pinfo.fblen);
+  if (fb_state.rambuf == NULL)
+    {
+      fprintf(stderr, "Couldn't allocate double buffer: %d\n", errno);
+      close(fb_state.fd);
+      return EXIT_FAILURE;
+    }
+#endif
+
+  /* Determine the ratio of the map size to the frame buffer size. If we can,
+   * use a scale factor greater than 1 so that the image is rendered larger.
+   *
+   * This is selected by picking the minimum of the scale options.
+   */
+
+  xscale = fb_state.pinfo.xres_virtual / CONFIG_GAMES_CGOL_MAPWIDTH;
+  yscale = fb_state.pinfo.yres_virtual / CONFIG_GAMES_CGOL_MAPHEIGHT;
+  fb_state.scale = xscale < yscale ? xscale : yscale;
+
+  /* Now we can seed the game with some random starting cells */
+
+  cgol_init(map, CONFIG_GAMES_CGOL_DENSITY);
+
+  /* Initially clear the game backdrop */
+
+  cgol_render_clear(&fb_state);
+
+  /* Loop the game forever */
+
+  for (; ; )
+    {
+      /* Render the freshly calculated cells */
+
+      cgol_render_alive(&fb_state, map);
+
+#if CONFIG_GAMES_CGOL_FRAMEDELAY > 0
+      usleep(CONFIG_GAMES_CGOL_FRAMEDELAY);
+#endif
+
+      /* Update the render with the new image */
+
+      cgol_render_update(&fb_state);
+
+      /* Calculate next state of the cells */
+
+      cgol_advance(map);
+
+      /* Clear the render in preparation for new cells to be rendered */
+
+      cgol_render_clear(&fb_state);
+    }
+
+#ifdef CONFIG_GAMES_CGOL_DBLBUF
+  free(fb_state.rambuf);
+#endif
+  close(fb_state.fd);
+  return EXIT_SUCCESS;
+}


Reply via email to