Pull to refresh

Writing The Matrix in Python

Reading time6 min
Views3.3K

There are different ways to learn new things. Some like to study theory first, and then apply it in practice. Others prefer to learn solely from examples found at Stackoverflow. And someone just downloads helicopter control skills and martial arts techniques via dedicated channel directly into the brain.

In any case, practical exercises are indispensable. After the accelerated download of knowledge, Neo still must spar with Morpheus to learn how to put the terabytes of downloaded skills into practice. Just the exercises are different. It is one thing—to fly dashingly up to the ceiling and break through the walls of the oriental gym, and quite another—to methodically polish your skills for hours.

Training in the Matrix
Training in the Matrix

Defining the goals

Programming textbooks usually do not indulge us with variety of examples. There are, of course, exceptions, but in most manuals, exercises are similar to each other and not particularly interesting: create another address book, draw a circle using turtle, develop a website for a store selling some kind of "necessary" advertising nonsense. Too far from the authentic imitation of "The Matrix". Although…

How about taking over the control and starting to invent exercises yourself?

Would you like to write your own personal little "Matrix"? Of course, not the one with skyscrapers, stylish phones of the time, and the ubiquitous invincible Agent Smiths. We surely will need a couple of more months of learning for that. But any beginner programmer can write a model of the cult splash screensaver with the green streams of digits flowing down the screen. This is what we are going to do.

It is possible to program such screensaver in almost any language. Let's try to create it in the "great and mighty" Python.

Original Matrix with green hieroglyphs
Original Matrix with green hieroglyphs

The technical requirements

First, we need to decide what we want to end up with. We should develop technical requirements—it is always useful. Just think of "The Matrix" for a moment, and your memory will probably suggest a familiar image—the dark console window in which streams of green numbers will flow. To make it even more interesting, let them move at different speeds. Each stream should have a beginning—a bright green zero—and an end. By the way, let the speeds of movement of the beginning and the end of the stream will also be different and determined randomly.

It doesn't sound so hard, does it? Now it's time to just write the code. Let's get started.

Technical task in "The Matrix"
Technical task in "The Matrix"

Preparing the tools

At first we will connect all the necessary modules, starting with the standard ones:

import sys
import random
import time

To work with the Windows console, we need the bext and the colorama modules. How to install them using pip, you probably already know. You need to connect them. But let's do it according to all the rules—with a check:

try:
    import bext, colorama
except ImportError:
    print ('Bext and colorama modules are required to run the program')
    sys.exit ()

Let's prepare the console for work:

bext.title ('Matrix')
bext.clear ()
bext.hide ()
width, height = bext.size ()
width -= 1
height -= 1

Now it remains only to create constants with colors for the colorama module. We need green and dark green:

lgreen = colorama.Fore.LIGHTGREEN_EX
green = colorama.Fore.GREEN

Windows is a tricky and controversial thing. The base color of green in the console is dark green.

Standard tools in "The Matrix"
Standard tools in "The Matrix"

Inventing the antimatter

Now let's think about the most important question: how can we program the flowing drops of the "Matrix"? Each drop is an object. All drops are different, but they behave the same. Therefore, we need to create a class in which we will describe the main actions with the drop and all its attributes.

To make the drops flow at different speeds, let's set a random delay timeout for each of them—in steps. Some drops will move at each step of updating our picture, some—less often.

What about the upper end of the stream? It must "dry", and at its own speed. Let's not invent anything complicated. Let the upper end also be a drop, but black. Such a "drop" when moving will not add digits, but rather erase them. It turns out just some kind of anti-drop, anti-matter—beautiful and stylish.

Since the drop and the anti-drop behave the same, let's program them in the same class.

Creating the drops

So, we will perform all actions with drops and anti-drops in the class methods. Let's call it Drop and write a method for creating a class object:

def __init__ (self):
    self.x = random.randint (0, width)
    self.y = -1
    self.drop_type = random.randint (0, 1)
    self.timeout = random.randint (0, 3)
    self.wait_count = random.randint (0, 3)

Everything is clear with the x and y attributes. The second one is '-1' so that the drop is not shown on the screen ahead of time. The timeout and wait_count attributes are needed to provide different drop rates. The first sets the constant flow rate, the second sets the iteration counter.

Moving the drops

Now let's write a method for moving a drop, taking into account its speed.

