Skip to content
Image with logo, providing a link to the home page
  • United Stated of America flag, representing the option for the English language.
  • Bandeira do Brasil, simbolizando a opção pelo idioma Português do Brasil.

Ideas, Rules, Simulation: Randomness and Noise

Images with noises (drawing of random pixels), resulting from the programs created for JavaScript with HTML Canvas, GDScript with Godot Engine, Python with PyGame, and Lua with LÖVE. The image also provides a link to this website: <www.francogarcia.com>, as well as the account francogarciacom, used for the Twitter and Instagram of the author.

Image credits: Image created by the author using the program Inkscape; icons by Font Awesome.

Requirements

The material for Ideas, Rules, Simulations promotes project based learning (interactive simulations and digital games) using multimedia resources. As such, it is complimentary to the material of Learn Programming, which introduces fundamentals and basic programming techniques for programming, with examples for JavaScript, Python, Lua and GDScript (for Godot Engine).

For Ideas, Rules, Simulations you will need a configured development environment for one of the previous languages:

JavaScript (for browsers) and GDScript provide built-in support for multimedia content.

I am also considering improving the online programming editors provided in this website, to support an interactive course. Currently, the page with tools provide options for:

The editors support images in the browser. However, Python and Lua use the JavaScript syntax for the canvas. If I create an abstraction and add support for audio, they could become valid tools (at least for the first activities).

However, it is worth configuring an Integrated Development Environment (IDE), or a combination of text editor with interpreter as soon as possible, to enable you to program using your machine with greater efficiency. Online environments are practical, though local environments are potentially more complete, efficient, faster and customizable. If you want to become a professional, you will need a configured development environment. The sooner you do it, the better.

Version in Video

This entry has video versions on the author's YouTube channel:

However, this text version is more complete.

Documentation

Practice consulting the documentation:

Most links have a search field. In Lua, the documentation is in a single page. You can search for entries using the shortcut Ctrl F (search).

Determinism, Indeterminism, Randomness

Although one can think about a system or algorithm as a black box that provides an output given an input, the output is not always predictable. At times, the output may depend on external factors such as chance.

Processes or systems on which the output depends only in the provided inputs (in other words, on which the results are predictable and repeatable, if one knows the algorithm) are called deterministic. For instance, the sum of two integer numbers is a deterministic mathematical operation. The value of is -- because that the convention; there is no other possibility. Thus, any implementation that a function add(x, y) called as add(1, 1) with a result different from 2 would be incorrect.

However, not every process or system is deterministic. If there is a chance of different results occurring for a very same input, there is indeterminism or randomness involved.

In an indeterministic or random process, a same input can lead to different outputs. Perhaps that these outputs belong of a set of well-known results. For instance, in a six-sided dice, a throw will result into the number 1, 2, 3, 4, 5, or 6. If it is an honest dice, every possible result is equiprobable (which means they have the same chance of occurring).

Perhaps the outputs will be completely different at every run. Perhaps the result will be unknown; an undetermined state belonging to some possibilities, such as the case of the Schrödinger's cat. Perhaps the result depends on a probability of occurrence; over multiple runs, the event is expected to happen, though there are not guarantees. Thus, the discussion of the existence of something that is truly random is more complex.

Nevertheless, programming often operates with indeterminism and randomness by means of pseudorandom numbers. Although many people use the term random numbers and random number generators (RNGs), computers typically use pseudorandom numbers and pseudorandom number generators (PRNGs). Thus, whenever this material mentions random numbers, the mention will usually refer to pseudorandom numbers, unless stated otherwise.

Pseudorandom numbers have been previously presented in Learn Programming: Repetition Structures. In this topic, they be remembered, because randomness is important to simulate stochastic processes. In practice, the term stochastic can be understood as random or with randomness. A system that simulates a stochastic process is called a stochastic simulations.

Stochastic simulations are interesting because they may generate different results every run. In other words, they are appealing for a series called Ideas, Rules, Simulation. Thus, it is worth starting by a complete chaos.

A Noise as an Image

In this topic, we will create the first stochastic simulation, as a noise. A noise is irrelevant or meaningless information that pollutes a signal. In other words, before we create meaningful simulations (a signal), we will create chaotic simulations.

