IPython Blocks

Welcome to the IPython Blocks! This is a grid of colored blocks we can use to practice all sorts of programming concepts. There's a tutorial on IPython Blocks at http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_demo.ipynb, but you don't need to read that now; just follow along here.

We start by creating our grid and giving it a name. We'll call it... hmm... let's see... how about "grid"?

In [None]:
!pip install ipythonblocks
In [None]:
from ipythonblocks import BlockGrid
grid = BlockGrid(8, 8, fill=(123, 234, 123))
grid

We can refer to each cell in the grid by its coordinates from the upper-left corner. The first index is the number of steps down, the second is the number of steps to the right.

In [None]:
grid[0, 0]

In programming, colors are often referred to by an "RGB tuple". That's a series of three numbers, representing how much red, how much green, and how much blue, respectively. For the grid, those numbers go on a scale from 0 to 255. So pure red is (255, 0, 0), black is (0, 0, 0), white is (255, 255, 255), and so on.

If we assign an "RGB tuple" to a cell from the grid, that cell takes on that color.

In [None]:
grid[0, 0] = (0, 0, 0)
grid[0, 2] = (255, 0, 0)
grid[0, 4] = (255, 255, 255)
grid[0, 6] = (0, 150, 150)
grid.show()

Let's not spend all day typing those tedious RGB tuples! This looks like a perfect place for a dictionary. Lucky us, we can get a dictionary with all sorts of colors defined from the ipythonblocks module, the same place we got the BlockGrid.

In [None]:
from ipythonblocks import colors
In [None]:
colors

Wow, looks like somebody used to work at a paint store! OK, let's use some of those in the next row down.

In [None]:
grid[1, 1] = colors['Teal']
grid[1, 2] = colors['Thistle']
grid[1, 3] = colors['Peru']
grid.show()

Now suppose we want to color a bunch of blocks. Let's use a loop so we don't have to write a line for every single one.

In [None]:
row_number = 3
for column_number in [0, 1, 2, 3, 4, 5, 6]:
    grid[row_number, column_number] = colors['Chocolate']
grid.show()

The grid defines a "height" and a "width", these will be handy to work on cells all the way across it.

In [None]:
grid.width
In [None]:
row_number = 5
for column_number in range(grid.width):
    grid[row_number, column_number] = colors['Violet']
grid.show()

How about columns? And how about painting three columns at once? Let's use nested loops.

In [None]:
for column_number in [4, 5, 6]:
    for row_number in range(grid.height):
        grid[row_number, column_number] = colors['Crimson']
grid.show()

Our grid is looking cluttered. Let's define a function to start over by painting it all one color.

In [None]:
def one_color(target_grid, color):
    for row_number in range(target_grid.height):
        for column_number in range(target_grid.width):
            grid[row_number, column_number] = color
In [None]:
one_color(grid, colors['LightGreen'])
grid.show()

Animation

A couple of tricks will let our grid change over time. We'll need sleep from the time module, plus the clear_output function from IPython.

In [None]:
import time
from IPython.display import clear_output
for color in [colors['Red'], colors['Green'], colors['Blue'], colors['White'], colors['Purple']]:
    clear_output()
    one_color(grid, color)
    grid.show()
    time.sleep(1)

What if we used grid.show() like before, instead of clear_output? Not telling. Feel free to try it yourself.

OK, how about a checkerboard? We could paint the whole grid black, then paint red onto every second square.

In [None]:
one_color(grid, colors['Black'])
for row_number in range(grid.height):
    for column_number in range(grid.width):
        if is_even(column_number):
            grid[row_number, column_number] = colors['Yellow']
grid.show()

Icky, an error message! Because there's no such thing as a function is_even; I just made that up. But we can fix that!

In [None]:
def is_even(number):
    if number % 2 == 0:
        return True
    else:
        return False

Now go back up and re-run the earlier cell. And we get... hmm, good for bumblebees, bad for checkers. Let's make a modified version.

In [None]:
one_color(grid, colors['Black'])
for row_number in range(grid.height):
    for column_number in range(grid.width):
        if is_even(column_number + row_number):
            grid[row_number, column_number] = colors['Yellow']
grid.show()

How crazy can we get, using just what we've already learned? Let's make a diagonal color gradient, then pour some bleach on it.

In [None]:
base_color = [50, 50, 50]
for i in range(200):
    clear_output()
    for row_number in range(grid.height):
        for column_number in range(grid.width):
            grid[row_number, column_number] = (base_color[0], base_color[1]+row_number*20, base_color[2]+column_number*20)
    grid.show()
    base_color[0] += 1
    base_color[1] += 1
    base_color[2] += 1
    time.sleep(0.02)
            

Now it's time to try out some ideas of your own! Here are some suggestions:

  • Diagonal stripes
  • Draw random stripes
  • A flag
  • Put a dark square in the middle of the grid, then make a randomly wandering path from there!

Use a bigger grid if you need one. Remember at the very beginning when we created the grid?

grid = BlockGrid(8, 8, fill=(123, 234, 123))

You can create it again with some different numbers.

Code snippets

Here are some more little pieces of code that might be fun elements to work into your projects!

Random numbers

Each time you execute this cell, you'll see a different, random shade.

In [None]:
from random import randint
random_color = (randint(0, 255), randint(0, 255), randint(0, 255))
one_color(grid, random_color)
print(random_color)
grid.show()

Pythagorean theorem

Can you make the color depend on the distance from the upper-left corner? Can you draw an arc, for instance?

In [None]:
for row_number in range(grid.height):
    for column_number in range(grid.width):
        distance_to_corner = (row_number**2 + column_number**2)**0.5
        # now what can you do with that number?
grid.show()

ASCII Art

There's a file called ascii8x8.py in this directory that we can use to do ASCII-style art with our blocks.

In [None]:
from ascii8x8 import Font8x8
print(Font8x8['p'])

It's hard to tell, but that's a list of strings, and if those strings are stacked, they form a rough inversed "p". But we've also provided a function that, using that same data, and given the (row_number, column_number) of a place in the grid, tells us whether that spot should be on (True) or off (False).

In [None]:
from ascii8x8 import screen
screen('p', 0, 0)

What good does that do? Well, here's an example of applying it to the grid:

In [None]:
for row_number in range(8):
    for column_number in range(8):
        if screen('p', row_number, column_number):
            grid[row_number, column_number] = colors['Black']
grid.show()

So, perhaps you could...

  • Flash your name across the grid, one letter at a time
  • Print a letter upside-down
  • Print a letter whose color changes with time
  • Make a very wide grid and print a whole word in it