Algoritmia: Del Análisis Del Problema Al Código

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 373

ALGORITMIA

Del Análisis del problema al código

Una introducción al desarrollo de algoritmos computacionales, haciendo énfasis


en la comprensión del problema y el Análisis del mismo, como requisito
fundamental para plantear una solución basada en un desarrollo incremental,
de lo general a lo particular, hasta obtener el detalle suficiente para llevar el
algoritmo a un leguaje de computador.

MARCO LEON MORA MENDEZ

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.

Las soluciones de los problemas de programación se dan a partir de la ALGORITMIA, la


cual se soporta en la aplicación de los algoritmos entendido como el conjunto de
instrucciones o reglas definidas y no-ambiguas, ordenadas y finitas que permite,
típicamente, solucionar un problema, realizar un cómputo, procesar datos y llevar a cabo
otras tareas o actividades, dado un estado inicial y una entrada, siguiendo los pasos
sucesivos se llega a un estado final y se obtiene una solución. Los algoritmos son el
objeto de estudio de la algoritmia.

En la vida cotidiana, se emplean algoritmos frecuentemente para resolver problemas.


Algunos ejemplos son los manuales de usuario, que muestran algoritmos para usar un
aparato, o las instrucciones que recibe un trabajador de su patrón. Algunos ejemplos en
matemática son el algoritmo de multiplicación, para calcular el producto, el algoritmo de
la división para calcular el cociente de dos números, el algoritmo de Euclides para
obtener el máximo común divisor de dos enteros positivos, o el método de Gauss para
resolver un sistema de ecuaciones lineales.

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.

Se destaca igualmente que en los capítulos finales desarrolla casos tipos en


programación, como búsquedas, sistemas binarios y ejercicios más complejos, como un
reloj análogo, una calculadora y un videojuego. En los cuales se muestran algunas
herramientas de análisis y diseño, como la OPP (Programación orientada a objetos).

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.

Félix Ramón Triana Gaitan


Septiembre de 2019

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.

Tras más de veinte años de práctica en la industria y otro tanto en la enseñanza,


trabajando con diversos lenguajes y ambientes, desde el campo de los
microcontroladores y PLCs hasta los dispositivos móviles, pasando por muchas
generaciones de computadores (desde el famoso IBM 630), hemos desarrollado algunas
técnicas para la programación , a partir de lo aprendido de muchos maestros, colegas
y estudiantes. Hoy queremos compartirlas con la esperanza de que sean útiles al lector.

La exposición de los diferentes temas tratados se hace de manera incremental, desde


los conceptos elementales hasta los mas complejos, con la idea de que el lector pueda
avanzar de manera progresiva en la comprensión del proceso algorítmico. Estrictamente,
no se requieren conocimientos previos de programación, pero, debido a que nos
enfocamos en las técnicas de programación, que son altamente independientes del
lenguaje, el lector debe apoyarse en otras fuentes para conocer los detalles del lenguaje
y del entorno de desarrollo a utilizar.

Para el análisis y solución de los 43 problemas planteados, se hace uso de varios


lenguajes de programacion y herramientas de apoyo: Se inicia explicando la
representación del algoritmo por medio de seudocódigo y del seudolenguaje LPP. Los
de mayor complejidad se desarrollan en los lenguajes de programación C Sharp (C#) y
javscript. Tambien, en los problemas que lo requieran, se utiliza el lenguaje de etiquetado
HTML junto con CSS. Se explica, de manera suscinta, el entorno de desarrollo Visual
Studio y la herramienta de depuración de los navegadores modernos.

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.

El segundo capítulo desarrolla algunos conceptos de problema, método y algoritmo; se


presentan orientaciones para enfrentar la solución del problema y, por medio de
ejemplos, se hace una introducción a las estructuras de control, que son los “ladrillos”
elementales con los que se construye una solución algorítmica.

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).

En el capitulo quinto, se enfatiza el uso adecuado de la documentación en el desarrollo


del código. Posteriormente, en el sexto, se realiza un estudio de algunos algoritmos
genéricos, desarrollando el análisis y solución de diversos problemas. En los tres
capítulos siguientes se muestra como manipular arreglos, vectores y matrices, la
aritmética binaria, octal y decimal y la utilización de algoritmos recursivos.
El capítulo 10 se dedica a explicar, especialmente de manera grafica, el funcionamiento
de los mas importantes algoritmos de búsqueda y ordenamiento mientras los capítulos
11 y doce tratan los temas de registros y la serializacion y persistencia.

En el capitulo 13 se desarrollan dos ejercicios para tratar el tema de la Programacion


Orientada a Eventos, en el 14 se hace una introducción a la Programacion Orientada a
Objetos y en el 15 se realizan ejercicios para explicar el uso de listas y pilas.

Por último, el capítulo 16 esta dedicado a mostrar en detalle el proceso de análisis y


desarrollo de un juego 2D para entorno WEB.

12
1. EL RAZONAMIENTO LÓGICO

Razonar es la actividad mental que permite lograr la estructuración y la organización de


las ideas para llegar a una conclusión.

La lógica es la ciencia dedicada a la exposición de las formas, los métodos y los


principios del conocimiento científico. Algo lógico es aquello que respeta estas reglas y
cuyas conclusiones resultan justificadas, válidas o naturales.

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.

En el desarrollo de algoritmos, es muy importante tener habilidades de razonamiento


(especialmente lógico y matemático), además del conocimiento general sobre el tema
del problema a resolver.

Ejercicios de Razonamiento lógico y matemático

En esta sección se proponen algunos ejercicios de razonamiento lógico y matemático a


manera de ejemplo. Por favor intente resolverlos de manera individual, describiendo el
procedimiento utilizado para llegar a la respuesta. Posteriormente reúnanse en grupos
de máximo tres personas, discutan el planteamiento, el razonamiento y la conclusión de
cada uno de ellos. Finalmente, compare sus notas con el análisis presentado.

1. ¿Cuantos cuartos son 6 mitades?

Análisis:

13
Un cuarto es la mitad de un medio, es decir:
𝟏
𝟐 =𝟏
𝟐 𝟒

Por lo que un medio tiene dos cuartos, entonces:

𝟏 𝟐 𝟏𝟐
𝟔∗ =𝟔∗ =
𝟐 𝟒 𝟒
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.

2. Se colocan 5 tapas como se muestra en la figura. Agregando solo una, se deben


formar 2 filas con 4 tapas en cada fila.

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;

Uniendo las anteriores ecuaciones, 3 NA = 2 x (3 MG);

Es decir, 3 NA = 6 MG;

Solución :

1 naranja = 2 mangos (simplificando 3 con 6 de la última ecuación).

Note como en la ecuación se identifican plenamente las partes del enunciado original:
3 manzanas se cambian (=) por dos veces tres mangos.

4. A la figura siguiente, ¿cuántos cortes como mínimo es necesario hacer para


obtener 2 partes iguales en forma y tamaño?

Análisis:

El total de cuadros es de (5 x 4) + (4 x 4) = 36, luego cada parte debe tener 18 cuadros.

Solución : Como mínimo 2 cortes

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:

Hay que encontrar la suma total y dividirla en dos. Si se


suma el número mayor con el menor 12 y 1, después 11 y 2
y así sucesivamente, se encuentra que todas las parejas
suman 13;

Como hay 6 parejas, entonces 6 x 13 = 78, o sea que la


suma de todos los números del reloj es 78.

La mitad es 39, que corresponde a tres parejas.

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.

6. Se tienen 6 trozos de cadena de 4 eslabones cada uno y se desea formar una


cadena cerrada. ¿Cuál es el valor mínimo a pagar si por cada corte de eslabón
se cobran $200 y por cada soldadura $800?

Análisis:

La solución obvia para formar una cadena cerrada (ver


figura de la derecha) implica 6 cortes y 6 soldaduras
para un total de (6 x 200) + (6 x 800) = $6.000.

Si en cambio se toma un trozo y se separan sus 4


eslabones (4 cortes), se habrá eliminado un trozo
completo: sus eslabones pasaran a unir otros trozos (4
soldaduras). Por lo que solo falta usar otro eslabón
para tener la cadena unida (ver figura). Aquí, la clave
consiste en que al eliminar un trozo nos ahorramos una
unión.

16
Solución :

Cinco cortes y cinco soldaduras: (5 x 200) + (5 x 800) = $5.000.

El anterior ejercicio ofrece un caso en el que la solución “Obvia” no es la mejor.

7. Una tela de 36 Metros se corta en partes iguales. Si se hacen 3 cortes, ¿Cuánto


cuesta cada pedazo si toda la tela costo $2400?

Análisis:

En un primer momento, cuando se dice que tres cortes, se podría asociar


erróneamente a tres pedazos. En la imagen se observa que tres cortes producen
cuatro pedazos.

Solución :

$2400 / 4 = $600 cada pedazo vale $600

El anterior ejercicio ofrece un caso en el que la solución “Obvia” no es la correcta.

8. Hallar el resultado de las siguientes sumatorias: (A) N= 2+4+6+ … + 38 +40 y (B)


N= 1 + 3 + 5 + … + 99

Análisis:

A. Sumar los números pares


desde 2 hasta 40: hay 20 números
pares (los otros 20 son impares);

Si se suma el primero con el


ultimo, etc. como se observa en la
figura, siempre se obtiene 42

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.

9. En una fiesta hay estudiantes de computación y estudiantes de derecho. Los


estudiantes de computación siempre dicen la verdad y los estudiantes de
derecho siempre mienten. En una mesa hay cuatro estudiantes sentados, Al
acercarnos nos dicen al unísono: “Aquí, en esta mesa, hay estudiantes de
derecho y hay estudiantes de computación”. ¿A qué carrera pertenecen los
cuatro estudiantes?

Análisis:

Si algún estudiante fuera de computación, diría solo la verdad (habrían estudiantes


de las dos carreras), entonces los de Derecho hubiesen contestado diferente; Pero
como todos contestaron lo mismo, entonces todos están mintiendo. De otra manera
se presentaría una contradicción lógica.

Solución : Los cuatro estudiantes son de derecho.

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).

Si X es la edad futura de Eva, la edad futura de Ana seria X + 8,

Cuando Ana tenga el doble de la edad de Eva, tendría: X + 8 = 2X.

18
Ordenando: 2X – X = 8 y simplificando: X = 8

Solución :

X = 8 (es la edad futura de Eva) y Ana tendrá 8 + 8 = 16 años.

Análisis Grafico: hacia la derecha (ordenadas) se representan los años. Hacia arriba
(abscisas) las edades.

Trace una recta para la edad de Eva


desde el punto (0,0), su nacimiento,
que pase por (4,4) su edad actual.

Ubique la edad actual de Ana en


(4,12) ya que tiene 3 veces la edad de
Eva.

Observe que la diferencia de edades


es de ocho.

Encuentre su ordenada en el origen


(0,8) y trace la otra recta (de Ana).

El grafico nos muestra que, al nacer


Eva, Ana ya tenía 8 años de edad.

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:

Si C: número de conejos y P: número de palomas, entonces:

(1) C + P = 35 cabezas (cada animal tiene una cabeza)

(2) 4C + 2P = 94 patas (los conejos 4 patas y las palomas 2).

De la ecuación (1) se despeja C = 35 – P

19
y se reemplaza en (2): 4(35 – P) + 2P = 94.

Solución :

Resolviendo la ecuación anterior: 140 – 4P + 2P = 94; 2P = 46, entonces P = 23;

C = 35 – 23; C = 12.

Hay 23 palomas y 12 conejos. Este es un caso de tratamiento con lenguaje


matemático. Notar la importancia del manejo del algebra.

12. Cinco pueblos A, B, C, D y E (no necesariamente en ese orden) se encuentran


a lo largo de una carretera. Las distancias (en kilómetros) entre ellos se
muestran en el siguiente cuadro: ¿Cuál es el orden correcto de estos pueblos
a lo largo de la carretera?

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:

Del cuadro se tiene:

1. A - B: 3 y A - C: 3.

Entonces, Como B y C están a la misma distancia de A, entonces A debe estar en el


medio:

B ------ 3 ------ A ------ 3 ------ C.

2. A - D: 1 y B - D: 2, es decir, D esta entre A y B:

B --- 2 ---- D -1- A

3. A – E: 6 y B - E: 3. Entonces, E está a la izquierda de B. (al lado contrario de A,


más cerca de B)

E----3----B----3----A

20
Solución :

Uniendo los elementos del análisis anterior:


E ----3 ----- B --- 2 --- D--1-- A --- 3 --- C,

Con lo que también se cumple que B - C: 6; C - D: 4 y C - E: 9.

El orden es: E, B, D, A, C.

La solución se encuentra con un razonamiento lógico simple en cascada, es decir, un


razonamiento secuencial teniendo en cuenta que cada nuevo supuesto debe cumplir con
los anteriores.

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:

A) Betty es mayor que Carla


B) Carla y Betty son mayores que Jessica
C) Carla y Jessica son mayores que Betty
D) Jessica y Betty son mayores que Carla
E) Betty es mayor que Jessica

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 :

Puesto que la mentirosa es la menor, entonces solo la afirmación C es verdadera:


“Carla y Jessica son mayores que Betty”. Observe como la búsqueda de la
afirmación correcta se hace después de realizar la conclusión lógica del enunciado.

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.

Otra opción: Después de realizar el paso explicado en el análisis, desocupa la de 3L


y vierte los 2L en la jarra pequeña. Llena de nuevo la de 5L, a la de 3L le faltara uno
para llenarse, por lo que vertiéndole desde la de 5L el litro faltante, en la jarra mayor
quedara los 4L requeridos.

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:

De la primera afirmación se tiene

Rojo – – Verde – – (Gris, Azul)? o Gris – – (Azul)? – – Rojo – – Verde.

La segunda afirmación separa Azules de Grises:

Azul – – Rojo – – Verde – – Gris

La posibilidad

Gris – – Rojo – – Verde – – Azul no es válida, por la primera afirmación.

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:

Evaluemos cada parte:

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.

“y es el menor posible”, o sea 2.

“ninguno es 3 ni 9”, quedan 1, 5, 7.

“los dígitos impares se encuentran ordenados de menor a mayor”, 1, 5, 7 ya están


ordenados.

Solución : Por lo tanto el número es 2157.

Para solucionar este problema debe tener clara la diferencia entre “digito” y “numero”.

17. De cinco futbolistas, donde ninguno tiene la misma cantidad de goles


convertidos, se sabe que Claudio tiene dos goles más que Abel, Fabio tiene dos
goles más que Roberto, pero uno menos que Abel y Andrés más goles que
Roberto, pero menos que Abel. ¿Cuántos goles menos que Claudio tiene
Andrés?

Análisis:

Si cada columna a la derecha representa un gol más,


entonces podemos representar las dos primeras
afirmaciones del enunciado así:

La tercera afirmación se acomoda considerando que ninguno tiene el mismo número


de goles:

Solución :

23
De la figura anterior se deduce que Andrés tiene 4 goles menos que Claudio.
.

18. En la figura siguiente se distribuyen los números 1,2,3,4,5,6,7,de modo que: 1,


2, 4 y 5 en A; 2,3,4,6 en B; 4,5,6,7 en C; 2 y 4 en A y B; 4 y 5 en A y C; 4 y 6 en B
y C. ¿Qué número se encuentra en A y C pero no en B?

Análisis :

Consideremos los números que comparten las


regiones A y B, A y C, B y C:

Observe que el 4 está en A, en B y en C;


El 2 en A y B;
El 5 en A y C;
El 6 en B y C.

Solución :

De la figura, El número 5 se encuentra en A y C pero no en B.

Para encontrar la solución, no es necesario colocar todos los números.

19. Se tienen tres ciudades M, N y P. Un empresario que viaja en avión, cuando va


de M hacia N tiene que atrasar su reloj 2 horas al llegar a N y cuando va de M
hacia P debe adelantarlo 3 horas al llegar a P. Si sale de P hacia N, a las 11 p.m.
y el viaje dura 4 horas, ¿qué hora es en N cuando llega?

24
Análisis:

En la figura, cada columna representa un huso


horario, Si atrasa el reloj significa que viaja al
occidente (hora local más temprano), si lo
adelanta, entonces es porque viaja al oriente (hora
local más tarde):

Solución :

Si sale de P hacia N, la hora de salida en N será igual a la de P – 5, ya que cruza 5 husos


horarios. Es decir, si sale a las 11 p. m. de la ciudad P, estaría saliendo a las (11 – 5) =
6 p. m. Hora local en N. Como la duración del vuelo es de 4 Horas, entonces la hora
de llegada será (6 pm + 4) = 10 p.m.

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:

El razonamiento siguiente: 2 m – 0.5 m = 1.5 m por día;


1.5 m x 5 días = 7.5 metros, es falso puesto que
considera también lo que reduce la noche del ultimo día
y se pregunta por el nivel máximo alcanzado durante el
quinto día.

En la gráfica se observa que, durante el día sube 2 m


el nivel y desciende 0.5 m en la noche. Al cabo del
quinto día ha alcanzado un nivel máximo de 8 m, antes
de perder los 0.5 en la noche.

Solución : Al final del quinto día el nivel ha alcanzado 8 metros.

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:

Para comprender la red de relaciones, es conveniente listar primero los parentescos


solicitados:

Abuelo, Abuela, Hijo, Hijo, Hijo, Hija,


Hija, Hija, Madre, Madre, Padre, Padre,
Suegra, Suegro, Nuera (15
parentescos).

Luego construya la red y vaya


asignando parentescos observando cual
individuo puede tener más de un rol. Por
ejemplo, un padre puede ser también
hijo, etc. (tache en la lista los
parentescos que va utilizando).

Solución : Después de construir el grafico de parentescos, se cuentan ocho


personas. Se advierte que esta no es la UNICA solución posible.

22. Un preso condenado a la pena de muerte, tiene una oportunidad de salvar su


vida, si es capaz de resolver el siguiente problema. El Juez, mostrándole dos
puertas, cada una cuidada por un guardia, le dijo: "Una de estas puertas
conduce a la libertad y la otra a la silla eléctrica; los guardias las conocen, solo
que uno de ellos siempre miente y el otro guardia siempre dice la verdad. Tienes
la opción de hacer una sola pregunta a uno de ellos". Tras unos minutos de
titubeo, el reo preguntó al guardia de la puerta B: “Si le pregunto al guardia de
la puerta A, cuál de las puertas conduce a la libertad, ¿qué me responderá?”,
“Te dirá que la puerta B” - respondió el custodio. Luego de oír la respuesta, el
preso se encaminó con toda seguridad hacia la "puerta de la vida" y salió libre.
¿Por cuál de las puertas salió?

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”.

Pero si ha preguntado al guarda no mentiroso, la respuesta del mentiroso habría sido “…


la puerta B”, cuando la verdad seria “…A”.

Solución : El reo salió por la puerta A, que conduce a la libertad.

27
28
2. PROBLEMAS Y ALGORITMOS

El Problema

En general, un problema es un planteamiento del que se espera una solución. Es una


pregunta que se hace para encontrar un valor o dato desconocido, a partir de datos
conocidos y/o suministrados en el problema; también se puede plantear para determinar
un método a seguir y obtener el resultado esperado.

En matemáticas, un problema es una proposición de la que se debe investigar el modo


de obtener un resultado a partir de algunos datos conocidos.

En las definiciones, observe que aparecen ciertas palabras clave:

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.

Parodiando a Thomas Alva Edison, “El desarrollo de Algoritmos es 99% comprensión


del problema y 1% descripción de la solución”.

Recomendación: Por favor, NUNCA empiece a desarrollar el método de solución


mientras NO ENTIENDA completamente el problema.

Para solucionar el problema siga estos pasos:

• Primero, usted debe tener bien claro QUE se quiere Salida


• Después, determine cuáles son los datos de partida, QUE se tiene Entrada
• Por último y a partir de lo anterior, determine COMO hacerlo Proceso

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 algoritmo es un conjunto ordenado y finito de operaciones o pasos que permite


hallar la solución de un problema.

El algoritmo describe un método para resolver un problema mediante una secuencia


de pasos a seguir . Dicha secuencia puede ser expresada en forma de diagrama o en
lenguaje natural, de manera estructurada, con el fin de entenderlo de una forma más
sencilla.

Un ejemplo:

Si usted encuentra que su vehículo se ha pinchado, describa la forma de repararlo.

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?

Le aseguro que el gato está oculto en el algoritmo propuesto…

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”.

Veamos de nuevo el problema anterior, detallándolo un poco má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.

• Posteriormente cambiar la llanta:


o Tome el gato e instálelo bajo la llanta a cambiar,
o Tome la cruceta y afloje los pernos un poco,
o Suba el gato
o Retire los pernos para quitar la llanta pinchada
o Coloque la nueva llanta y ajuste los pernos
o Baje el gato y retírelo
o Apriete los pernos

• Y por último quitar los tacos y guardar todo:


o Retire los tacos
o Guarde la rueda pinchada y demás equipo utilizado.

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).

Al desarrollar esta segunda iteración, se concentrara en especificar mejor el primer paso


de la primera solución, olvidándose de las demás. Cuando ésta se ha subdividido en
otros tres o unos pocos pasos más, usted puede continuar con el segundo paso. Este
método permite que su mente se enfoque en algo concreto, reduciendo los límites y por
lo tanto la complejidad del problema que está resolviendo en el momento, con mejores
resultados al ir creando un algoritmo con una estructura más sólida.

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

En ingeniería existe un principio denominado KISS . No es un beso, es el acrónimo de


“Keep It Simple, Stupid!” (“Manténgalo Simple, Estúpido!”). Es común en muchos
programadores querer poner “florituras” a su algoritmo, funcionalidades por fuera de los
requerimientos, adornos o efectos no solicitados, etc. Quieren colocar su “Sello
Personal”. Pero recuerde que la complejidad implica complicación! Siempre les digo
a mis estudiantes: “Lo que no le están solicitando ahora, déjelo para la versión 2.0”.

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”.

Las Estructuras de Control en la Programación

Para escribir un algoritmo, se usan ciertos bloques o módulos básicos, que se


constituyen en los ladrillos elementales con los que usted construirá su programa,
acompañados de una regla básica, elemental, sin la cual se corre el riesgo de complicar
la estructura del programa en construcción.

La regla básica estipula: “Todo programa, o modulo, o segmento, contiene


solamente una ENTRADA y una SALIDA” .

¡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:

INICIO PROGRAMA Cambiar Llanta


Asegurar el vehículo
Cambiar la llanta
Quitar los tacos y guardar todo
FIN PROGRAMA

Observe: Una sola entrada al programa (INICIO PROGRAMA) y una sola salida (FIN
PROGRAMA).

Una estructura como la anterior se denomina ESTRUCTURA SECUENCIAL . Es el


“ladrillo” fundamental con el que usted construirá código. Es secuencial puesto que
primero se desarrolla una acción (o un grupo de acciones relacionadas), después de
terminar esta acción o grupo, entonces se ejecuta la siguiente… y así hasta llegar al final.

Volviendo a nuestro ejemplo, agreguémosle la posibilidad de que el vehículo pueda estar


en movimiento cuando se presenta el daño. ¿Qué puede ocurrir? ¿Qué se debe agregar
a nuestro algoritmo de tres pasos para que funcione en este otro caso?

Analicemos:

33
Si el vehículo está en marcha, entonces hay que detenerlo y parquearlo, luego, proceder
con lo ya definido.

Pero si el vehículo está detenido, no hay necesidad de hacer nada adicional a lo ya


definido.

Entonces tendremos:

INICIO PROGRAMA Cambiar Llanta


SI (Vehículo está en marcha) ENTONCES
Detener el vehículo
Parquear el vehículo
FIN SI
Asegurar el vehículo
Cambiar la llanta
Quitar los tacos y guardar todo
FIN PROGRAMA

Este nuevo ladrillo se denomina ESTRUCTURA DE DECISION . Las


instrucciones que están en su interior, se comportan como otra SECUENCIA (Detener el
vehículo; Parquear el vehículo).

Ahora nuestro algoritmo tiene cuatro bloques SECUENCIALES. El primer bloque es, a
su vez, una estructura DE DECISION.

Esta estructura sigue la regla básica: Una sola entrada al bloque:

SI (Vehículo está en marcha) ENTONCES

Y una sola salida del bloque:

FIN SI

Dentro de este bloque se observa a su vez una estructura SECUENCIAL:

Detener el vehículo
Parquear el vehículo

Observe que el programa como un todo también continua cumpliendo la regla básica.

Ahora queremos que el algoritmo contemple la opción de que la llanta de repuesto o


cualquier herramienta necesaria estén disponibles o no.

¿Qué hacemos?... Pues hacemos otra pregunta. Y… ¿Qué preguntamos?

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.

Ahora, ¿dónde se insertara? Continuemos el análisis…

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:

INICIO PROGRAMA Cambiar Llanta


SI (Vehículo está en marcha) ENTONCES
Detener el vehículo
Parquear el vehículo
FIN SI
SI (están todos los elementos) ENTONCES
Asegurar el vehículo
Cambiar la llanta
Quitar los tacos y guardar todo
SINO
Llamar servicio de mantenimiento
FIN SI
FIN 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.

En el segundo bloque, cuando la afirmación (están todos los elementos) es


VERDAD se hace el cambio de llanta y cuando sea FALSO , se llama al servicio de
mantenimiento.

La estructura SI – FIN SI se denomina DECISION SIMPLE ,

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.

La estructura SI – SINO - FIN SI se denomina DECISION COMPUESTA .

En esta estructura se presentan dos


flujos y en cada uno se realiza un
proceso, al final se unen en una sola
salida.

Ya que estamos hablando de decisiones, mencionemos que existe otra variante


denominada DECISIÓN MULTIPLE o CASO , que veremos más adelante, cuando
estos conceptos iniciales estén más sólidos, por ahora, observe la siguiente imagen, que
representa la forma como el control de flujo sigue diversos caminos alternativos para
finalmente salir por un solo punto.

36
Continuemos. Recuerde este segmento de la segunda iteración:

• Posteriormente cambiar la llanta:


o Tome el gato e instálelo bajo la llanta a cambiar,
o Tome la cruceta y afloje los pernos un poco,
o Suba el gato
o Retire los pernos para quitar la llanta pinchada
o Coloque la nueva llanta y ajuste los pernos
o Baje el gato y retírelo
o Apriete los pernos

Pensemos más detalladamente en la parte de la instrucción “…y afloje los pernos


un poco”. ¿Cuántos pernos? ¿Todos los vehículos tienen el mismo número de
pernos?

¿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.

Estos casos se solucionan con el último tipo de estructura: La ESTRUCTURA


ITERATIVA . Esta estructura se utiliza en los casos en que el mismo proceso se repita
varias veces, en ella se presentan tres variantes:

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

En estos casos hay varios detalles para analizar:

• Advertir que todos tienen una sola entrada y una sola salida.

• En todos los tres casos se ejecuta el proceso interno Aflojar el perno,


mientras que la condición entre paréntesis sea VERDAD.

• Cuando sea FALSO se saldrá de la estructura a la siguiente en la secuencia


subir el gato.

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.

• En C ¿Qué es i y n? Estas letras son nombres de variables , utilizadas para


almacenar valores. i toma un valor inicial (en este caso uno) y se va
incrementando en cada iteración. Al repetir el ciclo se vuelve a evaluar la
condición. Cuando sea igual o mayor que n, se sale del ciclo. n en este caso
contendrá el número de pernos que tiene el vehículo. Recuerden que más arriba
dijimos que no conocíamos este número, por lo tanto esta variante de la estructura
iterativa (denominada PARA ) no es muy útil en este ejemplo, pero se presenta
porque en general es de las más utilizadas en la construcción de algoritmos.

• Si se desea ejecutar el proceso interno mientras que la condición entre el


paréntesis sea FALSO. ¿Cómo se implementara? Bueno, este es un problema
de simple lógica. La evaluación debe arrojar VERDAD para ingresar en el ciclo,
así que debemos cambiar la pregunta sin cambiar el sentido de la lógica total.
Cambiar la prueba, tanto externa como internamente (recuerde que la negación
de una negación es una afirmación) Observe los bucles o ciclos que se repiten
HASTA que se cumpla la condición.

HASTA QUE (NO existan pernos apretados) HAGA

¿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)

MIENTRAS – MIENTRAS (condición) HAGA


HAGA Proceso
FIN MIENTRAS

PARA PARA (inicio; condición; incremento)


HAGA
Proceso
FIN PARA

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

Cuando se está diseñando un algoritmo, es conveniente usar ciertas convenciones que


