#!/usr/bin/env python
# coding: utf-8
#
# # El juego del salto de la rana
# ## Objetivos del ejercicio
# 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_**
# ## Descripción del juego
# 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:
# * Se puede mover una ficha una única casilla hasta el hueco, que por supuesto deberá ser contiguo:
# 
# * O se permite avanzar dos casillas con una ficha, saltando otra ficha del color contrario:
# 
# ## Implementando el juego
# 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:
# * Primero actuamos como usuarios de librerías. Es decir, programamos aquello que deseamos, sin preocuparnos de si realmente disponemos de todas las funciones a las que estamos llamando.
# * Segundo recolectamos y listamos todas las funciones que necesitamos y que no están implementadas.
# * Empezamos por la más sencilla, y vamos una por una implementandolas (volviendo al principio de ésta lista).
#
# Por tanto, el primer paso es [crear la función principal](#main).
# ### main
#
# 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:
# In[1]:
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.
# ### init()
#
# 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
# In[2]:
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()`
# ### show(list)
#
# 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:
# In[3]:
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)`
# ### 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:
# In[4]:
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.
# ### move(list, pos)
#
# 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:
#
# * Ya hemos comprobado que la ficha se puede mover
# * Sólo existe una única posición a la que mover, pues sólo existe un hueco
#
# Así que en realidad esta función sólo debe intercambiar los valores de la posición elegida y del hueco.
# In[5]:
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.
# ### get_hole(list)
#
# Ésta función tan sólo debe buscar donde se encuentra el 0 en la lista, que además sabemos que será único.
# In[6]:
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)`.
# ### can_move(list, pos)
#
# É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:
# In[7]:
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
# # A jugar!
#
# Ya tenemos el juego listo, así que es momento de jugar!
# In[10]:
main()
# ---
# _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**
#
# * Libro "Learn Python the Hard Way" http://learnpythonthehardway.org/book/
# * Python Tutor, para visualizar código Python paso a paso http://pythontutor.com/
# * Libro "How To Think Like a Computer Scientist" http://interactivepython.org/runestone/static/thinkcspy/toc.html
# * Project Euler: ejercicios para aprender Python https://projecteuler.net/problems
# * Python Challenge (!) http://www.pythonchallenge.com/
# ---
#
# ####