game_of_life.๐Ÿ”ฅ - a late night Mojo hack

Let's start from yesterday

I'm not a huge fan of really long form podcasts, the main issue really being the signal to chatter ratio and the freely available time I have. Having said that, yesterday I was sent a link to a podcast episode where Chris Lattner was talking for hours about Mojo. I had several hours of train ride planned during the day, so I figured that it would be perfect to listen to the conversation. It took a while to get through even at 1.9x.

Perhaps it was too much coffee or the thoughts in my head, but I sat down after 11pm and tried to write some Mojo. Around 2am in the morning I had a very hacky version of Conway's Game of Life working. It's the kind of code I'll mostly likely throw away, but I figured I'd include it on the internet as a historical object of embarrassment for my future self.

Mojo ๐Ÿ”ฅ ?

Mojo is a new programming language primarily targeted 'for all AI developers'. Mojo aims to combine the ease of Python syntax (plus ecosystem) with the performance of systems programming languages such as Rust, while providing compile time metaprogramming capabilities. It is early days for Mojo and I'd urge the reader to visit the language manual to learn more about it.

I recently had the opportunity to explore the basics of Mojo with a fast.ai meetup group. Please feel free to view the slides and the video recordings here.

Game of Life

Back to the late night hacks, I'd recently played around with Conway's Game of Life and figured that this would be a pretty good small challenge to attempt in Mojo. With additional time, I could even try to render the game grid using Matplotlib and hence test the Python ecosystem interop.

The plan was set to implement a Gilder gun and watch it evolve. Something like this! (NOTE: the following is not the final result.)

Game of Life - Glider gun
Game of Life - Glider gun

game_of_life.๐Ÿ”ฅ

After referring to the various notebooks included in Modular playgroud, I was able to come up with some code that setup the Glider gun and started emitting gliders. This is pretty much my first throwaway Mojo code, I've basically copied over things from various notebooks "until it worked", so I will not bother walking through it. The reader is urged to not use any of this code. I'm only including it here for my own historical reference.

A Boolean Matrix (Batrix) from scratch*

from IO import print_no_newline

from DType import DType
from Pointer import DTypePointer

struct Batrix:
    var data: DTypePointer[DType.bool]
    var rows: Int
    var cols: Int

    fn __init__(inout self, rows: Int, cols: Int, val: Bool):
        self.rows = rows
        self.cols = cols
        self.data = DTypePointer[DType.bool].alloc(rows * cols)
        for r in range(self.rows):
            for c in range(self.cols):
                self[r, c] = False

    @always_inline
    fn __getitem__(self, row: Int, col: Int) -> SIMD[DType.bool, 1]:
        return self.load[1](row, col)

    @always_inline
    fn load[nelts:Int](self, row: Int, col: Int) -> SIMD[DType.bool, nelts]:
        return self.data.simd_load[nelts](row * self.cols + col)

    @always_inline
    fn __setitem__(self, row: Int, col: Int, val: SIMD[DType.bool, 1]):
        return self.store[1](row, col, val)

    @always_inline
    fn store[nelts:Int](self, row: Int, col: Int, val: SIMD[DType.bool, nelts]):
        self.data.simd_store[nelts](row * self.cols + col, val)

    # to_numpy below returns PythonObject
    # which requires self to be copyable
    fn __copyinit__(inout self, other: Self):
        self.data = other.data
        self.rows = other.rows
        self.cols = other.cols

    def to_numpy(self) -> PythonObject:
        let np = Python.import_module("numpy")
        let numpy_array = np.zeros((self.rows, self.cols), np.bool)
        for col in range(self.cols):
            for row in range(self.rows):
                numpy_array.itemset((row, col), self[row, col])
        return numpy_array

    fn dump(self):
        for x in range(self.rows):
            for y in range(self.cols):
                if self[x,y] == True:
                    print_no_newline("โ–ˆ")
                else:
                    print_no_newline("-")
            print("")

The GameOfLife board

struct Game:
    var board: Batrix
    var rows: Int
    var cols: Int
    
    fn __init__(inout self, rows: Int, cols: Int):
        self.rows = rows
        self.cols = cols
        self.board = Batrix(rows, cols, False)

        
    fn neighbour_count(self, row: Int, col: Int) -> Int:
        var count = 0
        for rOff in range(-1, 2):
            for cOff in range(-1, 2):
                if not ((rOff == 0) and (cOff == 0)):
                    if (self.board[row + rOff, col + cOff] == True):
                        count += 1
        return count
    
    fn replace_board(inout self, owned _board: Batrix):
        self.board = _board
    
    fn step(inout self):
        let next_board = Batrix(self.rows, self.cols, False)
        for row in range(self.rows):
            for col in range(self.cols):
                if self.board[row, col] == True:
                    if (self.neighbour_count(row, col) == 2) or (self.neighbour_count(row, col) == 3):
                        next_board[row, col] = True
                else:
                    if self.neighbour_count(row, col) == 3:
                        next_board[row, col] = True
        self.replace_board(next_board)

    fn evolve(inout self, steps: Int):
        for _ in range(steps):
            self.step()
        
    fn dump(self):
        self.board.dump()
        

The initial setup for the Gosper Glider Gun

g = Game(40, 80)

