Curso Kotlin | #12. Bucle While

Los bucles nos permiten iterar una serie procedimientos definidos. Dicho de una forma mas mundana, recorren una serie de lineas de código determinado por una X cantidad de veces. Los más conocidos en la mayoría de los lenguajes de programación son For y While. Hoy vamos a poner en práctica al segundo.

Iterando la Lotería

No tengo idea como puedo reutilizar un ejemplo tantas veces, pero aquí estamos, con otra aplicación interesante de conceptos a nuestra querida lotería.

En la clase anterior utilizamos la sentencia when para crear un sistema de puntuación dependiendo la posición de nuestro número ganador:

var score = 0

    when (selectedNum?.toInt()) {
        numbers[0] -> score += 5
        numbers[1] -> score += 4
        numbers[2] -> score += 3
        numbers[3] -> score += 2
        numbers[4] -> score += 1
        else -> {
            println("Has perdido! El numero era ${numbers[0]}")
            return
        }
    }

    println("Has ganado! Tu score final es $score")

El problema con esto es que si bien estamos sumarizando valores a la variable score, este programa se ejecuta una sola vez. Esto significa que no podemos hacer más que 5 puntos. Nuestra variable solo se modifica una vez, por lo cual no tiene mucha utilidad. Necesitamos iterar el comportamiento del when; armar un bucle que repita la operación y nos permita seguir jugando hasta cansarnos. Es el momento de usar la sentencia While.

Implementación de While

While, al igual que If, evalúa una expresión y acciona en base a la misma. La diferencia es que en el segundo caso, existirá un bucle, una ejecución repetitiva, hasta que la condición se deje de cumplir. Vamos a un ejemplo sencillo:

val isRunning = true
    
while (isRunning){
   println("Estoy corriendo!")
}

Mientras la expresión evaluada en While sea verdadera (true), se imprimirá en pantalla la frase “Estoy corriendo!”. Tenemos un bucle: todo lo que esté dentro de While se ejecutará hasta que la expresión sea falsa (false). Si dejamos el código así, tenemos lo que llamamos un “bucle infinito”. Nuestro programa seguirá corriendo hasta que se quede sin memoria o se termine forzadamente.

Un desarrollador que nació ayer (o escribe mensajes motivacionales en LinkedIn) te va a decir algo como “¡oh no, esto está mal!”. La realidad es que usar un bucle infinito no es algo incorrecto: depende de tu propósito. Por ejemplo en un videojuego, es muy común encontrar algo asi:

var isRunningAnimation = true
val isJumping = false

while (isRunningAnimation){
    println("Estoy corriendo!")

    if(isJumping) {
        println("Estoy saltando!")
        isRunningAnimation = false
    }
}

Mientras el jugador esta corriendo, se “reproduce” la animación de correr. Pero si presiona el botón de salto, la animación cambia y isRunningAnimation cambia a false, lo cual implica que en la siguiente iteración While evalúa que no se esta corriendo, y sale del bucle.

Esto es una explicación muy vaga porque en el desarrollo de videojuegos se utilizan máquinas de estado; pero la realidad es que existen los bucles infinitos para determinar estos mismos estados. Por lo cual, no existen las malas prácticas con los bucles siempre y cuando esten generando los resultados esperados.

Volviendo a nuestra lotería, ya te estas haciendo una idea de la utilidad que tiene While para nuestro ejemplo. Implementemos un “comando” para nuestra consola:

println("Elije un numero de los siguientes: ${numbers.contentToString()}")
println("Escribe exit para salir del juego.")

Esto por supuesto es plenamente visual, pero vamos a encapsularla dentro de while:

var score = 0
var isRunning = true

while (isRunning){
    println("Elije un numero de los siguientes: ${numbers.contentToString()}")
    println("Escribe exit para salir del juego.")
    numbers.shuffle()
    val selectedNum = readLine()

    if(selectedNum == "exit"){
        isRunning = false
    }

    when (selectedNum?.toInt()) {
        numbers[0] -> score += 5
        numbers[1] -> score += 4
        numbers[2] -> score += 3
        numbers[3] -> score += 2
        numbers[4] -> score += 1
        else -> println("Has perdido! El numero era ${numbers[0]}")
    }

    println("Has ganado! Tu score final es $score")
}