A simple way of achieving that is creating a noise using pseudorandom numbers.

HTML Canvas for JavaScript

As in the Introduction of Ideas, Rules, Simulation, JavaScript requires an auxiliary HTML file to declare the canvas. You can choose the name of the HTML file (for instance, index.html). The follow example assumes that the JavaScript fill is named script.js and the canvas will have the identifier (id) canvas. If you change the values, remember to modify them in the files as needed.

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <title>www.FrancoGarcia.com</title>
    <meta name="author" content="Franco Eusébio Garcia">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div style="text-align: center;">
      <canvas id="canvas"
              width="1"
              height="1"
              style="border: 1px solid #000000;">
      >
          Accessibility: alternative text for graphical content.
      </canvas>
    </div>

    <!-- NOTE Updated with the name of the JavaScript file. -->
    <script src="./script.js"></script>
  </body>
</html>

The file also keeps the reminder about accessibility. In this topic, the content will become even more inaccessible for people with certain vision disabilities, due to the addition of graphical content without alternatives to convey the contents.

In the future, the intention is discussing ways to make simulations more accessible. At this time, this reminder is only informative, to raise awareness of the importance of accessibility.

Pseudorandom Number Generators (PRNGs) for GDScript, JavaScript, Python and Lua

In Learn Programming: Repetition Structures, subroutines (functions) to create pseudorandom integer numbers. The next blocks of code present random_integer(), and define a complementary function called random_float(). The standard library of any programming language typically provides, at least, one of the implementations. Good libraries tend to provide both, and more complex statistical distributions (besides the uniform distribution).

extends Node


func random_integer(inclusive_minimum, inclusive_maximum):
    var minimum = ceil(inclusive_minimum)
    var maximum = floor(inclusive_maximum)

    # randi(): [0.0, 1.0[
    return randi() % int(maximum + 1 - minimum) + minimum


func random_float():
    return randf()


func _ready():
    randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())

    print(random_integer(0, 10))
    print(random_integer(0, 10))
    print(random_integer(0, 10))

    print(random_float())
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_integer(inclusive_minimum, inclusive_maximum) {
    let minimum = Math.ceil(inclusive_minimum)
    let maximum = Math.floor(inclusive_maximum)

    return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}


function random_float() {
    return Math.random()
}


function main() {
    console.log(random_integer(0, 10))
    console.log(random_integer(0, 10))
    console.log(random_integer(0, 10))

    console.log(random_float())
}


main()
import random


def random_integer(inclusive_minimum, inclusive_maximum):
    return random.randint(inclusive_minimum, inclusive_maximum)


def random_float():
    return random.random()


def main():
    random.seed()

    print(random_integer(0, 10))
    print(random_integer(0, 10))
    print(random_integer(0, 10))

    print(random_float())


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


function random_integer(inclusive_minimum, inclusive_maximum)
    return math.random(inclusive_minimum, inclusive_maximum)
end


function random_float()
    return math.random()
end


function main()
    math.randomseed(os.time())

    print(random_integer(0, 10))
    print(random_integer(0, 10))
    print(random_integer(0, 10))

    print(random_float())
end

main()

The implementations provide a main() function as the entry point (as defined in Learn Programming: Entry Point and Program Structure).

In all cases, pseudorandom numbers require initializing a value for the seed used for generating the sequence of numbers. Each entry point use the time or other standard value for the considered language. The exception is JavaScript for browsers; browsers automatically initialize the seed for Math.random().

The function random_integer() generates an integer number belonging to the interval defined by the minimum value (inclusive) and the maximum value (inclusive). The function random_float() generates a real number belonging to the interval 0.0 to 1.0. More details have been previously presented in Learn Programming: Repetition Structures.

Randomly Coloring Pixels

To make it easier to read the code, the implementations define the procedure franco_draw_pixel() to draw a pixel to the screen. The implementation use the drawing code presented on Ideas, Rules, Simulation: Pixels and Drawing Primitives (Points, Lines, and Arcs). The name is not draw_pixel to avoid name conflicts in the GDScript version (and to set a standard for all languages).

# Root must be a Node that allows drawing using _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240


func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func random_float():
    return randf()


func _ready():
    randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())

    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Hello, my name is Franco!")