lo hagan más legible. Esas convenciones se aplican para que usted (el programador)
comprenda mejor lo que está haciendo, con el objetivo final de tener una guía detallada
del algoritmo que finalmente trasladara a un lenguaje de programación .

Se denomina seudocódigo a la codificación del algoritmo en lenguaje natural


simplificado, donde se reflejen fácilmente las diferentes estructuras de control. En el
apartado anterior el algoritmo se ha escrito utilizando el seudocódigo, observe que:

• Las palabras que forman la estructura van en mayúscula y subrayadas.


• Las demás palabras explicativas del código se escriben en minúscula.
• El seudocódigo no es único, no existe un estándar para él. Cada programador lo
define para sí mismo, por lo tanto los puntos anteriores son MIS normas y las
seguiremos a lo largo de este libro.
• Estas palabras siempre se escribirán de la misma forma (en cualquier lenguaje de
programación se denominan palabras reservadas , puesto que no se pueden
usar con otro significado diferente.
• Por último, note que cualquier estructura que exista como un proceso interno de
otra estructura se escribe desplazando todo el bloque a la derecha. Esto se
denomina INDENTACIÓN y es muy importante porque esta práctica permite
visualizar fácilmente las estructuras del programa.

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.

Ya hemos iniciado nuestro tercer refinamiento en la construcción de código:

//Algoritmo para cambiar una llanta en caso de pinchada


INICIO PROGRAMA Cambiar Llanta
SI (Vehículo está en marcha) ENTONCES
Detener el vehículo
Parquear el vehículo

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

Reto: A manera de práctica, continue agregando detalle al código anterior, completando


la tercera iteración.

42
3. LAS VARIABLES

Problema 1: El cuadrado de un Número. Desarrolle un algoritmo que calcule el


cuadrado de un número entero dado.

Análisis:

QUE se quiere Mostrar EL CUADRADO de un número

QUE se tiene Se debe ingresar el número (lo usual es que se ingrese por
teclado)

COMO hacerlo El “Cuadrado” de un número es el resultado de multiplicar el


número por sí mismo.

Pero, ¿cómo manipulamos ese número ingresado? Y ¿Dónde almacenamos el resultado


para poder mostrarlo? ¡Aquí es donde las variables nos prestan su ayuda!

Debemos definir al menos una variable para almacenar el número ingresado y


multiplicarlo por si mismo. podemos usar otra para guardar el resultado de la operación.

Ya lo tenemos claro, entonces podemos proceder a escribir el seudocódigo


correspondiente:

/*Algoritmo para calcular y mostrar el cuadrado


de un numero entero ingresado por teclado*/
INICIO PROGRAMA Cuadrado
numero <- Leer(teclado)
cuadrado <- numero * numero
Mostrar(cuadrado)
FIN PROGRAMA

Observaciones al código anterior:


• Se ha escrito un comentario inicial con una breve descripción de la funcionalidad
del algoritmo, en este caso es un comentario multilinea.
• Se utilizan dos variables (numero, cuadrado), es decir, dos “cajas” que
contienen los valores del número ingresado y del resultado de la operación.
• Consideremos la instrucción: numero <- Leer(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.

El seudocódigo anterior puede ser mejorado un poco al reducir el número de variables


utilizadas (las variables ocupan espacio en la memoria ). Recuerde que la instrucción
cuadrado <- numero * numero no modifica el valor de la variable numero y esta
no vuelve a ser utilizada. Entonces, podríamos tener:

/*Algoritmo para calcular y mostrar el cuadrado


de un numero entero ingresado por teclado*/
INICIO PROGRAMA Cuadrado
numero <- Leer(teclado)
numero <- numero * numero
Mostrar(numero)
FIN PROGRAMA

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.

Los tipos de datos pueden ser:


• Entero
• Real

44
• Booleano
• Carácter
• Etc. (depende del lenguaje de programación a utilizar),

Más adelante se usaran distintos tipos de variables.

En algunos lenguajes de programación es requisito declarar las variables (incluso


algunos “declaran” las variables al escribir el seudocódigo), es decir, definir el tipo y el
nombre de la variable a utilizar, al inicio del programa, del procedimiento o de la función.
En mi concepto, cuando se escribe el seudocódigo, no es necesario declarar las
variables, en caso de duda bastará con un comentario explicativo, puesto que la
declaración depende del lenguaje de programación en el que se vaya a implementar el
algoritmo que se está construyendo.

Problema 2. Números primos. Desarrolle un algoritmo que lea un número entero


positivo y determine si es primo o no.

Análisis:

QUE se quiere Decir si el número leído es primo o no es primo.

QUE se tiene Se debe ingresar el número (lo usual es que se ingrese por
teclado)

COMO hacerlo Primero debemos tener claro qué es un número “primo”. Es


aquel que solo es divisible (es decir, con residuo cero en
división entera) por 1 o por el mismo.

La forma más fácil (pero no la más eficiente) de comprobar si un número es primo, es


dividir el numero por todos los números, desde 2 hasta el anterior a él.

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é?

Entonces, ¿qué estructura funcionara? Puesto que se conoce la cantidad de repeticiones


(ya que primero se ha ingresado el número), utilizaremos la estructura PARA:

/*Algoritmo para comprobar si un numero ingresado


por teclado es primo o no.*/

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

Ya tenemos el planteamiento inicial, falto definir como controlar la verificación. Podemos


iniciar asumiendo que el número ES PRIMO y si alguna división no arroja residuo (es
divisible exactamente), entonces cambiamos a NO ES PRIMO. ¿Cómo hacerlo?

Definiendo una variable de tipo booleano (lógico, con valores VERDAD/FALSO),


inicializarla en VERDAD, recorrer el ciclo de divisiones y, si en alguna se da el caso, se
cambia a FALSO. Al final y dependiendo del valor de esta nueva variable, se presenta
en resultado en pantalla.

/*Algoritmo para comprobar si un numero ingresado


por teclado es primo o no.*/
INICIO PROGRAMA Primos
esPrimo <- VERDAD
numero <- Leer(teclado)
PARA (i desde 2 hasta numero -1) HAGA
SI (numero MOD i = 0) ENTONCES
esPrimo <- FALSO
FIN SI
FIN PARA
Mostrar(resultado)
FIN PROGRAMA

Observaciones al código anterior:

• La variable esPrimo es de tipo lógico o booleano, solo recibe valores


VERDAD/FALSO.
• Tome nota de la forma como se escribe el nombre de la variable esPrimo
• La prueba lógica (numero MOD i = 0) utiliza el operador MOD, es un operador
cuyo resultado es el residuo de la división ENTERA, o sea que se divide numero
entre i y lo que sobre es lo que se compara contra 0.

46
• El operador de comparación es “=”, por eso el de asignación es “<-”, para
diferenciarlos.

Solo falta especificar mejor la última parte, Mostrar(resultado) :

/*Algoritmo para comprobar si un numero ingresado


por teclado es primo o no.*/
INICIO PROGRAMA Primos
esPrimo <- VERDAD
numero <- Leer(teclado)
PARA (i desde 2 hasta numero -1) HAGA
SI (numero MOD i = 0) ENTONCES
esPrimo <- FALSO
FIN SI
FIN PARA
SI (esPrimo) ENTONCES
Mostrar(“El numero ”, numero, “ es primo”)
SI NO
Mostrar(“El numero ”, numero, “ no es primo”)
FIN SI
FIN PROGRAMA

Observaciones al código anterior:

• En la última estructura de decisión, la prueba lógica (esPrimo) no requiere


ninguna comparación, puesto que ya tiene un valor booleano.
• La instrucción Mostrar(“El numero ”, numero, “ es primo”)tiene como
parámetro una cadena de caracteres compuesta por tres partes (cada parte se
separa con una coma).
o La primera parte es un LITERAL , es decir se muestra tal como está y debe
estar encerrado entre comillas.
o La segunda parte es el valor del número, que se agrega al primer literal.
Atención, no se agrega la palabra “numero”, sino el valor contenido en la
variable.
o La última parte también es un literal.

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.

En el siguiente seudocódigo, se muestra, además, otro cambio que permite eliminar la


última estructura condicional.

/*Algoritmo para comprobar si un numero ingresado


por teclado es primo o no. (mejorado)*/
INICIO PROGRAMA Primos
numero <- Leer(teclado)
esPrimo <- “ es primo.”
PARA (i desde 2 hasta numero/2) HAGA
SI (numero MOD i = 0) ENTONCES
esPrimo <- “ NO ”+ esPrimo
FIN SI
FIN PARA
Mostrar(“El numero ”, numero, esPrimo)
FIN PROGRAMA

Observaciones al código anterior:

• Se ha optimizado el código de dos maneras:


o La comentada inicialmente, reduciendo el número de iteraciones a la mitad.
o Y cambiando el tipo de la variable esPrimo de Booleano a cadena
(string) de caracteres y asignándole un valor inicial que asume que SI es
primo,
o En la decisión interna, se le agrega el literal “ NO ” a la cadena, si es el
caso. Acá el operador “+” no es una suma, se llama concatenar .
o La frase de salida resulta concatenando el literal “El numero” más el
valor del numero ingresado (numero), más el contenido de la variable del
tipo cadena esPrimo.

48
EJERCICIOS BASICOS CON PYTHON

Python es un lenguaje de programación cuya filosofía se enfoca en una sintaxis que


favorezca un código legible disponible en varias plataformas, la curva de aprendizaje es
muy rápida, por lo que es uno de los mejores lenguajes para iniciarse en la programación.

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.

Continuaremos nuestro proceso de análisis y construcción de algoritmos haciendo uso


del lenguaje Python. Para la edición del código, se recomienda utilizar “Sublime Text”,
de alta aceptación en la comunidad de programadores. Consulte en internet como
instalar Python y como asociarlo al editor “Sublime Text” para poder ejecutarlo
directamente. Visite https://www.Python.org para instalarlo en su computador, instale
también sublime text (https://www.sublimetext.com). Por ultimo, consulte como preparar
Sublime Text para programar en Python.

Continuando, transcribamos nuestro primer algoritmo a Python y veamos el resultado

#CUADRADO: Algoritmo para calcular y mostrar el cuadrado


#de un numero entero ingresado por teclado

#Solicita el numero y lo guarda en la variable num


num = int(input("Ingrese el numero que desea elevar al cuadrado: "))

#Calcula el resultado multiplicando el numero por si mismo


resultado = num*num

#Muestra el resultado
print ("El resultado es: ", resultado)

Observe como nuestro seudocódigo se transforma en el código PYTHON de la imagen


anterior. El input recibe una cadena de caracteres y hay que convertirla a entero para
asignarlo a la variable ‘num’ (mas adelante hablaremos de los tipos de datos).

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:

El problema 2, de los números primos en PYTHON quedará:

#PRIMOS: Algoritmo para comprobar si un numero ingresado


#por teclado es primo o no.

#Solicitar numero
print ("NUMEROS PRIMOS")
numero = int(input("Ingrese un numero: "))

#Buscar si es par o primo


esPrimo = "Es primo"
#numero + 1: Se suma uno porque en Python el ciclo for
# repite hasta el numero anterior al limite
for i in range(2, (numero +1) // 2):
if numero % i == 0:
esPrimo = "No es primo"

#Escribe el resultado
print ("El numero {} {}".format(numero, esPrimo))

Se han agregado algunas instrucciones ‘print’ para hacer mas amigable la interfaz del
usuario .

Algunas anotaciones acerca de la sintaxis Python:

• las estructuras se delimitan utilizando la Indentacion,

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).

Probando para el numero 5, todo correcto…

Ahora, para el número 20…

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:

Realice los siguientes pasos:

• Opción ‘Open Folder’ y seleccione la carpeta donde ya estan o va a almacenar


los programas.
• Opcion ‘New File’ y ‘Save As…’ con el nombre de archivo que desee. No olvide
agregar la extensión ‘.py’ para indicar que es código Python.
• Escriba el código y guarde los cambios con ‘Save’
• Si el archivo ya existe, ábralo con ‘Open File…’ o selecccionandolo en la barra
izquierda
• Para ejecutar el programa, pulse la tecla F5, se desplegara una ventana
emergente con los comandos basicos para depurar el programa.
• Usted puede agregar un punto de parada (Breakpoint) colocando el cursor a la
izquierda del numero de línea, aparecerá un punto rojo.

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:

QUE se quiere Mostrar los números pares entre 1 y 100 y la sumatoria de


ellos.

QUE se tiene No hay necesidad de ingresar ningún dato. Los números se


generan en el algoritmo.

COMO hacerlo Primero debemos tener claro qué es un número “par”. Es


aquel que al dividirlo en 2, no deja residuo.

Evidentemente, debemos realizar un ciclo desde 1 hasta 100 y para cada uno de ellos,
comprobar si es par.

O podríamos simplemente hacer un ciclo desde 2 hasta 100, incrementando de dos en


dos; en este caso no se requiere comprobación (realizaremos la primera opción, que se
ajusta mejor al requerimiento inicial: los números desde 1 hasta 100).

/*Mostrar los números pares desde 1 hasta 100


Y la sumatoria de ellos.*/
INICIO PROGRAMA Pares1a100
PARA (i desde 1 hasta 100) HAGA
SI es primo HAGA
¿?
Fin si
FIN PARA
¿?
FIN PROGRAMA

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:

/*Mostrar los números pares desde 1 hasta 100


Y la sumatoria de ellos.*/
INICIO PROGRAMA Pares1a100
PARA (i desde 1 hasta 100) HAGA
SI es primo HAGA
Suma <- suma + i
Mostrar(i)
Fin si
FIN PARA
Mostrar(suma)
FIN PROGRAMA

Ahora pasémoslo a PYTHON y probemos el programa. Recuerde inicializar suma a cero:

# Mostrar los números pares desde 1 hasta 100


# y la sumatoria de ellos

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) )

Salida del programa:

54
Observaciones al código anterior:

• La variable suma se debe inicializar (asignar un valor inicial) antes de agregarle


otros valores
• recuerde, el ciclo en Python (for) repite hasta el anterior al limite, por eso se coloca
101.
• Para evitar que la lista de números se salga de la ventana ‘terminal’ se pueden
imprimir horizontalmente, suprimiendo la nueva línea que print() agrega por
defecto, cambiando la instrucción de impresión dentro del bucle por:
Print(i), end = '')

Problema 4. Números impares. Desarrolle un algoritmo que muestre los 300


primeros números, determine cuántos de ellos son impares y al final indicar la
sumatoria de todos los números.

Análisis:

QUE se quiere Mostrar los números entre 1 y 300, mostrar cuantos son pares
y la sumatoria de todos ellos.

QUE se tiene No hay necesidad de ingresar ningún dato. Los números se


generan en el algoritmo.

COMO hacerlo Primero debemos tener claro qué es un número “impar”. Es


aquel que al dividirlo en 2, como residuo queda 1.

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.

Con esto en mente tendremos el siguiente código PYTHON, al modificar directamente el


código del problema anterior, sin necesidad de escribir el seudocódigo:

# Mostrar los números de 1 a 300, contar los impares


# y la sumatoria de todos

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

#Mostrar el resultado de la sumatoria


print ("")
print ("Todos los numeros suman {}".format(suma))
print ("existen {} numeros impares".format(impares))

Y la salida es:

Observaciones al código anterior:

• La variable i es un contador , y la variable suma es un acumulador , nombres


que se les da según su utilización en el algoritmo.

Problema 5. Hipotenusa. Desarrolle un algoritmo para determinar la hipotenusa de


un triángulo rectángulo, dadas las longitudes de sus dos catetos.

Análisis:

QUE se quiere Mostrar la hipotenusa de un triángulo rectángulo.

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 .

Ahora, el algoritmo se puede dividir en tres partes:

• 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.

Problema 6. Volumen Desarrolle un programa para calcular el volumen de un cubo


y de un cilindro. El usuario debe seleccionar la operación deseada.

Análisis:

QUE se quiere Mostrar el volumen del cuerpo seleccionado por el usuario.

QUE se tiene El usuario selecciona entre el cubo y el cilindro. Después


ingresa los parámetros necesarios (lado del cubo o radio y
altura del cilindro).

COMO hacerlo Inicialmente se debe presentar un menú con las dos


opciones; después, pedir los parámetros necesarios para
calcular el volumen; realizar el cálculo y por último mostrar el
resultado

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.

La primera aproximación será:

/*Algoritmo para calcular y mostrar el área de


Un cubo o de un cilindro*/
INICIO PROGRAMA Areas
Mostrar el menú
Según opción pedir parámetros
Calcular volumen
Mostrar volumen
FIN PROGRAMA

Ahora, enfoquémonos en el primer paso. Podemos pedir que el usuario escriba un


número o una letra y usar una variable para almacenar la opción seleccionada. En este
caso que sea un número (1 o 2), quedara:

escribir (“CALCULO DEL VOLUMEN”)


escribir (“Seleccione la opción deseada: ”)
escribir (“1. Volumen del Cubo”)
escribir (“2. Volumen del Cilindro”)
opción <- leer(teclado)

El segundo paso claramente es una estructura de decisión:

SI (opción = 1) ENTONCES //Caso Cubo


escribir(“Ingrese el lado del cubo:”)
a <- leer(teclado)
SINO //Caso Cilindro

59
escribir(“Ingrese el radio del Cilindro:”)
r <- leer(teclado)
escribir(“Ingrese la altura del Cilindro:”)
a <- leer(teclado)
FIN SI

El tercer paso también depende de la acción seleccionada, por lo que la estructura es


similar. Tenemos la alternativa de usar otra estructura de decisión o realizar los cálculos
dentro de la anterior. La ventaja de esta última alternativa es que se reduce el código, la
desventaja es que el bloque realizaría dos operaciones distintas, pedir parámetros y
calcular volumen. Puesto que este algoritmo es muy simple, optaremos por la última, ya
que no aporta confusión ni complejidad.

Entonces, nuestro algoritmo de primer nivel se modifica:


/*Algoritmo para calcular y mostrar el área de
Un cubo o de un cilindro*/
INICIO PROGRAMA Areas
Mostrar el menú
Según opción pedir parámetros y Calcular volumen
Mostrar volumen
FIN PROGRAMA

El segundo paso quedara:

SI (opción = 1) ENTONCES //Caso Cubo


escribir(“Ingrese el lado del cubo:”)
a <- leer(teclado)
volumen <- a^3
SINO //Caso Cilindro
escribir(“Ingrese el radio del Cilindro:”)
r <- leer(teclado)
escribir(“Ingrese la altura del Cilindro:”)
a <- leer(teclado)
volumen <- PI * r^2 * a
FIN SI

Y, el tercer paso:

escribir(“El volumen es ”, volumen)

El seudocódigo ya está lo suficientemente desglosado y claro para proceder a pasarlo al


lenguaje de programación:

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

print (" CALCULO DEL VOLUMEN")


print ("")
print ("1- Volumen del cubo")
print ("2- Volumen del cilindro")

#Obtener opcionión seleccionada


opcion = int(input("Seleccione la opcionion deseada: "))
print ("")
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)
else:
print ("La opcionion que ingreso no es valida")

# Mostrar resultado
if volumen > 0:
print ("El volumen es {}".format(volumen))

Corrida para el caso del cubo:

61
Y para el caso del Cilindro:

Observaciones al código anterior:

• Python es un lenguaje debilemte tipado, es decir, no es obligatorio declarar el tipo


de variables, ya que se hace implícitamente al momento de asignar un valor.
• La constante pi, ya esta predefinida, por lo que hay que importar la libreria ‘math’
y se utiliza invocando a ‘math.pi’
• ¿Qué pasa si el usuario ingresa un valor diferente a 1 o a 2?

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:

QUE se quiere Repetir el programa hasta que el usuario ingrese un valor


distinto a 1 o 2.

QUE se tiene El algoritmo para un solo cálculo

COMO hacerlo

Primero, el menú a presentar quedara:

escribir (“CALCULO DEL VOLUMEN”)


escribir (“1. Volumen del Cubo”)
escribir (“2. Volumen del Cilindro”)
escribir (“3. Salir…”)

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.

/*Algoritmo para calcular y mostrar el área de


Un cubo o de un cilindro*/
INICIO PROGRAMA Areas2
Opción = 0
MIENTRAS (opcion != 3)
Mostrar el menú
Según opción pedir parámetros y Calcular volumen
Mostrar volumen
FIN_MIENTRAS
FIN PROGRAMA

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:

/*Algoritmo para calcular y mostrar el área de


Un cubo o de un cilindro*/
INICIO PROGRAMA Areas2
Opción = 0
MIENTRAS (opcion != 3)
Mostrar el menú
SI (opcion == 1) ENTONCES
pedir parámetros y Calcular volumen del cubo

SINO SI(opcion == 2) ENTONCES


pedir parámetros y Calcular volumen del cilindro
FIN SI
Mostrar volumen
FIN_MIENTRAS
FIN PROGRAMA

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")

#Obtener opcionión seleccionada


opcion = int(input("Seleccione una opcion: "))
print ("")

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))

Se agrega una comprobación adicional si la opción es diferente a las tres validas.

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.

Los módulos de código que se separan del programa principal se denominan


Procedimientos o Funciones (anteriormente se llamaban subrutinas , ahora
también se denominan Métodos ) , realizan una tarea específica y pueden tener o no
tener parámetros . Los parámetros son los valores que la función necesita para realizar
la tarea, puede tener varios o ninguno. En algunos lenguajes las funciones devuelven un
valor y los procedimientos no. En los lenguajes modernos los ‘procedimientos’ no existen,
todos son funciones, devuelvan o no devuelvan valores. Veamos otra solución al
problema anterior, utilizando una función:

#******************************************
# 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

#Funcion para mostrar menu y capturar la opcion


def mostrarMenu():
print ("--------------------------")
print (" CALCULO DEL VOLUMEN")
print ("")
print (" 1- Volumen del cubo")
print (" 2- Volumen del cilindro")
print (" 3- Salir")

#Obtener opcionión seleccionada


opc = int(input("Seleccione una opcion: "))
print ("")
return opc

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))

El código anterior contiene la implementación de la función mostrarMenu(). Observe la


forma como se declara.

• 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

En el paso por valor , el parámetro (de la función o procedimiento) toma solamente el


VALOR de la variable utilizada creando una copia interna de esa variable. Dentro del
código, esa copia se puede modificar pero la variable original, en el módulo principal que
realiza el llamado, NO SE MODIFICA. Al terminar la ejecución esa copia se destruye (ya
no existe más, por lo que no se puede volver a referenciar).

En el paso por referencia , el parámetro toma la DIRECCIÓN de la variable en el


módulo principal, NO CREA COPIA, cuando se realice un cambio dentro del
procedimiento, este cambio se ve reflejado en la variable original. Realmente solo existe
esta variable.

Veamos estos conceptos gráficamente:

1. Inicialmente, el modulo principal ha creado en memoria una variable denominada


opción, el Sistema Operativo le ha asignado, por ejemplo, la dirección de memoria
25000. En ella se encuentra el valor 20.
2. Al invocar el procedimiento, el modulo principal pasa la dirección 25000 (no el valor)
al procedimiento.
3. Dentro del procedimiento, la instrucción de asignación carga el valor 35 en dicha
dirección, es decir en la variable opción. Por lo tanto su valor CAMBIA de 20 a 35.

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:

# Ejemplo de paso por valor

# funcion -------------------
def doblar_valor(numero):
numero *= 2
print(numero)

# Programa principal -------


numero = 20
doblar_valor(numero)
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:

# Ejemplo de paso por referencia

# funcion -------------------
def doblar_valores(numeros):
for i in range(0, len(numeros)):
numeros[i] *= 2

# Programa principal -------


ns = [10,50,100]
print(ns)

doblar_valores(ns)
print(ns)

La captura de pantalla muestra los valores de la lista, antes y despuesde llamar a la


función:

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.

La documentación de un programa consiste en añadir la suficiente información para


explicar lo que hace. Esta documentación, por supuesto, no es para el computador, estas
líneas son ignoradas al momento de ejecutar el código; lo que se quiere es que el
programador (el que escribe el código o el que más tarde lo interviene) tenga una guía
explicativa de la lógica implementada por el diseñador.

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.

Una manera de facilitar la documentación consiste en transcribir los pasos definidos en


el seudocódigo al código final, agregándole los signos de comentario según el lenguaje.
La gran ventaja de hacerlo radica en que, a su vez, es una guía que facilita la escritura
del código final. En otras palabras, se debe tratar de escribir la documentación antes de
escribir las instrucciones de código.

¿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

Son bloques de comentarios para documentar un módulo de código, contienen una


explicación de la funcionalidad, nombra los parámetros (tipo y nombre), describe el tipo
de dato devuelto y las excepciones que pueden ocurrir. Algunos lenguajes, como
JAVA, C, etc., tienen normas al respecto.

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

Problema 9. Validación. Digitar números del 1 al 20. Cuando se ingrese cero


presentar la sumatoria y terminar. Si el número es mayor a 20 o menor a cero,
avisar y continuar.

Análisis:

QUE se quiere Mostrar la sumatoria al final. Validar el ingreso de los


números.

QUE se tiene El usuario digita los números en el teclado.

COMO hacerlo Debe haber un ciclo para leer los números.

Como la sumatoria se presenta al final, se podrían ir guardando los números y después


calcular el promedio, o ir sumando los números a medida que se ingresan, en este caso
se necesita también un contador para poder calcular el promedio al final, con la siguiente
fórmula.

𝑠𝑢𝑚𝑎𝑡𝑜𝑟𝑖𝑎
𝑝𝑟𝑜𝑚𝑒𝑑𝑖𝑜 =
𝑐𝑎𝑛𝑡𝑖𝑑𝑎𝑑

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.

Primero, desarrollaremos el algoritmo en seudocódigo:

/*Algoritmo para calcular el promedio de los


numeros ingresados por teclado, entre 1 y 20*/
INICIO PROGRAMA Promedio
suma <- 0
cantidad <- 0
n <- 0
MIENTRAS-QUE (n <> 0) // n diferente de cero
n <- Leer(teclado) // falta validar numero
suma <- suma + n
cantidad <- cantidad + 1

73
FIN-MIENTRAS
Calcular promedio
Mostrar(promedio)
FIN PROGRAMA

Observe que se deben inicializar las variables.

Ahora, utilizando la estrategia “Divide y vencerás”, resolvamos el problema de la


validación; al leer el valor y si el número no está en el rango, se debe avisar y volver a
solicitar, ¿Qué estructura funcionaria?

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

Integrando todo tenemos:

/*Algoritmo para calcular el promedio de los


numeros ingresados por teclado, entre 1 y 20*/
INICIO PROGRAMA Promedio
suma <- 0
cantidad <- 0
n <- 0
MIENTRAS-QUE (n <> 0) // n diferente de cero
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
FIN-MIENTRAS
Calcular promedio
Mostrar(promedio)
FIN PROGRAMA

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): "))

if 0 < n <=20: #Valida el rango para hacer sumatoria


suma += n
cantidad += 1
elif n != 0:
print("número fuera de rango!")

#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.

QUE se tiene El usuario digita la nota en el teclado.

COMO hacerlo Después de ingresar la nota (se debe validar el rango), se


buscara la letra equivalente. Se pueden usar estructuras de
decisión múltiple, anidadas. Pero, dado que es una sola
variable (nota) se utilizara la estructura de decisión CASO.

El algoritmo consta de los tres pasos elementales (E, P, S):

• Pedir la nota, validando


• Realizar la conversión
• Mostrar la letra equivalente

Pasando directamente a PYTHON se tiene:


#***************************************************
# 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: 1.0
# Fecha: 29/03/2019
# Cambios:
#***************************************************/

print("NOTA EQUIVALENTE")
print("")

nota = -1 #valor inicial, para entrar en el ciclo

#Pedir la nota, validando el rango

76
while nota < 0 or nota >20:
nota=int(input("Escriba la nota entre 1 y 20: "))

if nota >= 20:


letra ="A"
elif nota >= 16:
letra ="B"
elif nota >= 13:
letra ="C"
elif nota >= 10:
letra="D"
else:
letra="E"

print("La nota equivalente a {} es {}".format(nota, letra))

Para una nota valida:

Para nota fuera de rango, continua hasta ingresar un valor en el rango:

Observaciones al código anterior:

• Resaltar la forma como se utiliza la estructura CASO en Python, para cuando la


decisión se basa en una sola variable.
• En la ventana de salida, observe como se validan las notas y solo entrega el
resultado con notas en el rango especificado.

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:

QUE se quiere Validar el ingreso de los números y mostrar la nota


equivalente.

QUE se tiene El usuario digita los números en el teclado.

COMO hacerlo Este algoritmo se puede resolver uniendo los problemas 9 y


