Would you like me to show you the magic of live code on p5py?

Inspired articlededicated to writing a cellular automaton in Godot and exporting the project to HTML, I want to show you how to use a modern online engine for these purposes p5py. The code is alive not only because we are talking about the game of Life, but also because of the way it was developed and launched. Everything is very alive!

godot

Black cloak

TL;DR: the final project is here Here. Just click and it will appear.

What's the magic?

  1. We will get a similar result, but much faster and more fun. We won't have to set up or download anything, which is great for beginners. Just follow the links in the article and run working examples.

  2. We won't need export to HTML either, since the code is in Python, thanks to p5py and online IDE, runs directly in the browser.

  3. Moreover, if you have a creative idea on how to improve the code and you implement it, then: a) you will immediately see the result, b) by clicking “Save”, you will receive a ready-made link that you can share with friends or in the comments.


p5py

This is an adaptation of the popular Processing (p5.js) for Python. I wrote it for conducting classes in children’s programming groups and for the book, which I already talked about in the article “As I wrote a book for children: “Mom, don’t distract. I'm learning Python!

godot

An online IDE has also been developed for it, which can be launched from all the links in this article. There are only two buttons: “Run” and “Save”. Don't get confused.

We get

  1. Instead of GDScript – the most standard Python (+ module p5py).

  2. No separate export is needed – the result is immediately available via an online link.

  3. Live sandbox – code automatically reruns in live mode as it is written (can be disabled if desired).

  4. The size is not a gigabyte, but 4.5 megabytes.


And he was in love with Life…

Let's recreate Conway's “Game of Life” – a cellular automaton capable of generating complex patterns that imitate life.

Source code on Godot:

extends Node2D
@export var cell_scene : PackedScene
var row_count : int = 45
var column_count : int = 80
var cell_width: int = 15
var cell_matrix: Array = []
var previous_cell_states: Array = []
var is_game_running: bool = false
var is_interactive_mode: bool = false

Our code for p5py:

row_count = 45
column_count = 80
cell_width = 15

Why so short? And because, because, because…

…that we take a project-oriented approach and don't write lines of code that won't be useful in the next step. Why keep them in your head? They only create noise. For now, all we need to know is the field size 45х80 and cell size 15х15.

Field, field, field…

White, white let's make an empty field of cells.

from p5py import *
run()

row_count = 45
column_count = 80
cell_width = 15

size(column_count * cell_width, row_count * cell_width)

def draw():
    background(255)  # Белый фон
    draw_grid()

def draw_grid():
    stroke(0)  # Черные линии сетки
    for x in range(column_count + 1):
        line(x * cell_width, 0, x * cell_width, row_count * cell_width)
    for y in range(row_count + 1):
        line(0, y * cell_width, column_count * cell_width, y * cell_width)

 # Здесь мы просто отображаем пустое поле
empty

Click Hereto run. If you suddenly get an error, please write in a personal message. The version is still 0.XXX, there may be bugs.

Experiment! Change the background and line colors. For example, background(200, 100, 0) is a shade of orange, and stroke(255, 120, 0) – shade of red.

Chance will decide how to fill the cells

Let's improve the original code:

  1. We immediately fill the field with random values ​​- this makes it more interesting for the player to watch the process. When you start it, something happens immediately.

  1. And since in the online-IDE p5py easy to press the cross, closing the program, and immediately press RUNhaving launched it again, you can play with different starting conditions.

# Инициализация состояния клеток случайными значениями
cell_matrix = [[rand(0, 15) <= 1 for _ in range(row_count)] for _ in range(column_count)]

def draw_cells():
    for col in range(column_count):
        for row in range(row_count):
            if cell_matrix[col][row]:
                fill(0)  # Черный цвет для живых клеток
            else:
                no_fill()  # Без заливки для мертвых клеток
            rect(col * cell_width, row * cell_width, cell_width, cell_width)
random

Like this it worked. Here I made the cells yellow. You can experiment with the code live, for example by replacing the number 15 with another one to change the density of the field.

The preparation is complete, now we have both a grid and randomly colored cells.

It's time to move on to…

Field update. Game not by the rules

Let's add a function update_game_state()which will simply go through each cell of the field and write a new status into it. Which, in turn, learns from the function get_next_state().

def update_game_state():
    global cell_matrix

    for col in range(column_count):
        for row in range(row_count):
            cell_matrix[col][row] = get_next_state(col, row)

def get_next_state(column, row):
    return rand(0, 15) <= 1
change

And here it is link to play around.

Yes, yes, the game is not by the rules yet. We use a mock (stub) to quickly see the result and it would be interesting to improve the program step by step.

Now let's move on to…

Game rules

And here comes the most important thing – creating the rules for the game of Life. We write functions to count living neighbors and determine the future state of each cell. These are the classic Conway rules: a cell becomes alive if it has exactly three living neighboring cells, and remains alive if it has two or three living neighboring cells.

Source code on Godot:

func get_count_of_alive_neighbours(column, row):
    var count = 0
    for x in range(-1, 2):
        for y in range(-1, 2):
            if not (x == 0 and y == 0):
                var neighbor_column = column + x
                var neighbor_row = row + y
                if neighbor_column >= 0 and neighbor_column < column_count and neighbor_row >= 0 and neighbor_row < row_count:
                    if previous_cell_states[neighbor_column][neighbor_row]:
                        count += 1
    return count