func _draw():
    VisualServer.set_default_clear_color(Color.black)

    franco_draw_pixel(10, 20, Color(random_float(), random_float(), random_float()))

    for x in range(0, WIDTH):
        franco_draw_pixel(x, 30, Color(random_float(), random_float(), random_float()))
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_integer(inclusive_minimum, inclusive_maximum) {
    let minimum = Math.ceil(inclusive_minimum)
    let maximum = Math.floor(inclusive_maximum)

    return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    franco_draw_pixel(10, 20, `rgb(${random_integer(0, 255)}, ${random_integer(0, 255)}, ${random_integer(0, 255)})`)
    for (let x = 0; x < WIDTH; ++x) {
        franco_draw_pixel(x, 30, `rgb(${random_integer(0, 255)}, ${random_integer(0, 255)}, ${random_integer(0, 255)})`)
    }
}


main()
import pygame
import math
import random
import sys

from pygame import gfxdraw
from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hello, my name is Franco!")


def random_integer(inclusive_minimum, inclusive_maximum):
    return random.randint(inclusive_minimum, inclusive_maximum)


def franco_draw_pixel(x, y, color):
    window.set_at((x, y), color)


def main():
    random.seed()

    window.fill(BLACK)

    franco_draw_pixel(10, 20, (random_integer(0, 255), random_integer(0, 255), random_integer(0, 255)))
    for x in range(WIDTH):
        franco_draw_pixel(x, 30, (random_integer(0, 255), random_integer(0, 255), random_integer(0, 255)))

    pygame.display.flip()

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)


if (__name__ == "__main__"):
    main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


function random_float()
    return math.random()
end


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function love.load()
    math.randomseed(os.time())

    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Hello, my name is Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_pixel(10, 20, {random_float(), random_float(), random_float()})
    for x = 0, WIDTH - 1, 1 do
        franco_draw_pixel(x, 30, {random_float(), random_float(), random_float()})
    end
end

The examples randomly color a pixel at the point (10, 20), and in the whole line with y = 30.

A possible result of the drawing is displayed on the next canvas. It is not a displaying error; it is actually an image of a noise. The colors do not have a pattern; they are random. If a pixel disappears, the random color was black.

Pixels with random colors on the canvas.

The expression "possible result" refers to the fact that the colors will change every time the program is run (due to the use of pseudorandom number to choose them). If you want to view the result on practice, you can update this page on your browser (shortcut: F5) a few times to watch the results. Although it is possible, it is unlikely that two consecutive runs will show the same colors.

Unlikely, not impossible. In fact, the use of pseudorandom numbers have some limitations. It is worth discussing two.

The first limitation is the possibility of forcing two executions to show the same result. To do this, it is sufficient to use the very same seed to generate the numbers in both runs. This is the reason to use the machine's time when initializing the seed: assuming that the clock is not changed, the next run will have a different time from the first, resulting in a different seed.

Evidently, this is only valid for a same machine. Two different machines could generate the same seed if they both run the program with the exact same time. To avoid this problem, some library provides alternatives to generate seeds that are more robust than the machine's time.

On the other hand, the first limitation can be beneficial in some scenarios. For instance, it can be useful to use the same seed to test systems working with pseudorandom numbers, because the generated numbers will be the same. If the program used the generated numbers at the same order and for the same purposes, the system would become deterministic for the chosen seed.

Animated Colors in Lua with LÖVE

The colors in the example using LÖVE will change at each update from the engine; thus, instead of static colors, the example provides an animation. In one of the next subsections, we will remove the animation from the LÖVE version for didactic purposes (the video version can be illustrative for a gentler approach). Next, we will add the animation to all programming languages.

Coloring All Pixels of a Window

To color all pixels on the window, it suffices to add a new repetition structure that iterates on all lines of the resolution. The use of two nested repetition structures will transverse all lines and columns of the window to fill it with random colors.

# Root must be a Node that allows drawing using _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240


func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func random_float():
    return randf()


func _ready():
    randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())

    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Hello, my name is Franco!")


