En todo curso de programación se enseñan tarde o temprano las funciones, y este por supuesto no ha sido una excepción. Lo que realmente diferencia un curso de programación de un curso de algorítmica o de métodos numéricos es como se enseñan dichas funciones, o más importante, como se saca partido de las mismas.
En ese sentido, La armada americana (US Navy) ya introdujo en 1960 el principio KISS
Principio que de alguna forma toma prestado el zen de Python. Desafortunadamente, ese principio no siempre se cumple, y a menudo es debido a una deficiente o incluso negligente formación.
Bien es cierto, y es de hecho el origen del problema, que el principio KISS para un profano en programación simplemente carece de significado. Por eso, durante este ejercicio vamos a intentar demostrar de forma práctica las ventajas de tomar como propio ese principio, usando un paradigma que yo he dado en describir como:
“Programar como humanos, no como máquinas
El juego es realmente un acertijo. Supongamos que tenemos un tablero como el siguiente:
Un tablero con 7 huecos, en los que los 3 huecos de la izquierda contienen fichas rojas, mientras que los 3 huecos de la derecha contienen fichas azules.
El objetivo del juego/acertijo, es conseguir que todas las fichas rojas ocupen las posiciones de las fichas azules, y viceversa. Para alcanzar el objetivo las fichas rojas sólo pueden mover hacia la derecha mientras que las fichas azules sólo pueden mover hacia la izquierda.
Los movimientos permitidos son los siguientes:
Tratamos de modificar la manera en la que tradicionalmente se enfoca un problema como este cuando se dan los primeros pasos en programación. Desafortunadamente, esto es estrictamente incompatible con la forma en la que se redacta y describe un notebook the Python, ya que obligaría a ejecutar las celdas en orden inverso.
Por tanto, los que vamos a hacer, es guiarnos por enlaces internos del documento.
La forma de pensar es la siguiente:
Por tanto, el primer paso es crear la función principal.
El juego consiste en una inicialización, en la que se creará el tablero, un bucle en el que el usuario va moviendo fichas hasta que no se puede mover nada más, y un final en el que se comprueba si el usuario ha ganado o ha perdido.
Esto es bien fácil de implementar si se consigue alcanzar un nivel suficiente de abstracción:
def main():
# Creamos el tablero de juego
board = init()
target = board[::-1]
# Vamos pasando turnos hasta que no podamos continuar
while can_move_something(board):
# Pintamos el tablero
show(board)
# Pedimos por teclado una ficha para mover
pos = int(input('Select a token to move (by its position): '))
# Nos aseguramos de que esa ficha puede mover
if not can_move(board, pos):
print('The token cannot move!')
continue
# Y la movemos
board = move(board, pos)
# Comprobamos si ha ganado
if board == target:
print('ENHORABUENA!')
else:
print('Pringao!')
Visto de esta forma, el juego parece bien simple, ¿cierto?
Efectivamente, al ejecutar la celda no nos da ningún error a pesar de que aún no hemos creado las siguientes funciones:
init()
can_move_something(list)
show(list)
can_move(list, int)
move(list, int)
Eso se debe a que al definir la función ésta no se ejecuta todavía.
Así pues debemos ir definiendo esas funciones. Podemos empezar por init()
, que es la más sencilla.
La creación del tablero no es demasiado compleja. Tan sólo hay que crear una lista con 7 números enteros, en el que el 0
representa el hueco vacío, el 1
las fichas rojas, y el -1
las fichas azules.
Puede parecer que la elección del valor para las fichas azules es un poco caprichoso, pero no lo es en absoluto, ya que de paso nos permite saber la dirección en la que avanzan, lo que nos ahorrará un poco de trabajo
def init():
return [1] * 3 + [0] + [-1] * 3
Como se puede observar, una función tremendamente simple, hasta tal punto que no requiere de ninguna otra función para trabajar. Por tanto, podemos recuperar la lista de funciones pendientes de main()
, y continuar por la más sencilla, que probablemente es show()
Mostrar el tablero puede ser bastante complejo, pero en ésta ocasión nos limitaremos simplemente a pedirle a Python que nos pinte la lista, sin preocuparnos por el formato:
def show(board):
print(board)
De las funciones que nos faltan, las dos pueden ser muy complejas si no nos esforzamos en abstraernos lo suficiente. Pero con un poquito de abstracción ambas se vuelven sumamente simples. Empezemos por can_move_something(list)
En realidad, ésta función sólo debe recorrer la lista, y preguntar si alguien puede moverse. En caso de encontrar alguna ficha que pueda moverse, devolveremos una respuesta afirmativa:
def can_move_something(board):
for i in range(len(board)):
if can_move(board, i):
return True
return False
Conviene reparar en que ésta función hace uso de otra función can_move(list, int)
, que todavía no hemos creado. Sin embargo, esa función ya la requeriamos en main()
, así que realmente no añadimos más trabajo, tán sólo aprovechamos las ventajas de usar funciones.
Entre las funciones que aún tenemos pendientes, can_move(list, int)
y move(list, int)
, claramente move(list, int)
debe ser nuestra siguiente candidata.
En un principio la función move(list, int)
puede parecer más compleja de lo que es, pero en realidad es una función muy sencilla si tenemos en cuenta que:
Así que en realidad esta función sólo debe intercambiar los valores de la posición elegida y del hueco.
def move(board, token):
hole = get_hole(board)
board[token], board[hole] = board[hole], board[token]
return board
En ésta ocasión, hemos añadido una nueva función que debemos implementar, get_hole(list)
, pero que es bien sencilla.
Ésta función tan sólo debe buscar donde se encuentra el 0 en la lista, que además sabemos que será único.
def get_hole(board):
for i,token in enumerate(board):
if not token:
return i
Finalmente, debemos enfrentarnos a la función más complicada, can_move(list, int)
.
Ésta función puede volverse muy complicada, así que conviene tomarse un tiempo para pensar una estrategia. En nuestro caso vamos a intentar aprovechar el hecho de que sólo hay un hueco:
def can_move(board, token):
# Excluimos por supuesto fichas fuera del tablero
if not -1 < token < len(board):
return False
# Y excluimos tambien el hueco
if not board[token]:
return False
# Vamos a ver donde esta el hueco
hole = get_hole(board)
# Si el hueco es contiguo, y esta en el lado correcto, entonces sabemos positivamente que podemos mover
if token + board[token] == hole:
return True
# Si no es el caso, la ficha esta obligada a saltar
if (token + 2 * board[token] == hole) and (board[token + board[token]] != board[token]):
return True
# Si la ficha no puede avanzar o saltar, entonces no se puede mover
return False
Ya tenemos el juego listo, así que es momento de jugar!
main()
[1, 1, 1, 0, -1, -1, -1] Select a token to move (by its position): 2 [1, 1, 0, 1, -1, -1, -1] Select a token to move (by its position): 5 The token cannot move! [1, 1, 0, 1, -1, -1, -1] Select a token to move (by its position): 4 [1, 1, -1, 1, 0, -1, -1] Select a token to move (by its position): 6 The token cannot move! [1, 1, -1, 1, 0, -1, -1] Select a token to move (by its position): 5 [1, 1, -1, 1, -1, 0, -1] Select a token to move (by its position): 4 The token cannot move! [1, 1, -1, 1, -1, 0, -1] Select a token to move (by its position): 3 [1, 1, -1, 0, -1, 1, -1] Select a token to move (by its position): 1 [1, 0, -1, 1, -1, 1, -1] Select a token to move (by its position): 2 [1, -1, 0, 1, -1, 1, -1] Select a token to move (by its position): 0 [0, -1, 1, 1, -1, 1, -1] Select a token to move (by its position): 1 Pringao!
En esta clase hemos visto cómo crear funciones que encapsulen tareas de nuestro programa y las hemos aplicado para respondernos ciertas preguntas sencillas.
Referencias
Las siguientes celdas contienen configuración del Notebook
Para visualizar y utlizar los enlaces a Twitter el notebook debe ejecutarse como seguro
File > Trusted Notebook
# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = '../styles/aeropython.css'
HTML(open(css_file, "r").read())