LISTADO DEL CÓDIGO FUENTE:
Descargar ejecutable
;---------------------------------------------------------------------------- ; Puzle del 15 (juego para MS-DOS) / Version 1.1 (1367 lineas) ; Ampliacion y mejora de practica final de AETC (UOC) / 0809-2 (v1.0) ; ; Programado por: DJMM ; ; Primera version: 25 de julio de 2009 (Santiago Apostol) ; Ultima revision: 21 de agosto de 2009 (San Pio X) ; Otras versiones: 01/08/2009-02/08/2009 ; ; 15.ASM (15.OBJ-15.EXE) / Assembler x86 / TASM 4.1+TLINK 7.1.30.1 ;---------------------------------------------------------------------------- .286 ;Generar codigo del 80286 .model small ;Modelo de memoria small .stack 2048 ;Reservamos 2048 bytes de pila title "EL 15" ;Titulo del programa para el Assembler subttl "PUZLE" ;Subtitulo ;---------------------------------------------------------------------------- ; DEFINICION DE CONSTANTES ;---------------------------------------------------------------------------- DOS equ 21h ;Servicios del DOS BIOS equ 10h ;Servicios del BIOS TPS equ 18 ;Ticks por segundo que genera el temporizador CLK equ 08h*4 ;Vector de la interrupcion de reloj ;---------------------------------------------------------------------------- ; SEGMENTO DE DATOS ;---------------------------------------------------------------------------- .data ;Tablero de juego que se muestra al inicio del programa: posicion [05,34] Board db " EL 15 " db 218,196,194,196,194,196,194,196,191 db 179,32,179,32,179,32,179,32,179 ;Primera fila db 195,196,197,196,197,196,197,196,180 db 179,32,179,32,179,32,179,32,179 ;Segunda fila db 195,196,197,196,197,196,197,196,180 db 179,32,179,32,179,32,179,32,179 ;Tercera fila db 195,196,197,196,197,196,197,196,180 db 179,32,179,32,179,32,179,32,179 ;Cuarta fila db 192,196,193,196,193,196,193,196,217 db " 00 mov. " ;Movimientos: [15,35] db " 00 seg. " ;Segundos: [16,35] ;Posiciones de las fichas: ;[07,35],[07,37],[07,39],[07,41] (primera fila) ;[09,35],[09,37],[09,39],[09,41] (segunda fila) ;[11,35],[11,37],[11,39],[11,41] (tercera fila) ;[13,35],[13,37],[13,39],[13,41] (cuarta fila) ;Pieces es la matriz 4x4 donde tenemos las fichas del juego ;1234 ;5678 ;9A F -- En esta fila esta la casilla blanca (posicion 2,2 = 10 en vector) ;DECB Pieces db "123456789A FDECB" ;Clon es una matriz auxiliar para almacenar los numeros aleatorios en curso ;y tambien para salvar matriz Pieces antes de leerla de nuevo por teclado Clon db 16 dup(?) ;Cadena de caracteres usada para chequear una matriz leida por teclado Chars db "123456789abcdefABCDEF" ;Fail: matriz que genera un puzle imposible de resolver (failure=fracaso) Fail db "123456789ABCDFE " ;Switches para utilizar en la busqueda de un caracter en una cadena Sw db 0 ;Su valor indicara si un caracter tecleado es "legal" Inside db 0 ;Nos informa de si una ficha ya esta dentro de Pieces ;Cadenas de caracteres para el titulo y el logo de autoria del programa Titl db "PUZLE$" Logo db "djmm 09$" Clr db 16 dup(32),'$' Tmp db "Espera...",'$' ;Definimos las varibles que seran las semillas para los numeros aleatorios Seed1 dw ? Seed2 dw ? Seed3 dw ? Seed4 dw ? ;RndNum (Random Number): variable que almacenara un numero aleatorio RndNum db ? ;State es la variable para indicar el estado del juego, a saber: ;0: Hemos pulsado la tecla 'q' o 'Q' para salir del programa ;1: Continuamos jugando ;2: Posicion ganadora. Todas las fichas estan ordenadas, indicarlo y acabar ;3: Demasiadas o ninguna casilla en blanco, indicarlo y acabar ;4: Movimientos disponibles agotados ;5: Tiempo agotado, indicarlo y acabar State db 1 ;Key: variable para almacenar el codigo ASCII de la tecla leida Key db 0 ;Variable para guardar cada caracter leido por teclado para llenar Pieces Echo db ? ;Row y Col indicaran la posicion del cursor en la pantalla en todo momento Row db 0 ;Fila del Cursor en la pantalla Col db 0 ;Columna del Cursor en la pantalla ;Variables con los mensajes que apareceran por pantalla debajo del tablero Msg0 db "CANCELADO",10,13,'$' Msg2 db "PERFECTO!",10,13,'$' Msg3 db " TABLERO ",10,13,'$' Msg4 db " AGOTADO ",10,13,'$' Msg5 db " TIEMPO! ",10,13,'$' ;Variable para controlar los movimientos que podemos realizar Moves dw 80 ;Variables para el control del tiempo de juego Ticks db 0 ;Var. que indicara los ticks de reloj transcurridos Secs dw 1 ;Var. que usaremos para contar los segundos ;Variables para salvar la direccion actual de la ISR de reloj Sgm dw ? ;Direccion de segmento de la ISR de reloj original Ofs dw ? ;Desplazamiento dentro del segmento ;---------------------------------------------------------------------------- ; SEGMENTO DE CODIGO ;---------------------------------------------------------------------------- .code ;---------------------------------------------------------------------------- ; ClearScreen ==> Borrar pantalla ; ; Borramos la pantalla completamente escribiendo espacios en blanco ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- ClearScreen: pusha ;Salvamos todos los registros en la pila ;INICIALIZACION NECESARIA PARA POSICIONAR EL CURSOR mov bh,00h ;Pagina mov dh,00h ;Fila inicial mov dl,00h ;Columna inicial mov ah,02h ;Servicio para posicionar el cursor int BIOS ;LLENAMOS LA PANTALLA DE ESPACIOS EN BLANCO mov bh,00h ;Pagina mov bl,07h ;Atributo de color: fondo negro y color blanco mov cx,2000 ;80*25 es el numero de caracteres a escribir mov al,32 ;Caracter que utilizaremos (espacio en blanco) mov ah,09h ;Servicio para escribir caracter int BIOS popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; PrintBoard ==> Imprimir tablero ; ; Muestra el tablero de juego sin datos, es decir, imprime la matriz Board ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- PrintBoard: pusha ;Salvamos todos los registros en la pila mov dh,05 ;Fila inicial en pantalla donde mostraremos tablero mov dl,34 ;Col. inicial en pantalla donde mostraremos tablero ;INICIALIZAMOS REGISTROS mov bl,07h ;Atributo mov bh,00h ;Pagina mov cx,1 ;Escribimos 1 caracter cada vez mov si,0 ;Indice para acceder al tablero (ponemos a 0) PBLoop: mov ah,02h ;Servicio para posicionar el cursor (DH:fila, DL:col) int BIOS mov al,Board[si] ;Caracter a escribir (tomado de la matriz Board) mov ah,09h ;Servicio para escribir caracter int BIOS inc si ;Incrementamos el indice para acceder a los datos ;ACTUALIZAMOS LA POSICION DEL CURSOR (FILA Y COLUMNA) inc dl ;Incrementamos el numero de columna cmp dl,43 ;43: ultima columna donde tenemos que escribir jl PBLoop ;Si es menor que 43 iteramos mov dl,34 ;34: primera columna donde tenemos que escribir inc dh ;Incrementamos el numero de fila cmp dh,17 ;17: ultima fila donde tenemos que escribir jl PBloop ;si es menor que 17 iteramos mov dh,8 ;Colocamos el cursor en la posicion [8,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Titl ;Imprimimos el titulo del programa mov ah,09h int DOS mov dh,12 ;Colocamos el cursor en la posicion [12,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Logo ;Imprimimos el logo de autoria mov ah,09h ;Servicio del DOS para imprimir una cadena int DOS popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; WriteChar ==> Escribir caracter ; ; Escribe un caracter por pantalla, segun las siguientes especificaciones: ; AL: caracter que queremos escribir (codificado en ASCII) ; DX: posicion donde queremos escribir (DH: fila, DL: columna) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- WriteChar: pusha ;Salvamos todos los registros en la pila mov bh,00h ;Pagina mov ah,02h ;Servicio del BIOS para posicionar cursor int BIOS mov bl,07h ;Atributo de color: fondo negro y color blanco mov cx,1 ;Numero de caracteres a imprimir mov ah,09h ;Servicio del BIOS para imprimir caracter int BIOS popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; UpdateBoard ==> Actualizar tablero ; ; Actualiza contenido del tablero de juego con los datos de la matriz Pieces; ; a continuacion vuelve a posicionar el cursor en el tablero de juego ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- UpdateBoard: pusha ;Salvamos todos los registros en la pila ;INICIALIZAMOS REGISTROS mov bh,00h ;Pagina mov dh,07 ;Fila inicial mov dl,35 ;Columna inicial mov bl,07h ;Atributo mov cx,1 ;Numero de caracteres a escribir mov si,0 ;Indice para acceder a los datos UBLoop: ;Iniciamos el bucle para mostrar los datos mov ah,02h ;Servicio para posicionar el cursor int BIOS mov al,Pieces[si] ;Caracter mov ah,09h ;Servicio para escribir caracter int BIOS inc si ;Incrementamos el indice para acceder a los datos add dl,2 ;Incrementamos columna cmp dl,43 ;Miramos si hemos llegado a la ultima columna jl UBLoop mov dl,35 ;Si llegamos al final de la fila inicializamos col. add dh,2 ;Incrementamos fila cmp dh,15 ;Miramos si hemos llegado a la ultima fila jl UBLoop push Moves ;Mostramos los movimientos que nos quedan por hacer mov dh,15 ;Fila mov dl,35 ;Columna push dx call ShowDigits ;Mostramos los digitos add sp,4 call PosCursor ;Volvemos a posicionar cursor en el tablero de juego popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; PosCursor ==> Posicionar cursor ; ; Posiciona el cursor en la pantalla en base a las variables: Row y Col ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- PosCursor: push ax ;Guardamos en la pila los registros que vamos a usar push bx push dx ;POSICIONAMOS EL CURSOR mov dh,Row ;Fila mov dl,Col ;Columna mov bh,00h ;Pagina mov ah,02h ;Servicio para posicionar el cursor int BIOS pop dx ;Restauramos los registros utilizados pop bx pop ax ret ;---------------------------------------------------------------------------- ; ShowDigits ==> Mostrar digitos decimales en pantalla, pasados a ASCII ; ; - Convierte un valor decimal (entre 0 y 99) en dos caracteres ASCII; para ; ello se divide el valor entre 10, el cociente representara las decenas y ; el resto las unidades, despues se tienen que convertir a ASCII ; - Muestra los 2 digitos ASCII en la posicion de pantalla indicada; el valor ; y la posicion se pasan por la pila como parametros ; ; Parametros de entrada: Valor [BP+6] y posicion (fila,columna) [BP+4] ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- ShowDigits: push bp ;Hacemos enlace dinamico mov bp,sp push ax ;Guardamos en la pila los registros que vamos a usar push bx push cx push dx push di ;SACAMOS LOS PARAMETROS DE LA PILA mov dx,[bp+4] ;Ponemos en DH la fila y en DL la col. donde escribir mov ax,[bp+6] ;Ponemos en AL el valor que queremos mostrar mov ah,0 ;CALCULAMOS EL CODIGO ASCII DE UNIDADES Y DECENAS cmp ax,100 ;Nos aseguramos de que vamos a convertir dos cifras jl Convert mov ax,99 ;Si el numero fuera mayor que 99 lo convertimos en 99 Convert: mov bl,10 div bl ;El cociente estara en AL y el resto en AH add ah,48 ;Convertimos los valores a ASCII add al,48 mov di,ax ;Guardamos los caracteres en DI ;MOSTRAMOS DIGITOS mov bh,00h ;Posicionamos el cursor: pagina mov ah,02h ;Servicio para posicionar el cursor int BIOS ;MOSTRAMOS EL PRIMER CARACTER ASCII mov bl,07h ;Fijamos atributo de color: fondo negro, color blanco mov ax,di ;Cogemos el primer caracter mov bh,00h ;Pagina mov cx,1 mov ah,09h ;Servicio para escribir caracter int BIOS ;MOVEMOS UNA POSICION EL CURSOR add dl,1 ;Nos colocamos en la siguiente posicion mov bh,00h ;Pagina mov ah,02h ;Servicio para posicionar el cursor int BIOS ;MOSTRAMOS EL SEGUNDO CARACTER ASCII mov ax,di ;Recuperamos los caracteres de DI mov al,ah ;Cogemos el segundo caracter mov bh,00h ;Pagina mov cx,1 mov ah,09h ;Servicio para escribir caracter int BIOS pop di ;Restauramos los registros utilizados pop dx pop cx pop bx pop ax pop bp ;Deshacemos enlace dinamico (sacamos BP de la pila) ret ;---------------------------------------------------------------------------- ; StartingPoint ==> Buscar posicion inicial para empezar a jugar ; ; - Busca donde esta el espacio en blanco y verifica que solo haya uno en la ; matriz Pieces; si solo hay un espacio en blanco inicializa Row y Col asi: ; <> (SI: posicion donde ; hemos encontrado el espacio en blanco en la matriz) ; - Si hay mas de un espacio o no hay ninguno ponemos State=3 para terminar ; ; Parametros de entrada: Direccion de la matriz [BP+4] ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- StartingPoint: push bp ;Salvamos BP en la pila (hacemos enlace dinamico) mov bp,sp ;Hacemos que BP apunte a la cima de la pila push ax ;Guardamos en la pila los registros que vamos a usar push bx push cx push si ;INICIAMOS BUCLE PARA ENCONTRAR LA POSICION INICIAL mov si,0 ;Inicializamos SI mov cl,0 ;CL nos indicara el numero de espacios encontrados mov bx,[bp+4] ;Ponemos en BX la dir. de Pieces (tomada de la pila) SPLoop: mov ch,[bx+si] ;Tambien sirve aqui: 'mov ch,[bx][si]' cmp ch,32 ;Miramos si el valor de CH es un espacio (chr(32)) jne SPNext mov ax,si ;Guardamos la posicion donde encontramos el espacio inc cl ;Contamos los espacios en blanco SPNext: inc si ;Incrementamos el indice para acceder a los datos cmp si,16 jl SPLoop Error: ;PONEMOS EL CURSOR EN LA POSICION INICIAL cmp cl,1 je Calc ;Si no hay ningun espacio o hay mas de uno acabar mov State,3 jmp SPEnd Calc: ;CALCULAMOS POSICION ACTUAL EN PANTALLA mov bl,4 ;Row=[cociente(AX/4)]*2+7, Col=[resto(AX/4)]*2+35 div bl ;AL(cociente): fila, AH(resto): columna shl al,1 ;Row*2 shl ah,1 ;Col*2 add al,7 add ah,35 mov Row,al ;Si solo hay un espacio inicializamos Row y Col mov Col,ah SPEnd: pop si ;Restauramos los registros utilizados pop cx pop bx pop ax pop bx ret ;---------------------------------------------------------------------------- ; ReadKey ==> Leer una tecla usando el servicio 08h de las llamadas al DOS ; ; Segun la tecla leida llamaremos a la subrutina correspondiente: ; * ['i','j','k','l','I','J','K','L']: llamar a la subrutina MoveCursor ; * : llamar a la subrutina MovePiece ; * ['a','A']: llamar a la subrutina GenPieces (generar matriz aleatoria) ; * ['z','Z']: llamar a la subrutina ReadPieces (leer matriz por teclado) ; * : llamar a la subrutina LoadFail (cargar matriz de fracaso) ; * ['q','Q']: poner State=0 para salir, mostrando el mensaje oportuno ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- ReadKey: push ax ;Guardamos el valor de AX en la pila mov ah,0Bh ;Miramos si hay alguna tecla para leer int DOS cmp al,0 ;Si no hay ninguna, salimos del procedimiento je RKEnd mov ah,08h ;Leemos la tecla y validamos el caracter leido int DOS mov Key,al ;Guardamos el codigo ASCII de la tecla leida en Key cmp al,97 ;'a' minuscula: obtener puzle aleatorio je RKRandom cmp al,65 ;'A' mayuscula: obtener puzle aleatorio je RKRandom cmp al,122 ;'z' minuscula: introducir nuevo puzle por teclado je RKRead cmp al,90 ;'Z' mayuscula: introducir nuevo puzle por teclado je RKRead cmp al,24 ;Ctrl-X (chr(24)): cargar puzle imposible je RKLoadFail cmp al,105 ;'i' minuscula: movimiento de cursor je RKCursor cmp al,73 ;'I' mayuscula: mover cursor arriba je RKCursor cmp al,106 ;'j' minuscula: mover cursor izquierda je RKCursor cmp al,74 ;'J' mayuscula: mover cursor izquierda je RKCursor cmp al,107 ;'k' minuscula: mover cursor derecha je RKCursor cmp al,75 ;'K' mayuscula: mover cursor derecha je RKCursor cmp al,108 ;'l' minuscula: mover cursor abajo je RKCursor cmp al,76 ;'L' mayuscula: mover cursor abajo je RKCursor cmp al,32 ;Es un espacio (chr(32)): movimiento de ficha je RKPiece cmp al,113 ;'q' minuscula: salir del programa je RKExit cmp al,81 ;'Q' mayuscula: salir del programa je RKExit jmp RKEnd RKCursor: call MoveCursor ;Llamar al procedimiento que mueve el cursor jmp RKEnd RKPiece: call MovePiece ;Llamar al procedimiento que mueve ficha jmp RKEnd RKRandom: call GenPieces ;Llamar al procedimiento que genera matriz aleatoria jmp RKEnd RKRead: call ReadPieces ;Llamar al procedimiento que lee matriz por teclado jmp RKEnd RKLoadFail: call LoadFail ;Llamar al procedimiento que carga el puzle imposible jmp RKEnd RKExit: ;Esto es vital para el bucle principal del programa mov State,0 ;Ponemos State a 0 (hemos decidido salir del programa) RKEnd: pop ax ;Sacamos el valor de AX de la pila ret ;---------------------------------------------------------------------------- ; MoveCursor ==> Mover el cursor segun la direccion indicada por la var. Key ; ; - La variable Key expresa la direccion del movimiento; las teclas son: ; 'i'-arriba, 'j'-izquierda, 'k'-abajo y 'l'-derecha (tambien en mays.) ; - Actualiza las variables Row y Col segun la dir. que se haya seleccionado; ; a continuacion, se posiciona el cursor en el tablero de juego (PosCursor) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- MoveCursor: pusha ;Salvamos todos los registros en la pila mov al,Key ;Recuperamos la tecla leida anteriormente en Key cmp al,'i' ;'i' minuscula: mover cursor arriba je MCUp cmp al,'I' ;'I' mayuscula: mover cursor arriba je MCUp cmp al,'j' ;'j' minuscula: mover cursor izquierda je MCLeft cmp al,'J' ;'J' mayuscula: mover cursor izquierda je MCLeft cmp al,'l' ;'l' minuscula: mover cursor derecha je MCRight cmp al,'L' ;'L' mayuscula: mover cursor derecha je MCRight cmp al,'k' ;'k' minuscula: mover cursor abajo je MCDown cmp al,'K' ;'K' mayuscula: mover cursor abajo je MCDown jmp MCEnd MCUp: ;MOVEMOS CURSOR ARRIBA cmp Row,7 ;Estamos en la fila 7? je MCEnd ;Salimos, hemos alcanzado el tope superior sub Row,2 ;Si no, hacemos el movimiento y restamos 2 a Row jmp Locate ;Saltamos a posicionar cursor MCLeft: ;MOVEMOS CURSOR IZQUIERDA cmp Col,35 ;Estamos en la columna 35? je MCEnd ;Salimos, hemos alcanzado el tope izquierdo sub Col,2 ;Si no, hacemos el movimiento y restamos 2 a Col jmp Locate ;Saltamos a posicionar cursor MCRight: ;MOVEMOS CURSOR DERECHA cmp Col,41 ;Estamos en la columna 41? je MCEnd ;Salimos, hemos alcanzado el tope derecho add Col,2 ;Si no, hacemos el movimiento y sumamos 2 a Col jmp Locate ;Saltamos a posicionar cursor MCDown: ;MOVEMOS CURSOR ABAJO cmp Row,13 ;Estamos en la fila 13? je MCEnd ;Salimos, hemos alcanzado el tope inferior add Row,2 ;Si no, hacemos el movimiento y sumamos 2 a Row Locate: call PosCursor ;Volver a posicionar el cursor en el tablero de juego MCEnd: popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; MovePiece ==> Movemos la ficha a la casilla vacia adyacente ; ; Para comprobar si es posible hacer el movimiento, calculamos posicion de la ; ficha actual dentro de la matriz Pieces en base a Row y Col, segun formula: ; <>, con las siguientes consideraciones: ; * Si estamos en la casilla vacia no podremos hacer ningun movimiento ; * Comprobamos si hay una casilla vacia al lado de la actual (arriba, ; abajo, izquierda o derecha) ; * Si es asi hacemos el movimiento de la ficha y actualizamos el contenido ; del tablero de juego; ademas decrementamos en 1 el num. de movimientos ; * Si no hay ninguna casilla vacia al lado de la casilla donde estamos, no ; haremos ningun movimiento ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- MovePiece: push ax ;Guardamos en la pila los registros que vamos a usar push dx push si push di ;CALCULAMOS POSICION DE FICHA Y ASIGNAMOS VALOR A SI mov dh,Row ;SI=([Row-7]*4+[Col-35])/2 sub dh,7 mov al,4 mul dh ;AX(AL)=[Row-7]*4 (AH siempre sera 0) mov dl,Col sub dl,35 ;DL=[Col-35] add al,dl ;AL=[Row-7]*4+[Col-35] mov dh,2 div dh ;AX=([Row-7]*4+[Col-34])/2 mov si,ax ;SI posicion de la ficha dentro de la matriz Pieces cmp Pieces[si],32 ;Miramos si aquella posicion es un espacio en blanco je MPEnd MPUp: ;MIRAMOS DONDE ESTA EL ESPACIO PARA HACER MOVIMIENTO cmp Row,7 je MPleft mov di,si sub di,4 cmp Pieces[di],32 ;Es un espacio (chr(32))? je MPMove ;Si es asi, hacemos el movimiento MPLeft: cmp Col,35 je MPright mov di,si sub di,1 cmp Pieces[di],32 ;Es un espacio (chr(32))? je MPMove ;Si es asi, hacemos el movimiento MPRight: cmp Col,41 je MPdown mov di,si add di,1 cmp Pieces[di],32 ;Es un espacio (chr(32))? je MPMove ;Si es asi, hacemos el movimiento MPDown: cmp Row,13 je MPend mov di,si add di,4 cmp Pieces[di],32 ;Es un espacio (chr(32))? je MPMove ;Si es asi, hacemos el movimiento jmp MPEnd MPMove: ;Intercambiamos contenidos para hacer el movimiento mov al,Pieces[si] mov ah,pieces[di] mov Pieces[si],ah mov Pieces[di],al dec Moves ;Decrementamos los movimientos que podemos hacer Call UpdateBoard ;Actualizamos el tablero de juego MPEnd: pop di ;Restauramos los registros utilizados pop si pop dx pop ax ret ;---------------------------------------------------------------------------- ; Seed ==> Obtener las semillas del numero aleatorio ; ; Obtenemos los valores de 4 semillas para numeros aleatorios que usaremos en ; el procedimiento Random, basandonos en la fecha y hora del sistema (se ha ; de ejecutar una vez antes de Random) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- Seed: push ax ;Guardamos en la pila los registros que vamos a usar push bx push cx push dx mov ah,02h ;Ponemos en AH el parametro para la hora int 01Ah ;Interrupcion para obtener fecha y hora del sistema mov ax,dx ;OPERAMOS CON LOS REGISTROS PARA OBTENER LAS SEMILLAS mov bx,cx mov Seed1,ax xchg al,ah ;Intercambio de registros sbb ah,al ;Resta entera con prestamo adc ax,ax ;Suma entera con acarreo ror bx,cl ;Rotacion a la derecha mov Seed2,ax ror ax, cl mov Seed3,ax mov cx,ds adc cx,bx adc ax,bx xchg ah,bl xchg al,ah adc al,ah mov Seed4,ax pop dx ;Restauramos los registros utilizados pop cx pop bx pop ax ret ;---------------------------------------------------------------------------- ; Random ==> Funcion para obtener numeros pseudo-aleatorios ; ; Utilizando las semillas antes generadas con la funcion Seed, a continuacion ; generamos un numero aleatorio, que usaremos mas adelante para llenar Pieces ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- Random: push ax ;Guardamos en la pila los registros que vamos a usar push bx push cx push dx mov ax,Seed1 ;Ponemos Seed1 en AX mov bx,Seed2 ;Ponemos Seed2 en BX mov cx,Seed3 ;Ponemos Seed3 en CX mov dx,Seed4 ;Ponemos Seed4 en DX add ax,bx ;INICIAMOS OPERACIONES ARITMETICAS CON LOS REGISTROS xchg cl,ch ;Intercambio de registros sbb dx,ax ;Resta entera con prestamo adc cl,al ;Suma entera con acarreo adc ah,bl adc ax,dx ror dx,cl ;Rotacion a la derecha rol ax,cl ;Rotacion a la izquierda adc ax,cx xchg dl,ch xchg dx,bx xchg ch,al xchg bl,dh sbb ax,cx adc ax,bx mov Seed1,ax ;Devolvemos a Seed1 el nuevo valor de AX mov Seed2,bx ;Devolvemos a Seed2 el nuevo valor de BX mov Seed3,cx ;Devolvemos a Seed3 el nuevo valor de CX mov Seed4,dx ;Devolvemos a Seed4 el nuevo valor de DX mov ah,0 ;En principio 0 es el minimo valor que puede aparecer mov cl,16 ;Rango del numero aleatorio generado: [0..15] div cl mov RndNum,ah ;En AH tenemos el resultado pseudo-aleatorio pop dx ;Restauramos los registros utilizados pop cx pop bx pop ax ret ;---------------------------------------------------------------------------- ; GenPieces ==> Generacion automatica de una matriz Pieces (aleatoriamente) ; ; Este procedimiento inicializa una matriz identica a Pieces, llamada Clon, ; con '?' (chr(16)), para a continuacion llenarla de resultados aleatorios ; obtenidos tras la ejecucion del procedimiento anterior (Random), teniendo ; en cuenta que deben ser todos diferentes, de lo cual nos aseguramos en un ; bucle; puede ejecutarse tantas veces como se desee durante una partida ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- GenPieces: pusha ;Salvamos todos los registros en la pila mov dh,9 ;Colocamos el cursor en la posicion [9,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Tmp ;Primero mostramos mensaje de espera al usuario mov ah,09h int DOS call PosCursor ;Y dejamos el cursor en donde estaba mov cx,16 ;DEBEMOS INICIALIZAR, POR SI NO ES LA PRIMERA VEZ Init: mov si,cx mov Clon[si-1],16 ;Llenamos la matriz Clon de caracteres chr(16) loop Init ;Iteramos hasta que CX=0 mov cx,0 ;Inicializamos el contador y el indice mov si,0 GPLoop: call Seed ;Generamos semillas del numero aleatorio call Random ;Generamos numero aleatorio entre 0 y 15 (inclusive) mov al,RndNum mov di,0 ;Inicializamos bucle de comprobacion (valor repetido) mov Inside,0 ;En principio consideramos que no ha salido Cont: cmp al,Clon[di] ;Comparamos numero obtenido con valor de Clon[indice] jne Outside ;Si no coincide significa que no ha salido mov Inside,1 ;En caso contrario, ya habia salido (esta dentro) Outside: inc di ;Incrementamos DI en 1 cmp di,16 ;Es el indice menor que 16? jl Cont ;Continuamos comparando cmp Inside,0 ;Esta dentro? je Insert ;No, insertarlo jmp NoInsert ;Si, no insertarlo Insert: mov Clon[si],al ;Insertamos el valor obtenido en la matriz Clon cmp al,15 ;Si es un 15, tenemos que poner una 'F' (hexadecimal) je Fifteen cmp al,14 ;Si es un 14, tenemos que poner una 'E' (hexadecimal) je Fourteen cmp al,13 ;Si es un 13, tenemos que poner una 'D' (hexadecimal) je Thirteen cmp al,12 ;Si es un 12, tenemos que poner una 'C' (hexadecimal) je Twelve cmp al,11 ;Si es un 11, tenemos que poner una 'B' (hexadecimal) je Eleven cmp al,10 ;Si es un 10, tenemos que poner una 'A' (hexadecimal) je Ten cmp al,0 ;Si es un 0, tenemos que poner un espacio je SpaceGP add al,48 ;Es un numero comprendido entre 0 y 9! jmp Go Fifteen: mov al,70 ;Ponemos 'F' en AL jmp Go Fourteen: mov al,69 ;Ponemos 'E' en AL jmp Go Thirteen: mov al,68 ;Ponemos 'D' en AL jmp Go Twelve: mov al,67 ;Ponemos 'C' en AL jmp Go Eleven: mov al,66 ;Ponemos 'B' en AL jmp Go Ten: mov al,65 ;Ponemos 'A' en AL jmp Go SpaceGP: mov al,32 ;Ponemos en AL Go: ;ADELANTE: ASIGNAMOS VALORES A Pieces mov Pieces[si],al inc si inc cx NoInsert: cmp cx,16 ;Hemos llegado a 16? jge GPEnd ;Si: terminamos jmp GPLoop ;No: iteramos (bucle principal GPLoop) GPEnd: ;INICIALIZAMOS EL PUZLE COMPLETAMENTE mov dh,9 ;Colocamos el cursor en la posicion [9,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Clr ;Imprimimos espacios en blanco para borrar cadena mov ah,09h int DOS mov ah,0Ch ;Borramos el buffer, por si hay teclas en espera int DOS mov Secs,0 ;Reiniciamos a 0 el contador de segundos (el reloj) mov Moves,80 ;Reiniciamos el contador de movimientos (a 80) push Moves ;Mostramos los movimientos que nos quedan por hacer mov dh,15 ;Fila mov dl,35 ;Columna push dx call ShowDigits ;Mostramos los digitos add sp,4 mov State,1 ;Nos aseguramos de que continuara el juego lea dx,Pieces ;Cargamos la direccion efectiva de la matriz Pieces push dx ;Guardamos el registro DX en la pila call StartingPoint ;Llamamos pasando direccion de Pieces por parametro add sp,2 call UpdateBoard popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; FindChar ==> Encontrar caracter ; ; Busca un caracter concreto en la matriz Chars, que se le debe especificar ; mediante la variable Echo, devolviendo el resultado en la variable Sw (de ; tipo switch), de la siguiente manera: si Sw=0, lo ha encontrado, si Sw=1, ; no lo ha encontrado ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- FindChar: push ds ;Salvamos DS en la pila xor ax,ax ;Ponemos AX a 0 push ax ;Guardamos AX en la pila mov ax,@data ;Ponemos datos en posicion de AX mov ds,ax ;Inicilizamos el registro DS (segmento de datos) mov es,ax ;Datos en AX y ES (segmento extra) cld ;Direccion de exploracion de izquierda a derecha lea di,Chars ;Fuente de cadena donde queremos buscar (Chars) mov cx,22 ;Transferimos 22 bytes (longitud de Chars) mov al,Echo ;Exploramos cadena para encontrar Echo repne scasb ;Repetimos exploracion hasta que se encuentre Echo jcxz None ;Bifurcamos a None si no se encuentra mov Sw,0 ;Si se ha encontrado, ponemos Sw a 0 jmp FCEnd None: mov Sw,1 ;Si no se ha encontrado, ponemos Sw a 1 FCEnd: ;Restauramos AX y DS de la pila pop ax pop ds ret ;---------------------------------------------------------------------------- ; ReadPieces ==> Lectura por teclado de una matriz Pieces (personalizada) ; ; Leemos por teclado una a una las fichas de la matriz Pieces, completandola ; a gusto del usuario, pero siempre respetando el rango de caracteres que se ; utiliza en el juego (los numeros del 1 al 9 y las letras de la A a la F), y ; realizando una llamada a StartingPoint tras completar la lectura de los 16 ; caracteres, con objeto de comprobar que no hay errores de tablero (ninguna ; casilla blanca introducida o mas de una) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- ReadPieces: pusha mov cx,16 ;Antes de empezar, salvamos la matriz Pieces Save: mov si,cx mov di,cx mov bl,Pieces[di-1] mov Clon[si-1],bl ;Utilizamos la matriz Clon para guardar Pieces loop Save mov dh,9 ;Posicionamos el cursor en las coordenadas [9,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo con WriteChar call WriteChar mov si,0 RPLoop: mov ah,07h ;Esperamos a caracter de teclado (sin visualizacion) int DOS cmp al,32 ;Es un espacio? je SpaceRP ;Bifucarmos a SpaceRP cmp al,0 ;Si es 0+[ASCII], se ha pulsado una tecla de funcion je Buffer cmp al,27 ;Hemos pulsado , volvemos al tablero je Escape cmp al,8 ;Hemos pulsado , volvemos al tablero je Escape mov Echo,al ;Asignamos a la variable Echo el valor leido Call FindChar ;Hacemos la comprobacion de rango pertinente cmp Sw,0 ;Si Sw vale 0 es que es un valor correcto jne Buffer ;En caso contrario vaciamos el buffer de teclado mov al,Echo ;Reasignamos Echo, esta vez con su valor final SpaceRP: cmp al,97 ;Si esta fuera del rango [a..f], saltamos a NotABCDEF jl NotABCDEF cmp al,102 jg NotABCDEF sub al,32 ;Es una letra --> le restamos 32 para pasar a mays. NotABCDEF: mov Pieces[si],al ;Asignamos el valor leido a Pieces[indice] call WriteChar ;Lo mostramos en pantalla inc dl ;Incrementamos DL (columna) mov al,0 ;Utilizando el caracter nulo call WriteChar ;Ponemos cursor en la siguiente columna inc si ;Incrementamos el indice cmp si,16 ;Si SI llega a 16 hemos completado la cadena jl RPLoop ;Si no, seguimos leyendo caracteres mov Secs,0 ;Reiniciamos el reloj (a 0) mov Moves,80 ;Reiniciamos los segundos a 80 push Moves ;Mostramos los movimientos que nos quedan por hacer mov dh,15 ;Fila mov dl,35 ;Columna push dx call ShowDigits ;Mostramos los digitos add sp,4 mov dh,9 ;Colocamos el cursor en la posicion [9,43]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Clr ;Imprimimos espacios en blanco para borrar cadena mov ah,09h int DOS Buffer: mov ah,0Ch ;Borramos buffer de teclado, por si procede de chr(0) int DOS cmp si,16 jl RPLoop mov State,1 call UpdateBoard lea dx,Pieces ;Cargamos la direccion efectiva de la matriz Pieces push dx ;Guardamos el registro DX en la pila call StartingPoint ;Llamamos pasando direccion de Pieces por parametro add sp,2 jmp RPEnd Escape: mov cx,16 ;Si hemos pulsado Escape, cargamos matriz de partida Load: mov si,cx mov di,cx mov bl,Clon[di-1] mov Pieces[si-1],bl loop Load mov dh,9 ;Colocamos el cursor en la posicion [9,44]... mov dl,44 mov al,0 ;...imprimiendo un caracter nulo call WriteChar lea dx,Clr ;Imprimimos espacios en blanco para borrar cadena mov ah,09h int DOS mov State,1 ;Continuamos jugando RPEnd: call PosCursor ;Devolvemos cursor a su posicion original popa ret ;---------------------------------------------------------------------------- ; LoadFail ==> Cargar matriz fracaso (llenamos Pieces con puzle imposible) ; ; Llenamos la matriz de juego (Pieces) con los valores de otra matriz (Fail) ; que tenemos en memoria, a sabiendas de que este puzle es matematicamente ; imposible de resolver (Sam Loyd, 1878); solo apto para gente muy aburrida ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- LoadFail: pusha ;Salvamos todos los registros en la pila mov cx,16 LFLoop: ;Llenamos Pieces con los valores de la matriz Fail mov si,cx mov di,cx mov bl,Fail[di-1] mov Pieces[si-1],bl loop LFLoop call UpdateBoard ;Actualizamos el tablero de juego mov Moves,80 push Moves ;Mostramos los movimientos que nos quedan por hacer mov dh,15 ;Fila mov dl,35 ;Columna push dx call ShowDigits ;Mostramos los digitos add sp,4 mov Row,13 mov Col,41 call PosCursor mov Secs,0 mov State,1 popa ;Rescatamos todos los registros de la pila ret ;---------------------------------------------------------------------------- ; CheckEnd ==> Comprueba situaciones que pueden determinar el final ; ; - Verifica si se han agotado los movimientos disponibles; en ese caso pone ; la variable State a 4 (movimientos agotados) ; - Verifica si el tablero esta ordenado, es decir, si todas las fichas estan ; ordenadas de izquierda a derecha y de arriba a abajo, quedando la casilla ; vacia en la ultima posicion (abajo-derecha); si es asi cambia State a 2 ; (posicion ganadora) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- CheckEnd: push ax ;Guardamos en la pila los registros que utilizamos push si CEMoves: cmp Moves,0 ;Preguntamos si el numero de movimientos es ya 0 jg CEOrder ;Si es mayor, procedemos a comprobar orden de Pieces mov State,4 ;Movimientos agotados jmp CEEnd CEOrder: mov si,0 ;Indice para acceder a los datos CELoop: ;INICIAMOS EL BUCLE PARA CONTROLAR LAS FICHAS mov al,Pieces[si] ;Usamos AL para guardar la ficha actual inc si ;Incrementamos indice con el que accedemos a Pieces cmp al,Pieces[si] ;Si es mayor que la siguiente aun no esta ordenada jg CEEnd cmp si,14 ;Comprobamos si el indice SI ha llegado a 14 jl CELoop ;Si es menor seguimos iterando mov State,2 ;Todas las fichas estan ordenadas CEEnd: pop si ;Restauramos los registros con los valores iniciales pop ax ret ;---------------------------------------------------------------------------- ; PrintMessage ==> Muestra un mensaje segun lo que indique la variable State ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- PrintMessage: push ax ;Guardamos en la pila los registros que utilizamos push bx push dx mov ah,02h ;Servicio para posicionar el cursor mov bh,00h ;Pagina mov dh,18 ;Fila 18 mov dl,34 ;Columna 34 int BIOS cmp State,0 ;0: hemos pulsado la tecla 'q' para salir del programa je PMState0 ;Salimos imprimiendo el mensaje correspondiente cmp State,2 ;2: ganador; todas las fichas estan ordenadas je PMState2 ;Indicarlo y acabar cmp State,3 ;3: error de tablero (demasiadas blancas o ninguna) je PMState3 ;Indicarlo y acabar cmp State,4 ;4: movimientos agotados (15 en total) je PMState4 ;Indicarlo y acabar cmp State,5 ;5: tiempo agotado (60 segundos en total) je PMState5 ;Indicarlo y acabar jmp PMEnd ;Si no es ninguno de estos estados salimos sin escribir PMState0: ;Cargamos direccion efectiva de mensaje: 'CANCELADO' lea dx,Msg0 jmp PMprint PMState2: ;Cargamos direccion efectiva de mensaje: 'PERFECTO!' lea dx,Msg2 jmp PMprint PMState3: ;Cargamos direccion efectiva de mensaje: 'TABLERO' lea dx,Msg3 jmp PMprint PMState4: ;Cargamos direccion efectiva de mensaje: 'AGOTADO' lea dx,Msg4 jmp PMprint PMState5: ;Cargamos direccion efectiva de mensaje: 'TIEMPO!' lea dx,Msg5 PMPrint: ;IMPRIMIMOS EL MENSAJE USANDO LOS SERVICIOS DEL DOS mov ah,09h int DOS PMEnd: pop dx ;Restauramos los registros con los valores iniciales pop bx pop ax ret ;---------------------------------------------------------------------------- ; ISR ==> Rutina de atencion a la interrupcion (ISR) del temporizador o reloj ; ; La ISR de reloj ira incrementando la variable Ticks para contar el numero ; de veces que el reloj del sistema interrumpe; cuando dicha variable llegue ; al valor indicado por la constante TPS (Ticks Por Segundo) la reiniciara a ; 0, incrementara la variable Secs (Segundos) y actualizara la visualizacion ; del tiempo, llamando a la subrutina ShowDigits; luego tendra que dejar el ; cursor en la misma posicion donde estaba (ISR: Interrupt Service Routine) ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- ISR: cli ;Cerramos las interrupciones push ax ;Guardamos en la pila los registros que utilizamos push bx push cx push dx push si push di push ds mov ax,@data ;Inicializamos el segmento de datos mov ds,ax inc Ticks cmp Ticks,TPS ;Cada 18 ticks de reloj incrementamos los segundos jne ISREnd mov Ticks,0 ;Cogemos la posicion actual del cursor mov bh,00h ;Pagina mov ah,03h ;Servicio para posicionar el cursor int BIOS mov di,dx ;Guardamos fila (DH) y columna (DL) en el registro DI push Secs ;Segundos mov dh,16 ;Fila mov dl,35 ;Columna push dx call ShowDigits ;Mostramos los digitos add sp,4 ;Restauramos posicion del cursor (fila y col. en DI) mov bh,00h ;Pagina mov dx,di ;Recuperamos DI para dejar el cursor donde estaba mov ah,02h ;Servicio para posicionar el cursor int BIOS inc Secs cmp Secs,90 ;Establecemos el limite de tiempo en 90 segundos jle ISREnd mov State,5 mov Secs,0 ISREnd: mov al,20h ;Generamos aviso de final de interrupcion (EOI) out 20h,al ;Escribiendo el valor 20h en el registro 20h de E/S pop ds ;Restauramos los registros con los valores iniciales pop di pop si pop dx pop cx pop bx pop ax sti ;Abrimos las interrupciones iret ;Retorno de interrupcion ;---------------------------------------------------------------------------- ; InstallISR ==> Instalamos la ISR de reloj declarada anteriormente ; ; Guardamos la direccion de la ISR de reloj original e instalamos la ISR de ; reloj de nuestro programa ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- InstallISR: push es ;Guardamos en la pila los registros que utilizamos push ax xor ax,ax mov es,ax mov ax,es:[CLK] ;Salvamos la direccion de la ISR de reloj actual mov Ofs,ax mov ax,es:[CLK+2] mov Sgm,ax ;Instalamos la nueva ISR cli ;Deshabilitar interrupciones lea ax,[ISR] ;Cargamos en AX la direccion de la ISR mov es:[CLK],ax ;Ponemos en ES:[vector de reloj] la dir. de la ISR mov es:[CLK+2],cs sti ;Habilitar interrupciones pop ax ;Restauramos los registros con los valores iniciales pop es ret ;---------------------------------------------------------------------------- ; UninstallISR ==> Desinstalamos la ISR de reloj que habiamos instalado antes ; ; Desinstalamos nuestra ISR de reloj, restaurando la direccion original de la ; ISR, que teniamos guardada en Sgm:Ofs ; ; Parametros de entrada: Ninguno ; Parametros de salida: Ninguno ;---------------------------------------------------------------------------- UninstallISR: push es ;Guardamos en la pila los registros que utilizamos push ax xor ax,ax ;Ponemos AX a cero mov es,ax ;Inicializamos el registro ES (Extra Segment) ;Reinstalamos direccion de la ISR de reloj original cli ;Deshabilitar interrupciones mov ax,Ofs ;Ponemos en AX el desplazamiento guardado mov es:[CLK],ax ;Y lo pasamos al desplazamiento del reloj en ES mov ax,Sgm ;Ponemos en AX el segmento guardado mov es:[CLK+2],ax ;Y lo pasamos al segmento del reloj en ES sti ;Habilitar interrupciones pop ax ;Restauramos los registros con los valores iniciales pop es ret ;---------------------------------------------------------------------------- ; Programa principal ;---------------------------------------------------------------------------- Main: STARTUPCODE ;Inicializamos el entorno call ClearScreen ;Borramos la pantalla call PrintBoard ;Imprimimos el tablero de juego mov Row,11 ;Asignamos la posicion inicial de la casilla blanca mov Col,39 call PosCursor ;Colocamos el cursor en la casilla blanca call InstallISR ;Instalamos la ISR de reloj creada por nosotros call UpdateBoard ;Actualizamos el tablero de juego MainLoop: call ReadKey ;Esperamos a la lectura de un caracter por teclado call CheckEnd ;Comprobamos si ya hay una situacion de final cmp State,1 ;Si la variable State es igual a 1... je MainLoop ;...seguimos jugando... call PrintMessage ;...si no, salimos mostrando el mensaje pertinente call UninstallISR ;No debemos olvidar desinstalar nuestra ISR EXITCODE 0 ;Fin de programa, codigo de error 0 end Main

Descargar fichero fuente (15.ASM)
© 2013 djmm All lefts reserved