func _draw():
    VisualServer.set_default_clear_color(Color.black)

    for x in range(0, WIDTH):
        for y in range(0, HEIGHT):
            franco_draw_pixel(x, y, Color(random_float(), random_float(), random_float()))
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_integer(inclusive_minimum, inclusive_maximum) {
    let minimum = Math.ceil(inclusive_minimum)
    let maximum = Math.floor(inclusive_maximum)

    return Math.floor(minimum + Math.random() * (maximum + 1 - minimum))
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    for (let x = 0; x < WIDTH; ++x) {
        for (let y = 0; y < HEIGHT; ++y) {
            franco_draw_pixel(x, y, `rgb(${random_integer(0, 255)}, ${random_integer(0, 255)}, ${random_integer(0, 255)})`)
        }
    }
}


main()
import pygame
import math
import random
import sys

from pygame import gfxdraw
from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hello, my name is Franco!")


def random_integer(inclusive_minimum, inclusive_maximum):
    return random.randint(inclusive_minimum, inclusive_maximum)


def franco_draw_pixel(x, y, color):
    window.set_at((x, y), color)


def main():
    random.seed()

    window.fill(BLACK)

    for x in range(WIDTH):
        for y in range(HEIGHT):
            franco_draw_pixel(x, y, (random_integer(0, 255), random_integer(0, 255), random_integer(0, 255)))

    pygame.display.flip()

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)


if (__name__ == "__main__"):
    main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


function random_float()
    return math.random()
end


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function love.load()
    math.randomseed(os.time())

    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Hello, my name is Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    for x = 0, WIDTH - 1, 1 do
        for y = 0, HEIGHT - 1, 1 do
            franco_draw_pixel(x, y, {random_float(), random_float(), random_float()})
        end
    end
end

A possible result of the drawing is displayed on the next canvas. Once again, it is not an error from the screen; it is correctly showing the noise.

Pixels with random colors on the entire canvas.

The Lua version using LÖVE is still animated. Before introducing animations to all versions, we will remove the animation from it.

Limiting the Number of Colors

Instead of a image with many colors, it would also be possible to create an image with two colors. For instance, it would be a black and white image.

To define alternative flows in programs, one can use a conditional structure (previously defined in Learn Programming: Conditional Structures). Conditional structures have been used in the text version of Ideas, Rules, Simulation: Pixels and Drawing Primitives (Points, Lines, and Arcs), though they have not been explored in the video version yet. Thus, it is time to present them.

An if/then/else structure define two possible alternative flows, one of which chosen according to the result of a logic expression.

However, in this case it is sufficient to use an if/then structure. For instance, random_float() could generate a real number. As the function to create a real number returns a value between 0.0 and 1.0, the values can be split in two different intervals. We can assume black color; if a number less than 0.5 is generated, we select white for pixel instead.

# Root must be a Node that allows drawing using _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240


func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func random_float():
    return randf()


func _ready():
    randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())

    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Hello, my name is Franco!")


func _draw():
    VisualServer.set_default_clear_color(Color.black)

    for x in range(0, WIDTH):
        for y in range(0, HEIGHT):
            var color = Color.black
            if (random_float() < 0.5):
                color = Color.white

            franco_draw_pixel(x, y, color)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_float() {
    return Math.random()
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    for (let x = 0; x < WIDTH; ++x) {
        for (let y = 0; y < HEIGHT; ++y) {
            let color = BLACK
            if (random_float() < 0.5) {
                color = WHITE
            }

            franco_draw_pixel(x, y, color)
        }
    }
}


main()
import pygame
import math
import random
import sys

from pygame import gfxdraw
from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hello, my name is Franco!")


def random_float():
    return random.random()


def franco_draw_pixel(x, y, color):
    window.set_at((x, y), color)


def main():
    random.seed()

    window.fill(BLACK)

    for x in range(WIDTH):
        for y in range(HEIGHT):
            color = BLACK
            if (random_float() < 0.5):
                color = WHITE

            franco_draw_pixel(x, y, color)

    pygame.display.flip()

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)


if (__name__ == "__main__"):
    main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


local pixels = {}


function random_float()
    return math.random()
end


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function love.load()
    math.randomseed(os.time())

    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Hello, my name is Franco!")

    for x = 0, WIDTH - 1, 1 do
        for y = 0, HEIGHT - 1, 1 do
            local color = BLACK
            if (random_float() < 0.5) then
                color = WHITE
            end

            table.insert(pixels, color)
        end
    end
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    local pixel_index = 1
    for x = 0, WIDTH - 1, 1 do
        for y = 0, HEIGHT - 1, 1 do
            franco_draw_pixel(x, y, pixels[pixel_index])
            pixel_index = pixel_index + 1
        end
    end
end

A possible result of the drawing is displayed on the next canvas. Once again, it is not an error from the screen; it is correctly showing the noise.

Pixels that have colored with black or white randomly on the whole canvas.

The Lua version using LÖVE uses an array (as a Lua table), introduced in Learn Programming: Arrays, Strings, Collections and Data Structures. At this time, it is not necessary to understand how to use the array. The use only highlights how to create a static image in engines that update the screen at every iteration: the data is pre-generated to create the illustration once, then the data is redrawn at every update.

Creating an Animation

In the case of Lua with LÖVE, an animation has been generated accidentally, due to how the engine runs the code written in love.draw(), which is updated at every update of the LÖVE's engine.

As it has been commented in Learn Programming: Records during the implementation of Conway's Game of Life, an animation results from the repetition of outputs with modifications at each iteration of a loop. As commented in the section of the Game of Life, the animation that results from the repetitions is similar to a flip book with drawings on paper: by alternating quickly between pages, the illusion of an animation is created.

To implement the same effect in the other languages, it is sufficient to use a repetition structure. On higher-level structures, such as frameworks or engines, it can be necessary to use subroutines:

  1. In GDScript, a method _process() must be added, and it must call update() to force an update of Control. Control is implemented in a way that minimizes redrawing to the screen; thus, it is necessary to inform the engine when an update must be performed;

  2. In JavaScript, requestAnimationFrame() can be used recursively to redraw content;

  3. In Python with PyGame, we will move the drawing code to the repetition structure that controls the window. This is the most traditional way, as well as the lowest-level one: the use of a repetition structure that ends only when the window is closed. In fact, all other implementations perform similar operations (behind the scenes; the complexity is handled by the framework or engine).

    Additionally, redrawing the content at every iteration of the loop will also fix a problem that has not been mentioned in previous topic (at it was not of interest at the time): if the window was moved or if another window was positioned over it, now the content will be correctly displayed after the next redrawing;

  4. In Lua with LÖVE, is suffices to remove the use of the array and restore the original code.

Thus, with some changes, all implementations will have an animated noise. The result will be similar to what happened in the past with television (TV) machines that were with "no signal".

# Root must be a Node that allows drawing using _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240


func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func random_float():
    return randf()


func _ready():
    randomize() # rand_seed(OS.get_unix_time()) / rand_seed(OS.get_system_time_secs())

    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Hello, my name is Franco!")


func _process(delta):
    update()


func _draw():
    VisualServer.set_default_clear_color(Color.black)

    for x in range(0, WIDTH):
        for y in range(0, HEIGHT):
            var color = Color.black
            if (random_float() < 0.5):
                color = Color.white

            franco_draw_pixel(x, y, color)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_float() {
    return Math.random()
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    for (let x = 0; x < WIDTH; ++x) {
        for (let y = 0; y < HEIGHT; ++y) {
            let color = BLACK
            if (random_float() < 0.5) {
                color = WHITE
            }

            franco_draw_pixel(x, y, color)
        }
    }

    requestAnimationFrame(draw)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    draw()
}


main()
import pygame
import math
import random
import sys

from pygame import gfxdraw
from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
WHITE: Final = (255, 255, 255)
BLACK: Final = (0, 0, 0)


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Hello, my name is Franco!")


def random_float():
    return random.random()


def franco_draw_pixel(x, y, color):
    window.set_at((x, y), color)


def main():
    random.seed()

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

        window.fill(BLACK)

        for x in range(WIDTH):
            for y in range(HEIGHT):
                color = BLACK
                if (random_float() < 0.5):
                    color = WHITE

                franco_draw_pixel(x, y, color)

        pygame.display.flip()


if (__name__ == "__main__"):
    main()
local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


function random_float()
    return math.random()
end


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function love.load()
    math.randomseed(os.time())

    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Hello, my name is Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    for x = 0, WIDTH - 1, 1 do
        for y = 0, HEIGHT - 1, 1 do
            local color = BLACK
            if (random_float() < 0.5) then
                color = WHITE
            end

            franco_draw_pixel(x, y, color)
        end
    end
end

A possible result of the drawing is displayed on the next canvas. The updates to the drawing are performed at every second, to save energy (or battery) of your device. If you run the original code in your browser, the animation will the significantly faster. For reference, you can use the Task Manager of your system to check the use of the processor and memory for the programs.

Pixels that have colored with black or white randomly on the whole canvas. The use of requestAnimationFrame() in draw() creates the animation.

How to control the velocity of animations will be discussed in the future. At this time, the implementations run the code at the maximum velocity allowed by the machine. This means that:

  1. The animations will be smooth and fluid;
  2. Machines with more powerful hardware will run the animation faster than machines with less powerful hardware;
  3. Cycles from the processor are wasted, because the updates can happen quicker than the maximum refresh rate of the screen.

For more robust programs, it can be useful to minimize unnecessary use of the processor. This is important both to optimize the multitasking performance of a machine, and to save energy, which contributes with practices for sustainability to preserve the environment. It is worth being a programmer who is responsible with the society and the environment.

Colorful Noise

If the conditional structure to choose the color is removed, the animation can be colored. To practice, try changing the code to do it.

Pixels colored with random colors on the entire canvas. The use of requestAnimationFrame() in draw() generates the animation.

For the solution, it is sufficient to change some lines of code in the desired implementation.

Concepts Covered From Learn Programming

Concepts from Learn Programming covered in this topic:

  1. Entry point;
  2. Output;
  3. Data types;
  4. Variables and constants;
  5. Arithmetic;
  6. Relational operations and comparisons;
  7. Logic operations;
  8. Conditional structures;
  9. Subroutines (functions and procedures);
  10. Repetition structures (or loops);
  11. Libraries.

It is worth noticing that even drawing primitives can use almost every basic programming concept.

Once again, the values for colors in Lua used tables (dictionaries) as array, described in Collections.

New Items for Your Inventory

Computational Thinking Abilities:

Tools:

  • Task manager.

Skills:

  • Using pseudorandom numbers;
  • Creating an animation.

Concepts:

  • Determinism (and deterministic);
  • Indeterminism (and indeterministic), randomness (and random);
  • Random numbers;
  • Pseudorandom numbers;
  • Random Number Generators (RNGs);
  • Pseudorandom Number Generators (PRNGs);
  • Stochastic processes and simulations;
  • Noise.

Programming resources:

  • Pseudorandom numbers;
  • Drawing all pixels on a window.

Practice

To learn programming, deliberate practice must follow the concepts. Try doing the next exercises to practice.

  1. Write a program that pain all pixels in the window with your favorite color;

  2. Write a program that fills the entire window with three different colors (for instance, red, green and blue);

  3. Change this topic's program to create whole lines with a same color, though with random colors for different lines. In other words, instead of coloring every pixel randomly, each line should have a random color.

    Randomly colored lines in the canvas.

    Tip. There are many ways of solving the problem. For a good solution, a few changes to the code are enough;

  4. Change the previous program to color all pixels of each row with a same color, though with random colors for different columns. This problem can be understood as a 90° rotation of the previous exercise;

  5. Challenge. Think about how to choose colors for the lines (or each pixel) following a more aesthetically pleasant pattern. For instance, using a color gradient.

    A good size of for a color gradient is 256 x 256 (256 lines by 256 columns), if 256 colors per primary color is assumed.

    Color gradient with shades of black, green and blue.

    The solution does not use random numbers; it uses arithmetic. As you may notice, the result is not a noise with random colors. Actually, it is an aesthetically pleasing image.

    Colors gradients will be discussed in a future topic. The problem can be complex for beginners who have started from Ideas, Rules, Simulation, though it is possible for people who have previously studied the material Learn Programming. Indeed, color gradients have been previously used as an example in Learn Programming: Files and Serialization (Marshalling).

Deepening

In Ideas, Rules, Simulation, this section provides complementary content.

LÖVE / Love2D: Canvas

An alternative to draw a static image in LÖVE consists of using a Canvas (documentation). The solution also becomes easier and clearer.

local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


local canvas = love.graphics.newCanvas(WIDTH, HEIGHT)


function random_float()
    return math.random()
