Programação 102: Introdução aos fundamentos

Este é realmente o primeiro grande módulo de programação. Apesar de já termos começado a usar funções e parâmetros num conjunto de instruções de desenho, os sketches ainda são muito operativos. Isto é, ainda não tiramos verdadeiro partido do computador.

Normalmente, esta matéria pode ser dividida em dois momentos. P. ex. agrupando as variáveis e os ciclos. Ou as variáveis e condições. Não dá é para fazer ciclos e condições sem variáveis. E, fazer variáveis sozinhas… bom… é muito desmotivante. Por isso, como ainda estamos em modo estático, o melhor é dedicar uma a duas horas a cada um destes três tópicos:

  • Variáveis (tipos, declaração, inicialização, utilização, conversão, visibilidade);
  • Ciclos (simples e nested, operadores de atribuição);
  • Condições (tipos, operadores relacionais e lógicos).

Dependendo do contexto, normalmente explico estes três conceitos fundamentais numa única sessão. Em mesmo em seminários e workshops mais curtos ou mais longos.

É claro que é preciso algum tempo posterior para os amadurecer, compreender e consolidar. Mas para isso é que existe o desafio final. Sem mais demoras…

[See processing.org tutorials]

Variáveis

As variáveis são uma espécie de “caixa” que guarda valores lá dentro. Normalmente explico isto com uma metáfora das mudanças de casa — temos uma caixa para as coisas da cozinha (p. ex. copos que são mais frágeis), outra para os livros (que são mais pesados), etc. Cada caixa tem o seu tipo de conteúdos para ser manipulado de forma específica.

Hoje [na aula de 2021], a metáfora que me ocorreu foi a do “tupperware”. Isto é, temos uma caixa de tupperware em que, p. ex. hoje vamos guardar piza. Amanhã guardamos lasanha. Depois tacos… O Douglas Coupland chama a isto “flat food” no espetacular romance JPod. Mas, voltando ao tema. Quando vamos ao frigorífico, vamos buscar “aquele” tupperware. Mas, quando o vamos buscar, não pensamos na caixa em si, mas no que ela tem dentro.

As variáveis funcionam da mesma maneira no Processing.

Primeiro precisamos de ir buscar uma caixa para colocar coisas. A isto chama-se declarar uma variável. Isto “diz” ao Processing para “reservar” espaço na “prateleira” (na memória do computador… que podemos dizer que é como uma prateleira de um frigorífico) para guardar, utilizar e modificar as variáveis. Tal como as caixas das mudanças, ou tupperwares há diferentes tipos de caixas para diferentes conteúdos. A isto chama-se type casting [confirmar]. Isto é. Uma caixa de números só pode armazenar números. Uma caixa de cores só pode armazenar cores. E assim em diante.

É um pouco mais “protocolar” (que é como quem diz estruturado) do que o Javascript ou o Python. Mas este protocolo de declarar o tipo de variável acaba por ser vantajoso a longo prazo. Ah… e podemos sempre converter uns tipos de valores noutro tipo (temos é que ter em atenção “onde” os guardamos).

É importante referir que as “caixas” de variáveis levam um nome para as identificar. Tal como as caixas e turpperwares. Normalmente, as caixas de mudanças — para aqueles mais organizados — têm etiquetas a dizer o que está lá dentro. Cá em casa damos nomes aos tupperwares para poder dizer “chega-me o miquinhas da tampa azul que está no frigorífico”.

Depois de declarar (criar) as variáveis com um nome e um tipo, podemos inicializar (atribuir) um valor à variável. Que é o mesmo que dizer: “colocar algo dentro da caixa”. E assim já podemos utilizar a variável para fazer cálculos ou desenhar. Por exemplo:

// declarar uma variável 
int myVar;

// inicializar a variável
myVar = 10; 

// modificar a variável
myVar = myVar+10;

// utilizar a variável
println(myVar);