func get_next_state(column, row):
    var current = previous_cell_states[column][row]
    var neighbours_alive = get_count_of_alive_neighbours(column, row)

    if current:
        return neighbours_alive == 2 or neighbours_alive == 3
    else:
        return neighbours_alive == 3

Almost the same as p5py:

def get_next_state(column, row):
    alive_neighbors = count_alive_neighbors(column, row)
    current = previous_cell_states[column][row]

    if current:
        # Cell is alive, it stays alive if it has 2 or 3 neighbors
        return alive_neighbors == 2 or alive_neighbors == 3
    else:
        # Cell is dead, it becomes alive if it has exactly 3 neighbors
        return alive_neighbors == 3

def count_alive_neighbors(column, row):
    count = 0
    for x in range(-1, 2):
        for y in range(-1, 2):
            if x == 0 and y == 0:
                continue  # Skip the cell itself
            neighbor_col = column + x
            neighbor_row = row + y
            if 0 <= neighbor_col < column_count and 0 <= neighbor_row < row_count:
                if previous_cell_states[neighbor_col][neighbor_row]:
                    count += 1
    return count

But also a function update_game_state() we will also have to change a little to save the previous value of the cells into a temporary array.

What do you think this step is for?

def update_game_state():
    global previous_cell_states, cell_matrix

    # Copy current state to previous
    for col in range(column_count):
        for row in range(row_count):
            previous_cell_states[col][row] = cell_matrix[col][row]

    # Apply the Game of Life rules
    for col in range(column_count):
        for row in range(row_count):
            cell_matrix[col][row] = get_next_state(col, row)

And if you want more Python style, then replace it with previous_cell_states = [row.copy() for row in cell_matrix].

Param-pam-pam

life4

Well that's it! Here it is, ours ready code.


NoDB: bug or feature

You have already noticed that the link to our code is long, like sentences in Julio Cortazar's books. But that's how it's meant to be. This is a modern NoDB approach (more precisely, URL-based storage), when small programs are slightly compressed and stored directly in the URL. Small, lieutenant, I said small!

In general, a few hours before publication previous article The main database broke down, and I temporarily put the project on the alternate NoDB airfield, which was ready earlier. As soon as time appears, I will return saving to a permanent database and short links.


Let's add interactivity

In the original article, the user could draw creatures on the field himself. Let's do the same on p5pybut only slightly improve the use of the source code:

  1. Let’s immediately turn on the “intervention” mode. Let the player be able to immediately add new pieces: is_game_running = True.

  2. Let's remove the on/off button for this drawing, since it is unclear why it is needed. Only the interface overloads.

  3. Let's replace the game stop button with… an automatic action. By clicking the mouse, the player can draw a new figure, and the game pauses for this time. As soon as the player releases the mouse, the game resumes. Yes, here I consciously sacrifice the opportunity to draw many figures at once during a pause, but we have a demo, and for a demo it will be more intuitive.

Add to def draw():

…hint:

fill(255, 140)
    text_size(20)
    text("Вы можете нарисовать фигуру мышкой", 30, 30)

…and start/stop the game:

if is_game_running:
    update_game_state()
if mouse_is_pressed:
    toggle_cell_at_mouse_position()
    is_game_running = False
def toggle_cell_at_mouse_position():
    col = int(mouse_x / cell_width)
    row = int(mouse_y / cell_width)

    if 0 <= col < column_count and 0 <= row < row_count:
        cell_matrix[col][row] = True

def mouse_released():
    global is_game_running
    is_game_running = True

The end. Draw…

life7

Link to code.


When mobile friends are with me

If you suddenly read this article on a mobile phone and clicked on one of the links above, the result was not good: the field in the original article is of a fixed width and extends beyond the boundaries. But our IDE is for p5py adapted for mobile. Let's fix the code right away. Let's just add automatic calculation of width and height for the current screen. Let's replace this:

cell_width = 15

column_count = 80
row_count = 45

size(column_count * cell_width, row_count * cell_width)

to this:

cell_width = 15

 # Проверяем условия для установки размеров окна
 # Предположим, что 600 — это ширина типичного мобильного устройства
if display_width < 600:
    w = display_width
    h = display_height * 2 // 3
else:
    w = display_width * 2 // 4
    h = display_height * 2 // 4

 # Рассчитываем количество строк и столбцов
column_count = w // cell_width
row_count = h // cell_width

w = column_count * cell_width
h = row_count * cell_width

size(w, h)
life8_1

life8_1

And now you can click from mobile phones: here.


What else can be done?

Let's improve usability.

The next step could be a simple modification: “Make the inscription disappear after the first mouse click. After all, the user has already read the instructions and completed the necessary actions.”

For someone who has just started learning Python, it might be interesting to do this task yourself. If you wish, share your solutions and improvements in the comments.

Note

Module p5py has many limitations. Unlike Godot, it is not designed for large and medium-sized projects. But good for small demonstrations and educational purposes.

Community. Where it is difficult for one…

I can handle it together with you. Mini IDE and p5py turned out to be good and kind. I decided to slowly, little by little, gather the community around p5py. Here is my old and almost empty Telegram channel: https://t.me/p4kids. I will try to gather interested teachers, lecturers and parents around this technology.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *