Avançar para o conteúdo
Imagem com logotipo, contendo link para a página inicial
  • 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.

Ideias, Regras, Simulação: Aleatoriedade e Ruído

Imagens com ruídos (desenhos de pixels aleatórios), resultantes de programas criados para JavaScript com HTML Canvas, GDScript com Godot Engine, Python com PyGame, e Lua com LÖVE. A imagem também apresenta um link para este website: <www.francogarcia.com>, assim como a conta francogarciacom, usada para o Twitter e Instagram do autor.

Créditos para a imagem: Imagem criada pelo autor usando o programa Inkscape; ícones de Font Awesome.

Pré-Requisitos

O material de Ideias, Regras, Simulação promove aprendizado baseado em projetos (simulações interativas e jogos digitais) com recursos multimídia. Assim, ele é complementar ao material de Aprenda Programação, que introduz fundamentos e técnicas básicas de programação, com exemplos para JavaScript, Python, Lua e GDScript (para Godot Engine).

Para Ideias, Regras, Simulação, você precisará de um ambiente de desenvolvimento configurado para uma das linguagens anteriores:

JavaScript (para navegadores) e GDScript fornecem suporte para conteúdo multimídia.

Também estou considerando melhorar os editores online para programação dentro do website, em um estilo de curso interativo. Atualmente, a página de ferramentas fornece opções para:

Os editores possuem suporte para imagens em navegador. Contudo, Python e Lua utilizam a sintaxe para JavaScript com canvas. Caso eu criasse uma abstração e adicionasse suporte para áudio, eles tornar-se-iam opções válidas (ao menos para primeiras atividades).

Contudo, convém configurar um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE), ou a combinação de editor de texto com interpretador assim que possível para programar em sua máquina com maior eficiência. Ambientes online são práticos, mas ambientes locais são mais potencialmente mais completos, eficientes, rápidos e personalizáveis. Caso queira tornar-se profissional, você precisará de um ambiente de desenvolvimento configurado. Quanto antes o fizer, melhor.

Versão em Vídeo

Este tópico possui versões compactas em vídeo no canal do autor no YouTube:

Contudo, esta versão em texto é mais completa.

Documentação

Pratique consultar pela documentação:

Praticamente todos os links possuem um campo para buscas. Em Lua, a documentação está em uma única página. Você pode procurar por entradas usando o atalho Ctrl F (busca ou search).

Determinismo, Indeterminismo, Aleatoriedade

Embora seja possível pensar em um sistema ou algoritmo como uma caixa preta que fornece uma saída dada uma entrada, a saída nem sempre é previsível. Por vezes, a saída pode depender de fatores externos como chance.

Processos ou sistemas cujas saídas dependem exclusivamente de entradas fornecidas (isto é, cujos resultados são previsíveis e repetíveis, caso se conheça o algoritmo) são ditos determinísticos. Por exemplo, uma soma de dois números inteiros é uma operação matemática determinística. O valor de é -- pois foi convencionado assim; não há outra possibilidade. Assim, qualquer implementação de uma função some(x, y) chamada como some(1, 1) com resultado diferente de 2 estaria errada.

Entretanto, nem todo processo ou sistema é determinístico. Se existe chance de resultados diferentes para uma mesma entrada, existe indeterminismo ou aleatoriedade envolvida.

Em um processo indeterminístico ou aleatório, é possível que uma mesma entrada leve a diferentes saídas. Talvez estas saídas pertençam a um conjunto de resultados conhecido. Por exemplo, em um dado com seis faces, um lançamento resultará no número 1, 2, 3, 4, 5, ou 6. Supondo-se um dado honesto, todos os possíveis resultados são equiprováveis (isto é, com a mesma chance de ocorrência).

Talvez as saídas sejam totalmente diferentes a cada execução. Talvez o resultado seja desconhecido: um estado indeterminado entre algumas possibilidades, como é o caso do Gato de Schrödinger. Talvez o resultado dependa de uma probabilidade de ocorrência: ao longo de uma múltiplas repetições, espera-se que o evento ocorra, embora não haja garantias. Assim, a discussão da existência de algo completamente aleatório é mais complexa.

Qualquer que seja o caso, programação comumente opera com indeterminismo e aleatoriedade por meio de números pseudoaleatórios (do inglês, pseudorandom number). Embora muitas pessoas usem o termo números aleatórios (random numbers) e geradores de números aleatórios (random number generators ou RNGs), computadores normalmente utilizam números pseudoaleatórios e geradores de números pseudoaleatórios (pseudorandom number generators ou PRNGs). Assim, quando este material mencionar números aleatórios, a menção normalmente referir-se-á a números pseudoaleatórios, exceto caso se afirme o contrário.

Números pseudoaleatórios foram apresentados previamente em Aprenda Programação: Estruturas de Repetição. Nesse tópico, eles serão retomados, porque a aleatoriedade é importante para a simulação de processos estocásticos. Na prática, o termo estocástico pode ser entendido como aleatório ou com aleatoriedade. Um sistema que simule um processo estocástico é dito uma simulação estocástica.

Simulações estocásticas são interessantes porque podem gerar resultados diferentes a cada execução. Ou seja, elas são oportunas para uma série chamada Ideias, Regras, Simulação. Assim, convém começar pelo caos.

Um Ruído como uma Imagem

Neste tópico, criaremos a primeira simulação estocástica, na forma de um ruído. Um ruído é informação irrelevante ou sem significado que polui um sinal. Em outras palavras, antes de criarmos simulações com significados (um sinal), criaremos simulações caóticas.

Uma forma de fazer isso é criar um ruído usando números pseudoaleatórios.

Canvas em HTML para JavaScript

Como na Introdução de Ideias, Regras, Simulação, JavaScript requer um arquivo HTML auxiliar para a declaração do canvas. O nome do arquivo HTML é de sua escolha (por exemplo, index.html). O exemplo a seguir assume que o arquivo JavaScript terá nome script.js e que o canvas terá identificador (id) canvas. Caso você altere os valores, lembre-se de modificá-los nos arquivos conforme necessário.

<!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;">
      >
          Acessibilidade: texto alternativo para conteúdo gráfico.
      </canvas>
    </div>

    <!-- NOTA Atualizar com nome do arquivo JavaScript. -->
    <script src="./script.js"></script>
  </body>
</html>

O arquivo também mantém o lembrete sobre acessibilidade. Neste tópico, o conteúdo tornar-se-á ainda mais inacessível para pessoas com certos tipos de deficiência visual, devido a adição de conteúdo gráfico sem opções alternativas para comunicação do conteúdo.

No futuro, a intenção é abordar formas de tornar simulações mais acessíveis. Neste momento, este lembrete é apenas informativo, para conscientizar você da importância de acessibilidade.

Geradores de Números Pseudoaleatórios (Pseudorandom Number Generators ou PRNGs) para GDScript, JavaScript, Python e Lua

Em Aprenda Programação: Estruturas de Repetição, definiu-se subrotinas (funções) para a criação de números inteiros pseudoaleatórios. Os próximos blocos de código ilustram a versão em Inglês para inteiro_aleatorio(), chamada de random_integer(), e definem uma função complementar chamada random_float() (para um real_aleatorio()). É bastante comum que a biblioteca padrão de uma linguagem de programação forneça ao menos uma das implementações. Boas bibliotecas tendem a fornecer ambas, assim como distribuições estáticas mais complexas (além da distribuição uniforme).

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()

As implementações definem uma função main() como ponto de entrada (como definido em Aprenda Programação: Ponto de Entrada e Estrutura de Programa).

Em todos os casos, números pseudoaleatórios requerem a inicialização de um valor para a semente usada para a geração de seqüência de números. Cada um dos pontos de entrada usam o tempo ou outro valor padrão para a linguagem considerada. A exceção é JavaScript para navegadores; navegadores inicializam automaticamente a semente para Math.random().

A função random_integer() gera um número inteiro pertencente ao intervalo compreendido entre valor mínimo (inclusive) e valor máximo (inclusive). A função random_float() gera um número real pertencente ao intervalo 0.0 até 1.0. Mais detalhes foram apresentados em Aprenda Programação: Estruturas de Repetição.

Colorindo Pixels Aleatoriamente

Para facilitar a leitura do código, as implementações definem o procedimento franco_draw_pixel() para o desenho de um pixel na tela. A implementação utiliza o código para desenho apresentado em Ideias, Regras, Simulação: Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). O nome não é draw_pixel() para evitar conflito de nomes na versão em GDScript (e manter o padrão em todas as linguagens).

# Raíz deve ser um Node que permita desenhar usando _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("Olá, meu nome é 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 = "Olá, meu nome é 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("Olá, meu nome é 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("Olá, meu nome é 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

Os exemplos colorem um pixel aleatoriamente no ponto (10, 20), e toda a linha com y = 30.

Um possível resultado do desenho é exibido no próximo canvas. Não se trata de um erro de exibição, mas da exibição de um ruído. As cores não possuem um padrão; elas são aleatórias. Caso um pixel desapareça, a cor sorteada foi preto.

Pixels coloridos aleatoriamente no canvas.

O uso de "possível resultado" refere-se ao fato de que as cores mudarão a cada execução do programa (devido ao uso de números pseudoaleatórios para escolhê-las). Caso queira verificar na prática, você pode atualizar esta página em seu navegador (atalho: F5) algumas vezes para observar os resultados. Embora seja possível, é improvável que duas execuções consecutivas apresentem as mesmas cores.

Improvável, mas não impossível. De fato, o uso de números pseudoaleatórios possui algumas limitações. Convém comentar sobre duas delas.

Uma primeira limitação é a possibilidade de forçar duas execuções a apresentarem o mesmo resultado. Para isso, basta usar a mesma semente para a geração dos números em ambas as execuções. Esse é o motivo do uso do horário da máquina para a inicialização da semente: assumindo que não se mude o relógio, a próxima execução terá um horário diferente da primeira, resultando em uma semente diferente.

Evidentemente, isso é válido apenas para uma mesma máquina. Duas máquinas diferentes poderiam gerar a mesma semente caso executassem o programa com o mesmo horário. Para evitar esse problema, algumas bibliotecas fornecem alternativas para geração de sementes que sejam mais robustas que o tempo.

Por outro lado, a primeira limitação pode ser um benefício em alguns cenários. Por exemplo, usar a mesma semente pode ser útil para testar sistemas que trabalhem com números pseudoaleatórios, pois os números gerados serão os mesmos. Caso o programa use os números gerados na mesma ordem e para os mesmos propósitos, o sistema tornar-se-ia determinístico para a semente escolhida.

A segunda limitação depende da semente e do algoritmo usado para o PRNG. A geração por algumas sementes pode não resultar em boas seqüências de números. Por exemplo, as seqüências de números podem degenerar em um padrão de números idênticos, tornando o sistema previsível.

Cores Animadas em Lua com LÖVE

As cores de exemplo em Lua usando LÖVE mudarão a cada atualização do motor; assim, ao invés de cores estáticas, o exemplo introduz uma animação. Em uma das próximas subseções, removeremos a animação da versão em LÖVE para propósitos didáticos (a versão em vídeo pode ser ilustrativa para uma versão mais simples). Em seguida, adicionaremos a animação em todas as linguagens de programação.

Colorindo Todos os Pixels da Janela

Para colorir todos os pixels da janela, basta inserir uma nova estrutura de repetição que itere sobre todos as linhas da resolução. O uso das duas estruturas de repetição aninhadas percorrerá todas as linhas e colunas da janela para preenchê-la com cores aleatórias.

# Raíz deve ser um Node que permita desenhar usando _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("Olá, meu nome é 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 = "Olá, meu nome é 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("Olá, meu nome é 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("Olá, meu nome é 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

Um possível resultado do desenho é exibido no próximo canvas. Novamente, não se trata de um erro de exibição, mas da exibição de um ruído.

Pixels coloridos aleatoriamente em todo o canvas.

A versão em Lua com LÖVE continua animada. Antes de introduzir animações em todas as versões, iremos remover a animação dela.

Limitando o Número de Cores

Ao invés de uma imagem com várias cores, também seria possível criar uma imagem com duas cores. Por exemplo, ela poderia ser uma imagem em preto e branco.

Para definir fluxos alternativos em programas, pode-se usar uma estrutura de condição (previamente definidas em Aprenda Programação: Estruturas de Condição). Estruturas de repetição foram usadas na versão de texto de Ideias, Regras, Simulação: Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos), mas ainda não foram na versão em vídeo. Assim, é hora de apresentá-las.

Uma estrutura se/então/senão define dois possíveis fluxos alternativos, sendo um o escolhido conforme o resultado de uma expressão lógica.

Contudo, neste caso basta usar uma estrutura se/então. Por exemplo, pode-se gerar um número real usando-se random_float(). Como a função gera um número real entre 0.0 e 1.0, pode-se dividir os valores em dois intervalos iguais. Assume-se cor preta; caso gere-se um número menor que 0.5, escolhe-se branco para o pixel.

# Raíz deve ser um Node que permita desenhar usando _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("Olá, meu nome é 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 = "Olá, meu nome é 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("Olá, meu nome é 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("Olá, meu nome é 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

Um possível resultado do desenho é exibido no próximo canvas. Novamente, não se trata de um erro de exibição, mas da exibição de um ruído.

Pixels coloridos aleatoriamente em branco ou preto em todo o canvas.

A versão em Lua com LÖVE utiliza um vetor (como uma tabela em Lua), introduzido em Aprenda Programação: Vetores, Cadeias de Caracteres, Coleções e Estruturas de Dados. Neste momento, não é necessário entender o uso do vetor. O uso apenas demonstra como criar uma imagem estática em motores que atualizem a tela a cada iteração: (pré-)gera-se os dados para se criar a ilustração uma única vez, então redesenha-os dados criados a cada atualização.

Criando uma Animação

No caso de Lua com LÖVE, gerou-se uma animação acidentalmente, devido à execução do código escrito em love.draw() a cada atualização do motor LÖVE.

Como comentado em Aprenda Programação: Registros durante a implementação do Jogo da Vida (Conway's Game of Life), uma animação resulta da repetição de saídas com alterações a cada iteração do laço. Como comentado na seção do Jogo da Vida, a animação resultante das repetições é similar um folioscópio (flip book) com desenhos em papel: alternando-se rapidamente entre as páginas, gera-se a ilusão de animação.

Para implementar o mesmo efeito nas demais linguagens de programação, basta usar uma estrutura de repetição. Em estruturas de nível mais alto, como frameworks ou motores (engines), pode ser necessário usar subrotinas:

  1. Em GDScript, deve-se adicionar um método _process() e chamar update() nele para forçar a atualização do Control. Control é implementado de forma a minimizar redesenhos na tela; assim, é preciso indicar quando se deseja forçar a atualização;

  2. Em JavaScript, requestAnimationFrame() pode ser usada recursivamente para redesenhar o conteúdo;

  3. Em Python com PyGame, move-se o código de desenho para dentro da estrutura de repetição que controla a janela. Esse é o processo mais tradicional e de nível mais baixo: utiliza-se uma estrutura de repetição que se encerra apenas quando a janela é fechada. De fato, todas as outras implementações realizam operações similares (por trás das cenas; a complexidade é gerenciada pelo framework ou motor).

    Adicionalmente, o redesenho a cada iteração do laço também corrigirá um problema não mencionado em tópicos anteriores (por não ser de interesse no momento): caso se mova a janela ou posicione-se outras janelas sobre ela, agora o conteúdo será apresentado corretamente após o próximo redesenho;

  4. Em Lua com LÖVE, basta remover o uso do vetor e restaurar o código original.

Assim, com algumas mudanças, todas as implementações terão um ruído animado. O resultado será similar ao que ocorria no passado com aparelhos de televisão (TV) "fora do ar".

# Raíz deve ser um Node que permita desenhar usando _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("Olá, meu nome é 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 = "Olá, meu nome é 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("Olá, meu nome é 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("Olá, meu nome é 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

Um possível resultado do desenho é exibido no próximo canvas. As atualizações no desenho ocorrem a cada segundo, para economizar energia (ou bateria) em seu dispositivo. Caso você execute o código original em seu navegador, a animação será significativamente mais rápida. Caso tenha interesse, você pode usar o Gerenciador de Tarefas (Task Manager) do seu sistema para verificar o uso do processador e de memória dos programas.

Pixels coloridos aleatoriamente em branco ou preto em todo o canvas. O uso de requestAnimationFrame() em draw() gera a animação.

O controle de velocidade para animações será considerado no futuro. Neste momento, as implementações executam o código na velocidade máxima permitida pela máquina. Isso significa que:

  1. As animações serão fluídas;
  2. Máquinas com hardware mais potente executarão a animação mais rapidamente que máquinas com configurações mais modestas;
  3. Desperdiça-se ciclos do processador desnecessariamente, pois as atualizações podem ocorrer mais rapidamente que a taxa de atualização (refresh rate) máxima do monitor.

Para programas mais robustos, pode ser interessante minimizar o uso desnecessário do processador. Isso é importante tanto para otimizar o desempenho multi-tarefas de uma máquina, quanto para economizar energia, contribuindo-se com práticas de sustentabilidade para a preservação do meio ambiente. Convém ser uma programadora ou um programador responsável com a sociedade e o meio ambiente.

Ruído Colorido

Caso se remova a estrutura de condição para a escolha de cor, pode-se tornar a animação colorida. Para praticar, tente modificar o código para fazê-lo.

Pixels coloridos aleatoriamente em todo o canvas. O uso de requestAnimationFrame() em draw() gera a animação.

Para a solução, basta alterar algumas linhas de código na implementação escolhida.

Conceitos Abordados de Aprenda Programação

Conceitos de Aprenda Programação abordados neste tópico:

  1. Ponto de entrada;
  2. Saída;
  3. Tipos de dados;
  4. Variáveis e constantes;
  5. Aritmética;
  6. Operações relacionais e comparações;
  7. Operações lógicas;
  8. Estruturas de condição;
  9. Subrotinas (funções e procedimentos);
  10. Estruturas de repetição (laços ou loops);
  11. Bibliotecas.

É interessante notar que mesmo primitivas para desenhos podem utilizar quase todos os conceitos básicos de programação.

Novamente, os valores para cores de pixels em Lua usaram tabelas (dicionários) como vetores, descritos em Coleções.

Novos Itens para Seu Inventário

Habilidades de Pensamento Computacional:

Ferramentas:

  • Gerenciador de tarefas.

Habilidades:

  • Uso de números pseudoaleatório;
  • Criação de animação.

Conceitos:

  • Determinismo (e determinístico);
  • Indeterminismo (e indeterminístico), aleatoriedade (e aleatório);
  • Números aleatórios;
  • Números pseudoaleatórios;
  • Geradores de números aleatórios (RNGs);
  • Geradores de números pseudoaleatórios (PRNGs);
  • Processos e simulações estocásticas;
  • Ruído.

Recursos de programação:

  • Números pseudoaleatórios;
  • Desenho de todos os pixels na janela.

Pratique

Para aprender programação, prática deliberada deve acompanhar conceitos. Tente fazer os próximos exercícios para praticar.

  1. Escreva um programa que pinte todos os pixels da janela com sua cor favorita;

  2. Escreva um programa que preencha toda a janela com três cores diferentes (por exemplo, vermelho, verde e azul);

  3. Modifique o programa deste tópico para criar linhas inteiras com a mesma cor, mas com cores aleatórias para linhas diferentes. Ou seja, ao invés de colorir todos os pixels aleatoriamente, cada uma das linhas deve ter uma cor aleatória.

    Linhas com cores aleatórias no canvas.

    Dica. Existem várias formas de resolver o problema. Para uma boa solução, poucas alterações no código bastam;

  4. Modifique o programa anterior para colorir todos os pixels de cada coluna com uma mesma cor, mas com cores aleatórias para colunas diferentes. O problema pode ser entendido como uma rotação de 90° do exercício anterior;

  5. Desafio. Pense com como escolher cores para linhas (ou cada pixel) seguindo um padrão mais esteticamente agradável. Por exemplo, um degradê (ou gradiente de cor).

    Um bom tamanho para um gradiente de cores é 256 x 256 (256 linhas por 256 colunas), caso se assuma 256 cores por cor primária.

    Gradiente de cores com tons de preto, azul e verde.

    A solução não utiliza números aleatórios, mas aritmética. Como se pode perceber, o resultado não é um ruído com cores aleatórias. Pelo contrário, trata-se de uma imagem agradável esteticamente.

    Gradientes de cores serão discutidos em um tópico futuro. O problema pode ser complexo para iniciantes que começaram em Ideias, Regras, Simulação, mas possível para pessoas que estudaram o material de Aprenda Programação. Afinal, gradientes de cores foram usados como um exemplo em Aprenda Programação: Arquivos e Serialização (Marshalling).

Aprofundamentos

Em Ideias, Regras, Simulação, esta seção apresenta conteúdo complementar.

LÖVE / Love2D: Canvas

Uma alternativa para desenhar uma imagem estática em LÖVE consiste em usar um Canvas (documentação). A solução também torna-se mais simples e limpa.

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("Olá, meu nome é 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

No caso, desenha-se o conteúdo desejado no canvas uma única vez (por exemplo, em love.load()) e copia-se o conteúdo a cada atualização em love.draw().

JavaScript: Outras Abordagens para Animações

requestAnimationFrame() é uma abordagem relativamente moderna para a criação de animações em JavaScript para navegadores. Implementações mais antigas podem usar setInterval() ou setTimeout() como alternativas.

As próximas subseções fornecem exemplos. No caso, a taxa de atualizações por segundos é aproximada pela constante FPS (frames per second ou frames por segundo). Valores típicos para FPS incluem 25 (comum em filmes), 30 (comum em filmes e jogos), 60 (comum em jogos) e 144 (ou 240; comuns em monitores de alto desempenho para jogos competitivos).

Para o exemplo de ruído, a escolha é irrelevante; quanto maior o número, maior será o número de atualizações (no caso, de redesenhos) da imagem por segundo. Em outras palavras, uma animação mais rápida.

Animações Usando 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 = "Olá, meu nome é Franco!"

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


main()

Animações Usando 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 = "Olá, meu nome é Franco!"

    draw()
}


main()

Próximos Passos

Em três tópicos de Ideias, Regras, Simulação, criamos uma janela, aprendemos a criar e usar primitivas gráficas, e definimos uma animação estocástica.

Com números pseudoaleatórios, é possível continuar a criar simulações progressivamente mais complexas. Esta série começou com o caos: um ruído. Talvez seja meio poético começar simulações com ruídos, pois dificilmente algo seria mais aleatório e com menos sentido do que conteúdo sem nenhum padrão.

Da ordem e padrões surgirão significados. Ou seja, precisamos de regras. Assim, aproxima-se o momento de começar a criar ordem e definir simulações com conteúdo mais significativo. Em outras palavras, de convertermos ideias em regras para criamos simulações. Ideias, Regras, Simulação. Coincidência?

Partiremos de algo simples. Um dos exemplos mais simples é a criação de simulações de arremessos de moedas e de dados. Com a introdução de algumas primitivas gráficas adicionais, será possível desenhá-las.

No mais, se você criou uma ilustração criativa ou interessante, considere compartilhá-la. Alternativamente, se você considerou este material útil, você também pode compartilhá-lo. Se possível, use as hashtags #IdeiasRegrasSimulação e #FrancoGarciaCom.

Agradeço a atenção. Até a próxima!

Ideias, Regras, Simulação

  1. Motivação;
  2. Introdução: Janela e Olá Mundo;
  3. Pixels e primitivas gráficas (pontos, linhas, e arcos);
  4. Aleatoriedade e ruído;
  5. Moedas e dados, retângulos e quadrados;
  6. Desenhando com primitivas gráficas (contornos e preenchimento para círculos, elipses e polígonos);
  7. Salvando e carregando arquivos de imagens;
  8. ...

A escrita do material está em progresso; portanto, se você chegou muito cedo e os itens anteriores não possuem links, por favor, retorne a esta página para conferir as atualizações.

Caso queira entrar em contato ou tenha alguma dúvida, você pode conversar comigo das seguintes maneiras:

Contatos e redes sociais também estão no rodapé das páginas.

Suas opiniões sobre a série serão fundamentais para que eu possa tornar o material mais acessível e simples para mais pessoas.

  • Vídeo
  • Informática
  • Programação
  • Iniciante
  • Pensamento Computacional
  • Ideias, Regras, Simulação
  • Python
  • PyGame
  • Lua
  • LÖVE (Love2D)
  • Javascript
  • HTML Canvas
  • Godot
  • Gdscript