Algoritmia: Del Análisis Del Problema Al Código
Algoritmia: Del Análisis Del Problema Al Código
Algoritmia: Del Análisis Del Problema Al Código
14-4-2015
1
2
ADVERTENCIA:
Este libro se encuentra en preparación.
Prohibida la reproducción de su contenido y solo
podrá ser utilizado para fines didácticos.
3
Contenido
PROLÓGO .......................................................................................................................................................................8
Dedicatoria ..................................................................................................................................................................10
INTRODUCCION ......................................................................................................................................................11
1. EL RAZONAMIENTO LÓGICO ..........................................................................................................................13
Ejercicios de Razonamiento lógico y matemático ............................................................................................13
2. PROBLEMAS Y ALGORITMOS ...................................................................................................................................29
El Problema ...........................................................................................................................................................29
El método ...............................................................................................................................................................29
El algoritmo ............................................................................................................................................................30
Divide y Vencerás .................................................................................................................................................30
El beso de la Muerte .............................................................................................................................................32
Las Estructuras de Control en la Programación ..............................................................................................32
El seudocódigo ......................................................................................................................................................41
3. LAS VARIABLES ..................................................................................................................................................43
Problema 1: El cuadrado de un Número .......................................................................................................43
Problema 2. Números primos .........................................................................................................................45
EJERCICIOS BASICOS CON PYTHON ..........................................................................................................................49
Depurando el código ............................................................................................................................................52
Problema 3. Sumatoria ....................................................................................................................................53
Problema 4. Números impares. ......................................................................................................................55
Problema 5. Hipotenusa. .................................................................................................................................56
Problema 6. Volumen .......................................................................................................................................58
Problema 7. Menú.............................................................................................................................................62
4. PROCEDIMIENTOS Y FUNCIONES ................................................................................................................66
Paso de variables por valor o por referencia ....................................................................................................68
5. DOCUMENTANDO EL CÓDIGO .......................................................................................................................71
¿Qué documentar? ...............................................................................................................................................71
¿Dónde documentar? ...........................................................................................................................................71
4
Los Docblocks .......................................................................................................................................................72
6. ESTUDIO DE ALGORITMOS GENERICOS ................................................................................................73
Problema 9. Validación. ...................................................................................................................................73
Problema 10. Calificaciones ............................................................................................................................76
Problema 11. Calificaciones con repetición ..................................................................................................78
Problema 12. Tabla de multiplicar ..................................................................................................................80
Problema 14. Tabla de Operaciones básicas ...............................................................................................82
Problema 15. Estacionamiento .......................................................................................................................88
Problema 16. Día de una fecha ......................................................................................................................96
Problema 17. Calendario .................................................................................................................................99
Problema 18. Nomina .....................................................................................................................................104
7. OTROS TIPOS DE VARIABLES (Arreglos, vectores y matrices) ...............................................................107
Problema 19. Nómina con nombres .............................................................................................................107
Problema 20. Nómina y billetes ....................................................................................................................111
Problema 21. Promedio de vectores ............................................................................................................117
Problema 22. Números no repetidos ...........................................................................................................120
Problema 23. Múltiplos de tres. ....................................................................................................................122
Problema 24. Recorridos en matrices..........................................................................................................124
Problema 25. Visualización de números .....................................................................................................134
Problema 26. Visualización vertical de números. ......................................................................................139
8. ARITMETICA BINARIA, OCTAL, DECIMAL Y HEXADECIMAL ................................................................143
Encontrar el equivalente en decimal de un valor dado en otra base ..........................................................143
Encontrar el equivalente en una base dada, de un valor en decimal .........................................................144
Problema 27: Bases numéricas. ...................................................................................................................146
Problema 28: Bases numéricas 2. ...............................................................................................................149
9. RECURSIVIDAD.................................................................................................................................................154
Ventajas de los algoritmos recursivos .............................................................................................................154
Desventajas de los algoritmos recursivos .......................................................................................................154
Consideraciones para diseñar un algoritmo recursivo ..................................................................................154
Problema 29: Máximo Común Divisor. ........................................................................................................155
Problema 30: Potencia de un Número.........................................................................................................158
10. ALGORITMOS DE BUSQUEDA Y ORDENAMIENTO ...............................................................................162
Búsqueda de un elemento .................................................................................................................................162
5
Problema 31: Número mayor. .......................................................................................................................162
Problema 32: Números mayor y menor.......................................................................................................164
Generar números aleatorios..........................................................................................................................164
Problema 33: Ordenar vector. .......................................................................................................................166
Método de ordenamiento por “burbuja” ..........................................................................................................167
Búsqueda binaria ................................................................................................................................................170
Problema 34: Búsqueda Binaria. ..................................................................................................................170
Método de ordenamiento por selección ..........................................................................................................175
Problema 35: Ordenar vector por el método de selección. ......................................................................176
Método de ordenamiento por Inserción ...........................................................................................................181
Problema 36: Ordenar vector por inserción. ...............................................................................................181
11. REGISTROS .....................................................................................................................................................186
Problema 37: Datos de Estudiantes. ...........................................................................................................186
Estructura de datos tipo Registro .....................................................................................................................186
EJERCICIOS CON C# (C Sharp).................................................................................................................................192
12. SERIALIZACION Y PERSISTENCIA ............................................................................................................196
Problema 38: Guardar Datos. .......................................................................................................................196
Serialización.........................................................................................................................................................196
Persistencia de Datos ........................................................................................................................................196
Problema 39: Movimiento del Caballo. ........................................................................................................204
13. LA PROGRAMACION ORIENTADA A EVENTOS .....................................................................................214
Problema 40: Reloj Análogo. ........................................................................................................................214
Programación por eventos ................................................................................................................................215
Eventos .................................................................................................................................................................216
Delegados ............................................................................................................................................................216
Problema 41: Calculadora. ............................................................................................................................227
El diagrama de estados .....................................................................................................................................235
Completando los requerimientos de la Calculadora. .........................................................................................247
Hacer funcional el teclado .................................................................................................................................250
14. LA PROGRAMACION ORIENTADA A OBJETOS (POO) ...........................................................................................251
Características de la POO .................................................................................................................................251
Relaciones ...........................................................................................................................................................253
Problema 42: Tetris. .......................................................................................................................................254
6
15. LISTAS DE OBJETOS Y PILAS ....................................................................................................................282
Arreglos de objetos ................................................................................................................................................282
Listas de objetos ....................................................................................................................................................282
Pilas........................................................................................................................................................................282
Problema 42: Asistente del Sudoku. ............................................................................................................283
16. DESARROLLO DE UN JUEGO PARA WEB ...............................................................................................301
Problema 43: Juego de Sokoban: ................................................................................................................301
CONCLUSIÓN......................................................................................................................................................339
ANEXO 1. OPERADORES Y FUNCIONES DEFINIDAS EN PYTHON ................................................................................341
operadores .............................................................................................................................................................341
Particularidades los tipos de datos en Python. ......................................................................................................342
Tipos numéricos: ....................................................................................................................................................342
Cadenas de caracteres (str) . .................................................................................................................................343
Tipos inmutables. ...................................................................................................................................................343
Reglas de precedencia en operaciones aritméticas. ..............................................................................................343
Operadores de identidad. ......................................................................................................................................343
ANEXO 2. OPERACIONES LOGICAS Y ALGEBRA BOOLEANA .................................................................345
ANEXO 3: CÓDIGO FUENTE JAVASCRIPT DE SOKOBAN..........................................................................347
ANEXO 4. GLOSARIO ...........................................................................................................................................357
ANEXO 5. REFERENCIAS DE INTERNET ........................................................................................................371
ANEXO 6. BIBLIOGRAFIA ....................................................................................................................................373
7
PROLÓGO
Prologar un libro considero que es un privilegio para quien lo hace. En este caso debo
reconocer que el autor Ing. Marco León Mora Mendez habría tenido múltiples
posibilidades para seleccionar la persona para realizar tan noble tarea. En esta ocasión
el privilegio es mucho mayor. Ante esta situación siento que tengo un problema entre
manos. Surge la tentación, por lo tanto, de utilizar la propuesta de este libro
“ALGORITMIA: Del análisis del problema al código” al problema de escribir el prólogo del
mismo, sin embargo, una lectura más cuidadosa del texto indica que el autor se refiere
a la comprensión del problema y el análisis del mismo con el fin de plantear una solución
basada en un desarrollo incremental, desde lo general a lo particular logrando los detalles
suficientes para llevar el algoritmo a un lenguaje de computador.
Esa es la dirección que sigue el libro que tiene en sus manos, es decir, orienta a quien
tiene un problema de programación, lo conduce hasta lograr obtener un código que, al
ejecutarse en el computador, es una solución al problema planteado. Caso de
estudiantes de ingeniería y técnicos y tecnólogos del SENA, en las formaciones
relacionadas con desarrollo de aplicaciones informáticas, videojuegos, bases de datos,
administración de redes, o la programación de dispositivos de control y elementos IoT
(internet de las cosas), como micro-controladores o PLC (controladores Lógicos
Programables).
8
Desde luego que el lector del presente libro requiere de un buen nivel de habilidades en
lógica matemática, necesaria para enfrentar la solución lógica de un problema. Siendo
consciente que el libro desarrolla una metodología de análisis para desglosar el
problema, poco a poco y a partir de una correcta comprensión previa del mismo, lo
conduce hasta lograr el detalle suficiente para convertir esta solución en un código
ejecutable.
Lo anterior me lleva a declarar que se cuenta con un libro que puede aportar en la
formación integral de los aprendices del SENA y de los estudiantes universitarios de tan
importante área del conocimiento como es la programación. Se cuenta entonces con un
libro que en la construcción del buen juicio buscará encontrar soluciones a problemas
reales en una sociedad a puertas de la 4ta. Revolución Industrial.
9
DEDICATORIA
A todos los aprendices SENA a los que he tenido el gran placer de orientar en sus
procesos de aprendizaje integral. He tenido la gran satisfacción personal de ver a
algunos de ellos, años después, como ciudadanos exitosos y comprometidos con el
desarrollo de sus familias y de la patria.
A través de ellos te dedico esta obra, Marco Alejandro, con ellos he logrado saldar una
pequeña parte de mi deuda contigo.
10
INTRODUCCION
Este libro esta pensado para aquellos que deseen conocer como abordar un problema
de programación, hasta llegar a obtener un código que, al ejecutarse en el computador,
sea una solución al problema planteado.
Los capítulos que siguen muestran, a través de ejemplos, un camino estructurado que
conduzca a la comprensión del problema, al análisis y al planteamiento de una solución
algorítmica a él, usando un proceso claro de desarrollo hasta encontrar la solución,
desde el entendimiento del problema ( el Qué ), hasta el código fuente que, al ser
ejecutado en un computador, sea capaz de solucionar ese problema ( el Cómo ).
11
En el primer capítulo se desarrollan algunos problemas de razonamiento lógico, en cuya
solución se hace uso de varias herramientas, especialmente de tipo grafico, para
entenderlos mejor y encontrar un camino de solución, se quiere mostrar la importancia
que tienen el razonamiento lógico y matemático para encontrar soluciones al problema.
Los capítulos tercero y cuarto explica el uso de las variables, se desarrollan los conceptos
de procedimientos y funciones y presenta el desarrollo de ejercicios básicos, haciendo
uso de un lenguaje de programación para principiantes (LPP).
12
1. EL RAZONAMIENTO LÓGICO
Un razonamiento lógico es un proceso mental que aplica la lógica. Con esta clase de
razonamiento se puede partir de unas premisas para lograr una conclusión que puede
determinarse como verdadera, falsa o posible. El razonamiento lógico se puede iniciar a
partir de una observación (es decir, una experiencia) o de una hipótesis. El proceso
mental de análisis puede desarrollarse de distintas maneras y convertirse en
un razonamiento inductivo, un razonamiento deductivo, etc. Según la clase de
razonamiento empleado, la conclusión tendrá mayor o menor posibilidad de resultar
válida.
Análisis:
13
Un cuarto es la mitad de un medio, es decir:
𝟏
𝟐 =𝟏
𝟐 𝟒
𝟏 𝟐 𝟏𝟐
𝟔∗ =𝟔∗ =
𝟐 𝟒 𝟒
Solución:
Doce Cuartos.
Observe como el uso de dibujos y/o graficas ayuda a un mejor entendimiento del
problema, lo que permite un más rápido acercamiento a la solución. También, la
importancia de tener claridad sobre la matemática básica, en este caso el significado
y las operaciones con quebrados.
Análisis:
En la figura, las dos filas formadas por tres tapas cada una, comparten la tapa del
extremo izquierdo. De los conceptos basicos de la geometría, se tiene que si dos
líneas rectas se cruzan, solo existe un punto común; por lo tanto para compartir otra
tapa, esta deberá estar sobre la primera (en el mismo punto).
Solución:
No siempre la solución sigue el camino “obvio” y muchas veces hay que buscar
soluciones ingeniosas, siempre y cuando no rompan las reglas del enunciado.
14
3. Se cambian 3 naranjas por 2 manzanas y 1 manzana por 3 mangos. ¿Cuántos
mangos se cambian por una naranja?
Análisis:
Planteemos lo anterior como ecuaciones (sea NA: Naranja, MZ: Manzana; MG:
Mango)
3NA = 2MZ;
1MZ = 3 MG;
Es decir, 3 NA = 6 MG;
Solución :
Note como en la ecuación se identifican plenamente las partes del enunciado original:
3 manzanas se cambian (=) por dos veces tres mangos.
Análisis:
Si visualiza dos áreas de 4 x 4, notara que sobra una fila en la mitad, asi que de esa
fila corresponden 2 cuadros al área izquierda y dos a la derecha. La línea roja separa
18 cuadros del primer rectángulo, al hacerlo “agrega” los cuadros faltantes del
segundo rectángulo (4 x 4 + 2). Las dos partes son iguales, ya que si “rota”
mentalmente uno de ellos, lo puede sobreponer sobre el otro.
15
5. Un reloj de pared se parte en 2, Si la suma de los números en cada parte es la
misma, ¿Cuál es esta suma?, ¿por dónde se partió el reloj?
Análisis:
Solución : Cada parte suma 39. Trazar una línea que deje a cada lado tres
parejas.
El reloj se partió por la línea azul. Aprecie como el uso de imágenes ayuda a encontrar
la solución.
Análisis:
16
Solución :
Análisis:
Solución :
Análisis:
Y como forman parejas completas (20 es par) no queda ningún número solo.
17
B. razonando de manera análoga, para la segunda sumatoria son 50 números con
suma 100, es decir 25 parejas.
Solución :
A. Como hay 20 números, entonces son diez parejas de 42. Es decir 10 X 42 = 420.
B. Para los impares de 1 a 100, hay 50 números, emparejando suman de a 100 cada
pareja y son 25 parejas. 25 x 100 = 2500.
Aunque este problema puede ser solucionado solamente con lenguaje matemático,
hemos querido resaltar el uso de dibujos como herramienta para un mejor
entendimiento.
Análisis:
10. Eva tiene 4 años. Su hermana mayor, Ana, es tres veces mayor que ella. ¿Qué
edad tendrá Ana cuando tenga el doble de edad que Eva?
Análisis:
Eva = 4; Ana = 3 x 4 = 12; O sea, Ana nació hace 12 - 4 = 8 años (Ana le lleva 8 años
a Eva).
18
Ordenando: 2X – X = 8 y simplificando: X = 8
Solución :
Análisis Grafico: hacia la derecha (ordenadas) se representan los años. Hacia arriba
(abscisas) las edades.
Avance a la derecha, contando las edades de Eva y Ana: A partir de hoy encontrara:
4 y 12; 5 y 13…etc. hasta que, cuatro años después encontrara 8 y 16,
Es decir Ana tendrá en ese momento el doble de Eva. Los gráficos son una
herramienta muy poderosa para entender y solucionar problemas.
11. En una jaula donde hay conejos y palomas, pueden contarse 35 cabezas y 94
patas. ¿Cuántos animales hay de cada clase?
Análisis:
19
y se reemplaza en (2): 4(35 – P) + 2P = 94.
Solución :
C = 35 – 23; C = 12.
A B C D E
A 0 3 3 1 6
B 3 0 6 2 3
C 3 6 0 4 9
D 1 2 4 0 5
E 6 3 9 5 0
Análisis:
1. A - B: 3 y A - C: 3.
E----3----B----3----A
20
Solución :
El orden es: E, B, D, A, C.
13. De Carla, Betty y Jessica se sabe que solo una de ellas miente, y que la que
miente es la menor de las tres. Si Betty dice que Carla y Jessica son mentirosas,
se puede afirmar que:
Análisis:
SI SOLO UNA es mentirosa, esa es Betty, ya que afirma que “Carla y Jessica son
mentirosas” y evidentemente NO hay dos mentirosas.
Solución :
14. Una receta exige 4 litros de agua: si tuvieras una jarra de 4 litros no habría
problema pero no posees más que 2 jarras sin graduar, una de 5 litros y otra
de 3. ¿Es posible medir los 4 litros que necesitamos?
Análisis:
Existen varias alternativas para medir 4 litros con solo dos medidas: Si llena la de 5L
y luego trasvasas a la de 3L, quedaran 2L en la primera jarra.
21
Solución :
Una opción: Los 2L que quedan en la jarra de 5L lo llevas a la receta, luego repite
para completar 4L.
15. A lo largo de una carretera hay cuatro pueblos seguidos, los rojos viven al lado
de los verdes pero no de los grises, los azules no viven al lado de los grises.
¿Quiénes son vecinos de los grises?
Análisis:
La posibilidad
Solución :
Por lo tanto, la solución es: Azules, Rojos, Verdes, Grises, o a la inversa, ya que no
se dice quien está a la izquierda.
16. Un numero de 4 dígitos es impar, su único dígito par no tiene ningún digito a su
izquierda y es el menor posible, los dígitos impares se encuentran ordenados
de menor a mayor, leídos de izquierda a derecha y ninguno es 3 ni 9. ¿Cuál es
el número?
Análisis:
22
“Un número de 4 dígitos es impar” nos dice que el dígito de la derecha es impar.
“Su único dígito par no tiene ningún digito a su izquierda”, es decir el de la izquierda
es par.
Para solucionar este problema debe tener clara la diferencia entre “digito” y “numero”.
Análisis:
Solución :
23
De la figura anterior se deduce que Andrés tiene 4 goles menos que Claudio.
.
Análisis :
Solución :
24
Análisis:
Solución :
20. Un tanque de reserva de agua utiliza una bomba neumática para surtirse de un
río cercano. Todos los días la bomba sube el nivel del agua 2m; por la noche,
el agua se filtra de regreso al río y el nivel baja 50cm. ¿Cuál es el nivel máximo
alcanzado por el tanque durante el quinto día de llenado?
Análisis:
25
21. ¿Cuál es el menor número de personas que se requiere para que en una familia
haya: un abuelo, una abuela, tres hijos, tres hijas, dos madres, dos padres, una
suegra, un suegro y una nuera?
Análisis:
Análisis:
26
Si el reo le ha preguntado al guarda mentiroso, entonces el que no miente habría
afirmado “… la puerta A” y el mentiroso habría cambiado la respuesta a “…B”.
27
28
2. PROBLEMAS Y ALGORITMOS
El Problema
Planteamiento – Solución,
Pregunta – Encontrar – Valor,
Planteamiento – Método- Resultado,
Proposición – Modo – Resultado…
El método
Observe en las anteriores definiciones que, para poder encontrar una respuesta ( salida )
es necesario tener unos supuestos o datos ( entrada ); el desarrollo del método que
conduce a la solución se denomina Proceso . Sobre este desarrollo trataremos en
adelante.
29
Desarrolle en ese orden. Recuerde que usted no debe empezar a describir una solución
hasta no entender completamente lo que se desea. Es un error que se observa en la
gran mayoría de principiantes (y hasta en los que cuentan con más experiencia) que,
cuando quieren resolver un problema de programación, lo primero que hacen es prender
el computador e iniciar escribiendo código sin comprender en profundidad lo que se
quiere. Esto lleva a errores de estructura del programa, a salidas que no son las
solicitadas, a errores de lógica, etc.
El algoritmo
Un ejemplo:
Solución :
• El primer paso es asegurar el vehículo (ponerle tacos para evitar que se ruede);
• Posteriormente cambiar la llanta;
• Y por último quitar los tacos y guardar todo;
Y…listo.
¿Esperaba una larga lista de pasos detallados? ¿Esos pasos son muy genéricos y no
me permitirán cambiar la llanta? ¿Dónde está el gato? ¿Y la cruceta o llave?
Divide y Vencerás
30
Esta es la clave para desarrollar algoritmos: SIEMPRE agrupe los pasos en una
cantidad pequeña, entre tres y cinco, excepcionalmente hasta siete pasos, que
envuelvan acciones relacionadas. De esta manera, en vez de tener un gran problema,
va a tener de tres a cinco pequeños problemas menos complejos; después, simplemente
repita el procedimiento anterior hasta que cada paso sea lo suficientemente claro y
explícito. Este método es denominado “Divide y Vencerás”.
• El primer paso es asegurar el vehículo (ponerle tacos para evitar que se ruede):
o Ponga el freno de mano,
o Saque los tacos del baúl (normalmente 2)
o Instálelos en llantas opuestas, adelante y atrás.
Por ahora dejémoslo así. Observe que cada paso de la primera iteración se ha
subdividido a su vez en otros pocos pasos, si se presentaran muchos pasos, considere
la posibilidad de reagruparlos. Recuerde: no más de siete (este número se explica a partir
de estudios sicológicos sobre la capacidad del cerebro de atender múltiples problemas).
31
La lista de pasos anterior está más detallada, pero puede que aún no sea suficiente, en
el sentido de que podrían presentarse otras circunstancias que no se han considerado,
por ejemplo, que falte una herramienta o la llanta de repuesto, o que el vehículo este en
movimiento, etc. No importa, en una posterior iteración se podrán considerar esos casos
específicos, siempre y cuando sean necesarios (los veremos más adelante).
El beso de la Muerte
Otro principio de ingeniería reza: “Lo más hermoso es lo más simple”. Aquí es donde
el programador debe enfocar su ingenio: en hacer un código simple pero totalmente
funcional, que cumpla con los objetivos concretos del requerimiento.
La simplicidad significa menos código, por lo tanto menos probabilidad de errores, mayor
facilidad para revisar, depurar o agregar funcionalidades, una lógica más limpia y
estructurada. Así que si usted desea poner su “Sello Personal”, que sea en la simplicidad,
no se deje dar el “beso de la muerte”.
¡No más!
32
Retomando el primer ejemplo del cambio de llanta, quedaría de la siguiente manera al
hacer evidente la regla básica:
Observe: Una sola entrada al programa (INICIO PROGRAMA) y una sola salida (FIN
PROGRAMA).
Analicemos:
33
Si el vehículo está en marcha, entonces hay que detenerlo y parquearlo, luego, proceder
con lo ya definido.
Entonces tendremos:
Ahora nuestro algoritmo tiene cuatro bloques SECUENCIALES. El primer bloque es, a
su vez, una estructura DE DECISION.
FIN SI
Detener el vehículo
Parquear el vehículo
Observe que el programa como un todo también continua cumpliendo la regla básica.
34
Compongamos una pregunta que cubra los nuevos requisitos, por ejemplo ¿tenemos las
herramientas necesarias para el cambio de llanta y está la llanta de repuesto en buenas
condiciones? O ¿están todos los elementos? Para mantener nuestro código sencillo y
legible, vamos a usar la última pregunta.
Si tenemos o no las herramientas, de todas formas hay que parquear, por lo tanto esta
nueva pregunta debe estar después de la primera y cubrirá todos los pasos definidos
inicialmente en el caso que si tengamos todo dispuesto. Pero si no se tiene todo para
cambiar la llanta, entonces debemos hacer otra cosa, por ejemplo llamar a un servicio
de mantenimiento.
A partir del análisis anterior tenemos una nueva versión del programa:
Revise la nueva versión: Ahora tenemos solo dos bloques de primer nivel, ambos son de
decisión. Dentro del primero, si la afirmación (Vehículo está en marcha) es
VERDAD se pueden realizar dos pasos o, en caso que sea FALSO , no se hace nada.
35
En la imagen se observa que hay una sola
entrada al bloque, dependiendo del resultado de
la prueba lógica se pueden tomar dos caminos,
pero SIEMPRE el flujo de control se une de
nuevo y se tiene solo una salida.
36
Continuemos. Recuerde este segmento de la segunda iteración:
¿Cómo realizar una expansión de esta instrucción, para darle más detalle? Por ejemplo:
Tomar la cruceta
Aflojar el primer perno
Aflojar el segundo perno
37
Aflojar el tercer perno
Aflojar el cuarto perno
… (etc.)
En esta solución se presentan dos problemas: No todos los vehículos tienen el mismo
número de pernos; además, si fuese un gran camión, probablemente tendríamos que
repetir la misma instrucción muchas más veces, lo que hace innecesariamente extenso
el código.
A) Tomar la cruceta
MIENTRAS (existan pernos apretados) HAGA
Aflojar el perno
FIN MIENTRAS
B) Tomar la cruceta
HAGA
Aflojar el perno
MIENTRAS (existan pernos apretados)
C) Tomar la cruceta
PARA (i desde 1 hasta n) HAGA
Aflojar el perno i
FIN PARA
• Advertir que todos tienen una sola entrada y una sola salida.
38
• ¿Qué diferencia existe entre A y B? En el primer caso se pregunta primero y
dependiendo de la evaluación lógica (Verdad/Falso) se ingresa al proceso interno
o se salta a la siguiente instrucción. En el segundo caso primero se ejecuta el
proceso y después se evalúa la condición. La diferencia practica entre las dos
consiste en que en MIENTRAS – HAGA puede que no se ejecute el proceso
nunca y en HAGA – MIENTRAS se ejecuta al menos una vez.
¿Qué diferencia hay entre MIENTRAS y PARA? En PARA se conoce con anterioridad
el número de ciclos.
39
El siguiente cuadro resume las estructuras de control:
ESTRUCTURAS DE CONTROL
SECUENCIAL Una instrucción después de otra…
DECISION SIMPLE SI (condición) ENTONCES
Proceso
FIN SI
COMPUESTA SI (condición) ENTONCES
Proceso 1
SINO
Proceso 2
FIN SI
MULTIPLE CASO (variable)
Valor 1:
Proceso 1
Valor 2:
Proceso 2
…
Valor n:
Proceso n
FIN CASO
ITERACION HAGA- HAGA
MIENTRAS Proceso
MIENTRAS (condición)
Nota: En la estructura PARA existen tres parámetros: El valor inicial de la variable de control, la prueba lógica contra
la que se evalúa la variable y el último indica el valor que se debe agregar a la variable en cada iteración, si no se
muestra explícitamente, se asume que se suma uno.
40
El seudocódigo
Otro punto que no se ha tocado aun pero que también es importantísimo es la muy
recomendable costumbre de insertar COMENTARIOS. Son frases explicativas que no
se consideran como parte de la lógica del código, pero necesarias para que las personas
entiendan la lógica seguida por el programador.
En general los comentarios se insertan dentro del seudocódigo o del CÓDIGO FUENTE ,
que no es más que el código escrito por el programador en cualquier lenguaje de
programación (no en seudocódigo). El comentario se inicia con “//” cuando son de una
línea, e iniciando con “/*” y terminando con “*/” para comentarios de varias líneas; en
otros lenguajes de programación se utiliza un “;”, etc.
41
FIN SI
SI (están todos los elementos) ENTONCES
Asegurar el vehículo
Poner el freno de mano
Sacar los tacos del baúl
Instalarlos en llantas opuestas
Cambiar la llanta
Colocar gato bajo la llanta a cambiar
Tomar la cruceta
HAGA
Aflojar el perno
MIENTRAS (existan pernos apretados)
Subir el gato
Retirar los pernos
Quitar la llanta pinchada
Colocar la nueva llanta
Ajustar los pernos
Bajar y retirar el gato
Apretar los pernos
Quitar los tacos y guardar todo
Retirar los tacos
Guardar la rueda pinchada y demás equipo utilizado
SINO
Llamar servicio de mantenimiento
FIN SI
FIN PROGRAMA
42
3. LAS VARIABLES
Análisis:
QUE se tiene Se debe ingresar el número (lo usual es que se ingrese por
teclado)
43
o La parte de la derecha se denomina función , que realiza algo (leer) y
devuelve un valor.
o La “flecha” indica que el valor devuelto por la función de la derecha pasa a
la izquierda.
o La palabra dentro del paréntesis se denomina parámetro . En este caso
indica en donde se debe leer (también podría ser en un archivo, etc.)
• En la instrucción: cuadrado <- numero * numero
o El valor contenido en numero se multiplica por sí mismo (elevar al
cuadrado).
o El resultado de esta operación se almacena en la variable cuadrado
o El contenido de la variable numero no sufre ningún cambio.
• En la instrucción Mostrar(cuadrado):
o Realiza algo (en este caso mostrar en pantalla) pero no devuelve ningún
valor y se denomina procedimiento .
o La palabra dentro del paréntesis también se denomina parámetro . En este
caso indica qué se va a mostrar.
Del ejemplo anterior podemos concluir que los distintos valores que se requieren en un
algoritmo se almacenan en variables. Las variables son espacios en la memoria de
trabajo donde se almacenan datos de un tipo específico.
44
• Booleano
• Carácter
• Etc. (depende del lenguaje de programación a utilizar),
Análisis:
QUE se tiene Se debe ingresar el número (lo usual es que se ingrese por
teclado)
Se divide por 2, después por 3, por 4, 5…Etc. ¿Cómo proceder con las divisiones?
Observe que es un solo proceso: Dividir; pero se repite numero -2 veces ¿Por qué?
45
INICIO PROGRAMA Primos
numero <- Leer(teclado)
PARA (i desde 2 hasta numero -1) HAGA
Verificar si es divisible
FIN PARA
Mostrar(resultado)
FIN PROGRAMA
46
• El operador de comparación es “=”, por eso el de asignación es “<-”, para
diferenciarlos.
Optimización
47
Podemos reducir casi a la mitad el número de iteraciones (ciclos) requeridos para
verificar si un número es primo; basta con realizar las divisiones hasta la mitad del valor
introducido (numero/2), esto se puede hacer porque al dividir por numero/2, se
obtendría 2 (caso que numero sea par) y si dividimos entre un número mayor a la mitad,
se obtendría menos que 2. Por ejemplo, si el número ingresado es 11, la mitad es 5 (se
toma el entero). Entonces bastara con dividir por 2, 3, 4 y 5. Los otros números entre 6 y
10, arrojaran un residuo entre 5 y 1, nunca cero. Y lo que se busca es que arroje cero
como residuo para confirmar que NO es primo.
48
EJERCICIOS BASICOS CON PYTHON
Phyton también es ideal para trabajar con grandes volúmenes de datos porque favorece
su extracción y procesamiento, a nivel científico, posee una amplia biblioteca de recursos
con especial énfasis en las matemáticas para programación en áreas especializadas. Lo
más importante para el estudiante es que cuenta con una comunidad de usuarios muy
activos que comparten constantemente sus conocimientos y recursos en línea.
#Muestra el resultado
print ("El resultado es: ", resultado)
49
Después de transcribir el código, seleccione el menú Tools > SublimeREPL > Python
> Python Run Current File. En el caso de existir un error se mostrara el mensaje
correspondiente. Si no hay errores, el programa se ejecutara, solicitando el numero,
después mostrara el resultado, como en la siguiente imagen:
#Solicitar numero
print ("NUMEROS PRIMOS")
numero = int(input("Ingrese un numero: "))
#Escribe el resultado
print ("El numero {} {}".format(numero, esPrimo))
Se han agregado algunas instrucciones ‘print’ para hacer mas amigable la interfaz del
usuario .
50
• La estructura ciclica ‘for’ usada para un numero conocido de bucles, termina
ANTES del numero limite, por lo que hay que sumar uno a ‘numero’ antes de
dividirlo en dos.
• El operador MODULO (resto de una división entera) se representa con ‘%’.
• Observe la diferencia del ultimo ‘print’, con el del programa anterior, Este usa
formateo, para reemplazar los símbolos ‘{}’ con los valores de las variables
listadas (numero, esPrimo).
51
Depurando el código
Vamos un paso mas adelante, para poder visualizar el contenido de las variables,
agregar puntos de parada al código para inspección, etc. Y ver en detalle el
comportamiento del algoritmo, debemos realizar un proceso que se denomina
Depuracion. En vez de Sublime Text, vamos a utilizar Visual Studio Code, que es un
editor de código fuente desarrollado por Microsoft para Windows, Linux y macOS. Incluye
soporte para la depuración, resaltado de sintaxis, y muchas otras ventajas. Para su
instalación y configuración para Python, se encuentra abundante información en la red.
La imagen siguiente muestra la pantalla principal:
52
Problema 3. Sumatoria. Desarrolle un algoritmo que calcule la sumatoria de los
números enteros pares comprendidos entre 1 y 100. El Programa deberá mostrar
los números.
Análisis:
Evidentemente, debemos realizar un ciclo desde 1 hasta 100 y para cada uno de ellos,
comprobar si es par.
Dentro del ciclo, debemos ir sumándolos para mostrar ese valor al final.
53
¿Cuándo mostrar cada número? También lo haremos dentro del ciclo; si no, tendríamos
que usar otro ciclo solo para mostrarlos, lo que no es eficiente.
Después de estas consideraciones podemos escribir el seudocódigo:
suma = 0
for i in range(1,101): #para que repita hasta 100
if i % 2 == 0:
suma += i
print(i)
#Mostrar sumatoria
print ("La sumatoria es {}".format(suma) )
54
Observaciones al código anterior:
Análisis:
QUE se quiere Mostrar los números entre 1 y 300, mostrar cuantos son pares
y la sumatoria de todos ellos.
Esta solución tiene prácticamente la misma estructura del algoritmo anterior. Pero se
deben SUMAR TODOS los números y CONTAR solo los Impares. Por lo tanto hay que
buscar el lugar adecuado para cada operación: dentro del ciclo iremos sumando todos y
dentro del condicional contando los impares.
55
suma = 0
impares = 0
print("NUMEROS DEL 1 AL 300")
for i in range(301):
print("{}, ".format(i) ,end = '')
suma = suma + i
if i % 2 == 1:
impares += 1
Y la salida es:
Análisis:
QUE se tiene Se ingresan por teclado las longitudes de los dos catetos
56
COMO hacerlo: Primero debemos tener claro cómo se calcula la hipotenusa
de un triángulo rectángulo, para ello aplicamos el teorema de
Pitágoras: sea h la hipotenusa, a y b sus dos catetos,
2
entonces 𝑎2 + 𝑏 2 = ℎ2 . O sea, ℎ = √𝑎2 + 𝑏 2 .
• Ingresar catetos
• Calcular Hipotenusa
• Mostrar resultado
Como cada uno de estos pasos no implica un desarrollo complejo, ni otras estructuras
internas, podemos pasarlo directamente al código PYTHON:
#**************************************
# algoritmo para determinar la hipotenusa
# de un triángulo rectángulo, dadas las
# longitudes de sus dos catetos.
# Programo: MLM
# Version: 1.0
# Fecha: 27/03/2019
#**************************************/
#Ingresar catetos
a = int(input("Ingrese el valor del primer cateto: "))
b = int(input("Ingrese el valor del segundo cateto: "))
#Calcular hipotenusa ()
h = (a*a + b*b)**(1/2)
#Mostrar el resultado
print("La hipotenusa del triangulo de lados {} y {} es igual a {}".format(a,
b, h))
Ejecutando:
57
Observaciones al código anterior:
• Tomar nota de los comentarios agregados, tanto al inicio del programa como en
cada uno de los bloques, donde se han transcrito los tres pasos definidos en el
análisis.
• Observe el uso de los paréntesis en la instrucción h = (a * a + b * b)**(1/2). Sirven
para agrupar expresiones u operaciones.
o El paréntesis general: … (…)**(1/2) obliga a realizar las operaciones
internas antes de calcular la raíz cuadrada.
o Elevar a la potencia (1/2) equivale a la raíz cuadrada. También debe estar
entre paréntesis, para que primero se realice la división de 1 entre 2.
o En los diferentes lenguajes de programación se establece la precedencia
de los operadores.
Análisis:
Primero debemos tener claro como calcular el volumen del cilindro y del cubo. Para
ambos cuerpos se aplica la formula 𝑽𝒐𝒍 = 𝑩𝒂𝒔𝒆 𝒙 𝒂𝒍𝒕𝒖𝒓𝒂, donde Vol es el volumen del
cuerpo, Base es el área de la base.
58
.
El cubo tiene todos sus lados iguales, por lo que solo requiere un parámetro.
El cilindro tiene por base una circunferencia cuya área se calcula como 𝑩 = 𝝅𝒓𝟐, en este
caso se debe solicitar el radio de la base (o el diámetro) y la altura.
59
escribir(“Ingrese el radio del Cilindro:”)
r <- leer(teclado)
escribir(“Ingrese la altura del Cilindro:”)
a <- leer(teclado)
FIN SI
Y, el tercer paso:
60
#******************************************
# Algoritmo para calcular y mostrar
# el área de un cubo o de un cilindro
# Programo: MLM
# Version: 1.0
# Fecha: 27/03/2019
#*****************************************/
import math
# Mostrar resultado
if volumen > 0:
print ("El volumen es {}".format(volumen))
61
Y para el caso del Cilindro:
Problema 7. Menú Modifique el algoritmo anterior para que además, exista una
tercera opción que permita terminar la ejecución, sino, repetir para otro cálculo.
Análisis:
COMO hacerlo
62
opción <- leer(teclado)
Como hay que repetir, todo el algoritmo se debe encerrar dentro de una iteración que
finalice cuando la opción sea 3. Para que ingrese la primera vez en el ciclo, se asigna un
valor cualquiera, diferente de 3, a la variable opción.
Pero, el segundo y tercer paso internos requieren un valor valido de la opción (1 o 2), por
lo que se deben incluir dentro de una estructura de decisión:
El codigo Python:
******************************************
# Algoritmo para calcular y mostrar
# el área de un cubo o de un cilindro
# Programo: MLM
63
# Version: 2.0
# Fecha: 27/03/2019
#*****************************************/
import math
opcion=0
while opcion != 3:
print ("--------------------------")
print (" CALCULO DEL VOLUMEN")
print ("")
print (" 1- Volumen del cubo")
print (" 2- Volumen del cilindro")
print (" 3- Salir")
volumen = 0
#Según opcion pedir parámetros y Calcular volumen
if opcion == 1:
l = int(input("Ingrese el lado del cubo: "))
volumen = l*l*l
elif opcion == 2:
r = int(input("Ingrese el radio del cilindro: "))
a = int(input("Ingrese la altura del cilindro: "))
volumen = (math.pi * r * r * a)
elif opcion != 3:
print ("La opción que ingresó no es válida")
# Mostrar resultado
if volumen > 0:
print ("El volumen es {}".format(volumen))
64
65
4. PROCEDIMIENTOS Y FUNCIONES
En programación hay un principio que dice: “si un segmento de código se repite dos
o más veces, entonces es candidato a separarlo del flujo principal” . Esto hace que
el código no quede repetido y cada funcionalidad sea independiente, con la ventaja de
que, al hacerlo modular, se facilita la comprensión de la lógica y cualquier mejora o
cambio se realizara en un solo sitio siendo más fácil el mantenimiento del código.
#******************************************
# Algoritmo para calcular y mostrar el área de
# un cubo o de un cilindro, usa funciones
# Programo: MLM
# Version: 3.0
# Fecha: 27/03/2019
#*****************************************/
import math
66
#Programa principal -------------------
opcion=0
while opcion != 3:
opcion = mostrarMenu()
volumen = 0
#Según opcion pedir parámetros y Calcular volumen
if opcion == 1:
l = int(input("Ingrese el lado del cubo: "))
volumen = l*l*l
elif opcion == 2:
r = int(input("Ingrese el radio del cilindro: "))
a = int(input("Ingrese la altura del cilindro: "))
volumen = (math.pi * r * r * a)
elif opcion != 3:
print ("La opcionion que ingresó no es válida")
# Mostrar resultado
if volumen > 0:
print ("El volumen es {}".format(volumen))
• En PYTHON inicia con la palabra reservada def (en otros lenguajes es diferente).
• Entre los paréntesis se declaran los parámetros (en este caso no es necesario)
• A continuación, todo el código del procedimiento se encuentra indentado a la
derecha de la palabra reservada def (Recuerde que Python usa la indentación
para definir el alcance de las estructuras, la mayoría de los lenguajes utiliza los
símbolos ‘{‘ y ‘}’).
• Si se usaran variables internas, estas solo son reconocidas dentro de la función,
esta característica se denomina “ámbito de la variable” .
• La función termina con la instrucción return opc, para que devuelva un valor.
• Para invocar la funcion se utiliza opcion = mostrarMenu(). El valor devuelto por
la función se almacenara en la variable ‘opcion’.
• El nombre de la variable no necesariamente debe ser el mismo al usado dentro
de la funcion (opción y opc).
67
Paso de variables por valor o por referencia
68
1. Inicialmente, el modulo principal ha creado una variable denominada opción, el
Sistema Operativo le ha asignado la dirección de memoria 25000. En ella se
encuentra el valor 20.
2. Al invocar el procedimiento, el modulo principal pasa el valor de la variable (20) al
procedimiento.
3. Dentro del procedimiento y al momento de invocarlo (llamarlo), se crea una variable
denominada op, el Sistema Operativo le ha asignado, por ejemplo, la dirección de
memoria 25500.
4. Más tarde, la instrucción de asignación carga el valor 35 en la variable op. Observe
que el valor de opción NO CAMBIA.
5. Al terminar la ejecución del procedimiento, op es destruida.
En Python Los tipos simples se pasan por valor (enteros, flotantes, cadenas, lógicos) y
los tipos compuestos se pasan por referencia (listas, diccionarios, conjuntos…. )
Veamos un ejemplo simple que demustra como al llamar una función, el paso por valor
de una variable no la modifica:
# funcion -------------------
def doblar_valor(numero):
numero *= 2
print(numero)
69
La variable ‘numero’ del programa principal es diferente a la utilizada en el
procedimiento. Observe que, primero imprime la variable dentro de la función (40), y el
valor en el programa principal sigue siendo 20:
Sin embargo, en Python, las listas u otras colecciones, al ser tipos compuestos se pasan
por referencia, y si se modifican dentro de la función se estaran modificándo también
afuera:
# funcion -------------------
def doblar_valores(numeros):
for i in range(0, len(numeros)):
numeros[i] *= 2
doblar_valores(ns)
print(ns)
70
5. DOCUMENTANDO EL CÓDIGO
Hasta ahora hemos ido incluyendo algunos comentarios en el código y algo se ha dicho
al respecto. En este capítulo explicaremos con mayor detalle la documentación del
código y su importancia.
Considere la documentación como una necesidad para cuando haya errores por reparar,
cuando se desee adaptar el código o realizar una refactorización del mismo.
¿Qué documentar?
Se debe documentar todo lo que no sea obvio, al escribir el comentario tener en mente
no repetir la instrucción sino explicar por qué se hace.
Hay que documentar cual es la función del programa o modulo, cómo usar el modulo,
para qué se utiliza la variable, qué parámetros son necesarios, la explicación de cada
parámetro, dejar nota sobre cuestiones pendientes por agregar o mejorar, citar la fuente
cuando se utiliza código de otro autor, etc.
¿Dónde documentar?
71
Los comentarios de ubican al inicio de todo programa o modulo (función, procedimiento,
método, clase, o como se denomine según el paradigma de programación utilizado).
Antes o al frente de una instrucción; cuando se requiera claridad sobre lo que hace o por
qué; antes de cada variable, sea global o local , al inicio de un bucle. En general,
siempre que el código no sea evidente o claro.
Los Docblocks
Ejemplo:
/*********************************************************
* Lee un archivo tipo y devuelve la cantidad de líneas en él.
*
* Abre el archivo y en un bucle cuenta las líneas encontradas
* PARAMETRO: string nombreArchivo
* RETORNA: int
* EXCEPCION: si nombreArchivo es NULL o vacío.
*
*********************************************************/
Por último, decir que, aunque en el código de producción (el desarrollado en la empresa)
se debe seguir una norma de programación unificada, en los ejercicios de práctica no es
tan importante. Se seguirá la práctica de documentar a manera de ejemplo y en algunos
ejercicios, por lo elementales, se reducirá la documentación a un mínimo aceptable (esto
es subjetivo y depende de la experiencia del programador).
72
6. ESTUDIO DE ALGORITMOS GENERICOS
Análisis:
𝑠𝑢𝑚𝑎𝑡𝑜𝑟𝑖𝑎
𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜 =
𝑐𝑎𝑛𝑡𝑖𝑑𝑎𝑑
Por lo que el ciclo debe leer, ir acumulando y contando. Usaremos el ciclo MIENTRAS-
QUE, que se repetirá hasta que se ingrese cero, por lo que se debe inicializar la
variable que recibirá los números en un valor diferente a cero.
73
FIN-MIENTRAS
Calcular promedio
Mostrar(promedio)
FIN PROGRAMA
n <- Leer(teclado)
SI (n entre 1 y 20)HAGA
suma <- suma + n
cantidad <- cantidad + 1
SINO SI (n <> 0) // n fuera del rango
Mostrar(aviso)
FIN SI
74
El código en PYTHON:
# Algoritmo para calcular el promedio de los
# numeros ingresados por teclado, entre 1 y 20
# termina al ingresar cero
# Programo:
# Version: 1.0
# Fecha:
# Cambios:
#------------------------------------------------
suma=0
cantidad=0
print("CALCULO DEL PROMEDIO")
n = 1 #Algun valor diferente de cero, para ingresar en el ciclo
while n != 0:
#Ingresar numeros
n = int(input("Ingrese un numero entre 1 y 20 (0 para salir): "))
#Calcular promedio
prom = suma/cantidad
print("El promedio es {} ".format(prom))
Captura de pantalla:
75
Problema 10. Calificaciones Desarrolle un algoritmo que convierta las
calificaciones numéricas (en el rango 1 a 20) en letras. Si el numero esta entre 19
y20, cambiar por A; Si el numero esta entre 16 y 18, por B; Si el numero esta entre
13 y 15, por C; Si el numero esta entre 10 y 12, por D; si es menor a 10, por E.
Análisis:
QUE se quiere Mostrar la letra equivalente a una nota numérica entre 1 y 20.
print("NOTA EQUIVALENTE")
print("")
76
while nota < 0 or nota >20:
nota=int(input("Escriba la nota entre 1 y 20: "))
77
Problema 11. Calificaciones con repetición Desarrolle un algoritmo que convierta
las calificaciones como en el problema anterior. El programa pedirá notas hasta
cuando se ingrese cero, entonces se debe terminar la ejecución.
Análisis:
Dentro del ciclo principal, se debe ejecutar un ciclo para pedir la nota, después se
entrega el resultado y se repite todo el proceso:
#***************************************************
# Algoritmo para convertir una nota numerica (1 a 20)
# en la letra equivalente asi: A (19-20), B (16-18)
# C (13-15), D (10-12), E (1-9)
#
# Programo: MLM
# Version: 2.0, con repeticion
# Fecha: 29/03/2019
# Cambios:
#***************************************************/
78
letra="D"
elif nota >= 1:
letra="E"
else:
letra = "O"
return letra
while True:
nota = -1 # un valor inicial, para entrar en el ciclo
if nota > 0:
letra = convertir(nota)
print("La nota equivalente a {} es {}".format(nota, letra))
print()
else:
break #Si es cero, sale del ciclo while
79
• La variable letra, dentro de la función convertir(n) es diferente a la declarada
en el programa principal y su ámbito es local (solo mientras se ejecuta la función).
• El parámetro nota se pasa por valor, dentro de la función se denomina n.
• El bloque:
if nota > 0:
...
else:
break #Si es cero, sale del ciclo while
es el que permite salir del bloque while y terminar el programa
Reto: Realice una nueva versión del programa anterior, que además, calcule el promedio
de las notas ingresadas y muestre la letra equivalente de ese promedio.
Análisis:
QUE se quiere Mostrar diez tablas, cada una multiplica hasta 10.
COMO hacerlo Como hay que mostrar diez tablas, esto se reduce a mostrar una
tabla diez veces, cambiando un parámetro (el multiplicador). Por lo
tanto, la estructura global es una iteración.
Para mostrar una tabla debemos mostrar diez veces la operación multiplicador POR
multiplicando. Los multiplicandos también van desde uno hasta diez. Por lo tanto se
requiere otra estructura PARA.
80
PARA (multiplicador <- 1 HASTA 10) HAGA
PARA (multiplicando <- 1 HASTA 10) HAGA
Mostrar (multiplicador X multiplicando = resultado)
FIN PARA
FIN PARA
Codificar en PYTHON:
#/*********************************************************
# Algoritmo que muestra las tablas de multiplicar, de una
# al diez.
# Programo: MLM
# Version: 1.0
# Fecha: 29/03/2019
# Cambios:
#*********************************************************/
print(" ")
81
Bueno, hay que mejorar la salida, se pierden las primeras tablas. Una opción sería
agregar una pulsación de tecla para mostrar cada tabla, hasta terminar:
#/*********************************************************
# Algoritmo que muestra las tablas de multiplicar, de una
# al diez.
# Programo: MLM
# Version: 1.0
# Fecha: 29/03/2019
# Cambios:
#*********************************************************/
82
Análisis:
COMO hacerlo Primero hay que mostrar el menú y leer la opción seleccionada
(implementar con una función). Después, leer el número. Mostrar la
tabla correspondiente según la operación seleccionada, (un
procedimiento con un ciclo PARA). Controlar la salida si se pulsa la
letra “S”.
FUNCION leerMenu()
HAGA
Presentar opciones del menú
menu <- leerNumero()
83
HASTA (menu > 0 Y menu < 5)
Retorne menu
FIN FUNCION
FUNCION leerNumero()
Pedir ingreso del op1
Op1 <- leerNumero()
Retorne op1
FIN FUNCION
3. Para mostrar la tabla hay que seleccionar el símbolo a mostrar, después repetir
diez veces la operación. Dentro del ciclo se llamara otra función que haga la
operación según el caso. Parámetros: op (número entre 1 y 4) indica el tipo de
operación a realizar, val es el primer operando que es igual para toda la tabla.
4. Esta función se limita a realizar la operación seleccionada entre los dos operandos
y retornar el resultado. Parámetros: operación es un numero de 1 a 4; op1, op2
son los operandos.
84
4: res <- op1 / op2
FIN CASO
Retorne res
FIN FUNCION
#/**********************************************************
#**********************************************************
# PROGRAMA PRINCIPAL
#*********************************************************/
op = " "
while op != "s" and op != "S":
operacion = leerMenu()
numero = leerNumero()
mostrarTabla(operacion, numero)
op = continuarSalir()
Después, codifique cada uno de los módulos (funciones), los cuales se escribirán ANTES
del sitio en que sean llamados o invocados, es decir, las funciones van antes del
programa principal:
#/**********************************************************
# Presenta el menu y devuelve el numero de opcion seleccionada
# 1: suma, 2: resta, 3: multiplicacion,4: division
# PARAMETRO:
# RETORNA: menu
# EXCEPCION:
#**********************************************************/
85
def leerMenu():
menu = -1
while menu < 0 or menu > 5:
print("***** TABLAS DE OPERACIONES BASICAS ******")
print(" ")
print("SUMA 1")
print("RESTA 2")
print("MULTIPLICACION 3")
print("DIVISION 4")
menu = int(input(".....? "))
return menu
#/**********************************************************
# lee del teclado un numero (operando)
# PARAMETRO:
# RETORNA: entero op1
# EXCEPCION: si se escribe algo diferente a un entero,
# no controlado
# EXCEPCION:
#***********************************************************/
def leerNumero():
op1 = int(input("Ingrese el numero al que se le generara la tabla: "))
return (op1)
#/**********************************************************
# calcula la operacion entre dos numeros
# Segun el tipo de operacion (1: suma, 2: resta,
# 3: multiplicacion,4: division), devuelve
# el resultado de aplicar la operacion a op1 y op2
# PARAMETROS: entero operacion, op1, op2
# RETORNA: real res
# EXCEPCION:
#**********************************************************/
def calcularOp(operacion, op1, op2):
res = 0
if operacion == 1:
res = (op1+op2)
elif operacion == 2:
res = (op1-op2)
elif operacion == 3:
res = (op1*op2)
elif operacion == 4:
res = (op1/op2)
return(res)
86
#/**********************************************************
# Muestra la tabla
# Muestra la tabla para el numero y la opcion seleccionada
# PARAMETROS: entero op (indica el tipo de operacion)
# entero val (numero al que se le genera la tabla)
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarTabla(op, val):
simbolo = " "
if op == 1:
simbolo = "+"
elif op == 2:
simbolo = "-"
elif op == 3:
simbolo = "x"
elif op == 4:
simbolo = "/"
for i in range(1,11):
res = calcularOp(op, val, i)
print("{} {} {} = {}".format(val, simbolo, i, res))
#/**********************************************************
# Lee letra para continuar o salir
# Presenta un aviso y lee del teclado un caracter
# PARAMETRO:
# RETORNA: caracter sigue
# EXCEPCION:
#**********************************************************/
def continuarSalir():
sigue = " "
print("")
sigue = input("S para salir, otra tecla para continuar...")
return(sigue)
Ahora que ya tiene todo el código listo en PYTHON, guárdelo (Ctrl S) y ejecútelo (F5).
Reto: Corrija los errores que van apareciendo. Utilice las opciones de depuracion.
87
Finalmente se tendrá algo como:
Reto: Modifique el código para que se pueda terminar el programa desde el menú
principal (agregue la opción de salir en ese modulo).
Análisis:
COMO hacerlo Primero hay que definir (o asumir) algunos conceptos que no
están claros en el enunciado:
88
• La hora militar se considera como un número de cuatro dígitos donde los dos
primeros representan la hora y los últimos los minutos. Por ejemplo. Las 0830
equivale a las 8 y 30 am. Y las 2030 equivale a las 8 y 30 pm.
• No existen horas tales como las 1780, dado que los minutos llegan máximo a 59.
• Como la entrada de datos no considera cantidad de días, vamos a asumir un rango
máximo de 24 horas; así, si la hora de salida es menor que la de entrada, entonces
ha pasado de un día a otro. Por ejemplo, entrada 2000 y salida 0600, significa que
ingreso a las 8 pm Y salió las 6 am del siguiente día. Si son iguales significara
que estuvo 24 horas (la otra posibilidad sería cero horas, que no tiene sentido).
• Confirmar que aunque sea un minuto más de la hora, se cobrara como otra hora
entera.
• Finalmente, la primera hora vale más que las otras.
• Ingresar Horas
• Calcular monto
• Mostrar monto
89
o Si hora < 0 entonces valido = falso (caso de horas negativas)
o Si (parte entera de hora/100) > 23 entonces valido = falso (caso de 24 horas
o más)
o Si (resto de hora/100) > 59 entonces valido = falso (caso de 60 minutos o
más)
Repasemos que instrucciones del algoritmo aun no son evidentes. Tenemos, del
análisis de tercer nivel, las siguientes:
90
A este nivel, ya el análisis empieza a depender de los recursos disponibles según el
lenguaje de programación, para encontrar una solución válida.
Para el caso de PYTHON, todos los puntos anteriores se resuelven usando los
operadores ‘//’ para divisiones enteras y ‘%’ para encontrar e modulo o resto de una
división entera, veamos:
#**********************************************************
#*Algoritmo que calcula el monto a pagar por servicio de
# parqueadero. Las horas de entrada y salida se dan en
# formato militar
#
# Programo: MLM
# Version: 1.0
# Fecha: 31/03/2015
91
# Cambios:
#*********************************************************/
#/**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarInicio():
print("------------ SERVICIO DE PARQUEADERO -------------")
print(" ")
print("Ingrese las horas utilizando formato Militar(HHMM)")
print(" ")
#/**********************************************************
# Ingresa la hora en formato militar
# Ingresa un entero validando el formato (HHMM)
# HH entre 0 y 23, MM entre 00 y 59
# PARAMETRO:
# RETORNA: numero hora
# EXCEPCION:
#**********************************************************/
def ingresarHora(h):
hora = 0
valido = False
while valido != True:
valido = True
hora = int(input("Ingrese la hora de {}: ".format(h)))
if valido == False:
print("numero no valido...")
return(hora)
#/**********************************************************
92
# pasar horas a minutos
# Calcula los minutos contenidos en un valor en formato militar
# PARAMETROS: entero hora
# RETORNA: entero minutos
# EXCEPCION:
#**********************************************************/
def pasarMinutos(hora):
minutos = (hora // 100) * 60 #//parte de la hora
minutos += (hora % 100) #//parte de minutos
return (minutos)
#/**********************************************************
# Calcula el tiempo transcurrido
# Calcula el tiempo transcurrido, en horas, la fraccion se aproxima
# a la siguiente hora
# PARAMETROS: entero hEntra, hSale: Horas de entrada y salida
# RETORNA: entero totTiempo en minutos
# EXCEPCION:
#**********************************************************/
def calcularTiempo(hEntra, hSale):
horas = 0
hEntra = pasarMinutos(hEntra)
hSale = pasarMinutos(hSale)
if dif % 60 > 0:
horas += 1 #Ajusta minutos restantes a otra hora
return (horas)
#/**********************************************************
# Calcula el valor del servicio de parquedero
93
#**********************************************************/
def calcularMonto(hEntra, hSale):
monto = 1000 #//valor primera hora
tiempo = calcularTiempo(hEntra, hSale)-1 #//menos hora inicial ya
cobrada
if tiempo > 0:
monto += (tiempo * 600) #//valor Horas adicionales
return monto
#/**********************************************************
#* PROGRAMA PRINCIPAL
#**********************************************************/
mostrarInicio()
horaEntrada = ingresarHora("entrada")
horaSalida = ingresarHora("salida")
monto = calcularMonto(horaEntrada, horaSalida)
print(" ")
print("El valor total del servicio es ${}".format(monto))
Observe como la estructura del programa principal mantiene el esquema del primer nivel
de análisis, agregando un método para mostrar la pantalla inicial y definiendo las
variables globales mínimas necesarias para el funcionamiento. La idea es mantener el
código compacto para que sea más fácil de entender.
94
Pantalla para el caso en que la hora de entrada sea menor que la de salida:
Pantalla para el caso en que la hora de entrada sea mayor que la de salida:
Reto: Eliminar los "números mágicos". Aquellos números que no sean obvios o que
estén repetidos. Se deben usar constantes, pero en PYTHON no están definidas por lo
que usara variables asignando una sola vez el valor. Por ejemplo el 60 y el 100.
95
Problema 16. Día de una fecha Realice un programa que, dada una fecha en
formato “dd/mm/aaaa”, muestre el nombre del día
Análisis:
QUE se quiere Mostrar el nombre del día correspondiente a una fecha dada.
a = (14 - Mes) / 12
y = Año - a
m = Mes + (12 * a) - 2
d = (día + y + y/4 - y/100 + y/400 + (31*m)/12)) mod 7
//El resultado es un cero (0) para el domingo, 1 para el lunes… 6 para el
sábado
//Todos los números son enteros
#**********************************************************
# Valida una fecha en formato DD/MM/AAAA
96
# convierte a enteros los componentes de la fecha
# y comprueba que estén en los rangos correctos.
# PARAMETROS: cadena[10] fecha
# RETORNA: Diccionario datosFecha{}
# EXCEPCION:
#*********************************************************/
def validarFecha(fecha):
# Se define un 'diccionario' para devolver varios datos en él:
datosFecha = {"nDia": 0, "nMes": 0, "nAnio": 0, "ok": True }
return (datosFecha)
#**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# Ingresa la fecha en formato DD/MM/AAAA y la valida
# si la fecha es correcta, la separa y carga en variables globales
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#*********************************************************/
def pedirDatos():
print("")
print("---------------- DIA DE UNA FECHA ----------------")
print("")
while True:
fecha = input("Ingrese la fecha en el formato DD/MM/AAAA: ")
fechaNumeros = validarFecha(fecha)
if fechaNumeros["ok"]==True:
return fechaNumeros
#**********************************************************
# Calcula el nombre del día de la semana de una fecha
97
# utiliza el algoritmo de la Congruencia de Zeller
# http://es.wikipedia.org/wiki/Congruencia_de_Zeller
# PARAMETROS: entero dia, mes, anio
# RETORNA: cadena nombreDia
# EXCEPCION:
#*********************************************************/
def calcularDia(dia, mes, anio):
nombreDia = {0:"Domingo", 1:"Lunes", 2:"Martes", 3:"Miercoles",
4:"Jueves", 5:"Viernes", 6:"Sabado"}
a = int((14 - mes) / 12)
ya = anio - a
m = int(mes + (12 * a) - 2)
d = int((dia + ya + (ya / 4) - (ya / 100) + (ya / 400) + (31 * m ) /
12) % 7)
diaSem = nombreDia[d]
return(diaSem)
#/**********************************************************
# PROGRAMA PRINCIPAL
#***********************************************************/
fechaNumeros = pedirDatos()
nDia = fechaNumeros["nDia"]
nMes = fechaNumeros["nMes"]
nAnio = fechaNumeros["nAnio"]
dia = calcularDia(nDia,nMes,nAnio)
print("El día corresponde a un {}".format(dia))
Para almacenar los nombres de los días se hace uso de una serie de variables dentro
de otra variabla (nombreDia), En Pytho estas variables se denomonan “diccionarios” y
es uno de los tipos de “Arreglos” , que son muy importantes y se verán en profundidad
más adelante.
La pantalla de salida, con las primeras fechas erradas para verificar la validacion:
98
Reto: Validar el día, según el mes y el año. Por ejemplo el 31 de junio no es válido, junio
tiene 30 días.
Análisis:
Para el segundo punto, simplificar la función calcularDia(), para que solo devuelva el
número del día (no el nombre) correspondiente al primero del mes.
Hay que saber cual es el ultimo dia del mes, para mostrar todos los números, se puede
lograr teniendo una lista ordenada con estos numeros.
• Mostrar un encabezado con los títulos y los cuadros con los nombres de los dias.
• Desde el día “Domingo”, generar cuadros vacíos hasta el correspondiente al día
primero (primera zona gris en la imagen siguiente).
• Desde ahí, ir generando cuadros con los números hasta el último día del mes.
99
• Se debe saber cuándo se han generado siete cuadros (una semana completa),
para pasar a la siguiente fila (semana). Hay que usar un contador que mantenga
el número de cuadros generados hasta el ultimo dia del mes.
• Generar cuadros vacíos al final, hasta la última columna (segunda zona gris en la
imagen).
#**********************************************************
# Valida una fecha en formato DD/MM/AAAA
# convierte a enteros los componentes de la fecha
# y comprueba que estén en los rangos correctos.
# PARAMETROS: cadena[10] fecha
# RETORNA: Diccionario datosFecha{}
# EXCEPCION:
#*********************************************************/
def validarFecha(fecha):
# Se define un 'diccionario' para devolver varios datos en él:
100
datosFecha = {"nMes": 0, "nAnio": 0, "ok": True }
return (datosFecha)
#**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# Ingresa la fecha en formato DD/MM/AAAA y la valida
# si la fecha es correcta, la separa y carga en variables globales
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#*********************************************************/
def pedirDatos():
print("")
print("---------------- CALENDARIO DEL MES ----------------")
print("")
while True:
fecha = input("Ingrese EL Mes y el Año en formato MM/AAAA: ")
fechaNumeros = validarFecha(fecha)
if fechaNumeros["ok"]==True:
return fechaNumeros
#**********************************************************
# Calcula el numero del dia en la semana, del primer dia de un mes
# utiliza el algoritmo de la Congruencia de Zeller
# http://es.wikipedia.org/wiki/Congruencia_de_Zeller
# PARAMETROS: entero mes, anio
# RETORNA: entero d
# EXCEPCION:
#*********************************************************/
def calcularDia(mes, anio):
a = int((14 - mes) // 12)
ya = anio - a
m = int(mes + (12 * a) - 2)
d = int((1 + ya + (ya // 4) - (ya // 100) + (ya // 400) + (31 * m )
// 12) % 7)
101
return(d)
#**********************************************************
# Despliega los titulos iniciales del calendario
# PARAMETROS:
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarTitulos(mm, aa):
print("")
print(" {} de {}".format(mm, aa))
print("|----|----|----|----|----|----|----|")
print("| Do | Lu | Ma | Mi | Ju | Vi | Sa |")
print("|----|----|----|----|----|----|----|")
#**********************************************************
# Despliega cuadros vacios antes del primer dia del calendario
# PARAMETROS: entero dia: primer dia
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarCuadros(dia):
for i in range(dia):
print("| ", end="")
#**********************************************************
# Despliega el calendario del mes
# PARAMETROS: diccionario fechaNumero {nMes", "nAnio", "ok"}}
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarCalendario(fechaNumero):
meses = ["", "Enero",
"Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octub
re","Noviembre", "Diciembre"]
diasMes = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
nMes = fechaNumero["nMes"]
nAnio = fechaNumero["nAnio"]
numDias = diasMes[nMes]
diaIni = calcularDia(nMes,nAnio) # dia semanal para el primero del mes
mostrarTitulos(meses[nMes], nAnio)
mostrarCuadros(diaIni)
102
#ciclo para mostrar los numeros
j = diaIni # para controlar los cuadros por semana
i = 1 #para controlar el dia del mes
while i <= numDias:
while j < 7 and i <= numDias: #para controlar los cuadros por semana
if i < 10:
print("| {} ".format(i), end="") #caso numeros de un digito
else:
print("| {} ".format(i), end="")
j += 1
i += 1
if j == 7:
print("|") #Barra final solo si termina fila
print("|----|----|----|----|----|----|----|")
j = 0 #otra semana (nueva fila)
#/**********************************************************
# PROGRAMA PRINCIPAL
#***********************************************************/
fechaNumero = pedirDatos()
mostrarCalendario(fechaNumero)
103
Reto: Por qué,en la función ‘mostrarCalendario’, los arreglos inician con un elemento
vacio?.
Reto: modifique el código para que devuelva 29 días en el caso de mes febrero y años
bisiestos.
Reto: Corra el código para enero de 2015. Corrija la presentación del calendario para
casos en que termine exactamente en la última columna.
Reto: Modifique el programa para que, con teclas, pueda ver el mes anterior o el
siguiente. Tenga en cuenta el cambio de año.
Problema 18. Nomina Desarrolle un algoritmo que muestre el valor a pagar por
cada empleado y el valor total de la nómina. Debe solicitar el nombre del empleado
(la empresa cuenta con cinco empleados) y la cantidad de horas extras trabajadas;
El salario básico es $800.000 y el valor de la hora extra es $20.000.
Análisis:
COMO hacerlo
• Solicitar, cinco veces el nombre y las HH de un empleado (ciclo PARA).
• Dentro del ciclo, calcular el sueldo total sumando el sueldo básico y el valor
de las HH y mostrarlo.
• Se debe ir acumulando para mostrar al final el monto de la nómina.
Como es un algoritmo sencillo y los diferentes bloques ya han sido trabajados, vamos a
escribir directamente en PYTHON (lo importante es tener totalmente claro lo que se
quiere antes de escribir el código).
#/**********************************************************
# Muestra el valor a pagar por cada empleado y el valor total de la nómina
# solicitar el nombre del empleado (son cinco empleados)
# y la cantidad de horas extras trabajadas.
# Programo: MLM
104
# Version: 1.0
# Fecha: 31/03/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarInicio():
print(" ****** PAGO DE NOMINA ******")
print(" ")
print("Ingrese los datos de cada empleado.")
print(" ")
#/**********************************************************
# Pide el nombre del empleado y el número de horas extras
# muestra y devuelve el valor del salario a pagar
# PARAMETROS:
# RETORNA: real salario
# EXCEPCION:
#**********************************************************/
def pagarSalario():
nombre = input("Nombre del empleado: ")
hh = float(input("Número de horas extras: "))
salario = 800000 + hh *20000
print("El salario del empleado {} es ${:0,.2f}".format(nombre, salario))
print("")
return (salario)
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
nomina = 0
mostrarInicio()
for i in range(5): #i va de cero a cuatro
nomina += pagarSalario()
print("")
print("El valor total de la nómina es ${:0,.2f}".format(nomina))
105
El formato {:0,.2f} se usa para mostrar los números con separación de miles, la
coma y dos decimales.
106
7. OTROS TIPOS DE VARIABLES (Arreglos, vectores y matrices)
Problema 19. Nómina con nombres Modificar el problema de nómina para que
primero pida todos los datos y después despliegue la lista con los nombres y
salarios, mostrando al final el monto total de la nómina.
Análisis:
COMO hacerlo
• Evidentemente hay que almacenar los cinco nombres y los cinco valores
de horas extras, en un ciclo PARA inicial.
• Después, en otro ciclo PARA se calcula y se muestra el nombre y el salario
de cada uno.
• Por último, mostrar el total de la nómina.
107
Cada una de las cajas interiores contiene un dato y todos los cinco datos son del mismo
tipo (cadena de caracteres).
Los números en la parte inferior indican la posición de cada caja dentro del conjunto y se
denominan índices porque “indican” la posición. También se pueden denominar
Apuntadores o punteros porque “Apuntan” a una posición definida dentro del arreglo.
Para referenciar una caja particular, se utiliza el nombre del arreglo y su posición. Por
ejemplo, si se desea cambiar el nombre “Luis” por “Joaquín” se emplearía la instrucción:
Puesto que no es muy versátil utilizar constantes como subíndice, usualmente se hace
uso de una variable entera, por ejemplo, para borrar todos los nombres:
Con este nuevo concepto, vamos a modificar el algoritmo del problema anterior:
108
#/**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarInicio():
print(" ****** PAGO DE NOMINA ******")
print(" ")
print("Ingrese los datos de cada empleado.")
print(" ")
#/**********************************************************
# Pide el nombre y el número de horas extras al empleado i
# PARAMETROS: lista nombres, por referencia
# lista hh, por referencia
# RETORNA:
# EXCEPCION:
#*********************************************************/
def pedirDatos(nombres, hh):
nombres.append(input(" Nombre del empleado {}? ".format(i+1)))
hh.append(float(input(" Número de horas extras? ")))
print("")
#/**********************************************************
# muestra y devuelve el valor del salario a pagar por empleado
# PARAMETROS: entero i
# RETORNA: real salario
# EXCEPCION:
#**********************************************************/
def pagarSalario(empleado, hh):
salario = 800000 + hh *20000
print("El salario del empleado {} es ${:0,.2f}".format(empleado,
salario))
print("")
return (salario)
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
mostrarInicio()
nombres = [] #Contendra los nombres de los empleados
hh = [] #Contendra las horas extrar de los empleados
for i in range(5):
109
pedirDatos(nombres, hh) #Se piden los datos
nomina = 0
for i in range(5):
nomina += pagarSalario(nombres[i], hh[i]) #Se muestra el salario
print("") #Se muestra el total
print("El valor total de la nómina es ${:0,.2f}".format(nomina))
110
Problema 20. Nómina y billetes Modificar el problema anterior para que además
indique cuantos billetes de cada denominación se le entregaran y la cantidad de
cada denominación que se pedirá en el banco. Considere las siguientes
denominaciones: $50.000, $ 20.000, $10.000, $5.000, $2.000, $1.000.
Análisis:
QUE se quiere Que también indique, para cada empleado cuantos billetes de
cada denominación se le entregara, y el total por
denominación.
COMO hacerlo
Cuando el arreglo es de una dimensión, como los que hemos estudiado, también se
denominan vectores . Cuando son de dos o más dimensiones, se denominan
matrices .
Esta imagen representa el vector con las denominaciones de los billetes, es de tipo
cadena:
111
Como todos los valores son de tipo entero, se han reunido en un solo arreglo, en este
caso es una estructura de 3x6 (tres filas x seis columnas), es decir, es bidimensional,
entonces es una matriz.
En la primera fila se tendrá el valor del billete, por cada denominación, en la segunda fila
se almacenara el número de billetes de cada denominación para un empleado y en la
tercera fila la cantidad acumulada de cada denominación.
Una solución se podría entender mejor con la siguiente imagen. En ella se ha supuesto
un monto a pagar de $187.000:
Etc.
112
A partir del monto inicial se cuenta cuantas veces “cabe” el billete de mayor
denominación, lo que sobra es ahora el nuevo valor inicial y se repite para las
subsiguientes denominaciones. Veamos el algoritmo:
PROCEDIMIENTO calcularBilletes(monto)
PARA i<- 1 HASTA 6 HAGA
Billetes[2, i] <- monto DIV Billetes[1, i]
Monto <- monto MOD Billetes[2, i]fa
Billetes[3, i] <- Billetes[3, i]+ Billetes[2, i]
FIN PARA
FIN FUNCION
Por supuesto, al inicio del programa se deben almacenar los valores y las
denominaciones de los billetes y se inician en cero los acumuladores (hacer otro
procedimiento).
113
# solicitar el nombre del empleado (son cinco empleados)
# y la cantidad de horas extras trabajadas.
# Programo: MLM
# Version: 3.0
# Fecha: 31/03/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Presenta la pantalla principal con los mensajes correspondientes
# PARAMETRO:
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarInicio():
print(" ****** PAGO DE NOMINA ******")
print(" ")
print("Ingrese los datos de cada empleado.")
print(" ")
#/**********************************************************
# Pide el nombre y el número de horas extras al empleado i
# PARAMETROS: lista nombres, por referencia
# lista hh, por referencia
# RETORNA:
# EXCEPCION:
#*********************************************************/
def pedirDatos(nombres, hh):
nombres.append(input(" Nombre del empleado {}? ".format(i+1)))
hh.append(float(input(" Número de horas extras? ")))
print("")
#**********************************************************
# devuelve cantidad de billetes de cada denominacon a cancelar,
# y la cantidad de billetes acumulados por denominacion
# PARAMETROS: entero salario, array billetes
# RETORNA:
# EXCEPCION:
#**********************************************************/
def calcularBilletes(m, billetes):
monto = m
for i in range(6):
billetes[1][i] = monto // billetes[0][i] # numero de billetes a un
empleado
monto = (monto % billetes[0][i])
114
billetes[2][i] += billetes[1][i] # numero de billetes
acumulado
#**********************************************************
# muestra la cantidad de billetes de cada denominacon por empleado,
# PARAMETROS: array billetes
# RETORNA:
# EXCEPCION:
#**********************************************************/
def mostrarBilletes(billetes):
# almacena el texto de las denominaciones de los billetes
denomBilletes = ["cincuenta mil", "veinte mil", "diez mil", "cinco mil",
"dos mil", "mil"]
for i in range(6):
if billetes[1][i] > 0:
print("{} billetes de {}, ".format(int(billetes[1][i]),
denomBilletes[i]), end="")
# print("") #nueva linea
#/**********************************************************
# muestra y devuelve el valor del salario a pagar
# PARAMETROS: cadena empleado, entero hh
# RETORNA: entero salario
# EXCEPCION:
#**********************************************************/
def pagarSalario(empleado, hh, billetes):
salario = 800000 + hh *20000
print("El salario del empleado {} es ${:0,.2f}".format(empleado,
salario))
calcularBilletes(salario, billetes)
mostrarBilletes(billetes)
print("")
return (salario)
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
mostrarInicio()
115
billetes = [[50000, 20000, 10000, 5000, 2000, 1000], #valor de cada
denominacion
[0, 0, 0, 0, 0, 0], #numero de billetes
por empleado
[0, 0, 0, 0, 0, 0]] #numero de billetes
acumulados
nomina = 0
116
Problema 21. Promedio de vectores Diseñe un programa que lea dos vectores
de 10 números y obtenga el promedio de cada vector y la suma.
Análisis:
COMO hacerlo
117
• Primero ingresar los números en los vectores: Dos ciclos PARA.
• En otro ciclo o, si se quiere optimizar código, en el ciclo de ingreso, se va
realizando la suma. En este caso realizaremos dos ciclos separados.
• Por último, mostrar los datos calculados
• #/**********************************************************
• # Lee dos vectores de 10 enteros cada uno
• # Obtiene el promedio de cada vector y la suma.
• # Programo: MLM
• # Version: 1.0
• # Fecha: 01/04/2019
• # Cambios:
• #**********************************************************/
•
• #/**********************************************************
• # PROGRAMA PRINCIPAL
• #**********************************************************/
• vectorA = []
• vectorB = []
• promedio = 0.0
• tamVector = 10
• suma = 0
•
• print("")
• print("---------------- SUMA Y PROMEDIO DE VECTORES ----------------")
• print("ELEMENTOS DEL VECTOR A:")
• for i in range(tamVector):
• vectorA.append(int(input("Elemento [{}]:? ".format(i))))
• print("")
• print("ELEMENTOS DEL VECTOR B:")
• for i in range(tamVector):
• vectorB.append(int(input("Elemento [{}]:? ".format(i))))
•
• for i in range(tamVector):
• suma += (vectorA[i]+vectorB[i])
• promedio = suma / (tamVector * 2)
•
• print("")
118
• print("La suma de todos los elementos es {} y el promedio es
{}".format(suma, promedio))
119
Problema 22. Números no repetidos Ingresar en un vector de tamaño 8, los
números deben estar en el rango de 1 al 10 (enteros) y no de deben repetir.
Análisis:
COMO hacerlo
¿Qué estructuras emergen del análisis anterior? Una posible solución sera:
• En un PARA hasta 8.
o un ciclo MIENTRAS esté repetido.
▪ Dentro de este, otro ciclo MIENTRAS no esté en el rango.
• ingresar un número entero.
• #/**********************************************************
• # Ingresa en un vector de tamaño 8, números en el rango de1 al 10 sin
repetir.
• # Programo: MLM
• # Version: 1.0
• # Fecha: 01/04/2019
• # Cambios:
• #**********************************************************/
•
• #/**********************************************************
• # Revisa si un numero se encuentra en una lista o array
• # PARAMETROS: lista vector
• # entero n
• # RETORNA: bool rep
• # EXCEPCION:
• #*********************************************************/
•
• def verificarRepetido(vector, n):
• rep = False
• for i in range(len(vector)):
• if n == vector[i]:
• rep = True
120
• return rep
•
• #/**********************************************************
• # PROGRAMA PRINCIPAL
• #**********************************************************/
• vectorA = []
• tamVector = 8
•
• print("")
• print("---------------- NUMEROS NO REPETIDOS ----------------")
•
• for i in range(tamVector):
• repetido = True #Inicia con valor verdad para ingresar al
while
• while repetido:
• numero = 0 #Inicia con valor 0 para ingresar al while
• while numero < 1 or numero > 10:
• try: #Bloque para validar que el numero sea
entero
• numero = int(input("ingrese un entero: "))
• except ValueError:
• numero = 0 #Caso de error, intenta de nuevo
•
• #cuando el numero esta en el rango 1 a 10, verifica que no este
repetido
• repetido = verificarRepetido(vectorA, numero)
• if not repetido:
• vectorA.append(numero) #Si no esta repetido, lo agrega al
vector
•
•
• #Para terminar mustra todo el vector
• print("El vector es:")
• print(*vectorA, sep = ", ")
En la siguiente figura se tiene la salida del programa, observe que no acepta números
fuera del rango ni repetidos.
121
Reto: presente una advertencia cuando el número este fuera de rango.
Análisis:
QUE se quiere Con los números de un arreglo, mostrar los múltiplos de tres.
COMO hacerlo
122
• Después, se recorre el vector (ciclo PARA) comprobando si es múltiplo de
tres (usar el operador MODULO).
• Mostrarlo si es múltiplo.
• Se mostrara todo el vector para tener referencia visual de los múltiplos
#/**********************************************************
# Revisa si un numero se encuentra en una lista o array
# PARAMETROS: lista vector
# entero n
# RETORNA: bool rep
# EXCEPCION:
#*********************************************************/
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
vectorA = []
tamVector = 20
print("")
print("------------ VECTOR DE 20 NUMEROS NO REPETIDOS ------------")
for i in range(tamVector):
repetido = True #Inicia con valor verdad para ingresar al while
while repetido:
numero = 0 #Inicia con valor 0 para ingresar al while
123
while numero < 1 or numero > 100:
try: #Bloque para validar que el numero sea entero
numero = int(random.randrange(1, 100)) #Genera entero al
azar entre 1 y 100
except ValueError:
numero = 0 #Caso de error, intenta de nuevo
Salida obtenida:
Problema 24. Recorridos en matrices Dada la matriz 7x7. Poblarla con números
generados al azar, entre 10 y 99.
124
Diseñe algoritmos para:
Análisis:
COMO hacerlo Para generar los números, adaptar la función del problema
anterior. Se requieren dos ciclos PARA anidados, dada que
la matriz es bidimensional.
Para recorrer la matriz en las diferentes formas, se debe “descubrir” como cambian los
índices de fila y de columna. Podríamos cambiar las filas en el ciclo externo, desde 1
hasta 7 y para las columnas estudiar cada caso:
125
• Diagonal inversa: Para los valores de f (fila), c (columna) tendrá los siguientes
valores:
f c
1 7
2 6
3 5
4 4
5 3
6 2
7 1
f c
1 1…7
2 2…7
3 3…7
4 4…7
5 5…7
6 6…7
7 7
126
• matriz superior derecha (sin diagonal principal):
Valores:
f c
1 2…7
2 3…7
3 4…7
4 5…7
5 6…7
6 7
7 -
f c
1 1
2 1…2
3 1…3
4 1…4
5 1…5
6 1…6
7 1…7
127
FIN PARA
FIN PARA
f c
1 1…7
2 1…6
3 1…5
4 1…4
5 1…3
6 1…2
7 1
f c
1 7
2 6…7
3 5…7
4 4…7
5 3…7
6 2…7
7 1…7
128
Matriz[f, c]
FIN PARA
FIN PARA
Después del análisis y de tener claro lo que se quiere en cada caso, procedemos a
codificar en PYTHON, a partir del problema anterior.
Para que el ejercicio sea práctico, se debe visualizar la matriz en sus diferentes opciones,
por lo que hay que tener especial cuidado en la forma como se mostrará. Presentar las
columnas en orden, dejar espacios en donde se requiera y alinear los números. Esta
parte del código es más intuitiva y depende de la habilidad del programador, por lo que
los algoritmos de visualización se irán ajustando en un proceso de depuración repetitivo.
#/**********************************************************
# Genera una matriz de 7 x 7 , con números aleatorios no repetidos
# Ofrece los siguientes recorridos:
# a. Recorrer la diagonal principal
# b. Recorrer la diagonal inversa
# c. Recorrer la diagonal superior derecha (sin diagonal principal)
# d. Recorrer la diagonal superior derecha con diagonal principal
# e. Recorrer la diagonal inferior izquierda
# f. Recorrer la diagonal superior izquierda
# g. Recorrer la diagonal inferior derecha.
#
# Programo: MLM
# Version: 1.0
# Fecha: 01/04/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# En una matriz dimension: tam x tam,
# la carga con numeros al azar entre 10 y 99
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def cargarMatriz(vector, tam):
for i in range(tam):
for j in range(tam):
vector[i][j] = int(random.randrange(10, 100))
#/**********************************************************
# Muestra todos los elementos de una matriz
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarMatrizCompleta(Mat, tam):
129
print("")
print("MATRIZ COMPLETA:")
print("")
for i in range(tam):
print("[", end="")
print(*Mat[i], sep = ", ", end="") #Toda la fila
print("]")
#/**********************************************************
# Muestra todos los elementos de la diagonal principal
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarDiagPpal(Mat, tam):
print("")
print("DIAGONAL PRINCIPAL:")
print("")
for i in range(tam):
print("[", end="")
for j in range(i):
print(" ", end=",")
print(Mat[i][i], end=",")
for j in range(i+1, tam):
print(" ", end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la diagonal Inversa
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarDiagInv(Mat, tam):
print("")
print("DIAGONAL INVERSA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(tam-i-1):
print(" ", end=",")
print(Mat[i][tam-i-1], end=",")
for j in range(tam-i, tam):
print(" ", end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la matriz superior derecha
# sin la diagonal principal
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
130
def mostrarMatSupDerSin(Mat, tam):
print("")
print("MATRIZ SUPERIOR DERECHA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(i+1):
print(" ", end=",")
for j in range(i+1, tam):
print(Mat[i][j], end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la matriz superior derecha
# Con la diagonal principal
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarMatSupDerCon(Mat, tam):
print("")
print("MATRIZ SUPERIOR DERECHA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(i):
print(" ", end=",")
for j in range(i, tam):
print(Mat[i][j], end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la matriz superior izquierda
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarMatSupIzq(Mat, tam):
print("")
print("MATRIZ SUPERIOR IZQUIERDA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(tam - i):
print(Mat[i][j], end=",")
for j in range(tam - i, tam):
print(" ", end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la matriz inferior izquierda
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
131
# EXCEPCION:
#*********************************************************/
def mostrarMatInfIzq(Mat, tam):
print("")
print("MATRIZ INFERIOR IZQUIERDA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(i+1):
print(Mat[i][j], end=",")
for j in range(i+1, tam):
print(" ", end=",")
print("]")
#/**********************************************************
# Muestra todos los elementos de la matriz inferior derecha
# PARAMETROS: lista matriz
# entero tam
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarMatInfDer(Mat, tam):
print("")
print("MATRIZ INFERIOR DERECHA:")
print("")
for i in range(tam):
print("[", end="")
for j in range(tam - i - 1):
print(" ", end=",")
for j in range(tam - i - 1, tam):
print(Mat[i][j], end=",")
print("]")
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
tamMatriz = 7
#Genera la matriz, inicialmente con ceros
matriz = [[0] * tamMatriz for i in range(tamMatriz)]
cargarMatriz(matriz, tamMatriz)
mostrarMatrizCompleta(matriz, tamMatriz)
mostrarDiagPpal(matriz, tamMatriz)
mostrarDiagInv(matriz, tamMatriz)
mostrarMatSupDerSin(matriz, tamMatriz)
mostrarMatSupDerCon(matriz, tamMatriz)
mostrarMatSupIzq(matriz, tamMatriz)
mostrarMatInfIzq(matriz, tamMatriz)
mostrarMatInfDer(matriz, tamMatriz)
132
Reto: ¿Cómo eliminaria la ultima coma visible en cada fila?.
Reto: Agregue un menú para mostrar solo una matriz cada vez.
133
Problema 25. Visualización de números. Cree un programa que usando matrices
visualice un número de cuatro dígitos en pantalla. Los dígitos ocuparan cada uno
cinco filas y tres columnas y estarán formados por el carácter “*”
Análisis:
COMO hacerlo
134
o Como los datos son de tipo cadena (que realmente son arreglos de
caracteres) se puede escribir toda la fila de cada digito.
o Agregar un espacio en las cadenas de cada fila para facilitar el código. Este
espacio se puede agregar al definir los dígitos para simplificar el código.
#/**********************************************************
# programa que usa matrices para visualice un número
# de cuatro dígitos en pantalla.
# Los dígitos ocuparan cada uno cinco filas y tres columnas
# Programo: MLM
# Version: 1.0
# Fecha: 01/04/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Carga los asteriscos en
# PARAMETROS: lista mat
# entero tam
# RETORNA:
# EXCEPCION:
135
#*********************************************************/
def poblarMatriz(Mat):
#digito 0:
Mat[0][0] = "*** "
Mat[0][1] = "* * "
Mat[0][2] = "* * "
Mat[0][3] = "* * "
Mat[0][4] = "*** "
#digito 1:
Mat[1][0] = " ** "
Mat[1][1] = " * "
Mat[1][2] = " * "
Mat[1][3] = " * "
Mat[1][4] = " * "
#digito 2:
Mat[2][0] = "*** "
Mat[2][1] = " * "
Mat[2][2] = "*** "
Mat[2][3] = "* "
Mat[2][4] = "*** "
#digito 3:
Mat[3][0] = "*** "
Mat[3][1] = " * "
Mat[3][2] = "*** "
Mat[3][3] = " * "
Mat[3][4] = "*** "
#digito 4:
Mat[4][0] = "* * "
Mat[4][1] = "* * "
Mat[4][2] = "*** "
Mat[4][3] = " * "
Mat[4][4] = " * "
#digito 5:
Mat[5][0] = "*** "
Mat[5][1] = "* "
Mat[5][2] = "*** "
Mat[5][3] = " * "
Mat[5][4] = "*** "
#digito 6:
136
Mat[6][0] = "** "
Mat[6][1] = "* "
Mat[6][2] = "*** "
Mat[6][3] = "* * "
Mat[6][4] = "*** "
#digito 7:
Mat[7][0] = "*** "
Mat[7][1] = " * "
Mat[7][2] = " ** "
Mat[7][3] = " * "
Mat[7][4] = " * "
#digito 8:
Mat[8][0] = "*** "
Mat[8][1] = "* * "
Mat[8][2] = "*** "
Mat[8][3] = "* * "
Mat[8][4] = "*** "
#digito 9:
Mat[9][0] = "*** "
Mat[9][1] = "* * "
Mat[9][2] = "*** "
Mat[9][3] = " * "
Mat[9][4] = " ** "
#/**********************************************************
# Lee un numero de cuatro digitos y devuelve un array
# con los digitos separados
# PARAMETROS:
# RETORNA: list numero[4]
# EXCEPCION:
#*********************************************************/
def leerNumero():
numero =[0,0,0,0] #inicia en ceros
num = -1
while num < 0 or num > 9999:
try: #Bloque para validar que el numero sea entero
num = int(input("ingrese un entero: "))
except ValueError:
num = -1 #Caso de error, intenta de nuevo
# Separa los digitos
numero[0] = num // 1000
num = num % 1000
137
numero[1] = num // 100
num = num % 100
numero[2] = num // 10
num = num % 10
numero[3] = num
return numero
#/**********************************************************
# Muestra el numero en la pantalla
# # PARAMETROS: list num, digito
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarNumero(num, digito):
print("")
for i in range(5):
print(" ", end="")
for d in range(4):
print(digito[num[d]][i], end=" ")
print("")
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
digito = [[" " for col in range(5)]for row in range(10)]
#Genera la matriz, inicialmente con ceros
poblarMatriz(digito)
#lee el numero en un array con los 4 digitos
numero = leerNumero()
#Despliega el numero en la pantalla
mostrarNumero(numero, digito)
138
• Si lo ingresado no es numérico, se repite.
Reto: Los números se visualizan muy alargados, ¿qué modificaria para que sean mas
anchos?
Reto: Modificar el algoritmo para que acepte números desde uno a seis dígitos (rango
entre 0 y 999999).
Análisis:
139
• El ciclo exterior mostrará los dígitos, desde el cuarto al primero
o Para cada dígito, en otro ciclo mostrará cada columna desde la ultima
hasta la primera, (recuerde que también hay espacios).
Una posibilidad es cambiar las cadenas de asteriscos para representar las columnas, en
cuyo caso habrían solo tres, pero debido a que en la terminal cada carácter es muy alto,
se opta por utilizar los caracteres ASCII extendidos y solo dos columnas.
#/**********************************************************
# programa que usa matrices para visualice un número
# de cuatro dígitos, en forma vertical en pantalla.
# Programo: MLM
# Version: 2.0
# Fecha: 01/04/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Carga los símbolos para representar numeros verticales
# PARAMETROS: lista mat, por referencia
# RETORNA:
# EXCEPCION:
#*********************************************************/
def poblarMatriz(Mat):
#digito 0:
Mat[0][0] = "╔═════╗"
Mat[0][1] = "╚═════╝"
#digito 1:
Mat[1][0] = "╔══════"
Mat[1][1] = " "
#digito 2:
Mat[2][0] = "╔══╗ ╗"
Mat[2][1] = "╚ ╚══╝"
#digito 3:
Mat[3][0] = "╔══╦══╗"
Mat[3][1] = "╚ ╝"
#digito 4:
Mat[4][0] = "═══╦═══"
Mat[4][1] = "═══╝ "
#digito 5:
140
Mat[5][0] = "╔ ╔══╗"
Mat[5][1] = "╚══╝ ╝"
#digito 6:
Mat[6][0] = " ╔══╗"
Mat[6][1] = "╚══╩══╝"
#digito 7:
Mat[7][0] = "╔══╦═══"
Mat[7][1] = "╚ "
#digito 8:
Mat[8][0] = "╔══╦══╗"
Mat[8][1] = "╚══╩══╝"
#digito 9:
Mat[9][0] = "╔══╦══╗"
Mat[9][1] = "╚══╝ "
#/**********************************************************
# Lee un numero de cuatro digitos y devuelve un array
# con los digitos separados
# PARAMETROS:
# RETORNA: list numero[4]
# EXCEPCION:
#*********************************************************/
def leerNumero():
numero =[0,0,0,0] #inicia en ceros
num = -1
print("")
while num < 0 or num > 9999:
try: #Bloque para validar que el numero sea entero
num = int(input("ingrese un entero: "))
except ValueError:
num = -1 #Caso de error, intenta de nuevo
# Separa los digitos
numero[0] = num // 1000
num = num % 1000
numero[1] = num // 100
num = num % 100
numero[2] = num // 10
num = num % 10
numero[3] = num
return numero
141
#/**********************************************************
# Muestra el numero en la pantalla
# # PARAMETROS: list num, digito
# RETORNA:
# EXCEPCION:
#*********************************************************/
def mostrarNumero(num, digito):
for d in range(3, -1, -1): #para cada digito, desde el ultimo
for c in range(2): #Para cada columna de un digito
print(digito[num[d]][c])
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
digito = [[" " for col in range(2)]for row in range(10)]
#Genera la matriz, inicialmente con ceros
poblarMatriz(digito)
#lee el numero en un array con los 4 digitos
numero = leerNumero()
#Despliega el numero en la pantalla
mostrarNumero(numero, digito)
Salida:
142
8. ARITMETICA BINARIA, OCTAL, DECIMAL Y HEXADECIMAL
En primer lugar, comprender cómo se logra obtener un valor en una base dada:
Los dígitos validos en cada base inician en cero y van hasta b-1. Así:
• Binario 0, 1
• Octal 0, 1, 2, 3, 4, 5, 6, 7
• Decimal 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
• Hexadecimal 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. aquí, los dígitos
después del 9 se representan con letras, pues el alfabeto solo tiene 10 símbolos
numéricos.
143
• = 388310 (El subíndice indica la base del número)
En los ejemplos anteriores observe que para pasar de cualquier base a decimal,
debemos calcular (en decimal) las potencias de la base, es decir (hace referencia a la
posición del digito y a su “peso”) y proceder a descomponer multiplicando por esas
potencias y sumando, entonces:
En seudocódigo:
// B: número de la base
// numero: número escrito en la base B
// numDig: cantidad de dígitos de numero
// valor: valor en decimal
PROCEDIMIENTO convertirBaseDecimal()
pot<- 1
valor<-0
PARA i<- numDig HASTA 1 HAGA //de derecha a izquierda
Valor <- Valor + numero[i] * pot
pot<- pot * B
FIN PARA
FIN PROCEDIMIENTO
144
Ejemplo: Convertir a binario el número 321110 :
Divisiones por 2:
De la tabla anterior se toman los restos o módulos (el primero va a la derecha) ¿Por qué?
Piense que el último resto de la tabla se ha obtenido después de hacer más divisiones,
por lo que será el de mayor “peso” o valor equivalente y por lo tanto estará a la izquierda.
Note que para operar con números en base diferente a decimal, es más fácil tratarlos
como una cadena de caracteres (string).
// B: número de la base
// numero: número en decimal,
// numDig: número de dígitos de numero
// valor: valor en la base B
PROCEDIMIENTO convertirDecimalBase()
valor<-“”
145
simbolo <- "0123456789ABCDEF"
MIENTRAS numero > 0 HAGA
aux <- numero MOD B
valor <- símbolo[aux] + Valor //valor anterior queda a la derecha
numero <- numero DIV B //toma el cociente
FIN MIENTRAS
FIN PROCEDIMIENTO
Al pasar a PYTHON hay que hacer algunos “trucos” para manipular las cadenas.
Problema 27: Bases numéricas. Realice un programa que tome un número entero
y lo presente en binario, octal o hexadecimal, según la base escogida.
Análisis:
Algoritmo:
• Seleccionar la base
o Presentar un menú
o Ingresar una letra o número según opción
• Ingresar el número.
• Convertir a la base seleccionada
o Encontrar el modulo y el cociente al dividir el número por la base
o Almacenar el modulo, desde la derecha (menor peso primero)
o Hacer número igual al cociente
o Repetir hasta que cociente sea cero
• Mostrar el numero convertido
146
A partir de lo tratado anteriormente y teniendo en cuenta el algoritmo anterior, se
construye el código PYTHON:
#/**********************************************************
# Programa que toma un número entero y lo presenta en
# binario, octal o hexadecimal, según la base escogida
# Programo: MLM
# Version: 1.0
# Fecha: 07/05/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Presenta el menu y toma una opcion valida
# PARAMETROS:
# RETORNA: entero opc
# EXCEPCION:
#*********************************************************/
def leerMenu():
print()
print("------ CONVERSION NUMERICA ------")
print("1. BINARIO")
print("2. OCTAL")
print("3. HEXADECIMAL")
print("0. Terminar")
opc = -1
while opc < 0 or opc > 3:
try: #Bloque para validar que el numero sea entero
opc = int(input("ingrese una opcion: "))
except ValueError:
opc = -1 #Caso de error, intenta de nuevo
return opc
#/**********************************************************
# Lee el numero a convertir
# PARAMETROS:
# RETORNA: entero num
# EXCEPCION:
#*********************************************************/
def leerNumero():
num = 0
print()
while num < 1:
try: #Bloque para validar que el numero sea entero
num = int(input("ingrese un numero en decimal, mayor a cero: "))
147
except ValueError:
num = 0 #Caso de error, intenta de nuevo
return num
#/**********************************************************
# Convierte un numero decimal a una base numerica distinta
# PARAMETROS: entero numero, base
# RETORNA: cadena valor
# EXCEPCION:
#*********************************************************/
def convertirDecBase(numero, base):
simbolo = "0123456789ABCDEF" #Simbolos a leer
valor= "" #Contendra caracteres del numero convertido,
while numero > 0:
aux = numero % base
valor = simbolo[aux] + valor #Agrega el simbolo por la izquierda
numero = numero // base
return valor
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
opcion = 1 # un valor inicial para entrar al while
baseNum = [0, 2, 8, 16] # las bases segun opcion
while opcion in range(1, 4): # entre 1 y 3
opcion = leerMenu()
if opcion > 0:
numDecimal = leerNumero()
base = baseNum[opcion]
convertido = convertirDecBase(numDecimal, base)
print("El numero {} en base {} es {}".format(numDecimal, base,
convertido))
Pregunta: Por qué la lista ‘baseNum[]’ tiene como primer elemento el cero?
Salidas:
148
Problema 28: Bases numéricas 2. Realice un programa que, dado un número
binario, octal o hexadecimal, lo muestre en decimal.
Análisis:
149
QUE se quiere Un número convertido en decimal
Algoritmo:
• Seleccionar la base
o Presentar un menú
o Ingresar una letra o número según opción
• Ingresar el número y validar según la base.
• Convertir a Decimal
o Hacer resultado = 0
o Hacer pot= 1 (equivale a B0)
o Repetir para cada digito (desde la derecha)
▪ Hacer digito * pot y sumarlo a resultado
▪ Hacer pot = pot * B (equivale a Bn siendo n la posición del digito)
• Mostrar el número convertido
#/**********************************************************
# Programa que toma un número binario, octal o hexadecimal,
# según la base escogida y lo convierte a Decimal
# Programo: MLM
# Version: 1.0
# Fecha: 07/05/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Presenta el menu y toma una opcion valida
# PARAMETROS:
# RETORNA: entero opc
# EXCEPCION:
#*********************************************************/
def leerMenu():
print()
print("------ CONVERSION NUMERICA ------")
print("1. BINARIO")
print("2. OCTAL")
150
print("3. HEXADECIMAL")
print("0. Terminar")
opc = -1
while opc < 0 or opc > 3:
try: #Bloque para validar que el numero sea entero
opc = int(input("ingrese una opcion: "))
except ValueError:
opc = -1 #Caso de error, intenta de nuevo
return opc
#/**********************************************************
# Lee el numero a convertir,
# valida que sus digitos sean validos, segun la base
# PARAMETROS: entero base
# RETORNA: cadena num
# EXCEPCION:
#*********************************************************/
def leerNumero(base):
simbolo = "0123456789ABCDEF" #Simbolos a leer
sBase = simbolo[:base] #Toma la subcadena de digitos validos segun
base
num = ""
print()
ok = False #para obligar a entrar en el while
while not ok:
num = input("ingrese un numero en base {}, mayor a cero:
".format(base))
ok = True #inicialmente asume un numero correcto
for i in range(len(num)): #Revisa cada caracter del numero
if num[i] not in sBase:
ok = False
return num
#/**********************************************************
# Convierte un numero decimal a una base numerica distinta
# PARAMETROS: cadena numero
# entero base
# RETORNA: cadena valor
# EXCEPCION:
#*********************************************************/
def convertirBaseDec(numero, base):
simbolo = "0123456789ABCDEF" #Simbolos a leer
resultado = 0
pot = 1
l = len(numero) - 1 #indice del primer digito a la derecha
151
for i in range(l, -1, -1): #recorre cada digito hacia la izquierda
#toma el digito y encuentra su valor (posicion en la lista simbolo)
digito = simbolo.index(numero[i])
resultado += digito * pot
pot *= base
return resultado
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
opcion = 1 # un valor inicial para entrar al while
baseNum = [0, 2, 8, 16] # las bases segun opcion
while opcion in range(1, 4): # entre 1 y 3
opcion = leerMenu()
if opcion > 0:
base = baseNum[opcion] #Base del numero a ingresar
numConvertir = leerNumero(base)
convertido = convertirBaseDec(numConvertir, base)
print()
print("El numero {}(base {}), en decimal es {}".format(numConvertir,
base, convertido))
152
Reto: Realice cambios al código para poder ingresar las letras también en minúscula
cuando se use la base hexadecimal.
153
9. RECURSIVIDAD
Hasta ahora, los algoritmos han utilizado estructuras cíclicas para la solución de
problemas que impliquen repetir operaciones. Una alternativa a esa solución es emplear
módulos de código que se llamen a sí mismos, estos módulos se denominan
recursivos .
Los algoritmos recursivos son más simples y compactos que sus correspondientes
iterativos, sin embargo, su ejecución es más lenta y requieren más recursos.
Un algoritmo recursivo se diseña considerando que deben existir algoritmos para resolver
una versión reducida del problema, que se denominan casos base . Hay que
descomponer el problema en sub problemas cuya solución vaya aproximándose a los
casos base.
Los algoritmos recursivos pueden llegar a utilizar grandes cantidades de memoria, pues
al llamarse a sí mismos, se implementa una pila cuyo tamaño crece linealmente con el
número de recursiones necesarias en el algoritmo, pudiendo llegar a bloquearse la
ejecución del programa.
154
• El subprograma se invoca a sí mismo (esto es lo que lo convierte en recursivo).
• Cada llamada recursiva se hace con un parámetro de menor valor que el de la
anterior llamada. Así, cada vez se está invocando a otro problema idéntico pero
de menor tamaño.
• Existe un caso base en el que ya no se utiliza la recursividad.
• Lo importante es ASEGURAR que el tamaño del problema disminuye hasta legar
a este caso base. Esto es necesario, porque de lo contrario el programa estaría
ejecutándose indefinidamente.
Análisis:
El MCD se define como el número más grande por el cual se puedan dividir exactamente
los dos números. El MCD se encuentra así:
𝑚𝑐𝑑 (𝑎 − 𝑏, 𝑏) 𝑠𝑖 𝑎 > 𝑏
𝑚𝑐𝑑(𝑎, 𝑏) = { 𝑚𝑐𝑑(𝑎, 𝑏 − 𝑎)𝑠𝑖 𝑏 > 𝑎 }
𝑎 𝑠𝑖 𝑏 = 0
𝑏 𝑠𝑖 𝑎 = 0
155
En planteamiento anterior, observe que en los casos recursivos, siempre van
disminuyendo los valores hasta que eventualmente se llega a uno de los casos base,
que tienen solución inmediata.
FUNCION mcd(a, b)
SI (b=0) ENTONCES
m <- a
SINO
SI (a=0) ENTONCES
m <- b
SINO
SI (a > b) ENTONCES
m <- mcd(a - b, b)
SINO
m <- mcd(a, b-a)
FIN SI
FIN SI
FIN SI
Retorne m
FIN
Observar que, dentro del algoritmo, se prueban primero los casos base, si no, se invoca
de nuevo al algoritmo para un caso menor.
#/**********************************************************
# Algoritmo recursivo que muestra el Máximo Común Divisor (MCD)
# de dos enteros dados
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Lee un numero entero mayor a cero, validando
# PARAMETROS:
# RETORNA: entero num
# EXCEPCION:
#*********************************************************/
def leerNumero():
num = 0
print()
while num < 1:
try: #Bloque para validar que el numero sea entero
num = int(input("ingrese un numero entero, mayor a cero: "))
156
except ValueError:
num = 0 #Caso de error, intenta de nuevo
return num
#/**********************************************************
# Encuentra el Maximo comun divisor entre dos numeros
# utiliza un algoritmo recursivo
# PARAMETROS: entero a, b
# RETORNA: entero mcd
# EXCEPCION:
#*********************************************************/
def encontrarMCD(a, b):
mcd = 0
if b == 0:
mcd = a
elif a == 0:
mcd = b
elif a > b:
mcd = encontrarMCD(a-b, b)
else:
mcd = encontrarMCD(a, b-a)
return mcd
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ MAXIMO COMUN DIVISOR ------")
numA = leerNumero()
numB = leerNumero()
maxComDiv = encontrarMCD(numA, numB)
print("El MCD entre {} y {} es {}".format(numA, numB, maxComDiv))
157
Reto: Ejecute el programa paso a paso y observe el funcionamiento recursivo.
Análisis:
El código:
#/**********************************************************
# Algoritmo recursivo que muestra el resultado de un numero
# elevado a un exponente
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
#**********************************************************/
#/**********************************************************
# Lee un numero entero mayor a cero, validando
# PARAMETROS: cadena msj
# RETORNA: entero num
# EXCEPCION:
#*********************************************************/
def leerNumero(msj):
num = -1
print()
while num < 0:
try: #Bloque para validar que el numero sea entero
num = int(input(msj))
except ValueError:
158
num = -1 #Caso de error, intenta de nuevo
return num
#/**********************************************************
# Encuentra el Maximo comun divisor entre dos numeros
# utiliza un algoritmo recursivo
# PARAMETROS: entero base, exp
# RETORNA: entero result
# EXCEPCION:
#*********************************************************/
def calcularPotencia(n, p):
if p == 0:
pot = 1
else:
pot = n * calcularPotencia(n, p-1)
return pot
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ POTENCIA DE UN NUMERO ------")
Salida:
Algoritmo mejorado:
159
Ejemplo: 28 = 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2
También 28 = (2 ∗ 2 ∗ 2 ∗ 2) ∗ (2 ∗ 2 ∗ 2 ∗ 2)
El seudocódigo:
160
return pot
Reto: A los dos programas anteriores, agregue una variable que cuente el numero de
iteraciones recursivas y compare los resultados.
161
10. ALGORITMOS DE BUSQUEDA Y ORDENAMIENTO
Búsqueda de un elemento
Análisis:
COMO hacerlo Para ello debemos recorrer todo el vector buscando el mayor
número y registrando su posición.
Algoritmo:
• Para poblar el vector, se toma un ejemplo anterior, usando la función aleatoria.
• Para buscar la posición del número mayor:
o Asumir que el valor mayor es -1 (hay solo números positivos en el vector)
o Recorrer el arreglo
▪ Si algún elemento es mayor al valor, guardar el nuevo valor y su
posición.
• Mostrar el vector y el valor de posición, que indicara el lugar del mayor.
162
#/**********************************************************
# Llena una lista de 20 enteros con números aleatorios,
# muestra el contenido y determina en qué posición de la
# lista está el número mayor
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# Genera 20 numeros aleatorios entre 0 y 100 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 20):
#Genera entero al azar entre 0 y 100
entero.append(int(random.randrange(0, 100)))
return entero
#/**********************************************************
# Busca la posicion del numero mas grande en una lista
# PARAMETROS: lista vector
# RETORNA: entero pos
# EXCEPCION:
#*********************************************************/
def buscarMayor(vector):
pos = 0
mayor = -1
for i in range(0, len(vector)):
if(vector[i] > mayor):
pos = i
mayor = vector[i]
return pos
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ BUSCA EL NUMERO MAYOR ------")
vector = generarNumeros()
posMayor = buscarMayor(vector)
print()
print("Contenido del vector:")
print(vector)
163
Si esta dos veces el número mayor (87), ¿se muestra la primera ubicación o la segunda?.
¿Por qué? Si se permiten números negativos, ¿Qué se debe modificar?
Reto: Modifique el algoritmo para generar números negativos, digamos entre -100 y 100.
Análisis:
#/**********************************************************
# Llena una lista de 20 enteros con números aleatorios,
# muestra el contenido y determina en qué posición de la
# lista está el número mayor y el numero menor
164
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# Genera 20 numeros aleatorios entre 0 y 100 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 20):
#Genera entero al azar entre 0 y 100
entero.append(int(random.randrange(-100, 100)))
return entero
#/**********************************************************
# Busca la posicion del numero mas grande en una lista
# PARAMETROS: lista vector
# RETORNA: entero posMayor, posMenor
# EXCEPCION:
#*********************************************************/
def buscarPosMayorMenor(vector):
posMayor = posMenor = 0
mayor = menor = vector[0]
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
165
print()
print("------ BUSCA EL NUMERO MAYOR ------")
vector = generarNumeros()
posMayor, posMenor = buscarPosMayorMenor(vector)
print()
print("Contenido del vector:")
print(vector)
Análisis:
166
QUE se quiere Mostrar el vector antes de y después de ordenarlo.
Para ordenar un vector o arreglo de valores, existen varios métodos que son más o
menos eficientes dependiendo de los valores y del tamaño del arreglo a ordenar.
Veamos primero un cuadro con ejemplo del ordenamiento por “Burbuja”, para visualizar
la forma como se efectúa el ordenamiento y los intercambios que se realizan en el
proceso:
BURBUJA
Pivote, el cual comparara con los
X siguientes
X Y Se hace comparacion (X?Y)
Y X Se realizo intercambio (X>Y)
X Elemento ya ordenado
167
-81 -26 86 -44 -31 34
-81 -26 86 -44 -31 34
-81 -44 86 -26 -31 34
-81 -44 86 -26 -31 34
-81 -44 86 -26 -31 34
-81 -44 86 -26 -31 34
-81 -44 -26 86 -31 34
-81 -44 -26 86 -31 34
-81 -44 -31 86 -26 34
-81 -44 -31 86 -26 34
-81 -44 -31 86 -26 34
-81 -44 -31 -26 86 34
-81 -44 -31 -26 86 34
-81 -44 -31 -26 86 34
-81 -44 -31 -26 34 86
-81 -44 -31 -26 34 86 Ordenado
Análisis:
Los demás elementos del algoritmo (poblar el vector, mostrar) ya han sido tratados
anteriormente.
#/**********************************************************
# Llena una lista de 20 enteros con números aleatorios
# y muestra el contenido.
# Despues ordena la lista de menor a mayor
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
168
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# Genera 20 numeros aleatorios entre 0 y 100 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 20):
#Genera entero al azar entre 0 y 100
entero.append(int(random.randrange(-100, 100)))
return entero
#/**********************************************************
# Ordena los elementos de una lista
# utilizando el metodo de burbuja.
# PARAMETROS: lista vector
# RETORNA: lista vector
# EXCEPCION:
#*********************************************************/
def ordenarBurbuja(vector):
return vector
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ ORDENAR UNA LISTA ------")
vector = generarNumeros()
print()
print("Contenido del vector inicial:")
print(vector)
vector = ordenarBurbuja(vector)
169
print()
print("Contenido del vector ordenado:")
print(vector)
Búsqueda binaria
Análisis:
COMO hacerlo Hay que generar el vector, ordenar (usar método anterior) y
buscar el valor, realizando una búsqueda binaria.
170
elementos del vector. Una opción es ir comparando hasta que se encuentre el elemento
o este sea mayor (o menor) del valor buscado. En este momento se pude detener el
proceso, ya que cualquier otro elemento posterior también será mayor (o menor). Esta
opción es suficientemente eficiente para arreglos pequeños, pero cuando el arreglo es
grande, se puede emplear un método denominado búsqueda binaria que reduce el
número de comparaciones. Para que este método funcione es obligatorio que el arreglo
este ordenado.
Explicación gráfica:
BUSQUEDA BINARIA
1 10
Val -30 Valor a Buscar medio=
Inf+(Sup
X Elemento a Comparar Lim. Inferior Lim. Superior - Inf)/2
1 2 3 4 5 6 7 8 9 10 i
Inicial
-> -81 -61 -44 -31 -26 16 34 63 69 86 1 10 5 <
-81 -61 -44 -31 -26 16 34 63 69 86 1 4 2 >
-81 -61 -44 -31 -26 16 34 63 69 86 3 4 3 >
-81 -61 -44 -31 -26 16 34 63 69 86 4 4 4 >
-81 -61 -44 -31 -26 16 34 63 69 86 5 4 ?
Limites cruzados, no encontrado
BUSQUEDA BINARIA
medio=
Inf+(Sup -
Val 63 Valor a Buscar Inf)/2
Lim. Inferior Lim. Superior
1 2 3 4 5 6 7 8 9 10 i
171
Inicial
-> -81 -61 -44 -31 -26 16 34 63 69 86 1 10 5 >
-81 -61 -44 -31 -26 16 34 63 69 86 6 10 8 =
Encontrado
inf <- 1
sup <- tam //tamaño del arreglo
MIENTRAS inf <= sup HAGA
pos <- inf+(sup-inf)/2 //se trunca la fracción
SI vec[pos] = dato ENTONCES
Devolver(pos) //se ha encontrado
SINO
SI dato < vec[pos] ENTONCES
sup = pos – 1 //acorta por derecha
SINO
inf = pos + 1 //acorta por izquierda
FIN SI
FIN SI
FIN MIENTRAS
Devolver(no encontrado)
#/**********************************************************
# Busca un número en un vector ordenado,
# indica si se encuentra y en qué posición
# Programo: MLM
# Version: 1.0
# Fecha: 15/05/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
172
#/**********************************************************
# Genera 20 numeros aleatorios entre 0 y 100 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 20):
#Genera entero al azar entre 0 y 100
entero.append(int(random.randrange(0, 100)))
return entero
#/**********************************************************
# Ordena los elementos de una lista
# utilizando el metodo de burbuja.
# PARAMETROS: lista vector
# RETORNA: lista vector
# EXCEPCION:
#*********************************************************/
def ordenarBurbuja(vector):
#/**********************************************************
# Lee un numero entero mayor a cero, validando
# PARAMETROS: cadena msj
# RETORNA: entero num
# EXCEPCION:
#*********************************************************/
def leerNumero(msj):
num = -1
print()
while num < 0:
try: #Bloque para validar que el numero sea entero
num = int(input(msj))
except ValueError:
173
num = -1 #Caso de error, intenta de nuevo
return num
#/**********************************************************
# busca un numero en una lista y devuelve su posicion,
# si no se encuentra devueve -1
# PARAMETROS: lista vector
# entero num
# RETORNA: entero pos
# EXCEPCION:
#*********************************************************/
def buscarBinario(vector, num):
inf = 0
sup = len(vector)
while inf <= sup:
pos = (inf + sup)//2
if vector[pos] == num:
return pos
elif num < vector[pos]:
sup = pos -1
else:
inf = pos + 1
if inf == len(vector):
break
return -1
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ BUSQUEDA BINARIA ------")
vector = ordenarBurbuja(generarNumeros())
print()
print("Contenido del vector ordenado:")
print(vector)
num = leerNumero("Ingrese un numero entero positivo: ")
174
Nota: Observe la llamada a dos funciones en una sola línea de código, ¿Cómo funciona?.
175
recorrido y la posición donde se encontró en pos. Al final de cada recorrido se hace el
intercambio, si es necesario.
SELECCIÓN
X Pivote, el cual comparara con los siguientes
X Y Se hace comparacion (X?Y)
Y X Se realizo intercambio (X>Y)
X Elemento ya ordenado
1 2 3 4 5 6 i j Menor pos
Inicial 86 -26 -44 -81 -31 34 1 2 1
86 -26 -44 -81 -31 34 1 2 -26 2
86 -26 -44 -81 -31 34 1 3 -44 3
86 -26 -44 -81 -31 34 1 4 -81 4
86 -26 -44 -81 -31 34 1 5 -81 4
86 -26 -44 -81 -31 34 1 6 -81 4
-81 -26 -44 86 -31 34
-81 -26 -44 86 -31 34 2 -26 2
-81 -26 -44 86 -31 34 2 3 -44 3
-81 -26 -44 86 -31 34 2 4 -44 3
-81 -26 -44 86 -31 34 2 5 -44 3
-81 -26 -44 86 -31 34 2 6 -44 3
-81 -44 -26 86 -31 34
-81 -44 -26 86 -31 34 3 -26 3
-81 -44 -26 86 -31 34 3 4 -26 3
-81 -44 -26 86 -31 34 3 5 -31 5
-81 -44 -26 86 -31 34 3 6 -31 5
-81 -44 -31 86 -26 34
-81 -44 -31 86 -26 34 4 86 4
-81 -44 -31 86 -26 34 4 5 -26 5
-81 -44 -31 86 -26 34 4 6 -26 5
-81 -44 -31 -26 86 34
-81 -44 -31 -26 86 34 5 86 5
-81 -44 -31 -26 86 34 5 6 34 6
-81 -44 -31 -26 34 86
-81 -44 -31 -26 34 86
Análisis:
176
QUE se quiere Si se encuentra el número, Indicar la posición, sino, indicar
donde debería estar.
COMO hacerlo Hay que generar el vector, ordenar (usar método selección) y
buscar la posición del valor.
177
En seguida se presenta el código PYTHON:
#/**********************************************************
# Carga un vector de 10 enteros con números aleatorios
# Lo ordena de menor a mayor utilizando el método de selección.
# Recibe un número y determina en qué posición esta,
# si no se encuentra, determina en qué posición debería estar.
# Programo: MLM
# Version: 1.0
# Fecha: 10/06/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# Genera 10 numeros aleatorios entre 0 y 1000 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 10):
#Genera entero al azar entre 0 y 1000
entero.append(int(random.randrange(0, 1000)))
return entero
#/**********************************************************
# Ordena los elementos de una lista
# utilizando el metodo de seleccion.
# PARAMETROS: lista vector
# RETORNA: lista vector
# EXCEPCION:
#*********************************************************/
def ordenarSeleccion(vector):
for i in range(0, len(vector)-1):
pos = i
menor = vector[i]
for j in range(i+1, len(vector)):
if vector[j] < menor:
pos = j
menor = vector[j]
178
vector[i] = vector[pos]
vector[pos] = aux
return vector
#/**********************************************************
# Lee un numero entero mayor a cero, validando
# PARAMETROS: cadena msj
# RETORNA: entero num
# EXCEPCION:
#*********************************************************/
def leerNumero(msj):
num = -1
print()
while num < 0:
try: #Bloque para validar que el numero sea entero
num = int(input(msj))
except ValueError:
num = -1 #Caso de error, intenta de nuevo
return num
#/**********************************************************
# Recibe un número y determina en qué posición esta,
# si no se encuentra, determina en qué posición debería estar.
# PARAMETROS: lista vector
# entero num
# RETORNA: entero pos
# booleano encontrado
# EXCEPCION:
#*********************************************************/
def buscarBinario(vector, num):
inf = 0
sup = len(vector)
179
return pos, False
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ ORDENAMIENTO POR SELECCION ------")
vector = ordenarSeleccion(generarNumeros())
print()
print("Contenido del vector ordenado:")
print(vector)
num = leerNumero("Ingrese un numero entero positivo: ")
if(encontrado):
print("El numero {} se encuentra en la posición {}.".format(num, posNum))
else:
print("El numero {} Debería estar en la posición {}.".format(num,
posNum))
180
Caso de un número que si está en el vector:
Nota: Revise que pasa cuando la variable inf llega hasta la longitud del vector.
Problema 36: Ordenar vector por inserción. Poblar un vector de 50 enteros con
números aleatorios y ordenarlos de menor a menor utilizando el método de
Inserción.
181
Análisis:
Veamos la imagen:
INSERCIÓN
X Pivote, se comparara con los anteriores
X Y Se hace comparacion (X?Y)
X Elemento ya ordenado
1 2 3 4 5 6
Inicial 86 -26 -44 -81 -31 34
-26
-44
182
-81
-31
34
183
Observe que después del MIENTRAS y en caso de haber recorrido todo el vector a la
izquierda, hay que corregir el valor del índice p, para que apunte al primer elemento. Al
final, hay que decidir dónde ubicar el pivote.
Código PYTHON:
#/**********************************************************
# Carga un vector de 50 enteros con números aleatorios
# Lo ordena de menor a mayor utilizando el método de inserción.
# Programo: MLM
# Version: 1.0
# Fecha: 10/06/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios
#/**********************************************************
# Genera 50 numeros aleatorios entre 0 y 1000 y los guarda
# en una lista
# PARAMETROS: lista entero
# RETORNA: lista entero
# EXCEPCION:
#*********************************************************/
def generarNumeros():
entero = []
for i in range(0, 50):
#Genera entero al azar entre 0 y 1000
entero.append(int(random.randrange(0, 1000)))
return entero
#/**********************************************************
# Ordena los elementos de una lista
# utilizando el metodo de seleccion.
# PARAMETROS: lista vector
# RETORNA: lista vector
# EXCEPCION:
#*********************************************************/
def ordenarInsercion(vector):
for i in range(1, len(vector)):
pivote = vector[i]
p = i-1
while p >= 0 and pivote < vector[p]:
vector[p+1] = vector[p]
p -= 1
if p == -1:
p = 0
if pivote < vector[p]:
vector[p] = pivote
else:
vector[p+1] = pivote
return vector
184
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
print()
print("------ ORDENAMIENTO POR INSERCION ------")
vector = generarNumeros()
print()
print("Contenido del vector sin ordenar:")
print(vector)
vector = ordenarInsercion(vector)
print()
print("Contenido del vector ordenado:")
print(vector)
Pantalla de salida.
185
11. REGISTROS
Problema 37: Datos de Estudiantes. Se desea consultar los datos básicos de cinco
estudiantes (número de matrícula, nombres, sexo, carrera, semestre) a partir del
número de matrícula. Los datos de los estudiantes se digitaran al inicio.
Análisis:
Para el caso del problema planteado, a cada estudiante (el objeto) le corresponderá un
registro. Cada registro se compone de los siguientes campos (la asignación de los
nombres es un tanto arbitraria, pero debería ser un indicador de su uso o contenido):
186
En PYTHON el registro se puede asociar a la estructura de datos denominada dictionary
(diccionario). Los diccionarios se encuentran a veces en otros lenguajes como “memorias
asociativas” o “arreglos asociativos” (caso PHP). A diferencia de las listas, que se
indexan mediante un rango numérico, los elementos de un diccionario se indexan con
claves, que pueden ser cualquier tipo inmutable (su estado no puede ser modificado una
vez creado).
alumno = {
“numMatricula”:456789
“nombre”: “Juan Perez”
“sexo”: “M”
“carrera”: “Ingenieria Mecánica”
“semestre”: 6
}
187
El funcionamiento de una GUI se basa normalmente en eventos, tema que se revisara
mas adelante, por ahora tener en cuenta que, cada que el usuario interactua con el
formulario, se produce un evento, que puede ser capturado por el programa para ejecutar
una función asociada a dicho evento.
#**********************************************************
# Limpia los campos del formulario
# PARAMETROS:
# EXCEPCION:
#**********************************************************
def limpiarForm():
numMatricula.set("")
nombre.set("")
sexo.set("")
carrera.set("")
semestre.set("")
188
alum = {'numMatricula': "", 'nombre':"", 'sexo':"", 'carrera': "",
'semestre': ""}
#limpia el formulario
limpiarForm()
#**********************************************************
# Se ejecuta al pulsar el boton btnConsultar
# toma el numero de matricula y lo busca en la lista.
# si lo encuentra, despliega datos en el formulario
# si no, da un aviso de no encontrado en el campo nombre
# PARAMETROS: lista se define global.
# RETORNA:
# EXCEPCION:
#**********************************************************
def btnMostrarclicked():
global lista
codigo = numMatricula.get()
#borra los campos
limpiarForm()
# lo busca
posEsta = -1
for i in range(len(lista)):
if codigo == lista[i]['numMatricula']:
posEsta = i
break
189
else:
nombre.set('Matricula no encontrada')
return
#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
#La lista que contendra los registros
lista =[]
#Etiquetas
lbl1 = Label(contenedor, text="Matricula", anchor="e", width=10,
pady=5).grid(column=0, row=0)
lbl2 = Label(contenedor, text="Nombre", anchor="e", width=10,
pady=5).grid(column=0, row=1)
lbl3 = Label(contenedor, text="Sexo", anchor="e", width=10,
pady=5).grid(column=0, row=2)
lbl4 = Label(contenedor, text="Carrera", anchor="e", width=10,
pady=5).grid(column=0, row=3)
lbl5 = Label(contenedor, text="Semestre", anchor="e", width=10,
pady=5).grid(column=0, row=4)
#cajas de entrada
txtNumMatricula = Entry(contenedor, textvariable=numMatricula,
width=30).grid(column=1, row=0)
txtNombre = Entry(contenedor, textvariable=nombre, width=30).grid(column=1,
row=1)
txtSexo = Entry(contenedor,textvariable=sexo, width=30).grid(column=1,
row=2)
190
txtCarrera = Entry(contenedor,textvariable=carrera, width=30).grid(column=1,
row=3)
txtSemestre = Entry(contenedor,textvariable=semestre,
width=30).grid(column=1, row=4)
Salidas:
191
Reto: Agregue las validaciones para permitir solo números enteros positivos para la
matricula y el semestre y para que la longitud de las cadenas de datos no exceda el
espacio disponible en el formulario (use funciones).
Reto: Realice los ajustes necesarios para que no se permitan números de matrícula
repetidos.
Reto: Agregue el código necesario para mostrar el número del registro que se está
ingresando o mostrando.
Hasta ahora hemos implementado los algoritmos utilizando el lenguaje PYTHON, los
ejercicios que siguen serán desarrollados en el lenguaje C#, desarrollado por Microsoft,
y que está incluido en el ambiente de desarrollo Visual Studio (VS). Consulte en la
web como descargar la versión Express, también existe infinidad de tutoriales y
orientaciones para su uso.
Para crear un programa en Visual Studio C#, siga los siguientes pasos:
192
• Inicie “Visual Studio”. Aparecerá una ventana similar a la de la imagen. Clic en
“Nuevo Proyecto…”, a la izquierda, abajo.
22
En visual Studio, una “Solución” está compuesta por uno o más “proyectos”.
193
• Después que Visual Studio se configure y construya el proyecto inicial con el
nombre asignado por usted, se visualizara la siguiente pantalla. En ella
resaltaremos lo siguiente:
o A. El nombre de su proyecto como título de la ventana.
o B. La Barra de Menú Principal.
o C. La pestaña con el nombre del archivo. VS asigna el nombre
“Program.cs”.
o D. El nombre del programa.
o E. El método principal (equivale al programa principal en PYTHON) que se
ejecutara en primer lugar. En C# para consola se denomina “Main()”.
o F. Las referencias a librerías que se usaran en el programa.
o G. La clase “Program”, que inicialmente, solo contiene al método Main().
Más adelante trataremos con mayor detalle el tema de “Clases”.
o H. EL “espacio de nombres”, que en este caso coincide con el nombre del
proyecto asignado por nosotros.
o I. La ventana de propiedades, por ahora vacía. También se puede visualizar
la ventana del “Explorador de Soluciones”, etc. (Dar clic en el menú “VER”) .
o J. La barra de estado.
194
o Observe las pequeñas casillas con un signo menos. De clic en ellas para
ocultar partes del código y facilitar la revisión del mismo.
Con esta información básica sobre Visual Studio, continuaremos el estudio de los temas
de programación. Usted debe conocer la sintaxis y la semántica del lenguaje a utilizar,
pero por ahora, un estudio detallado del código presentado en el ejercicio le permitirá ir
adquiriendo los elementos básicos de este lenguaje.
195
12. SERIALIZACION Y PERSISTENCIA
Análisis:
COMO hacerlo Hay que agregar una función para leer los datos del Disco
Duro, si existen, al momento de iniciar el programa, es
conveniente preguntar si se quieren cargar los datos del
archivo o ingresarlos por teclado. También, ver como guardar
los datos en el disco, justo antes de terminar el programa.
Serialización
Persistencia de Datos
196
Código C# para el problema 38:
/**********************************************************
* SERIALIZACION y PERSISTENCIA
* Se ingresan y despues se consultan los datos básicos de
* cinco estudiantes (número de matrícula, nombres, sexo,
* carrera, semestre) a partir del número de matricula.
*
* El programa abre un archivo plano para
* cargar los datos al inicio, o guardarlos al final.
*
* Programo: MLM
* Version: 1.0
* Fecha: 15/04/2015
* Cambios:
* **********************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace P38_GuardarDatos
{
//La estructura es la definicion del registro
//puede ir "fuera" de la clase pues es una "plantilla".
struct reg_alumno
{
public int numMatricula;
public string nombre;
public char sexo;
public string carrera;
public int semestre;
};
class Program
{
//PROPIEDADES (Variables) CON AMBITO GLOBAL
//************************************************************************
/// <summary>
/// Muestra el formulario de datos del registro alumno
/// </summary>
static void mostrarFormulario()
{
197
Console.Clear();
Console.WriteLine();
Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.Black;
Console.Write(" *************** DATOS DE LOS ESTUDIANTES
**************** ");
Console.WriteLine();
Console.Write("
");
Console.WriteLine();
Console.Write(" |--------------------------------------------------------
---| ");
Console.WriteLine();
Console.Write(" | Numero de matricula:
| ");
Console.WriteLine();
Console.Write(" | Nombre del estudiante:
| ");
Console.WriteLine();
Console.Write(" | Sexo(M/F):
| ");
Console.WriteLine();
Console.Write(" | Carrera:
| ");
Console.WriteLine();
Console.Write(" | Semestre(1...10):
| ");
Console.WriteLine();
Console.Write(" |--------------------------------------------------------
---| ");
Console.WriteLine();
Console.Write("
");
//*********************************************************************
/// <summary>
/// Ingresa datos del registro alumno
/// </summary>
/// <param name="i">Indice del registro</param>
static void pedirDatos(int i)
198
{
mostrarFormulario();
Console.SetCursorPosition(27, 4);
estudiantes[i].numMatricula = Convert.ToInt32(Console.ReadLine());
Console.SetCursorPosition(27, 5);
estudiantes[i].nombre = Console.ReadLine();
Console.SetCursorPosition(27, 6);
estudiantes[i].sexo = Convert.ToChar(Console.ReadLine());
Console.SetCursorPosition(27, 7);
estudiantes[i].carrera = Console.ReadLine();
Console.SetCursorPosition(27, 8);
estudiantes[i].semestre = Convert.ToInt32(Console.ReadLine());
Console.BackgroundColor = ConsoleColor.Black;
Console.SetCursorPosition(2, 11);
Console.ForegroundColor = ConsoleColor.White;
}
//****************************************************************************
/// <summary>
/// Muestra datos del registro alumno
/// </summary>
/// <param name="i">Indice del registro</param>
static void mostrarDatos(int i)
{
mostrarFormulario();
Console.SetCursorPosition(27, 4);
Console.Write(estudiantes[i].numMatricula.ToString());
Console.SetCursorPosition(27, 5);
Console.Write(estudiantes[i].nombre);
Console.SetCursorPosition(27, 6);
Console.Write(estudiantes[i].sexo);
Console.SetCursorPosition(27, 7);
Console.Write(estudiantes[i].carrera);
Console.SetCursorPosition(27, 8);
Console.Write(estudiantes[i].semestre.ToString());
Console.BackgroundColor = ConsoleColor.Black;
Console.SetCursorPosition(2, 11);
Console.ForegroundColor = ConsoleColor.White;
}
//********************************************************************
/// <summary>
/// Busca el numero del registro para una matricula dada
/// </summary>
/// <param name="matr">numero de matricula a buscar</param>
/// <returns>Indice del registro</returns>
static int buscarMatricula(int matr)
{
int pos = 0;
for (int i = 0; i < numRegs; i++)
{
if(estudiantes[i].numMatricula == matr)
{
pos = i;
199
}
}
return pos;
}
//***************************************************************************
/// <summary>
/// Lee los registros de matricula desde un archivo
/// </summary>
/// <param name="nombreArchivo">Nombre del archivo a leer</param>
static void leeArchivo(string nombreArchivo)
{
int i = 0;
//Abre el archivo pafra lectura secuencial
if (File.Exists(nombreArchivo))
{
System.IO.StreamReader archivo = new
System.IO.StreamReader(nombreArchivo);
while (!archivo.EndOfStream) //Mientras no sea el fin del archivo
{
estudiantes[i].numMatricula =
Convert.ToInt32(archivo.ReadLine());
estudiantes[i].nombre = archivo.ReadLine();
estudiantes[i].sexo = System.Convert.ToChar(archivo.ReadLine());
estudiantes[i].carrera = archivo.ReadLine();
estudiantes[i].semestre = Convert.ToInt32(archivo.ReadLine());
i++; //Apunta al siguiente registro
}
archivo.Close();
}
else
{
Console.WriteLine();
Console.Write("Archivo no encontrado. pulse una tecla para
continuar...");
Console.ReadKey();
}
}
//*********************************************************************
/// <summary>
/// Guarda los registro de matricula en un archivo
/// </summary>
/// <param name="nombreArchivo">Nombre del archivo a escribir</param>
static void guardaDatos(string nombreArchivo)
{
//Abre archivo para sobreescribir. Si no existe, lo crea
System.IO.StreamWriter archivo = new
System.IO.StreamWriter(nombreArchivo);
for (int i = 0; i < numRegs; i++) //Lee los datos de los estudiantes
{
archivo.WriteLine(estudiantes[i].numMatricula.ToString());
archivo.WriteLine(estudiantes[i].nombre);
archivo.WriteLine(estudiantes[i].sexo.ToString());
200
archivo.WriteLine(estudiantes[i].carrera);
archivo.WriteLine(estudiantes[i].semestre.ToString());
}
archivo.Close();
}
201
guardaDatos(nombreArchivo);
}
}
}
Para evitar que aborte el programa, se hace una comprobación de la existencia del
archivo, cuando se intenta cargar:
202
Observaciones al código anterior, desarrollado en C# (C Sharp):
• En el primer bloque se hace referencia (using) a las librerías que contienen las
funciones propias del lenguaje, a ser utilizadas.
• El “Espacio de nombres” (namespace) declara un ámbito que contendrá todos los
objetos relacionados entre sí (los proyectos), con el fin de organizar elementos de
código y crear tipos a nivel global.
• Dentro del namespace se describen las distintas clases (en este caso existe una
sola clase denominada Program.
• Al inicio de la clase se declaran las propiedades de la clase (es decir las variables
que definen sus características).
• Los distintos métodos (módulos) se codifican de manera parecida a PYTHON.
Cuando un método devuelve “void” (vacío), es similar a un procedimiento
PYTHON.
• Todas las instrucciones (métodos) de entrada y salida a la pantalla, pertenecen a
la clase Console.
• Para invocar (llamar) un método propio de la clase, basta con dar su nombre y
entre paréntesis los parámetros, si los requiere.
• En C#, como en otros lenguajes, los arreglos inician en la posición 0 (en PYTHON
inician en 1).
• Se utiliza un método de la clase Environment (ambiente) para tomar el nombre de
la ruta a la carpeta “Mis Documentos”.
203
• El lector debe estudiar la sintaxis propia del lenguaje para comprender el
significado de cada instrucción particular.
Problema 39: Movimiento del Caballo. Diseñe un algoritmo que lea las
coordenadas con la ubicación de un caballo en un tablero de ajedrez (Columna,
Fila), muestre como resultado una matriz con el tablero, marcando con la letra A la
ubicación actual del caballo y con la letra C las casillas a las que se puede mover,
considere los límites del tablero. Finalmente, cada que se ingrese una posición,
esta y los posibles lugares de destino se guardaran en un archivo. Se termina
cuando se ingrese un cero.
Análisis:
204
En las siguientes figuras, se ha sobrepuesto al tablero la matriz anterior de movimientos
para tres posiciones iniciales del caballo.
En la primera, el caballo inicia en (4,4), todos los ocho destinos son permitidos.
En la segunda, a partir de (1,2) solo se permiten tres, los otros cuatro quedarían por fuera
del tablero. Igualmente, en el tercer tablero (7,7) solo son permitidos 4 movimientos.
En la tabla siguiente, Las columnas grises contienen las cantidades que cambian Col y
Fil y, como ejemplo, asumiendo que la posición actual es 7,7, las dos últimas columnas
muestran las coordenadas de destino, indicando en rojo las que no son permitidas
(mayores a ocho).
Esta tabla nos muestra una posible solución parcial a nuestro algoritmo: Una matriz
donde se carguen inicialmente los valores de las dos primeras columnas con los
desplazamientos relativos (asumiendo la posición inicial = 0,0), luego al introducir la
205
posición inicial, calcular las últimas dos columnas. A partir de esta matriz, se mostraran
solo las posiciones cuyos valores de Col y Fil sean mayores a Cero y menores a nueve.
Así, una primera aproximación al algoritmo será (por ahora se ignora el requerimiento de
almacenar en un archivo):
206
Por ejemplo, si usamos dos filas de pantalla para dibujar una fila del tablero de ajedrez y
el tablero inicia en la fila 2 de la pantalla. Para la fila 8 del tablero se debe escribir en: 1+
(8 x 2) = 17. (1 es la fila anterior al inicio, 8 la fila del tablero a escribir, 2 la cantidad de
filas de pantalla para cada fila del tablero). De manera similar se calculara para las
columnas.
Está pendiente la última parte del problema, guardar en archivo los valores de posición
actual y posibles movimientos, para lo cual debemos agregar código que abra un archivo
para escritura y, cada vez que se ingrese una posición, se guarden los valores. Antes de
salir se debe “cerrar” el archivo.
También hay que definir la forma en que se guardaran los datos. Se puede tener una
línea por cada caso, con la siguiente estructura (se puede definir como se quiera):
“I: (” + <col ini> + “, ” + <fil ini> + “); D: (” + [<col dest> + “, ” + <cfil dest> + “) ; ” ]… +<nl>
Por ejemplo:
La “I” indica la posición inicial, la “D” los posibles destinos, una línea por cada posición
inicial.
Posible solución:
• Declarar una variable tipo cadena, con la cadena inicial “I: (”.
• Cuando se ingrese la posición actual, agregar a la cadena la columna y la fila para
tener datos válidos y evitar escribir cuando se ingrese cero.
• Agregar a la cadena los destinos válidos.
• Guardar la cadena en el archivo. La primera vez y antes de escribir la línea, se
debe “abrir” el archivo.
• Al terminar el programa, “cerrar” el archivo.
Los detalles para crear, abrir, escribir, etc. el archivo son propios del lenguaje a utilizar.
Se recomienda al lector revisar la documentación relacionada.
El código c#:
/*****************************************************************************
* MOVIMIENTO DEL CABALLO
* Lee las coordenadas con la ubicación de un caballo en un tablero de ajedrez
207
* (Columna, Fila) y muestra el tablero, marcando con la letra A la
* ubicación actual y con la letra C las casillas a las que se puede mover.
*
* La posicion inicial y los lugares de destino se guardan en un archivo.
* Se termina cuando se ingrese un cero.
*
* Programo: MLM
* Version: 1.0
* Fecha: 22/04/2015
* Cambios:
* ****************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace P39_Caballo
{
class Program
{
//VARIABLES GLOBALES ***************************************************
static int col, fil; //Datos leidos del teclado
static int[,] posiciones = new int[4, 8]; //Posibles posiciones del caballo
static string datosArchivo = "I: ("; //Cadena a escribir para cada posicion
static string nombreArchivo = ""; //Contendra el nombre del archivo
static FileStream flujoDatos; //Objetos para el manejo del archivo
static StreamWriter escribeArchivo;
static Boolean abierto = false; //indica si el archivo ya fue abierto
//**********************************************************************
/// <summary>
/// Inicializa valores en matriz de nuevas posiciones del caballo
/// </summary>
static void inicializar()
{
//Columnas
posiciones[0, 0] = posiciones[0, 1] = -2;
posiciones[0, 2] = posiciones[0, 3] = -1;
posiciones[0, 4] = posiciones[0, 5] = 1;
posiciones[0, 6] = posiciones[0, 7] = 2;
//Filas
posiciones[1, 2] = posiciones[1, 4] = -2;
posiciones[1, 0] = posiciones[1, 6] = -1;
posiciones[1, 1] = posiciones[1, 7] = 1;
posiciones[1, 3] = posiciones[1, 5] = 2;
}
//**********************************************************************
/// <summary>
/// Ingresa la posición del caballo.
/// </summary>
208
static void ingresarPosicion()
{
Console.Clear();
Console.WriteLine();
Console.Write(" *** MOVIMIENTOS DEL CABALLO *** ");
Console.WriteLine();
Console.Write(" ");
Console.WriteLine();
Console.Write(" Numero de Columna (1...8): ");
col = Convert.ToInt32(Console.ReadLine());
if (col > 0)
{
Console.Write(" Numero de Fila (1...8): ");
fil = Convert.ToInt32(Console.ReadLine());
}
}
//**********************************************************************
/// <summary>
/// Calcula las coordenadas de destino del caballo
/// Si sale del tablero, cambia a cero
/// </summary>
static void calcularDestinos()
{
for (int i = 0; i < 8; i++)
{
posiciones[2, i] = posiciones[0, i] + col;
posiciones[3, i] = posiciones[1, i] + fil;
if (posiciones[2, i] < 1 || posiciones[2, i] > 8
|| posiciones[3, i] < 1 || posiciones[3, i] > 8)
{
posiciones[2, i] = posiciones[3, i] = 0; //No permitido
}
}
}
//**********************************************************************
/// <summary>
/// Dibuja el tablero en pantalla
/// </summary>
static void dibujarTablero()
{
Console.Write(" ");
for (int i = 0; i < 8; i++)
{
Console.WriteLine();
Console.Write(" |---|---|---|---|---|---|---|---|");
Console.WriteLine();
Console.Write(" | | | | | | | | |");
}
Console.WriteLine();
Console.Write(" |---|---|---|---|---|---|---|---|");
//Console.WriteLine();
}
209
//**********************************************************************
/// <summary>
/// Marca la posición actual del caballo
/// </summary>
static void marcarActual()
{
int f, c;
f = 5 + (2 * fil);
c = 4 * col;
Console.SetCursorPosition(c, f);
Console.Write("A");
datosArchivo = datosArchivo + col.ToString() + ", " + fil.ToString() +
"); D:";
//**********************************************************************
/// <summary>
/// Marca solo los destinos permitidos.
/// </summary>
static void marcarDestinos()
{
int ff, cc;
for (int i = 0; i < 8; i++)
{
if (posiciones[2, i] > 0)
{
ff = 5 + (2 * posiciones[3, i]);
cc = 4 * posiciones[2, i];
Console.SetCursorPosition(cc, ff);
Console.Write("1");
datosArchivo = datosArchivo + " (" + posiciones[2, i].ToString()+
", " + posiciones[3, i].ToString() + ");";
}
}
guardarDatos(); //Guarda la linea de datos en el archivo
Console.SetCursorPosition(0, 23);
}
//**********************************************************************
/// <summary>
/// Abre el archivo para escritura, guarda la linea.
/// </summary>
static void guardarDatos()
{
if (!abierto) //La primera vez abre el archivo
{
nombreArchivo = Environment.GetFolderPath
(Environment.SpecialFolder.MyDocuments);
nombreArchivo += @"\datosCaballo.txt";
flujoDatos = new FileStream
(nombreArchivo, FileMode.OpenOrCreate, FileAccess.Write);
escribeArchivo = new StreamWriter(flujoDatos);
abierto = true;
}
210
escribeArchivo.WriteLine(datosArchivo);
datosArchivo = "I: ("; //Reinicia para otra linea de datos
}
//**********************************************************************
/// <summary>
/// PROGRAMA PRINCIPAL
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
inicializar();
do
{
ingresarPosicion();
if (col > 0 && fil > 0)
{
calcularDestinos();
dibujarTablero();
marcarActual();
marcarDestinos();
Console.Write("<ENTER> para continuar...");
int a = Console.Read();
Console.Read();
}
} while (col > 0 && fil > 0);
escribeArchivo.Close(); //Cierra el flujo de datos al archivo
}
}
}
//************** FIN DEL CODIGO ************************************************
211
Observaciones al código anterior:
• Revisar las librerías utilizadas (using), las cuales son generadas automáticamente
al crear el proyecto. Se pueden agregar manualmente otras, en caso de utilizar
instrucciones que lo requieran.
• El código se escribe dentro del “namespace” y la clase.
• Se cargan en la matriz los valores posibles de desplazamiento del caballo desde
la posición actual.
• El método ingresarPosicion() limpia la pantalla, escribe un título y pide la posición.
Si la columna es cero, termina. Observe que NO se realiza ninguna validación, se
podría ingresar un número mayor a 8 o una letra o cadena. En este caso el
programa abortara.
• El método calcularDestinos(), completa los valores en las columnas vacías de la
matriz, si se sale del tablero, los deja en cero.
• dibujarTablero() muestra todas las casillas vacías.
• Para calcular la fila en la pantalla, los métodos marcarActual() y marcarDestinos()
agregan las cinco líneas antes del inicio del tablero al producto del número de fila
(del tablero) por las filas (de pantalla) usadas por cada fila del tablero.
• Para calcular la columna no se agrega nada, pues los caracteres a escribir en la
primera columna, están en la posición 4 (inicia desde cero) y el ancho de cada
columna es de cuatro.
• El programa principal inicializa, después repite mientras que los números
ingresados sean mayores a cero (si se ingresa cero, se termina la ejecución).
• Note las instrucciones dentro del SI interno. Después de dibujar las posiciones, se
pide el ingreso de un valor, como recurso para detener el siguiente ciclo y evitar
que la pantalla se borre, con el fin que el usuario observe el resultado antes de
continuar. La siguiente instrucción read(), limpia el buffer del teclado (pruebe
suprimiéndola).
• Se agrega una librería (using System.IO;) para el manejo de Entrada/Salida
(archivos).
• La cadena para ensamblar cada línea a escribir (datosArchivo).
• Los objetos de las clases FileStream y StreamWriter, se usan para declarar
el flujo de datos a un archivo, para escritura.
• El booleano para marcar si ya se ha abierto el archivo (opcional y depende de la
implementación realizada).
212
• En el método marcarActual(), que escribe en el tablero la posición inicial del
caballo, se agrega una instrucción para agregar las coordenadas de la posición
inicial a los datos de la línea a escribir en el archivo.
• En marcarDestinos() se agrega, para cada destino valido, la información de
las coordenadas, entre paréntesis y finalizando en “;”.
• Una vez agregadas todas las posiciones finales, se escribe la línea en el buffer
del archivo.
El método guardarDatos():
Como resultado, el archivo de datos con el registro de cuatro posiciones iniciales del
caballo y los posibles destinos para cada una:
Reto: Agregue las validaciones necesarias para evitar que el programa aborte.
Investigue y utilice las sentencias “try - catch”.
Reto: Agregue la opción “cargar datos”, que lea del archivo y muestre las posiciones en
pantalla.
213
13. LA PROGRAMACION ORIENTADA A EVENTOS
Problema 40: Reloj Análogo. Desarrolle un programa que muestre un reloj análogo,
cuya manecilla horaria, el minutero y el segundero se muevan de acuerdo con la
hora del sistema.
Análisis:
COMO hacerlo Para realizar la animación del reloj, primero hay que dibujar
la caratula del reloj, luego unas pequeñas líneas que marquen
las posiciones de cada hora, por ultimo las manecillas…
Analicemos como calcular un ángulo a partir de un valor de hora, por ejemplo, si la hora
es 4:32:50 AM.
• Tomar el valor de la hora, incluida la parte decimal de los minutos, dado que el
movimiento de la aguja es continuo, no por saltos: Hora + minutos/60 = 4+ 32/60
= 4.53 (se puede ignorar el efecto de los segundos).
• Calcular el ángulo de la hora:
o Corregir cuando sea mayor a las 12: Hora = Hora módulo 12. En este
ejemplo no cambia.
o Angulo de la hora: 4.53 x 360 / 12 = 135.90.
• Calcular el ángulo de los minutos:
o 32 x 360 / 60 = 1920.
• Calcular el ángulo de los segundos:
o 50 x 360 / 60 = 3000.
En la siguiente imagen se muestran los elementos a utilizar para definir los puntos
necesarios al dibujar de la línea (L) que represente una manecilla.
214
• Se define un elemento contenedor cuadrado para el dibujo con ancho (W) igual
a la altura (H).
• El radio de la esfera es un poco menor que la mitad del ancho del elemento
contenedor (W).
• El centro de la esfera (O) tendrá coordenadas (W/2, W/2), que también son las
coordenadas iniciales de la línea a dibujar.
• La longitud de la línea (L) depende del radio de la esfera menos un pequeño
valor, diferente para cada manecilla.
• Las coordenadas del otro extremo de la línea se encuentra así:
o 𝐷𝑦 = 𝐿 𝑠𝑒𝑛𝑜(á𝑛𝑔𝑢𝑙𝑜)
o 𝐷𝑥 = 𝐿 𝑐𝑜𝑠𝑒𝑛𝑜(á𝑛𝑔𝑢𝑙𝑜)
• Estos valores hay que sumarlos a la coordenada del centro, para tene el otro
extremo de la línea.
Este proceso se puede repetir cada segundo para simular el movimiento de las
manecillas del reloj. ¿Cómo hacer para que se ejecute el código cada segundo?
Un evento es un mensaje que envía un objeto cuando ocurre una acción. La acción
podría ser causada por la interacción del usuario, como un clic del botón, o podría ser
iniciada por otra lógica de programa, como cambiar el valor de una variable. El objeto
que provoca el evento se conoce como emisor del evento . El emisor del evento no
sabe qué objeto o método recibirá (controlará) los eventos que genera. El evento
normalmente es un miembro del emisor del evento; por ejemplo, el evento Click es un
miembro de la clase Button.
Delegados
Los delegados son de multidifusión , o sea que pueden guardar referencias a más de
un método de control de eventos. Esto quiere decir que varios manejadores de evento
diferentes se pueden “disparar” (ejecutar) al producirse un evento determinado. Un
delegado actúa como remitente de eventos de la clase que genera el evento y mantiene
una lista de los controladores registrados para el evento (uno o varios).
216
Inicie Visual Studio y siga los siguientes pasos:
217
Se visualizara la ventana así:
218
• La pestaña diseño (A), muestra el nombre del formulario.
• El recuadro interno (B) es el objeto de la clase PictureBox insertado desde el
cuadro de herramientas.
o Clic derecho sobre él y en el menú desplegable seleccionar “Propiedades”.
o En la ventana Propiedades del objeto, usted puede administrar los eventos
(D) asociados a dicho objeto, o sus propiedades (E).
o Busque y modifique, sobre la ventana propiedades, la siguiente:
▪ (name): pbxReloj.
• Clic sobre el formulario (form1), el cuadro de propiedades cambiara a las del
formulario.
o Busque y modifique, sobre la ventana propiedades (de form1), las
siguientes:
▪ (name): frmReloj.
▪ Text: Reloj Análogo.
• Visualice la ventana “Explorador de soluciones”:
o Clic derecho sobre “form1.cs”.
o En el menú desplegable Seleccione “Cambiar nombre” y cámbielo a
“frmReloj”.
• De nuevo, desde el cuadro de herramientas, busque y agregue un objeto de la
clase “Timer”.
• Observe que se crea un objeto “timer1”, pero no se inserta en el formulario, sino
que se muestra en la parte inferior de la ventana. Esto es debido a que dicho
objeto no tiene interfaz de usuario (window). Cambie las siguientes propiedades:
o (name): tmrReloj.
o Enabled: True.
o Interval: 1000.
• En esta misma ventana, seleccione el icono “Eventos”.
• Doble clic sobre el evento Tick.
o Observe que aparece el nombre “tmrReloj_Tick”
o Al mismo tiempo se muestra la ventana “frmReloj.cs” con el código: Se ha
creado un delegado y agregado el método manejador del evento Tick.
• Sobre el objeto pbxReloj, doble clic para desplegar la ventana de propiedades.
o seleccione “Eventos”.
o Busque el evento Paint y de doble clic. Se genera el método manejador
para este evento.
219
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RelojAnalogo
{
public partial class frmReloj : Form
{
public frmReloj()
{
InitializeComponent();
}
/// <summary>
/// Manejador del evento Tick del cronometro.
/// Se ejecuta cada que termina un periodo de tiempo.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void tmrReloj_Tick(object sender, EventArgs e)
{
}
/// <summary>
/// Manejador del evento Paint del picturebox
/// Dibuja todos los elementos del reloj.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pbxReloj_Paint(object sender, PaintEventArgs e)
{
}
}
}
220
En el explorador de soluciones, puede observar por ejemplo, el archivo
“frmReloj.Designer.cs”, que contiene el código generado automáticamente cuando usted
añadió objetos al formulario y modifico sus propiedades y eventos.
Hasta acá, hemos creado dos objetos adicionales dentro del formulario y cambiado
algunas de sus propiedades.
Antes de continuar con la construcción del código, realicemos un análisis más detallado
del algoritmo. Además de los métodos constructor y manejador del evento Tick, donde
se realizaran los cálculos geométricos de las líneas, necesitamos un método para
dibujarlas (Paint) y posiblemente algunos métodos auxiliares para evitar repetir código.
Realicemos una revisión de los componentes del programa, antes de iniciar la
codificación:
221
o Convertir el ángulo a radianes para poder usar las funciones
trigonométricas.
o Calcular los valores de los componentes X, Y de la línea, a partir del ángulo,
la distancia y las coordenadas del centro.
o Este método devolverá una estructura tipo Point con los valores X,Y del
extremo final de la línea.
• Otro método para calcular las coordenadas inicial y final de todas las 12 líneas
que señalan las horas en la caratula del reloj, para poblar la matriz:
o Iniciar con ángulo = 0, para la línea de las 12.
o Determinar dos distancias desde el centro: La mitad del largo (el radio) para
el extremo más alejado del centro y el mismo valor menos un 30%, ajustar
por prueba y error hasta obtener unas líneas cortas y acordes al tamaño de
la caratula.
o Por último, dentro de un ciclo PARA (las 12 horas):
▪ Llamar al método que calcula el punto final, pasándole como
parámetros el ángulo y el extremo más alejado y después con el
extremo más cercano al centro. De esta manera se encuentran los
dos puntos, con el mismo ángulo.
▪ Incrementar el ángulo en 30 grados, para la siguiente hora.
• El método manejador del evento tick, debe realizar:
o Actualizar los valores del área de dibujo, en caso que el usuario modifique
el tamaño del formulario.
o Asignar longitudes a las manecillas, a partir del radio, reduciendo en un
15%, 20% y 30% (Ajustar por prueba).
o Tomar la hora del sistema y desglosarla.
o Calcular el ángulo y determinar el punto final de la manecilla, usando el
método correspondiente. (repetir para las tres manecillas).
o Recalcular los valores de la matriz.
o Lo más importante: Generar un evento Paint, para que se dibuje la pantalla.
Esto se logra invocando al método invalidate().
• El método manejador del evento Paint realizara:
o Determinar los colores para el fondo y el borde de la esfera y para las
manecillas.
o Instanciar un objeto grafico para poder dibujar en él.
o Instanciar objetos Brush (brocha) y Pen (lápiz) para realizar los dibujos.
o Definir un rectángulo (un cuadrado) y dibujar el circulo de la caratula.
o Dibujar las 12 líneas de las horas.
o Dibujar cada manecilla, cambiando su color y grosor.
222
El código completo:
/*****************************************************************************
* RELOJ ANALOGO
* Muestra un reloj con manecillas para la hora, minutos y segundos.
*
* El reloj muestra la hora del sistema y se actualiza cada segundo.
* Cuando se cambia el tamaño de la ventana, se redibuja el reloj
* con el nuevo tamaño.
*
* Programo: MLM
* Version: 1.0
* Fecha: 22/04/2015
* Cambios:
* ****************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RelojAnalogo
{
public partial class frmReloj : Form
{
//Propiedades globales
//Puntos (x,y) para el centro y el final de las lineas de las manecillas
Point centro, pHora, pMinuto, pSegundo;
Boolean listo = false; //Para dibujar solo despues del primer tick.
// En la matriz se guardan las coordenadas de las lineas que marcan las horas
// en la caratula del reloj
Point[,] pNum = new Point[12, 2];
/// <summary>
/// Constructor de la Clase
/// </summary>
public frmReloj()
{
InitializeComponent();
//Modifica el tamaño del picture, segun menor tamaño del formulario
ajustarPicture();
223
}
/// <summary>
/// Modifica el tamaño del picture, segun menor tamaño del formulario
/// </summary>
private void ajustarPicture()
{
largo = this.Width;
if (largo > this.Height) largo = this.Height;
largo *= 0.9;
pbxReloj.Width = pbxReloj.Height = (int)(largo);
//actualiza el centro de la figura
centro.X = pbxReloj.Width / 2;
centro.Y = pbxReloj.Height / 2;
}
/// <summary>
/// devuelve coordenadas del punto final de la linea
/// </summary>
/// <param name="ang">Angulo de la linea en grados</param>
/// <param name="largo">longitud de la linea</param>
/// <returns>coordenada X, Y del extremo de la linea</returns>
private Point calcularPFinal(double ang, double largo)
{
ang = Math.PI * ang / 180.0; //angulo convertido a radianes
double dX = Math.Sin(ang) * largo; //Componente X
double dY = Math.Cos(ang) * largo; //Componente Y
int pX = (int)(centro.X + dX);
int pY = (int)(centro.Y - dY);
return new Point(pX, pY); //Punto final de la linea
}
/// <summary>
/// Define las coordenadas inicial y final para las lineas de la caratula
/// y las guarda en la matriz
/// </summary>
private void calcularLineasHoras()
{
double ang = 0; //el angulo de cada hora,
double longMax = (largo / 2) * 0.85; //Radio externo de las lineas
double longMin = longMax * 0.7; //Radio interno
for (int i = 0; i < 12; i++)
{
pNum[i, 0] = calcularPFinal(ang, longMin);
pNum[i, 1] = calcularPFinal(ang, longMax);
ang += 30; //incrementa 360/12 = 30 grados.
}
}
/// <summary>
/// Manejador del evento Tick del cronometro.
/// Se ejecuta cada que termina un periodo de tiempo.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
224
private void tmrReloj_Tick(object sender, EventArgs e)
{
ajustarPicture(); //En caso que se cambie el tamaño de la ventana
/// <summary>
/// Manejador del evento Paint del picturebox
/// Dibuja todos los elementos del reloj.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pbxReloj_Paint(object sender, PaintEventArgs e)
{
if (listo)
{
//Definicion de colores
Color cFondo = Color.FromArgb(255, 249, 249, 249);
Color cBorde = Color.FromArgb(255, 253, 156, 63);
Color cLineaH = Color.FromArgb(200, 250, 0, 0);
Color cLineaM = Color.FromArgb(160, 250, 96, 63);
Color cLineaS = Color.FromArgb(100, 250, 96, 63);
225
new Rectangle(10, 10, pbxReloj.Width - 15, pbxReloj.Height - 15);
226
Observaciones al código anterior:
Análisis:
227
QUE se tiene El usuario ingresa los comandos, haciendo clic en los botones
o por el teclado.
En primer lugar el maquetado de la página WEB. Para ello usaremos HTML y CSS .
El lector deberá conocer, al menos elementalmente, el lenguaje de etiquetas (HTML) y
la hoja de estilos en cascada (CSS).
Como guía para el diseño de la vista (maquetado), nos apoyamos en la siguiente imagen
(tomada de la calculadora de Microsoft).
228
Se muestra el código del archivo index.html. En el evento onload() se abre una nueva
ventana con el código de calculadora.html y algunos parámetros adicionales, como el
tamaño de la ventana y la posición donde se mostrara.
<!--*****************************************************************
* CALCULADORA HTML *
* INDEX.HTML invoca una nueva ventana para la calculadora *
* *
* Autor: Marco Leon Mora *
* Version: 1.0 *
* Fecha: Abril/2018 *
******************************************************************-->
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Calculadora JS</title>
</head>
<body onLoad= "window.open('calculadora.html', 'Calculadora',
'directories=0,titlebar=1,toolbar=0,location=0,status=0,menubar=0,scrollbars=0,resiza
ble=0,width=290,height=410,left=200,top=150')">
</body>
</html>
229
• El div “contenedor” (B) se usa para “arropar” todos los elementos dentro del body
(A).
• El div “pantalla1” (D), dentro de “pantalla” (C) se usa para lograr que los label
(etiquetas para visualizar el texto) permanezcan uno debajo del otro, a la derecha,
aun si no tienen texto. También se puede usar <BR> para separar los dos label.
• El label “txtMemoria” aparecerá a la izquierda dentro de (C) y señala si se ha
guardado un valor en la memoria (M).
• Dentro del div “teclado” (E) van 20 teclas, tipo button; el div “teclado2” (F) y el
“btnVertical”. (para la tecla ‘=’) Es necesario agregar este div para lograr que la
tecla vertical se posicione a la derecha de las dos últimas filas de teclas.
El código de calculadora.html:
<!--*****************************************************************
* CALCULADORA HTML *
* Contenido de la página de la calculadora *
* *
* Autor: Marco León Mora *
230
* Versión: 1.0 *
* Fecha: Abril/2015 *
******************************************************************-->
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Calculadora JS</title>
<link rel="stylesheet" href="calculadora.css">
<script src = "calculadora.js"></script>
</head>
<body>
<div id="contenedor">
<div id="pantalla">
<div id="pantalla1">
<label id= "txtPantalla1"></label>
</div>
<label id= "txtMemoria"></label>
<label id= "txtPantalla">0</label>
</div>
<div id= "teclado">
<button onclick = "calcular('MC')"> MC </button>
<button onclick = "calcular('MR')"> MR </button>
<button onclick = "calcular('MS')"> MS </button>
<button onclick = "calcular('M+')"> M+ </button>
<button onclick = "calcular('M-')"> M- </button>
231
</div>
</body>
</html>
Para dar la presentación adecuada, codificaremos el archivo CSS. Por facilidad y dado
que la calculadora no cambia de tamaño, las medidas se pueden dar en pixeles y se
calculan a partir del tamaño definido para el objeto button, teniendo en cuenta también
los margin y padding. Damos por entendido que usted conoce, por lo menos en forma
básica, el funcionamiento de una Hoja de estilos. El siguiente es el archivo CSS.
/********************************************************************
* CALCULADORA *
* Hoja de estilos para los objetos del HTML *
* *
* Autor: Marco León Mora *
* Versión: 1.0 *
* Fecha: Abril/2015 *
********************************************************************/
*{
margin: 0;
padding: 0;
}
/**************************** CONTENEDORES *************************/
body{
background-image: linear-gradient(to bottom, #D1E8ED 0%, #A0B1B5 100%);
min-height: 370px;
}
div{
border: solid 1px #aaa;
border-radius: 5px;
}
#contenedor{
232
background-image: linear-gradient(to bottom, #D1E8ED 0%, #A0B1B5 100%);
height: 100%;
width: 90%;
padding: 13px;
border: 0;
}
#pantalla{
background-color: #fff;
height: 65px;
margin: auto;
margin-bottom: 5px;
padding: 1px;
width: 244px;
}
#pantalla1{
height: 30px;
margin: auto;
width: 244px;
border: none;;
}
#teclado{
height: 297px;
margin: auto;
width: 246px;
padding: 2px;
border: 0;
}
#teclado2{
margin: 0;
padding: 0;
border: 0;
float:left;
height: 100px;
width: 196px;
}
button:hover {
background-color: #CACACA;
}
233
.btnHoriz{
width: 96px;
}
.btnVertical{
height: 96px;
}
#txtPantalla1{
font-size: 0.7em;
}
#txtMemoria{
float: left;
}
234
Posteriormente, en el lenguaje javascript , se desarrollara la lógica del programa, según
los requerimientos planteados, por etapas y de manera incremental, así:
En el archivo HTML se asoció un método manejador para los eventos click de todos los
botones (observe el código HTML). Ejemplo:
El diagrama de estados
235
Por ahora realizaremos el análisis sin tener en cuenta las teclas de memorias ni de
borrado. Estas funcionalidades serán implementadas posteriormente.
• En espera del primer digito del primer operando (al iniciar o después de borrar). Lo
llamaremos E1 (es el estado inicial).
• En proceso de ingreso de dígitos del primer operando, E2. Nota: Se diferencian estos
dos estados ya que, por ejemplo, si en E1 se pulsa una tecla de operador, el
resultado es diferente al caso de pulsar una tecla de operador estando en estado E2.
• En espera del primer digito del segundo operando, E3.
• En proceso de ingreso de dígitos del segundo operando, E4.
• Tecla igual pulsada, E5.
236
Cuando se inicia la calculadora (circulo negro) se pasa a E1 (estado inicial).
Estando en E1, si se pulsa la tecla “=”, se queda en ese estado, si se pulsa un digito, se
pasa a E2 y si se pulsa un operador, se pasara a E3.
En general, observe que al inicio de cada arco dirigido (flecha) se indica el evento que
dispara esa transición.
En los diagramas de estado también se pueden indicar las acciones realizadas como
efecto de una transición (al final del arco), pero en este caso y para mantener sencillo el
diagrama, haremos uso de otra herramienta de apoyo: una hoja Excel con una matriz en
la que se registren las acciones que deseamos realizar al responder a un evento dado
según el diagrama de estados anterior.
En la tabla anterior se describen los cinco estados considerados hasta ahora (E1-E5) y
se definen tentativamente las propiedades (variables) a utilizar, la mayoría son de tipo
cadena de caracteres. valor1 y valor2 contendrán los números convertidos desde
cadena o los resultados de operaciones realizadas. Las etiquetas (etqResultado,
etqHistorial del DOM) son los valores a mostrar en la pantalla.
En la tabla anterior se muestran las acciones a realizar cuando se pulsa una tecla
numérica o el punto, dependiendo del estado en que se encuentre.
237
El estado E1 es el inicial. Cada fila describe el cambio realizado en la propiedad
correspondiente, el orden en que se ejecutara se muestra entre paréntesis. Se reinician
las propiedades, el valor de la tecla digitada se lleva a etqResultado (para visualizarlo)
controlando no repetir ceros a la izquierda y se cambia estado a 2. Observe que en el
estado 5 el comportamiento es idéntico. Los estados E2, E3 y E4 son similares (pero en
E3 cambia a estado 4).
Operador binario
E1 E2 E3 E4 E5
operador = tecla (1) = tecla (2) = tecla (3) = tecla (1) = tecla (2)
= etqResultado = etqResultado = operar(v1, v2,
valor1 = 0 (2) (1) (1) operador) (3) =etqResultado (1)
= etqResultado
valor2 (2)
etqResultado = 0 = valor1 (4)
= valor1 + += valor1 + reemplaza += valor2 = valor1 +
etqHistorial operador (3) operador (3) operador (2) +operador (5) operador (3)
Estado = 3 (4) = 3 (4) = 3 (6) = 3 (4)
Operador unario
E1 E2 E3 E4 E5
operador
=operarUnario
= operar (etqResult,
valor1 Unario(0) (1) tecla) (1)
= operarUnario
(etqResult, tecla) = operarUnario = operarUnario
valor2 (1) (etqResult, tecla) (1) (etqResult, tecla) (1)
etqResulta
do = valor1 (3) = valor1 (3) = valor2 (3) = valor2 (3) = valor2 (3)
= cadena += cadena += cadena
etqHistorial Unario (2) Unario(2) Unario(2) += cadenaUnario(2) += cadenaUnario(2)
Estado = 3 (3) = 3 (4) = 5 (4) = 5 (4) = 3 (4)
238
valor1, en los demás se lleva a valor2 para no perder el resultado de la operación
anterior.
Hay que tener en cuenta (está pendiente) insertar código en el lugar adecuado para
controlar la división por cero.
Igual
E1 E2 E3 E4 E5
operador
= operar(v1, v2, = operar(v1, v2, = operar(v1, v2,
valor1 operador) (2) operador) (2) operador) (1)
valor2 = etqResultado (1) = etqResultado (1)
etqResultado = valor1 (3) = valor1 (3) = valor1 (2)
etqHistorial = "" (4) = "" (4) = ""
Estado = 5 (1) = 5 (5) = 5 (5)
Para la tecla “igual” las acciones en los estados E1 y E2 son similares: hasta ahora se
recibe el primer operando, por lo que no se realiza una operación. Los estados E3, E4 y
E5 también son prácticamente iguales. Esta anotación se hace para considerar la
reducción de código repetido.
/********************************************************************
* CALCULADORA *
* Presenta una calculadora que realiza las operaciones *
* según teclas pulsadas (similar a calculadora de Windows) *
* *
* Ejercio de Analisis y diseño de algoritmos y programacion *
* *
* Autor: Marco Leon Mora *
* Version: 1.3 *
* Fecha: Abril/2017 *
********************************************************************/
239
var estado = 1 //Estado inicial
/*******************************************************************
* Metodo inicial. Enlaza los elementos del DOM tipo label
*
* parametro:
* retorna:
* Afecta:
*******************************************************************/
function iniciar(){
etiqResultado = document.getElementById("txtPantalla");
etiqHistorial = document.getElementById("txtPantalla1");
etiqMemoria = document.getElementById("txtMemoria");
return;
}
/*******************************************************************
* Realiza acciones al pulsar una tecla
*
* Selecciona la acción según tecla:
* numérica, operador, operador unario, igual
* parámetros: tecla: string
* retorna:
* Afecta: etiqHistorial, etiqResultado, valor1, valor2
*******************************************************************/
function calcular(tecla){
switch(tecla){
// Agrega digito a la cadena
case "0": case "1": case "2": case "3": case "4":
case "5": case "6": case "7": case "8": case "9": case ".":
calcularDigito(tecla);
break;
// Operadores
case "+": case "-": case "/": case "*":
calcularOperador(tecla);
break;
case "=":
240
calcularIgual(tecla);
break;
// Borrar
case "←": case "CE": case "C":
// borrar(tecla);
break;
// Memoria
case "MC": case "MR": case "MS": case "M+": case "M-":
// calcularMemoria(tecla);
break;
// Operadores unarios
case "√": case "+-": case "1X": case "%":
calcularUnario(tecla);
break;
default:
break;
}
}
/*******************************************************************
* Ingresa un digito a la cadena, parte del número que se ingresa.
*
* parámetros: tecla: string
* retorna:
* Afecta: cadena, etiqResultado, estado
*******************************************************************/
function calcularDigito(tecla){
switch (estado){
case 1: case 5:
etiqHistorial.innerHTML = "";
etiqResultado.innerHTML = "";
operador = "";
valor1 = valor2 = 0;
estado = 2;
break;
case 3:
etiqResultado.innerHTML = "";
estado = 4;
}
//Condición para que al insertar valores no permita ceros a la izquierda
241
if(etiqResultado.innerHTML[0] == 0){
etiqResultado.innerHTML = tecla;
}else{
etiqResultado.innerHTML += tecla;
}
}
/*******************************************************************
* Realiza acciones según ESTADO, al pulsar teclas de operador
* (+, -, *, /, con dos operandos
*
* parámetros: tecla: string
* retorna:
* Afecta: etiqHistorial, etiqResultado, valor1, valor2
*******************************************************************/
function calcularOperador(tecla){
switch (estado){
case 1: //Espera primer digito de valor1
etiqResultado.innerHTML = "0";
case 2: //Recibiendo digitos de valor1
valor1 = parseFloat(etiqResultado.innerHTML);
operador = tecla;
etiqHistorial.innerHTML += (valor1 + operador);
estado = 3;
break;
case 3: //Espera primer digito de valor2. Cambia operador
valor1 = parseFloat(etiqResultado.innerHTML);
etiqHistorial.innerHTML =
etiqHistorial.innerHTML.replace(operador, tecla);
operador = tecla;
break;
case 4: //Recibiendo digitos de valor2
operador = tecla;
valor2 = parseFloat(etiqResultado.innerHTML);
valor1 = operar(valor1, valor2, operador);
etiqResultado.innerHTML = valor1;
etiqHistorial.innerHTML += (valor2 + operador);
estado = 3;
break;
case 5:
valor1 = parseFloat(etiqResultado.innerHTML);
operador = tecla;
etiqHistorial.innerHTML = valor1 + operador;
estado = 3;
break;
}
}
242
• El método calcularOperador() realiza diferentes acciones según el estado en que se
encuentre, estas operaciones se definieron inicialmente en los cuadros anteriores,
observe como se sigue en el código lo definido allí.
• Como E1 es similar a E2, en case 1: se realiza la operación diferente (igualar a cero)
y se elimina el break; para permitir que la ejecución continúe con las operaciones de
case 2:.
• En E3 se reemplaza el operador en la cadena de historial, en caso de pulsar dos
operadores seguidos.
• Observe la función para pasar una cadena de texto a un valor numérico.
/*******************************************************************
* Realiza acciones según ESTADO, para operaciones unarias
* (con un solo operando)
*
* parámetros: tecla: astringe
* retorna:
* Afecta: etiqHistorial, etiqResultado, valor1, valor2
*******************************************************************/
function calcularUnario(tecla){
switch (estado){
case 1: //Espera primer digito de valor1
valor1 = operarUnario(0, tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML= valor1;
estado= 3;
break;
case 2: //Recibiendo digitos de valor1
valor1= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor1;
estado= 3;
break;
case 3:
valor2= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor2;
estado = 5;
break;
case 4: //Recibiendo digitos de valor2
valor2= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor2;
estado = 5;
break;
case 5: //Despues de pulsar =,
243
valor2= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor2;
estado = 3;
break;
}
}
/*******************************************************************
* Realiza acciones según ESTADO, para operaciones unarias
* (con un solo operando)
*
* parámetros: tecla: string
* retorna:
* Afecta: etiqHistorial, etiqResultado, valor1, valor2
*******************************************************************/
function calcularUnario(tecla){
switch (estado){
case 1: //Espera primer digito de valor1
etiqResultado.innerHTML= "0";
case 2: //Recibiendo digitos de valor1
valor1= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor1;
estado= 3;
break;
case 3: case 4: case 5: //Recibiendo digitos de valor2 o despues de =
valor2= operarUnario(parseFloat(etiqResultado.innerHTML), tecla);
etiqHistorial.innerHTML += agregarCadenaUnario
(etiqResultado.innerHTML, tecla);
etiqResultado.innerHTML = valor2;
(estado == 5)? estado = 3: estado = 5;
break;
}
}
244
/*******************************************************************
* Realiza acciones según ESTADO, al pulsar la tecla igual (=)
*
* parámetros: tecla: string
* retorna:
* Afecta: etiqHistorial, etiqResultado, valor1, valor2
*******************************************************************/
function calcularIgual(tecla){
switch (estado){
case 1: //Espera primer digito de valor1
break; //NADA
case 2:
estado = 5;
break;
case 3: case 4: //Recibiendo digitos de valor2
valor2 = parseFloat(etiqResultado.innerHTML);
estado = 5;
case 5: //Despues de pulsar =, repite ultima operacion
valor1= operar(valor1, valor2, operador);
etiqResultado.innerHTML = valor1;
etiqHistorial.innerHTML = "";
break;
}
}
/*******************************************************************
* Efectúa la operación según opr
*
* parameters: op1, op2: float, opr: sgtring
* retorna: op: float
* Afecta:
*******************************************************************/
function operar(op1, op2, opr) {
var op = 0;
switch (opr) {
case "+":
op = op1 + op2;
break;
case "-":
op = op1 - op2;
break;
case "*":
op = op1 * op2;
245
break;
case "/":
if (op2 == 0) {
op = "Indeterminado";
}
else op = op1 / op2;
break;
default:
break;
}
return op;
}
/*******************************************************************
* Efectúa la operación unaria (SQR, -+, %, 1/x)
*
* Retorna el resultado de la operación y modifica mensajes en ventana
*
* parámetro: op: float, opr: string
* retorna: float
* Afecta: etiqHistorial
*******************************************************************/
function operarUnario(op, opr) {
var op1 = 0;
switch (opr) {
case "+-":
op1 = op * -1;
break;
case "√":
op1 = Math.sqrt(op);
break;
case "%":
op1 = valor1 * op / 100;
break;
case "1X":
if (op == 0) {
op1 = "Indeterminado";
estado = 6;
}
else op1 = 1 / op;
break;
default:
break;
}
return op1;
}
/*******************************************************************
* Agrega a la cadena de historial, segun operador unario
*
* parametros: valor, tecla: string
* retorna: cadena: string
* Afecta:
*******************************************************************/
246
function agregarCadenaUnario(valor, tecla) {
var strUnario;
switch (tecla) {
case "+-":
strUnario = "";
break;
case "√":
strUnario = "raiz(" + valor + ")";
break;
case "%":
strUnario = valor.toString() + "%";
break;
case "1X":
strUnario = "inv(" + valor + ")";
break;
default:
break;
}
return strUnario;
}
/*******************************************************************
* Borra ultimo carácter, ultimo numero o acumulado según tecla:
*
* parámetro: tecla: string
247
* retorna:
* Afecta:
*******************************************************************/
function borrar(tecla){
switch(tecla){
case "←": //elimina el ultimo caracter
if(etiqResultado.innerHTML.length > 0)
etiqResultado.innerHTML = etiqResultado.innerHTML.substring(0,
etiqResultado.innerHTML.length-1);
break;
case "CE": //borra el valor parcial ingresado
etiqResultado.innerHTML = "0";
if (estado == 2 || estado == 4) estado -= 1;
break;
case "C": //borra todo y reincia
etiqResultado.innerHTML= "0";
etiqHistorial.innerHTML="";
operador = "";
valor1 = valor2 = 0;
estado = 1;
break;
default:
break;
}
}
var memoria = 0;
var memActiva = false;
248
El siguiente código implementa las funcionalidades de memoria, incluyendo el cambio
de estado en algunas teclas:
/*******************************************************************
* Realiza acciones según tecla de memoria pulsada
* Actúa sobre la variable "memoria"
*
* parámetros: tecla: string
* retorna:
* Afecta: memoria, memActiva
*******************************************************************/
function calcularMemoria(tecla){
switch (tecla){
//Limpia el valor de la memoria
case "MC": //Borra contenido de memoria
memoria = 0;
memActiva = false;
etiqMemoria.innerHTML = "";
break;
switch (estado){
case 1: case 2: case 4:
estado = 3;
break;
}
249
}
Reto: Revise el comportamiento cuando se ingresa más de una vez el punto decimal.
Corrija si es necesario.
Para detectar las pulsaciones del teclado es necesario agregar un manejador de eventos,
usaremos el evento “keypress” (cuando se pulsa una tecla) para que se dispare un
método que identifique la tecla e invoque el método calcular(). Agregar la siguiente línea
al inicio del código, en el bloque “EVENT HANDLER”:
Reto: Incluya, dentro del método detectarTecla() la posibilidad de utilizar teclas para los
casos "←", "CE", "MC", "MR", "MS", "M+", "M-", "√", "+-", "1X", por ejemplo, teclas de
función o conjuntos de dos o más teclas para indicar la función deseada.
250
14. LA PROGRAMACION ORIENTADA A OBJETOS (POO)
Como su nombre indica, los principales componentes son los objetos, compuestos por
sus propiedades o características y sus comportamientos (denominados “métodos”). Las
propiedades hacen referencia a valores que toma el objeto y el conjunto de sus
propiedades definen el estado del objeto; mientras los métodos son procedimientos o
funciones que, normalmente, cumplen un objetivo específico (comportamiento).
Características de la POO
251
Otra propiedad importante es la Modularidad , que permite crear por partes, integrando
más fácilmente los componentes para que funcionen como un todo; permite agregar,
modificar o eliminar componentes sin interferir en el funcionamiento de otros.
252
El Objeto empieza a “existir” cuando se instancia o se crea, a partir del código definido
en la Clase; por lo tanto, se pueden crear múltiples objetos de una misma clase y cada
objeto podrá tener diferentes valores de sus propiedades y un comportamiento que
puede cambiar, dependiendo precisamente del valor actual de alguna(s) propiedad(es).
Es decir, el método operara con los valores de las propiedades de “ese” objeto, sin
interferir en otros objetos, así sean de la misma clase. Eso sí, cada objeto es único e
identificable.
Relaciones
Agregación : Es un tipo de asociación. Cuando se usan dos o más clases base para
conformar una clase más compleja, por ejemplo, automóvil está compuesto por motor,
transmisión… En esta relación, automóvil se puede considerar como “un todo” y motor
representa una parte del todo.
Uso : Esta relación se establece momentáneamente entre dos objetos (caso Cliente –
Servidor). El Cliente utiliza funcionalidades del Servidor sin mayores dependencias.
253
Dejemos hasta acá la teoría y vamos a realizar una implementación utilizando algunos
conceptos de la POO.
Para practicar con algunos los elementos de la POO, implementaremos desde cero el
famoso “Tetris”, diseñado originalmente por Alekséi Pázhitnov, para lo cual utilizaremos
el framework “Visual Studio” de Microsoft y codificaremos en C#.
Problema 42: Tetris. Desarrolle un programa para jugar Tetris. La pantalla debe
mostrar el tablero de juego, la imagen de la siguiente ficha y la ficha actual en el
tablero, la que podrá ser manipulada usando las teclas de flechas para girarla,
desplazarla a izquierda o derecha y hacerla caer. La ficha puede caer hasta
encontrar un obstáculo, integrándose al mundo. Se eliminaran las filas completas
y se llevara la cuenta de los puntos, asignándolos por cada fila eliminada y por
cada casilla eliminada, que tenga vecinas del mismo color. El juego termina
cuando se ocupen casillas del mundo en la fila superior.
Análisis:
254
El juego está compuesto por siete figuras geométricas (fichas)
formada cada una por cuatro cuadrados de un mismo color.
Nota: Los dos sprites deben ser del mismo tamaño. Y si cambia
este tamaño, cambiara el tamaño del área de juego.
255
Definiendo la Interfaz de usuario
Detengámonos un poco a pensar en la secuencia del juego. Primero, aparece una ficha
a la derecha (ficha previa) y, la primera vez, simultáneamente una ficha empieza a caer
en el mundo. Esta ficha empezara unas tres filas por encima del mundo. Como todos los
juegos, debe existir un evento que se ejecute periódicamente (timer) para actualizar los
valores y refrescar la pantalla. Hay que controlar las coordenadas de posición de la ficha
en el mundo y revisar si la ficha puede caer en la siguiente fila (para cada celda de la
ficha, verificar que la correspondiente posición en el mundo esté libre). Lo mismo cuando
se quiera desplazar horizontalmente. Para girar, se tendrá una matriz auxiliar donde se
representa la ficha girada para comprobar si las celdas no se “estrellan” con celdas
ocupadas en el mundo, si esto sucede, se retoma la matriz original (no hay giro), si no,
la matriz girada se copia en la matriz original de la ficha.
256
También hay que revisar en cada ciclo si existen filas completas en el mundo. En este
caso hay que “bajar” todas las filas superiores. Es el momento de contabilizar los puntos
ganados, una cierta cantidad por cada fila eliminada y, además, por cada celda eliminada
con vecinos del mismo color, también asignar un puntaje.
Por último, controlar que el mundo no tenga celdas ocupadas en la fila superior, en cuyo
caso el juego termina.
La Clase Mundo, además de propiedades y métodos (aún por definir) contendrá también
dos objetos de la Clase Ficha (previa y en juego). Podemos pensar en una relación de
composición.
257
En la figura anterior se muestra un diagrama de clases; la clase frmTetris, (formulario)
compuesta por un Mundo. La clase Mundo está compuesta de las clases FichaNueva
y Ficha, quien hereda de FichaNueva.
frmTetris es un formulario que hereda de la clase Form (Windows Microsoft). Debe tener
como propiedad un objeto de la clase Mundo. Un método constructor de la clase, que
es el método que se ejecuta cuando se instancia un objeto de la clase, donde se
instanciaran los controles definidos (que también son objetos) y se instanciara un objeto
mundo. Se tendrá un manejador de eventos para dibujar el mundo en un pictureBox y
otro para dibujar la ficha nueva. Un timer para controlar cada ciclo, en el que se intentara
hacer descender la ficha, buscar fichas llenas para eliminarlas, disparar los manejadores
para que los objetos ficha se redibujen, mostrar el puntaje y controlar cuando se termina
el juego. Se controlara el evento KeyDown (al pulsar una tecla) para girar o desplazar la
ficha en juego.
Mundo debe tener una matriz de 20 x 10 para mantener información de cada celda, una
FichaNueva, una FichaJuego, el puntaje, un sprite de fondo. Debe instanciar FichaNueva
y FichaJuego y cambiar sus propiedades cada vez que una nueva ficha se integre al
mundo, debe eliminar filas completas, calcular el puntaje…
FichaNueva debe tener forma, color y un sprite de ficha. Debe poder asignar un color,
dibujarse…
Ficha debe tener forma, color y posición, un sprite de ficha. Debe poder asignar un color,
dibujarse, desplazarse, girar…
Las Fichas deben mantener la forma en una matriz (de 4x4), para que se puedan dibujar
y revisar contra las celdas del mundo en caso de girar o desplazar.
Algunos métodos auxiliares para revisar si se puede mover la ficha, buscar filas llenas,
contar colores vecinos para asignar puntajes, controlar cuando se pierde el juego…
En fin, hay que considerar que cada propiedad o método necesarios deben pertenecer a
la clase que más los requiera y en la lógica de cada método se debe intentar cumplir la
mínima cantidad posible de funcionalidades, en caso necesario crear otros métodos.
258
Estos objetivos del diseño de clases buscan cumplir con las características de la POO,
especialmente buscar una alta cohesión y un bajo acoplamiento .
Algunas propiedades, como la lista de definición de colores a usar, el tamaño del mundo,
el margen y la posición inicial de las celdas, contendrán valores que se definen una sola
vez, no cambian y se utilizan en diferentes métodos de las clases. Por tal razón se
definirá una clase esta tatica denominada Valores (no se muestra en el gráfico de
clases). Las clases estáticas no se pueden instanciar; desde un punto de vista práctico,
son similares a variables globales.
En una etapa más avanzada del diseño, hemos usado de nuevo una hoja de Excel para
asignar mejor las propiedades y métodos y en la cual se han agregado otras columnas
para definir el tipo de datos y la visibilidad y poder revisar con mayor detalle las relaciones
entre las clases. Este proceso, como casi todo en el diseño, es cíclico y cada refinamiento
exige revisar lo anterior y efectuar algunos cambios.
frmTetris
Visibilidad Nombre Tipo Observaciones
privado mundo Mundo Objeto de la clase Mundo
Publico frmTetris() Constructor de la clase. Instancia a mundo
privado pbxMundo_Paint() void Llama a mundo.dibujar(), le pasa el objeto grafico
(pictureBox) donde se dibujara.
259
privado tmrReloj_Tick() void Manejador del evento Tick del timer. Llama los métodos
caerFicha(), buscarFilaLlena(), Get Puntos() y
perderJuego() de la clase Mundo. Invalida el panel2 y el
pbxMundo para obligar a dibujar.
En la clase Mundo, observar las propiedades, todas son privadas. Los métodos públicos
permiten que sean llamados desde la clase frmTetris, una revisión de la columna de
observaciones permite identificar cuales métodos de Mundo se requieren públicos. Los
demás deben ser privados, ya que son llamados desde otros métodos de la misma clase.
Mundo
Visibilidad Nombre Tipo Observaciones
privado cuadroFicha Bitmap Contiene el dibujo de una celda de ficha
privado mundo[ , ] int[ , ] Matriz con los valores de cada celda del mundo
privado puntos int Puntaje del juego
privado nuevaFicha NuevaFicha Objeto
privado ficha Ficha Objeto
publico puntos() int Metodo GET, devuelve el valor de puntos
publico mundo() Constructor de la clase. Instancia los objetos
nuevaFicha y ficha. Llama al método cargarFicha() de
la clase NuevaFicha.
260
private integrarFicha() void lleva a la matriz del mundo las celdas de la ficha.
Invoca los métodos GET mFicha y ColorFicha de la
clase Ficha.
publico buscarFilaLlena() void Recorre el mundo, si hay renglones llenos, los elimina.
Llama los métodos cuentaColor() y eliminarFila().
privado eliminarFila(int ) void Parámetro: Numero de fila. Elimina una fila y desplaza
todas las filas superiores. Asigna 100 puntos por cada
fila eliminada
privado cuentaColor(int ) int Parámetro: Numero de fila. Asigna un punto por cada
vecino del mismo color
publico perderJuego() booleano Revisa si existe una celda llena en la primera fila del
mundo.
publico dibujar(objeto) void Parámetro: Objeto gráfico. Dibuja el mundo de
acuerdo con la matriz. Llama al método dibujar() de
la clase Ficha
publico dibujarSiguiente(objeto) void Parámetro: Objeto gráfico. Dibuja la siguiente ficha.
Llama al método dibujar() de la clase NuevaFicha
Nota: Los métodos GET y SET son mecanismos que buscan proteger a las
propiedades de una clase, que debieran ser siempre privadas. Entonces, la forma de leer
o escribir desde un ámbito diferente es por intermedio de este tipo de métodos (observar
más adelante su codificación).
Nueva Ficha
Visibilidad Nombre Tipo Observaciones
protegido colorFicha int Color de la ficha
protegido posicion Point Posición donde se muestra la próxima figura,
coordenada de esquina sup. izq. En pixeles
protegido ficha[ , ] [ int , int ] Matriz 4x4 contiene la forma de la ficha. 1 si
hay celda, 0 si no.
privado formas[ , , ] [ int , int, Matriz 7x4x4 contiene las formas de las fichas.
int] 1 si hay celda, 0 si no.
privado r Random() Objeto generador de números aleatorios
protegido cuadroFicha Bitmap Carga la imagen de una celda, para dibujar
261
publico mFicha[ , ] int[ , ] Método GET devuelve la Matriz ficha[ , ]
publico colorFicha int Método GET devuelve el numero del color
publico NuevaFicha() Método constructor. Llama a cargarFicha()
publico cargarFicha() void Inicializa propiedades de la ficha
publico dibujar(objeto) void Virtual. Parámetro: Objeto gráfico. Dibuja el
mundo de acuerdo con la matriz ficha[,]
Por último la clase Ficha. Observe el método dibujar() que, como tiene el mismo
nombre del método de la clase padre, pero su comportamiento es diferente, se hace
necesario agregar el calificador override.
Ficha
Visibilidad Nombre Tipo Observaciones
privado fichaGirada[ , ] int[,] Provisional con la matriz de la ficha girada
publico posición() Point Método GET Devuelve la Posición de la celda
superior izquierda de la ficha
publico fichaGirada() int[ , ] Método GET/SET de la propiedad fichaGirada[ , ].
Llama a los métodos copiarFicha() y girarFicha()
publico ficha(int, int[,]) void Parámetros: numero de color, matriz con la
forma. Método constructor. Llama a cargar
Ficha()
publico cargarFicha(int, int[,]) void Parámetros: numero de color, matriz con la
forma. Inicializa propiedades de la ficha
privado copiarFicha() void Pasa la matriz fichaGirada a ficha[,]
privado girarFicha() void Gira la ficha 90 grados
publico desplazarFicha(int, int) void
Parámetros: movimiento horizontal. Movimiento
vertical. Ajusta posición de la ficha
privado dibujar(objeto) void Override. Parámetro: Objeto gráfico. Dibuja una
ficha dentro del mundo
262
En el diagrama anterior, observe los diferentes símbolos y consulrte su significado.
Construyendo la Interfaz
263
2. En el formulario. Cambie las siguientes propiedades o métodos:
a. Name: frmTetris
b. Locked: true
c. MaximumSize: 418, 560
d. MinimumSize: 418, 560
e. ShowIcon: False
f. Size: 418, 560
g. StartPosition: CenterScreen
h. Text: TETRIS
i. Evento KeyDown: Manejador de evento frmTetris_KeyDown
264
j. SplitterDistance: 260
265
Después de definir el funcionamiento de los diferentes miembros (propiedades y
métodos) de las clases definidas (realice el seudocódigo de cada método), se pasara al
código la lógica operativa de cada uno. Enseguida se muestra el código fuente del
programa Tetris:
/************************************************************************
Proyecto: TETRIS
Clase: frmTetris
Hereda de Form.
Representa la ventana del juego.
***********************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Resources;
namespace tetris
{
/// <summary>
/// Esta es la Clase principal
/// </summary>
public partial class frmTetris : Form
{
//PROPIEDADES
Mundo mundo; //Objeto de la clase mundo
/// <summary>
/// Constructor
/// </summary>
public frmTetris()
{
InitializeComponent();
mundo = new Mundo();
}
/// <summary>
/// Dibuja el mundo
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void pbxMundo_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics; //Objeto grafico (lienzo)
266
mundo.dibujar(g);
}
/// <summary>
/// ciclo del juego
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void tmrReloj_Tick(object sender, EventArgs e)
{
mundo.caerFicha(); //La ficha activa va descendiendo
mundo.buscarFilaLlena(); //Elimina filas completas
spContenedor.Panel2.Invalidate(); //Obliga a dibujar siguiente ficha
pbxMundo.Invalidate(); // Obliga a dibujar el mundo
lblPuntos.Text = mundo.Puntos.ToString();
if (mundo.perderJuego())
{
tmrReloj.Enabled = false;
MessageBox.Show("Usted Ha Perdido!!!", " GAME OVER ",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
this.Close();
}
}
/// <summary>
/// Se ejecuta cuando se pulsa una tecla de direccion
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void frmTetris_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Up: // Flecha arriba: Gira 90 grados a la derecha
if(mundo.girar()) pbxMundo.Invalidate();
break;
case Keys.Right: // Flecha a la derecha: Desplaza a derecha
if(mundo.desplazar(1, 0)) pbxMundo.Invalidate();
break;
case Keys.Down: // Flecha abajo: baja una fila
if(mundo.desplazar(0, 1)) pbxMundo.Invalidate();
break;
case Keys.Left: // Flecha a la izquierda: Desplaza a izquierda
if (mundo.desplazar(-1, 0)) pbxMundo.Invalidate();
break;
}
}
/// <summary>
/// Dibujar la siguiente ficha
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
267
private void spContenedor_Panel2_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics; //Objeto grafico
mundo.dibujarSiguiente(g);
}
}
}
//*************** FIN DEL CODIGO DE FRMTETRIS **************************************
/************************************************************************
Proyecto: TETRIS
Clase: VALORES
Esta es una clase estática que contiene valores globales a ser usados
por otras clases
***********************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace tetris
{
public static class Valores
{
/// <summary>
/// Arreglo con los posibles colores de una ficha
/// </summary>
public static Color[] colores = new Color[11]
{
Color.FromArgb(255,220,220,190), // Color.LightGray),
Color.FromArgb(200, Color.DeepSkyBlue),
268
Color.FromArgb(200, Color.Yellow),
Color.FromArgb(200, Color.DarkGray),
Color.FromArgb(200, Color.DarkOrange),
Color.FromArgb(200, Color.GreenYellow),
Color.FromArgb(200, Color.DeepPink),
Color.FromArgb(200, Color.Green),
Color.FromArgb(200, Color.Purple),
Color.FromArgb(200, Color.Red),
Color.FromArgb(200, Color.Blue)
};
public static Point tamMundo = new Point(10, 20); //10 columnas x 20 filas
//Tamaño de cada celda en el area de dibujo, en pixeles
public static int tamCelda = 25;
//Margenes del borde del mundo
public static int margen = 10;
//Posicion inicial de la figura que cae
public static Point posIni = new Point(3, -3);
}
}
/************************************************************************
Proyecto: TETRIS
Clase: MUNDO
Contiene el mundo de juego. instancia a NuevaFicha y a Ficha
***********************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace tetris
{
class Mundo
{
#region //PROPIEDADES **************************************
/// <summary>
/// Arreglo con los posibles colores de una ficha
/// </summary>
//Carga la imagen de una celda
269
private int[,] mundo = new int[Valores.tamMundo.X, Valores.tamMundo.Y];
private int puntos = 0;
//Siguiente ficha a visualizar a la derecha
NuevaFicha nuevaFicha;
//El mundo contiene una ficha
Ficha ficha;
#endregion
//************************************************************
/// <summary>
/// Ciclicamente, va cayendo la ficha
/// </summary>
public void caerFicha()
{
//prueba si hay espacio debajo, en el mundo
if (puedeMover(0, 1, ficha.mFicha))
//Desplaza la ficha a la siguiente fila
ficha.desplazarFicha(0, 1);
else
{
integrarFicha(); //Las celdas de la ficha se integran
//Ficha toma valores de Nueva Ficha
ficha.cargarFicha(nuevaFicha.ColorFicha, nuevaFicha.mFicha);
//Otra nueva ficha
nuevaFicha.cargarFicha();
}
}
/// <summary>
/// intenta mover la ficha horizontal o verticalmente (solo abajo, no sube)
/// </summary>
/// <param name="h">-1: izquierda, 1: derecha</param>
/// <param name="v">1: abajo</param>
public bool desplazar(int h, int v)
{
270
bool siPuede = puedeMover(h, v, ficha.mFicha);
if (siPuede) //prueba si hay espacio en el mundo
ficha.desplazarFicha(h, v); //Desplaza la ficha
return siPuede;
}
/// <summary>
/// Comprueba si la ficha se puede desplazar una fila (bajar)
/// o una columna (derecha o izquierda)
/// </summary>
/// <returns>Verdadero si se puede mover</returns>
private bool puedeMover(int h, int v, int[,] mFicha)
{
bool siPuede = true;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (mFicha[i, j] > 0) //Si existe una celda
{
//Siguiente columna (derecha o izquierda)...
int x = ficha.Posicion.X + i + h;
//o siguiente fila (solo abajo)
int y = ficha.Posicion.Y + j + v;
/// <summary>
/// Intenta girar la ficha 90 grados a la derecha
/// </summary>
public bool girar()
{
//Recupera copia de la ficha girada para probar
int[,] fichaG = ficha.FichaGirada;
bool siPuede = puedeMover(0, 0, fichaG); //Comprueba si se puede
271
if (siPuede) //Si se puede...
ficha.FichaGirada = fichaG; //gira la ficha en la matriz original
return siPuede;
}
/// <summary>
/// lleva a la matriz del mundo las celdas de la ficha
/// </summary>
private void integrarFicha()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (ficha.mFicha[i, j] > 0) //Si existe una celda
{
int x = ficha.Posicion.X + i;
int y = ficha.Posicion.Y + j;
if(y>=0)
mundo[x, y] = ficha.ColorFicha;
}
}
}
}
/// <summary>
/// Recorre el mundo, si hay renglones llenos, los elimina
/// </summary>
public void buscarFilaLlena()
{
for (int i = Valores.tamMundo.Y - 1; i > 0; i--)
{
int total = 0;
for (int j = 0; j < Valores.tamMundo.X; j++)
{
if (mundo[j, i] > 0)
total++;
}
if (total == Valores.tamMundo.X)
{
puntos += cuentaColor(i); //Puntos por vecinos del mismo color
eliminaFila(i++); //Desplaza fila y vuelve a revisar
}
}
}
/// <summary>
/// Elimina una fila y desplaza todas las filas superiores
/// </summary>
/// <param name="i">fila a eliminar</param>
private void eliminaFila(int fila)
{
//desplaza las filas superiores
for (int i = fila; i > 0; i--)
272
{
for (int j = 0; j < Valores.tamMundo.X; j++)
{
//Desplaza la celda
mundo[j, i] = mundo[j, i - 1];
}
}
puntos += 100; //Puntos por fila eliminada
}
/// <summary>
/// Asigna puntos por vecinos del mismo color
/// </summary>
/// <param name="f">numero de la fila</param>
/// <returns>total puntos por color de la fila</returns>
private int cuentaColor(int f)
{
int p = 0; //Almacena los puntos por vecinos del mismo color
//recorre todas las celdas de la fila
for(int c = 0; c < Valores.tamMundo.X; c++)
{
for (int l = c - 1; l <= c + 1; l++) //revisa los vecinos
{
//controla limites horizontales
if (l >= 0 && l < Valores.tamMundo.X)
{
//no cuenta fila inferior
for (int m = f - 1; m <= f; m++)
{
//controla limites verticales
if (m >= 0 && m < Valores.tamMundo.Y)
{
if (mundo[c, f] == mundo[l, m])
p++;
}
}
}
}
p--; //despuenta por la propia celda
}
return p;
}
/// <summary>
/// Revisa si existe una celda llena en la primera fila del mundo
/// </summary>
/// <returns></returns>
public bool perderJuego()
{
bool perdio = false;
for (int j = 0; j < Valores.tamMundo.X; j++)
if (mundo[j, 0] > 0)
perdio = true;
return perdio;
}
273
//********************************************************************************
/// <summary>
/// Dibuja el mundo de acuerdo con la matriz
/// </summary>
/// <param name="g"></param>
public void dibujar(Graphics g)
{
// Crea una brocha solida
SolidBrush brocha = new SolidBrush(Valores.colores[0]);
/// <summary>
/// Dibuja la siguiente ficha
/// </summary>
/// <param name="g"></param>
public void dibujarSiguiente(Graphics g)
{
nuevaFicha.dibujar(g);
}
}
}
//*************** FIN DE LA CLASE MUNDO ********************************
274
• Cuando se crea el mundo (método constructor Mundo()) se instancian los objetos
nuevaFicha y ficha. La primera vez se debe pasar a ficha las propiedades de
nuevaFicha.
• Se recomienda ejecutar paso a paso para comprender mejor la lógica de los
diferentes métodos.
• Observe como se accesa un método de la clase estática Valores.
/************************************************************************
Proyecto: TETRIS
Clase: NUEVAFICHA
Genera aleatoriamente una forma y color para la nueva ficha
Con método para Dibujar la ficha
***********************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace tetris
{
class NuevaFicha
{
//PROPIEDADES **********************************************************
protected int colorFicha = 0; //color de la ficha
//Donde se muestra la proxima figura, coordenada de esquina sup. izq.
protected Point posicion = new Point(10, 10);
protected int[,] ficha = new int[4, 4]; //contiene la forma de la ficha
private Random r = new Random(); //generador de numeros aleatorios
//Carga la imagen de una celda, para dibujar
protected Bitmap cuadroFicha = global::tetris.Properties.Resources.Ficha;
275
{0,0,0,0},
},
{ //Ficha tipo 3
{0,0,0,0},
{0,0,1,1},
{0,1,1,0},
{0,0,0,0},
},
{ //Ficha tipo 4
{0,0,0,0},
{0,1,1,0},
{0,0,1,1},
{0,0,0,0},
},
{ //Ficha tipo 5
{0,0,0,0},
{0,1,1,0},
{0,1,1,0},
{0,0,0,0},
},
{ //Ficha tipo 6
{0,0,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,0},
},
{ //Ficha tipo 7
{0,0,0,0},
{0,1,1,0},
{0,1,0,0},
{0,1,0,0},
},
};
#endregion
/// <summary>
/// Devuelve el numero del color
/// </summary>
public int ColorFicha
{
get { return colorFicha; }
}
//METODO CONSTRUCTOR ***************************************************
/// <summary>
/// Constructor de la clase
/// </summary>
276
public NuevaFicha()
{
cargarFicha();
}
//*********************************************************************
/// <summary>
/// Inicializa propiedades de la ficha
/// </summary>
public void cargarFicha()
{
colorFicha = r.Next(1, 11); //Definir el color aleatoriamente
int num = r.Next(7); //definir la forma aleatoriamente
for (int i = 0; i < 4; i++) //cargar la forma en la matriz
{
for (int j = 0; j < 4; j++)
{
ficha[i, j] = formas[num, i, j];
}
}
}
//*********************************************************************
/// <summary>
/// Dibuja una ficha dentro del mundo
/// </summary>
/// <param name="g">Lienzo para dibujar</param>
public virtual void dibujar(Graphics g)
{
// Crea una brocha solida con el color de la ficha
SolidBrush brocha = new SolidBrush(Valores.colores[colorFicha]);
int x = Valores.margen; //Eje X en pixeles del area de dibujo
for (int i = 0; i < 4; i++)
{
int y = Valores.margen; //Eje Y en pixeles del area de dibujo
for (int j = 0; j < 4; j++)
{
if (ficha[i, j] > 0)
{
g.DrawImage(cuadroFicha, x, y, cuadroFicha.Width,
cuadroFicha.Height); //Los bordes del cuadro
g.FillRectangle(brocha, x, y, cuadroFicha.Width,
cuadroFicha.Height); //El color de fondo, con transparencia
}
y += Valores.tamCelda;
}
x += Valores.tamCelda;
}
}
}
}
277
• La matriz estática formas contiene siete bloques en las que se determinan las formas
de las fichas. Esta matriz no se lleva a la clase Valores, pues solo se utiliza en la
clase NuevaFicha.
• La forma y el color de cada ficha se seleccionan aleatoriamente, ver cargarFicha().
/************************************************************************
Proyecto: TETRIS
Clase: FICHA
Hereda de NUEVAFICHA
Copia las propiedades de NuevaFicha.
Realiza los métodos para moverse en el mundo, girar, etc.
***********************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace tetris
{
class Ficha : NuevaFicha
{
//PROPIEDADES ****************************************************
private int[,] fichaGirada = new int[4, 4]; //para intentar girar
/// <summary>
/// Devuelve la copia de la Matriz girada 90 grados a derecha
/// </summary>
public int[,] FichaGirada
{
get
{
girarFicha();
return fichaGirada;
}
278
set
{
copiarFicha();
}
}
//******************************************************************************
/// <summary>
/// Inicializa propiedades de la ficha
/// </summary>
public void cargarFicha(int colFicha, int[,] formas)
{
posicion = Valores.posIni; //Posicion inicial de la figura que cae
colorFicha = colFicha; //Definir el color aleatoriamente
/// <summary>
/// Pasa la matriz fichaGirada a ficha
/// </summary>
private void copiarFicha()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
ficha[i, j] = fichaGirada[i, j];
}
}
}
//************************************************************************
/// <summary>
/// Ajusta posicion de la ficha
/// </summary>
public void desplazarFicha(int h, int v)
{
posicion.X += h;
279
posicion.Y += v;
}
//***********************************************************************
/// <summary>
/// Gira la ficha 90 grados
/// </summary>
private void girarFicha()
{
for (int i = 0; i < 4; i++) //cargar la forma
{
for (int j = 0; j < 4; j++)
{
fichaGirada[j, 3 - i] = ficha[i, j];
}
}
}
//*************************************************************************
/// <summary>
/// Dibuja una ficha dentro del mundo
/// </summary>
/// <param name="g">Lienzo para dibujar</param>
public override void dibujar(Graphics g)
{
// Crea una brocha solida con el color de la ficha
SolidBrush brocha = new SolidBrush(Valores.colores[colorFicha]);
//Eje X en pixeles del area de dibujo
int x = posicion.X * Valores.tamCelda + Valores.margen;
280
• La herencia se indica con dos puntos, class Ficha : NuevaFicha
• Observe como se construyen los métodos get/set en FichaGirada.
• Revise la lógica de girarFicha(). ¿Por qué, fichaGirada[j,3-i] = ficha[i,j];?
• El constructor de la clase requiere dos parámetros, debido a que copia propiedades
de la clase NuevaFicha.
Reto: Agregue un método que detenga el timer cuando se minimice la ventana del juego.
Reto: Agregue un modulo que muestre el mayor puntaje obtenido en varios juegos.
281
15. LISTAS DE OBJETOS Y PILAS
Arreglos de objetos
Cuando en una aplicación se necesite agrupar grupos de objetos relacionados entre sí,
se puede hacer fundamentalmente de dos maneras: creando una matriz o arreglo de
objetos o creando una colección o lista de objetos.
Los arreglos pueden contener cualquier tipo de datos, incluso datos compuestos
(estructuras), por lo tanto también pueden contener objetos, los que también se pueden
considerar como estructuras de datos con métodos asociados. Así, para accesar algún
miembro de un objeto contenido en un arreglo, se identificara dicho objeto usando el
nombre del arreglo y un índice y luego, después de un punto, el nombre del miembro, ya
sea propiedad o método (en otros lenguajes como PHP se utiliza “->”).
Las matrices son muy útiles para crear y trabajar con un número fijo de objetos
fuertemente tipados.
Listas de objetos
Las listas proporcionan una manera más flexible de trabajar con grupos de objetos. A
diferencia de los arreglos, el grupo de objetos con el que trabaja puede aumentar y
reducirse de manera dinámica a medida que cambian las necesidades de la aplicación.
Pilas
La pila es una forma de organización de los datos u objetos, en los que cada elemento
particular se inserta en la pila (“push”) solo al final de la misma y solo se puede retirar el
último elemento insertado en la pila (“pop”), es decir, es una organización “LIFO” (Last
In, First Out), a diferencia de las colas , que son “FIFO” (First In, First Out).
282
Una pila se puede entender si pensamos en un conjunto de platos en la cocina, colocados
uno encima del otro. Cada que se coloca uno, se hace en la parte superior (push), para
sacar un plato, se debe tomar el que este mas encima (pop).
Problema 42: Asistente del Sudoku. Desarrolle un programa que funcione como un
asistente para la resolución de sudokus. Debe mostrar las casillas, organizadas
por filas, columnas y cajas; mostrar, para cada casilla, todos los dígitos
candidatos; Al ingresar un número en una casilla, se debe verificar si es aceptado
y se eliminara de todos los candidatos de la fila, columna y caja correspondiente.
Además, deberá ser posible guardar el estado del tablero en cualquier momento y
recuperar el último estado guardado.
Análisis:
El Sudoku es un tablero de 81 casillas organizado en una matriz de nueve filas por nueve
columnas.
Las casillas se agrupan, además, en regiones o “cajas” de 3x3. Así, cada casilla
pertenece simultáneamente a una fila, a una columna y a una caja.
283
En cada casilla, se colocara solamente un digito del
1 al 9 (se exceptúa el cero). El juego inicia
suministrando una configuración inicial (ver imagen),
a partir de la cual, el jugador intentara llenar las
casillas vacías con la única condición de que el
digito contenido en cada casilla no se repita, ni en la
fila, ni en la columna, ni en la caja.
284
Cada caja de texto validara el ingreso de dígitos, comprobando que
este en el rango (1 a 9) y que este permitido (exista en la lista de
candidatos). También se incluirá un botón para reiniciar (limpiar) el
tablero.
Construyendo la Interfaz
285
b. Anchor: Top, Right
c. BackColor: Bisque
d. Location: 625, 139
e. Size: 162, 453
6. Dentro del panel anterior inserte un Label, Cambie las siguientes propiedades:
a. BackColor: Bisque
b. Font: Microsot Sans Serif, 12pt, style=Bold
c. ForeColor: Brown
d. Location: 17, 7
e. Text: Pila de Tableros
7. Dentro del panel anterior inserte un Button, Cambie las siguientes propiedades:
a. Name: btnGuardar
b. BackColor: MistyRose
c. Font: Microsot Sans Serif, 12pt, style=Bold
d. ForeColor: Brown
e. Location: 25, 28
f. Size: 113, 32
g. Text: Guardar
8. Dentro del panel anterior inserte otro Button, Cambie las siguientes propiedades:
a. Name: btnCargar
b. BackColor: MistyRose
c. Font: Microsot Sans Serif, 12pt, style=Bold
d. ForeColor: Brown
e. Location: 26, 97
f. Size: 113, 32
g. Text: Cargar
9. Dentro del panel anterior inserte otro Button, Cambie las siguientes propiedades:
a. Name: btnLimpiar
b. BackColor: MistyRose
c. Font: Microsot Sans Serif, 12pt, style=Bold
d. ForeColor: Brown
e. Location: 25, 94
f. Size: 113, 32
g. Text: Limpiar
286
Observe que aún no aparecen las 81 cajas de texto ni sus etiquetas, ni la visualización
de la pila. Es muy engorroso construirlas manualmente, en tiempo de diseño ,
especialmente para poder identificar cada una en el código. Por ello se crearan en
tiempo de ejecución , es decir, con código.
Diseñando el código
• Tendremos una matriz para almacenar el valor de cada casilla, así como la columna,
fila y caja a la que pertenece, la definiremos como sudo[81,4]. En cada fila (81) los
datos de cada casilla así: Columna 0: número de columna de la casilla, columna 1:
número de fila, columna 2: número de caja y columna 3: valor. Nota: posiblemente
no sea estrictamente necesario, pero facilitara mucho la codificación.
• Declararemos una constante para controlar el máximo de tableros guardados en la
pila: MAX_MEMORIA.
• Declararemos tres listas que contendrán: los objetos TextBox (casillas), Label
(etiquetas con los números candidatos) y Panel (para indicación visual del uso de la
pila). Se llamaran txtLista, lblLista y pnlLista, respectivamente.
• Por último, tendremos una propiedad de tipo booleano, para controlar el ingreso al
manejador del evento TextChanged. Este evento será invocado cada que cambie
el contenido de una casilla, tanto al momento de instanciar los objetos TextBox, como
287
cuando se revise el tablero o cuando se cargue uno desde la pila, produciendo
demora en el proceso y posiblemente algunos efectos colaterales indeseados. Lo
llamaremos bPermiso y será desactivado por los métodos que hagan cambios en
las cajas de texto (casillas).
Analizando con mayor profundidad, dspues de revisar las acciones a realizar, crearemos
otros métodos auxiliares más especializados, así:
288
Numero de Columna = Numero de Fila =
Numero de Casilla RESIDUO(Casilla/9) entero(casilla/9)
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 0 0 0 0 0 0 0 0 0
9 10 11 12 13 14 15 16 17 0 1 2 3 4 5 6 7 8 1 1 1 1 1 1 1 1 1
18 19 20 21 22 23 24 25 26 0 1 2 3 4 5 6 7 8 2 2 2 2 2 2 2 2 2
27 28 29 30 31 32 33 34 35 0 1 2 3 4 5 6 7 8 3 3 3 3 3 3 3 3 3
36 37 38 39 40 41 42 43 44 0 1 2 3 4 5 6 7 8 4 4 4 4 4 4 4 4 4
45 46 47 48 49 50 51 52 53 0 1 2 3 4 5 6 7 8 5 5 5 5 5 5 5 5 5
54 55 56 57 58 59 60 61 62 0 1 2 3 4 5 6 7 8 6 6 6 6 6 6 6 6 6
63 64 65 66 67 68 69 70 71 0 1 2 3 4 5 6 7 8 7 7 7 7 7 7 7 7 7
72 73 74 75 76 77 78 79 80 0 1 2 3 4 5 6 7 8 8 8 8 8 8 8 8 8 8
Para encontrar la caja, primero tomamos la parte entera de la columna/3 (una caja
tiene tres columnas). Observe en la siguiente tabla, que las tres primeras cajas están
bien numeradas, pero desde la tercera hasta la quinta hay que sumarle 3 y desde la
sexta hasta la novena hay que sumarle 6. Cambiando en la formula la columna por
la fila, se obtiene algo similar:
Pero, de las dos tablas obtenemos el valor correcto al sumar el valor obtenido en el
primero con el valor del segundo multiplicado por tres.
289
0 0 0 1 1 1 2 2 2
3 3 3 4 4 4 5 5 5
3 3 3 4 4 4 5 5 5
3 3 3 4 4 4 5 5 5
6 6 6 7 7 7 8 8 8
6 6 6 7 7 7 8 8 8
6 6 6 7 7 7 8 8 8
Finalmente tendremos:
290
▪ La propiedad Width = lado
▪ Asignar la propiedad Font
▪ Hacer xPos += lado
▪ SI ((i+1) MOD 9 = 0) ENTONCES xPos = valor inicial //nueva fila
▪ Adicionar el objeto al formulario.
o reiniciarCadenas().
• TextChange(): Este es el método que se ejecuta cada que cambia el valor de una
casilla. Hay que crear un objeto interno para manipular al objeto TextBox que
dispara el evento. Conocer el número de la casilla leyendo su propiedad Tag.
Realizar las validaciones así: Verificar que la propiedad Text no este vacía;
verificar que lo ingresado sea un digito en el rango 1-9; que exista en la lista de
candidatos para actualizar las cadenas de la respectiva columna, fila y caja. Si no
cumple con las validaciones, se debe borrar y restituir las cadenas de candidatos.
Por último, recordar que se ejecutara todo lo anterior solo si está permitido
(bPermiso).
o SI (bPermiso) ENTONCES
▪ Crear objeto tb = referencia al objeto que envió el mensaje (evento)
▪ SI (longitud del texto > 0) ENTONCES
• Intentar convertir texto en valor
• SI (el número y está en el rango) ENTONCES
o i = propiedad Tag //número de la casilla
o SI (su etiqueta contiene el valor) ENTONCES
▪ Leer columna, fila, caja de sudo[i, ]
▪ Guardar el valor en sudo[i, 3]
291
▪ Actualiza las cadenas relacionadas
o SINO //No está entre los candidatos
▪ No valido, limpiar la casilla
• SINO //No está en el rango
o No valido, limpiar la casilla
▪ SINO //longitud = 0, se limpió la casilla
• i = propiedad Tag //número de la casilla
• sudo[i, 3] = 0 //valor de la casilla
• restituye la cadena de candidatos a “123456789”
• Reinicia todas las cadenas
• Revisa todas las cadenas
292
• btnGuardar_Click(): Guarda los valores de las casillas en la pila, para
recuperarlos posteriormente y cambia el color del panel visualizador
correspondiente.
Escribiendo el código
Con los elementos obtenidos hasta el momento, es mucho más fácil convertirlos al
código del lenguaje C#:
/*****************************************************************
* SUDOKU
* Asistente para la resolución de sudokus. Muestra las casillas,
* organizadas por filas, columnas y cajas; muestra, para cada
* casilla, todos los dígitos candidatos; Al ingresar un numero en
293
* una casilla verificr si es aceptado y se elimina de todos los
* candidatos de la fila, columna y caja correspondiente.
* Guarda el estado del tablero en cualquier momento y recupera
* el ultimo estado guardado
*
* Programo: MLM
* Version: 1.0
* Fecha: 06/05/2016
* Cambios:
* **************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
namespace Sudoku2
{
public partial class Form1 : Form
{
//Valor de casillas se guarda en matriz, con su posicion en el tablero
int[,] sudo = new int[81, 4]; //Col, Fil, Caja y valor de cada casilla
/// <summary>
/// Metodo constructor
/// </summary>
public Form1()
{
InitializeComponent();
//Instancia los objetos de las cajas de texto, etiquetas y los paneles
cargarMatriz(); //Numeros de columna, fila y caja para cada casilla
dibujarCajasTexto();// Asigna propiedades y Muestra las cajas de texto
dibujarPanelMemoria(); //Indicador grafico de las memorias usadas
bPermiso = true; //controla ingreso al manejador de evento
}
/// <summary>
/// Para cada casilla carga la Fila, Columna y Caja a la que pertenece
/// asigna 0 al valor, calcula a que fila, columna y caja pertenece
294
/// </summary>
private void cargarMatriz()
{
for (int i = 0; i < 81; i++)
{
sudo[i, 0] = i % 9; //columna 0: Columna de la casilla
sudo[i, 1] = i / 9; //columna 1: Fila de la casilla
sudo[i, 2] = (sudo[i, 0] / 3) + ((sudo[i, 1] / 3) * 3); //columna 2:
Caja de la casilla
sudo[i, 3] = 0; //Inicialmente cajas vacias
}
}
/// <summary>
/// Crea la coleccion de objetos (cajas) con sus propiedades
/// y asocia disparador de eventos
/// </summary>
private void dibujarCajasTexto()
{
int xPos = 10; //posicion de inicio y tamaño de las casillas
int yPos = 20;
int lado = 63;
//Colores para distinguir las cajas (cada una de 3x3 casillas)
Color color1 = Color.LightBlue;
Color color2 = Color.CadetBlue;
// Initializa las casillas (textbox y label)
for (int n = 0; n < 81; n++)
{
//Cajas de Texto
txtLista.Add(new TextBox());
txtLista[n].Tag = n; //Propiedad TAG = numero consecutivo
// Etiquetas
lblLista.Add(new Label());
lblLista[n].Left = xPos;
lblLista[n].Top = yPos + txtLista[n].Height;
lblLista[n].Width = lado;
lblLista[n].Font = new Font("Arial", 8.0f);
xPos += lado;
295
yPos += (lado); // Baja la distancia de una casilla;
}
this.Controls.Add(txtLista[n]); //Agrega objetos al formulario
this.Controls.Add(lblLista[n]);
/// <summary>
/// Coloca objetos Panel para visualizar el uso de la pila
/// </summary>
private void dibujarPanelMemoria()
{
int xPos = 51;
int yPos = pnlMemoria.Height - 30;
/// <summary>
/// Manejador del evento TextChanged (cambio del texto)
/// Valida el numero digitado, actualiza candidatos
/// Si se borra un numero, restituye en los candidatos
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void TextChange(Object sender, System.EventArgs e)
{
if (bPermiso)
{
int iValor = 0; //contendra digito ingresado en la caja
int i = 0; //contendra el numero de la caja
TextBox tb = (TextBox)sender; //Toma caja con valor digitado
if (tb.Text.Length > 0) //Evita entrar cuando se limpia
{ //Validar si es digito
bool bPrueba = Int32.TryParse(tb.Text, out iValor);
if (bPrueba && iValor > 0 && iValor < 10)
{
i = int.Parse(tb.Tag.ToString()); //Numero de caja
//Si el numero es aceptado...
if (lblLista[i].Text.Contains(tb.Text))
{
296
int col = sudo[i, 0];
int fil = sudo[i, 1];
int caja = sudo[i, 2];
sudo[i, 3] = iValor;
actualizarCadenas(i, tb.Text, col, fil, caja);
}
else //NO aceptado, lo borra
{
tb.Text = "";
}
}
else //No valido, lo borra
{
tb.Text = "";
}
}
else //Caso de Borrar casilla
{
i = int.Parse(tb.Tag.ToString()); //Numero de caja
sudo[i, 3] = 0;
lblLista[i].Text = "123456789";
reiniciarCadenas();
revisarCadenas();
}
}
}
/// <summary>
/// Elimina numero de las cadenas vecinas de una casilla
/// </summary>
/// <param name="casilla: Casilla origen de la actualizacion"></param>
/// <param name="digito: Caracter a eliminar de las cadenas"></param>
/// <param name="col: Columna de la casilla"></param>
/// <param name="fil: Fila de la casilla"></param>
/// <param name="caja: Caja de la casilla"></param>
private void actualizarCadenas(int casilla, string digito, int col, int fil,
int caja)
{
bPermiso = false;
for (int j = 0; j < 81; j++)
{
if ((sudo[j, 0] == col) || (sudo[j, 1] == fil) || (sudo[j, 2] ==
caja))
{
lblLista[j].Text = lblLista[j].Text.Replace(digito, " ");
}
}
if (txtLista[casilla].Text.Length > 0) //Si la casilla tiene un numero...
lblLista[casilla].Text = ""; //...Limpia cadena de candidatos
bPermiso = true;
}
/// <summary>
/// Elimina los numeros digitados en todos los TexBox
/// </summary>
297
private void borrarTablero()
{
for (int j = 0; j < 81; j++)
{
txtLista[j].Text = "";
}
}
/// <summary>
/// Actualiza Todos los candidatos del tablero
/// </summary>
private void revisarCadenas()
{
for (int i = 0; i < 81; i++)
{
int col = sudo[i, 0];
int fil = sudo[i, 1];
int caja = sudo[i, 2];
actualizarCadenas(i, sudo[i, 3].ToString(), col, fil, caja);
}
}
/// <summary>
/// Asigna todos los digitos a las etiquetas de candidatos
/// </summary>
private void reiniciarCadenas()
{
for (int j = 0; j < 81; j++)
{
lblLista[j].Text = "123456789";
}
}
/// <summary>
/// Boton Iniciar: Limpia textbox y reinicia valores de las etiquetas
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnIniciar_Click(object sender, EventArgs e)
{
cargarMatriz();
borrarTablero();
reiniciarCadenas();
}
/// <summary>
/// Convierte el contenido de las casillas a numeros
/// y lo lleva a la matriz de valores
/// </summary>
private void leerTablero()
{
for (int i = 0; i < 81; i++)
{
if (txtLista[i].Text.Length > 0)
{
298
sudo[i, 3] = int.Parse(txtLista[i].Text);
}
else
sudo[i, 3] = 0;
}
}
/// <summary>
/// Guarda los valores de las casillas para recuperar posteriormente
/// Visualiza en pantalla el indicador de la pila
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnGuardar_Click(object sender, EventArgs e)
{
if (pila.Count < MAX_MEMORIA)
{ //Arreglo unidemensional para extraer valores
int[] sudo1 = new int[81];
//Guarda valores del tablero actual
for (int i = 0; i < 81; i++) sudo1[i] = sudo[i, 3];
pnlLista[pila.Count].BackColor = Color.Gold; //Marca en la ventana
pila.Push(sudo1); //agrega el arreglo a la pila
}
}
/// <summary>
/// Recupera los valores guardados en el tablero
/// Visualiza en pantalla el indicador de uso de la pila
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnCargar_Click(object sender, EventArgs e)
{
if (pila.Count > 0) //Si hay algun tablero guardado
{ //Arreglo unidemensional para extraer valores
int[] sudo1 = new int[81];
sudo1 = (int[])pila.Pop(); //Extrae de la pila
//Desmarca nivel de guardados
pnlLista[pila.Count].BackColor = Color.Gainsboro;
for (int i = 0; i < 81; i++)
{
sudo[i, 3] = sudo1[i]; //Transfiere a la matriz principal
if (sudo[i, 3] > 0)
{
txtLista[i].Text = sudo[i, 3].ToString();
}
else
txtLista[i].Text = "";
}
revisarCadenas();
}
}
/// <summary>
/// Elimina todos los datos de los tableros guardados en pila
299
/// y desmarca los paneles indicadores
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLimpiar_Click(object sender, EventArgs e)
{
int i = pila.Count;
pila.Clear(); //Elimina tableros de la pila
while (--i >= 0) //desmarca indicadores
{
pnlLista[i].BackColor = Color.Gainsboro;
}
}
}
}
//*************** FIN DEL CODIGO ***********************************************
300
16. DESARROLLO DE UN JUEGO PARA WEB
El objetivo principal del presente capitulo es mostrar una metodología incremental para
el planteamiento, diseño e implementación del código necesario para el desarrollo de
pequeños juegos.
Problema 43: Juego de Sokoban: Desarrollaremos un juego desde cero, para lo que
hemos escogido el tradicional SOKOBAN, creado por Hiroyuki Imabayashi, en el que el
personaje (un trabajador de bodega) debe empujar una serie de cajas hasta colocarlas
en ciertos lugares predefinidos. Diseñaremos el mundo (la bodega con sus paredes),
marcaremos los sitios a donde deben llegar las cajas, colocaremos las cajas en su
posición inicial y desarrollaremos la lógica necesaria para controlar los movimientos.
• El mundo del juego es una cuadrícula donde, en cada casilla solo habrá un
elemento: Pared, caja, el piso o el personaje.
• Las paredes, por supuesto, no deben ser atravesadas.
• Hay cierta cantidad fija de cajas a mover.
• Hay unos lugares marcados en el piso, que son el destino final de las cajas.
• El personaje (llamémoslo Soko) solo puede EMPUJAR una caja a la vez.
• Soko NO PUEDE TIRAR de las cajas.
• El juego termina cuando todas las cajas están en las posiciones marcadas.
301
El mundo del juego contendrá paredes, el piso, marcas de posición final de las cajas, las
cajas y a Soko, quien deberá representarse en las diferentes orientaciones (mirando a la
derecha, arriba, etc.).
Por lo tanto, debemos construir los siguientes Sprites (pequeño mapa de bits que se
dibujan en la pantalla), los que incluiremos en un solo archivo (una Hoja de Sprites):
• Soko
• Caja en posición libre
• Caja en posición final
• Bloque de pared
• Piso
• Piso con marca de destino
Dado que Soko tiene una forma diferente a la rectangular (recuerde que es un juego
2D y que el punto de vista es el superior, estaremos
observando hacia abajo), es necesario que los pixeles
alrededor del personaje sean transparentes, es decir, hay que
controlar el canal alfa de la imagen y no lo podemos hacer
con Paint de Microsoft. Usaremos un editor libre, por ejemplo
Paint.NET, definimos cuadros de 20 x 20 pixeles, por lo que
la hoja de sprites tendrá un tamaño total de 120 x 20px.
En la figura anterior, la caja en posición libre, observe los pequeños rectángulos en las
dos últimas columnas y filas, significa que ahí no hay color (máxima transparencia), por
lo que al dibujarlo, tomará el color del fondo.
302
Las siguientes figuras son del piso, la última con la marca donde deberá colocarse una
caja. Note que estas no tienen pixeles transparentes.
Por último, nuestro personaje, esta dibujado en posición mirando a la derecha. Note el
área a su alrededor, totalmente transparente, para poder traslucir el piso bajo él.
Así se ven los diferentes Spritres en tamaño normal, donde hay pixeles transparentes se
observa el color blanco del fondo:
303
En la imagen anterior, las coordenadas iniciales (x,y) de cada sprite son:
Soko: 0,0;
Bloque de pared: 20,0
Piso: 40,0;
Piso con marca de destino: 60,0;
Caja en posición libre: 80,0;
Caja en posición destino: 100,0;
Cree una carpeta denominada “recursos” dentro de su carpeta del juego. Guarde ahí el
archivo “soko.png” con los sprites en el orden como se observan en la figura anterior.
Antes de empezar, recuerde que HTML5 se fundamenta en tres pilares: HTML (El
contenido), CSS (la presentación) y JavaScript (el comportamiento). También podría
asociar lo anterior al paradigma “Modelo-Vista-Controlador”
El archivo HTML de inicio, debe contener una etiqueta “canvas” para poder mostrar los
objetos graficos y, por supuesto, los enlaces al archivo de formato (CSS) y al de la lógica
del juego (js).
Vamos ahora a ver un poco de código: Cree una carpeta nueva para su proyecto,
denomínela Sokoban. Con el editor “Sublime Text” (o el de su preferencia), cree un
archivo HTML, denomínelo index.html e inserte el siguiente código:
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Sokoban</title>
<link rel="stylesheet" href="soko.css">
<script src = "soko.js"></script>
</head>
<body>
<div id="contenedor1">
<h1 id="titulo">SOKOBAN</h1>
<canvas id="canvas" width= 450 height=450>Canvas no soportado</canvas>
304
</div>
</body>
</html>
#contenedor1{
background-color: #D38BFE;
height: 600px;
margin: auto;
padding: 5px;
width: 550px;
}
h1{
font-size: 2em;
text-align: center;
}
canvas{
border: solid 2px #00c;
margin-left: 8%;
}
305
De ahora en adelante nos centraremos en el contenido del archivo soko.js. La lógica
general de un juego sigue el siguiente esquema:
Para empezar con el código de soko.js prepararemos una especie de plantilla con una
función recursiva que controla la parte del ciclo, llamando a los métodos actualizar() y
dibujar() y el tiempo en milisegundos entre cada ciclo, dicha función la denominamos
run().
306
Esta es una plantilla que s epuede utilizar como base para el desarrollo de diferentes
juegos 2D. Escriba el siguiente código en el archivo soko.js:
/********************************************************************
* Plantilla de Código JavaScript para un juego 2D *
* Autor: Marco León Mora *
* Versión: 1.0 *
* Fecha: Marzo/2013 *
********************************************************************/
307
/***************** INICIALIZACION EL PROGRAMA *******************/
function iniciar(){
canvas=document.getElementById('canvas');
ctx=canvas.getContext('2d');
cargar();
run();
}
Al iniciar el juego, se debe dibujar el mundo con todos los componentes en su posición
inicial, además, en todo momento se debe conocer la posición de cada objeto, por lo que
es necesario algún tipo de estructura de datos que contenga esa información.
Nuestro mundo será un rectángulo dividido en celdas y en cada una estará una pared,
el piso (libre o con marca de destino), una caja (en posición libre o final) o Soko.
Usaremos un tamaño fijo de 20 x 20 celdas, para poder dibujar escenarios de diferentes
308
tamaños, es decir, diferentes problemas a resolver por Soko (aunque, en primer lugar
dibujaremos solo un escenario). Bueno, así es como se debe ver, pero, ¿cómo
representar esa información en la memoria del computador?
Para nuestra primera práctica, vamos a representar el siguiente mundo, observe las
marcas en el piso donde deben llegar las cajas, al lado derecho:
309
Ahora, pasemos esa información a la matriz, observe que nuestro mundo es de 19
columnas x 11 filas, lo representaremos desde la izquierda superior de la matriz de 20 x
20:
Nuestro primer mundo deja libres las filas F11 a F19 y la columna C19.
Es necesario resolver un pequeño problema: Si movemos una caja desde una celda de
piso con marca de destino (5) o en posición libre (4), ¿Cómo saberlo para dibujar el piso
310
correspondiente que estaría “bajo” la caja (3: Piso con marca de destino o 2: Piso libre),
después de retirar la caja?
Y, ¿en el caso de Soko? Para usar el mismo truco, deberíamos restar 4, pero para que
funcione es necesario definir un nuevo valor:
7 – Soko sobre Piso con marca de destino.
Entonces,
Soko sobre piso libre: (6 – 4 = 2) 2: Piso libre
Soko sobre piso con marca de destino: (7 – 4 = 3) 3: Piso con marca de destino.
En primer lugar, el Mundo es el escenario que visualizara las paredes, el piso y las
posiciones de destino de las cajas. El mundo es una matriz de 20 x 20 casillas, en cada
casilla ira uno de los valores definidos anteriormente, dichos valores los guardaremos en
valorCasilla.
311
Modifique la plantilla con el código js así:
/********************************************************************
* SOKOBAN. *
* El que el personaje debe empujar una serie de cajas *
* hasta colocarlas en ciertos lugares predefinidos. *
* Autor: Marco León Mora *
* Versión: 1.0 juego básico con un mundo *
* Fecha: Abril/2015 *
********************************************************************/
312
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
Ejecute el código, si todo va bien, debe aparecer una ventana de mensaje, este mensaje
se usa solo para fines de depuración (retírelo o coméntelo).
313
La primera visión del mundo del juego
Vamos a dibujar el mundo representado en la matriz. Para empezar, hay que definir
algunos valores iniciales. En el CSS se definió el canvas de 450 X 450 pixeles. Podemos
dibujar los frames a escala 1:1 (20px), entonces para un mundo de 20 casillas
necesitaremos un área de dibujo de 400 X 400 pixeles, sobran 50. Podemos empezar
haciendo las posiciones de inicio iniX = iniY = 25px. y delta = 20. Agregue el código:
case valorCasilla.caja:
case valorCasilla.cajaDestino:
//Dibuja un piso y la caja
ctx.drawImage(img1,(obj-2)*wFrame,0,wFrame,hFrame, X, Y, dX, dY);
ctx.drawImage(img1, obj*wFrame, 0, wFrame, hFrame, X, Y, dX, dY);
break;
case valorCasilla.soko:
case valorCasilla.sokoDestino:
//Dibuja un piso y a Soko
ctx.drawImage(img1,(obj-4)*wFrame,0,wFrame,hFrame, X, Y, dX, dY);
314
ctx.drawImage(img1, 0, 0, wFrame, hFrame, X, Y, dX, dY);
break;
default:
break; //Caso de casilla vacia
}
}
}
}
En el método anterior, se definieron miembros para indicar la posición inicial del dibujo
en la pantalla y el tamaño de cada Sprite y poder computar la posición de cada celda.
La estructura Switch permite dibujar de manera diferente según el valor de la matriz. Para
el caso de pared, piso o piso destino, se dibuja directamente el sprite. Para el caso de
las cajas y de Soko, se dibuja primero el piso correspondiente para lograr el color
adecuado en las zonas transparentes de los sprites.
Ahora, ejecute el programa. Si no tuvo errores de código, debería ver algo como la
siguiente figura:
315
Para entender mejor el código, se sugieren algunas pruebas:
• Cambie valores en la matriz “mundo”, ejecute.
• Intercambie los índices en la siguiente instrucción,
var obj = mundo[j][i] por var obj = mundo[i][j]. Ejecute. ¿Qué pasa?
• Intente dibujar el mundo en el centro del canvas. ¿Cómo calcularía para que el
mundo SIEMPRE se dibuje en la mitad de la pantalla, sin importar su tamaño?
NOTA: Como habrá podido observar al cambiar los índices de la matriz la imagen se
transpone, pues el primer índice señala el número de la fila, en la imagen siguiente sería
el eje Y, el segundo índice señala las columnas, es decir, la posición de un elemento en
determinada fila (eje X). Por lo tanto es necesario que, al referirnos a cualquier elemento
316
del “mundo”, primero refiramos la Y, luego la X, así: “mundo [Y, X]”. Aunque, como dijo
Einstein, “todo es relativo”…
Controlando el movimiento
Soko puede moverse solo a una casilla libre inmediatamente al lado de su posición
actual, es decir, puede caminar por cualquier espacio libre, yendo de casilla en casilla. O
empujar una caja, siempre y cuando la casilla delante de la caja este libre.
Por lo tanto, antes de mover, debemos realizar los siguientes pasos (algoritmo):
317
Para saber a dónde se quiere mover, primero debemos conocer en donde se encuentra
Soko y la dirección del movimiento, o la caja a mover. Junto a la declaración de
miembros (PROPIEDADES GLOBALES), agregue:
Puesto que hemos declarado variables para la posición de Soko (), codifiquemos de una
vez un método que busque en qué posición se encuentra. Este método se ejecutara al
inicializar:
318
La lógica de control actualiza el mundo según las teclas pulsadas, por lo que se debe
codificar en el método actualizar(). Pero, antes de poder detectar las teclas pulsadas,
hay que agregar un manejador de eventos para el teclado, después del evento “load”:
Y el método que se ejecutara al pulsar una tecla, al final del código. El valor de la tecla
pulsada se guarda en la propiedad anterior:
Si se ha pulsado una nueva tecla, entonces, se calcula la casilla a la que iría Soko, se
verifica si está libre para moverlo.
Si no está libre, se verifica si existe una caja en esa posición, en caso de existir, se
verifica si la siguiente posición a la caja está libre, en cuyo caso se moverán la caja y
Soko.
319
case 37: //Izquierda
dirSoko.X--;
cambiar = true
break;
case 38: //Arriba
dirSoko.Y--;
cambiar = true;
break;
case 39: //Derecha
dirSoko.X++;
cambiar = true;
break;
case 40: //Abajo
dirSoko.Y++;
cambiar = true;
break;
default:
break; //Otra tecla o ninguna
}
valorTecla = 0; //Borra para no usar de nuevo
En el código anterior se llaman los siguientes métodos nuevos, que se construyen para
evitar bloques de código muy extenso (legibilidad) y/o para poder reusarlos:
320
posSigueSoko.X = posActualSoko.X+ dirSoko.X;
posSigueSoko.Y = posActualSoko.Y+ dirSoko.Y;
//Revisa limites y reajusta
if(posSigueSoko.X == tamMundo) posSigueSoko.X--;
if(posSigueSoko.X == -1) posSigueSoko.X++;
if(posSigueSoko.Y == tamMundo) posSigueSoko.Y--;
if(posSigueSoko.Y == -1) posSigueSoko.Y++;
}
casillaLibre(pos) devuelve “true” o “false” según esté o no esté libre la posición pos:
321
mundo[posSigueSoko.Y][posSigueSoko.X] =
mundo[posSigueSoko.Y][posSigueSoko.X] -2;
//La coloca en la siguiente posicion
mundo[posSigueCaja.Y][posSigueCaja.X] =
mundo[posSigueCaja.Y][posSigueCaja.X] + 2;
}
Si todo está bien, podrá mover las cajas solo si hay espacio libre y podrá terminar el
juego si piensa un poco la secuencia correcta de movimientos.
Para que Soko “mire” en la posición en que se mueve, usaremos la solución fácil, agregar
tres frames de Soko, así:
322
Observe que si va a la derecha debe dibujar el frame 0, arriba dibuja el 6, a la izquierda
el 7 y abajo el 8. Agreguemos una enumeración “miraSoko”, y una propiedad que
almacenara el valor adecuado en cada ciclo. Modifique el código así:
Y, al pulsar las teclas, actualizaremos la propiedad mSoko, de tal manera que, aún si no
se puede mover, Soko girará en la dirección de la tecla pulsada. Agregue las
instrucciones en el método actualizar(), dentro del switch:
Para dibujar a Soko girando su Sprite, es necesario cambiar, en el método dibujar(), así:
case valorCasilla.soko:
case valorCasilla.sokoDestino:
//Dibuja un piso y a Soko
ctx.drawImage(img1, (obj - 4) * wFrame, 0, wFrame, hFrame, X, Y, dX, dY);
323
ctx.drawImage(img1, mSoko * wFrame, 0, wFrame, hFrame, X, Y, dX, dY);
break;
Ahora, Soko gira en la misma casilla y también se mueve girando a la posición correcta.
¿Cuándo se termina el juego? Cuando TODAS las cajas se encuentren en las posiciones
marcadas. Entonces, al inicio del juego y al final de cada ciclo de actualización, hay que
contar cuantas cajas hay y cuantas se encuentran en la posición final.
Agregar un método para contar el total de cajas. Como también necesitamos un método
para contar las cajas en posición final, haremos uno solo con un parámetro que indique
cuales cajas contar:
324
En cada ciclo (método actualizar()), por dentro y al final de la estructura if(cambiar):
//Contar cajas en posicion final y verificar si ya termino
totCajasDestino = cuentaCajas(valorCasilla.cajaDestino);
if(totCajasDestino == totalCajas){
juegoTerminado = true;
}
//Contadores de movimiento
var pasosSoko = 0;
var movimCajas = 0;
Los cuales se actualizan cuando se mueva Soko o una caja (al final del método
mueveSoko():
//Cuenta pasos
pasosSoko++;
Escribir texto
Vamos a agregar el código necesario para mostrar los pasos de Soko y el número de
movimientos de cajas en la pantalla.
En el archivo HTML, dentro del div <contenedor1> y al final, agregar elementos <h3>
necesarios para mostrar la información, dentro de otro div:
325
<div>
<h3 id="pasosSoko">Pasos Soko: </h3>
<h3 id="pasosCaja">movimientos: </h3>
</div>
En el CSS agregar propiedades a los <h3>, también modificar márgenes del canvas:
#contenedor1 h3{
display: inline-block;
margin-left: 8%;
}
- Al inicio, declarar las propiedades a relacionar con los objetos <h3> del DOM:
function cargar(){
//Elementos del DOM
verPasosSoko = document.getElementById('pasosSoko');
verPasosCaja = document.getElementById('pasosCaja');
326
AMBIENTANDO CON SONIDOS
Busquemos en la Web algunos efectos de sonido para utilizarlos cuando Soko se mueve,
cuando algún objeto le impide hacerlo y cuando se gane el juego. Escoja los que desee
(cámbieles el nombre para identificarlos) y añádalos a su proyecto dentro de la carpeta
recursos.
327
diversos navegadores. Use una aplicación web para hacer la conversión, por ejemplo
http://www.coolutils.com/Online/Audio-Converter/, conviértalos a los dos formatos más
compatibles y agréguelos a la carpeta:
Ya tenemos los archivos de sonido listos, ahora vamos a hacer que suenen:
- En el HTML, agregue los elementos necesarios, al final y dentro de un nuevo
<div>:
<div id="sonidos">
<audio id="audioPaso">
<source src="Recursos/paso.ogg" type="audio/ogg"></source>
<source src="Recursos/paso.mp3" type="audio/mpeg"></source>
</audio>
<audio id="audioCaja">
<source src="Recursos/Caja.ogg" type="audio/ogg"></source>
<source src="Recursos/Caja.mp3" type="audio/mpeg"></source>
</audio>
<audio id="audioNoMueve">
<source src="Recursos/nomueve.ogg" type="audio/ogg"></source>
<source src="Recursos/nomueve.mp3" type="audio/mpeg"></source>
</audio>
<audio id="audioAplausos">
<source src="Recursos/aplausos.ogg" type="audio/ogg"></source>
<source src="Recursos/aplausos.mp3" type="audio/mpeg"></source>
</audio>
</div>
</body>
</html>
Observe que se han incluido dos etiquetas <source> con los formatos
alternativos, si desea, agregue también el formato wav.
- En el CSS agregue:
#sonidos{
display: none;
}
328
- En el método cargar(),
audioPaso = document.getElementById("audioPaso");
audioCaja = document.getElementById("audioCaja");
audioNoMueve = document.getElementById("audioNoMueve");
audioAplausos = document.getElementById("audioAplausos");
329
MULTIPLES MUNDOS
Bueno, hasta ahora el juego es interesante solo para usted como programador, desde el
punto de vista de su desarrollo, pero si desea hacerlo interesante para otros jugadores,
deberá crear varios escenarios, por grado de dificultad. Lo más sencillo es “tomarlos
prestados” de la Web y codificarlos de tal manera que se carguen correctamente.
La siguiente es una lista de funcionalidades que podríamos agregar a nuestro juego para
poder jugar varios escenarios diferentes (niveles):
Resolvamos el primer problema: crear un archivo con los diferentes niveles: Para crearlo,
podemos usar un editor de texto como “Bloc de Notas” y crear un archivo “plano” con la
información codificada de cierta manera que podamos interpretar desde el juego.
En el código se “creo” el primer mundo en una matriz de 20x20. Podemos definir que
cada línea de nuestro archivo de datos, con los valores de casilla separados por comas,
represente una fila completa de la matriz y existirán tantas líneas como se requieran para
representar cada nivel, sin tener que escribir las filas vacías.
Cada nivel podría ser identificado por el signo # seguido del número, por ejemplo: #1,
sería nuestro primer nivel. Podríamos insertar comentarios, iniciados con algún signo
especial, digamos el punto y coma. Ejemplo:
; SOKOBAN
; Archivo con la descripción de varios niveles de juego
#1
0,0,0,0,1,1,1,1,1
0,0,0,0,1,2,2,2,1
0,0,0,0,1,4,2,2,1
0,0,1,1,1,2,2,4,1,1
0,0,1,2,2,4,2,4,2,1
1,1,1,2,1,2,1,1,2,1,0,0,0,1,1,1,1,1,1
1,2,2,2,1,2,1,1,2,1,1,1,1,1,2,2,3,3,1
330
1,2,4,2,2,4,2,2,2,2,2,2,2,2,2,2,3,3,1
1,1,1,1,1,2,1,1,1,2,1,6,1,1,2,2,3,3,1
0,0,0,0,1,2,2,2,2,2,1,1,1,1,1,1,1,1,1
0,0,0,0,1,1,1,1,1,1,1
;Este es el segundo nivel
#2
Observe que se han eliminado los ceros a la derecha (no a la izquierda), pues no aportan
información adicional.
Para cargar otros mundos, busque en internet un juego de Sokoban y codifique varios
niveles. Podría usar una hoja de Excel para representarlo con más claridad, después
copie la matriz y péguela en su Bloc de Notas, reemplace los tabuladores por comas
(Edición – Reemplazar…) y agréguele la línea de identificación y los comentarios que
desee. El carácter final debe ser un “#”, para indicar que termina la descripción del ultimo
mundo.
#2
1,1,1,1,1,1,1,1,1,1,1,1
1,3,3,2,2,1,2,2,2,2,2,1,1,1
1,3,3,2,2,1,2,4,2,2,4,2,2,1
1,3,3,2,2,1,4,1,1,1,1,2,2,1
1,3,3,2,2,2,2,6,2,1,1,2,2,1
1,3,3,2,2,1,2,1,2,2,4,2,1,1
1,1,1,1,1,1,2,1,1,4,2,4,2,1
0,0,1,2,4,2,2,4,2,4,2,4,2,1
0,0,1,2,2,2,2,1,2,2,2,2,2,1
0,0,1,1,1,1,1,1,1,1,1,1,1,1
#3
0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
0,0,0,0,0,0,0,0,1,2,2,2,2,2,6,1
0,0,0,0,0,0,0,0,1,2,4,1,4,2,1,1
0,0,0,0,0,0,0,0,1,2,4,2,2,4,1
0,0,0,0,0,0,0,0,1,1,4,2,4,2,1
1,1,1,1,1,1,1,1,1,2,4,2,1,2,1,1,1
331
1,3,3,3,3,2,2,1,1,2,4,2,2,4,2,2,1
1,1,3,3,3,2,2,2,2,4,2,2,4,2,2,2,1
1,3,3,3,3,2,2,1,1,1,1,1,1,1,1,1,1
1,1,1,1,1,1,1,1
#4
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
0,0,0,0,0,0,0,0,0,0,0,1,2,2,3,3,3,3,1
1,1,1,1,1,1,1,1,1,1,1,1,2,2,3,3,3,3,1
1,2,2,2,2,1,2,2,4,2,4,2,2,2,3,3,3,3,1
1,2,4,4,4,1,4,2,2,4,2,1,2,2,3,3,3,3,1
1,2,2,4,2,2,2,2,2,4,2,1,2,2,3,3,3,3,1
1,2,4,4,2,1,4,2,4,2,4,1,1,1,1,1,1,1,1
1,2,2,4,2,1,2,2,2,2,2,1
1,1,2,1,1,1,1,1,1,1,1,1
1,2,2,2,2,1,2,2,2,2,1
1,2,2,2,2,2,4,2,2,2,1,1
1,2,2,4,4,1,4,4,2,2,6,1
1,2,2,2,2,1,2,2,2,2,1,1
1,1,1,1,1,1,1,1,1,1,1
#5
0,0,0,0,0,0,0,0,1,1,1,1,1
0,0,0,0,0,0,0,0,1,2,2,2,1,1,1,1,1
0,0,0,0,0,0,0,0,1,2,1,4,1,1,2,2,1
0,0,0,0,0,0,0,0,1,2,2,2,2,2,4,2,1
1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,2,1
1,3,3,3,3,2,2,1,1,2,4,2,2,4,1,1,1
1,3,3,3,3,2,2,2,2,4,2,4,4,2,1,1
1,3,3,3,3,2,2,1,1,4,2,2,4,2,6,1
1,1,1,1,1,1,1,1,1,2,2,4,2,2,1,1
0,0,0,0,0,0,0,0,1,2,4,2,4,2,2,1
0,0,0,0,0,0,0,0,1,1,1,2,1,1,2,1
0,0,0,0,0,0,0,0,0,0,1,2,2,2,2,1
0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1
#6
1,1,1,1,1,1,0,0,1,1,1
1,3,3,2,2,1,0,1,1,6,1,1
1,3,3,2,2,1,1,1,2,2,2,1
1,3,3,2,2,2,2,2,4,4,2,1
1,3,3,2,2,1,2,1,2,4,2,1
1,3,3,1,1,1,2,1,2,4,2,1
1,1,1,1,2,4,2,1,4,2,2,1
0,0,0,1,2,2,4,1,2,4,2,1
0,0,0,1,2,4,2,2,4,2,2,1
0,0,0,1,2,2,1,1,2,2,2,1
0,0,0,1,1,1,1,1,1,1,1,1
#7
0,0,0,0,0,0,0,1,1,1,1,1
0,1,1,1,1,1,1,1,2,2,2,1,1
1,1,2,1,2,6,1,1,2,4,4,2,1
1,2,2,2,2,4,2,2,2,2,2,2,1
1,2,2,4,2,2,1,1,1,2,2,2,1
1,1,1,2,1,1,1,1,1,4,1,1,1
332
1,2,4,2,2,1,1,1,2,3,3,1
1,2,4,2,4,2,4,2,3,3,3,1
1,2,2,2,2,1,1,1,3,3,3,1
1,2,4,4,2,1,0,1,3,3,3,1
1,2,2,1,1,1,0,1,1,1,1,1
1,1,1,1
#8
0,0,1,1,1,1
0,0,1,2,2,1,1,1,1,1,1,1,1,1,1,1
0,0,1,2,2,2,2,4,2,2,2,4,2,4,2,1
0,0,1,2,4,1,2,4,2,1,2,2,4,2,2,1
0,0,1,2,2,4,2,4,2,2,1,2,2,2,2,1
1,1,1,2,4,1,2,1,2,2,1,1,1,1,2,1
1,6,1,4,2,4,2,4,2,2,1,1,2,2,2,1
1,2,2,2,2,4,2,1,4,1,2,2,2,1,2,1
1,2,2,2,4,2,2,2,2,4,2,4,2,4,2,1
1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1
0,0,1,2,2,2,2,2,2,1
0,0,1,2,2,2,2,2,2,1
0,0,1,3,3,3,3,3,3,1
0,0,1,3,3,3,3,3,3,1
0,0,1,3,3,3,3,3,3,1
0,0,1,1,1,1,1,1,1,1
#9
0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1
0,0,0,0,0,0,0,0,0,0,1,2,2,3,3,3,1
0,0,0,0,0,0,1,1,1,1,1,2,2,3,3,3,1
0,0,0,0,0,0,1,2,2,2,2,2,2,3,2,3,1
0,0,0,0,0,0,1,2,2,1,1,2,2,3,3,3,1
0,0,0,0,0,0,1,1,2,1,1,2,2,3,3,3,1
0,0,0,0,0,1,1,1,2,1,1,1,1,1,1,1,1
0,0,0,0,0,1,2,4,4,4,4,1,1
0,1,1,1,1,1,2,2,4,2,4,2,1,1,1,1,1
1,1,2,2,2,1,4,2,4,2,2,2,1,2,2,2,1
1,6,2,4,2,2,4,2,2,2,2,4,2,2,4,2,1
1,1,1,1,1,1,2,4,4,2,4,2,1,1,1,1,1
0,0,0,0,0,1,2,2,2,2,2,2,1
0,0,0,0,0,1,1,1,1,1,1,1,1
#10
0,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1
1,1,6,1,1,1,1,2,2,2,2,2,2,2,1,2,2,2,1
1,2,4,4,2,2,2,4,4,2,2,4,2,4,2,3,3,3,1
1,2,2,4,4,4,1,2,2,2,2,4,2,2,1,3,3,3,1
1,2,4,2,2,2,1,2,4,4,2,4,4,2,1,3,3,3,1
1,1,1,2,2,2,1,2,2,4,2,2,2,2,1,3,3,3,1
1,2,2,2,2,2,1,2,4,2,4,2,4,2,1,3,3,3,1
1,2,2,2,2,1,1,1,1,1,1,2,1,1,1,3,3,3,1
1,1,2,1,2,2,1,2,2,4,2,4,2,2,1,3,3,3,1
1,2,2,1,1,2,1,2,4,4,2,4,2,4,1,1,3,3,1
1,2,3,3,1,2,1,2,2,4,2,2,2,2,2,2,1,3,1
1,2,3,3,1,2,1,2,4,4,4,2,4,4,4,2,1,3,1
1,1,1,1,1,2,1,2,2,2,2,2,2,2,1,2,1,3,1
0,0,0,0,1,2,1,1,1,1,1,1,1,1,1,2,1,3,1
333
0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,1,3,1
0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
#
Primero, manipularemos el HTML para crear las etiquetas necesarias. Dentro del <div
contenendor1> y después del div con las etiquetas <h3> :
<div class="botones">
<input type="file" id="archivo" size= "1" class="input-file"/>
Cargar Archivo
</div>
<div class="botones"><a onclick="mundoAnterior()">anterior</a ></div>
<div class="botones"><a onclick="mundoSiguiente()">siguiente</a></div>
Y el CSS para darles formato (usted puede mejorar la presentación, cambie la altura del
contenedor, etc.):
.botones a{
text-decoration: none;
font-size: 1.1em;
color: white;
margin-left: 10px;
}
.botones .input-file {
margin: 0;
padding: 0;
outline: 0;
opacity: 0;
334
filter: alpha(opacity=0);
position: absolute;
right: -100px;
top: 0px;
cursor: pointer;
Pruebe los tres botones, el primero debe mostrar la ventana modal “Abrir Archivo”.
- Agregue una propiedad para guardar los mundos y otra (booleana) para indicar si
ya se cargó un archivo (ambas públicas):
335
/******************** FIN DEL CODIGO *******************************/
336
if (archivoCargado && (nivelActivo > 1)) {
nivelActivo--;
leerNivel();
}
else {
alert("No existe un nivel menor al #" + nivelActivo);
}
}
Y un método para leer el nivel seleccionado al que llamaremos “leerNivel()”, que deberá
recorrer la lista buscando el número de nivel a cargar y después, leer línea a línea,
separar los números y cargarlos en la matriz “mundo”:
En primer lugar se limpia toda la matriz para eliminar información del nivel anterior, el
ciclo principal repite hasta encontrar otro indicador de nivel “#” o hasta el final del arreglo
de origen.
337
Las instrucciones
Crean una nueva lista, cada una con un número leído del archivo, el método para separar
es “Split” y el criterio de separación es la coma. Luego se recorre esta lista y se van
cargando los números, al final del bucle es necesario incrementar los índices.
Para terminar, hay que encontrar la nueva posición de Soko y reiniciar contadores, se
usa un nuevo método reiniciar().
338
Guarde y ejecute. ¿Qué pasa? ¿Por qué se sobreponen los diferentes mundos?
CONCLUSIÓN
Hemos diseñado y desarrollado, desde cero, todo el código para hacer un juego
funcional e interesante. El objetivo es mostrar una metodología de análisis y desarrollo
de pequeños juegos, al desglosar el problema general en otros problemas, cada vez más
pequeños y focalizados, hasta tener una solución directa que pueda ser aplicada para
desarrollar todo el código.
339
340
ANEXO 1. OPERADORES Y FUNCIONES DEFINIDAS EN PYTHON
operadores
( tomado de
https://www.fing.edu.uy/inco/cursos/fpr/wiki/index.php/Operadores_en_Python )
341
~ not r = ~3 # r es -4
<< Desplazamiento a la izquierda r = 3 << 1 # r es 6
>> Desplazamiento a la derecha r = 3 >> 1 # r es 1
Operadores de asignación
Operador Descripción Ejemplo
= Asignación simple x=y
+= Suma x += y equivale a x = x + y
-= Resta x -= y equivale a x = x - y
*= Multiplicación x *= y equivale a x = x * y
**= Exponente x ** = y equivale a x = x ** y
/= División x /= y equivale a x = x / y
//= División entera x //= y equivale a x = x // y
%= Residuo de división x %= y equivale a x = x % y
Fuertemente tipado: Existen operaciones que no están permitidas entre tipos que no
sean compatibles.
Los tipos son clases: En Python todos sus elementos son objetos y los datos una vez
identificados, se convierten objetos instanciados del tipo al que pertenecen.
Tipos numéricos:
Tipo Descripción Ejemplo
Python identifica a los número enteros como un tipo
de dato el cual puede ser expresado de varias
maneras.
Números enteros Decimal 24
(int)
Binario 0b010011
Hexadecimal 0x18
Octal 30
342
-45356
6.32 + 45j
Números 0.117j
Los objetos de tipo complex corresponden al
complejos
conjunto de los números complejos. (2 + 0j)
(complex).
1j
Tipos inmutables.
Los objetos de tipo inmutable son aquellos cuya estructura no puede modificarse a
menos que sean eliminados. Por sus características, son inmutables los tipos:
• int
• float
• bool
• complex
• str
Operadores de identidad.
Los operadores is e is not evalúan si un identificador se refiere exactamente al mismo
objeto o pertenece a un tipo.
343
Operador Evalúa
is a is b Equivale a id(a) == id(b)
is not a is not b Equivale a id(a) != id(b)
344
ANEXO 2. OPERACIONES LOGICAS Y ALGEBRA BOOLEANA
Como otro elemento para su “caja de herramientas” se presenta una compilación de las
operaciones booleanas y las tablas de verdad para las operaciones básicas del
algebra de Boole y de algunos de sus axiomas y leyes. Es tan importante conocerlas
como que siempre se usan en las pruebas lógicas de las estructuras de decisión y
cíclicas.
Las siguientes Tablas de Verdad muestran el resultado de las operaciones para las
distintas combinaciones posibles de los valores de operandos:
El operador O (OR) se lee “Si A o B”, es decir, si alguno de los dos es verdad, entonces
el enunciado es verdad. En esta tabla se presentan cuatro combinaciones posibles (2 2),
y el resultado de la operación es 1 cuando al menos uno de los operandos es 1.
345
El operador Y (AND) se lee “Si A y B”, es decir, solo si los dos son verdad, entonces el
enunciado es verdad.
Atención, lea los símbolos como se indica en el párrafo anterior, por ejemplo a + 0 = a,
NO significa (ni se lee como) “a más cero” sino “a o cero”, es decir “a unión cero”, o “a
unión falso” que evidentemente es solo “a”.
Para comprender las ecuaciones anteriores, apoyese en las tablas de verdad y observe
los resultados.
346
ANEXO 3: CÓDIGO FUENTE JAVASCRIPT DE SOKOBAN
/********************************************************************
* SOKOBAN. *
* El personaje debe empujar una serie de cajas *
* Hasta colocarlas en ciertos lugares predefinidos. *
* Autor: Marco León Mora *
* Versión: 1.1 Carga archivo de mundos *
* Fecha: Abril/2015 *
********************************************************************/
//Contadores de movimiento
var pasosSoko = 0;
var movimCajas = 0;
347
var dirSoko = { X: 0, Y: 0 };
audioPaso = document.getElementById("audioPaso");
audioCaja = document.getElementById("audioCaja");
audioNoMueve = document.getElementById("audioNoMueve");
audioAplausos = document.getElementById("audioAplausos");
348
"nada": 0, //Casilla vacia, no usada
"pared": 1, //Pared
"piso": 2, //Piso libre
"destino": 3, //Piso con marca de destino
"caja": 4, //Caja en posicion libre
"cajaDestino": 5, //Caja en posicion destino
"soko": 6, //Soko sobre piso libre
"sokoDestino": 7 //Soko sobre piso destino
};
Object.freeze(valorCasilla);
349
default:
break; //Otra tecla o ninguna
}
valorTecla = 0; //Borra para no usar de nuevo
350
//Borrar el contenido del canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
case valorCasilla.caja:
case valorCasilla.cajaDestino:
//Dibuja un piso y la caja
ctx.drawImage(img1, (obj-2)*wFrame,0,wFrame,hFrame,X,Y,dX,dY);
ctx.drawImage(img1, obj*wFrame,0,wFrame,hFrame,X,Y,dX,dY);
break;
case valorCasilla.soko:
case valorCasilla.sokoDestino:
//Dibuja un piso y a Soko
ctx.drawImage(img1, (obj-4) wFrame,0,wFrame,hFrame,X,Y,dX,dY);
ctx.drawImage(img1, mSoko*wFrame,0,wFrame,hFrame,X,Y,dX,dY);
break;
default:
break; //Caso de casilla vacia
}
}
}
verPasosSoko.innerHTML = "Pasos Soko: " + pasosSoko;
verPasosCaja.innerHTML = "Movimientos Cajas: " + movimCajas;
}
351
}
352
//Cambia la posicion de Soko en la matriz del mundo
function mueveSoko() {
//Lo quita de la posicion actual
mundo[posActualSoko.Y][posActualSoko.X] =
mundo[posActualSoko.Y][posActualSoko.X]- 4;
//Lo coloca en la siguiente posicion
mundo[posSigueSoko.Y][posSigueSoko.X] = mundo[posSigueSoko.Y][posSigueSoko.X]+4;
//Registra la nueva posicion
posActualSoko.X = posSigueSoko.X;
posActualSoko.Y = posSigueSoko.Y;
//Cuenta pasos
pasosSoko++;
353
reader.readAsText(file);
}
else {
alert("No se pudo leer el archivo...");
}
}
354
}
}
var j = 0; //indice de columna de la matriz del mundo
//Recorre hasta encontrar otro delimitador #
while (archivoMundos[fi].substring(0, 1) != "#") {
var filaDatos = new Array();
filaDatos = archivoMundos[fi].split(","); //Separa los numeros de una fila
for (var i = 0; i < filaDatos.length; i++) {//Recorre los numeros de una fila
var res = parseInt(filaDatos[i]); //Toma cada numero
mundo[j][i] = res; //Lo guarda en matriz del mundo
}
j++ //Siguiente fila del mundo
fi++ //Siguiente fila de archivoMundos
}
355
356
ANEXO 4. GLOSARIO
Acoplamiento: Mide el nivel de interacción que un módulo tiene con otros. Entre menor
sea el acoplamiento, el módulo será más fácil de diseñar, programar, probar y
mantener. Al reducir al mínimo el nivel de interacción, se reducen efectos secundarios
no deseados. Las clases deben tener la menor dependencia de otras clases.
Análisis del problema: Proceso inicial, antes de definir el algoritmo, en el que se debe
identificar el objetivo (salida), los datos con los que se cuenta (entrada) y los pasos
generales a seguir. No se recomienda empezar a escribir código sin tener bien definida
esta etapa.
Archivo plano: Archivo compuesto solo por texto, sin formatos especiales.
357
Arreglo: Conjuntos de variables del mismo tipo, generalmente de tamaño fijo, Cada
elemento tiene un orden y se puede accesar mediante su posición (ver apuntador).
Asociación: en POO, dos objetos independientes que se unen para realizar un objetivo.
En el análisis, debe tener sentido el conector “…usa un…”.
Binario: Que puede tener dos valores; se aplica a sistemas numéricos o lógicos.
También puede ser una base numérica utilizando solo los dígitos 0 y 1.
Booleano: Tipo de dato lógico, que puede valer verdadero o falso. También hace
referencia a la Lógica, en la que se emplean operadores relacionales (>, <, >=, etc.) y
operadores lógicos (AND, OR, NOT, XOR…).
Cadena (string): Tipo de datos que contiene caracteres y con los que no se puede
realizar operaciones aritméticas.
Campo: Se refiere a cada unidad indivisible en el que se puede almacenar un dato. Los
campos componen estructuras de datos más complejas, como el registro.
358
Código ASCII: Esquema de codificación utilizando números enteros, para representar
los símbolos utilizados en la escritura humana y, por extensión, para otros símbolos
de control como “new line”, “enter”, etc.
Código fuente: Archivo plano que contiene las instrucciones de código, en un lenguaje
de programación determinado, escritas por el programador. Es necesario que este
código sea compilado posteriormente para poder ser ejecutado.
Cola: Es un tipo de lista de datos en la que solo se puede ingresar por un extremo y
acceder por el otro, obedece la regla FIFO (First In, First Out), Primero que entra,
primero que sale.
Colección:
Comentario: Una o varias líneas de texto incrustadas en el código fuente, con el objeto
de aclarar o especificar mejor la lógica del programa. Los comentarios son ignorados
por el compilador y solo son útiles para los humanos.
Concatenar: Unir dos o más variables del tipo cadena de texto (string), generalmente
produce una tercera variable.
Constante: Valor que no cambia. Un campo o memoria que contiene un valor estático
durante la ejecución del programa.
359
Contador: Es una variable en la memoria que se incrementará en una unidad cada vez
que se ejecute un proceso.
CSS: Acrónimo de cascading style sheets (Hoja de estilo en cascada) lenguaje utilizado
para definir la forma como se presenta en un navegador una pagina HTML.
Desborde: Ocurre desborde cuando, en una variable, el valor que se intenta introducir
no “cabe” en el espacio de memoria asignado.
Diagrama de estados: Grafica que muestra los diferentes estados por los que pasa un
objeto, en respuesta a eventos. También puede mostrar las respuestas del objeto al
entrar y/o salir de un estado determinado.
360
DOM: Acrónimo de “Document Object Model” o Modelo de Objetos del Documento. Es
una interfaz de programación para documentos HTML y XML. Define la estructura
lógica de los documentos y el modo como se accede y manipula un documento; con
el DOM se puede construir, recorrer, añadir, modificar o eliminar elementos y
contenido. Se puede acceder a cualquier cosa que se encuentre en un documento
HTML o XML.
Estático: Un objeto o elemento que no cambia de valor. En POO una clase estática no
se puede instanciar y se referencia directamente con su nombre.
Evento: Es una acción que ocurre en el sistema, por ejemplo, pulsar una tecla o recibir
una comunicación, etc. Cuando se crean “Manejadores de Eventos”, el evento
“dispara” la ejecución de estos módulos para que el programa responda
adecuadamente.
361
Excepción: cualquier situación inesperada o excepcional que se presente mientras se
ejecuta un programa. Las excepciones pueden hacer “abortar” el programa, a menos
que se incluyan mecanismos de control.
Función: es un módulo de código que realiza una tarea específica y puede devolver un
valor. La función puede requerir parámetros para su ejecución.
GET: En POO, es un método público, utilizado para leer el valor de una propiedad privada
de un objeto. Es un mecanismo de protección que crea una “interfaz” para evitar los
accesos directos.
Hexadecimal: Sistema numérico cuya base utiliza 16 dígitos. Debido a que en nuestra
cultura solo se emplean diez símbolos numéricos, se utilizan también las letras de la
A a la F, para representar los valores decimales 10 a 15.
Inicializar: Asignar un valor inicial a una variable. La mayoría de las variables deben ser
inicializadas explícitamente para evitar resultados imprevistos (basura).
362
Instanciar: En el código fuente, es la “creación” o implementación de un objeto. A partir
de este momento el objeto puede ser utilizado. El objeto debe ser declarado antes de
ser instanciado.
Interfaz del usuario: En un programa, es la parte que permite la interacción del usuario.
Invocar: Es la llamada a un módulo (método, función, etc.) para que se ejecute; se puede
requerir el paso de parámetros.
Literal: Valor que permanece sin cambio y se expresa a sí mismo, por ejemplo “6” o
“Hola Mundo”. Es similar a una constante en cuanto que su valor no cambia, pero la
constante es referenciada por un nombre, mientras que el literal no tiene nombre.
363
Método: En POO, un bloque o código que cumple una funcionalidad específica,
aportando al “comportamiento” de un objeto. También se puede decir que es una
“técnica” para resolver problemas.
Números mágicos: en la escritura del código, hace referencia a ciertos valores literales
que no tienen una explicación inmediata y hacen difícil el mantenimiento posterior del
mismo. Por ejemplo, si se tiene una lista de 100 objetos y se deben realizar
operaciones sobre ella en distintas partes del código, un programador novato podría
escribir una línea como: “PARA i=1 hasta 100 HAGA”. Si cambia el tamaño de la lista,
habría que buscar todas las ocurrencias del literal “100” para cambiarlo por el nuevo
valor, con el riesgo de cambiar donde no corresponda. Para evitar estos
inconvenientes, se sugiere definir una constante, por ejemplo “tamañoLista = 100” y
hacer todas las líneas “PARA i=1 hasta tamañoLista HAGA”. En este caso, un cambio
en el tamaño solo implica cambiar el valor asignado a la variable, en un solo lugar, sin
efectos secundarios.
364
Objeto: En POO, un objeto es una unidad modular con información del mundo real, cuyas
características y comportamiento están definidos por la clase de la cual es
instanciado.
Octal: Sistema numérico cuya base utiliza 8 dígitos. Se utilizan los símbolos numéricos
del 0 al 7.
Operador lógico: Tipos de tratamiento dado en la lógica proposicional para definir si una
proposición es verdadera o falsa. Los operadores lógicos son utilizados principalmente
para determinar el flujo de control del programa. También se denominan “operadores
booleanos”.
Paso por valor: Cuando se suministra un parámetro a un método, se toma una copia
del valor de una propiedad. Cualquier cambio ejecutado en ese valor dentro del
método no afectara a la propiedad original.
365
desarrollen en contextos similares. Se clasifican en patrones de Creación (solucionan
problemas de creación de instancias, ayudan a encapsular y abstraer); patrones
Estructurales (de diseño software que solucionan problemas de composición y
agregación de clases y objetos) y patrones de Comportamiento (ofrecen soluciones a
la interacción y responsabilidades entre clases y objetos).
Pila: Es un tipo de lista de datos en la que solo se puede acceder por un extremo,
obedece la regla LIFO (Last In, First Out), Ultimo que entra, primero que sale.
Problema: Planteamiento de una situación que requiere ser solucionada, a partir de unos
datos iniciales conocidos.
366
herencia, polimorfismo, encapsulamiento, cohesión, acoplamiento, abstracción, etc.
Este paradigma ha tenido una fuerte acogida en la comunidad de desarrolladores y en
la actualidad existen muchos lenguajes que soportan la POO.
Propiedad: En POO, son los elementos que definen el “estado” o las características de
un objeto. Se puede asociar a “variable” en la programación clásica.
Protegido: En POO, cuando una propiedad o método solo puede ser accesada por su
propia clase y por las subclases que la heredan. Es una de las clasificaciones de la
“visibilidad”.
Razonamiento lógico: Proceso mental que utiliza la lógica y que, partiendo de unas
premisas, se llega a un aconclusion que puede ser evaluada como verdadera, falsa o
posible.
Registro: Termino utilizado en Bases de Datos para una “estructura de datos”, en la que
cada dato se denomina “campo”. La agrupación de registros conforma una “Tabla”.
Salida: Resultado obtenido al aplicar un algoritmo. La salida es una de las tres divisiones
básicas de un algoritmo (Entrada – Proceso – Salida).
367
Semántica: El resultado de la evaluación de cadenas legales (sintácticamente correctas)
en un lenguaje de programación especifico.
SET: En POO, es un método público, utilizado para modificar el valor de una propiedad
privada de un objeto. Es un mecanismo de protección que crea una “interfaz” para
evitar los accesos directos.
Sobre escritura (override): En POO, cuando una clase que hereda (subclase) modifica
el comportamiento de un método de la clase principal (superclase o clase base).
Proporciona una nueva implementación del método de la superclase.
Subrutina: Modado de código, separado del programa principal y que puede ser
invocado desde este o desde otro modulo.
368
el código. Aunque el objeto, evidentemente, solo existirá al ejecutarse el código, este
código es subyacente y, muchas veces, el programador no esta conciente de el.
Tipo de dato: Propiedad que se la asigna a una variable y que limita los valores
admitidos, el tipo de operaciones que se pueden efectuar y como son representados
internamente. Cada variable tiene asociado un tipo de datos y solo se pueden asignar
valores de ese tipo a la variable.
Variable global: En un programa, variable que puede ser acusada desde cualquier
módulo de código.
Variable Local: En un programa, variable que puede ser accesada solamente desde el
modulo que la declara; es invisible para otros módulos o su tiempo de vida se limita a
cada ejecución del módulo.
369
370
ANEXO 5. REFERENCIAS DE INTERNET
http://profe-alexz.blogspot.com/2011/04/razonamiento-logico-18-problemas.html
http://profe-alexz.blogspot.com/2011/03/razonamiento-logico-matematico.html
http://razonamiento-logico-problemas.blogspot.com/
http://news.sciencemag.org/2009/08/multitasking-muddles-mind
http://programarconalgoritmos.blogspot.com/2013/02/el-lenguaje-PYTHON.html
http://es.slideshare.net/urumisama/funciones-y-procedimientospropiosPYTHON-
33646718
http://www.lab.dit.upm.es/~lprg/material/apuntes/doc/doc.htm
http://blog.rhiss.net/introduccion-documentacion-php.html
http://www.eduteka.org/pdfdir/AlgoritmosProgramacion.pdf
http://www.miriamruiz.es/slides/2011_Algoritmos_Scratch.pdf
http://di002.edv.uniovi.es/~dani/asignaturas/transparencias-leccion6.PDF
http://es.wikiversity.org/wiki/Curso_de_Estructuras_de_Datos_y_Algoritmos_/_Algoritm
os_recursivos
http://librosweb.es/libro/algoritmos_Python/capitulo_18/un_ejemplo_de_recursividad_el
egante.html
http://www.lcc.uma.es/~jlleivao/algoritmos/t3.pdf
http://es.wikipedia.org/wiki/%C3%81lgebra_de_Boole
https://msdn.microsoft.com/es-es/library/edzehd2t(v=vs.110).aspx
http://www.codecompiling.net/files/slides/UML_clase_04_UML_clases.pdf
http://www.genbetadev.com/metodologias-de-programacion/patrones-de-diseno-que-
son-y-por-que-debes-usarlos
http://www.oodesign.com/
http://patronesdediseno.net16.net/
https://www.fing.edu.uy/inco/cursos/fpr/wiki/index.php/Operadores_en_Python
371
372
ANEXO 6. BIBLIOGRAFIA
Ceballos Sierra, francisco, (2ª Ed. 2011). Microsoft c#. Curso de programación. RA-MA Editorial.
Jiménez, Alfonso, Marín, Francisco & Pérez, Manuel, (2012). Aprende a programar con
java. Paraninfo.
Jiménez, José, Jiménez, Erendida & Alvarado, Laura, (2014). Fundamentos de programación.
Diagramas de flujo, diagramas N-S, Seudocódigo y java. Alfaomega.
Joyanes, Luis, (3ª Ed. 2003). Fundamentos de programación. Algoritmos, estructuras de datos y
objetos., McGrawHill.
Trejos, Omar Iván (1999). La Esencia de la Lógica de Programación – Básico. Editorial Papiro.
373