Há vários tipos de variáveis. Nesta fase, concentramo-nos apenas nas variáveis simples. É necessário conhecer as diferente vantagens e desvantagens dos vários tipos de variáveis:

Primitive / Simple variables

Para uma primeira fase é importante trabalhar com estes tipos simples. Mas, a longo prazo convém dar uma vista de olhos no:

E, com um pouco de paciência e perseverança — à medida que os projetos ficam mais complexos — eventualmente nos:

Nesta altura do campeonato, também é bom rever a secção do Environment. Há pelo menos duas variáveis de sistema que são automáticas — também chamadas de palavras reservadas — e que nos dão muito jeito para desenhar de forma relativa (elástica/responsive) no ecrã:

Com tempo e paciência também importante explorar:

Os mais aventureiros podem espreitar algumas funções como:

Mas, mais à frente, no modo dinâmico, iremos explorar as restantes variáveis (palavras reservadas) e funções específicas.

Normalmente explico que todos os dados (p. ex. um número como a localização ou dimensão de um quadrado) que repetimos a sua inserção mais do que uma vez, vale a pena converter em variável. Isto porque se (que é como quem diz que é certo que) mudarmos o valor, em vez de mudarmos em todos os sítios que o usamos. Se usarmos uma variável, ele muda automaticamente para todos

Exemplo de um desenho (Miffy) repetida mudando apenas a variável de localização!

Para a conversão, convém estudar as funções específicas de conversão de dados. É um pouco cedo. Nesta altura precisamos basicamente de converter um float num int. Mas, fica a referência para tópicos mais avançados.

Conversion

O mais comum (diria eu) e necessário nesta fase são estas conversões: de números inteiros para reais e de reais para inteiros. Isto porque há funções que fazem uso deles.

Para os mais interessados e aventureiros ficam aqui as mais utilizadas de uma forma geral. Confesso que não faço muito uso destas… mas são úteis:

Os realmente corajosos podem aventurar-se nestas (acho que nunca usei aqui em LSI):

Por fim, ainda não falamos (e é difícil de ver ainda) mas é importante falar do conceito da visibilidade das variáveis (scoping) Mas para isso, vamos passar à primeira utilização de um “bloco” de código. Os ciclos (loops).

Ciclos

Os ciclos (loops) são uma forma de automatizar tarefas repetitivas. Isto é, imaginem que no exemplo anterior desenhamos 5 (quem diz 5 diz 1000) bolas. Se elas forem desenhadas de forma repetitiva (com uma regra ou padrão), vale a pena automatizar. Que é como quem diz — “desenha uma ellipse. Agora repete 1000 vezes”. Tal como com as variáveis, normalmente explico os ciclos dizendo que se repetimos a instrução anterior, mas mudamos apenas um parâmetro/variável, então vale a pena converter num ciclo (são apenas mais duas linhas de código).

A mesma instrução mais do que uma vez? Então vale a pena fazer um ciclo

Há pelo menos dois tipos de ciclos. Normalmente explico o ciclo for(). No entanto é vantajoso estudar os dois tipos de estruturas de controlo (de iteração) de um programa:

Iteration

Os ciclos trabalham de uma forma interessante. É preciso uma variável inicial para dizer ao computador: “vais repetir uma coisa 5 vezes, por isso começa a contar de um-em-um, a partir do zero e para quando chegares a 5”. É isto. Parece o jogo das escondidas…

Mas em “computadorês” temos que traduzir a expressão em algo que ele consiga usar. Entram as variáveis.

// ciclo de circulos na horizontal
for (int i = 0; i < 10; i++) {
  ellipse(i*75 + 50, 75, 50, 50);
}