# Setup a Glider gun
# https://en.wikipedia.org/wiki/Gun_(cellular_automaton)

g.board[5, 1] = True
g.board[5, 2] = True

g.board[6, 1] = True
g.board[6, 2] = True

g.board[5, 11] = True
g.board[6, 11] = True
g.board[7, 11] = True

g.board[4, 12] = True
g.board[8, 12] = True

g.board[3, 13] = True
g.board[9, 13] = True

g.board[3, 14] = True
g.board[9, 14] = True

g.board[6, 15] = True

g.board[4, 16] = True
g.board[8, 16] = True

g.board[5, 17] = True
g.board[6, 17] = True
g.board[7, 17] = True

g.board[6, 18] = True

g.board[3, 21] = True
g.board[4, 21] = True
g.board[5, 21] = True

g.board[3, 22] = True
g.board[4, 22] = True
g.board[5, 22] = True

g.board[2, 23] = True
g.board[6, 23] = True

g.board[1, 25] = True
g.board[2, 25] = True
g.board[6, 25] = True
g.board[7, 25] = True

g.board[3, 35] = True
g.board[4, 35] = True

g.board[3, 36] = True
g.board[4, 36] = True


g.evolve(150)
g.dump()

Game board initilization output

After the setup above, the following is the result of printing the game board. The Glider gun is ready to shoot gliders.

--------------------------------------------------------------------------------
-------------------------โ–ˆ------------------------------------------------------
-----------------------โ–ˆ-โ–ˆ------------------------------------------------------
-------------โ–ˆโ–ˆ------โ–ˆโ–ˆ------------โ–ˆโ–ˆ-------------------------------------------
------------โ–ˆ---โ–ˆ----โ–ˆโ–ˆ------------โ–ˆโ–ˆ-------------------------------------------
-โ–ˆโ–ˆ--------โ–ˆ-----โ–ˆ---โ–ˆโ–ˆ---------------------------------------------------------
-โ–ˆโ–ˆ--------โ–ˆ---โ–ˆ-โ–ˆโ–ˆ----โ–ˆ-โ–ˆ------------------------------------------------------
-----------โ–ˆ-----โ–ˆ-------โ–ˆ------------------------------------------------------
------------โ–ˆ---โ–ˆ---------------------------------------------------------------
-------------โ–ˆโ–ˆ-----------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

Evolve the board

After evolving the board through 150 generations

g.evolve(150)
g.dump()

the glider generators seem to be working well

-------------------------------------โ–ˆโ–ˆโ–ˆ----------------------------------------
-------------------------โ–ˆ---------โ–ˆ---โ–ˆ----------------------------------------
-----------------------โ–ˆ-โ–ˆ--------โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ-----------------------------------------
-------------โ–ˆโ–ˆ------โ–ˆโ–ˆ----------โ–ˆ----โ–ˆ-----------------------------------------
------------โ–ˆ---โ–ˆ----โ–ˆโ–ˆ-----------โ–ˆโ–ˆโ–ˆ-------------------------------------------
-โ–ˆโ–ˆ--------โ–ˆ-----โ–ˆ---โ–ˆโ–ˆ------------โ–ˆ--------------------------------------------
-โ–ˆโ–ˆ--------โ–ˆ---โ–ˆ-โ–ˆโ–ˆ----โ–ˆ-โ–ˆ------------------------------------------------------
-----------โ–ˆ-----โ–ˆ-------โ–ˆ------------------------------------------------------
------------โ–ˆ---โ–ˆ---------------------------------------------------------------
-------------โ–ˆโ–ˆ-----------------------------------------------------------------
------------------------โ–ˆ-------------------------------------------------------
-------------------------โ–ˆโ–ˆ-----------------------------------------------------
------------------------โ–ˆโ–ˆ------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-------------------------------โ–ˆ-โ–ˆ----------------------------------------------
--------------------------------โ–ˆโ–ˆ----------------------------------------------
--------------------------------โ–ˆ-----------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
---------------------------------------โ–ˆ----------------------------------------
----------------------------------------โ–ˆโ–ˆ--------------------------------------
---------------------------------------โ–ˆโ–ˆ---------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------------โ–ˆ-โ–ˆ-------------------------------
-----------------------------------------------โ–ˆโ–ˆ-------------------------------
-----------------------------------------------โ–ˆ--------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------------------------------------------โ–ˆ-------------------------
-----------------------------------------------------โ–ˆ-โ–ˆ------------------------

Bonus section - Matplotlib integration

It was still only 2am, so I figured I could push 30 more minutes until I figured out how to use Python integration and used matplotlib to render the game board. Here's the result. Not bad, I'd say. Maybe I'll actually render a gif next time. This is good enough to throw away for today.

def plot_board(inout game: Game):
    np = Python.import_module("numpy")
    plt = Python.import_module("matplotlib.pyplot")

    fig = plt.figure(1, [10, 10*game.board.cols//game.board.rows])
    ax = fig.add_axes([0.0, 0.0, 1.0, 1.0], False, 1)
    plt.imshow(game.board.to_numpy(), 'gray')
    plt.axis("off")
    plt.show()

plot_board(g)
Game of Life - Glider guns - Mojo Matplotlib integration
Game of Life - Glider gun

Resources