10, ya que en ellos se han resuelto las distintas partes
requeridas.

La lógica de conversión de numero a letras se separara en una función con parámetro


la nota numérica y que retorne el equivalente en letra.

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:
#***************************************************/

#Convierte la nota numerica en la letra equivalente


def convertir(n):
if nota >= 20:
letra ="A"
elif nota >= 16:
letra ="B"
elif nota >= 13:
letra ="C"
elif nota >= 10:

78
letra="D"
elif nota >= 1:
letra="E"
else:
letra = "O"
return letra

#Programa principal -------------------------------


print("NOTA EQUIVALENTE")
print("")

while True:
nota = -1 # un valor inicial, para entrar en el ciclo

# Pedir la nota, validando el rango, repite hasta que sea correcto


while nota < 0 or nota >20:
nota=int(input("Escriba la nota entre 1 y 20, 0 para salir: "))

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

Observaciones al código anterior:

• Se ha agregado al código documentación en forma de comentarios,


especialmente los docblocks al inicio del. Programa.
• La función convertir(n) recibe el numero y devuelve solo una letra.

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.

Problema 12. Tabla de multiplicar Desarrolle un algoritmo que muestres la tablas


de multiplicar del 1 al 10.

Análisis:

QUE se quiere Mostrar diez tablas, cada una multiplica hasta 10.

QUE se tiene Los números son generados en el algoritmo.

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.

Como conocemos la cantidad de ciclos (10), la estructura es PARA.

PARA (multiplicador <- 1 HASTA 10) HAGA


Mostrar la tabla
FIN PARA

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("**** TABLAS DE MULTIPLICAR ****")

for multiplicador in range(1, 11):


for multiplicando in range(1, 11):
print("{} X {} = {}".format(multiplicador,
multiplicando,(multiplicador * multiplicando)))

print(" ")

Nota: La instrucccion print es una sola línea.

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:
#*********************************************************/

print("**** TABLAS DE MULTIPLICAR ****")

for multiplicador in range(1, 11):


for multiplicando in range(1, 11):
print("{} X {} = {}".format(multiplicador,
multiplicando,(multiplicador * multiplicando)))
print(" ")
input("Ingrese una tecla para continuar...")
print(" ")

Problema 14. Tabla de Operaciones básicas Desarrolle un programa que solicite al


usuario el tipo de operación (entre las 4 operaciones básicas: suma, resta,
multiplicación y división) y un número. El programa debe mostrar la tabla para esta
operación, con los números de 1- 10. Debe repetir hasta que se pulse la letra “S”.

82
Análisis:

QUE se quiere Mostrar una tabla, con la operación seleccionada y el número


ingresado.

QUE se tiene El usuario escoge la operación (+, -, x, /) e ingresa un número.

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”.

Pasando lo anterior a seudocódigo:

/*Algoritmo que muestra la tabla con la operación


* seleccionada y el numero ingresado. Repite hasta
* que se pulse “S” */
INICIO PROGRAMA Operaciones
HAGA
Operación <- leerMenu()
Numero <- leerNumero()
mostrarTabla(operación, numero)
op <- continuarSalir()
HASTA (op= 'S' O op='s')
FIN PROGRAMA

Observaciones: El anterior es el seudocódigo del programa principal, cada bloque se


implementará como función; notar que la prueba lógica del HAGA –HASTA es una
función O (Hasta que se pulse la s en mayúscula o minúscula).

Seudocódigo de los módulos auxiliares:

1. Se debe presentar el menú, leer la opción del usuario y validar. Si el número


esta fuera del rango se debe repetir.

FUNCION leerMenu()
HAGA
Presentar opciones del menú
menu <- leerNumero()

83
HASTA (menu > 0 Y menu < 5)
Retorne menu
FIN FUNCION

2. Simplemente lee el multiplicando.

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.

PROCEDIMIENTO mostrarTabla(entero op, entero val)


CASO op
1: símbolo <- “ + ”
2: símbolo <- “ - ”
3: símbolo <- “ x ”
4: símbolo <- “ / ”
FIN CASO
PARA (i <- 1 HASTA 10) HAGA
res <- calcularOp(op, val, i)
Mostrar (val, símbolo, i, “ = ”, res )
FIN PARA
FIN PROCEDIMIENTO

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.

FUNCION calcularOp(operación, op1, op2)


CASO operacion
1: res <- op1 + op2
2: res <- op1 - op2
3: res <- op1 * op2

84
4: res <- op1 / op2
FIN CASO
Retorne res
FIN FUNCION

En este momento el seudocódigo tiene la suficiente claridad como para convertirlo al


código PYTHON. No olvide indentar adecuadamente, ya que es la forma como Python
reconoce las estructuras. Primero escriba la documentación general y el programa
principal:
#*********************************************************
# Programa OPERACIONES: Muestra la tabla con la operación seleccionada
# y el numero ingresado. Repite hasta que se pulse "S"
#
# Programo: MLM
# Version: 1.0
# Fecha: 30/03/2015
# Cambios:

#/**********************************************************

#**********************************************************
# PROGRAMA PRINCIPAL
#*********************************************************/
op = " "
while op != "s" and op != "S":
operacion = leerMenu()
numero = leerNumero()
mostrarTabla(operacion, numero)
op = continuarSalir()

Nota: Observe como la estructura HAGA - HASTA (op= 'S' O op='s'),del


seudocódigo debe invertirse, ya que la sintaxis de Python solo utiliza la estructura
MIENTRAS-QUE,y para que funcione de la misma manera hay que invertir la lógica de
la prueba.

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: Realice ajustes para que la pantalla sea más agradable.

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).

Problema 15. Estacionamiento Realice un algoritmo que calcule el monto a pagar


por el servicio de estacionamiento, teniendo en cuenta que la primera hora de
estadía tiene una tarifa de $1000 y las restantes tienen una de $600. Se tiene como
dato: Horas de entrada y salida en formato militar. Cualquier fracción de hora se
cobra como hora total.

Análisis:

QUE se quiere Mostrar el monto a pagar por el servicio de estacionamiento.

QUE se tiene Horas de entrada y salida, en formato militar.

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.

Habiendo aclarado estos puntos, ya podemos iniciar el diseño de nuestro algoritmo; a


nivel muy general tenemos tres partes (primer nivel de análisis):

• Ingresar Horas
• Calcular monto
• Mostrar monto

Ingresar Horas (segundo nivel de análisis):

• Leer hora entrada y validar


• Leer hora salida y validar

Calcular el monto (segundo nivel de análisis):

• Calcular cantidad de tiempo transcurrido entre las horas de entrada y salida,


aproximando minutos a siguiente hora.
• Calcular el monto, el valor de la primera hora es mayor a las demás horas.

Mostrar monto es inmediato y no requiere mayor análisis.

Para validar las horas de entrada y salida (tercer nivel de análisis):

• Repetir hasta que sea valido


o Valido = verdad (Se asume verdad y se cambiara si hay algún error)
o Ingresar hora

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)

Para la cantidad de tiempo transcurrido no se puede, simplemente, restar entrada de


salida, dado que el número esta en decimal y la hora militar va hasta 24 para la parte de
las horas y hasta 60 para la parte de los minutos. Después de hacer algunas pruebas se
encontró este algoritmo (tercer nivel de análisis):

• Pasar hora de entrada a minutos


• Pasar hora de salida a minutos
• Diferencia = minutos salida – minutos entrada
• Si diferencia < = 0 entonces a diferencia sumarle 1440 (los minutos de un día, ya
que el valor negativo significa que ha pasado de un día a otro)
• Encontrar las horas contenidas en Diferencia (múltiplos de 60 minutos)
• Si minutos restantes > 0 sumar uno a horas

Para calcular el monto (tercer nivel de análisis):

• Hacer monto = valor hora inicial


• Restarle a horas uno (la hora inicial)
• Si horas > 0 entonces sumarle a monto el total de horas multiplicado por valor
hora adicional

Observe como se ha ido subdividiendo el problema global en pequeños problemas,


cada uno de ellos más fácil de resolver, hasta llegar a descripciones triviales que
fácilmente se pueden traducir a un lenguaje de programación.

Repasemos que instrucciones del algoritmo aun no son evidentes. Tenemos, del
análisis de tercer nivel, las siguientes:

• Si (parte entera de hora/100) > 23 entonces valido = falso


• Si (resto de hora/100) > 59 entonces valido = falso
• Pasar hora (en formato militar) a minutos
• Encontrar las horas contenidas en Diferencia
• Si minutos restantes > 0 sumar uno a horas

Cuarto nivel de Análisis:

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:

• Si (hora // 100) > 23 entonces valido = falso


o Esta operación realiza la división entera, descartando decimales.
o Se divide en 100 para eliminar la parte de los minutos (últimos dos
dígitos).
• Si (hora %100) > 59 entonces valido = falso
o Esta operación devuelve lo que sobra de la división entera, es decir, el
resto. Módulo 100 para dejar solo la parte de los minutos del formato
militar.
• Pasar hora en formato militar a minutos
o De manera similar, se toma la parte de la hora y se multiplica por 60.
o Se toma la parte de los minutos y se le agrega al valor anterior.
• Encontrar las horas contenidas en Diferencia
o Usar //.
• Si minutos restantes > 0 sumar uno a horas
o Usar %.

Ahora sí, estamos en capacidad de escribir nuestro código. Usar funciones o


procedimientos para mantener el código modular, minimizando las variables de ámbito
global y manteniendo las demás aisladas en su ámbito local.

En PYTHON los módulos se codificaran antes del programa principal:

#**********************************************************
#*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 hora < 0: #//numeros negativos


valido = False

if (hora // 100) > 23: #//hora mayor a 23


valido = False

if (hora % 100) > 59: #//minutos mayor a 59


valido = False

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)

dif = hSale - hEntra


if dif <= 0:
dif += 1440 #Ajuste por cambio de día

horas = dif // 60 #Cantidad de horas

if dif % 60 > 0:
horas += 1 #Ajusta minutos restantes a otra hora

return (horas)

#/**********************************************************
# Calcula el valor del servicio de parquedero

# Calcula el valor del parqueo, a partir de la hora de entrada


# y la hora de salida. La primera hora vale $1000,
# las demas a $600. Minutos adicionales se aproximan a la hora
# PARAMETROS: entero hEntra, hSale
# RETORNA: entero monto
# EXCEPCION:
#

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.

Algunos criterios para decidir que puede separarse en otro modulo:

• “Zapatero a tus zapatos” , significa que cada módulo se enfocara en realizar la


acción para la que se definió. Otras funcionalidades deben separarse, a menos
que sean muy sencillas.
• Cuando se observe código repetido, intentar crear un solo modulo. Resulta en
código más compacto, más eficiente y más fácil de mantener, por ejemplo pedir
las horas.
• Cuando el código de un módulo se hace muy extenso (como criterio general, que
no se observe completo en la pantalla), se recomienda subdividirlo; es más fácil
entender su lógica cuando se observa completo.

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:

Control de errores en el ingreso de la hora:

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.

Reto: Puede refactorizar la función calcularMonto()? Refactorizar es modificar el código


para optimizarlo o simplificarlo, sin cambiar la funcionalidad del mismo.

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.

QUE se tiene Se digita la fecha como una cadena con formato


“dd/mm/aaaa”.

COMO hacerlo Primero, realizar una consulta en la web para encontrar un


algoritmo que realice el cálculo del día para una fecha dada.

En http://es.wikipedia.org/wiki/Congruencia_de_Zeller se encuentra el siguiente


desarrollo de la denominada congruencia de Zeller:

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

Para separar los componentes de la fecha, según el formato de entrada en PYTHON


basta indicar con subíndices el segmento de cadena que se convertirá a entero.

Enseguida se presenta el código PYTHON:


#/**********************************************************
# Muestra el nombre del dia correspondiente a una fecha.
# Programo: MLM
# Version: 1.0
# Fecha: 02/04/2019
# Cambios:
#**********************************************************/

#**********************************************************
# 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 }

datosFecha["nDia"] = int(fecha[0:2]) #Toma los dias


datosFecha["nMes"] = int(fecha[3:5]) #Toma los meses
datosFecha["nAnio"] = int(fecha[6:10]) #Toma los años

if datosFecha["nDia"] < 1 or datosFecha["nDia"] > 31:


datosFecha["ok"] = False
if datosFecha["nMes"] < 1 or datosFecha["nMes"] > 12:
datosFecha["ok"] = False
if datosFecha["nAnio"] < 1800:
datosFecha["ok"] = False

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.

Reto: La función “validarFecha()” devuelve dentro del diccionario un booleano indicando


si la fecha es correcta o no (“ok:”). Que pasara si se ingresan caracteres diferentes a
números?

Problema 17. Calendario Escriba un programa que muestre en pantalla el


calendario de un mes, dado el número del mes y el año, en formato “mm/aaaa”.

Análisis:

QUE se quiere Desplegar un cuadro con el mes correspondiente,


considerando el día que inicia y el número de días de ese
mes.

QUE se tiene Se digita el mes y el año con formato “mm/aaaa”.

COMO hacerlo Como siempre, el problema lo dividimos en varias partes:


• Ingresar el mes y el año, validando
• Encontrar el día correspondiente al primero del mes a
mostrar
• Generar el cuadro con el calendario del mes.

Para ingresar el mes y el año, modifiquemos un poco el procedimiento correspondiente


del problema anterior, quitando la parte del día, ya que siempre será el primero.

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.

Nos queda generar el cuadro con el calendario:

• 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).

Con el algoritmo descrito y ayudados por el código ya realizado para el problema


anterior, podemos pasar directamente a escribir el nuevo código. Claro, que hay que
corregir errores de Sintaxis y depurar, sobre todo las salidas hasta que se tenga una
presentación correcta:
#**********************************************************
# Muestra el calendario de un mes. Se ingresa el mes y el año
# Programo: MLM
# Version: 1.0
# Fecha: 03/04/2019
# Cambios:
#*********************************************************/

#**********************************************************
# 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 }

datosFecha["nMes"] = int(fecha[0:2]) #Toma los meses


datosFecha["nAnio"] = int(fecha[3:7]) #Toma los años

if datosFecha["nMes"] < 1 or datosFecha["nMes"] > 12:


datosFecha["ok"] = False
if datosFecha["nAnio"] < 1800:
datosFecha["ok"] = False

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)

mostrarCuadros(7-j) #muestra los cuadros faltantes de la ultima semana


print("|") #cierra el ultimo cuadro
print("|----|----|----|----|----|----|----|")

#/**********************************************************
# 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:

QUE se quiere Mostrar el nombre y el salario total para cada empleado. Al


final mostrar el valor de la nómina.

QUE se tiene Se digita el nombre y las horas extras de cinco empleados.

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.

Observación: La función “pagarSalario” realiza tres tareas diferentes, como se dijo


anteriormente, no es conveniente que un módulo realice varias cosas distintas -
“Zapatero a tus Zapatos”.

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:

QUE se quiere Después de pedir todos los datos, Mostrar el nombre y el


salario total para cada empleado y al final mostrar el valor de
la nómina.

QUE se tiene Se digita el nombre y las horas extras de cinco empleados.

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.

¿Cómo almacenar cinco nombres en cinco variables distintas, dentro de un ciclo en el


que se piden los datos de una persona?

La solución viene de la mano de los arreglos : Un arreglo es un grupo de variables del


mismo tipo, con un solo nombre. Para acceder a cada una de las variables individuales
se hace uso de los índices , estos pueden ser constantes o variables de tipo entero, ya
que sirven para indicar la posición dentro del arreglo. Veámoslo gráficamente:

En la figura anterior, la caja grande, que envuelve todo se ha denominado nombres.

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:

nombres[3] <- “Joaquín”

Para llevar el nombre “María” a otra variable, se usaría:

nombreEmpleado <- nombres[4]

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:

PARA i <- 1 HASTA 5 HAGA


nombres[i] <- “”
FIN PARA

Advertencia: En el ejemplo anterior, el primer elemento tiene el índice 1 (uno). La


mayoría de los lenguajes, incluido Python, definen el primer índice como 0 (cero).

Con este nuevo concepto, vamos a modificar el algoritmo del problema anterior:

• En un ciclo, solicitar, cinco veces el nombre y las HH de un empleado (ciclo


PARA), guardar en arreglos.
• En otro ciclo PARA, calcular el sueldo total sumando el sueldo básico y el valor
de las HH y mostrarlo para cada empleado. Acumular sueldos
• Por último, mostrar el monto de la nómina.

El código PYTHON queda así:


#/**********************************************************
# 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
# Version: 2.0
# Fecha: 31/03/2019
# Cambios:
#**********************************************************/

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))

Reto: Mejore la presentación de la pantalla.

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.

QUE se tiene Se digita el nombre y las horas extras de cinco empleados.

COMO hacerlo

Vamos a revisar como almacenar los datos:

• Los nombres y HH en dos arreglos, como ya se hizo.


• Se debe almacenar el nombre de cada denominación en otro arreglo,
para poder ensamblar las frases en la salida.
• También hay que mantener cada valor de billetes, la cantidad que será
entregada a cada empleado y la cantidad total requerida.
• Como estos últimos datos son de tipo entero, podríamos almacenarlos en
un arreglo de dos dimensiones .

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:

Veamos en la siguiente imagen el concepto de matriz:

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.

Reorganicemos las ideas:

• En un ciclo PARA, pedir los datos y almacenarlos en vectores.


• En otro ciclo PARA, calcular número de billetes, por denominación y por
empleado, acumular y mostrar.
• Mostrar el total de la nómina y el total de billetes de cada denominación

Revisando el código con la solución al problema anterior, se encuentra que es similar,


solo se cambia la forma como se almacena.

Queda un problema de algoritmia por resolver; ¿Cómo calcular la cantidad de billetes,


para una cifra dada?

Una solución se podría entender mejor con la siguiente imagen. En ella se ha supuesto
un monto a pagar de $187.000:

Primero se calcula el número de billetes de $50.000 (3 y queda pendiente $37.000).

Se agrega un billete de $20.000 (quedan $17.000).

Se agrega un billete de $10.000 (quedan $7.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

Notas al seudocódigo anterior:

• Son seis ciclos, uno por cada denominación.


• Billetes[1, i] toma el valor del billete (primera fila).
• Billetes[2, i] almacena la cantidad por empleado y denominación.
• Billetes[3, i] almacena la cantidad acumulada por denominación (toda la
nómina).
• Las operaciones MOD y DIV ya han sido explicadas.

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).

Ahora, el código PYTHON:


#/**********************************************************
# Muestra el valor a pagar por cada empleado con diferentes
# denominaciones y el valor total de la nómina.

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()

nombres = [] #Contendra los nombres de los empleados


hh = [] #Contendra las horas extrar de los empleados

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

#Se piden los datos


for i in range(5):
pedirDatos(nombres, hh) #los arreglos pasan por referencia

nomina = 0

#Calcula valor de cada salario y muestra total y denominaciones


for i in range(5):
nomina += pagarSalario(nombres[i], hh[i], billetes) #billetes pasa por
referencia, es array

print("") #Se muestra el total


print("El valor total de la nómina es ${:0,.2f}".format(nomina))

Reto: Por qué, la instrucción print("{} billetes de {},


".format(int(billetes[1][i]), denomBilletes[i]), end="") Incluye el comando
int() ?

Reto: En la función pagarSalario(), aparece un “numero magico”: el 800000. Si en


cualquier momento se desea cambiar el salario base, hay que buscar en el código para
encontrar esevalor. ¿Cómo podría mejorar esto?

Reto: aceptar decimales en las HH.

Reto: Refactorizar el código para reducir el espacio de variables.

La salida para el programa anterior:

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:

QUE se quiere Mostrar el promedio y la suma de los elementos de dos


vectores.

QUE se tiene Se digitan 20 números en dos vectores.

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))

Observaciones al código anterior:

• Como el algoritmo es corto, no se han desarrollado funciones.


• La variable promedio es de tipo real, ya que debe almacenar parte decimal.
• Analice el uso de la variable tamanoVector, ¿Para qué se usa?
• Si no se usara y el tamaño de los vectores cambiara a 20, ¿Qué habría que
hacer?
• ¿Que sucede si al ingresar los valores se digita algo diferente a un numero?

Reto: Consulte e implemente un procedimiento de validación para evitar el error


anterior.

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:

QUE se quiere Evitar números repetidos y que estén en el rango 1 a 10.

QUE se tiene Se digitan 8 números en un vector.

COMO hacerlo

• Ingresar cada número y validar que este en el rango 1 a 10.


• Revisar que no este entre los números ingresados previamente
• Si ya esta, volver a ingresar
• Etc. Hasta el octavo, verificando que no sea igual a los siete anteriores

¿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 = ", ")

Observaciones al código anterior:

• Observe la estructura try-except para capturar el error al ingresar los numeros.


• La ultima instrucción print() permite mostrar todo el contenido de la lista

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.

Reto: presente una advertencia cuando el número este repetido.

Problema 23. Múltiplos de tres. Hacer un algoritmo que registre en un arreglo de


una dimensión, 20 números no repetidos generados aleatoriamente y que muestre
los que sean múltiplos de 3.

Análisis:

QUE se quiere Con los números de un arreglo, mostrar los múltiplos de tres.

QUE se tiene Se genera aleatoriamente 20 números no repetidos.

COMO hacerlo

• Generar aleatoriamente 20 números y almacenarlos en el vector,


validando que no estén repetidos. Como no se dice otra cosa, se
generarán en el rango de 1 a 100.

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

Usaremos como base el código anterior, cambiando lo necesario:


#/**********************************************************
# registra en un arreglo de una dimensión, 20 números no repetidos
# generados aleatoriamente y muestra los que sean múltiplos de 3.
# Programo: MLM
# Version: 1.0
# Fecha: 01/04/2019
# Cambios:
#**********************************************************/
import random #libreria para numeros aleatorios

#/**********************************************************
# 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
return rep

#/**********************************************************
# 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

#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

# Mustra todo el vector


print("El vector es: [", end="")
print(*vectorA, sep = ", ", end="")
print("]")
#Buscar los multiplos de tres
print("Los multiplos de 3 son:")
for i in range(tamVector):
if vectorA[i] % 3 == 0:
print(vectorA[i], end=", ")
print("")

Observaciones al código anterior:

• Observe la librería necesaria para ulilizar la función random.randrange(1, 100).


• Revise los paramatros agregados a la función print(), ¿para qué se usan?

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:

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.

Y en cada caso, mostrar la parte de matriz recorrida.

Análisis:

QUE se quiere Recorrer la matriz en las formas indicadas.

QUE se tiene Se genera aleatoriamente 49 números no repetidos y se


cargan en una matriz.

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:

• Diagonal principal: Para cada valor de f (fila), c (columna) tendrá el mismo


valor. Se puede usar un solo ciclo PARA

PARA f <- 1 HASTA 7 HAGA


Matriz[f, f]
FIN PARA

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

La función puede ser 𝑐 = 8 −𝑓

PARA f <- 1 HASTA 7 HAGA


Matriz[f, 8-f]
FIN PARA

• matriz superior derecha (con diagonal principal):


Valores:

f c
1 1…7
2 2…7
3 3…7
4 4…7
5 5…7
6 6…7
7 7

La función puede ser 𝐶 = 𝐹…7

PARA f <- 1 HASTA 7 HAGA


PARA c <- f HASTA 7 HAGA
Matriz[f, c]
FIN PARA
FIN PARA

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 -

La función puede ser 𝐶 = (𝐹 + 1) ℎ𝑎𝑠𝑡𝑎 7

PARA f <- 1 HASTA 7 HAGA


PARA c <- f+1 HASTA 7 HAGA
Matriz[f, c]
FIN PARA
FIN PARA

• matriz inferior izquierda:


Valores:

f c
1 1
2 1…2
3 1…3
4 1…4
5 1…5
6 1…6
7 1…7

La función puede ser 𝐶 = 1…𝐹

PARA f <- 1 HASTA 7 HAGA


PARA c <- 1 HASTA f HAGA
Matriz[f, c]

127
FIN PARA
FIN PARA

• matriz superior izquierda:


Valores:

f c
1 1…7
2 1…6
3 1…5
4 1…4
5 1…3
6 1…2
7 1

La función puede ser 𝐶 = 1 … (8 − 𝐹)

PARA f <- 1 HASTA 7 HAGA


PARA c <- 1 HASTA 8-f HAGA
Matriz[f, c]
FIN PARA
FIN PARA

• matriz inferior derecha:


Valores:

f c
1 7
2 6…7
3 5…7
4 4…7
5 3…7
6 2…7
7 1…7

La función puede ser 𝐶 = (8 − 𝐹) … 7

PARA f <- 1 HASTA 7 HAGA


PARA c <- 8-f HASTA 7 HAGA

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:

QUE se quiere Visualizar un número usando caracteres predefinidos.

QUE se tiene se ingresa un número de cuatro dígitos.

COMO hacerlo

En primer lugar definamos las formas de los dígitos, en matrices de 5x3:

Se almacenara en una matriz de tres dimensiones: digito[10, 5, 3]. El primer subíndice


hace referencia al digito 0, etc. hasta el 9. Las otras dos dimensiones son las filas y
columnas de cada digito. ¿Cómo se define en PYTHON?

Ahora, pensemos en el algoritmo:

• Ingresar un número validando que sea de cuatro dígitos.


• Hay que separar los dígitos en variables (mejor en un vector de 4 enteros para
facilitar las iteraciones).
• Ahora, proceder a “dibujar”
o Dibujar la primera fila, la segunda…hasta la quinta: Estructura PARA de 1
a 5.
o En cada fila dibujar las tres columnas de cada digito, dejando un espacio
(otro ciclo PARA de 1 a 4).

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.

Veámoslo todo gráficamente:

Pasemos al código PYTHON:

#/**********************************************************
# 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)

Observaciones al código anterior:

• No hay necesidad de declarar una variable de tres dimensiones, puesto que la


última dimisión es una cadena.
• Al llamar las funciones, se pasan los parámetros por referencia (en Python las
listas pasan por referencia.
• Al llenar la matriz, en cada fila se agregan espacios en blanco, para que no queden
unidos los números al presentarlos.
• En el procedimiento leerNumero() se lee el numero y se extraen los caracteres
según su posicion.

138
• Si lo ingresado no es numérico, se repite.

Salida del programa, se observa que repite el ingreso si el número no es válido.

Reto: Los números se visualizan muy alargados, ¿qué modificaria para que sean mas
anchos?

Sugerencia: Caracteres ASCII extendidos.

Reto: Modificar el algoritmo para que no muestre ceros a la izquierda.

Reto: Modificar el algoritmo para que acepte números desde uno a seis dígitos (rango
entre 0 y 999999).

Reto: Al reto anterior, agregar la visualización del punto de miles (999.999).

Problema 26. Visualización vertical de números. Modifique el programa de


visualización de números para que los muestre de manera vertical (inclinada 90°)

Análisis:

QUE se quiere Visualizar los números de manera vertical

QUE se tiene se ingresa un número de cuatro dígitos.

COMO hacerlo Solo se debe cambiar en el algoritmo anterior, la función


mostrarNumero().

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

A diferencia de la aritmética de base decimal (10), que manejamos intuitivamente


desde la experiencia de toda la vida. Otras bases numéricas: Binaria (2), Octal (8),
Hexadecimal (16), utilizadas en el ámbito de la computación, no son tan evidentes o
parecen de difícil comprensión. Pero todas tienen un comportamiento similar y los
algoritmos para pasar un valor de una base a otra son los mismos.

En primer lugar, comprender cómo se logra obtener un valor en una base dada:

• El “peso” de un digito es el valor por el que se multiplica para obtener su “aporte”


al valor total del número.
• El digito de la derecha tiene un peso de 1, que equivale a 𝑏 0 , siendo b el valor
de la base (2, 8, 10, 16, etc.).
• El digito en la segunda posición, desde la derecha, tiene un peso de 𝑏1 , es decir,
se debe multiplicar por 2, 8, 10, 16, dependiendo de la base.
• Cada digito adicional, a la izquierda tiene un peso de 𝑏 𝑛 , siendo n su posición
iniciando en cero a la derecha.

Un ejemplo en base 10: supongamos el número 5482, se descompone en 2x1 + 8x10 +


4x100 + 5x1000. En idioma natural se evidencia este concepto: “cinco mil
cuatrocientos ochenta y dos”.

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.

Encontrar el equivalente en decimal de un valor dado en otra base.

Ejemplos: En base octal, el número 74538 se descompone en

• = 3x80 + 5x81 + 4x82 + 7x83


• = 3x1 + 5x8 + 4x64 + 7x512
• = 3 + 40 + 256 + 3584

143
• = 388310 (El subíndice indica la base del número)

El número Hexadecimal F2B16:

• = Bx160 + 2x161 + Fx162


• = 11x1 + 2x16 + 15x256
• = 11 + 32 + 3840
• = 388310

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:

• El valor acumulado inicia en cero y la potencia inicia en 1 (X0=1)


• Recorrer todos los dígitos, desde el de menor peso (a la derecha)
o Tomar el digito multiplicado por la potencia y agregarlo al valor
o Calcular la siguiente potencia

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

Encontrar el equivalente en una base dada, de un valor en decimal.

Un algoritmo para convertir de decimal a otra base:

• Se toma el número y se divide (división en enteros) en la base.


• El resto (modulo) es el digito de menor peso (el de la derecha)
• Se repite la división y se va colocando el resto a la izquierda de los obtenidos
antes, hasta llegar a cero

144
Ejemplo: Convertir a binario el número 321110 :

Divisiones por 2:

Numero Resto Cociente


3211 1 1605
1605 1 802
802 0 401
401 1 200
200 0 100
100 0 50
50 0 25
25 1 12
12 0 6
6 0 3
3 1 1
0 1 0

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.

1100100010112 equivale a 321110

Observando en la tabla anterior:


• se inicia con un valor inicial en “Numero”,
• se divide (división de enteros) entre la base a la cual se va a convertir,
• Lo que sobra (resto), va generando el resultado (se agregara por la izquierda)
• El cociente se convierte en el nuevo valor de “Numero”.
• Repetir (desde el segundo paso) hasta que el cociente sea cero.

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

En el algoritmo anterior, la variable número (convertido) es tipo cadena.

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:

QUE se quiere Un número convertido en la base seleccionada

QUE se tiene se ingresa un número (decimal) y se escoge la base a la que


se ha de convertir.

COMO hacerlo Aplicar los algoritmos estudiados anteriormente.

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))