def move (self):
    if drop.wait_count < drop.timeout:
        drop.wait_count += 1
        return False
    else:
        drop.wait_count = 0
        drop.y += 1
        return True

The method returns a boolean value—the fact that the drop has moved. Or antidrops—all the same.

Drawing the stream

Now we’ve done with moving the drops. It's time to draw!

def draw (self):
    if self.drop_type == 1:
        symbol = str (random.randint (1, 9))
        con_print (self.x, self.y, green, symbol)
        self.zero_draw ()
    else:
        con_print (self.x, self.y, green, ' ')

Here we call two new methods not yet written: con_print and zero_draw. The first one will output the character of the desired color to the specified location in the console window. The second will draw an extra bright zero at the beginning of the trickle.

Here is the second method:

def zero_draw (self):
    if (self.y < height):
    con_print (self.x, self.y+1, lgreen, '0')
Rain in "The Matrix"
Rain in "The Matrix"

Organizing the reincarnation of the drops

We have already understood that what we are getting is not a simple "Matrix", but a philosophical one. Therefore, when the drops and anti-drops reach the bottom edge of the screen, let's make them reborn. They will get a new life at the upper border. This will only cost us two lines:

def renew (self):
    self.__init__ ()

Now we have everything we need. The Drop class is ready.

Outputting text to the console

Let's break out of the boundaries of the Drop class and write two functions for displaying text in the console window.

If we try to print something in the lower right corner of the console window, then another line will automatically be added to the bottom. Nothing can be done: in this place in our ideal "Matrix" there will be a "broken pixel":

def is_rb_corner (x, y):
    return x==width and y==height

Now everything is ready to print the requested character in the right place.

def con_print (x, y, color, symbol):
    if not is_rb_corner (x, y):
        bext.goto (x, y)
        sys.stdout.write (color)
        print (symbol, end='')

Getting it all together

All components of our future "Matrix" are ready. Now it remains to put everything together and run.

Let's create an array of drops and antidrops (what type of object is born is a matter of chance).

drops = []
for i in range (1, width*2//3):
    drop = Drop ()
    drops.append (drop)

And finally, the most important loop:

while True:
    for drop in drops:
        if drop.move ():
            drop.draw ()
            if drop.y >= height:
                drop.renew ()
    key = bext.getKey (blocking = False)
    if key == 'esc':
        bext.clear ()
        sys.exit ()
    time.sleep (0.02)

The entire program

import sys
import random
import time

try:
    import bext, colorama
except ImportError:
    print ('Bext and colorama modules are required to run the program')
    sys.exit ()

class Drop:
    def __init__ (self):
        self.x = random.randint (0, width)
        self.y = -1
        self.drop_type = random.randint (0, 1)
        self.timeout = random.randint (0, 3)
        self.wait_count = random.randint (0, 3)
    def renew (self):
        self.__init__ ()
    def move (self):
        if drop.wait_count < drop.timeout:
            drop.wait_count += 1
            return False
        else:
            drop.wait_count = 0
            drop.y += 1
            return True
    def draw (self):
        if self.drop_type == 1:
            symbol = str (random.randint (1, 9))
            con_print (self.x, self.y, green, symbol)
            self.zero_draw ()
        else:
            con_print (self.x, self.y, green, ' ')
    def zero_draw (self):
        if (self.y < height):
            con_print (self.x, self.y+1, lgreen, '0')

def is_rb_corner (x, y):
    return x==width and y==height

def con_print (x, y, color, symbol):
    if not is_rb_corner (x, y):
        bext.goto (x, y)
        sys.stdout.write (color)
        print (symbol, end='')

bext.title ('Matrix')
bext.clear ()
bext.hide ()
width, height = bext.size ()
width -= 1
height -= 1

green = colorama.Fore.GREEN
lgreen = colorama.Fore.LIGHTGREEN_EX

drops = []
for i in range (1, width*2//3):
    drop = Drop ()
    drops.append (drop)

while True:
    for drop in drops:
        if drop.move ():
            drop.draw ()
            if drop.y >= height:
                drop.renew ()
    key = bext.getKey (blocking = False)
    if key == 'esc':
        bext.clear ()
        sys.exit ()
    time.sleep (0.02)

You can also experiment with this program. Try adding your own additional unique effects.
I wonder what your "Matrix" will be like.

Tags:
Hubs:
Total votes 11: ↑10 and ↓1+9
Comments0

Articles