Aqui nesta expressão podemos ver a declaração da variável “i” (de iteração) atribuindo ao mesmo tempo o valor “0”. Isto é, começa a desenhar as ellipses em repetição, a contar as repetições a partir do zero (como se fossem abdominais no ginásio 😉 Repete o desenho das ellipses enquanto a variável i for menor do que o valor da repetições pretendidas (10). E, sempre que acabares de desenhar (repetir) aumenta o número da variável em +1.

Nesta fase convém conhecer um pouco das manias dos programadores. Este “i++” é igual a dizer “i = i+1”. Ou ainda é igual a dizer “i += 1”. Dêm uma vista de olhos nos operadores:

Tipicamente, um sinal matemático p. ex. “=” significa atribuição. Quando usamos dois (p. ex. “+=”) significa uma atribuição por operação matemática.

Como ainda temos algum tempo, e fazer um linha de bolas não tem piada, convertemos o desenho anterior numa matriz de bolas. E o que mais tarde dá o nosso esquadrão de robôs.

Esta operação é conseguida através de um ciclo dentro de outro — um nested loop.

// ciclo de circulos na horizontais
for (int k = 0; k < 5; k++) {
  // ciclo de colunas ou celulas
  for (int i = 0; i < 10; i++) {
    ellipse(i*75 + 50, k*75 +50, 50, 50);
  }
}

Aqui é a altura ideal para falar da visibilidade das variáveis. Reparem que na primeira linha usamos uma variável chamada “k”. Esta variável é só visível para o interior deste bloco de código (as instruções “dentro” das chavetas). E, porque já usamos uma variável “k”, no segundo ciclo, temos que usar outra variável (do mesmo tipo, mas com outro nome) para podermos dizer/utilizar para diferentes fins/com diferentes valores de cada vez (senão em vez de uma matriz, desenhavamos uma diagonal).

[insert bad diagonal example here]

Mas, a visibilidade funciona de uma forma organizada “de fora para dentro”. Isto é, uma variável definida fora é visivel para o interior dos blocos. Mas, uma variável definida no interior de um bloco não é visível para fora deste.

Mais à frente este conceito vai ser pertinente e utilizado para otimizar o programa definindo e utilizando variáveis globais (definidas na raíz) e variáveis locais (definidas dentro de blocos ou funções), ou mesmo privadas (pertencendo a métodos ou objetos específicas, também chamadas de propriedades… mas estou a adiantar-me). Aqui quero deixar/frisar apenas a visibilidade global (uma variável definida logo no início do programa. Visível para todos. Ou uma visibilidade local (quando definida num bloco específico).

Atenção à visibilidade das variáveis na estrutura dos blocos

É por isso que a variável “k” é visível para no primeiro bloco/ciclo e no segundo, e a variável “i” é só visível dentro do segundo ciclo e, quando tentamos usar (depois), ainda no primeiro ciclo, o programa “queixa-se” pois não sabe quem é o “i”. Reparem na imagem de cima, a mensagem na consola: “The variable “i” does not exist”.

Explicados os ciclos (pelo menos o primeiro), a ideia é começar a tornar o programa mais interessante visualmente. Vamos ver a segunda estrutura de controlo mais importante de um programa: as condições.

Condições

As condições são estruturas de código que permitem verificar se o programa está a fazer algo previsto e responder de acordo. Por exemplo, pensem no “Pong”: se a bola bate no topo do ecrã (que é como quem diz, se a posição vertical da bola passar o limite do ecrã, então inverte a direção). E o mesmo na tabela.

Pong: se a bola passa o limite superior, inverte a direção. Um exemplo de uma condição.

Mas, para já uma coisa mais simples. Por exemplo, se a bola for a terceira pinta-a de vermelho.

As condições são várias e implicam algum estudo:

Conditionals

A primeira (mais importante) e mais simples é uma estrutura condicional

A partir daqui faz-se tudo. Esta expressão aparece muitas vezes abreviada com a notação

O If / Else é uma estrutura simples para verificar se algo está numa posição ou noutra (p. ex. para cima ou para baixo). Dizendo: “se x for superior a y, então faz algo. Senão faz outra coisa”. Ou seja é o ideal para fazer uma, ou outra coisa. Podemos ainda usar o caso de verificação de números. Por exemplo idades. “Se a pessoa tem menos de x anos, então faz algo. Senão, se a pessoa tem mais de x anos, então faz outra coisa.” Mas ainda falta um caso: “Senão, se a pessoa tem exatamente x anos, então faz ainda uma outra coisa”. Para mim, este é o limite de um if. Dois ou três casos possíveis. Para mais condições, podemos usar outras estruturas.

Para os mais corajosos:

Para usar e dominar esta estrutura condicional é necessário compreender também o:

Vamos usar nos próximos exemplos (nomeadamente nos eventos de utilizador).

Mas, de volta ao if. Para usar o if é preciso compreender o conceito de um booleano — o que está dentro dos parâmetros da função permite executar as instruções apenas se for verdadeiro. Caso contrário, faz outra coisa.

Para verificar estas coisas é preciso conhecer os operadores relacionais (que são diferentes das atribuições)

Relational Operators
if(true) {
  doSomething();
} else {
  doAnotherThing();
}

Mas, para fazer casos mais úteis/reais podemos combinar expressões na verificação do if. Por exemplo, se o que estamos a verificar está para cima e para a direita ao mesmo tempo.

Para isso é importante conhecer os operadores relacionais e lógicos.

Logical Operators
Repete o desenho sempre com a mesma cor. Mas, se o desenho for na segunda linha e também na segunda coluna pinta de vemelho.

// ciclo de circulos na horizontais
for (int k = 0; k < 5; k++) {
  // ciclo de colunas ou celulas
  for (int i = 0; i < 10; i++) {
    fill(200);       // pinta sempre de branco
    
    if (k == 1 && i == 2) {    // se for a segunda linha E a terceira coluna
      fill(255, 0, 0);  // pinta vermelho
    }
    
    ellipse(i*75 + 50, k*75 +50, 50, 50);
  }
}

E é isto. Para fazer um programa relaticamente complexo basta saber estes três conceitos fundamentais. O resto é imaginação.

O resultado e objetivo de aprendizagem desta aula é o desafio de desenhar uma companhia de robôs (~100 cópias aproximadamente) do desenho que fizeram na aula anterior.

Junta-se um pouco de random() à mistura e o resultado é o que chamo de desafio “Garven Dreiss (AKA Red Leader).”

Nessa companhia, ou esquadrão, de forma definida ou aleatória desenhem um líder de esquadrão.

Red Leader challenge: where’s Finn? 😉

// Stormtrooper v.5 2018-02-12 (dynamic / static mode with variables, conditions & loops)
// Stormtrooper v.6 2018-02-15 (export PDF option)
// PAmado, FBAUP, LSI TP02

//Import libraries in the first place
import processing.pdf.*;

// declare globals
// starting position
int x = 0;
int y = 100;

// random displacement — will add a slight random value to look more "human"/disorganized group of soldiers 
int rx = 0;
int ry = 0;

// if true, this will trigger the randomness
boolean rand = true;

// if true, this will draw the officers badge
boolean officers = true;

// this will set how many officers (max number/percentage)
float cofficer = 0.9; // captains rank higher (10%) = threshold 90%
float pofficer = 0.7; // petty officers (30%) = threshold = 70%

// if true, some of the normal soldiers will not have helmets on…
boolean bhelmets = true; 

// Max number of soldiers per line and max number of lines
// Squad (4–10 Soldiers); Platoon (3–4 Squads / 16–40 Soldiers); Company (3–4 Platoons / 100–200 Soldiers); Battalion (3–5 Companies / 500–900 Soldiers)…
int nx = 5;
int ny = 6;

// select one stormtrooper and draw him as Finn ;)
// randomly choose one from the max lines and columns
int finnx = int( random(nx) );
int finny = int( random(ny) );
boolean finn = true;

// in the future (module 2 of LSI Class)
// use processing active mode
// start of setup fucnction
// void setup() {

// check if the print option is true to export PDF 
// don't forget to exit in the end of the code
// 300 DPI, A3 (297 x 420 mm = 11.69 x 16.53 in =  3507 x 4959 px)
//size(3507, 4959, PDF, "clone-wars.pdf");
size(1600, 980);


// set properties
noFill();
noStroke();
rectMode(CENTER);

// start drawing (preparation)
background(225);
noFill();
noStroke();

// end of setup function
// }

// start of draw function
// void draw() {
  
// start drawing

// start repetition with j = 0 until the max number of vertical repetitions (number of horizontal lines of soldiers)
for (int j=0; j<=ny; j++) {

  //advance y according to the desired height/line interval of the soldiers
  y = j*100+100;

  // for each horizontal line of soldiers, start a horizontal repetition with the number of soldiers per line
  // start with x = 0 until the max number defined
  for (int i=0; i<=nx; i++) {

    // if this is true (see global variable declaration/inicialization) then setup a random number between negative and positive 10 to slightly change the position of the soldiers
    if (rand) {
      rx =  int(random(-20, 20));
      ry =  int(random(-20, 20));
    }

    // Set the horizontal position according to the repetition number of the horizontal soldier * width of the soldier (i*230);
    // add a starter space/position to adjust to the right (+200)
    // add random / human variation to the position.
    x = i*230 + 200 + rx;

    // set the vertical position with the randomness for each soldier
    // the relative vertical position of the line (y) was already defined in the first loop
    y = y + ry;


    ////////////////////////////// Draw the soldier / robot / stormprooper /////////////////////////

    // Neck
    fill(0);
    rect(x, y+22, 20, 20, 6);

    // Waist
    // Pelvis
    fill(0);
    rect(x, y+125, 115, 15);

    // Crotch
    rect(x, y+145, 20, 50);

    // Hands
    // Wrists (almost pelvis height)
    stroke(0);
    strokeWeight(18);
    line(x-66, y+70, x-76, y+95); // right
    line(x+66, y+70, x+76, y+95); // left

    // Clamp
    strokeWeight(12);
    noFill();
    arc(x-85, y+118, 33, 33, -3.5, 0.9); // right
    arc(x+85, y+118, 33, 33, -4.2, 0.3); // left

    // Head
    noStroke();
    fill(255, 200, 0);
      // check if it is Finn ;)
      // first if draw finn is true (boolean)
      // then if the i and j correspond to the random position assigned
      if (finn && i == finnx && j == finny) {
        fill(100, 60, 0);
      }
    rect(x, y-12, 68, 52, 8);
    rect(x, y-22, 32, 52, 8);
    
    // Eyes
    fill(0);
    ellipse(x-10, y-18, 10, 10); // right
    ellipse(x+10, y-18, 10, 10); // left

    // Mouth
    noFill();
    stroke(0);
    strokeWeight(6);
    line(x-8, y, x+8, y);

    // Body
    noStroke();
    fill(250);
    beginShape();
    vertex(x-50, y+20); // top left
    vertex(x-56, y+115);
    vertex(x+57, y+115);
    vertex(x+50, y+20); // top right
    endShape(CLOSE);
    // Abdomin lines
    noFill();
    stroke(0);
    strokeWeight(2);
    beginShape();
    vertex(x-46, y+100); // top left
    vertex(x-28, y+75);
    vertex(x+28, y+75);
    vertex(x+46, y+100); // top right
    endShape(OPEN);

    // Arms
    noFill();
    stroke(250);
    strokeWeight(27);
    line(x-45, y+32, x-66, y+65); // left arm
    line(x-66, y+65, x-70, y+80); // left forearm
    line(x+45, y+32, x+66, y+65); // right arm
    line(x+66, y+65, x+70, y+80); // right forearm

    // Elbows (shadows)
    // in the future…

    // Legs
    noStroke();
    fill(250);
    rect(x-32, y+177, 48, 86); // right
    rect(x+32, y+177, 48, 86); // left

    // Knees (lines)
    stroke(0);
    strokeWeight(2);
    line(x-50, y+175, x-15, y+175);
    beginShape();
    vertex(x+15, y+175);
    vertex(x+24, y+165);
    vertex(x+43, y+165);
    vertex(x+52, y+175);
    endShape(OPEN);

    // Feet
    noStroke();
    fill(240);
    rect(x-32, y+200, 44, 5); // right
    rect(x+32, y+200, 44, 5); // left

    // set a random number between 0 and 1
    float nr = random(1);
    println(nr);
    
    // if officers is enabled
    // and…
    // if it is NOT finn (i and j different / != finnx & finny)
    // draw badges 
    if (officers && (i != finnx && j !=finny) ) {
     // if a big percentage (70%) of the random number is higher than 0.3 (see globals)
     if (nr >= pofficer ) {
      // set the badge as a petty officer (yellow)
      fill(255, 200, 0);

        // but if a small percentage (10%) is higher than 0.9 (see globals)
        // set the badge as a captain (red)
        if (nr >= cofficer) {
          fill(255, 0, 0);
        }
      
      // and draw the badge on the soulder
      noStroke();

      beginShape();
      vertex(x-75, y+48);
      vertex(x-55, y+18);
      vertex(x-15, y+20);
      vertex(x-53, y+63);
      endShape(CLOSE);

      stroke(0);
      strokeWeight(8);
      noFill();
      beginShape();
      vertex(x-15, y+20);
      vertex(x-56, y+60);
      vertex(x-75, y+48);
      endShape(OPEN);
      }
    }// else nothing…
     
      

    float rh = random(1);
    // check three conditions 
    // first if soldiers / robots / drawings are not ranking officers nr <= pofficer 
    // and if helmets can be off
    // and if they are really low / crappy soldiers dum enough to take helmets off… 
    
    if (nr <= pofficer && bhelmets && rh < 0.3) { // very low ranking / probability 30% of 30%
      // do nothing / no helmet…
      
      // Else check if it is Finn and take his helmet off ;)
      // can be done with and OR || expression
    } else if (i == finnx && j == finny){
      // do nothing / no helmet…
      
    } else {
      // if 70% soldier and / or officers
      // Draw Helmets
      noStroke();
      fill(240); // draw shadows
      rect(x, y-5, 75, 88, 20); // base shadow (+5 px below)

      fill(250);
      rect(x, y-15, 75, 85, 30); // base
      // earpieces
      rect(x, y-12, 90, 25, 10);
      // side breathers
      noFill();
      strokeWeight(20);
      // draw shadows
      stroke(240);
      line(x-38, y+20, x-20, y+35);
      line(x+37, y+20, x+20, y+35);

      stroke(250);
      line(x-38, y+15, x-20, y+30);
      line(x+38, y+15, x+20, y+30);

      // Face
      noStroke();
      fill(0);

      // Eyes
      rect(x, y-25, 77, 6); // eyebrows
      triangle(x-36, y-25, x-30, y-8, x-5, y-25);
      triangle(x+35, y-25, x+30, y-8, x+5, y-25);

      // Mouth
      beginShape();
      vertex(x-30, y+20); // bottom right
      vertex(x, y-5); // nose
      vertex(x+30, y+20); 
      vertex(x, y+10); 

      endShape(CLOSE);

      // side breathers
      ellipse(x-20, y+32, 10, 10);
      ellipse(x+20, y+32, 10, 10);

      // middle mouth
      beginShape();
      vertex(x-5, y+25); 
      vertex(x+5, y+25);  
      vertex(x+10, y+35); 
      vertex(x-10, y+35);  
      endShape(CLOSE);
    }
  }  // End horizontal i for// End vertical j for

// print option = true finish the PDF
// use this with key presses or triggers when using active draw modes
//exit();

// end of draw function
// }

Este artigo é um artigo em progresso. Editado 2019-03-12, 2020-02-27, 2021-02-25