#Fin del codigo ***********************************************

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

QUE se tiene se ingresa un número en la base seleccionada.

COMO hacerlo Aplicar los algoritmos estudiados anteriormente y analizar la


conversión de otra base a 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))

#Fin del codigo ***********************************************

Pantallas de salida para las distintas bases

152
Reto: Realice cambios al código para poder ingresar las letras también en minúscula
cuando se use la base hexadecimal.

Reto: Reúna en un solo programa los dos problemas anteriores.

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.

Ventajas de los algoritmos recursivos

En los problemas esencialmente recursivos su implementación se facilita mediante un


algoritmo recursivo, sin tener que implementar un método iterativo. En dichas ocasiones
el código es más pequeño.

La recursividad es una técnica de programación muy potente que permite diseñar


algoritmos que dan soluciones elegantes y simples y generalmente bien estructuradas y
modulares, a problemas de gran complejidad.

Desventajas de los algoritmos recursivos

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.

Consideraciones para diseñar un algoritmo recursivo

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.

Problema 29: Máximo Común Divisor. Diseñar un algoritmo recursivo que


muestre el Máximo Común Divisor (MCD) de dos enteros dados.

Análisis:

QUE se quiere Encontrar el MCD de dos números

QUE se tiene se ingresan dos enteros.

COMO hacerlo Primero, consultar en que consiste el Máximo Común Divisor


(MCD).

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

Es decir, sea a y b los números enteros, se presentan cuatro posibilidades:


• Si a es mayor que b, entonces encontrar el MCD de a-b, b (recursividad).
• Si b es mayor que a, entonces encontrar el MCD de a, b-a (recursividad).
• Si b es igual a cero, entonces el MCD es a (caso base)
• Si a es igual a cero, entonces el MCD es b (caso base)

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))

#Fin del codigo ***********************************************

157
Reto: Ejecute el programa paso a paso y observe el funcionamiento recursivo.

Problema 30: Potencia de un Número. Diseñar un algoritmo recursivo que


muestre el resultado de un número elevado a un exponente.

Análisis:

QUE se quiere Mostrar N elevado a la E

QUE se tiene se ingresan dos enteros, el número y el exponente.

COMO hacerlo La potencia de un número se define como el resultado de


multiplicar el número por sí mismo, tantas veces como el valor
del exponente, cuando el exponente es cero, la potencia vale
1.

Pensando recursivamente, encontramos que:

• El caso base se presenta cuando el exponente es 0, con resultado inmediato 1.


𝒏𝟎 = 𝟏.
• Los casos recursivos se encuentran como 𝒏𝑬𝒙𝒑 = 𝒏 ∗ 𝒏𝑬𝒙𝒑−𝟏

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 ------")

base = leerNumero("Ingrese el numero base:")


exp = leerNumero("Ingrese el exponente: ")
resultado = calcularPotencia(base, exp)
print()
print("El resultado de elevar {} a la potencia {} es {}".format(base, exp,
resultado))

#Fin del codigo ***********************************************

Salida:

Algoritmo mejorado:

Aprovechando la propiedad asociativa de la multiplicación, se puede reducir el número


de llamadas recursivas.

159
Ejemplo: 28 = 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2

También 28 = (2 ∗ 2 ∗ 2 ∗ 2) ∗ (2 ∗ 2 ∗ 2 ∗ 2)

En el caso de un exponente impar: 29 = (2 ∗ 2 ∗ 2 ∗ 2) ∗ (2 ∗ 2 ∗ 2 ∗ 2) ∗ 2

En los ejemplos anteriores basta resolver (2 ∗ 2 ∗ 2 ∗ 2).

El seudocódigo:

FUNCION potencia (n, p)


INICIO
SI p=0 ENTONCES
pot <- 1
SINO
SI p MOD 2 = 0 ENTONCES
pot <- potencia(n, p/2)
pot <- pot * pot //dos veces
SINO
pot <- potencia(n, (p-1)/2)
pot <- pot * pot * n //dos veces más uno
FIN SI
FIN SI
RETORNE pot
fin

En PYTHON solo hay que cambiar esta función:


#/**********************************************************
# Encuentra el Maximo comun divisor entre dos numeros
# utiliza un algoritmo recursivo mejorado
# PARAMETROS: entero base, exp
# RETORNA: entero result
# EXCEPCION:
#*********************************************************/
def calcularPotencia(n, p):
if p == 0:
pot = 1
elif (p % 2) == 0:
pot = calcularPotencia(n, p/2)
pot *= pot
else:
pot = calcularPotencia(n, (p-1)/2)
pot *= pot * n

160
return pot

Reto: A los dos programas anteriores, agregue una variable que cuente el numero de
iteraciones recursivas y compare los resultados.

Reto: Realice un algoritmo recursivo para calcular la serie de Fibonacci de un número.

Reto: Realice un algoritmo recursivo para calcular el factorial de un número.

Reto: A manera de comparación, realice todos los problemas recursivos anteriores,


como algoritmos iterativos.

161
10. ALGORITMOS DE BUSQUEDA Y ORDENAMIENTO

Todo buen programador debe tener en su “caja de herramientas (ToolBox)” un buen


arsenal de métodos probados y efectivos al enfrentar el diseño de un algoritmo; en este
capítulo estudiaremos algunos para ordenar y buscar en arreglos.

Búsqueda de un elemento

Problema 31: Número mayor. Poblar un vector de 20 enteros con números


aleatorios, mostrar el contenido y determinar en qué posición del vector está el
número mayor.

Análisis:

QUE se quiere Mostrar el vector y la posición del elemento mayor.

QUE se tiene El algoritmo genera los valores en el vector.

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.

Vamos directo al código PYTHON pues ya hay suficiente claridad en el algoritmo y


anteriormente se ha resuelto parte de él.

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)

print("El numero mayor es {}, se encuentra en la posicion


{}".format(vector[posMayor], posMayor))

#Fin del codigo ***********************************************

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 la función buscarMayor() para usar solo la variable pos.

Reto: Modifique el algoritmo para generar números negativos, digamos entre -100 y 100.

Problema 32: Números mayor y menor. Poblar un vector de 20 enteros con


números aleatorios, mostrar el contenido y buscar entre ellos el menor y el mayor,
mostrar sus posiciones.

Análisis:

QUE se quiere Mostrar el vector y las posiciones de los elementos mayor y


menor.

QUE se tiene El algoritmo genera los valores en el vector.

COMO hacerlo Es fundamentalmente el mismo problema anterior. Solo hay


que agregar las respectivas variables para el número menor.

Generar números aleatorios

Como demostración adicional, generaremos números aleatorios entre -100 y 100.

#/**********************************************************
# 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]

for i in range(1, len(vector)):


if(vector[i] > mayor):
posMayor = i
mayor = vector[i]

if(vector[i] < menor):


posMenor = i
menor = vector[i]

return posMayor, posMenor

#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/

165
print()
print("------ BUSCA EL NUMERO MAYOR ------")
vector = generarNumeros()
posMayor, posMenor = buscarPosMayorMenor(vector)

print()
print("Contenido del vector:")
print(vector)

print("El numero mayor es {}, se encuentra en la posicion


{}".format(vector[posMayor], posMayor))
print("El numero menor es {}, se encuentra en la posicion
{}".format(vector[posMenor], posMenor))

#Fin del codigo ***********************************************

Observaciones al código anterior:

• El procedimiento generarNumeros() ha cambiado para obtener el nuevo rango.


• La función de búsqueda ahora debe retornar dos números. Como una alternativa
a retornar un arreglo con los dos valores, Python permite retornar mas de una
variable independiente, hay que tener en cuenta el orden para recuperarlas en la
llamada, desde el programa principal. Otra opción es utilizar estructuras de
datos , que se verán posteriormente (en Python “diccionarios”).
• Al inicio del algoritmo de búsqueda, se asume que el valor en la primera posición
es a su vez mayor y menor. Se hace el recorrido del vector desde la segunda
posición.

Problema 33: Ordenar vector. Poblar un vector de 20 enteros con números


aleatorios y ordenarlos de menor a menor. Mostrar el vector antes y después de
ordenarlo.

Análisis:

166
QUE se quiere Mostrar el vector antes de y después de ordenarlo.

QUE se tiene El algoritmo genera los valores en el vector.

COMO hacerlo Hay que ordenar el vector, lo demás ya se ha tratado y


usaremos el código del problema anterior, adaptándolo.

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.

Método de ordenamiento por “burbuja”

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

86 -26 -44 -81 -31 34 Inicial


-26 86 -44 -81 -31 34
-26 86 -44 -81 -31 34
-44 86 -26 -81 -31 34
-44 86 -26 -81 -31 34
-81 86 -26 -44 -31 34
-81 86 -26 -44 -31 34
-81 86 -26 -44 -31 34
-81 86 -26 -44 -31 34

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:

• Sea N el número de elementos (tamaño del arreglo, en este ejemplo es 6), El


elemento “pivote” es el que compara con los siguientes (rosado en la gráfica) y en
el caso de ser mayor, se realiza un intercambio.
• Entonces, el “pivote” se desplaza en un ciclo PARA desde 1 hasta N-1.No es
necesario llegar hasta N, pues ya no tendría con quien comparar.
• Los elementos contra quienes se compara el pivote son los siguientes a él. Se
toman en un ciclo PARA desde “pivote” +1 hasta N.

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):

for i in range(0, len(vector)-1):


for j in range(i+1, len(vector)):
if vector[i] > vector[j]:
aux = vector[j]
vector[j] = vector[i]
vector[i] = aux

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)

#Fin del codigo ***********************************************

Ejemplo de una corrida del programa:

Reto: Como el ejercicio anterior, pero ordenados de mayor a menor.

Búsqueda binaria

Problema 34: Búsqueda Binaria. Buscar un número en un vector ordenado, decir


si se encuentra y en qué posición.

Análisis:

QUE se quiere Si se encuentra el número, Indicar la posición.

QUE se tiene El algoritmo genera los valores en el vector. Se ingresa el


número.

COMO hacerlo Hay que generar el vector, ordenar (usar método anterior) y
buscar el valor, realizando una búsqueda binaria.

Cuando un vector se encuentra ordenado es más eficiente realizar la búsqueda de un


elemento ya que no hay necesidad de comparar el valor buscado con todos los

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

En la figura se tiene un arreglo ordenado de 10 elementos enteros y se desea buscar el


valor -30. Se definen los extremos del vector en el que se realizara la búsqueda, 𝑖𝑛𝑓 =
1, 𝑠𝑢𝑝 = 10, es decir, la primera vez es todo el vector. Se elige el elemento medio como
(𝑠𝑢𝑝 − 𝑖𝑛𝑓)
𝑖𝑛𝑓 + (división entera) y se compara contra este. Si el valor buscado es mayor
2
que el elemento medio, entonces la posición del elemento medio más uno pasa a ser el
nuevo límite inferior y se repite el proceso. Si es menor, el elemento medio menos uno
será el nuevo límite superior. Si los valores son iguales, entonces se ha encontrado el
elemento y se termina la búsqueda. Si el límite inferior es mayor al superior entonces no
se encuentra el valor en el arreglo.

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

En el caso anterior, el elemento si se encuentra. ¿Cuántas comparaciones se


efectuaron? ¿Cuántas se efectuarían si fuese una búsqueda lineal desde el primer
elemento?

En síntesis, el método de búsqueda binaria, va descartando segmentos del vector, por


encima o por debajo del valor buscado.

Ya comprendido el procedimiento, vamos a traducirlo a un algoritmo. Hay dos opciones


(al menos): Un algoritmo iterativo o uno recursivo.

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):

for i in range(0, len(vector)-1):


for j in range(i+1, len(vector)):
if vector[i] > vector[j]:
aux = vector[j]
vector[j] = vector[i]
vector[i] = 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:

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: ")

posNum = buscarBinario(vector, num)

if(posNum > -1):


print("El numero {} se encuentra en la posicion {}.".format(num, posNum))
else:
print("El numero {} no se encuentra en el vector.".format(num))

#Fin del codigo ***********************************************

174
Nota: Observe la llamada a dos funciones en una sola línea de código, ¿Cómo funciona?.

Reto: Estudie que pasa si el valor no se encuentra.

Reto: ¿Por qué es necesaria la instruccion if inf == len(vector)y que hace?

Reto: El método “buscarBinario” no cumple estrictamente las exigencias de la


programación estructurada. ¿Por qué?

Reto: Realizar el algoritmo recursivo para el caso anterior.

Salidas del programa:

Método de ordenamiento por selección

En este método, a diferencia del de burbuja, no se realiza el intercambio en cada


comparación, pero se guarda el valor si es menor y su posición. Al final del recorrido para
cada pivote, se realiza el intercambio, si es necesario.

En la siguiente figura, además de los apuntadores para el pivote (i) y el elemento a


comparar (j), se utiliza la variable menor, que guarda el valor menor encontrado en ese

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

Problema 35: Ordenar vector por el método de selección. Poblar un vector de 10


enteros con números aleatorios y ordenarlos de menor a mayor utilizando el
método de selección. Ingresar un número y decir en qué posición esta, si no se
encuentra, decir en qué posición debería estar.

Análisis:

176
QUE se quiere Si se encuentra el número, Indicar la posición, sino, indicar
donde debería estar.

QUE se tiene El algoritmo genera los valores en el vector. Se ingresa el


valor a buscar.

COMO hacerlo Hay que generar el vector, ordenar (usar método selección) y
buscar la posición del valor.

• El vector se genera, como se ha visto en problemas anteriores.


• El ordenamiento por selección:
o Un bucle externo para los pivotes, i desde 1 hasta N-1
▪ guardar el valor y posición del pivote, como el menor
▪ Un bucle interno para comparar, desde i+1 hasta N
• Si el elemento es menor, guardar su valor y posición
▪ Al terminar el bucle interno, intercambiar si es necesario
• Ingresar el valor a buscar
• Realizar búsqueda binaria (ya se desarrolló, ajustar para caso que no se
encuentre)
• Mostrar posición del valor dentro del arreglo.

Veamos el algoritmo de ordenamiento por selección:

//N es la última posición del arreglo


PARA i <- 1 HASTA N -1 HAGA
pos <- 1
menor <- vec[i]
PARA j <- i+1 HASTA N HAGA
SI vec[j] < menor ENTONCES
pos <- j
menor <- vec[j]
FIN SI
FIN PARA
SI vec[i] > menor ENTONCES
Intercambiar (vec[i], vec[pos])
FIN SI
FIN PARA

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]

if vector[i] > menor:


aux = vector[i]

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)

while inf <= sup:


pos = (inf + sup)//2
if vector[pos] == num:
return pos, True
elif num < vector[pos]:
sup = pos -1
else:
inf = pos + 1
if inf == len(vector):
pos += 1
break

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: ")

#devuelve true o false si lo encontro o no y posicion.


posNum, encontrado = buscarBinario(vector, num)

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))

#Fin del codigo ***********************************************

Casos de un número que no está en el vector, se presenta para la primera, intermedia y


ultima posicion:

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.

Método de ordenamiento por Inserción

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:

QUE se quiere Realizar el ordenamiento por el método de Inserción.

QUE se tiene El algoritmo genera los valores en el vector.

COMO hacerlo El ordenamiento por Inserción es una manera muy similar a


como una persona ordena, por ejemplo, un mazo de cartas.

• Inicialmente se tiene solo el primer elemento, que obviamente esta ordenado.


• Después, cuando hay k elementos ordenados de menor a mayor, se toma el
elemento k+1 (el pivote) y se va comparando con todos los elementos ya
ordenados, hacia la izquierda:
o Si el pivote es menor, se desplaza el elemento comparado una posición a
la derecha.
o Si se llega al elemento 1, se inserta ahí el pivote (es el menor de todos).
o Si el pivote es mayor, se inserta en la posición a la derecha del elemento
comparado y se termina este ciclo de comparación. Esta posición estará
“libre”, pues en el paso anterior se corrió a la derecha.

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

86 -44 -81 -31 34

-26 86 -44 -81 -31 34

-44

-26 86 -81 -31 34

-44 -26 86 -81 -31 34

182
-81

-44 -26 86 -31 34

-81 -44 -26 86 -31 34

-31

-81 -44 -26 86 34

-81 -44 -31 -26 86 34

34

-81 -44 -31 -26 86

-81 -44 -31 -26 34 86

A partir de lo explicado y lo mostrado en la imagen, pasemos a escribir el seudocódigo:

//N es la posición del último elemento


PARA i <- 2 HASTA N HAGA
pivote <- vec[i]
p <- i-1
MIENTRAS (p >= 1 Y pivote < vec[p]) HAGA
vec[p+1] <- vec[p]
p <- p-1
FIN MIENTRAS
SI p= 0 ENTONCES //Es el primer elemento
p <- 1
FIN SI
//Inserta pivote en su nueva posición
SI pivote < vec[p] ENTONCES
vec[p] <- pivote
SINO
vec[p+1] <- pivote
FIN SI
FIN PARA

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)

#Fin del codigo ***********************************************

Pantalla de salida.

Reto: Reunir los tres métodos de ordenamiento en un solo programa y analizar la


cantidad de comparaciones y de intercambios realizados en cada uno, para el mismo
vector inicial; aumente el tamaño del vector a, por ejemplo 5000 o mas. Agregue variables
para contar los ciclos de comparación e intercambio y capture la hora antes y después
de la llamada a las funciones de ordenamiento, visualice la diferencia y determine cual
método es mejor para vectores grandes.

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:

QUE se quiere mostrar los datos de un estudiante.

QUE se tiene Se ingresaran los datos por teclado. Se digita el número de


matrícula del estudiante a consultar.

COMO hacerlo Aunque los datos pueden ser guardados en matrices, es


mucho más intuitivo utilizar agrupaciones de datos
denominados registros .

Estructura de datos tipo Registro

Un Registro es un grupo de datos (comúnmente de distinto tipo) o de otros registros,


relativos a una entidad u objeto . Las unidades de datos que componen un registro se
denominan campos y tanto al registro como a cada uno de los datos se le asigna un
nombre.

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):

• Número de registro (numMatricula, entero).


• Nombre del estudiante (nombre, cadena
• Sexo del estudiante (sexo, carácter)
• Carrera cursada (carrera, cadena
• Semestre en curso (semestre, entero)

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).

Se puede pensar en un diccionario como un conjunto no ordenado de pares clave: valor,


con el requerimiento de que las claves sean únicas dentro de un diccionario en particular.
Un par de llaves crean un diccionario vacío: {}. Colocar una lista de pares clave:valor
separados por comas entre las llaves añade pares clave:valor iniciales al diccionario.

Asi se codificaría en Python un diccionario:

alumno = {
“numMatricula”:456789
“nombre”: “Juan Perez”
“sexo”: “M”
“carrera”: “Ingenieria Mecánica”
“semestre”: 6
}

El anterior segmento de código PYTHON declara un dictionary, denominado alumno,


compuesta de cinco pares (campos); cada campo define su tipo de datos según el tipo
de datos del valor asignado.

El algoritmo para el problema planteado quedara:

• Declarar la lista que contendrá los cinco registros y un diccionario vacio


• Capturar los datos de los usuarios
• ingresar un numero de matrícula y buscar en la lista de estudiantes, si se
encuentra se mostraran sus datos.

Para reutilizar código, construyamos un procedimiento que presente la pantalla con un


formulario para los datos. Este formulario puede usarse tanto para el ingreso como para
la consulta. Haremos uso de la librería TKinter, que viene por defecto en Python y no
requiere instlacion adicional, con ella construiremos una interfaz gráfica de usuario (GUI)
para tener mayor interaccion con el programa. La documentación de esta librería la
puede encontrar en https://docs.python.org/3/library/tk.html

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.

El código completo en PYTHON:


#/**********************************************************
# Programa que lee los datos básicos de cinco estudiantes
# (número de matrícula, nombres, sexo, carrera, semestre)
# y muestra los datos de un estudiante particular a partir
# del número de matrícula.
# Utilza la libreria Tkinter para la interfaz grafica
# Los datos de los estudiantes se digitan al inicio
# Programo: MLM
# Version: 1.0
# Fecha: 10/06/2019
# Cambios:
#**********************************************************/
from tkinter import *

#**********************************************************
# Limpia los campos del formulario
# PARAMETROS:
# EXCEPCION:
#**********************************************************
def limpiarForm():
numMatricula.set("")
nombre.set("")
sexo.set("")
carrera.set("")
semestre.set("")

# MANEJADORES DE EVENTOS CLICK


#**********************************************************
# Se ejecuta al pulsar el boton btnGuardar
# crea un diccionario, toma los datos del formulario
# inserta el diccionario en la lista y lo limpia
# PARAMETROS: lista se define global.
# RETORNA:
# EXCEPCION:
#**********************************************************
def btnGuardarclicked():
global lista
#diccionario:

188
alum = {'numMatricula': "", 'nombre':"", 'sexo':"", 'carrera': "",
'semestre': ""}

#lee los datos del formulario


alum['numMatricula'] = numMatricula.get()
alum['nombre'] = nombre.get()
alum['sexo'] = sexo.get()
alum['carrera'] = carrera.get()
alum['semestre'] = semestre.get()

#Agrega registro (diccionario) a la lista


lista.append(alum)

#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

#Si lo encontro, lo muestra


if posEsta > -1:
numMatricula.set(lista[posEsta]['numMatricula'])
nombre.set(lista[posEsta]['nombre'])
sexo.set(lista[posEsta]['sexo'])
carrera.set(lista[posEsta]['carrera'])
semestre.set(lista[posEsta]['semestre'])

189
else:
nombre.set('Matricula no encontrada')
return

#/**********************************************************
# PROGRAMA PRINCIPAL
#**********************************************************/
#La lista que contendra los registros
lista =[]

# Define la interfaz grafica


gui = Tk()
gui.title("Datos de Estudiantes")
gui.geometry("400x300")
gui.config(bg="grey")
contenedor = Frame()
contenedor.pack()
contenedor.config(width="350", height="280", pady=20 , padx=20 )

#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)

#variables asociadas a las cajas de entrada


numMatricula = StringVar()
nombre= StringVar()
sexo = StringVar()
carrera= StringVar()
semestre= StringVar()

#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)

# crea botones y asigna el evento click a manejador


btnGuardar = Button(contenedor, text="Guardar ", width=15, pady=10,
command=btnGuardarclicked)
btnGuardar.grid(column=0, row=6)
btnConsultar = Button(contenedor, text="Consultar ", width=15, pady=10,
command=btnMostrarclicked)
btnConsultar.grid(column=1, row=6)

#ciclo infinito, en espera de eventos en la interfaz grafica


gui.mainloop()
#Fin del codigo ***********************************************

Observaciones al código anterior:

• La variable lista, definida en el el programa principal, se manipula en las


funciones, declarándola como global.

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.

EJERCICIOS CON C# (C Sharp)

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.

• En la siguiente pantalla, seguir estos pasos (siga las flechas en la imagen


siguiente):
o A. En “plantillas”, seleccionar “Visual c#”.
o B. Clic en “Aplicación de consola”
o C. Asigne un nombre al proyecto2
o D. Indique la dirección donde desea guardarlo.
o E. Cuando es el primer Proyecto de una Solución (en aplicaciones simples
utilizaremos un solo proyecto), la solución tomara el mismo nombre.
o F. Marque el “Check Box” para que se cree un directorio para la solución.
o G. Por último, pulsar “Aceptar”.

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

Problema 38: Guardar Datos. Al problema 37 se deben agregar funciones para


almacenar los datos en el disco al terminar el programa y cargarlos al iniciar el
mismo.

Análisis:

QUE se quiere Almacenar los registros y leerlos en y desde el disco duro.

QUE se tiene El programa funcional manipulando los datos en memoria.

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

La serialización es el proceso de trasladar los datos desde la memoria del programa a


una secuencia de bytes para guardarlos en un medio de almacenamiento o para
transmitirlos a otro dispositivo.

Persistencia de Datos

El concepto de persistencia de Datos no es más que la capacidad de poder restaurar


el estado de las variables al ejecutar una nueva corrida de un programa, es decir, hacer
que los datos “persistan” entre varias corridas (que su valor al momento de terminar la
primera corrida sea recuperado al inicio de la siguiente). Es evidente que para lograrlo
se hace uso de la serialización.

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;

using System.IO; //Agregar para usar metodo comprobar si existe un archivo

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

static int indice, matricula; // Numero del registro y de la matricula


static char ingresoDato; // Para escoger opcion por teclado
static string nombreArchivo; // Para leer/escribir. Nombre del archivo

//Se declara un arreglo de 5 registros del tipo reg_alumno


static int numRegs = 5; //tamano del arreglo
static reg_alumno[] estudiantes = new reg_alumno[numRegs];

//************************************************************************
/// <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("
");

//Fondo para los datos


Console.WriteLine();
Console.BackgroundColor = ConsoleColor.White;
Console.SetCursorPosition(27, 4);
Console.Write(" ");
Console.SetCursorPosition(27, 5);
Console.Write(" ");
Console.SetCursorPosition(27, 6);
Console.Write(" ");
Console.SetCursorPosition(27, 7);
Console.Write(" ");
Console.SetCursorPosition(27, 8);
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();
}

//************************ METODO RINCIPAL****************************


/// <summary>
/// Programa principal
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
//Toma la direccion de la carpeta "mis documentos"
string mdoc =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
nombreArchivo = mdoc + @"\datos1.txt";
Console.WriteLine();
Console.Write("Leer Archivo (A) o Ingresar datos por teclado (T)?: ");
ingresoDato = System.Convert.ToChar(Console.ReadLine());

if (ingresoDato == 'A' || ingresoDato == 'a')


{
leeArchivo(nombreArchivo);
}
else
{
for (int i = 0; i < numRegs; i++) //Lee los datos de los estudiantes
{
pedirDatos(i);
}
}

//Pedir un numero de matricula para mostrar registro


do
{
Console.WriteLine();
Console.Write("Ingrese numero de matricula a buscar: ");
matricula = Convert.ToInt32(Console.ReadLine());
if (matricula > 0)
{
indice = buscarMatricula(matricula);
if (indice > 0)
{
mostrarDatos(indice);
}
else
{
Console.Write("Matricula no encontrada!!");
}
}
} while (matricula > 0);

//Al terminar, siempre guardara los datos en el archivo

201
guardaDatos(nombreArchivo);
}
}
}

Observaciones al código anterior: Para generar los “DocBloks” posicione el cursor en la


línea anterior alinicio del método y teclee tres veces el símbolo “/”.

Al iniciar el programa, se pregunta cómo se tomaran los datos:

Para evitar que aborte el programa, se hace una comprobación de la existencia del
archivo, cuando se intenta cargar:

Las demás pantallas son similares a las del problema 37.

Este es el archivo plano (de texto) generado:

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:

QUE se quiere Se requieren dos productos: El primero, mostrar en el tablero


las posiciones de origen y posibles destinos del caballo. La
segunda, un archivo con estos datos.

QUE se tiene Se ingresa la ubicación inicial del caballo (columna y fila).

COMO hacerlo El movimiento del caballo en un tablero de ajedrez se da en


forma de L, si tenemos un ajedrez de (8x8) casillas, El caballo
puede moverse entre casillas así: dos en una dirección y una
en una dirección perpendicular. La figura siguiente muestra
las casillas donde el caballo puede llegar, indicando cuanto
cambian los valores (Col, Fil) desde la posición inicial. Se han
indicado algunas formas del movimiento en “L” propio del
caballo.

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):

• Inicializar valores en matriz


• Ingresar posición del caballo.
• Calcular los destinos
• Dibujar el tablero
• Marcar la posición actual del caballo
• Marcar solo los destinos permitidos.

En una segunda iteración, especificamos con mayor detalle cada acción:

• Inicializar valores en matriz


o Cargar, una a una, las variaciones de C y F de la tabla anterior (ocho)
• Ingresar posición del caballo.
o Leer de teclado, los valores de colActual y filActual
• Calcular los destinos
o Para cada fila de la matriz, calcular los valores de la tercera y cuarta
columna, así: C3= colActual + C1 y C4= filActual + C2.
o SI los valores son menores a 1 o mayores a 8 ENTONCES cambiar por
cero.
• Dibujar el tablero
o Se puede dibujar de manera similar a como se hizo en el problema 17.
• Marcar la posición actual del caballo
o Desplazar el cursor y escribir, como en el problema 37.
• Marcar solo los destinos permitidos.
o En un ciclo PARA recorrer la matriz de destinos.
▪ SI los valores de col y fil están entre 1 y 8, marcar la casilla
correspondiente en la pantalla.

Para ubicar el cursor en la pantalla a partir de las coordenadas de la casilla, se puede


calcular sumando la posición en pantalla de la primera casilla y los valores de las
coordenadas (posiblemente multiplicando previamente por el ancho o alto de cada casilla
en pantalla).

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>

(nl es “nueva línea”).

Por ejemplo:

“I: (7, 7); D: (6,5); (8,5); (5,6); (5,8);”.

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 ************************************************

Salidas del programa:

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():

• Si es la primera vez, ensambla el nombre del archivo, incluida la ruta e instancia


los objetos para el flujo de datos
• Escribe la línea en el flujo de datos
• Reinicia la cadena a escribir, para la siguiente línea.

Al terminar el programa se cierra el archivo.

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:

QUE se quiere Mostrar en una ventana un reloj análogo animado.

QUE se tiene Se toma la hora del sistema.

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…

Se debe tomar la hora el sistema y desglosarla en los valores de horas, minutos y


segundo. Posteriormente se calcula el ángulo para cada valor, para poder dibujar las
líneas que representen las agujas del reloj.

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.

Continuando, para cada manecilla dibujar la línea correspondiente.

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?

Programación por eventos

En programación .NET , los eventos se basan en un modelo de delegado . El modelo de


delegado sigue el patrón de diseño de observador , que habilita a un suscriptor para
registrarse y recibir notificaciones de un proveedor . El emisor de un evento publica una
notificación de que se ha producido un evento, y un receptor de eventos recibe la
notificación y define una respuesta a la misma.
215
Eventos

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

Un delegado es un tipo que tiene una referencia a un método, un delegado equivale a un


puntero a una función. En la programación orientada a eventos, un delegado es un
intermediario (o puntero entre el origen del evento y el código que controla el evento.

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).

Para nuestro problema, dado que desarrollaremos en C# (C Sharp) .NET, utilizaremos


un manejador de eventos que se ejecute cada vez que un objeto de la clase Timer genera
un evento tick, el cual se programa para que ocurra cada 1000 mS (un segundo). Hay
que conocer las instrucciones para implementar lo anterior, según el lenguaje a utilizar.
Para este problema desarrollaremos creando un proyecto tipo “Aplicación de Formularios
Windows” en el IDE Visual Studio:

216
Inicie Visual Studio y siga los siguientes pasos:

• Seleccionar “Nuevo proyecto…” (A).


• En la ventana emergente, marcar la Plantilla “Visual C#” (B).
• Seleccionar “Aplicación para Windows Forms” (C).
• Darle un nombre al proyecto (D), la solución tomara el mismo nombre.
• Marcar el CheckBox “Crear directorio para la solución”.
• Por ultimo pulse el botón “Aceptar”.

En la nueva ventana, siga los pasos siguientes:

• Observe que Visual Studio genera una solución ya ejecutable y un nuevo


formulario (A).
• En el menú “VER” (B) seleccione:
o Si no está visible aun, el Explorador de soluciones (C).
o el cuadro de herramientas (D).
• En el cuadro de herramientas, busque y haga doble clic sobre “Picture Box”.

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)
{
}
}
}

No olvide documentar, al menos un poco, cada método.

En el código anterior observe:

▪ Las librerías (using), el espacio de nombres y la clase.


▪ La clase frmReloj, hereda de la clase Form.
▪ El método constructor de la clase: frmReloj().
▪ El método manejador del evento del timer: tmrReloj_Tick().

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.

ATENCION: Si no está seguro, no modifique nada en estos archivos.

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:

• Como propiedades de la clase tendríamos:


o Las coordenadas del centro, desde donde iniciaran todas las manecillas,
ya que se usaran en el método calcular y también en el método dibujar.
o Igualmente, los puntos finales de cada manecilla que son distintos entre sí.
o El valor del lado del contenedor del dibujo (PictureBox).
o Para dibujar las pequeñas líneas que marcan las 12 horas es necesario,
para cada una de ellas, conocer los dos puntos extremos, por lo que es
mejor usar una matriz de 12 x 2, que los guarde. Se debe actualizar al inicio
o cada que el usuario modifique el tamaño de la ventana.
• Tanto al inicio, como también al modificar el tamaño del formulario, se debe ajustar
el tamaño del área de dibujo (pictureBox) para que permanezca cuadrada y, así
evitar que el círculo se convierta en una elipse:
o Hacer la propiedad largo igual al menor valor entre el ancho y la altura del
formulario, por ensayo ajustar a un menor valor, hasta que las líneas
dibujadas queden contenidas totalmente en el formulario (totalmente
visibles).
o Cambiar las propiedades Height y Width del picturebox al valor del largo.
o Determinar las coordenadas del centro del picturebox, que servirán para
dibujar el círculo y las manecillas.
• Dado que es necesario calcular varias veces los extremos de las líneas (tanto para
las manecillas como para las líneas de hora), es mejor un método que realice este
cálculo:
o Utilizará los siguientes parámetros: el ángulo de la línea y el largo.

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;

// La longitud del area de dibujo, el 90% del formulario (ventana)


double largo;

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

//Se captura la hora del sistema en un miembro de tipo FECHA-HORA


DateTime laHora = DateTime.Now;

//El radio igual a la distancia desde cero hasta el centro


int radio = centro.X;
//El largo de las manecillas, un poco mas cortas que el radio
double largoHora = radio * 0.7;
double largoMinuto = radio * 0.8;
double largoSegundo = radio * 0.85;

//A la hora y se le agrega la parte decimal correspondiente a los minutos


double hora = laHora.Hour + (double)laHora.Minute / 60;
hora = hora % 12; //Modulo 12 (caso de horas despues de las 12 M.)
int minuto = laHora.Minute; //Separar minutos y segundos
int segundo = laHora.Second;

double ang = hora * 360 / 12; //angulo hora en grados


pHora = calcularPFinal(ang, largoHora); //Final de manecilla hora
ang = minuto * 360 / 60; //angulo minuto en grados
pMinuto = calcularPFinal(ang, largoMinuto); //Final de manecilla minuto
ang = segundo * 360 / 60; //angulo segundo
pSegundo = calcularPFinal(ang, largoSegundo);//Final de manecilla segundo

//Define los puntos para dibujar lineas en la caratula


calcularLineasHoras();
listo = true;
//Obliga a generar el evento PAINT
pbxReloj.Invalidate();
}

/// <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);

base.OnPaint(e); //Referencia a la ventana de usuario


Graphics g = e.Graphics; //Superficie de dibujo
//Coordenadas del rectangulo sobre el que se dibuja
Rectangle rectangle =

225
new Rectangle(10, 10, pbxReloj.Width - 15, pbxReloj.Height - 15);

//dibuja caratula del reloj. Pinta el fondo de la esfera


SolidBrush brocha = new System.Drawing.SolidBrush(cFondo);
g.FillEllipse(brocha, rectangle);

Pen lapiz = new Pen(cBorde, 8);


g.DrawEllipse(lapiz, rectangle);

//Dibuja lineas para las horas


lapiz = new Pen(cBorde, 5);
for (int i = 0; i < 12; i++)
{
g.DrawLine(lapiz, pNum[i, 0], pNum[i, 1]);
}

//dibuja las manecillas del reloj


lapiz = new Pen(cLineaH, 10);
g.DrawLine(lapiz, centro, pHora);

lapiz = new Pen(cLineaM, 7);


g.DrawLine(lapiz, centro, pMinuto);

lapiz = new Pen(cLineaS, 5);


g.DrawLine(lapiz, centro, pSegundo);

//Libera recursos de los objetos de dibujo


brocha.Dispose();
lapiz.Dispose();
}
}
}
}

El reloj visualizado en la ventana del programa (cambie el tamaño de la ventana para


observar cómo se redibuja):

226
Observaciones al código anterior:

• Cuando se desea entender en detalle el código, es muy recomendable realizar la


ejecución paso a paso. Utilice la opción “Depurar” del menú principal.
• El método ajustarPicture(), busca el menor tamaño entre la alto y el ancho del
formulario, lo reduce en un 10% y lo asigna a “largo”. Después ajusta el tamaño
del pictureBox y determina las coordenadas del centro de la imagen.
• El método calcularPFinal() encuentra las coordenadas de un punto en una línea
que inicia en el centro, con un ángulo y longitud dadas. Se utiliza para calcular los
extremos de las líneas que marcan las horas y las manecillas.
• calcularLineasHoras() utiliza el método anterior 24 veces, para las 12 líneas de
las horas (puntos inicial y final de cada línea).
• El método tmrReloj_Tick() es el manejador del evento Tick del temporizador, que
se ejecuta cada segundo. Determina las longitudes de las manecillas y su ángulo
para calcular sus puntos extremos.
• Por último, el metodo pbxReloj_Paint() es el manejador del evento Paint, que se
ejecuta al invocar el metodo invalidate().
• Observe los bloques de documentación al inicio de cada método. Para que VS los
genere, localice el cursor justo encima de la primera letra de la declaración del
código y pulse la tecla “/” por tres veces.

Problema 41: Calculadora. Desarrolle un programa en un entorno WEB, que


funcione como una calculadora que realice las siguientes operaciones: suma,
resta, multiplicación, división, porcentaje, inverso, raíz cuadrada, cambio de signo,
debe aceptar números con decimales y contar con teclas para borrar último digito,
borrar acumulados parcial y total; teclas para manejo de memoria: M+ (sumar
parcial a la memoria), M- (restar parcial a la memoria), MS (pasar parcial a
memoria), MR (pasar memoria a parcial), MC (borrar memoria). Además de los
botones en la ventana de la calculadora, se debe poder usar el teclado del
computador. La visualización en el área de pantalla debe incluir una línea superior
con la ecuación en proceso y una línea inferior con el número que se esté
ingresando o con el resultado (resultado parcial). También un mensaje que indique
el uso de la memoria. La calculadora deberá aparecer como una ventana flotante
sobre la pantalla.

Análisis:

QUE se quiere Una calculadora funcional, según los requisitos anteriores.

227
QUE se tiene El usuario ingresa los comandos, haciendo clic en los botones
o por el teclado.

COMO hacerlo Vamos a encarar este problema por partes:

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).

Primero el código HTML (archivo index.html).

Para que la calculadora se despliegue en una ventana independiente, es necesario que


el archivo index.html tenga un enlace a un segundo archivo que se desplegará en una
nueva ventana. Si está usando un servidor web, Index.html se ejecuta al escribir la url
(si es un servidor local: localhost/calculadora), sino, ejecute directamente el archivo
index.html en la carpeta del proyecto.

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>

En seguida se diseña la estructura del contenido ( DOM ) de la ventana


(calculadora.html), para que se visualice adecuadamente es necesario agrupar los
elementos en bloques, de manera jerárquica, en la siguiente imagen se muestra dicha
jerarquía:

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.

La siguiente es la estructura jerárquica de los elementos en la página:

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>

<button onclick = "calcular('←')"> ← </button>


<button onclick = "calcular('CE')"> CE </button>
<button onclick = "calcular('C')"> C </button>
<button onclick = "calcular('+-')"> ± </button>
<button onclick = "calcular('√')"> √ </button>

<button onclick = "calcular('7')"> 7 </button>


<button onclick = "calcular('8')"> 8 </button>
<button onclick = "calcular('9')"> 9 </button>
<button onclick = "calcular('/')"> / </button>
<button onclick = "calcular('%')"> % </button>

<button onclick = "calcular('4')"> 4 </button>


<button onclick = "calcular('5')"> 5 </button>
<button onclick = "calcular('6')"> 6 </button>
<button onclick = "calcular('*')"> * </button>
<button onclick = "calcular('1X')">1/x</button>
<div id= "teclado2">
<button onclick = "calcular('1')"> 1 </button>
<button onclick = "calcular('2')"> 2 </button>
<button onclick = "calcular('3')"> 3 </button>
<button onclick = "calcular('-')"> - </button>
<button class = "btnHoriz" onclick = "calcular('0')"> 0</button>
<button onclick = "calcular('.')"> . </button>
<button onclick = "calcular('+')"> + </button>
</div>
<button class = "btnVertical" onclick = "calcular('=')"> =</button>
</div>

231
</div>
</body>
</html>

Y este es el resultado del código HTML (sin maquetado CSS):

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;
}

/****************************** BOTONES ***************************/


button{
background-color: #eee;
margin: 1px;
width: 47px;
height: 47px;
font-size: 130%;
float:left;
border-radius: 5px;
box-shadow: 3px 3px 5px 0px #41454f;
}

button:hover {
background-color: #CACACA;
}

233
.btnHoriz{
width: 96px;
}

.btnVertical{
height: 96px;
}

/**************** ETIQUETAS *************************************/


label{
font-size: 1.2em;
margin: 5px;
float: right;
font-family: Arial, Helvetica, sans-serif;
}

#txtPantalla1{
font-size: 0.7em;
}

#txtMemoria{
float: left;
}

Resultado de incluir el archivo CSS.

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í:

• Ingreso de números y operaciones básicas con clic en los botones de la


calculadora.
• Visualización en la pantalla. Mostrar una línea superior con la ecuación en proceso
y una línea inferior con el resultado parcial o final.
• Hacer funcionales las teclas de borrado.
• Hacer funcionales las teclas de memoria.
• Activar el ingreso por el teclado del computador.

En el archivo HTML se asoció un método manejador para los eventos click de todos los
botones (observe el código HTML). Ejemplo:

<button onclick = "calcular('7')"> 7 </button>

La etiqueta anterior crea un elemento clase button, y se le define que, en el evento


onclick se ejecute la función calcular(). Dado que esta función es la misma para todos
los botones, se pasara un parámetro que identifique el botón pulsado, en este caso el
valor es ‘7’, que corresponde a su etiqueta en pantalla.

El diagrama de estados

Es hora de determinar cómo funcionara la calculadora. Para un análisis más detallado


del algoritmo, haremos uso de una herramienta UML utilizada en el diseño de Software:
El Diagrama de Estados .

Primero, agruparemos las teclas por funcionalidad, encontramos:

• Teclas numéricas [0…9] y el punto.


• Operadores binarios (requieren dos operandos) [ +, - , *, / ].
• Tecla igual [ = ].
• Operadores unarios (requieren un operando) [ +-, Raíz, 1/x, %].
• Teclas para borrar [ <-, CE, C ].
• Las teclas para manejo de memoria [ MC, MR, MS, M+, M-].

235
Por ahora realizaremos el análisis sin tener en cuenta las teclas de memorias ni de
borrado. Estas funcionalidades serán implementadas posteriormente.

Desde el punto de vista funcional, la calculadora utiliza la notación infija , en la que el


operador se encuentra en la mitad (<operando1> <operador> <operando2>), donde
<operando1> y <operando2> son cualquier combinación de dígitos y hasta una vez el
punto y <operador> es cualquiera de los operadores binarios. Observando con detalle
el comportamiento de la calculadora de Windows (nuestra guía), se encuentra que los
operadores unarios (raíz, inverso, etc.) operan sobre el valor parcial o resultado. Se
pueden definir varios estados de operación:

• 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.

En una primera aproximación encontramos el siguiente comportamiento, desde el punto


de vista de los diferentes estados definidos y los grupos de teclas a analizar (numéricas,
operador binario, operador unario, igual). Para no complicar el primer diagrama, no se
consideran las teclas de borrado ni memoria.

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.

CALCULADORA – ESTADOS Y VARIABLES GLOBALES


ESTADOS PROPIEDADES
E1 Espera primer digito para Valor1 nombre tipo contenido
E2 Ingresando más dígitos para Valor1 operador caracteres El símbolo del operador
E3 Espera primer digito para Valor2 valor1 numérico primer operando
E4 Ingresando más dígitos para Valor2 valor2 numérico segundo operando
E5 Se ha pulsado tecla igual etqResultado caracteres Mensaje inferior en pantalla

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.

Numérico [0..9], punto


E1 E2 E3 E4 E5
operador ="" (1) ="" (1)
valor1 = 0 (1) = 0 (1)
valor2 = 0 (1) = 0 (1)
Tecla (2) cero Tecla (2) cero
etqResultado izq? + = Tecla (1) = Tecla (1) += tecla (1) izq?
etqHistorial = "" (1) = "" (1)
Estado = 2 (3) = 4 (2) = 2 (3)

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)

La tabla anterior muestra el comportamiento detectado cuando se pulsa una tecla de


operador binario (+, -, x. /). Observe que en E4 se debe realizar la operación, por lo cual
se define un método (función) denominado operar() con tres parámetros: los dos valores
y el operador). Esto se hace porque en este momento ya se tiene el valor1, un operador
y al menos un digito de valor2 y hay que acumular esta operación para marcar el
siguiente operador. Tener en cuanta al codificar que los valores de texto hay que
convertirlos a numérico para asignarlos a valor1 o valor2.

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)

Se define los método operarUnario() para realizar la operación y CadenaUnario() para


mostrar el texto de la operación realizada. En los estados 1 y 2, el resultado se lleva a

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.

A partir del análisis anterior, se puede iniciar la codificación en el archivo JavaScript. Se


recomienda revisar la lógica definida en los cuadros anteriores, revise lo que sucede
paso a paso y según el estado inicial y la tecla pulsada en cada caso:

/********************************************************************
* 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 *
********************************************************************/

/********************** EVENT HANDLER ******************************/

/*Al cargar la pagina lanza el metodo INICIAR asociado al evento LOAD*/


window.addEventListener('load', iniciar, false);

/**************** PROPIEDADES GLOBALES ******************************/


var etiqResultado = null; //Etiquetas del DOM.
var etiqHistorial = null;
var etiqMemoria = null;
var operador = ""; //Para calculos
var valor1, valor2;

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;
}

Observaciones al código anterior:

• Cuando se carga la página se ejecuta el primer método (iniciar()). Esta acción se


ejecuta al definir un manejador de eventos en el evento ‘load’.
• Se definen variables para manipular las etiquetas del DOM, una variable que indica
el estado actual, otra para guardar el operador y dos variables numéricas para los
valores parcial y final de las operaciones (probablemente se pueda usar solo una,
pero por ahora sigamos así).

/************************** METODOS ********************************/

/*******************************************************************
* 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;
}
}

• El método calcular(tecla) realiza el “puente” entre el HTML y el JavaScript. Cada que


se pulsa una tecla se invoca este método y se trae como parámetro el valor de la
tecla pulsada.
• La estructura CASO (switch) direcciona a otros métodos, según el valor de la tecla:
calcularDigito() para el caso de números, calcularOperador() para los cuatro
operadores básicos, calcularIgual() y calcularUnario().
• Observe que están comentados los métodos para las teclas de borrado y memoria
(se desarrollaran posteriormente).

/*******************************************************************
* 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;
}
}

• El método calcularDigito() limpia algunas etiquetas, agrega el digito digitado y cambia


de estado (ver el diagrama de estados).
• Observe al final la estructura de decisión para evitar repetir ceros a la izquierda de
un número.

/*******************************************************************
* 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;
}
}

A manera de ejemplo, el bloque anterior se ha codificado tal cual se definió en la tabla


de Excel, observe la cantidad de código repetido. En seguida se presenta el código
después de realizar una refactorización:

/*******************************************************************
* 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;
}
}

• La diferencia entre E1 y E2 es que en E1 se opera con valor 0. No se coloca break;


al final de case 1: para poder continuar con las instrucciones de case 2:
• Los estados E3, E4 y E5 son idénticos, excepto que E5 cambia a E3.
• Observe la manera alternativa de codificar una estructura de decisión compuesta:
(estado == 5)? estado = 3: estado = 5.

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;
}
}

• El método calcularIgual() llama al método operar(), para realizar la operación


según el valor del parámetro opr, que contiene el signo de la operación a realizar.

En seguida se muestran los módulos auxiliares operar(), operarUnario() y


agregarCadenaUnario():

/*******************************************************************
* 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;
}

Codifique y ejecute la calculadora. Deberá observar las operaciones básicas (recuerde


que faltan algunas cosas por implementar). Es muy recomendable realizar una
depuración ejecutando paso a paso, principalmente para comprender cabalmente la
lógica del código.

Para ello, en la pantalla de la calculadora pulsar el botón derecho, seleccionar


“Inspeccionar” en el menú emergente. Seleccionar el menú “sources”, click en
“calculadora.js”. Se presenta el código, el cual puede ejecutar paso a paso o colocar
puntos de detención (breakpoints), revisar el valor de las variables pasando el cursor
sobre ellas, etc.

Completando los requerimientos de la Calculadora.

Para las teclas de borrado, se quieren las siguientes acciones:

• Tecla “←”: Eliminar el ultimo digito ingresado.


• Tecla “CE” Limpia el resultado.
• Tecla “C” Limpia todo.

/*******************************************************************
* 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;
}
}

Para las teclas de memoria:

• Tecla “MC”: borra el contenido de la memoria.


• Tecla “MR”: Muestra el contenido de la memoria.
• Tecla “MS”: Sobrescribe el contenido de la memoria.
• Tecla “M+”: Suma el número dado al contenido de la memoria.
• Tecla “M-”: Resta el número dado al contenido de la memoria.
• Hay que incluir una etiqueta para mostrar el estado de la memoria.
• Por supuesto, una variable numérica y una variable lógica que indicara si se está
usando la memoria.

Realice las siguientes acciones:

• Elimine los marcadores de comentario para las funciones borrar() y


calcularMemoria() dentro de la función calcular().
• Agregue en el bloque de propiedades globales las siguientes líneas:

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;

case "MR": //Muestra el contenido de memoria


if (memActiva == true){
etiqResultado.innerHTML = memoria.toString();
}
else {
etiqResultado.innerHTML = "0";
}
break;

case "MS": //Sobreescribe el valor de memoria


memoria = parseFloat(etiqResultado.innerHTML);
memActiva = true;
etiqMemoria.innerHTML = "M";
break;

case "M+": //Suma el número dado al valor que hay en memoria


memoria += parseFloat(etiqResultado.innerHTML);
memActiva = true;
etiqMemoria.innerHTML = "M";
break;

case "M-": //Resta el número dado al valor que hay en memoria


memoria -= parseFloat(etiqResultado.innerHTML);
memActiva = true;
etiqMemoria.innerHTML = "M";
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.

Hacer funcional el teclado

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”:

window.document.addEventListener("keypress", detectarTecla, false);

El método se denomina detectarTecla() y tiene, como parámetro un objeto denominado


e, dicho objeto contiene las propiedades del teclado. A partir de dichas propiedades, se
extrae el código de la tecla pulsada, se pasa a carácter y se lleva a mayúsculas (para el
caso de la letra C) y se invoca el método principal (calcular()).

//Manejador del evento "keypress" **********************************/


function detectarTecla(e){
var ev = (e)? e: event;
tecla = String.fromCharCode((ev.which)? ev.which: event.keyCode);
tecla = tecla.toUpperCase()
calcular(tecla);
}

/******************** FIN DEL CODIGO ******************************/

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)

La programación Orientada a Objetos (o por Objetos) es una técnica, aunque puede


considerarse un paradigma, al convertirse en un “estilo” seguido por programadores
experimentados. Utiliza módulos de software que representan objetos del mundo real;
en realidad, se trata de definir un “modelo” algorítmico que represente al mundo real
(entiéndase por “mundo real” al sistema que quiere representarse en un programa o
aplicación informática.

La POO busca diseñar y construir software utilizando un conjunto de objetos que


cooperan entre sí, mientras que la programación tradicional se basa en conjuntos de
funciones. Cada objeto puede verse de manera independiente, con objetivos bien
definidos y es capaz procesar datos y de recibir y enviar mensajes a otros objetos. El
código en POO es mas fácil de desarrollar y mantener.

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).

En los siguientes párrafos presentaremos de manera muy sintética algunos elementos


conceptuales relacionados con la POO, existe bastante información al respecto y un
tratamiento más profundo excede los objetivos de este libro.

Características de la POO

Los objetos, como unidades que contienen sus características y comportamientos, al


interrelacionarse con otros objetos constituyen el modelo del mundo real; ellos se pueden
relacionar ya sea con otros objetos de la misma clase o de clases distintas. Así, la POO
tiene como ventaja principal el poder representar el mundo real más fácilmente.

La Abstracción es un concepto clave en la POO, permite seleccionar las características


relevantes, identificar comportamientos comunes, dejando de lado otras características
que no sean “pertinentes” a la parte del mundo real que se desee modelar. Es
especialmente útil en el proceso de análisis y diseño del sistema.

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.

La Reutilización es la posibilidad de utilizar el mismo código para varias


implementaciones diferentes, ofreciendo una gran ventaja al obtener código más
compacto, fácil de depurar y de modificar.

El Polimorfismo significa que un elemento puede tener distintas formas, en concreto,


poder usar varios tipos de datos en un mismo método. Como ejemplo clásico un método
sumar(a, b) que devuelve el entero a+b, si los parámetros a y b son enteros o devuelve
ab si son cadenas de caracteres (a unida a b).

La sobrecarga está relacionada con el polimorfismo. Significa que un método puede


ser “invocado” o “llamado” suministrándole diferentes tipos de parámetros. En el ejemplo
anterior, la primera vez sus parámetros a y b serán de tipo entero, la siguiente vez serán
de tipo cadena de caracteres (string), por lo tanto “devolverá” diferentes tipos de datos
según la manera como sean llamados. En la práctica, no es que un solo método se
comporte diferente, lo que pasa es que hay que codificar los distintos comportamientos
en varios métodos, ¡dándoles el mismo nombre!

El Encapsulamiento es lo que permite que tanto la estructura (propiedades) como el


comportamiento (métodos) se encuentren dentro del mismo cuerpo de código de la clase
con la que se crean los objetos. Dentro de la clase se deben agrupar tanto la información
o datos de los campos como las operaciones o métodos o funciones que operan sobre
esta información.

La visibilidad es la propiedad que tienen los elementos (propiedades o métodos) de un