end


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function love.load()
    math.randomseed(os.time())

    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Hello, my name is Franco!")

    love.graphics.setCanvas(canvas)
    for x = 0, WIDTH - 1, 1 do
        for y = 0, HEIGHT - 1, 1 do
            local color = BLACK
            if (random_float() < 0.5) then
                color = WHITE
            end

            franco_draw_pixel(x, y, color)
        end
    end
    love.graphics.setCanvas()
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    love.graphics.draw(canvas)
end

In this case, the desired content is drawn to the canvas a single time (for instance, in love.load()), and the content is copied at every update in love.draw().

JavaScript: Other Approaches for Animations

requestAnimationFrame() is a relatively modern approach to create animations in JavaScript for the browser. Older implementations may still use setInterval() or setTimeout() as alternatives.

The following subsections provide examples. In the code, the update rate per second is approximated by the FPS (frames per second) constant. Typical values for FPS FPS include 25 (common in moves), 30 (common in moves and games), 60 (common in games) and 144 (or 240; common in high-performance screens for competitive games).

For the example of a noise, the choice is not important; the larger the value, the higher will be the number of updates (in this case, of redraws) of the image per second. In other words, a faster animation.

Animations Using setInterval()

const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_float() {
    return Math.random()
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    for (let x = 0; x < WIDTH; ++x) {
        for (let y = 0; y < HEIGHT; ++y) {
            let color = BLACK
            if (random_float() < 0.5) {
                color = WHITE
            }

            franco_draw_pixel(x, y, color)
        }
    }
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    const FPS = 10
    setInterval(draw, 1000.0 / FPS)
}


main()

Animations Using setTimeout()

const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function random_float() {
    return Math.random()
}


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height)
    context.fillStyle = BLACK
    context.fillRect(0, 0, canvas.width, canvas.height)

    for (let x = 0; x < WIDTH; ++x) {
        for (let y = 0; y < HEIGHT; ++y) {
            let color = BLACK
            if (random_float() < 0.5) {
                color = WHITE
            }

            franco_draw_pixel(x, y, color)
        }
    }

    const FPS = 1
    setTimeout(draw, 1000.0 / FPS)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    document.title = "Hello, my name is Franco!"

    draw()
}


main()

Next Steps

In three topics of Ideas, Rules, Simulation, we have created a window, learned how to create and use drawing primitives, and defined a stochastic animation.

With pseudorandom numbers, it is possible to keep creating simulations that are increasingly complex. This series has started with the chaos: a noise. Perhaps it is somewhat poetic start simulations with noise, because there hardly can exist something more random and with less meaning than content without any pattern.

From order and patterns will emerge meaning. In other words, we are going to need rules. Thus, the moment to start creating order and defining simulations with more meaningful content is approaching. This means that we will start converting ideas into rules to create simulations. Ideas, Rules, Simulation. Coincidence?

We will start with something simple. One of the simplest examples is the creation of simulations to throw coins and dices. With the introduction of some additional drawing primitives, it will be possible to draw them.

Furthermore, if you have created a creative or interesting illustration, consider sharing it. Alternatively, if you have found this material useful, you can also share it. If possible, use the hashtags #IdeasRulesSimulation and #FrancoGarciaCom.

I thank you for your attention. See you soon!

Ideas, Rules, Simulation

  1. Motivation;
  2. Introduction: Window and Hello World;
  3. Pixels and drawing primitives (points, lines, and arcs);
  4. Randomness and noise;
  5. Coins and dice, rectangles and squares;
  6. Drawing with drawing primitives (strokes and fillings for circles, ellipses and polygons);
  7. Saving and loading image files;
  8. ...

This material is a work in progress; therefore, if you have arrived early and the previous items do not have links, please return to this page to check out updates.

If you wish to contact me or have any questions, you can chat with me by:

Information about contact and social networks are also available at the footer of every page.

Your opinion about the series will be fundamental to enable me to create a material that is more accessible and simpler for more people.

  • Video
  • Informatics
  • Programming
  • Beginner
  • Computational Thinking
  • Ideas, Rules, Simulation
  • Python
  • PyGame
  • Lua
  • LÖVE (Love2D)
  • Javascript
  • HTML Canvas
  • Godot
  • Gdscript