Vamos a dividir todo lo que esta ocurriendo en algunos pasos, para mayor entendimiento:

  • Definimos la variable score, inicializada en cero.
  • Definimos una variable booleana isRunning inicializada en true.
  • Comprobamos que la loteria siga funcionando mientras isRunning sea true.
  • Imprimimos las instrucciones en pantalla y mezclamos el array.
  • Comprobamos el input de selectedNum, y si escribió “exit” salimos.
  • Si escribió un número, tenemos el when de siempre pero quitamos el return porque queremos que la iteración se complete.
  • Finalmente, si ganamos, mostramos el mensaje con la variable score acumulando los puntos, y vuelta a empezar.

Todo esto suena genial hasta que lo ejecutamos, ganamos, perdemos y queremos salir escribiendo “exit”:

Nos encontramos con un error en la linea 19, vamos a ver que pasó:

when (selectedNum?.toInt()) {

No podemos castear “exit” a un entero; pero tampoco es el comportamiento que esperábamos. Esto ocurrió porque luego de la validación, nuestra instrucción While continuó ejecutando el resto del bloque. El funcionamiento de nuestro bucle es correcto, pero no es lo que queríamos. Podemos solucionarlo de la siguiente manera:

if(selectedNum == "exit"){
    isRunning = false
    break
}

La sentencia break genera, como lo dice la palabra, un salto en el código. Nos permite saltar entre condicionales y bucles, dependiendo de donde estemos. En este caso, si el jugador escribió “exit”, comprobaremos el valor de selectedNum y saldremos del bucle. Pero ahora Intellij nos indica que isRunning ya no tiene utilidad.

Por supuesto, tiene razón: si incluimos un break en este caso, nuestra variable de estado dejó de tener mucho sentido. Por lo cual, vamos a eliminarla y decirle a while lo siguiente:

while (true){

Esto podría ser un bucle infinito, porque “true” siempre es verdadero. No hay una condición que pueda decir lo contrario. Sin embargo, al usar break estamos generando una salida determinada, pero sin una variable que cambie en el bucle. Este sería el código final:

val numbers = arrayOf(1, 4, 6, 9, 15, 30, 45, 60, 78, 90)

var score = 0

while (true){
    println("Elije un numero de los siguientes: ${numbers.contentToString()}")
    println("Escribe exit para salir del juego.")
    numbers.shuffle()
    val selectedNum = readLine()

    if(selectedNum == "exit"){
        break
    }

    when (selectedNum?.toInt()) {
        numbers[0] -> score += 5
        numbers[1] -> score += 4
        numbers[2] -> score += 3
        numbers[3] -> score += 2
        numbers[4] -> score += 1
        else -> println("Has perdido! El numero era ${numbers[0]}")
    }

    println("Has ganado! Tu score final es $score")
}

Y dado que tenemos solo una linea en el If, podríamos acotarlo de la siguiente manera:

if(selectedNum == "exit") break

Con esto tenemos nuestra lotería funcional e iterativa.

Conclusiones

Me gusta que te puedas llevar la idea de que el bucle While es mucho más poderoso de lo que realmente se piensa. También es importante remarcar el hecho de que un bucle infinito no siempre es malo, si la intención es justificada. En la próxima clase vamos a tratar con otro tipo de bucle un poco más complejo pero fundamental cuando se trata de listas y arreglos: For.

¡Deja un comentario!

Artículos relacionados

Curso Kotlin | #19. Pair

A veces necesitamos relacionar dos valores y almacenarlos en una variable única, y para ello tenemos a Pair.

Curso Kotlin | #17. Proyecto: The Hero Legacy

Si empezaste esta serie desde cero y ya leíste 16 capítulos, ¡Felicidades! Empezaste a dominar los primeros conceptos fundamentales en Kotlin. Este es el primer paso de tu senda como desarrollador/a. Estamos en una instancia en donde podemos poner en práctica todo lo que aprendiste hasta ahora. Bienvenido/a al primer proyecto de la serie.

Curso Kotlin | #16. Funciones

Las funciones son procedimientos que se pueden reutilizar y nos permiten encapsular comportamientos y mejorar la lectura del código.

Curso Kotlin | #15. Excepciones

Las excepciones en Kotlin nos permiten evitar comportamientos no deseados ante problemas que se presentan en tiempo de ejecución. Por ejemplo cuando se intenta dividir por cero, castear un tipo por otro no válido, intentar agregar un valor a una lista nula, entre otros casos. Hoy vamos a ver su implementación en el código.

Curso Kotlin | #14. Rangos

Cuando hablamos de rangos, nos referimos a un intervalo de números en el sentido matemático. Podemos generar, por ejemplo, un rango que comprenda los números del 1 al 10. Hoy veremos como aplicarlo a los conceptos anteriormente aprendidos.