objeto para ser “visibles” o “accesibles” desde afuera de la definición de la clase. Los
siguientes niveles de visibilidad son los más importantes: Publico (+), visible desde todos
los demás objetos que tengan relaciones con este. Privado (-), visible solo por los
métodos de su propia clase. Protegido (#), solo se puede tener acceso desde las
subclases que lo heredan.

En este punto es importante aclarar la diferencia entre Objeto y Clase y entender el


termino Instanciar . La Clase es la “plantilla” o el “molde”; es el código en el que se
determinan las propiedades y métodos de una “Clase de Objetos”. En este momento no
existe ningún Objeto, aunque se haya definido una Clase.

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

Algunas de las más importantes definiciones de relaciones en la POO:

Herencia : Es otra importante característica de la POO. Significa que una clase de


objetos (subclase) puede “heredar” de una Clase superior (superclase). Al definir una
clase heredera de otra, ella puede tener comportamiento similar, con algunos cambios
definidos específicamente para esa subclase, al agregar propiedades o métodos, o al
cambiar sus características, lo que se denomina sobre escritura (override) . Un objeto
de la subclase puede acceder a los métodos de la clase superior, es decir que “hereda”
sus atributos y métodos; también, una subclase puede poseer método propios que no
tiene la superclase (especialización / generalización).

Asociación : Es una relación de estructura entre clases, es decir, una entidad se


construye a partir de otra u otras. Aunque este tipo de relación es más fuerte que
la Dependencia es más débil que la Agregación, ya que el tiempo de vida de un objeto
no depende de otro.

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.

Composición : Es un tipo de asociación, pero más fuerte. Cuando un objeto de una


clase A tiene (o contiene, o posee) objeto(s) de la clase B. Al instanciar el objeto A, este
instancia a su vez al B. El objeto A no puede existir sin B.

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:

QUE se quiere Jugar Tetris, según las reglas del juego.

QUE se tiene Una vez iniciado el juego, el usuario puede controlar el


movimiento de la ficha, utilizando las teclas de flechas..

COMO hacerlo En seguida se desarrollara el proceso de diseño, iniciando por


la comprensión detallada del juego.

Las reglas del Tetris

254
El juego está compuesto por siete figuras geométricas (fichas)
formada cada una por cuatro cuadrados de un mismo color.

Dichas figuras “aparecen” por arriba y “caen” en un mundo de


10 x 20 cuadros. El jugador no puede impedir esta caída, pero
puede rotar la figura y decidir el lugar donde debe caer. Cuando
una línea horizontal se completa, esa línea desaparece y todas
las piezas que están por encima descienden una posición,
liberando espacio de juego en la parte superior y por tanto
facilitando la tarea de situar nuevas piezas. La caída de las
piezas se acelera progresivamente. El juego acaba cuando las
piezas se amontonan hasta llegar a la fila más alta.

Para hacer más emocionante el juego, se asignara un puntaje


por cada línea eliminada y adicionalmente, se aumentara el
puntaje cuando cada cuadro que desaparece tiene como
vecinos otros cuadros del mismo color, lo que obliga a cambiar
la estrategia para buscar colocar mejor las piezas, no solo completando las filas.

Preparando los sprites

Para dibujar el mundo basta repetir un cuadro de fondo


cuatrocientas veces. Dibuje una figura de 25 x 25 pixeles como
la siguiente, observe que los pixeles del borde son de un color
un poco más claro, para crear la ilusión del entramado o rejilla
(ver figura anterior), no olvide asignarle un valor al canal alfa (la
transparencia), practique para encontrar un valor apropiado y
guárdelo en un archivo denominado “Fondo.png”.

De la misma manera, todas las fichas son repeticiones de un solo


cuadro de 25 x 25 pixeles, con la diferencia que se le asigna, al
ejecutarse, un color diferente a cada ficha. También en este caso
se cambian un poco los colores en los bordes para dar la ilusión
de relieve, no olvide el canal alfa. Guárdelo como “Ficha.png”.

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

El juego se presentara en una ventana de tamaño fijo,


con el “mundo” a la izquierda para permitir que en la
parte superior derecha se visualice la siguiente figura a
caer, mientras el jugador manipula la figura actual.
(Esto “obliga” al jugador a cambiar la táctica, al
considerar la forma y color de la siguiente figura
mientras intenta la mejor posición para la figura que
está cayendo. Las figuras se manipularan con las teclas
de flechas (Flecha arriba para girar, Flechas derecha e
izquierda para desplazar y flecha abajo para hacer caer
más rápido la ficha).

Pensando en Clases y Objetos

En primer lugar existe un “mundo” en el cual se desarrolla el juego. También existen


fichas, las cuales o se encuentran en un estado previo (visible y estatica) o están cayendo
dentro del mundo (realmente una sola ficha en cada estado). Cuando una ficha no puede
caer más, esta ficha pasa a formar parte del fondo (del mundo), integrándose a él y
desapareciendo como tal.

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.

Ya que las fichas se componen de pequeños rectángulos, podríamos considerar a cada


uno como un objeto, pero también podríamos representarlos como valores (si existe o
no y el color) en una lista o una matriz de enteros (esto hace más fácil su manejo, ver
Sokoban). Cada ficha se puede representar en una matriz de 4x4 (ver imagen de las
fichas).

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.

Basta tener un objeto para cada clase de ficha


pues, al desaparecer una ficha del juego se
creara otra y, para evitar el costo computacional
y de memoria asociado a instanciar y eliminar
cada objeto, basta con cambiar las propiedades
de forma, color y posición del mismo objeto
cuando corresponda tener otra ficha.Por otro
lado, la diferencia entre los dos objetos de la
clase “Ficha” consiste en que el primero (la ficha
previa) no se moverá ni estará referenciada en
ninguna posición del mundo, mientras la ficha en
juego se comportara según las condiciones del
fondo (el mundo) y de las órdenes dadas desde
el teclado. Así, la ficha en juego heredara
algunos atributos y, posiblemente métodos, de la
clase ficha previa.

Hay que considerar la interfaz de usuario, es


decir, el formulario que se ejecutara en primer lugar y en el que se instanciaran los
objetos definidos antes; es donde se leen las teclas, se maneja el timer para controlar la
repetición del juego (y la velocidad) y se visualiza el mundo, las fichas y el puntaje, etc.
Desde acá se invocan todos los métodos necesarios.

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.

Nota: Para no complicar los fundamentos presentados en este libro, no se ha querido


profundizar en los elementos de la POO ni de UML, se encuentra muy variada
información al respecto.

Para continuar con la definición de las clases haremos un listado de propiedades y


métodos necesarios, asignándolos a la clase correspondiente.

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.

En el cuadro siguiente se presentan las propiedades de la clase estática valores.


Observe que todos los miembros son públicos y estáticos y no hay métodos.

Valores (Clase estática)


Visibilidad Nombre Tipo Observaciones
publico colores[11] Color[] Estático. Color de fondo y 10 colores para celdas
publico tamMundo Point Estático. Tamaño de la matriz del mundo
publico tamCelda int Estático. Ancho en pixeles de una celda
publico margen int Estático. Márgenes del borde del mundo
publico posIni Point Estático. Posición inicial de la figura que cae

En la clase frmTetris todos sus miembros son privados, en la columna de observaciones


se hace una breve descripción de cada uno. Excepto el método constructor, los demás
deben tener como parámetros, un objeto y los eventos asociados (ver el código más
adelante).

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.

privado frmTetris_KeyDown() void Llama a los métodos girar() y desplazar() de la clase


Mundo. Invalida el pbxMundo para obligar a dibujar.
privado spContenedor_Paint() void llama a mundo.dibujarSiguiente(), le pasa el objeto
grafico (pictureBox) donde se dibujara

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.

publico caerFicha() void Llama los métodos puedeMover(), integrarFicha(). A


los métodos ficha (GET), desplazarFicha(),
cargarFicha() de la clase Ficha y al metodo
cargarFicha() de la clase NuevaFicha.
publico desplazar(int, int) booleano Parámetros: movimiento horizontal, movimiento
vertical. Intenta bajar la ficha una fila. Intenta mover
la ficha horizontal o verticalmente. Llama al método
puedeMover(). Al método desplazarFicha() de la clase
Ficha
puedeMover(int, int, booleano Parámetros: movimiento horizontal. movimiento
int[,]) vertical, Matriz de ficha. Comprueba si la ficha se
puede desplazar.
publico girar() booleano Intenta girar la ficha 90 grados a la derecha. Invoca al
método puedeMover(). Invoca los método GET/SET
FichaGirada de la clase Ficha.

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).

Continuando, se muestra la descripción de miembros de la clase NuevaFicha, de la cual


hereda la clase Ficha. Observe que al método dibujar() se le ha agregado el calificador
Virtual, ya que este método es sobrescrito por la clase hija Ficha. Además, los métodos
privados que también utilizara la clase hija, se les cambia su visibilidad a protegido.

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

El diagrama uml de Clase de diseño para Tetris:

262
En el diagrama anterior, observe los diferentes símbolos y consulrte su significado.

Construyendo la Interfaz

1. Cree un nuevo proyecto, denomínelo Tetris.

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

3. Agregue un SplitContainer cambie las siguientes propiedades:


a. Name: spContenedor
b. Dock: Fill
c. Enabled: False
d. FixedPanel: Panel1
e. Location: 0,0
f. MaximumSize: 400, 520
g. MinimumSize: 400, 520
h. ShowIcon: False
i. Size: 400, 520

264
j. SplitterDistance: 260

4. Dentro del Panel 1 de spContenedor, inserte un PictureBox (es el área de dibujo,


o canvas):
a. Name: pbxMundo
b. Dock: Fill
c. Llocation: 0,0
d. Size: 260,520
e. Evento Paint: Manejador de evento pbxMundo_Paint

5. Dentro del Panel 2 de spContenedor, inserte un Label:


a. Name: label1 (Nota: no es necesario cambiar, ya que
no se usara desde el código)
b. AutoSize: True
c. Font: Sans Serif, 12pt, bold
d. ForeColor: DarkRed
e. Location: 40,271
f. Text: Puntos:
g. TextAlign MiddleCenter

6. Dentro del Panel 2 de spContenedor, inserte un Label:


a. Name: lblPuntos
b. AutoSize: True
c. Font: Sans Serif, 12pt, bold
d. ForeColor: DarkRed
e. Location: 59,300
f. Size: 19,20
g. Text: 0
h. TextAlign MiddleCenter

7. Inserte un Timer (controla los ciclos del juego):


a. Name: tmrReloj
b. Enabled: True
c. Interval: 500
d. Evento Tick: Manejador de evento tmrReloj_Tick

Implementando los Objetos

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 **************************************

Observaciones al código anterior:

• Observar las librerías referenciadas (using).


• Revise en donde se declara el objeto mundo y en donde se instancia.
• Observe como, en el manejador del evento Paint, el objeto contenedor (pbxMundo)
de la interfaz de usuario, se pasa como parámetro para que mundo dibuje el juego.
• En el ciclo periódico (tmrReloj_Tick), se realiza la lógica del juego, invocando los
métodos correspondientes del objeto mundo, que deberán ser públicos, al final, se
disparan eventos Invalidate que obligan a ejecutar los métodos Paint (dibujar) del
contenedor, para actualizar la imagen de la ficha previa y el puntaje y del picture box
(pbxMundo) para redibujarlo. También se controla si el juego termina.
• El manejador del evento KeyDown verifica si se pulso una tecla de flecha, la gira o
desplaza (si se puede) y redibuja el lienzo.

/************************************************************************
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);
}
}

//*************** FIN DE LA CLASE VALORES *******************************

Observaciones al código anterior:

• Un arreglo estático con 11 colores. El primero representa una celda libre.


• Todos los valores se pueden considerar como constantes globales.

/************************************************************************
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

private Bitmap cuadroFicha = global::tetris.Properties.Resources.Ficha;


//Contiene el mundo del juego

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

// METODOS GET / SET ************************************************


/// <summary>
/// Devuelve los puntos acumulados
/// </summary>
public int Puntos
{
get { return puntos; }
}

//************ METODOS DE LA CLASE ***********************************


/// <summary>
/// Constructor de la clase
/// </summary>
public Mundo()
{
nuevaFicha = new NuevaFicha(); //Instancia la ficha
ficha = new Ficha(nuevaFicha.ColorFicha, nuevaFicha.mFicha);
nuevaFicha.cargarFicha();
}

//************************************************************
/// <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;

if (x < 0 || x >= Valores.tamMundo.X)


{
siPuede = false;
break; //Obliga a Salir del for interno
}
//Evita comprobar cuando la ficha esta iniciando (y < 0)
if (y >= 0)
{
if ((y >= Valores.tamMundo.Y) || (mundo[x, y] > 0))
{
siPuede = false;
break; //Obliga a Salir del for interno
}
}
}
}
if (!siPuede) break; //Obliga a Salir del for externo
}
return siPuede;
}

/// <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]);

int x = Valores.margen; //coordenadas en el picture


for (int i = 0; i < Valores.tamMundo.X; i++)
{
int y = Valores.margen;
for (int j = 0; j < Valores.tamMundo.Y; j++)
{
if (mundo[i, j] > 0)
{
g.DrawImage(cuadroFicha, x, y, cuadroFicha.Width,
cuadroFicha.Height); //Los bordes del cuadro
}
brocha.Color = Valores.colores[mundo[i, j]];
g.FillRectangle(brocha, x, y, Valores.tamCelda -2,
Valores.tamCelda -2); //Dibuja una celda
y += Valores.tamCelda;
}
x += Valores.tamCelda;
}
ficha.dibujar(g);
}

/// <summary>
/// Dibuja la siguiente ficha
/// </summary>
/// <param name="g"></param>
public void dibujarSiguiente(Graphics g)
{
nuevaFicha.dibujar(g);
}
}
}
//*************** FIN DE LA CLASE MUNDO ********************************

Observaciones al código anterior:

• Observe como se declaran e instancian simultáneamente los objetos Bitmap,


cargando inmediatamente los sprites.
• Revise la forma de codificar el método GET Puntos (publico) para aislar la propiedad
puntos, que es privada.

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;

// ESTRUCTURA CON LAS FORMAS DE LAS FICHAS


static int[, ,] formas = new int[7, 4, 4]
{
{ //Ficha tipo 1
{0,0,0,0},
{1,1,1,1},
{0,0,0,0},
{0,0,0,0},
},
{ //Ficha tipo 2
{0,0,0,0},
{0,1,1,1},
{0,0,1,0},

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

// METODOS GET / SET ****************************************************


/// <summary>
/// Devuelve la Matriz con la forma de la ficha
/// </summary>
public int[,] mFicha
{
get { return ficha; }
}

/// <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;
}
}
}
}

//*************** FIN DE LA CLASE NUEVAFICHA **************************

Observaciones al código anterior:

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

// METODOS GET / SET **********************************************


/// <summary>
/// Devuelve la Posicion de la celda superior izquierda de la ficha
/// </summary>
public Point Posicion
{
get { return posicion; }
}

/// <summary>
/// Devuelve la copia de la Matriz girada 90 grados a derecha
/// </summary>
public int[,] FichaGirada
{
get
{
girarFicha();
return fichaGirada;
}

278
set
{
copiarFicha();
}
}

//METODO CONSTRUCTOR ******************************************************


/// <summary>
/// Constructor de la clase
/// </summary>
public Ficha(int colFicha, int[,] forma)
{
cargarFicha(colFicha, forma);
}

//******************************************************************************
/// <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

for (int i = 0; i < 4; i++) //cargar la forma


{
for (int j = 0; j < 4; j++)
{
ficha[i, j] = formas[i, j];
}
}
}

/// <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;

for (int i = 0; i < 4; i++)


{
//Eje Y en pixeles del area de dibujo
int y = posicion.Y * Valores.tamCelda + Valores.margen;
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;
}
}
}
}
//*************** FIN DE LA CLASE FICHA ************************************

Observaciones al código anterior:

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.

Reto: Codifique el juego TETRIS para ambiente Web.

281
15. LISTAS DE OBJETOS Y PILAS

En este capítulo desarrollaremos un ejercicio para practicar el uso de listas de objetos y


el funcionamiento y uso de pilas. Para el efecto, construiremos un pequeño programa,
que sirva como un asistente para resolver sudokus. Pero antes, una rápida explicación
de los conceptos a tratar en este capítulo.

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:

QUE se quiere Visualizar un tablero de Sudoku, mostrando los números


candidatos de todas las casillas, almacenar y recuperar varios
estados del tablero. Validar el ingreso de números.

QUE se tiene El problema no plantea el ingreso inicial de datos (carga


inicial). El usuario digita números en las casillas, hasta llenar
todo el tablero.

COMO hacerlo En seguida se desarrollara el proceso de diseño, iniciando por


la comprensión detallada del juego.

Las reglas del Sudoku

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.

El “Asistente” para resolver sudokus se limitara a


mostrar los números candidatos, sin
realizar ningún tipo de análisis táctico,
sino, jugar no sería un reto para el
usuario.

Construiremos un tablero con las


casillas para introducir el número y,
en la parte inferior de cada una, se
mostraran todos los candidatos (ver
imagen). Cada vez que un digito sea
ingresado en otra casilla de la
misma fila, o columna, o caja, dicho
digito se eliminara de la lista de
candidatos de la casilla. Si se
ingresa un digito en la casilla, por
supuesto se eliminaran todos los
candidatos.

Una vez ingresada la configuración


inicial, se deberá ver así (observe
como se eliminan los candidatos no
permitidos: ver los números
pequeños debajo de cada casilla):

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.

Además, construiremos una pila de tableros (valores de las casillas


en un momento dado), para almacenamiento temporal, en caso de
querer recuperar un tablero anterior, si se encuentra que no existe
una solución para los números colocados por el jugador. Es decir,
el jugador puede regresar a una situación anterior e intentar otra
solución. Hay que visualizar el estado de la pila en cada momento.
Tendrá botones para guardar (push) tableros en la pila, cargar
(pop) tableros de la pila y para limpiar toda la pila (clear). Cada
que se guarde o se cargue un tablero, se indicara visualmente la
acción, cambiando el color del panel (caja) correspondiente.

Construyendo la Interfaz

1. Cree un nuevo proyecto, denomínelo Sudoku.


2. En el formulario. Cambie las siguientes propiedades:
a. BackColor: SeaShell
b. Size: 817, 642
c. Text: SUDOKU
3. Inserte un Panel, Cambie las siguientes propiedades:
a. Anchor: Top, Right
b. BackColor: Bisque
c. Location: 625, 20
d. Size: 162, 96
4. Dentro del panel anterior inserte un Button, Cambie las siguientes propiedades:
a. Name: btnIniciar
b. BackColor: MistyRose
c. Font: Microsot Sans Serif, 12pt, style=Bold
d. ForeColor: Brown
e. Location: 26, 20
f. Size: 113, 43
g. Text: Iniciar
5. Inserte un Panel, Cambie las siguientes propiedades:
a. Name: pnlMemoria

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

La ventana se visualizara de la siguiente manera:

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

Como primera aproximación, definamos algunas propiedades:

• 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).

Y analicemos que métodos necesitamos:

• El método constructor, además de inicializar los componentes creados en tiempo de


diseño (InitializeComponent()), debe cargar la matriz sudo[], calculando, por una
sola vez, los valores de columna, fila y caja para cada casilla. También debe crear
los objetos de las cajas de texto, etiquetas y paneles, adicionarlos a las listas
correspondientes y añadirlos al formulario, para que se hagan visibles y se puedan
manipular. Después de esto, activar bPermiso.
• El manejador del evento TextChanged deberá identificar la casilla en la que se
escribió un digito, validar el carácter ingresado, que este en el rango de números del
1 al 9, que no exista previamente en la misma fila, columna o caja. Si está permitido,
debe actualizar las demás etiquetas, eliminando el número ingresado de ellas. Si no
es válido, debe borrarlo y, revisar todas las cadenas.
• Por supuesto, cada botón tendrá asociado un manejador del evento Click.
o Para el botón btnIniciar : Cargar la matriz de nuevo, borrar el tablero y reiniciar
las etiquetas de candidatos.
o Para el botón btnGuardar : Revisar si el tamaño de la pila es menor al máximo,
entonces ingresar (push) los valores del tablero actual y cambiar el color del
panel correspondiente.
o Para el botón btnCargar : Revisar si el tamaño de la pila es mayor a cero,
entonces sacar (pop) los valores de la pila, escribirlos en el tablero y cambiar el
color del panel correspondiente.
o Para el botón btnLimpiar : Eliminar todos los objetos de la pila (clear), y cambiar
el color de los paneles activos.

Analizando con mayor profundidad, dspues de revisar las acciones a realizar, crearemos
otros métodos auxiliares más especializados, así:

• cargarMatriz(): Se trata de calcular y guardar el número de columna, fila y caja


correspondiente a cada una de las 81 casillas. Para descubrir cómo hacerlo,
utilizaremos una hoja de cálculo:

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

En la primera tabla el número de cada casilla (desde 0 hasta 80). En la segunda


tabla, el número de columna se obtiene con el residuo de la casilla / 9. En la tercera
tabla, para encontrar la fila, se toma la parte entera de casilla / 9. Hasta acá todo
correcto.

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:

Numero de Caja= entero(columna/3) Numero de Caja= entero(fila/3)


0 0 0 1 1 1 2 2 2 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 2 2 2 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 2 2 2 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1
0 0 0 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1
0 0 0 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1
0 0 0 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
0 0 0 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2
0 0 0 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2

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.

Numero de Caja= (Col/3) + (Fil/3)*3


0 0 0 1 1 1 2 2 2
0 0 0 1 1 1 2 2 2

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:

o PARA i = 0 HASTA 80 HAGA


▪ Sudo[i, 0] = i % 9 //Columna = RESIDUO(Casilla/9)
▪ Sudo[i, 1] = i / 9 //Fila = entero(casilla/9)
▪ Sudo[i, 2] = (Sudo[i, 0]/3) + (3 * Sudo[i, 1]/3) //Caja= (Col/3) + (Fil/3)*3
▪ Sudo[i, 3] = 0 //Contenido inicial de la casilla

• dibujarCajasTexto(): Se trata de crear cada objeto de la clase TextBox, calcular su


posición, tamaño, color, agregarlo a la lista e incluirlo en el formulario para hacerlo
visible, igualmente crear un delegado para el manejador del evento TextChanged.
En el mismo método se crearan los objetos de la clase Label, con sus propiedades:

o Asignar xPos, yPos, lado //donde inicia la primera casilla y su ancho


o Asignar color1, color2 //para visualizar las cajas
o PARA i = 0 HASTA 80 HAGA
▪ Agregar nuevo TextBox a txtLista
▪ En la propiedad Tag del objeto ingresar i //El número de la casilla
▪ La propiedad Width = lado
▪ SI (I es par) ENTONCES
BackColor = color1 SINO BackColor = color2
▪ Asignar las propiedades Font, TextAlign
▪ SI (i MOD 3 = 0) ENTONCES xPos += 8 //Para separar un poco las
cajas, cada tres casillas.
▪ La propiedad Left = xPos y Top = yPos
▪ Adicionar el objeto al formulario.
▪ Agregar un manejador de eventos al objeto
▪ Agregar nuevo Label a lblLista
▪ La propiedad Left = xPos y Top = yPos + altura del TextBox

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().

• dibujarPanelMemoria(): En el panel respectivo, insertar objetos de la clase


Panel, en la posición adecuada para formar una columna y asignar sus
propiedades.
o Asignar xPos, yPos //donde inicia el primer Panel
o PARA i = 0 HASTA MAX_MEMORIA HAGA
▪ Agregar a pnlLista un nuevo objeto de la clase Panel
▪ Asignar las propiedades Left, Top, Width, Height y BacKolor
▪ Agregarlo al contenedor pnlMemoria //para visualizarlo
▪ Ajustar el valor de yPos.

• 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

• actualizarCadenas(): Elimina el numero ingresado en la casilla de todas las


cadenas vecinas( misma columna, misma fila y misma caja).
o bPermiso = falso
o PARA (j = 0 HASTA 80) HAGA //Revisa todas las casillas
▪ SI (elemento j es vecino) ENTONCES
• Elimina valor de la etiqueta de candidatos
o SI(hay un valor en la casilla) ENTONCES
▪ Limpia todos los candidatos de su etiqueta
o bPermiso = verdad

• borrarTablero(): Recorre todas las casillas y limpia su propiedad Text.

• revisarCadenas(): Recorre la matriz sudo[] para conocer su columna, fila y caja


y actualiza todas las cadenas de candidatos, llamando actualizarCadenas().

• reiniciarCadenas(): Recorre toda la lista de etiquetas y les asigna el valor


“123456789”.

• leerTablero(): Convierte el contenido de las casillas a números y los lleva a


sudo[].

• btnIniciar_Click(): Carga la Matriz sudo[], borra el tablero y reinicia 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.

o SI (tamaño de la pila < MAX_MEMORIA) ENTONCES


▪ Crea un arreglo de enteros
▪ Carga en el contenido de los valores actuales (sudo[ ,3])
▪ Cambia el color del panel de visualización correspondiente.
▪ Mete el arreglo en la pila (Push).

• btnCargar_Click(): Recupera los valores de un tablero desde la pila y cambia el


color del panel visualizador correspondiente.

o SI (la pila no está vacía) ENTONCES


▪ Crea un arreglo de enteros
▪ Carga en el contenido de último objeto en la pila (Pop).
▪ Cambia el color del panel de visualización correspondiente.
▪ PARA (i = 0 HASTA 80) HAGA
• Guarda el valor correspondiente desde el recuperado de la pila.
• SI (el valor > 0) ENTONCES
o Escribe el texto en la casilla correspondiente
• SINO
o Limpia el texto en la casilla correspondiente
• Revisa todas las cadenas

• btnLimpiar_Click(): Elimina todos los datos de la pila y cambia el color de los


paneles visualizadores.

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

const int MAX_MEMORIA = 15; //Cantidad MAX de tableros guardados


bool bPermiso = false; //Para ingresar en algunos eventos

Stack pila = new Stack(); //Pila para guardar los tableros


List<TextBox> txtLista = new List<TextBox>(81); //Para las casillas
List<Label> lblLista = new List<Label>(81); //Para las cadenas de candidatos
List<Panel> pnlLista = new List<Panel>(MAX_MEMORIA); //Indicador visual

/// <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

txtLista[n].Width = lado; //Tamaño caja


if (sudo[n, 2] % 2 == 0) //Aplica colores por caja
txtLista[n].BackColor = color1;
else
txtLista[n].BackColor = color2;

txtLista[n].Font = new Font("Arial", 22.0f);


txtLista[n].TextAlign = HorizontalAlignment.Center;
if ((n % 3) == 0) xPos += 8; //Separacion vertical cada tres cajas
txtLista[n].Left = xPos; //Asigna posicion a cada celda
txtLista[n].Top = yPos;

// 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;

if ((n + 1) % 9 == 0) //Determina nueva fila


{
xPos = 10; //Va a la izquierda

295
yPos += (lado); // Baja la distancia de una casilla;
}
this.Controls.Add(txtLista[n]); //Agrega objetos al formulario
this.Controls.Add(lblLista[n]);

// Asigna manejador del evento TextChanged a cada TextBox


txtLista[n].TextChanged += new System.EventHandler(TextChange);
}
reiniciarCadenas();//Asigna todos los digitos cadenas de candidatos
}

/// <summary>
/// Coloca objetos Panel para visualizar el uso de la pila
/// </summary>
private void dibujarPanelMemoria()
{
int xPos = 51;
int yPos = pnlMemoria.Height - 30;

for (int i = 0; i < MAX_MEMORIA; i++)


{
pnlLista.Add(new Panel()); //Crea un objeto Panel
pnlLista[i].Left = xPos; //Asigna propiedades
pnlLista[i].Top = yPos;
pnlLista[i].Width = 60;
pnlLista[i].Height = 19;
pnlLista[i].BackColor = Color.Gainsboro;
pnlMemoria.Controls.Add(pnlLista[i]); //Lo agrega al contenedor
yPos -= (pnlLista[i].Height + 1); //Posicion Y del siguiente
}
}

/// <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.

Para el desarrollo de este capítulo es necesario tener conocimientos previos sobre


HTML , CSS y preferiblemente Javascript , aunque el manejo de lenguajes como C++,
C#, Java, etc. serán de gran ayuda. También, el lector deberá sentirse cómodo con la
terminología de la Programación Orientada a Objetos.

Las reglas de Sokoban

Nuestro juego debe cumplir las siguientes reglas:

• 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.

Preparando los sprites

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.

Fig. Caja en posición libre

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.

Esta figura es la de una caja en su posición final, note que


cambia un poco el color para resaltarla.

Fig. Caja en posición final

302
Las siguientes figuras son del piso, la última con la marca donde deberá colocarse una
caja. Note que estas no tienen pixeles transparentes.

Fig. 3. Piso Fig. 4. Piso con marca de destino

La siguiente imagen representa un segmento de pared, también sin transparencias.

Fig. 5. Bloque de pared Fig. 6. El personaje, Sokoban

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.

Preparando la página HTML

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>

En el código anterior (index.html), se ha insertado un enlace (link) al archivo que


contendrá el estilo (soko.css) y una referencia al código de la lógica del juego (soko.js).
En el archivo CSS inserte el siguiente código:

/*Formato de elementos del juego SOKOBAN*/


*{
margin: 0;
padding: 0;
}

#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%;
}

Con esto le estaremos dando una


presentación inicial a la página, por ahora, no
hay nada en el archivo soko.js. Guarde y
visualice la página, obtendrá algo como esto:

