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;
+}