526 lines
19 KiB
Text
526 lines
19 KiB
Text
[#_lesson_3_making_decisions]
|
|
== Lección 3: Tomar decisiones
|
|
|
|
image::learn/03-decisions.png[La lección terminada, 480]
|
|
|
|
Todo lo que has escrito hasta ahora hace lo mismo en cada fotograma. La palabra
|
|
de la lección dos se movía quisieras o no, y lo único que la hacía dar la vuelta
|
|
eran cuatro bloques de código que tuviste que creerte sin más.
|
|
|
|
Esta lección trata de esos cuatro bloques, como es debido. Al final de ella tu
|
|
programa hará preguntas, actuará según las respuestas y -- como la pregunta más
|
|
interesante que puede hacer un juego es «¿qué está haciendo el jugador?» --
|
|
estarás manejando una caja por la ventana con las teclas de flecha. Guarda esa
|
|
caja. Es el jugador del juego que construyes en la lección siete.
|
|
|
|
[#_start_a_new_folder_2]
|
|
=== Empieza una carpeta nueva
|
|
|
|
Una carpeta llamada `decisions`, dentro un archivo llamado `decisions.singe`, y
|
|
`Singe -R decisions` ejecutándose en una terminal. Igual que la última vez.
|
|
|
|
[#_what_an_if_really_says]
|
|
=== Lo que dice de verdad un `if`
|
|
|
|
Aquí está el bloque de la lección dos por separado.
|
|
|
|
[source,lua]
|
|
----
|
|
if wordX > lastColumn then
|
|
wordX = lastColumn
|
|
end
|
|
----
|
|
|
|
Cuatro partes. La palabra `if`. Una pregunta. La palabra `then`. Y un cuerpo de
|
|
líneas que solo se ejecutan cuando la respuesta a la pregunta es sí, cerrado con
|
|
`end`.
|
|
|
|
La pregunta es `wordX > lastColumn`, y el `>` es el que hace el trabajo de
|
|
verdad. Compara los dos números que tiene a cada lado y da una respuesta que es
|
|
*verdadero* o *falso*. Esas dos palabras no son texto ni son números; son
|
|
valores por derecho propio, llamados *booleanos*, y un booleano es lo único que
|
|
le interesa a un `if`.
|
|
|
|
La sangría es la misma idea que en la lección uno: las líneas de dentro van
|
|
sangradas para que veas de un vistazo qué pertenece al `if`, y el `end` se
|
|
alinea con el `if` que lo abrió. Lua lo ejecutaría sin los espacios. Tú no
|
|
podrías leerlo.
|
|
|
|
Hay seis comparaciones como esta y las usarás todas.
|
|
|
|
[cols="1,4"]
|
|
|===
|
|
| `a < b` | a es menor que b
|
|
| `a > b` | a es mayor que b
|
|
| `a <= b` | a es menor o igual que b
|
|
| `a >= b` | a es mayor o igual que b
|
|
| `a == b` | a es lo mismo que b
|
|
| `a ~= b` | a no es lo mismo que b
|
|
|===
|
|
|
|
Las dos últimas son con las que hay que tener cuidado, por motivos distintos.
|
|
|
|
`~=` significa «no es igual a». La mayoría de los lenguajes lo escriben `!=`, y
|
|
si has visto algún programa antes, tus dedos escribirán `!=` y Lua lo
|
|
rechazará. La virgulilla está en tu teclado junto al `1` o debajo del `#`, según
|
|
dónde vivas.
|
|
|
|
`==` son dos signos igual, y no es una errata por uno. Es uno de los errores más
|
|
comunes en programación, y vale la pena pararse en él un momento.
|
|
|
|
En la lección dos aprendiste que un signo igual significa *mete el lado derecho
|
|
en el nombre de la izquierda*. Eso es algo que haces. Cambia algo.
|
|
|
|
Dos signos igual significan *¿son iguales estas dos cosas?* Eso es una pregunta
|
|
que haces. No cambia nada, y devuelve verdadero o falso.
|
|
|
|
[source,lua]
|
|
----
|
|
lives = 3
|
|
if lives == 3 then
|
|
----
|
|
|
|
La primera línea pone `lives` a tres. La segunda pregunta si vale tres. Trabajos
|
|
distintos, símbolos distintos, a un carácter de distancia. Lua te pillará si los
|
|
confundes, y verás el error que usa al final de esta lección.
|
|
|
|
[#_when_the_answer_is_no]
|
|
=== Cuando la respuesta es no
|
|
|
|
Un `if` solo no hace nada en absoluto cuando la respuesta es falsa. Muchas veces
|
|
quieres que en ese caso pase otra cosa, y eso es `else`.
|
|
|
|
[source,lua]
|
|
----
|
|
if lives > 0 then
|
|
message = "Ready"
|
|
else
|
|
message = "Game Over"
|
|
end
|
|
----
|
|
|
|
Se ejecuta exactamente uno de esos dos cuerpos, nunca los dos, nunca ninguno,
|
|
así que `message` siempre acaba guardando una de las dos cadenas y nunca nada.
|
|
|
|
Cuando hay más de dos posibilidades, `elseif` las encadena.
|
|
|
|
[source,lua]
|
|
----
|
|
if score >= 10000 then
|
|
rank = "ace"
|
|
elseif score >= 5000 then
|
|
rank = "pilot"
|
|
elseif score >= 1000 then
|
|
rank = "rookie"
|
|
else
|
|
rank = "passenger"
|
|
end
|
|
----
|
|
|
|
Lua prueba las preguntas desde arriba y se para en la primera que es verdadera.
|
|
Una puntuación de 6000 no es mayor que 10000, así que prueba la siguiente, que
|
|
es verdadera, así que `rank` pasa a ser `pilot` y las otras dos preguntas no se
|
|
llegan a hacer. Eso último importa: el `elseif` de 1000 también es verdadero
|
|
para una puntuación de 6000, y nunca se llega a él, y por eso el orden va de
|
|
mayor a menor.
|
|
|
|
Todo el bloque necesita exactamente un `end`, da igual cuántas partes `elseif`
|
|
tenga. Es una sola decisión, no cuatro.
|
|
|
|
[#_true_false_and_nothing_at_all]
|
|
=== Verdadero, falso y nada de nada
|
|
|
|
No hace falta comparar dos cosas para obtener un booleano. Puedes guardar uno.
|
|
|
|
[source,lua]
|
|
----
|
|
paused = false
|
|
----
|
|
|
|
Es una variable como cualquier otra, y el valor que tiene es el propio booleano
|
|
`false`, escrito sin comillas porque no es la palabra «false», es la idea. Lo
|
|
mismo pasa con `true`. Una variable que guarda uno de los dos puede ir
|
|
directamente en un `if` sin ninguna comparación:
|
|
|
|
[source,lua]
|
|
----
|
|
if paused then
|
|
----
|
|
|
|
Léelo como «si está en pausa». Se entiende mejor que `if paused == true`, que
|
|
hace lo mismo dando un rodeo, y los programadores con experiencia te mirarán
|
|
raro si lo escribes.
|
|
|
|
Tres palabras unen y dan la vuelta a estas respuestas.
|
|
|
|
[source,lua]
|
|
----
|
|
if alive and not shielded then
|
|
if coins > 0 or freePlay then
|
|
----
|
|
|
|
`and` es verdadero cuando lo son los dos lados. `or` es verdadero cuando lo es
|
|
cualquiera de los dos, o los dos. `not` convierte verdadero en falso y falso en
|
|
verdadero. Se leen como el inglés y, cosa rara en programación, significan lo
|
|
mismo que en inglés.
|
|
|
|
Hay un valor más que conviene conocer, y es el que produce los errores más
|
|
confusos de tu primer año.
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(0, 0, "lives " .. tostring(lives))
|
|
----
|
|
|
|
Si nunca has metido nada en `lives`, eso imprime `lives nil`. Lua no se queja de
|
|
un nombre que no ha visto nunca; te da `nil`, que es su palabra para *aquí no
|
|
hay nada*. No es cero y no es una cadena vacía. El cero es un número que querías
|
|
guardar. `nil` es la ausencia de un valor.
|
|
|
|
Ya te has encontrado con `nil` dos veces sin que nadie te lo dijera.
|
|
`frames = frames + 1` sin la línea `frames = 0` encima falla porque le pediste a
|
|
Lua que sumara uno a nada. Y un callback mal escrito también es `nil`, y por eso
|
|
un `onOverlayUpdate` mal escrito te da una ventana negra y ningún error: Singe
|
|
busca el nombre, no encuentra nada y sigue tan tranquilo con lo suyo.
|
|
|
|
`tostring` está en esa línea porque `..` no une un booleano ni un `nil` con
|
|
nada. Los números te los convierte; estos no. Esa era la promesa del final de
|
|
la lección dos, y ahora tienes para qué usarlo.
|
|
|
|
Por último, la regla que lo ata todo, porque `if` acepta cualquier cosa que le
|
|
des, no solo verdadero y falso:
|
|
|
|
[quote]
|
|
En Lua todo es verdadero salvo `false` y `nil`.
|
|
|
|
El cero es verdadero. Una cadena vacía es verdadera. Solo esos dos no lo son, y
|
|
una vez que lo sabes, `if movingLeft then` se lee perfectamente tanto si
|
|
`movingLeft` guarda `true` como `false`, o si nunca le has dado valor.
|
|
|
|
[#_reading_the_controls]
|
|
=== Leer los controles
|
|
|
|
Es hora de preguntarle algo al jugador. Para eso hace falta un callback nuevo.
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_RIGHT then
|
|
boxX = boxX + 10
|
|
end
|
|
end
|
|
----
|
|
|
|
`onInputPressed` es el segundo callback que escribes, y funciona exactamente
|
|
como describía la lección uno: tú lo escribes, Singe lo llama. A
|
|
`onOverlayUpdate` se le llama con un temporizador. A este se le llama cuando
|
|
pasa algo -- cuando el jugador pulsa cualquier cosa que el motor reconoce.
|
|
|
|
La diferencia con `onOverlayUpdate` es la palabra que hay dentro de los
|
|
paréntesis. Singe tiene que decirte *qué* control se ha pulsado, así que le da
|
|
un valor a la función, y `what` es el nombre con el que has elegido recibirlo.
|
|
La lección uno llamaba argumentos a las cosas que le pasas a una función; desde
|
|
dentro, el nombre que recoge uno es un *parámetro*. El nombre es tuyo: llámalo
|
|
`what`, llámalo `button`, llámalo `k`. Singe lo rellena igual.
|
|
|
|
`SWITCH_RIGHT` es un nombre que el motor ya le ha dado a un valor, como
|
|
`OVERLAY_UPDATED` en la lección uno. No tienes que cargar nada para usarlo;
|
|
Singe los define todos antes de que se ejecute tu script. Hay uno para cada cosa
|
|
que tiene una recreativa:
|
|
`SWITCH_LEFT`, `SWITCH_RIGHT`, `SWITCH_UP`, `SWITCH_DOWN`, de `SWITCH_BUTTON1`
|
|
a `SWITCH_BUTTON4`, `SWITCH_START1`, `SWITCH_COIN1` y más. La tabla de
|
|
Constantes del motor de la referencia cubre toda la familia, que va de
|
|
`SWITCH_UP` a `SWITCH_RECORD`. A propósito, no se llaman «la tecla de flecha derecha», porque llega el
|
|
mismo `SWITCH_RIGHT` tanto si el jugador ha pulsado una tecla de flecha como si
|
|
ha empujado un joystick o el stick de un mando. Escribes el juego una vez y
|
|
funciona con los tres.
|
|
|
|
Añade ese callback, dale a `boxX` un valor inicial fuera de las funciones y
|
|
dibuja algo en `boxX`. Pulsa la flecha derecha. La caja salta diez píxeles cada
|
|
vez que la pulsas.
|
|
|
|
[#_it_happened_and_it_is_happening]
|
|
=== Ha pasado, y está pasando
|
|
|
|
Mantén pulsada la tecla de flecha. No pasa nada más.
|
|
|
|
A `onInputPressed` se le llama una vez, al bajar la tecla, y no otra vez hasta
|
|
que la sueltas y la vuelves a pulsar. No hay repetición de teclas. Eso es justo
|
|
lo correcto para algunas cosas -- disparar, echar una moneda, elegir una opción
|
|
de un menú -- y justo lo incorrecto para caminar, que tiene que seguir pasando
|
|
mientras la tecla esté pulsada.
|
|
|
|
Son dos preguntas realmente distintas. *Ha pasado* es un evento, y te enteras
|
|
una vez. *Está pasando* es un estado, y dura. Singe te avisa de los eventos, y la
|
|
forma de convertir eventos en estado es recordarlos tú.
|
|
|
|
Dos callbacks y una variable entre los dos.
|
|
|
|
[source,lua]
|
|
----
|
|
movingRight = false
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
end
|
|
end
|
|
----
|
|
|
|
`onInputReleased` es el tercer callback, y es el reflejo del segundo: Singe lo
|
|
llama cuando la tecla vuelve a subir, con el mismo valor `SWITCH_`. Entre los
|
|
dos consiguen que `movingRight` diga la verdad sobre lo que está haciendo el
|
|
dedo del jugador en este momento.
|
|
|
|
Todavía no se ha movido nada, y de eso se trata. Pulsar y moverse son ahora dos
|
|
trabajos separados. El movimiento va donde va todo el trabajo de cada
|
|
fotograma.
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
if movingRight then
|
|
boxX = boxX + BOX_SPEED
|
|
end
|
|
...
|
|
----
|
|
|
|
Ahora la caja se desliza suavemente mientras la tecla esté pulsada, a una
|
|
velocidad que controlas tú, y la aritmética vuelve a estar en la única función
|
|
que se ejecuta sesenta veces por segundo, que es donde debe estar. Esta forma --
|
|
los eventos ponen indicadores, el fotograma actúa según los indicadores -- es
|
|
como casi todos los juegos manejan sus controles, y la volverás a usar en cada
|
|
lección que tenga un jugador.
|
|
|
|
[#_drawing_a_box]
|
|
=== Dibujar una caja
|
|
|
|
`overlayBox` recibe dos esquinas: el borde izquierdo y el de arriba del
|
|
rectángulo, y luego el derecho y el de abajo.
|
|
|
|
[source,lua]
|
|
----
|
|
colorForeground(255, 220, 0)
|
|
overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE)
|
|
----
|
|
|
|
Dibuja un *contorno*, cuatro líneas de un píxel de grosor, no un rectángulo
|
|
relleno. En las llamadas de dibujo del overlay no hay ningún rectángulo relleno.
|
|
Cuando más adelante quieras un bloque sólido hay formas de conseguirlo, y la
|
|
lección nueve tiene la habitual.
|
|
|
|
Sus números son píxeles, no celdas de carácter. Esto pilla a la gente, porque
|
|
`overlayPrint` en la lección dos contaba en celdas de seis por trece píxeles y
|
|
todo lo demás en Singe cuenta en píxeles. `overlayPrint` es el raro; el resto de
|
|
las llamadas de dibujo -- `overlayBox`, `overlayLine`, `overlayCircle`,
|
|
`overlayEllipse` y `overlayPlot` -- trabajan todas en los mismos píxeles que da
|
|
`overlayGetWidth`.
|
|
|
|
`colorForeground` pone el color, con tres números de 0 a 255: cuánto rojo,
|
|
cuánto verde, cuánto azul. Los tres a 255 es blanco, todos a 0 es negro, y
|
|
`(255, 220, 0)` es un amarillo cálido. Fíjate en que no es un argumento de
|
|
`overlayBox`. Es un ajuste: lo cambias, y todo lo que dibujes después sale en el
|
|
color nuevo hasta que lo vuelvas a cambiar. `overlayPrint` vuelve a ser la
|
|
excepción -- la fuente de consola trae sus propios colores de serie y no te hace
|
|
caso.
|
|
|
|
[#_the_whole_thing_2]
|
|
=== Todo junto
|
|
|
|
[source,lua]
|
|
----
|
|
BOX_SIZE = 20
|
|
BOX_SPEED = 2
|
|
|
|
width = overlayGetWidth()
|
|
height = overlayGetHeight()
|
|
maxX = width - BOX_SIZE - 1
|
|
maxY = height - BOX_SIZE - 1
|
|
|
|
boxX = width // 2
|
|
boxY = height // 2
|
|
|
|
movingLeft = false
|
|
movingRight = false
|
|
movingUp = false
|
|
movingDown = false
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
elseif what == SWITCH_UP then
|
|
movingUp = true
|
|
elseif what == SWITCH_DOWN then
|
|
movingDown = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
elseif what == SWITCH_UP then
|
|
movingUp = false
|
|
elseif what == SWITCH_DOWN then
|
|
movingDown = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
if movingLeft and not movingRight then
|
|
boxX = boxX - BOX_SPEED
|
|
elseif movingRight and not movingLeft then
|
|
boxX = boxX + BOX_SPEED
|
|
end
|
|
|
|
if movingUp and not movingDown then
|
|
boxY = boxY - BOX_SPEED
|
|
elseif movingDown and not movingUp then
|
|
boxY = boxY + BOX_SPEED
|
|
end
|
|
|
|
if boxX < 0 then
|
|
boxX = 0
|
|
elseif boxX > maxX then
|
|
boxX = maxX
|
|
end
|
|
|
|
if boxY < 0 then
|
|
boxY = 0
|
|
elseif boxY > maxY then
|
|
boxY = maxY
|
|
end
|
|
|
|
overlayClear()
|
|
|
|
if movingLeft or movingRight or movingUp or movingDown then
|
|
colorForeground(255, 220, 0)
|
|
else
|
|
colorForeground(80, 160, 255)
|
|
end
|
|
|
|
overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE)
|
|
overlayPrint(0, 0, "x " .. boxX .. " y " .. boxY)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Hay cuatro cosas ahí dentro que merecen una segunda mirada.
|
|
|
|
`movingLeft and not movingRight` está ahí porque un jugador puede mantener
|
|
pulsadas las dos flechas a la vez. Sin el `not`, las dos se pelearían y la caja
|
|
temblaría o se iría hacia un lado; con él, si mantienes pulsadas las dos no gana
|
|
ninguna y la caja se queda quieta, que es lo que espera un jugador.
|
|
|
|
`maxX` se calcula una vez arriba, con `overlayGetWidth` como en la lección dos,
|
|
y el `- 1` es porque el último píxel de la superficie es uno menos que su ancho.
|
|
Quita el `- 1` y el borde derecho de la caja se sale de la superficie y
|
|
desaparece, porque Singe tira sin quejarse todo lo que dibujas fuera del
|
|
overlay.
|
|
|
|
Los bloques que limitan la posición usan `elseif` en lugar de dos bloques `if`
|
|
separados. La caja no puede salirse por el borde izquierdo y por el derecho a la
|
|
vez, así que hacer la segunda pregunta cuando la primera era verdadera es
|
|
trabajo perdido.
|
|
|
|
Y la decisión del color es un `if` cuyo único trabajo es elegir un valor.
|
|
Amarillo mientras el jugador está haciendo algo, azul mientras no, que es una
|
|
forma gratis de ver que tu manejo de la entrada funciona de verdad.
|
|
|
|
[#_try_it_3]
|
|
=== Pruébalo
|
|
|
|
. *Cambia la velocidad.* `BOX_SPEED = 1` y `BOX_SPEED = 8`. Con ocho, fíjate en
|
|
lo que hace el límite cuando empujas la caja contra una esquina.
|
|
. *Muestra tus indicadores.* Añade una línea a `onOverlayUpdate`:
|
|
`overlayPrint(0, 1, "left " .. tostring(movingLeft))`. Mantén pulsada y suelta
|
|
la flecha izquierda y mira cómo cambia. Luego quita el `tostring` y lee el
|
|
error que te da Lua.
|
|
. *Que vaya más rápido con un botón pulsado.* Añade un indicador `running`
|
|
controlado por `SWITCH_BUTTON1` en los dos callbacks de entrada, y usa un `if`
|
|
en `onOverlayUpdate` para moverte el doble de `BOX_SPEED` mientras sea
|
|
verdadero.
|
|
. *Marca el centro.* Dibuja una segunda caja, pequeña, en el centro de la
|
|
ventana, que cambie de color cuando la caja del jugador se solape con ella.
|
|
Necesitarás `and` para unir cuatro comparaciones, y es más difícil de lo que
|
|
parece. Que te salga un poco mal es normal; la lección once lo hace como es
|
|
debido.
|
|
. *Quita lo de soltar.* Borra toda la función `onInputReleased` y ejecútalo. La
|
|
caja da un paso y luego no para nunca. Averigua qué línea no se alcanza nunca
|
|
ahora.
|
|
|
|
[#_break_it_on_purpose_3]
|
|
=== Rómpelo a propósito
|
|
|
|
Busca el bloque que limita la posición y cambia el `<` de su primera comparación
|
|
por un solo signo igual, para que quede así:
|
|
|
|
[source,lua]
|
|
----
|
|
if boxX = 0 then
|
|
----
|
|
|
|
Guárdalo. La ventana se queda ahí vacía y la terminal dice:
|
|
|
|
----
|
|
Error running script: decisions/decisions.singe:57: 'then' expected near '='
|
|
----
|
|
|
|
Este es el error del que avisaba toda la sección sobre `==`, y el mensaje es un
|
|
buen ejemplo de un error que es totalmente exacto y totalmente inútil si te lo
|
|
tomas al pie de la letra. Lua no te está pidiendo que añadas la palabra `then`.
|
|
Lua llegó hasta `if boxX`, esperaba a continuación una comparación o un `then`,
|
|
y se encontró `=`, que no puede ser ninguna de las dos cosas.
|
|
|
|
Siempre que veas `'then' expected` después de un `if`, busca un `=` simple que
|
|
debería ser doble. Casi siempre es eso.
|
|
|
|
Fíjate en dónde ha aparecido este: el juego ni siquiera llegó a empezar, igual
|
|
que con la cadena sin terminar de la lección uno, porque Lua no podía leer el
|
|
archivo. El error del contador de fotogramas de la lección dos pasó durante la
|
|
ejecución. Los errores en la forma de tu código te paran antes de que empiece el
|
|
juego. Los errores en lo que hace tu código esperan a que se ejecute.
|
|
|
|
[#_what_you_learned_3]
|
|
=== Lo que has aprendido
|
|
|
|
* `if question then ... end` ejecuta un cuerpo solo cuando la respuesta es
|
|
verdadera.
|
|
* `else` da el otro caso, y `elseif` encadena más; gana la primera pregunta
|
|
verdadera y las demás no se llegan a hacer.
|
|
* Las comparaciones son `<`, `>`, `<=`, `>=`, `==` y `~=`.
|
|
* `=` mete un valor en un nombre. `==` pregunta si dos cosas son iguales.
|
|
* `true` y `false` son valores, y puedes guardarlos en variables.
|
|
* `and`, `or` y `not` unen y dan la vuelta a esas respuestas.
|
|
* `nil` es lo que obtienes de un nombre al que nunca has dado valor, y no es
|
|
cero.
|
|
* Todo es verdadero salvo `false` y `nil`.
|
|
* `onInputPressed` y `onInputReleased` son callbacks que Singe llama con un
|
|
valor `SWITCH_` cuando el jugador pulsa o suelta un control.
|
|
* Una pulsación es un evento y ocurre una vez; guarda el estado en una variable
|
|
y actúa según él en cada fotograma.
|
|
* `overlayBox` dibuja un contorno en píxeles, y `colorForeground` es un ajuste,
|
|
no un argumento.
|
|
|
|
[#_next_time_3]
|
|
=== La próxima vez
|
|
|
|
Una caja es fácil. Cien estrellas no lo son, al menos no tal como has escrito
|
|
las cosas hasta ahora, porque escribir cien líneas `overlayPlot` no es el plan
|
|
ideal para una tarde. La lección cuatro trata del bucle: decirle al ordenador
|
|
que haga lo mismo muchas veces, con algo distinto en cada vuelta, que es lo que
|
|
hace posible llenar una pantalla de cualquier cosa.
|