3.5.- La recursividad Se refiere a procedimientos o funciones que se llaman asi mismos, en realidad viene a ser lo mismo que llamar a otro procedimiento, la diferencia consiste en que debe haber una sentencia que haga que el bucle finalice o de lo contrario con cada llamada creceran las variables en la pila (lugar donde se guarda la direccion de retorno de las subrutinas y tambien de las variables locales) lo que provocara pronto el colapso en el sistema, en este caso solo quedara aplicarle reset a la computadora. Recuerda nuestro programa para calcular el numero de fibonacci? bueno ahora vamos a hacerlo recursivo. PROGRAM FIBONACCI_RECURSIVO; VAR DATO : INTEGER; CONTADOR : LONGINT; FUNCTION FIBONACCI (NUM : INTEGER) : REAL; BEGIN INC (CONTADOR); WRITELN ('LLAMADA A LA FUNCION NUMERO ',CONTADOR); IF NUM>1 THEN FIBONACCI:=FIBONACCI(NUM-1)+FIBONACCI(NUM-2); IF NUM = 1 THEN FIBONACCI := 1; IF NUM = 0 THEN FIBONACCI := 0; END; BEGIN WRITELN('INGRESE EL VALOR PARA BUSCAR EL FIBONACCI'); READLN (DATO); WRITELN('SU FIBONACCI ES',FIBONACCI (DATO)); END. Si corre el programa no debera ponerle un numero mayor de 25, de hacerlo el programa demoraria mucho y de poner un numero excesivo (no crea que sera muy grande) como por ejemplo de 35 provocara tantas llamadas recursivas que se llenara la pila y colapsara el sistema, la insercion de la variable contador fue precisamente para ver que el lector vea cuantas llamadas se necesitan y tambien para que se de cuenta de lo lento que ocurre el proceso debido a que los calculos los repite una y otra vez. Es por esto que la solucion recursiva solo se justifica cuando es decididamente superior a la no recursiva por conceptos de claridad, facilidad y documentacion, aunque tambien puede ser rapida si no se ejecutan pasos innecesarios como en el algoritmo de quicksort que se incluye en el pascal 6.0. Ademas debido al proceso de recuperacion de la pila que es "Primero en entrar, ultimo en salir" ayuda a escribir rutinas que deban ejecutarse en forma inversa a la de su llamada. Por ejemplo, un programa que lee una caracteres y los devuelve invertidos: PROGRAM RECURSIVO; USES CRT; PROCEDURE LEER; VAR NUMERO : CHAR; BEGIN NUMERO := READKEY; WRITE (NUMERO); IF NUMERO <> #13 THEN LEER ELSE WRITELN; WRITE (NUMERO); { SOLO SE EJECUTA DESDE AQUI CADA VEZ QUE } END; { DEBE VOLVER DEL PROCEDIMIENTO. } BEGIN CLRSCR; WRITELN ('ESCRIBA NUMEROS SEGUIDOS, ENTER PARA TERMINAR'); LEER; END. Fijese en la llamada, primero se pregunta un numero (lo almacena en la pila, junto con el sitio a donde tendra que seguir luego de terminar en este caso la sentencia write) y examina si termino, si no es asi entonces vuelve a preguntar, hasta que la condicion de finalizacion se cumple, en este momento se ejecuta el write que imprime solo un caracter de los almacenados, el ultimo, (se borra este ultimo de la pila y retorna a write otra vez) luego, vuelve a ejecutarse y asi hasta terminar con todos los almacenados produciendo una salida inversa a la entrada. Veamos ahora el convertidor a hexadecimal como rutina recursiva: PROGRAM HEXADECIMAL; VAR NUM : LONGINT; PROCEDURE HEXA (NUM : LONGINT); VAR NIB : BYTE; BEGIN IF NUM > 0 THEN { CONTINUA MIENTRAS HAYA NUMERO } BEGIN NIB := (NUM AND $000F); { ALMACENA EL NIBBLE } NUM := (NUM SHR 4); { PREPARA NUM PARA SIGUIENTE } IF NUM > 0 THEN HEXA (NUM); { LLAMADA RECURSIVA } END; IF NIB > 9 THEN WRITE (CHR (55 + NIB)) ELSE WRITE (NIB); END; { SI ES MAYOR QUE 9 LO CONVIERTE EN LETRA } BEGIN WRITELN ('INGRESE UN NUMERO EN BASE DECIMAL'); READLN (NUM); HEXA (NUM); END. El programa fue explicado por lo que no es necesario ir mas alla en este ejemplo. Otro uso de la pila consiste en la exploracion de posibilidades para obtener la solucion de un determinado problema, como algo generico voy a mostrar un programa que logra salir de un laberinto utilizando metodos recursivos. (por ahora no se preocupe por las instruccions que generan graficas, ellas son necesarias para hacer el programa mas atractivo). Observe que los muros del laberinto actuan como limitantes, asi se podria aplicar el metodo en un juego por ejemplo, siendo el delimitador las reglas de dicho juego (claro que una busqueda de este tipo en un juego de complejidad moderarada lleva muchisimo tiempo, a este tipo de busqueda se le llama de profundidad ante todo y consiste un investigar todas las ramas de una posibilidad antes de considerar siquiera alguna otra posibilidad). PROGRAM PILAS; USES CRT,GRAPH; CONST LABERINTO : ARRAY [1..20,1..28] OF BYTE = ((1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1), (0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1), (1,1,0,1,0,1,0,0,0,1,1,0,1,0,1,0,1,1,1,1,1,1,0,0,0,0,0,1), (1,1,0,1,0,1,0,1,0,0,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,1,0,1), (1,0,0,0,0,1,0,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1), (1,0,1,1,1,1,0,1,0,1,1,0,1,1,1,0,0,0,1,1,0,1,1,0,0,1,0,1), (1,0,1,1,0,0,0,1,0,0,0,0,1,0,1,1,1,0,0,0,0,1,1,1,0,0,0,1), (1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,0,1,1,1,0,1), (1,1,1,0,1,1,1,0,0,1,1,0,1,0,1,0,0,0,0,1,1,1,0,1,1,1,0,1), (1,1,1,0,1,1,1,0,1,1,1,0,1,0,0,1,1,1,0,1,1,1,0,1,1,0,0,1), (1,0,0,0,0,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1), (1,0,1,0,1,0,1,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1), (1,0,1,0,1,0,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1), (1,0,1,0,1,0,0,0,0,1,0,1,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,1), (1,0,1,0,1,0,1,1,0,1,0,1,1,1,0,1,1,0,0,1,0,0,0,1,0,0,0,1), (1,0,1,0,1,0,0,1,0,1,0,0,1,0,0,1,1,1,0,1,1,1,0,0,0,1,0,1), (1,0,0,0,1,1,0,0,0,1,1,0,1,0,1,1,0,0,0,1,0,1,1,0,1,1,1,1), (1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,0,0,0,0,1,0,1,1,1,1), (1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,0), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)); VAR G,M,LONGITUD,DOBLE,C1, CENTRO,RADIO,RETARDO : INTEGER; IMAGEN : POINTER; FIN : BOOLEAN; PROCEDURE DIBUJA_LABERINTO; VAR { EL DIBUJO DEL LABERINTO } A,B,X1,Y1,X2,Y2 : INTEGER; { ES INDEPENDIENTE DEL SISTEMA } { GRAFICO DEBIDO A QUE SE } BEGIN { UTILIZA LA VARIABLE LONGITUD } FOR A := 1 TO 20 DO { PARA DIBUJAR LA CANTIDAD DE } FOR B := 1 TO 28 DO { PIXELS NECESARIOS. } BEGIN X1 := LONGITUD + ((B-1) * LONGITUD); X2 := X1 + LONGITUD; Y1 := LONGITUD + ((A-1) * LONGITUD); Y2 := Y1 + LONGITUD; IF LABERINTO [A,B]=1 THEN SETFILLSTYLE (INTERLEAVEFILL,YELLOW) ELSE SETFILLSTYLE (SOLIDFILL,C1); BAR (X1,Y1,X2,Y2) END; END; PROCEDURE MUEVE_PELOTA (X,Y: INTEGER; DIR : BYTE); VAR A,X1,Y1 : INTEGER; BEGIN { OTRA VEZ EL MOVIMIENTO ES } X1 := Y * LONGITUD; { CONTROLADO POR LA VARIABLE } Y1 := X * LONGITUD; { LONGITUD POR LO CUAL ES } FOR A := 1 TO LONGITUD DO { INDEPENDIENTE DEL SISTEMA } BEGIN { GRAFICO. } CASE DIR OF 0: PUTIMAGE (X1-LONGITUD+A,Y1,IMAGEN^,COPYPUT); 1: PUTIMAGE (X1,Y1-LONGITUD+A,IMAGEN^,COPYPUT); 2: PUTIMAGE (X1+LONGITUD-A,Y1,IMAGEN^,COPYPUT); 3: PUTIMAGE (X1,Y1+LONGITUD-A,IMAGEN^,COPYPUT); END; DELAY (RETARDO); { EL RETARDO TAMBIEN ES VARIABLE } END; END; PROCEDURE REALIZA_MOVIMIENTO (X,Y : INTEGER; DIR : BYTE); BEGIN (* PARA EJECUTAR ESTO LA POSICION SIGUIENTE DEBE ESTAR LIBRE,*) (* LA DIRECCION ANTERIOR DEBE SER LA CONTRARIA Y ADEMAS NO *) (* DEBE HABER TERMINADO TODAVIA. *) (* PARA INDICAR SITIO PASADO SE USA EL NUMERO 2 EN LA MATRIZ *) IF (LABERINTO [X,Y+1] = 0) AND (DIR <> 2) AND (FIN=FALSE) THEN BEGIN MUEVE_PELOTA (X,Y+1,0); { MOVIMIENTO HACIA LA DERECHA } LABERINTO [X,Y+1] := 2; { DIR = 0 } REALIZA_MOVIMIENTO (X,Y+1,0); END; (* LLEGADO AQUI YA NO PUEDE AVANZAR HACIA LA DERECHA POR LO *) (* CUAL INTENTAMOS HACIA ABAJO *) IF (LABERINTO [X+1,Y] = 0) AND (DIR <> 3) AND (FIN=FALSE) THEN BEGIN MUEVE_PELOTA (X+1,Y,1); { MOVIMIENTO HACIA ABAJO } LABERINTO [X+1,Y] := 2; REALIZA_MOVIMIENTO (X+1,Y,1); END; (* LLEGADO AQUI NO PODEMOS AVANZAR NI POR LA DERECHA NI PARA *) (* ABAJO, AHORA INTENTAMOS HACIA ARRIBA *) IF (LABERINTO [X-1,Y] = 0) AND (DIR <> 1) AND (FIN=FALSE) THEN BEGIN MUEVE_PELOTA (X-1,Y,3); { MOVIMIENTO HACIA ARRIBA } LABERINTO [X-1,Y] := 2; REALIZA_MOVIMIENTO (X-1,Y,3); END; (* EN ESTA PUNTO HEMOS FRACASADO INTENTANDO HACIA DERECHA, *) (* ABAJO Y ARRIBA NOS QUEDA SOLO HACIA LA IZQUIERDA *) IF (LABERINTO [X,Y-1] = 0) AND (DIR <> 0) AND (FIN=FALSE) THEN BEGIN MUEVE_PELOTA (X,Y-1,2); { MOVIMIENTO HACIA LA IZQUIERDA } LABERINTO [X,Y-1] := 2; REALIZA_MOVIMIENTO (X,Y-1,2); END; (* HEMOS FRACASADO EN AVANZAR, QUIERE DECIR QUE CHOCAMOS, *) (* ENTONCES SACAMOS EL ULTIMO PASO DE LA PILA, (PASCAL LO *) (* HACE AUTOMATICO) PERO ADEMAS MOVEMOS LA PELOTA ATRAS *) IF ((X = 19) AND (Y = 28)) THEN FIN := TRUE; { IF FIN = FALSE THEN BEGIN } {FIN = FALSE CON COMENTARIO PARA QUE REGRESE} CASE DIR OF 0 : MUEVE_PELOTA (X,Y-1,2); 1 : MUEVE_PELOTA (X-1,Y,3); 2 : MUEVE_PELOTA (X,Y+1,0); 3 : MUEVE_PELOTA (X+1,Y,1); END; { END; } { ESTA PORCION ESCRIBE GRIS POR DONDE } IF FIN = FALSE THEN { NO SE DEBE PASAR. } BEGIN SETFILLSTYLE (SOLIDFILL,LIGHTGRAY); BAR (Y*LONGITUD,X*LONGITUD,LONGITUD*(Y+1),LONGITUD*(X+1)); END; END; BEGIN G := DETECT; DETECTGRAPH (G,M); INITGRAPH (G,M,'\BP\BGI'); CASE G OF CGA : BEGIN LONGITUD:= 8; RETARDO:=0; C1:=GREEN; ;END; EGA : BEGIN LONGITUD:= 16; RETARDO:=3; C1:=RED; ;END; VGA : BEGIN LONGITUD:= 22; RETARDO:=0; C1:=RED; ;END; ELSE LONGITUD := 16; END; DOBLE := LONGITUD * 2; CENTRO := LONGITUD + LONGITUD DIV 2; RADIO := LONGITUD DIV 2 - LONGITUD DIV 6; SETFILLSTYLE (SOLIDFILL,BLUE); FILLELLIPSE (CENTRO,CENTRO,RADIO,RADIO); GETMEM (IMAGEN,IMAGESIZE (LONGITUD,LONGITUD,DOBLE,DOBLE)); GETIMAGE (LONGITUD,LONGITUD,DOBLE,DOBLE,IMAGEN^); OUTTEXTXY (13 * LONGITUD,0,'LABERINTO'); FIN := FALSE; DIBUJA_LABERINTO; REALIZA_MOVIMIENTO (2,1,0); FREEMEM (IMAGEN,IMAGESIZE (LONGITUD,LONGITUD,DOBLE,DOBLE)); READLN; CLOSEGRAPH; END. El proceso para salir del laberinto es el mismo que cualquier ser humano realizaria en una condicion similar, veamoslo. Una persona comenzaria a caminar en una determinada direccion, con la prevision de marcar aquellos sitios por donde ya paso (para no pasar por los mismos lugares) por ejemplo con tiza o con migajas de pan (en esto ultimo hay el peligro que se lo coman los pajaros) y tambien debe recordar la direccion actual sino podria avanzar y retroceder en el mismo lugar y no llegaria a ninguna parte. El problema surje cuando la persona se encuentra con que no puede avanzar en ninguna direccion aparte de la cual vino entonces dice; "Si el laberinto tiene solucion entonces retrocediendo hasta donde este la ultima bifurcacion y tomo otra direccion y repito este proceso entonces llegare a una salida de todas maneras." Este proceso deductivo es el que genera el concepto de retroalimentacion, y es vital en inteligencia artificial. La tecnica de avanzar ciegamente y retroceder cuando algo anda mal se usa en muchos programas que logran tomar decisiones de diversa indole, generalmente el problema no consiste en retroceder sino saber cuando es que algo anda mal, en nuestro caso esto no es problema puesto que nos hallamos en evidentes aprietos cuando ya no podemos seguir en la misma direccion por donde ibamos. Ademas las llamadas recursivas almacenan las variables en la pila de manera automatica asi cuando llegamos a la salida tendremos un camino de regreso en la pila aunque no necesariamente el mas corto (para hallar esto seria necesario investigar todas las salidas posibles y contar cuanto nos demoramos, lo cual seria un poco mas complejo, aunque no mucho). Debe notar aqui la forma de como se realizan procedimientos recursivos. Debe pensar en la ultima accion que se efectuara antes de solucionar el problema, entonces debe colocar un if para que salga si se termino, sino que llame recursivamente mientras todavia no se haya producido esto. Las llamadas recursivas haran el resto del trabajo repetitivo sin esfuerzo. Por ultimo usted no tiene que saber nada acerca de graficos para entender el programa anterior. El unico que realiza todo el trabajo es el procedimiento realiza_movimiento este hace todos los calculos y llamadas recursivas, esta alli el misterio del funcionamiento del programa y no en otro lugar. Por ultimo para reforzar todos los conocimientos adquiridos hasta ahora vamos a hacer un programa que use todo lo aprendido hasta ahora se trata de un juego en el cual hay que intentar adivinar el codigo pensado por el otro. El juego se desarrolla asi: El jugador y la maquina, ambos piensan un numero estos numeros deben estar todos comprendidos entre 0..9 y ademas todos deben ser diferentes. Suponga que estos sean: Numero del jugador Numero de la maquina 4 5 3 6 8 9 2 7 Una vez hecho esto la maquina espera que le adivine el numero que ella genero (se supone que usted no lo sabe) suponga que usted cree que es el 4 8 2 9 y lo escribe, entonces usted recibira como respuesta un simbolo (+) por cada numero que este en su posicion correcto y un (@) por cada numero que existe pero en un lugar que no le corresponde. Veamos el ejemplo: 4 8 2 9 @@+ Lo cual significa que solo uno de los numeros existe y se encuentra en su posicion correcta (se refiere al 2) mientras que 2 numeros existen pero deben colocarse en diferente posicion (se refieren al 8 y al 9) con esta informacion usted tendra que vencer a la maquina. El codigo actual del programa es teoricamente perfecto pero es demasiado predecible como para enfrentarse contra un humano y por ello usted deberia ganarle mas cantidad de juegos, como ejercicio al lector le dejo el programa para que lo haga casi invencible, (una idea es que en vez de mostrar la primera posibilidad real que se encuentre primero hacer que se generen todas las posibilidades guardarlas en una tabla y luego mostrar una de ellas de forma aleatoria, vera como la fuerza de su juego aumenta). He aqui el listado del programa : PROGRAM ADIVINA_NUMEROS; USES CRT; CONST NUMEROS = 4; TIRADAS = 10; TYPE VECTOR1 = ARRAY [1..NUMEROS] OF INTEGER; VECTOR2 = ARRAY [1..TIRADAS] OF INTEGER; MEMORIA = ARRAY [1..TIRADAS,1..NUMEROS] OF INTEGER; VAR NUMERO_MAQUINA,NUMERO_HUMANO,NUM_GEN : VECTOR1; CORRECTOS,EXISTEN : VECTOR2; HISTORIA : MEMORIA; COR1,EXISTEN1,A,B : INTEGER; TRAMPOSO,POSIBLE,GANA_HUMANO,GANA_MAQUINA,EMPATE : BOOLEAN; NOMBRE : STRING [15]; CAR : CHAR; PROCEDURE DIBUJA; VAR A : INTEGER; BEGIN CLRSCR; GOTOXY (23,1);TEXTCOLOR (WHITE); WRITE('JUEGO DE NUMEROS'); TEXTCOLOR (LIGHTGREEN);TEXTBACKGROUND (BLACK); GOTOXY (3,4) ; WRITE ('JUGADOR : ',NOMBRE); GOTOXY (32,4); WRITE ('MAQUINA'); TEXTCOLOR (YELLOW); GOTOXY (1,17); FOR A := 1 TO 60 DO WRITE (CHR (205)); GOTOXY (1,5); FOR A := 1 TO 60 DO WRITE (CHR (205)); GOTOXY (1,3); FOR A := 1 TO 60 DO WRITE (CHR (205)); FOR A := 3 TO 17 DO BEGIN GOTOXY (1,A); WRITE (CHR(186)); GOTOXY (30,A); WRITE (CHR(186)); GOTOXY (60,A); WRITELN (CHR (186)); END; GOTOXY (30,3);WRITE (CHR(203));GOTOXY (30,17);WRITE (CHR(202)); GOTOXY (1,3) ;WRITE (CHR(201));GOTOXY (60,3) ;WRITE (CHR(187)); GOTOXY (1,17);WRITE (CHR(200));GOTOXY (60,17);WRITE (CHR(188)); GOTOXY (1,5) ;WRITE (CHR(204));GOTOXY (30,5) ;WRITE (CHR(206)); GOTOXY (60,5);WRITE (CHR(185)); TEXTCOLOR (LIGHTGRAY); FOR A := 3 TO 11 DO BEGIN GOTOXY (3,A+4) ; WRITE (A-2,'.- '); GOTOXY (32,A+4); WRITE (A-2,'.- '); END; END; PROCEDURE GENERA (VAR VECTOR : VECTOR1); VAR A,B : INTEGER; VALIDO : BOOLEAN; BEGIN REPEAT VALIDO := TRUE; FOR A := 1 TO NUMEROS DO VECTOR [A] := RANDOM (10); FOR A := 1 TO NUMEROS DO FOR B := 1 TO NUMEROS DO IF B > A THEN IF VECTOR [A] = VECTOR [B] THEN VALIDO := FALSE; UNTIL VALIDO; END; PROCEDURE PREGUNTA_Y_RESPONDE; VAR C,B,D : INTEGER; E : CHAR; BEGIN GOTOXY (22,20); WRITE ('ES EL TURNO DE ',NOMBRE); GOTOXY (7,6+A); FOR B := 1 TO 4 DO BEGIN REPEAT E := READKEY UNTIL (E IN ['0'..'9']); WRITE (E); VAL (E,D,C); NUMERO_HUMANO [B] := D; END; WRITE (' '); D := 0; FOR C := 1 TO NUMEROS DO IF NUMERO_MAQUINA [C] = NUMERO_HUMANO [C] THEN BEGIN WRITE ('+'); INC (D); END; IF D = 4 THEN GANA_HUMANO := TRUE; FOR C := 1 TO NUMEROS DO FOR B := 1 TO NUMEROS DO IF B <> C THEN IF NUMERO_MAQUINA [C] = NUMERO_HUMANO [B] THEN WRITE ('@'); END; PROCEDURE GENERA_POSIBILIDAD (VAR TRAMPA : BOOLEAN); VAR VALIDO : BOOLEAN; C,B : INTEGER; BEGIN REPEAT INC (NUM_GEN [4]); IF (NUM_GEN [4] = 10) THEN BEGIN NUM_GEN [4] := 0; INC (NUM_GEN [3]); IF (NUM_GEN [3] = 10) THEN BEGIN NUM_GEN [3] := 0; INC (NUM_GEN [2]); IF (NUM_GEN [2] = 10) THEN BEGIN NUM_GEN [2] := 0; INC (NUM_GEN [1]); IF NUM_GEN [1] = 10 THEN TRAMPA := TRUE; END; END; END; GOTOXY (36,6+A); FOR B := 1 TO 4 DO WRITE (NUM_GEN [B]); VALIDO := TRUE; FOR C := 1 TO NUMEROS DO FOR B := 1 TO NUMEROS DO IF B > C THEN IF NUM_GEN [C] = NUM_GEN [B] THEN VALIDO := FALSE; UNTIL VALIDO; END; PROCEDURE SEA_POSIBLE (LUGAR : INTEGER); VAR A,B,C,COR1,EXISTEN1 : INTEGER; BEGIN POSIBLE := TRUE; A := LUGAR-1; WHILE (A >= 1) DO BEGIN COR1 := 0; EXISTEN1 := 0; FOR B := 1 TO NUMEROS DO IF NUM_GEN [B] = HISTORIA [A,B] THEN INC (COR1); FOR B := 1 TO NUMEROS DO FOR C := 1 TO NUMEROS DO IF C <> B THEN IF NUM_GEN [B] = HISTORIA [A,C] THEN INC (EXISTEN1); IF (CORRECTOS [A] <> COR1) OR (EXISTEN [A] <> EXISTEN1) THEN POSIBLE := FALSE; A := A - 1; END; END; BEGIN GANA_MAQUINA := FALSE; GANA_HUMANO := FALSE; EMPATE := FALSE; RANDOMIZE; CLRSCR; WRITE ('CUAL ES TU NOMBRE : ');READLN (NOMBRE); DIBUJA; TRAMPOSO := FALSE; GENERA (NUMERO_MAQUINA); A := 0; REPEAT INC (A); PREGUNTA_Y_RESPONDE; FOR B := 1 TO NUMEROS DO NUM_GEN [B] := 0; GOTOXY (22,20); WRITE ('AHORA ME TOCA A MI '); REPEAT GENERA_POSIBILIDAD (TRAMPOSO); SEA_POSIBLE (A); UNTIL POSIBLE OR TRAMPOSO; IF TRAMPOSO THEN BEGIN GOTOXY (20,20); WRITE ('AQUI VIENE TU MENSAJE'); HALT (1); {EL MENSAJE COMPLETO SALE EN EL FICHERO BATCH} END ELSE BEGIN GOTOXY (42,6+A); B := 0; EXISTEN1 := 0; COR1 := 0; REPEAT REPEAT CAR := READKEY UNTIL (CAR IN ['@','+',#13]); INC (B); CASE CAR OF '@' : BEGIN INC (EXISTEN1); WRITE ('@') END; '+' : BEGIN INC (COR1); WRITE ('+'); END; #13 : ; END; UNTIL (CAR = #13) OR (B >= 4); IF COR1 = 4 THEN GANA_MAQUINA := TRUE; IF GANA_MAQUINA AND GANA_HUMANO THEN EMPATE := TRUE; CORRECTOS [A] := COR1; EXISTEN [A] := EXISTEN1; FOR B := 1 TO NUMEROS DO HISTORIA [A,B] := NUM_GEN [B]; END; UNTIL GANA_MAQUINA OR GANA_HUMANO OR EMPATE; GOTOXY (16,20); IF EMPATE THEN WRITE ('ESTO ES UN EMPATE...BUEN JUEGO ',NOMBRE,' !!') ELSE IF GANA_HUMANO THEN WRITE (' ME VENCISTE...BRAVO ',NOMBRE,' !!') ELSE WRITE (' FUISTE UN BUEN RIVAL...',NOMBRE); END. Tambien damos el listado del fichero numeros.bat que informa si hubo trampas, este imprime tramposo cuando se ejecuta halt(1) (que pasara a errorlevel el numero del parametro) @NUMEROS @IF ERRORLEVEL 1 GOTO MENSAJE @GOTO SALIR :MENSAJE @ECHO ON @CLS @ECHO ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» @ECHO º ***** *** *** * * *** *** *** *** º @ECHO º ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! º @ECHO º ! !*** !***! ! * ! !*** ! ! **** ! ! º @ECHO º ! ! * ! ! ! ! ! ! ! ! ! ! º @ECHO º ! ! * ! ! ! ! ! *** *** *** º @ECHO ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ :SALIR En realidad este programa tiene que ver con el concepto de inteligencia artificial de feedback (retroalimentacion). Para explicar este problema veamos como resuelve el ser humano si sus hipotesis son ciertas. Vamos a imaginarnos que usted regresa del trabajo y al llegar a su casa, no encuentra a su seniora entonces empieza generando su primera hipotesis: 1 : Mi esposa salio de viaje. Lo cual parece razonable hasta que se acuerda que no tiene dinero para viajar. Asi lanza una segunda hipotesis: 2 : Mi seniora se fue a visitar a unas amigas. Y esta hipotesis se desploma al encontrar una carta donde su mujer dice que lo abandona. Asi lanza otra hipotesis : 3 : Mi mujer me abandono. Y luego constrasta esta hipotesis con la realidad y le da la misma respuesta, es decir cuando la esposa lo abandona a uno, ella no esta presente y tambien cuando la esposa lo abandona a uno suele dejarle una carta de despedida tras lo cual debe aceptar como cierta la ultima hipotesis (aunque no sea necesariamente cierta puesto que puede ser una broma, pero esto todavia no se le ocurrio al marido). La explicacion que acabamos de hacer es muy elegante para describir como el programa se las arregla para generar los numeros. En primer lugar el programa empieza con numero_generado = (0,0,0,0), que es una hipotesis, luego compara el primer numero con el segundo, tercero y cuarto, luego el segundo con tercero y cuarto y por ultimo el tercero con el cuarto al ver que dos numeros son iguales se incrementa el ultimo asi hasta llegar hasta el (0,1,2,3) que viene a ser la primera hipotesis verdaderamente posible luego contrasta con la realidad (no la hay puesto que es el primer tiro) asi que la toma por verdadera y la muestra. Suponga que se jugaron algunas tiradas: JUEGO DE NUMEROS ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º JUGADOR : andres º MAQUINA º ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͹ º º º º 1.- 1683 + º 1.- 0123 @ º º 2.- 2590 @@ º 2.- 1456 @@+ º º 3.- 7253 +@@ º 3.- 1547 @+ º º 4.- 7285 @@ º 4.- 1684 @@ º º 5.- 4905 @@ º 5.- 4506 +++ º º 6.- 5423 ++++ º 6.- 4536 ++++ º º 7.- º 7.- º º 8.- º 8.- º º 9.- º 9.- º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Esto es un empate...buen juego andres !! Suponga que la computadora esta pensando su sexta tirada, cuando genera (0,0,0,0) vimos que incrementa hasta (0,1,2,3) ahora este numero es la hipotesis y vamos a constrastarla con la realidad, primero nos preguntamos ¨Que respuesta hubiera generado el numero si fuera posible? La respuesta es sencilla: el mismo resultado que genero el numero. Asi comparamos (0,1,2,3) con el primer numero generado (0,1,2,3) y nos resulta (++++) como esto es diferente al @ que nos dio el usuario, entonces nuestra hipotesis es falsa, luego incrementamos el numero y seguimos comprobando infructuosamente hasta llegar al (1,4,5,6), este al compararlo con la primera posibilidad (0,1,2,3) nos da (@) que es lo mismo que respondio el usuario con lo cual debemos seguir comprobando con el que sigue que es (1,4,5,6) y de hecho al ser igual que el numero que estamos generando nos dara (++++) lo cual no fue cuando le preguntamos al usuario, asi seguimos incrementado hasta el (4,5,3,6) comparando con el primero nos da (@) con el segundo (@@+) con el tercero (@+) y asi iguales con todos hasta comparar con el anterior (4,5,0,6) y nos dara +++ al haber sacado resultados iguales en todas las comparaciones quiere decir que el numero es posible, de no encontrar un numero posible hasta llegar al 9999 se ejecuta halt (1) que hace que el fichero batch escriba un mensaje informando que se intento hacer trampas. .