Source code for nx3d.examples.gameoflife

""" This source implements the Game of Life on arbitrary graphs.

The rules are:
- node with 2 or 3 live neighbors lives
- dead node with 3 live neighbors lives
- all else is dead

Interior nodes have 8 neighbors
"""
import itertools as itt
import random
from typing import Optional

import networkx as nx
from loguru import logger

from nx3d.core import Nx3D

COLOR_DEAD = (0.2, 0.2, 0.2, 1)
COLOR_LIVE = (0.8, 0.8, 0.8, 1)


def grid_gol_graph(dim):
    # n1 connected to nodes on 2x2 hypercube centered at n1
    # that is, add in the diagonals
    g = nx.grid_graph(dim)
    edges = set()
    for n in g:
        for nbr in g[n]:
            for i in range(len(dim)):
                if n[i] == nbr[i]:
                    planar_nodes = set()
                    for adja in itt.combinations_with_replacement([-1, 0, 1], len(dim)):
                        n1 = tuple([ni - ai for ni, ai in zip(nbr, adja)])
                        planar_nodes.add(n1)
                    edges.update([(n, pn) for pn in planar_nodes])
    g.add_edges_from(edges)
    g1 = nx.grid_graph(dim)
    return nx.induced_subgraph(g, g1.nodes)


def _update_colors(g):
    """black if alive else white"""
    for _, nd in g.nodes(data=True):
        val = nd["val"]
        nd["color"] = COLOR_LIVE if val else COLOR_DEAD


def _clear_board(g):
    for n in g:
        g.nodes[n]["val"] = 0
        g.nodes[n]["last_val"] = 0


def _reset_board(g, n_live: Optional[int] = None):
    _clear_board(g)
    g.graph["reset"] = True
    if n_live is None:
        n_live = len(g) // 2
    elif n_live <= 0:
        return
    for n in random.sample(g.nodes, k=n_live):
        g.nodes[n]["val"] = 1
    _update_colors(g)


def _do_life(g: nx.Graph, di, dt):
    if all(nd["val"] == nd["last_val"] for _, nd in g.nodes(data=True)):
        logger.success("dead board, resetting")
        _reset_board(g)
    else:
        g.graph["reset"] = False
        for n in g:
            g.nodes[n]["last_val"] = g.nodes[n]["val"]
        vals = {}
        for nd, nbrsdict in g.adjacency():
            live_nbrs = 0
            nbrs = list(nbrsdict.keys())
            live_nbrs = sum(g.nodes[nbr]["val"] for nbr in nbrs)
            if live_nbrs == 3:
                vals[nd] = 1
            elif g.nodes[nd]["val"] and live_nbrs == 2:
                vals[nd] = 1
            else:
                vals[nd] = 0
        for nd in g:
            g.nodes[nd]["val"] = vals[nd]
    _update_colors(g)


[docs]def game_of_life(g=grid_gol_graph((16, 16)), **kwargs): """This function opens a popup that runs the Game of Life. `` python -m nx3d life `` Args: kwargs: passed to Nx3D.__init__ """ _reset_board(g) g = nx.convert_node_labels_to_integers(g) app = Nx3D(g, state_trans_func=_do_life, **kwargs) app.run()