Si en vez del recuadro azul, tiene un mensaje


“Canvas no soportado”, entonces cambie el
navegador por uno más moderno!

La estructura del juego

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:

En el primer módulo se instancian los objetos, inicializando las propiedades necesarias,


posteriormente se cargan los archivos de recursos a utilizar en la animación (sprites,
imágenes, sonidos, etc.). El módulo tercero y el cuarto son los que están en ejecución
continúa durante el desarrollo del juego: El tercer módulo actualiza las propiedades de
los diferentes objetos (escenario o mundo, actores, etc.) y, una vez actualizados, es
necesario dibujarlos de nuevo para recrear el movimiento. Este ciclo Actualizar – Dibujar
debe repetirse unas 50 veces por segundo para obtener una animación fluida (claro que
la frecuencia depende de muchos factores y es necesario ajustarla en la etapa de
pruebas.

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().

En la función iniciar() se debe referenciar al objeto canvas y su contexto 2D en el DOM ,


llama a la función cargar() y por ultimo inicia el ciclo run(), que es una función recursiva
y permite la animación del juego.

Todo este código se asocia al HTML, al agregar un “Escuchador de Eventos”,


concretamente el evento “load” que se dispara cuando se ha cargado el contenido de
la página. Esta plantilla podría ser la base para otros juegos.

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 *
********************************************************************/

/*Al cargar la pagina lanza el método INICIAR asociado al evento LOAD*/


window.addEventListener('load', iniciar, false);
window.document.addEventListener("keydown", detectarTecla, false);
window.document.addEventListener('change', manejarArchivo, false);

/****************** PROPIEDADES GLOBALES ***************************/


//referencia al objeto Canvas del DOM y al contexto de dibujo (global)
var canvas=null, ctx=null;

// objeto con la hoja de sprites


var img1 = new Image();

//Guarda el valor de la ultima tecla pulsada


var valorTecla =0;

/************************** METODOS ********************************/

/*********** CARGA IMAGENES, SPRITES, SONIDOS, ETC ****************/


function cargar(){
//Elementos del DOM
xxx = document.getElementById('xxx');

//Carga el archivo con la hoja de sprites


img1.src = "Recursos/xxx.png";
}

/************* ACTUALIZACION PERIODICA DEL JUEGO *******************/


function actualizar(){
}

/*************** DIBUJO PERIODICO DEL CANVAS ********* **************/


function dibujar(){
//Borrar el contenido del canvas
ctx.clearRect(0,0,xx,yy);
}

/****************Controla el ciclo repetitivo***********************/


function run(){
setTimeout(run, 50);
actualizar();
dibujar(ctx);
}

307
/***************** INICIALIZACION EL PROGRAMA *******************/
function iniciar(){
canvas=document.getElementById('canvas');
ctx=canvas.getContext('2d');
cargar();
run();
}

/****************** METODOS AUXILIARES *****************************/

//Manejador del evento "keydown" ***********************************/


function detectarTecla(e){
var ev = (e)? e: event;
valorTecla = (ev.which)? ev.which: event.keyCode;
}

/************ metodos para cargar archivos TXT de datos ************/

//Manejador del evento 'Change' del objeto 'Archivo'


//Ejecuta cuando se selecciona un archivo
function manejarArchivo(evt){
var file = evt.target.files[0];
if(file){
var reader = new FileReader();
//evento para cargar archivo
reader.onload = function(e) {
archivoXXX = e.target.result.split(/\n/);
archivoCargado= true;
};
reader.readAsText(file);
}
else{
alert("No se pudo leer el archivo...");
}
}

/******************** FIN DEL CODIGO *******************************/

Localizando los componentes

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 el efecto usaremos una matriz de 20 x 20 enteros, en cada posición un número


representa el contenido de la celda, así:
0 – Nada
1 – Pared
2 – Piso libre
3 – Piso con marca de destino
4 – Caja en posición libre
5 – Caja en posición destino
6 – Soko

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:

Dividamos el mundo en celdas para poder codificar la información y llenar al matriz:

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?

Bueno, una solución sería restar 2:


Caja en posición libre (4 – 2 = 2), 2: Piso libre.
Caja en posición destino (5 – 2 = 3), 3: Piso con marca de destino.

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.

Por lo que en nuestro algoritmo, si se realiza un movimiento en una determinada casilla


y su valor actual es 4 o 5, entonces restaremos 2 para obtener el valor del siguiente
estado, Y si es 6 o 7, entonces restaremos 4 (Cuando estemos definiendo la lógica del
juego aplicaremos estas reglas). Ver el código js mas adelante.

Los objetos del juego

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.

En el código, en “PROPIEDADES GLOBALES” modifique así:

/****************** PROPIEDADES GLOBALES ***************************/


//referencia al objeto Canvas del DOM y al contexto de dibujo (global)
var canvas=null, ctx=null;

//Contendra enum de estados de la casilla


var valorCasilla = null;

// Matriz con el contenido de las celdas del escenario


var mundo= [];

// objeto con la hoja de sprites


var img1 = new Image();

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 *
********************************************************************/

/*Al cargar la pagina lanza el método INICIAR asociado al evento LOAD*/


window.addEventListener('load', iniciar, false);
/*window.document.addEventListener("keydown", detectarTecla, false);
window.document.addEventListener('change', manejarArchivo, false);

/****************** PROPIEDADES GLOBALES ***************************/


//referencia al objeto Canvas del DOM y al contexto de dibujo (global)
/****************** PROPIEDADES GLOBALES ***************************/
//referencia al objeto Canvas del DOM y al contexto de dibujo (global)
var canvas = null, ctx = null;

//Contendra enum de estados de la casilla


var valorCasilla = null;

// Matriz con el contenido de las celdas del escenario


var mundo = [];

// objeto con la hoja de sprites


var img1 = new Image();

/************************** METODOS ********************************/

/*********** CARGA IMAGENES, SPRITES, SONIDOS, ETC ****************/


function cargar() {

//Cargar valores en la matriz del escenario


mundo = [
[0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 4, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 2, 2, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 2, 4, 2, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
[1, 2, 2, 2, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 3, 3, 1, 0],
[1, 2, 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 1, 0],
[1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 6, 1, 1, 2, 2, 3, 3, 1, 0],
[0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0],

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]
];

//Posibles valores en la matriz del mundo


valorCasilla = {
"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);

//Carga el archivo con la hoja de sprites


img1.src = "Recursos/soko.png";
alert("cargar...")
}

/************* ACTUALIZACION PERIODICA DEL JUEGO *******************/


function actualizar(){
}

/*************** DIBUJO PERIODICO DEL CANVAS ********* **************/


function dibujar(){
}

/****************Controla el ciclo repetitivo***********************/


function run(){
setTimeout(run, 50);
actualizar();
dibujar(ctx);
}

/***************** INICIALIZACION EL PROGRAMA *******************/


function iniciar(){
canvas=document.getElementById('canvas');
ctx=canvas.getContext('2d');
cargar();
run();
}

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).

Reto: ¿Por qué la instrucción “Object.freeze(valorCasilla);”?

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:

/*************** DIBUJO PERIODICO DEL CANVAS ********* **************/


function dibujar() {
//Propiedades privadas. Para dibujar la imagen
var dX = 20; //Tamaño de cada casilla
var dY = 20;
var wFrame = 20; //tamaño del frame original
var hFrame = 20;

var iniX = 25; //Posicion inicial del dibujo del mundo


var iniY = 25;
var X = 0; //Posicion inicial de cada figura en el canvas
var Y = 0;
//Tamaño de la matriz del mundo, se asume cuadrada.
var tamMundo = 20;

for (var j = 0; j < tamMundo; j++) { //Recorre columnas del mundo


for (var i = 0; i < tamMundo; i++) { //Recorre filas
var obj = mundo[j][i]; //Lee el valor de la casilla a dibujar

X = i * dX + iniX; //Calcula la posicion en el canvas


Y = j * dY + iniY;

switch (obj) { //Segun el tipo de objeto...


case valorCasilla.pared:
case valorCasilla.piso:
case valorCasilla.destino:
//Dibuja la pared o el piso
ctx.drawImage(img1,obj*wFrame, 0, wFrame, hFrame, X, Y, dX, dY);
break;

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.

Posteriormente se recorre toda la matriz del mundo para extraer la información y


proceder a dibujar cada cuadro.

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?

• ¿Qué pasa si “tamMundo” vale 25?


• ¿Qué ocurre si intercambia las siguientes instrucciones (o las que dibuja a
Soko)?
//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);

• 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):

1. Detectar la orden dada. Utilizaremos las teclas UP/DOWN/LEFT/RIGTH.


2. Verificar si en la dirección detectada, la siguiente casilla (desde la posición de Soko)
está libre, entonces se podrá mover.
3. Si no está libre, verificar si hay una caja.
4. Si hay una caja, verificar que la siguiente casilla (desde la caja) está libre, entonces
se podrán mover la caja y Soko.
5. Si no está libre y no hay caja, NO se puede mover.

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:

//Posición actual y siguiente de Soko y direccion deseada


//Se definen como estructuras de datos (tipo POINT en C#)
var posActualSoko = {X:0, Y:0};
var posSigueSoko = {X:0, Y:0};
var dirSoko = {X:0, Y:0};
//Pos. destino de la caja a mover
var posSigueCaja = {X:0, Y:0};

Y la instrucción var tamMundo = 20; en dibujar(), llévela a las PROPIEDADES


GLOBALES, para hacer esta propiedad de ámbito global y poder usarla en otros
métodos:

//Tamaño de la matriz del mundo, se asume cuadrada.


var tamMundo = 20;

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:

/****************** METODOS AUXILIARES *****************************/


//Busca la posición de Soko en la matriz
function buscaPosSoko(){
for(var j=0; j< tamMundo; j++){ //Recorre columnas del mundo
for(var i=0; i< tamMundo; i++){ //Recorre filas
if((mundo[j][i] == valorCasilla.soko) ||
(mundo[j][i] == valorCasilla.sokoDestino)){
//encontro al personaje, guarda la posicion
posActualSoko.X = i;
posActualSoko.Y = j;
i=j= tamMundo; //Para salir de los ciclos...
}
}
}
}

Agregue al final del método cargar():

buscaPosSoko(); //coordenada inicial de Soko en el mundo

Recibir órdenes del teclado

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”:

/*Al cargar la pagina lanza el método INICIAR asociado al evento LOAD*/


window.addEventListener('load', iniciar, false);
window.document.addEventListener("keydown", detectarTecla, false);

Agregue una propiedad global que contendrá el valor de la tecla pulsada:

//Guarda el valor de la última tecla pulsada


var valorTecla =0;

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:

//Manejador del evento "keydown" **********************************/


function detectarTecla(e){
var ev = (e)? e: event;
valorTecla = (ev.which)? ev.which: event.keyCode;
}

Ahora vamos a codificar la detección de las teclas de flechas, actualizando la dirección


del movimiento (dirSoko). Por razones de eficiencia, se usará una variable de tipo lógico
(cambiar) para realizar el proceso posterior solo en caso de que una tecla haya sido
pulsada.

En el siguiente código, se inicia la dirección en cero, se revisa el valor de la tecla pulsada


reajustando la dirección, marcando la variable cambiar y borrando el valor de la tecla ya
usada, para evitar reusarla en el siguiente ciclo.

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.

/************* ACTUALIZACION PERIODICA DEL JUEGO *******************/


function actualizar() {
var cambiar = false;
dirSoko.X = dirSoko.Y = 0;
posSigueSoko.X = posActualSoko.X; //la misma posicion
posSigueSoko.Y = posActualSoko.Y;

switch (valorTecla) { //Segun la tecla pulsada...

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

if (cambiar) { //Si se ha pulsado una flecha


cambiaPosicionDeseada(); //modifica coordenada de posSigueSoko

//Verificar si siguiente casilla esta libre


if (casillaLibre(posSigueSoko)) {
mueveSoko(); //Si, mueve...
} //Si no, verificar si hay una caja
else if (casillaCaja(posSigueSoko)) {
//calcula sig. posicion de la caja
posSigueCaja.X = posSigueSoko.X + dirSoko.X;
posSigueCaja.Y = posSigueSoko.Y + dirSoko.Y;

//verificar siguiente posicion de la caja


if (casillaLibre(posSigueCaja)) {
mueveCaja(); //Si, mueve caja
mueveSoko(); // y a Soko
}
}
}
}

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:

cambiaPosicionDeseada(); casillaLibre(pos); mueveSoko(); casillaCaja(pos); mueveCaja()

cambiaPosicionDeseada() modifica las coordenadas de posSigueSoko y verifica que no


exceda los límites de la matriz del mundo:

//modifica coordenada de posSigueSoko y valida que no salga del mundo


function cambiaPosicionDeseada(){

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:

//revisa si la casilla en la posición “pos” está libre


function casillaLibre(pos){
if((mundo[pos.Y][pos.X] == valorCasilla.piso)||
(mundo[pos.Y][pos.X] == valorCasilla.destino))
return true;
else
return false;
}

casillaCaja(pos) revisa si la posición pos está ocupada por una caja:

//revisa si la casilla en la posición “pos” tiene una caja


function casillaCaja(pos){
if((mundo[pos.Y][pos.X] == valorCasilla.caja)||
(mundo[pos.Y][pos.X] == valorCasilla.cajaDestino))
return true;
else
return false;
}

mueveSoko() se usa para actualizar la posición del personaje:

//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;
}

Por último, mueveCaja():

//Cambia la posicion de la caja empujada


function mueveCaja(){
//La quita de la posicion actual

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;
}

Ejecute el programa, intente mover a Soko con las flechas…

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.

SOKO MIRA AL FRENTE

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í:

//Contendra enum de estados de la casilla y orientacion de Soko


var valorCasilla = null, miraSoko=null;
var mSoko=0;

Y definamos los valores en el método cargar():

//Frames a mostrar segun orientacion de Soko


miraSoko = {"derecha": 0, "arriba": 6, "izquierda": 7, "abajo": 8};
Object.freeze(miraSoko);

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:

switch(valorTecla){ //Segun la tecla pulsada...


case 37: //Izquierda
dirSoko.X--;
cambiar = true;
mSoko = miraSoko.izquierda;
break;
case 38: //Arriba
dirSoko.Y--;
cambiar = true;
mSoko = miraSoko.arriba;
break;
case 39: //Derecha
dirSoko.X++;
cambiar = true;
mSoko = miraSoko.derecha;
break;
case 40: //Abajo
dirSoko.Y++;
cambiar = true;
mSoko = miraSoko.abajo;
break;
default:
break; //Otra tecla o ninguna
}

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.

¿TERMINO SOKO SU JORNADA DE TRABAJO?

¿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.

Agregue, al inicio en las siguientes variables globales:

//Cuantas cajas hay y cuantas están en posición destino


var totalCajas = 0;
var totCajasDestino = 0;
var juegoTerminado = false;

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:

//Cuenta las cajas del tipo indicado****/


function cuentaCajas(tipoCaja){
var total = 0;
for(var j=0; j< tamMundo; j++){ //Recorre columnas del mundo
for(var i=0; i< tamMundo; i++){ //Recorre filas
if(mundo[j][i] == tipoCaja){
total++;
}
}
}
return total;
}

En el metodo cargar(), después de actualizar los valores de la matriz:

//Cuenta las cajas con que inicia el juego


totalCajas = cuentaCajas(valorCasilla.caja);
totalCajas += cuentaCajas(valorCasilla.cajaDestino);

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;
}

Ya sabemos cuándo termina el juego, más adelante usaremos esta información.

CONOCER LOS PUNTAJES

Se deben contar y mostrar la cantidad de pasos dados por Soko y la Cantidad de


movimientos de cajas realizados. Agregar otras dos variables globales:

//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++;

Y del método mueveCaja():

//Cuenta el movimiento de la caja


movimCajas++;

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%;
}

Y en el JS, las siguientes instrucciones:

- Al inicio, declarar las propiedades a relacionar con los objetos <h3> del DOM:

//referencia al objeto Canvas del DOM y al contexto de dibujo (global)


var canvas = null, ctx = null, verPasosSoko = null, verPasosCaja = null;

- En el método cargar(), instanciarlos, relacionándolos con el DOM:

function cargar(){
//Elementos del DOM
verPasosSoko = document.getElementById('pasosSoko');
verPasosCaja = document.getElementById('pasosCaja');

- Y por último, al final del método dibujar(), modificar el texto a mostrar:

verPasosSoko.innerHTML="Pasos Soko: "+ pasosSoko;


verPasosCaja.innerHTML="Movimientos Cajas: "+ movimCajas;

Así se deberá ver el juego:

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.

Bueno, la mayoría de navegadores reconocen los formatos ".ogg" y ".mp3". Debemos


convertir nuestros sonidos en formato “.wav” para hacer la aplicación compatible con

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;
}

- En el JS, recuerde declarar al inicio otras variables globales,

var audioPaso=null, audioCaja=null, audioNoMueve=null, audioAplausos=null;

328
- En el método cargar(),

audioPaso = document.getElementById("audioPaso");
audioCaja = document.getElementById("audioCaja");
audioNoMueve = document.getElementById("audioNoMueve");
audioAplausos = document.getElementById("audioAplausos");

- Y ejecutarlos en el momento adecuado, cambiando un poco la lógica, dentro del


método actualizar():

if(cambiar && !juegoTerminado){ //Si se ha pulsado una flecha


cambiaPosicionDeseada(); //modifica coordenada de posSigueSoko

//Verificar si siguiente casilla esta libre


if(casillaLibre(posSigueSoko)){
mueveSoko(); //Si, mueve...
audioPaso.play();
} //Si no, verificar si hay una caja
else if(casillaCaja(posSigueSoko)){
//calcula sig. posicion de la caja
posSigueCaja.X = posSigueSoko.X + dirSoko.X;
posSigueCaja.Y = posSigueSoko.Y + dirSoko.Y;

//verificar siguiente posicion de la caja


if(casillaLibre(posSigueCaja)){
mueveCaja(); //Si, mueve caja
mueveSoko(); // y a Soko
audioCaja.play();
}
else{
audioNoMueve.play();
}
}
else{
audioNoMueve.play();
}

//Contar cajas en posicion final y verificar si ya termino


totCajasDestino = cuentaCajas(valorCasilla.cajaDestino);
if(totCajasDestino == totalCajas){
juegoTerminado = true;
audioAplausos.play();
}
}

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):

1. Al iniciar, leer un archivo con la información de cierta cantidad de niveles.


2. Presentar el listado de niveles disponibles para que el jugador pueda seleccionar
uno.
3. Botones para seleccionar el siguiente o anterior nivel.

Cargando los mundos

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.

; SOKOBAN Ver. 1.0


; 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
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

#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
#

Ya tenemos el archivo, lo llamaremos “Niveles.txt” (o el nombre que desee). Guárdelo en


su carpeta del proyecto. Después de leerlo lo llevaremos a un arreglo de cadenas, para
poder manipularlo dinámicamente, cuando el usuario lo desee. Para el efecto, crearemos
tres botones, el primero para seleccionar el archivo, uno para cargar el mundo anterior
al actual y el último para cargar el mundo siguiente.

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 de archivo y de selectores de nivel ****/


.botones {
overflow: hidden;
position: relative;
cursor: pointer;
margin-left: 8%;
background-color: #009999;
width: 110px;
height: 25px;
padding: 5px;
font-size: 1.1em;
color: white;
float: left;
}

.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”.

Para cargar el archivo, siga estos pasos en el JS:


- Adicione el manejador del evento “change”, cuando se selecciona un archivo,
junto a los otros:

window.document.addEventListener('change', manejarArchivo, false);

- Agregue una propiedad para guardar los mundos y otra (booleana) para indicar si
ya se cargó un archivo (ambas públicas):

//Para leer el archivo con varios mundos


var archivoMundos = new Array();
var archivoCargado = false;
- Al final del código, el método manejador y los métodos asociados a los otros dos
botones, por ahora no tienen nada (provisionalmente se agregó la instrucción en
la línea 375 (No olvide eliminarla después de la prueba):

/************ métodos para cargar varios mundos ********************/


//Manejador del evento 'Change' del objeto 'Archivo'
//Ejecuta cuando se selecciona un archivo
function manejarArchivo(evt) {
var file = evt.target.files[0];
if (file) {
var reader = new FileReader();
//evento para cargar archivoMundos cuando se lea el archivo
reader.onload = function (e) {
archivoMundos = e.target.result.split(/\n/);
archivoCargado = true;
};
reader.readAsText(file);
}
else {
alert("No se pudo leer el archivo...");
}
}

//Carga el mundo anterior al actual. Revisa si ya esta el No. 1


function mundoAnterior() {
}

//Carga el mundo siguiente al actual. Revisa si ya esta el ultimo


function mundoSiguiente() {
alert(archivoMundos[2]);
}

335
/******************** FIN DEL CODIGO *******************************/

Listo, ahora vamos a probar lo anterior:

- Guarde los cambios, refresque la página,


- pulse “Cargar Archivo”, seleccione el archivo anteriormente guardado,
- pulse “Siguiente”. Debe mostrar una ventana modal con la tercera línea del archivo
“Niveles.txt”.

Cargando y visualizando los niveles

Es necesaria otra propiedad para saber qué nivel está activo:

//Indica el numero del mundo actual


var nivelActivo = 1;

Codifique los métodos asociados a los botones:

//Carga el mundo anterior al actual. Revisa si ya esta el No. 1


function mundoAnterior() {

336
if (archivoCargado && (nivelActivo > 1)) {
nivelActivo--;
leerNivel();
}
else {
alert("No existe un nivel menor al #" + nivelActivo);
}
}

//Carga el mundo siguiente al actual. Revisa si ya esta el ultimo


function mundoSiguiente() {
if (archivoCargado && (nivelActivo < 10)) { //Ojo modificar constante!!
nivelActivo++;
leerNivel();
}
else {
alert("No existe un nivel mayor 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”:

//Carga el nuevo nivel en la matriz mundo


//Reinicia contadores de pasos y cajas
function leerNivel(){

//Recorre el arreglo buscando el numero de nivel a cargar


var i=0; //va contando el numero de linea

while(i < archivoMundos.length){


if(archivoMundos[i].substring(0,1) == "#"){ //Busca inicio de un mundo
//Si lo encuentra...
if(parseInt(archivoMundos[i].substring(1))== nivelActivo){
//Avanza el contador de linea y carga la matriz
cargaMatriz(++i);
i= archivoMundos.length; //Para salir del bucle
}
}
i++; //Siguiente linea y sigue buscando
}
}

Para cargar la matriz, se llama al método “cargaMatriz(++i)” que inicia en la siguiente


línea a la que tiene el número de nivel deseado.

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

var filaDatos = new Array();


filaDatos = archivoMundos[fi].split(","); //Separa los numeros de una fila

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.

//Carga la matriz desde el arreglo archivoMundos iniciando en la fila fi


//Parametro: fi = numero de la linea para empezar a cargar
function cargaMatriz(fi) {
//Limpia la matriz del mundo
for (var j = 0; j < tamMundo; j++) {
for (var i = 0; i < tamMundo; i++) {
mundo[j][i] = 0;
}
}
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();
//Separa los numeros de una fila
filaDatos = archivoMundos[fi].split(",");
//Recorre los numeros de una fila
for (var i = 0; i < filaDatos.length; i++) {
var res = parseInt(filaDatos[i]); //Toma cada numero
mundo[j][i] = res; //Lo guarda en la matriz del mundo
}
j++ //Siguiente fila del mundo
fi++ //Siguiente fila de archivoMundos
}

//Reinicia valores del nuevo juego


reiniciar();
}

Para terminar, hay que encontrar la nueva posición de Soko y reiniciar contadores, se
usa un nuevo método reiniciar().

//Reinicia las propiedades para el nuevo juego


function reiniciar(){
buscaPosSoko();
pasosSoko = 0;
movimCajas = 0;
juegoTerminado = false;
totalCajas = cuentaCajas(valorCasilla.caja);
totalCajas += cuentaCajas(valorCasilla.cajaDestino);
cajaDestino = 0;
}

338
Guarde y ejecute. ¿Qué pasa? ¿Por qué se sobreponen los diferentes mundos?

El problema se soluciona agregando la siguiente instrucción al método dibujar(), antes


de iniciar el ciclo for.

//Borrar el contenido del canvas


ctx.clearRect(0, 0, canvas.width, canvas.height);

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.

Lo anterior aplica cuando el código es pequeño. En caso de programas más complejos,


es mucho más eficiente realizar el análisis y diseño con un enfoque como la POO y con
herramientas adecuadas, por ejemplo UML.

Para entender mejor lo expuesto, experimente el código, modifique valores y ensaye.


Recuerde que los algoritmos presentados son susceptibles de mejora, busque en el
código “números mágicos” (como el tamaño de la matriz 20 x20, o del canvas) y defínalos
como constantes, así, si se quiere modificar su valor, basta con cambiar en un solo punto
sin tener que recorrer todo el código.

Preste atención a la documentación del código, aunque no lo crea, es importante una


buena documentación para el mantenimiento posterior.

Reto: Modificar el código para agregarle otras prestaciones, por ejemplo:

• Agregue al título el nivel actual


• Agregue un botón “Reiniciar” que haga uso del método reiniciar() para dejar en
ceros el mundo actual.
• Agregue lo necesario para poder devolver o avanzar una o más jugadas.
• Desarrollar un aplicativo para crear nuevos mundos.
• Utilizar la Programación Orientada a Objetos (POO) para reordenar el código y
hacerlo más flexible.
• Etc…

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 )

Operadores lógicos: Devuelven un bool


Operador Descripción Ejemplo

and ¿se cumple a y b? r = True and False, # r es False

or ¿se cumple a o b? r = True o False, # r es True

not No a r= not True # r es False

Operadores relacionales: comparan dos expresiones y devuelven un bool


Operador Descripción Ejemplo
== ¿Son iguales a y b? r = 5 == 3 # r es False
!= ¿Son distintos a y b? r = 5 != 3 # r es True
< ¿Es a menor que b? r = 5 < 3 # r es False
> ¿Es a mayor que b? r = 5 > 3 # r es True
<= ¿Es a menor o igual que b? r = 5 <= 5 # r es True
>= ¿Es a mayor o igual que b? r = 5 >= 3 # r es True

Operadores aritméticos: de números reales y enteros


Operador Descripción Ejemplo
+ Suma r = 3 + 2 # r es 5
- Resta r = 4 - 7 # r es -3
- Negación r = -7 # r es -7
* Multiplicación r = 2*6 # r es 12
** Exponente r= 2**6 # r es 64
/ División r = 3.5 / 2 # r es 1.75
// División Entera r = 3.5 // 2 r es 1.0
% Módulo r = 7 % 2 # r es 1

Operadores a nivel de Bit


Operador Descripción Ejemplo
& and r = 3 & 2 # r es 2
| or r = 3 | 2 # r es 3
^ xor r = 3^2 # r es 1

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

Particularidades los tipos de datos en Python.

Tipos dinámicos: Python es un lenguaje que no requiere que se defina el tipo de un


objeto. El intérprete "infiere" el tipo de dato del que se trata.

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

Números de punto Los objetos tipo float corresponden al conjunto de 3.14


flotante (float) los números reales. 12

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

Valores booleanos El tipo booleano es una especie de tipo numérico


True, False
(bool) que es utilizado para evaluar expresiones lógicas.

Cadenas de caracteres (str) .


Las cadenas de caracteres son secuencias de caracteres encerradas entre comillas (" ")
o apóstrofes (' ') indistintamente: 'Hola Mundo', "Hola Mundo".

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

Reglas de precedencia en operaciones aritméticas.


Los operadores se apegan a la siguiente regla de precedencia siguiendo una secuencia
de izquierda a derecha:
Paréntesis.
Exponente.
Multiplicación.
División.
Suma.
Sustracción.

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.

Un buen conocimiento de estas operaciones le permite definir la manera correcta de


hacer la prueba lógica, cuya mala implementación es causa de muchos errores, algunos
de ellos de difícil depuración debido a que son poco obvios.

Los elementos en el universo booleano toman valores 0 y 1 (falso y Verdadero) y se


definen las operaciones Unión (O, OR, +), Intersección (Y, AND, •), Negación o
complemento (NO, NOT, ¯) y O exclusiva (XOR).

Las siguientes Tablas de Verdad muestran el resultado de las operaciones para las
distintas combinaciones posibles de los valores de operandos:

El operador NEGADO (NOT) se lee “A negado”, es decir, el resultado es lo contrario del


valor del operando. A solo puede tomar dos valores y la negación es el valor contrario.

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.

El operador O EXCLUSIVO (XOR) se lee “Si A o B exclusivo”, es decir, si solo uno de


los dos es verdad, entonces el enunciado es verdad.

En seguida se presentan las propiedades más importantes del álgebra booleana:

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 *
********************************************************************/

/*Al cargar la pagina lanza el método INICIAR asociado al evento LOAD*/


window.addEventListener('load', iniciar, false);
window.document.addEventListener("keydown", detectarTecla, false);
window.document.addEventListener('change', manejarArchivo, false);

/****************** PROPIEDADES GLOBALES ***************************/

//referencia al objeto Canvas del DOM y al contexto de dibujo (global)


/****************** PROPIEDADES GLOBALES ***************************/
//referencia al objeto Canvas del DOM y al contexto de dibujo (global)
var canvas = null, ctx = null, verPasosSoko = null, verPasosCaja = null;
var audioPaso = null, audioCaja = null, audioNoMueve = null, audioAplausos = null;

//Contendra enum de estados de la casilla y orientacion de Soko


var valorCasilla = null, miraSoko = null;
var mSoko = 0;

//Contendra enum de estados de la casilla


var valorCasilla = null;

// Matriz con el contenido de las celdas del escenario


var mundo = [];

//Guarda el valor de la ultima tecla pulsada


var valorTecla = 0;

//Cuantas cajas hay y cuantas estan en posicion destino


var totalCajas = 0;
var totCajasDestino = 0;
var juegoTerminado = false;

//Contadores de movimiento
var pasosSoko = 0;
var movimCajas = 0;

// objeto con la hoja de sprites


var img1 = new Image();

//Posición actual y siguiente de Soko y direccion deseada


//Se definen como estructuras de datos (tipo POINT en C#)
var posActualSoko = { X: 0, Y: 0 };
var posSigueSoko = { X: 0, Y: 0 };

347
var dirSoko = { X: 0, Y: 0 };

//Pos. destino de la caja a mover


var posSigueCaja = { X: 0, Y: 0 };

//Tamaño de la matriz del mundo, se asume cuadrada.


var tamMundo = 20;

//Para leer el archivo con varios mundos


var archivoMundos = new Array();
var archivoCargado = false;

//Indica el numero del mundo actual


var nivelActivo = 1;

/************************** METODOS ********************************/

/*********** CARGA IMAGENES, SPRITES, SONIDOS, ETC ****************/


function cargar() {
//Elementos del DOM
verPasosSoko = document.getElementById('pasosSoko');
verPasosCaja = document.getElementById('pasosCaja');

audioPaso = document.getElementById("audioPaso");
audioCaja = document.getElementById("audioCaja");
audioNoMueve = document.getElementById("audioNoMueve");
audioAplausos = document.getElementById("audioAplausos");

//Cargar valores en la matriz del escenario


mundo = [
[0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 4, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 2, 2, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 2, 4, 2, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
[1, 2, 2, 2, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 3, 3, 1, 0],
[1, 2, 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 1, 0],
[1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 6, 1, 1, 2, 2, 3, 3, 1, 0],
[0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];

//Posibles valores en la matriz del mundo


valorCasilla = {

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);

//Frames a mostrar segun orientacion de Soko


miraSoko = { "derecha": 0, "arriba": 6, "izquierda": 7, "abajo": 8 };
Object.freeze(miraSoko);

//Carga el archivo con la hoja de sprites


img1.src = "Recursos/soko.png";

buscaPosSoko(); //coordenada inicial de Soko en el mundo

//Cuenta las cajas con que inicia el juego


totalCajas = cuentaCajas(valorCasilla.caja);
totalCajas += cuentaCajas(valorCasilla.cajaDestino);

/************* ACTUALIZACION PERIODICA DEL JUEGO *******************/


function actualizar() {
var cambiar = false;
dirSoko.X = dirSoko.Y = 0;
posSigueSoko.X = posActualSoko.X; //la misma posicion
posSigueSoko.Y = posActualSoko.Y;

switch (valorTecla) { //Segun la tecla pulsada...


case 37: //Izquierda
dirSoko.X--;
cambiar = true;
mSoko = miraSoko.izquierda;
break;
case 38: //Arriba
dirSoko.Y--;
cambiar = true;
mSoko = miraSoko.arriba;
break;
case 39: //Derecha
dirSoko.X++;
cambiar = true;
mSoko = miraSoko.derecha;
break;
case 40: //Abajo
dirSoko.Y++;
cambiar = true;
mSoko = miraSoko.abajo;
break;

349
default:
break; //Otra tecla o ninguna
}
valorTecla = 0; //Borra para no usar de nuevo

if (cambiar && !juegoTerminado) { //Si se ha pulsado una flecha


cambiaPosicionDeseada(); //modifica coordenada de posSigueSoko

//Verificar si siguiente casilla esta libre


if (casillaLibre(posSigueSoko)) {
mueveSoko(); //Si, mueve...
audioPaso.play();
} //Si no, verificar si hay una caja
else if (casillaCaja(posSigueSoko)) {
//calcula sig. posicion de la caja
posSigueCaja.X = posSigueSoko.X + dirSoko.X;
posSigueCaja.Y = posSigueSoko.Y + dirSoko.Y;

//verificar siguiente posicion de la caja


if (casillaLibre(posSigueCaja)) {
mueveCaja(); //Si, mueve caja
mueveSoko(); // y a Soko
audioCaja.play();
}
else {
audioNoMueve.play();
}
}
else {
audioNoMueve.play();
}

//Contar cajas en posicion final y verificar si ya termino


totCajasDestino = cuentaCajas(valorCasilla.cajaDestino);
if (totCajasDestino == totalCajas) {
juegoTerminado = true;
audioAplausos.play();
}
}
}

/*************** DIBUJO PERIODICO DEL CANVAS ********* **************/


function dibujar() {

//Propiedades privadas. Para dibujar la imagen


var dX = 20; //Tamaño de cada casilla
var dY = 20;
var wFrame = 20; //tamaño del frame original
var hFrame = 20;

var iniX = 25; //Posicion inicial del dibujo del mundo


var iniY = 25;
var X = 0; //Posicion inicial de cada figura en el canvas
var Y = 0;

350
//Borrar el contenido del canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);

for (var j = 0; j < tamMundo; j++) { //Recorre columnas del mundo


for (var i = 0; i < tamMundo; i++) { //Recorre filas
var obj = mundo[j][i]; //Lee el valor de la casilla a dibujar

X = i * dX + iniX; //Calcula la posicion en el canvas


Y = j * dY + iniY;

switch (obj) { //Segun el tipo de objeto...


case valorCasilla.pared:
case valorCasilla.piso:
case valorCasilla.destino:
//Dibuja la pared o el piso
ctx.drawImage(img1, obj * wFrame,0,wFrame,hFrame,X,Y,dX,dY);
break;

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;
}

/****************Controla el ciclo repetitivo***********************/


function run(){
setTimeout(run, 50);
actualizar();
dibujar(ctx);
}

/***************** INICIALIZACION EL PROGRAMA *******************/


function iniciar(){
canvas=document.getElementById('canvas');
ctx=canvas.getContext('2d');
cargar();
run();

351
}

/****************** METODOS AUXILIARES *****************************/

//Busca la posicion de Soko en la matriz


function buscaPosSoko() {
for (var j = 0; j < tamMundo; j++) { //Recorre columnas del
mundo
for (var i = 0; i < tamMundo; i++) { //Recorre filas
if ((mundo[j][i] == valorCasilla.soko) ||
(mundo[j][i] == valorCasilla.sokoDestino)) {
//encontro al personaje, guarda la posicion
posActualSoko.X = i;
posActualSoko.Y = j;
i = j = tamMundo; //Para salir de los ciclos...
}
}
}
}

//Manejador del evento "keydown" **********************************/


function detectarTecla(e){
var ev = (e)? e: event;
valorTecla = (ev.which)? ev.which: event.keyCode;
}

//modifica coordenada de posSigueSoko y valida que no salga del mundo


function cambiaPosicionDeseada() {
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++;
}

//revisa si la casilla en la posición “pos” está libre


function casillaLibre(pos) {
if ((mundo[pos.Y][pos.X] == valorCasilla.piso) ||
(mundo[pos.Y][pos.X] == valorCasilla.destino))
return true;
else
return false;
}

//revisa si la casilla en la posición “pos” tiene una caja


function casillaCaja(pos) {
if ((mundo[pos.Y][pos.X] == valorCasilla.caja) ||
(mundo[pos.Y][pos.X] == valorCasilla.cajaDestino))
return true;
else
return false;
}

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++;

//Cambia la posicion de la caja empujada


function mueveCaja() {
//La quita de la posicion actual
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;

//Cuenta el movimiento de la caja


movimCajas++;

//Cuenta las cajas del tipo indicado****


function cuentaCajas(tipoCaja) {
var total = 0;
for (var j = 0; j < tamMundo; j++) { //Recorre columnas del mundo
for (var i = 0; i < tamMundo; i++) { //Recorre filas
if (mundo[j][i] == tipoCaja) {
total++;
}
}
}
return total;
}

/************ metodos para cargar varios mundos ********************/


//Manejador del evento 'Change' del objeto 'Archivo'
//Ejecuta cuando se selecciona un archivo
function manejarArchivo(evt) {
var file = evt.target.files[0];
if (file) {
var reader = new FileReader();
//evento para cargar archivoMundos cuando se lea el archivo
reader.onload = function (e) {
archivoMundos = e.target.result.split(/\n/);
archivoCargado = true;
};

353
reader.readAsText(file);
}
else {
alert("No se pudo leer el archivo...");
}
}

//Carga el mundo anterior al actual. Revisa si ya esta el No. 1


function mundoAnterior() {
if (archivoCargado && (nivelActivo > 1)) {
nivelActivo--;
leerNivel();
}
else {
alert("No existe un nivel menor al #" + nivelActivo);
}
}

//Carga el mundo siguiente al actual. Revisa si ya esta el ultimo


function mundoSiguiente() {
if (archivoCargado && (nivelActivo < 10)) { //Ojo modificar constante!!
nivelActivo++;
leerNivel();
}
else {
alert("No existe un nivel mayor al #" + nivelActivo);
}
}

//Carga el nuevo nivel en la matriz mundo


//Reinicia contadores de pasos y cajas
function leerNivel() {

//Recorre el arreglo buscando el numero de nivel a cargar


var i = 0; //va contando el numero de linea

while (i < archivoMundos.length) {


if (archivoMundos[i].substring(0, 1) == "#") { //Busca inicio de un mundo
//Si lo encuentra...
if (parseInt(archivoMundos[i].substring(1)) == nivelActivo) {
cargaMatriz(++i); //Avanza contador de linea y carga matriz
i = archivoMundos.length; //Para salir del bucle
}
}
i++; //Siguiente linea y sigue buscando
}
}

//Carga la matriz desde el arreglo archivoMundos iniciando en la fila fi


//Parametro: fi = numero de la linea para empezar a cargar
function cargaMatriz(fi) {
//Limpia la matriz del mundo
for (var j = 0; j < tamMundo; j++) {
for (var i = 0; i < tamMundo; i++) {
mundo[j][i] = 0;

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
}

//Reinicia valores del nuevo juego


reiniciar();
}

//Reinicia las propiedades para el nuevo juego


function reiniciar() {
buscaPosSoko();
pasosSoko = 0;
movimCajas = 0;
juegoTerminado = false;
totalCajas = cuentaCajas(valorCasilla.caja);
totalCajas += cuentaCajas(valorCasilla.cajaDestino);
cajaDestino = 0;
}

/******************** FIN DEL CODIGO *******************************/

355
356
ANEXO 4. GLOSARIO

Abstracción: Permite simplificar el entendimiento de un componente de software al


hacer énfasis en el “¿Qué hace?”, desentendiéndose del “¿Cómo lo hace?”. Consiste
en conocer las Entradas y Salidas, ignorando el proceso.

Abortar: Consiste en detener la ejecución de un código de manera abrupta, ya sea por


intervención humana o por error del código.

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.

Acumulador: En programación, se refiera a una variable que guarda cantidades


variables.

Algoritmo: Es el conjunto de instrucciones o reglas, que permite realizar una actividad


desde el inicio hasta el fin.

Ambiente de desarrollo: Es una aplicación que permite, de manera integrada, que el


programador realice, de manera más fácil, el desarrollo del código. También se
denomina Ambiente de Desarrollo Integrado (IDE)

Ámbito: Es el espacio, o el (los) modulo(s), en los que una variable es “visible” o se


puede acusar.

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.

Anidar: En el proceso de codificar, los bloques de código embebidos (insertados) dentro


de otros bloques, siguiendo la regla básica de mantener una sola entrada y una sola
salida, para cada bloque de código.

Apuntador: Una variable cuyo contenido es la posición o la dirección de otra variable u


objeto, dentro de un arreglo, matriz, lista, etc.

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…”.

Base numérica: En un sistema numérico, define la magnitud en que se incrementa cada


digito de mayor peso (a la izquierda). También señala la cantidad de símbolos
empleados para representar los números. Por ejemplo, el sistema decimal utiliza 10
símbolos y el sistema binario utiliza dos números.

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…).

Buffer: Comúnmente se refiere a un área de memoria para almacenamiento provisional,


para transferencia de datos.

Búsqueda binaria: Algoritmo para búsqueda de elementos en estructuras ordenadas,


permite ir segmentando el área de búsqueda en n/2 del área inicial n.

Cadena (string): Tipo de datos que contiene caracteres y con los que no se puede
realizar operaciones aritméticas.

Caja de herramientas (toolbox): Conjunto de algoritmos de uso común, con el que el


programador experimentado cuenta para agilizar el desarrollo de algoritmos.

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.

Caso: Estructura de programación básica, en la que el flujo del programa recorre


caminos alternativos dependiendo del valor de una variable.

Casos base: En un algoritmo recursivo, se refiere al segmento del algoritmo en el que


se encuentra una solución y, por lo tanto, no se realiza una nueva llamada.

Clase: En POO, es la definición de las características y comportamiento (propiedades y


métodos) de una “familia” de objetos relacionados.

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.

Cohesión: En POO, es la manera en que se agrupan las unidades o módulos. Se busca


una alta cohesión para tener módulos lo más especializados posibles, con poca
interacción con otros módulos. Esto hace al código más fácil de diseñar, programar,
probar y mantener.

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.

Compilar: En general, es el proceso de traducir un código fuente en instrucciones


ejecutables por la máquina. También puede producir un código en lenguaje
intermedio.

Composición: En POO, es un tipo de relación de dependencia, en la que un objeto es


formado por otros objetos.

Concatenar: Unir dos o más variables del tipo cadena de texto (string), generalmente
produce una tercera variable.

Conclusión: Es el resultado de efectuar un razonamiento lógico, a partir de unas


premisas. La conclusión puede dar como resultado verdadero, falso o posible.

Constante: Valor que no cambia. Un campo o memoria que contiene un valor estático
durante la ejecución del programa.

Constructor: En POO, hace referencia a un método que se ejecuta en primer lugar, al


instanciar un objeto de determinada clase.

359
Contador: Es una variable en la memoria que se incrementará en una unidad cada vez
que se ejecute un proceso.

Controlador: Módulos de código que se ejecutan al presentarse un evento determinado.

Control de Flujo: Hace referencia al orden en que se ejecutan las instrucciones en el


programa. Este orden depende de las estructuras utilizadas y del resultado de las
evaluaciones lógicas en ellas.

Corrida: Se refiere a cada ejecución de un programa.

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.

Decisión: ver Estructura de Decisión.

Declarar: En el código fuente, es la definición del nombre y tipo de un objeto. El objeto


puede ser instanciado en la misma instrucción o posteriormente.

Delegado: Es una referencia a un método, con una lista de parámetros y un valor


devuelto. Se utiliza para invocar controladores de eventos. También son utilizados
para que una clase delegue en otra alguna funcionalidad (similar a la herencia).

Depurar: Revisar el código para corregir errores sintácticos, semánticos o de lógica en


un algoritmo.

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.

Dimensión: Es el “orden” de un arreglo o matriz, se refiere al número de elementos


(subíndices) en cada una de los componentes utilizados para referenciar un elemento
de la matriz.

Dirección: La posición física en la memoria, o relativa en un área determinada, de una


variable.

Docblock: En el código fuente, bloque de comentarios que identifica un objeto o método.


Contiene el nombre, la descripción, identificación de parámetros, tipo devuelto, fecha,
programador, versión, etc.

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.

El cómo: En el proceso de análisis de un algoritmo, hace referencia a la lógica o la


metodología utilizada (proceso) para lograr entregar los resultados solicitados (salida)
a partir de los elementos disponibles (entrada).

El qué: En el proceso de análisis de un algoritmo, hace referencia a los resultados


deseados (salida).

Entidad: En el diseño de Bases de Datos, es la representación de un objeto o un


concepto del mundo real.

Entrada: En el proceso de análisis de un algoritmo, hace referencia a los elementos con


que se cuenta para lograr los resultados esperados.

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.

Estructura de decisión: Una de las estructuras básicas de la programación. Permite


seleccionar un camino alternativo en el flujo del programa, como resultado de una
evaluación lógica. Se puede subdividir en decisión simple, compuesta o múltiple.

Estructura iterativa: Una de las estructuras básicas de la programación. Permite que el


flujo del programa repita un bloque de código (proceso).

Estructura secuencial: Una de las estructuras básicas de la programación. En ella, el


flujo del programa recorre uno a uno de los procesos que componen la estructura.

Estructura de datos: Es un conjunto de datos elementales, que pueden ser de


diferentes tipos, que hacen parte de las propiedades de un objeto. Lo anterior permite
tener una lista o conjunto de objetos y tener acceso a cada dato de manera más
eficiente.

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.

Framework: Ambiente de trabajo para desarrollo de programas; normalmente integra


componentes que facilitan el desarrollo de aplicaciones como una interfaz gráfica,
soportes de programa, bibliotecas, plantillas, etc.

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.

Global: Ver “Variable Global”.

Haga – mientras: Implementación de una de las estructuras básicas de la programación.


En ella, el proceso se ejecuta al menos una vez (haga), antes de realizar la prueba
lógica (mientras) para decidir si se realiza o no otra iteración.

Herencia: En POO, cuando una clase (hija) se implementa utilizando características de


una clase superior (padre). Estas características pueden ser propiedades o métodos.

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.

HTML: Acrónimo de HyperText Markup Languaje (Lenguaje de marcas de hipertexto).


Lenguaje empleado para el desarrollo de páginas de internet. Está compuesto por
etiquetas que el navegador interpreta y da forma en la pantalla. HTML dispone de
etiquetas para imágenes, hipervínculos, saltos de línea, listas, tablas, etc.

Indentación: “Buena práctica” de programación, consistente en desplazar las líneas de


código para visualizar la estructura de programación empleada. Esto permite un más
rápido entendimiento de la lógica y facilita la depuración del código.

Índice: Variable que contiene como valor, la posición de un elemento dentro de un


arreglo o vector.

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.

Iteración: Cada repetición en un bloque de código.

Javascript: Es un lenguaje de programación que permite crear una interaccion dinámica


en las páginas web. Al contrario de PHP que se usa al lado del servidor, Javascript
se utiliza al lado del cliente, aportando la lógica para complementar las funcionalidades
de HTML y CSS.

Lenguaje de programación: Es un lenguaje formal, diseñado para “traducir” un


algoritmo definido en lenguaje humano, a códigos capaces de ser ejecutados por la
computadora.

Librería: Es un conjunto de funcionalidades o bloques de codigo relacionados entre sí,


a ser utilizados en un programa. Estas funcionalidades no pueden ser invocadas de
manera autónoma, sino dentro del contexto del código implementado por el
programador.

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.

Local: Ver “Variable Local”.

Lógica: Método de razonamiento en el que las ideas se desarrollan de forma coherente


y sin contradicciones.

Maquetado: En el desarrollo de páginas web, es la diagramación de los elementos de la


página, incluyendo forma y color, para que sean agradables a la vista.

Matriz: Estructura de objetos o de datos, en la que los componentes se identifican por


un orden o ubicación. Las matrices son, por definición, multidimensionales.

Memoria: Dispositivo electrónico que permite el almacenamiento y retención de datos


en un computador.

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.

Método Constructor: En POO, es el método que se ejecuta al instanciar un objeto.

Miembro: Cualquier varible o función, propiedad o método o grupo de ellos que


pertenecen a un modulo o a un objeto identificable.

Modelo: Representación simplificada de una parte de la realidad (o del mundo). La


definición del modelo es parte fundamental del análisis que debe llevarse a cabo antes
de codificar un programa.

Modelo – Vista – Controlador: Es un estilo de arquitectura de software, en el que las


partes del sistema están agrupadas en tres bloques: El Modelo, es la representación
de los datos; la Vista, presenta la información al usuario de manera adecuada,
permitiéndole interactuar más cómodamente; el Controlador responde a los eventos o
a las acciones del usuario, haciendo de “intermediario” ente la vista y el modelo.

Modularidad: En POO, es la propiedad que permite subdividir un componente en partes


más pequeñas (llamadas módulos), tan independientes entre sí, como sea posible.

Multidifusión: En la programación orientada a eventos, un evento puede “disparar”


simultáneamente la ejecución de distintos “manejadores” o módulos que respondan a
dicho evento.

Notación infija: Sistema de representación de funciones aritméticas y lógicas, en la que


el operador va entre los operando.

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.

Observador: Un patrón de diseño en el que un objeto (observado), cuando cambia su


estado, notifica a todos los objetos dependientes (observadores), quienes reaccionan
a él.

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”.

Optimización: Cambios realizados a la lógica del programa para reducir el número de


ciclos, o de instrucciones a ejecutar, para hacer su ejecución más rápida o para reducir
el espacio de memoria ocupado.

Palabras reservadas: En un lenguaje de programación, son aquellas con significado


gramatical especial. No pueden ser utilizadas para identificar objetos o variables.

Para: Implementación de una de las estructuras básicas de la programación. En ella, el


proceso se ejecuta una cantidad de veces fija y conocida; la prueba lógica para decidir
si se realiza o no otra iteración se realiza contra un contador de los ciclos realizados.

Paradigma: Un paradigma de programación es un enfoque particular para diseñar


soluciones. Es una forma de “razonar” y diseñar una solución al problema planteado.

Parámetro: información necesaria para que un método realice su trabajo. Los


parámetros deben ser suministrados al momento de invocar el método.

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.

Paso por referencia: Cuando se suministra un parámetro a un método, se suministra la


“dirección” de una propiedad. Cualquier cambio ejecutado en ese valor dentro del
método afectara directamente a la propiedad original.

Patrón de diseño: Es un “esquema” o “plantilla” para la solución de un problema


particular, ofreciendo resultados ya probados y documentados a problemas que se

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).

Persistencia: Capacidad para mantener un registro del estado de la memoria del


programa, o de las propiedades de algunos objetos importantes, para ser recuperados
al ejecutarse una nueva corrida. También hace referencia a la posibilidad de registrar
los datos en un formato adecuado para poder ser transmitidos.

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.

Polimorfismo: En POO, es la capacidad de un método de responder de manera diversa,


dependiendo de los parámetros utilizados al momento de invocarlo. Dependiendo de
los tipos de datos de los parámetros, el método devuelve resultados distintos.

Precedencia: Es el orden en que se evalúan los operadores en una instrucción, en un


lenguaje determinado. Las operaciones con mayor precedencia se evalúan primero.

Problema: Planteamiento de una situación que requiere ser solucionada, a partir de unos
datos iniciales conocidos.

Procedimiento: En programación, un módulo de programa separado, que puede


requerir parámetros de entrada y ejecuta alguna operación, pero, a diferencia de una
función, no retorna ningún valor.

Proceso: En programación, serie de pasos para resolver un problema y obtener una


salida, a partir de unos datos de entrada.

Programación: Proceso de diseñar un algoritmo, programarlo en un lenguaje, corregir


posibles errores (depurarlo), generando código con instrucciones que sean ejecutadas
por un computador para resolver un problema determinado.

Programación orientada por eventos: Es una forma de programación (paradigma) en


la que los diferentes módulos son invocados y ejecutados como respuesta a la
generación de diferentes eventos provocados por el usuario, por otros procesos o
módulos o por el hardware.

Programación orientada a objetos (POO): un paradigma de programación, en el que


el diseñador organiza los módulos de código y las variables, siguiendo un “modelo” de
los objetos en el mundo real del problema a solucionar. La POO utiliza técnicas como

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.

Proposición: En lógica, es una oración gramatical expresada en lenguaje natural o


formal (por ejemplo, notación matemática), que tiene un valor de verdad (verdadero o
falso).

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”.

Proveedor: En el patrón de diseño “observador”, es la entidad que suministra


notificaciones a los “suscriptores”.

Puntero: Ver Apuntador.

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.

Razonar: Actividad mental que permite lograr la estructuración y la organización de las


ideas para llegar a una conclusión.

Recursividad: Técnica de solución de algoritmos, en la que el modulo o método se


invoca a sí mismo, hasta llegar a una solución base, a partir de la cual va resolviendo
hacia atrás cada una de las llamadas. La recursividad ofrece soluciones más simples
a costa de una mayor utilización de memoria.

Refactorización: Proceso, posterior a la codificación, en el que se realizan cambios al


algoritmo, no para depurar, sino para mejorar la eficiencia o legibilidad del código.

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”.

Resultado: Producto de la aplicación de un método de razonamiento a la solución de un


problema.

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.

Seudocódigo: Escritura de un algoritmo utilizando un lenguaje natural simplificado y


algunas convenciones arbitrarias para su fácil interpretación.

Sintaxis: El conjunto de reglas a seguir para escribir correctamente el código en un


lenguaje de programación.

Sobrecarga: En POO, es un procedimiento mediante el cual se implementa el


polimorfismo. Es la capacidad que tienen los objetos de una clase de responder al
mismo mensaje o evento de manera diferente en función de los parámetros utilizados
al invocarlo.

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.

Solución algorítmica: Procedimiento que soluciona un problema, descrito utilizando las


estructuras de programación. La solución algorítmica puede encontrarse en forma de
seudocódigo, diagrama de flujo o en un lenguaje computacional.

Sprite: Técnica que surge del desarrollo de videojuegos y consiste en la utilización de


un solo archivo con varias imágenes organizadas en cuadricula (normalmente del
mismo tamaño). El programa puede hacer uso de las imágenes individuales.

Subíndice: variable o conjunto de variables, utilizada para referenciar un elemento de


un vector o una matriz.

Subrutina: Modado de código, separado del programa principal y que puede ser
invocado desde este o desde otro modulo.

Suscriptor: En el patrón de diseño “observador”, es cada una de las entidades


notificadas por un “proveedor”.

Tiempo de diseño: En los entornos de desarrollo integrados (IDE), cuando se crean y


configuran las propiedades de objetos o controles con la ayuda de asistentes graficos.
El programador no escribe código, solo modifica valores y es el asistente el que genera

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.

Tiempo de ejecución: En los entornos de desarrollo integrados (IDE), cuando el


programador escribe el código para la creación de objetos o controles, incluyendo sus
peropiedades.

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.

UML: Acrónimo de Unified Modeling Language (Lenguaje Unificado de Modelado). Es


una técnica para la especificación de sistemas utilizando gráficos. Es el lenguaje de
modelado más utilizado en la actualidad.

URL: Acrónimo de Uniform Resource Locator (Localizador Uniforme de Recursos). Es la


“dirección” que permite a un navegador encontrar un objeto, está compuesto por una
cadena de texto que identifica cada recurso disponible en la web.

Uso: En POO, es un tipo de relación entre objetos, que se establece momentáneamente


y sin mayores dependencias, para utilizar funcionalidades del objeto utilizado

Variable: Espacio de memoria que un programa utiliza para almacenar valores de un


tipo de datos determinado. La variable puede ser utilizada de manera dinámica y su
contenido puede ser leído o modificado.

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.

Vector: En programación, es un tipo de matriz unidimensional.

Visibilidad: En POO, es el ámbito en el que un elemento puede ser accesado.

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.

Joyanes, Luis, Zahonero Ignacio, (2011). Programación en java 6. Algoritmos, Programación


orientada a objetos e Interfaz gráfica de usuario. Editorial McGrawHill.

Méndez Girón, Alejandra, (2013). Diseño de algoritmos y su programación en C. Editorial:


Alfaomega.

Trejos, Omar Iván (1999). La Esencia de la Lógica de Programación – Básico. Editorial Papiro.

Ziviami, Nivio, (2007). Diseño de algoritmos con implementaciones en pascal y C. Thomson.

373

También podría gustarte