Diferencia entre revisiones de «PrInf15: Juego: acertar en un polígono»
(→Versión con límite de intentos) |
(→Versión con límite de intentos) |
||
| Línea 143: | Línea 143: | ||
{{ Tarea | Modifica el programa anterior, para que si el usuario consume tres intentos sin acertar, termine el bucle usando ''break''. Actualiza también la salida de datos para informar al usuario de que ha perdido por agotar tres intentos, en caso de que no acierte.}} | {{ Tarea | Modifica el programa anterior, para que si el usuario consume tres intentos sin acertar, termine el bucle usando ''break''. Actualiza también la salida de datos para informar al usuario de que ha perdido por agotar tres intentos, en caso de que no acierte.}} | ||
En realidad, no es necesario usar ''break'' para terminar el bucle en caso de que agote el número de intentos. También podríamos modificar la condición del programa usando un operador lógico, para que el bucle solo continúe mientras no acierte, pero tampoco haya consumido el límite de intentos. En la práctica [[PrInf14: Interrumpir bucles while]] veíamos un ejemplo que usaba ''break''; en el ejercicio post-práctica se pedía eliminar el ''break'' usando un operador lógico. | En realidad, no es necesario usar ''break'' para terminar el bucle en caso de que agote el número de intentos. También podríamos modificar la condición del programa usando un operador lógico, para que el bucle solo continúe mientras no acierte, pero tampoco haya consumido el límite de intentos. En la práctica [[PrInf14: Interrumpir bucles while]] veíamos un ejemplo que usaba ''break''; en el ejercicio post-práctica se pedía eliminar el ''break'' usando un operador lógico. | ||
| − | {{ Tarea | Quita el ''break'' del programa que acabas de escribir, y modifica la condición del bucle para que el comportamiento del programa no cambie.}} | + | {{ Tarea | Quita el ''break'' del programa que acabas de escribir, y modifica la condición del bucle para que el comportamiento del programa no cambie. Es decir, si el usuario no acierta en tres intentos, no debería seguir pidiendo puntos y debería mostrar el mensaje diciendo que ha perdido al agotar todos los intentos disponibles.}} |
== Ejercicio post-práctica == | == Ejercicio post-práctica == | ||
Revisión del 00:46 9 ago 2013
| Práctica de Informática | |
|---|---|
| Juego: acertar en un polígono | |
| Práctica anterior | Siguiente práctica |
| Este artículo es un guión de prácticas de Informática | |
En esta práctica vamos a aplicar los conocimientos que hemos adquirido sobre bucles para crear un juego sencillo. Se trata de pedir al usuario un punto, que representa una posición en el plano, y comprobar si el punto está dentro de un polígono. El jugador acierta si proporciona un punto dentro del polígono. Si no acierta dentro del polígono, se le da otra oportunidad. Si alcanza un número límite de intentos, pierde la partida.
Contenido
1 Requisitos previos
Es importante haber realizado las siguientes prácticas antes de realizar esta práctica:
2 Comandos que se aprenderán en esta práctica
| fill | inpolygon |
3 Contenido de la práctica
Al igual que en la práctica anterior, en este caso usaremos un bucle while que tiene una condición compleja. Para implementar esta condición podemos usar tanto un operador lógico, como un break dentro del bucle. En la práctica usaremos un break, y dejaremos como ejercicio post-práctica la implementación que usa un operador lógico y evita el break.
Este programa tiene dos partes complejas, que no hemos programado hasta ahora:
- Representar y dibujar un polígono
- Comprobar si un punto está dentro de un polígono
3.1 Representar y dibujar un polígono
Para representar y dibujar un polígono podemos usar la función fill.
| |
Tarea: | Comprueba cómo se usa la función fill usando help fill |
La función fill acepta tres argumentos de entrada:
- Un vector con las coordenadas x de los vértices del polígono
- Un vector con las coordenadas y de los vértices del polígono
- El color con el que se va a rellenar el polígono
Cada vértice del polígono es un punto en el plano, que tendrá dos coordenadas. Por ejemplo, el punto de la imagen de la derecha tiene como coordenadas [math]x=3[/math] e [math]y=2[/math], y se representa como [math](3,2)[/math]. Si tenemos varios puntos y los unimos, podemos formar un polígono. Por ejemplo, con tres puntos podemos formar un triángulo.
El comando fill se encarga de unir los puntos y dibujar el polígono. Pero tiene una manera peculiar de recibir los puntos que forman el polígono. En vez de recibir una lista de puntos, acepta por un lado la lista de las coordenadas x de esos puntos, y como segundo argumento la lista de las coordenadas y de los mismos puntos. Además, necesita también que especifiquemos el color con el que va a pintar el polígono. Si alguno de estos tres argumentos no es correcto, la función fill no pintará el polígono.
Por ejemplo, el triángulo de la derecha está formado por los tres puntos siguientes: [math]\begin{matrix} P1:& (0, 0)\\ P2:& (0.5, 1)\\ P3:& (1, 0)\\ \end{matrix}[/math] Sin embargo, para poder representarlo con el comando fill tenemos que separar las coordenadas x e y de cada punto. El orden en el que ponemos las coordenadas es importante, ya que tanto el vector x como el vector y tienen que coincidir en sus posiciones (de lo contrario, estaríamos representando un polígono diferente):
x = [0 0.5 1];
y = [0 1 0];
fill(x, y, 'g'); % Lo pinta de color verde, g viene de greenEl comando anterior dibujaría el triángulo relleno de color verde. La forma del triángulo es similar a la mostrada en la figura de la derecha.
3.2 Comprobar si un punto está dentro de un polígono
Para comprobar si un punto dado cae dentro o fuera de un polígono, podemos usar la función inpolygon.
| |
Tarea: | Comprueba cómo se usa la función inpolygon con help inpolygon. ¿Cuántos argumentos de salida y entrada tiene esta función? |
La función inpolygon espera el polígono en el mismo formato que la función fill, es decir, con los valores de las coordenadas x e y en vectores separados. Además, acepta dos números que representan el punto que vamos a comprobar. Por ejemplo, en el triángulo que hemos representado anteriormente, está claro que el punto [math](0.5, 0.5)[/math] cae dentro del polígono, mientras que el punto [math](3,2)[/math] cae fuera. Veamos el resultado de unas pruebas en la línea de comandos de Octave UPM:
>> x = [0 0.5 1];
>> y = [0 1 0];
>> inpolygon(0.5, 0.5, x, y)
ans = 1
>> inpolygon(3, 2, x, y)
ans = 0Como vemos, en el caso del punto que está dentro la función devuelve 1, y en el caso del punto que está fuera ha devuelto 0.
| |
Tarea: | Ejecuta los comandos anteriores. ¿De qué tipo es el valor que devuelve la función inpolygon? |
| |
Tarea: | El punto (0,0) es uno de los vértices del triángulo. Según la función inpolygon, ¿está dentro o fuera del triángulo? |
Conociendo fill e inpolygon sería ya posible implementar el juego pedido en esta práctica. En la siguiente sección vamos a proporcionar una primera versión del juego. En esta versión, no hay límite de intentos para el jugador. Puedes intentar hacer tú esta versión del juego antes de pasar a la próxima sección.
3.3 Primera versión del juego
Como hemos hecho hasta ahora, vamos a estructura el programa en tres partes:
- Entrada de datos
- Algoritmo
- Salida de datos
En este programa, la entrada de datos es el punto que pedimos al usuario, y que probaremos para ver si está dentro del polígono. El polígono lo incluiremos nosotros directamente en el código fuente, aunque también podríamos pedir los vectores de coordenadas al usuario. Pero por hacer el programa más sencillo para los usuarios, lo fijaremos nosotros en el código fuente.
El algoritmo consiste en comprobar si el punto está dentro o fuera del polígono. Si está fuera, hay que pedir otro punto al usuario, hasta que acierte con un punto que está dentro. Como podemos comprobar, el algoritmo tendrá una parte de entrada de datos también, ya que en caso de que el usuario no acierte, tiene que preguntar nuevamente otro punto.
En cuanto a la salida de datos, cuando el jugador haya acertado le mostraremos un mensaje diciendo el número de intentos que ha necesitado para acertar con un punto dentro del polígono.
Vayamos con la primera parte del programa. Como tenemos que preguntar un punto, vamos a pedir al usuario que los introduzca como un vector, usando corchetes. De este modo, podemos preguntar las dos coordenadas usando un único comando input (si no, tendríamos que preguntar cada coordenada por separado):
%% Juego: acertar en un polígono
%% Versión: 0.1
%% Fecha: Agosto de 2013
% Entrada de datos
p = input('Introduce el punto de la forma [x y]: ');
% Vértices del polígono
x = [0 0.5 1];
y = [0 1 0];Como podemos ver, hemos añadido al inicio algunos comentarios que identifican al programa. Estos comentarios pueden ser útiles cuando estemos leyendo código que hemos escrito hace tiempo. Es muy útil asignar un número de versión a cada programa que hagamos, para saber si lo hemos modificado. La fecha es también importante, ya que nos hace recordar cuándo escribimos el código. También puedes añadir tu nombre, por si distribuyes el código por Internet.
Vamos ahora a programar el algoritmo. En el algoritmo, tenemos que llevar un contador del número de tiradas, y comprobar si el punto está dentro del polígono. En esta versión, vamos a inicializar tiradas con el valor 1, por la manera en la que vamos a implementar el algoritmo. En otras implementaciones alternativas, podríamos haber inicializado la variable tiradas a 0. Si no lo está, hay que preguntar otro punto al usuario:
% Algoritmo
tiradas = 1;
xj = p(1); % Coordenada x del punto del jugador
yj = p(2); % Coordenada y del punto del jugador
while ~inpolygon(xj, yj, x, y)
disp('El punto está fuera del polígono. Inténtalo de nuevo.');
tiradas = tiradas + 1;
p = input('Introduce el punto de la forma [x y]: ');
xj = p(1);
yj = p(2);
endComo vemos, usamos el operador ~ para negar el resultado de inpolygon. Literalmente, estamos repitiendo el bucle mientras el punto no esté en el polígono. Si el usuario no ha acertado a la primera, le mostramos un mensaje informativo y le pedimos otro punto. Es muy importante actualizar las variables xj e yj dentro del bucle. De lo contrario, el bucle estaría continuamente usando los valores que introdujo el usuario al principio del programa.
Una vez que el usuario haya introducido un punto dentro del polígono, la condición del bucle se volverá falsa, y el programa continuará justo después del bucle. La variable tiradas se ha estado incrementando cada vez que el usuario fallaba, así que ya sabemos el número de intentos que ha necesitado. Vamos a programar entonces la salida de datos:
% Salida de datos
fprintf('Has acertado en %d intentos!\n', tiradas);
3.4 Una versión más gráfica del juego
Esta versión es muy similar a la anterior, pero vamos a añadirle una parte gráfica, para que el jugador pueda ver el polígono y sus intentos. En la línea 10 borramos la ventana de gráficos para asegurarnos que no contiene ningún resto de un gráfico anterior. En las líneas 11 y 12 dibujamos el polígono, y ejecutamos hold on para que todos los gráficos que se dibujen después se superpongan con el actual.
La línea 19 dibuja el primer intento introducido por el usuario, y en la línea 27 se dibuja cada uno de los nuevos intentos.
La ventana de gráficos aparecerá justo después de introducir el primer punto, y cada vez que introduzcamos un nuevo punto. Podemos cerrarla cuando ya la hayamos visto, y aparecerá de nuevo con todos los contenidos tras cada nuevo intento.
%% Juego: acertar en un polígono
%% Versión: 0.1
%% Fecha: Agosto de 2013
% Entrada de datos
p = input('Introduce el punto de la forma [x y]: ');
% Vértices del polígono
x = [0 0.5 1];
y = [0 1 0];
clf; % Borra la ventana de gráficos
fill(x, y, 'g'); % Dibuja el polígono en color verde
hold on; % Dibuja siempre encima del gráfico actual
% Algoritmo
tiradas = 1;
xj = p(1); % Coordenada x del punto del jugador
yj = p(2); % Coordenada y del punto del jugador
% Dibuja el punto con una cruz, con una línea de grosor 8
plot(xj, yj, 'x', 'LineWidth',8);
while ~inpolygon(xj, yj, x, y)
disp('El punto está fuera del polígono. Inténtalo de nuevo.');
tiradas = tiradas + 1;
p = input('Introduce el punto de la forma [x y]: ');
xj = p(1);
yj = p(2);
plot(xj, yj, 'x', 'LineWidth',8); % Dibuja el nuevo intento
end
% Salida de datos
fprintf('Has acertado en %d intentos!\n', tiradas);
hold off;3.5 Versión con límite de intentos
En los programas anteriores, el usuario tiene que seguir introduciendo puntos de manera indefinida hasta que acierte dentro del polígono. Si nunca acierta dentro del polígono, el programa seguirá pidiendo puntos y nunca terminará. Un programa debería intentar terminar siempre. En este caso, debería terminar tras varios intentos fallidos.
En realidad, no es necesario usar break para terminar el bucle en caso de que agote el número de intentos. También podríamos modificar la condición del programa usando un operador lógico, para que el bucle solo continúe mientras no acierte, pero tampoco haya consumido el límite de intentos. En la práctica PrInf14: Interrumpir bucles while veíamos un ejemplo que usaba break; en el ejercicio post-práctica se pedía eliminar el break usando un operador lógico.
4 Ejercicio post-práctica
En el programa que hemos escrito en esta práctica, en cualquiera de sus versiones hay algunas partes que están duplicadas:
- el comando input que le pide el punto al usuario
- las variables xj e yj que recogen las coordenadas de p
- el comando plot que dibuja el punto introducido por el usuario
Esta duplicación del código no es una buena característica. Por ejemplo, si decidiéramos cambiar cómo se dibuja el punto, tendríamos que tocar en dos sitios del programa. Es fácil que se nos olvide tocar en algún sitio, y que tengamos entonces un programa inconsistente.
Es posible simplificar el programa usando una variable bandera. Un posible nombre para esta bandera puede ser estaDentro. Tomaría el valor true si el punto está dentro, y false si no lo está, mediante asignación de la salida de inpolygon.
Si modificamos el bucle while para que use esta bandera, podríamos simplificar el programa para que las partes anteriores solo se escriban en un sitio del programa, que sería dentro del bucle. Esto requeriría que el bucle tuviera que ejecutarse al menos una vez.
Es cierto que en este caso, al usar esta variable bandera, estaríamos moviendo la entrada de datos dentro del bucle, por lo que no sería posible separar la entrada de datos y el algoritmo. Por regla general, no es una buena idea mezclar diferentes partes del programa, pero en este caso evita la duplicidad del código, por lo que es la solución preferida.
| |
Tarea: | Modifica el programa para que use la variable estaDentro como bandera, y no contenga líneas duplicadas. |
Recuerda que en la práctica PrInf11: Bucles while se mostraban algunos ejemplos de uso de variables bandera con bucles while.