jueves, 19 de mayo de 2016

Curso de Juegos (6)

Curso “Programación de Juegos”

Curso Gratuito con codigo fuente.
Entrega 6.



Eliminando el BUG de nuestro programa
 

Nuestro programa de la nota anterior no estaba completamente depurado, porque tenía un pequeño error.

Como habrá visto al correr el programa, en cada colisión, el carácter-sprite se “come” al obstáculo representado por la [X], pero resulta ser que, debido a la técnica empleada para dibujar la pantalla, cuando borramos las [X], éstas se borran de la pantalla, pero “persisten” dentro de las variables Screen$(filas) o para ser más claros, en la memoria RAM. 


La consecuencia de esto es que después de limpiar la pantalla, debemos también eliminar las X de la RAM (es decir, de las variables que las contienen).


Si no lo hacemos, la rutina de detección de colisiones (que trabaja con la RAM, no con la pantalla dibujada) vuelve a detectar una colisión porque ha quedado el FANTASMA: no se ve en la pantalla pero para el programa sigue "estando" en la RAM


Esto trae una consecuencia: si vuelve a pasar el caracter-sprite por el lugar en donde antes había una X, el programa vuelve a sumar puntos, reaccionando como si la X estuviera aún dibujada.

Algo de Teoría
 

Lamentablemente ahora debo hacer un paréntesis para explicarle algo. Estos conceptos  deberían ser básicos para cualquier programador: ¿Qué sucede cuando creamos y asignamos una variable?

No se ofenda, pero es un hecho que muchos programadores tienen dificultades al desarrollar sus programas porque no saben (a ciencia cierta) cómo trabajan los lenguajes en la RAM. Es por eso que le explicaré en detalle lo que sucede cuando usted crea una variable, le asigna un valor y trabaja con esa variable.


Ahora veamos el primer grafico para entender esta explicación
 


Casi todos los lenguajes admiten más de una manera de crear o definir una variable. Mientras más estricto sea el lenguaje, menos formas de definir y asignar variables tendrá.
 

Cuando usted crea una variable tiene que ponerle un nombre.

Pero el nombre de una variable es un artificio del alto nivel, que hace que la vida del programador sea más sencilla. Con un nombre de variable, el programador no necesita tener en cuenta las posiciones de memoria. Porque eso es una variable: una posición de memoria que contiene datos.
 
El sistema (que trabaja en código máquina) no ncesita identificar una variable con un nombre, porque en realidad trabaja con los espacios de memoria RAM que pueden contener información (y que el programador identifica con el Nombre_de_la_variable).


Cuando alguien crea una variable, el sistema aparta en RAM un espacio que tendrá tantos bytes como el tipo de variable que usted esté creando:


•    Una variable integer (entera o identificada como %) se guardará en 16 bits o 2 Bytes.
•    Una variable long (o entero-largo o &) se almacenará en 32 bits o 4 Bytes
 

Todas las variables numéricas tienen una cantidad fija de Bytes para su almacenamiento. Si usted crea un array en la RAM se almacenarán tantos bytes como haya declarado en el array:

•    Por ejemplo, si en el alto nivel usted declara a%(1 to 10)
•    En la RAM se reservarán 20 bytes:  a%(1) ocupará los bytes 1 y 2, a%(2) ocupará los bytes 3 y 4, a%(3) los bytes 5 y 6 y así hasta terminar en a%(10) que ocupará los últimos 2 bytes del espacio reservado en RAM. ¿Por qué solo 2 Bytes por elemento? Pues porque USTED declaro con el símbolo % a la variable a%() y el sistema reservará 2 bytes por elemento para ese tipo de variable.


Cada vez que usted use una etiqueta, indicando que necesita usar el valor almacenado en la variable ( por ejemplo, invoca a%(12) ), el sistema ubicará en la RAM el espacio reservado para a%(12) y leerá los datos almacenados en esos dos bytes interpretándolos como datos numéricos de entero corto. Y se los retornará o los usará según sus instrucciones.
 

En realidad, el programador (desde el alto nivel), no necesita usar la etiqueta para trabajar con los datos. ¿Por qué? Porque si conoce el lugar de la RAM en donde están los datos, puede leerlos directamente sin usar el nombre de la variable. Incluso puede modificarlos.

Es por ese motivo que muchos lenguajes de alto nivel le dan la posibilidad a los programadores de leer y escribir directamente en las posiciones de memoria, con la única  condición de que escriba y lea en los lugares correctos para evitar un crash.


En Qbasic usted tiene las instrucciones PEEK y POKE para leer datos y escribir directamente en RAM. Para saber en que lugar de la memoria se almacenan los datos de una variable, tiene VARSEG que le indica el segmento de ram en la que se almacena y tiene VARPTR para saber su desplazamiento (alto, no se desespere, recuerde que Qbasic es PREHISTORICO…jaja y usa técnicas antiguas para manejo de RAM).
 

En realidad, si el lenguaje le da o no posibilidades o instrucciones para determinar la posición de RAM de los datos de una variable, no tiene mucha importancia. Hay muchas formas de leer directamente la RAM, aún cuando el lenguaje no le ayude. Pero tocar la ram directamente sin saber lo que está haciendo… es condenar al programa al crash inevitable.
 

En un lenguaje usted puede “leer” un contenido de esta manera:
 

B% = A%(12)
 

Y el programa hará lo siguiente: buscará en RAM los dos bytes que corresponden alos datos almacenados en a%(12), identificará la posición de memoria que almacena datos para B%, y después de eso, transferirá los datos entre las dos posiciones de memoria RAM. Es decir, llevará los dos bytes de a%(12) hasta la ram que identifica a B%.

¿Muchos pasos, verdad? Allí usted puede ver que hay un conjunto de lecturas y pasos adicionales que no hacen más que enlentecer su código, porque se podrían trasladar los datos más rapidamente haciendo un "enroque" de datos entre las dos posiciones de memoria.

Así es que una forma más eficiente de transferir esos datos sin tanta demora (o uso innecesario de ciclos de reloj de microprocesador) sería usar una instrucción de este tipo:
 

Poke Varptr(b%), Peek(Varptr(a%(12)))
 

O algo por el estilo (según el intérprete/compilador/versión de lenguaje que use). 

Esta instrucción transfiere directamente datos entre dos posiciones de memoria sin tanto alboroto, interpretación o perdida de tiempo de microprocesador. El problema es que si usa mal las instrucciones, se alterarán los datos de la ram con consecuencias imprevisibles.

Algunos lenguajes, hacen justamente eso desde el alto nivel (probablemente conoce la instrucción SWAP de algunos lenguajes de alto nivel). O la instrucción SWAP de lenguajes de bajo nivel qie intercambian el byte alto con el bajo.

Lamento toda la jerga técnica de procesos obsoletos, pero creo que la idea de entender cómo se trabajan las variables en RAM es importante para un programador. 

Mucho más para un programador de juegos porque a veces necesitará aumentar la velocidad de sus aplicativos. Las técnicas de acceso de datos en RAM sin pasar por el alto nivel, disparan la velocidad de los juegos.

Esto depende del lenguaje que usted esté usando, claro está. Algunos lenguajes mastodónticos son tan pesados y dependen tanto de rutinas y funciones de alto nivel que no importa lo que haga el programador, nunca tendrán buena velocidad. Sucede mucho con los que dependen de un run-time o un framework…. tienen grandes demoras en la ejecución.

Comprenderá que como programador…no me gustan los run-times, los frameworks ni los lenguajes interpretados… pienso que limitan a los programadores a los caprichos de los fabricantes de lenguajes. Microsoft es muy famoso por sus caprichos y dependiendo de sus intereses económicos, deja de actualizar entornos completos de programación.


También están los vaivenes económicos. Las empresas quiebran, cierran, o se fusionan. Muchos lenguajes prometedores dejaron de producirse por cierre o por decisiones de directorios en empresas fusionadas.
  
A eso me refiero cuando digo que es bueno que los programadores no hagan depender a sus proyectos de herramientas de terceros.

Un programador C++, PowerBasic, Delphi o Masm32 nunca dependerá de nadie. Si usted aprende a trabajar con un lenguaje de propósito general, nunca dependerá de rutinas de terceros. Cuando necesite algo en particular, simplemente...lo creará, lo inventará... y lo usará tantas veces como lo necesite.

Es por eso que es muy bueno (desde el punto de vista profesional)  crear nuestros propios motores o engines.  Y no me refiero solo a juegos, sino a aplicativos de todo tipo.

Hoy veo un gran problema en los países en desarrollo. La gran dependencia de herramientas de terceros. También se ve en la industria de juegos. Muchisimos programadores dependen de engines y sin ellos, serían incapaces de crear sus propios juegos. Hay pocos programadores básicos, con dominio completo de lenguajes de propósito general.


En fin, sigamos adelante…
 

Continuemos con la solución al BUG
 

Ahora que ya comprende como se manejan las variables en RAM, entenderá las dos posibles soluciones al BUG que propongo.

Ambas soluciones se aplican a la función GetColision%


La primer solución (la menos eficiente)


Esta solución es la menos eficiente, porque trabaja con muchas instrucciones adicionales. Pero la doy porque algunos compiladores no permiten la segunda solución propuesta.


Esta solución consiste en dividir la cadena Screen$(fila%) de la que debemos borrar la X. 


Tenemos que crear dos subcadenas: una con los caracteres que están a la izquierda del valor de columna% y otra con los caracteres que están a la derecha del valor columna%
 


Para el trabajo, se usan variables intermedias:

DIM der$
DIM izq$
DIM LenDer%
DIM LenIzq%

 
LenDer% y LenIzq% son números que contienen la longitud (en caracteres) de las cadenas derecha e izquierda. Se calculan teniendo en cuenta el valor columna% que apunta a la X que debe ser borrada:

LenIzq% = columna% - 1 (un lugar a la izquierda de columna%)
LenDer% = LEN(Screen$(fila%)) - columna% (al tamaño total de Screen$(fila%) se le resta el valor de columna% y nos da el tamaño de la cadena derecha)

Una vez obtenidos estos valores, simplemente definimos las subcadenas derecha e izquierda de este modo:

izq$ = LEFT$(Screen$(fila%), LenIzq%)
der$ = RIGHT$(Screen$(fila%), LenDer%)

 
Y terminamos reconstruyendo la nueva cadena Screen$(fila%) asegurándonos de colocar entre las cadenas izquierda y derecha un espacio vacio que en definitiva es el que borra la X fantasmal de nuestro juego:

Screen$(fila%) = izq$ + " " + der$

Mucho trabajo ¿verdad? Pero nada puede compensar el entretenimiento de ser programador básico… jeje.

Segunda solución: la más eficiente


Esta solución implica modificar directamente el contenido de la RAM.


Para eso usaremos la instrucción MID$ que modifica directamente en RAM los bytes que contienen sus datos. Escribimos esta instrucción:

MID$(Screen$(fila%), columna%, 1) = " "
 

Esta orden le dice al intérprete Qbasic que modifique dentro de la variable Screen$(fila%), al carácter número columna%. Así se ordena el reemplazo de un solo caracter (eso indica el ,1), que en este caso el el byte que contiene la X a borrar, para asignarle a ese carácter  un espacio vacío
 

Tenga en cuenta que algunos compiladores no permiten la manipulación directa en RAM a partir de funciones incorporadas, como el caso de MID$

Pero no es el caso de Qbasic, que como puede (y podrá ver en otros capítulos), tuvo muchisima popularidad por muchisimas buenas razones técnicas (en su momento, claro, porque ahora es obsoleto).

Como puede ver, es la solución más rápida porque tiene menos instrucciones, lo que se traduce en menos tiempo de ejecución.


Conclusión
 

Hay otro motivo por el que incluí las dos soluciones. Muchas veces desarrollar juegos implica que hayan varios programadores involucrados y suele haber también un coordinador general que toma decisiones al respecto de ciertos temas.

En casos parecidos a este del BUG, puede ser que el programador le presente una u otra solución y el coordinador deberá tomar la decisión de qué solución será la adoptada.
 

A veces los programadores no proponen las mejores soluciones técnicas o las más eficientes, sino que adoptan las soluciones más portables. No siempre lo mejor desde el punto de vista técnico es lo más portable.

Escuche lo que tienen para decir sus programadores.
Puede ser que le estén evitando un dolor de cabeza más adelante, cuando deba portar su proyecto a otros entornos.
 

Finalmente, es bueno que “lea” códigos fuentes y los re-escriba una y otra vez...  Eso es lo que le aconsejo con este curso.
 

Re-escriba estos códigos. Equivóquese y acierte implementando otras soluciones que las propuestas. Ser programador es mucho más que leer un código y aceptar premisas porque alguien lo dice (en este caso el que escribe).
 

Modifique. Explore. Genere versiones mejoradas. ¿Que no puede? ¿Qué no sabe cómo? Pues… bienvenido al mundo de los programadores. Los programadores y desarrolladores somos los que vivimos en las fronteras, allí en donde se hacen cosas que nunca nadie hizo antes. Y se hacen de un modo completamente diferente al habitual.







   Descarga Qbasic+DosBox desde aquí.
  ----------------------------------------------------------------------
  Curso Gratuito de Programación de VideoJuegos

  1. Introducción

  2. A quienes va dirigido el curso
  3.Temario del curso / Herramientas
  4.Primer programa (codigo fuente) 
  5.Detectando colisiones y comiendo objetos
  6.Eliminando el bug de nuestro programa
  ----------------------------------------------------------------------


lunes, 16 de mayo de 2016

Curso de Juegos (5)

Curso “Programación de Juegos”

Curso Gratuito con codigo fuente.
Entrega 5.


Detectando colisiones en los programas y comiendo objetos

Nota: Al final de este artículo está el link de descarga para este nuevo programa (02.Bas) y los links a todas las notas del curso

En el programa anterior (01.BAS) aprendimos a delimitar un escenario y a controlar el movimiento de un objeto en la pantalla.

A ese mismo programa, ahora le agregaremos un par de funcionalidades especiales:

  • La detección de una colisión de nuestro carácter-sprite con algún objeto en pantalla
  • La suma de puntos por cada colisión del carácter-sprite.

La detección de colisiones es importante en un videojuego porque puede servir para crear comportamientos diversos:
  • Evitar que un jugador atraviese un muro o algún objeto sólido de pantalla que debe comportarse como un obstáculo
  • Saber si un jugador ha podido “tocar” algún objeto para asignarle o quitarle puntos. Piense en juegos como el  pac-man en donde algún come-cocos alcanza al jugador y le debe restar vidas o puntos, o en juegos de guerra en donde el jugador atraviesa alambrados y le disminuyen vida en la partida, por ejemplo.
  • Juegos en donde algún disparo o meteorito alcanza al jugador.
  • Juegos en donde el jugador alcanza un objeto que debe sumar algo.
En nuestro juego, ahora vamos a hacer justamente esto último: cuando nuestro carácter-sprite alcance un objeto de la pantalla, se lo “comerá” para hacerlo desaparecer de la vista y sumar un punto para el jugador que controla al caracer-sprite.

Las colisiones pueden ser tan complejas como lo necesite el juego. En las pantallas gráficas las técnicas de colisiones pueden ser un poco más complejas, pero en definitiva se trabajan con rutinas semejantes.

Le recuerdo que estamos trabajando con pantallas de tipo texto para justamente, aprender lo básico de las técnicas, pero mi idea es hacer escalar este curso al entorno gráfico, así podrá ver que no es tan difícil aprender a programar videojuegos si lo hace paso a paso.

Que necesitamos para detectar colisiones

Para lograr esto, vamos a crear un par de variables auxiliares y una función que nos ayudarán con la gestión de mensajes en el sistema (Digo sistema porque esto ya comienza de a poco, a ser un programa complejo)

Variables auxiliares [CONST Si = 1]  y [CONST No = 0] estas son variables constantes que por defecto también son globales y la única función que tienen es permitir crear una respuesta lógica boooleana del tipo SI / NO para distintas respuestas en funciones y delimitar comportamientos definidos sin confusiones provocados por valores numéricos.
 
Variable auxiliar [DIM Puntos AS INTEGER] esta variable sirve para controlar los puntos adquiridos en cada colision. Puede sumar o quitar puntos dependiendo de cómo quiera usted realizar el comportamiento del juego.

Variable tipo Flag (o bandera) [DIM FlagColision AS INTEGER] esta es una variable tipo flag que puede tener dos valores: SI o NO.  Nos alerta en el programa sobre una colision detectada en un momento determinado. En este caso en particular, es usada en tiempo real para evitar que el sistema sume puntos indiscriminadamente y sin límite.

Ya vimos en la nota anterior que durante la ejecución de cada bucle DO/LOOP, el sistema controla si el usuario ha efectuado un movimiento y realiza el ajuste de la posición o coordenada de pantalla a través de las variables c (columna) y f (fila).

En el momento exacto en que se realiza un ajuste de alguna de esas variables (c / f), el sistema pone a FlagColision en NO cuando el usuario ha realizado un movimiento.

Al terminar de evaluar la condición de movimiento del usuario (en el bloque If /Else dentro de Do/Loop), el sistema procederá a verificar si con la nueva posición del carácter-cursor del usuario existe una situación de colisión a través de la función GetColision% (vea más adelante).

Al detectarse esa colisión la función retornará el estado SI, por lo cual el sistema inmediatamente sumará un punto por colisión y además pone el flag en estado NO para evitar que se vuelva a sumar puntos. De este modo se evita sumar más de un punto por colisión.

Esto se hace en cualquiera de los 4 movimientos previstos (arriba, abajo, derecha, izquierda). Vea en el codigo fuente la asignación [FlagColision = No] en cada condición de movimiento:

ELSEIF KeyPress = CHR$(0) + CHR$(77) AND c < MxCol THEN
         'derecha
         LOCATE f, c
         PRINT " "
         c = c + 1
         FlagColision = No 


Si quita esa condición (
FlagColision = No), verá que cuando se realice una colisión, el sistema sumará puntos indiscriminadamente hasta que usted proceda a quitar de la posición de colisión al carácter-sprite que estamos usando en el programa. Este flag limita de ese modo a la suma (o quita de puntos, según como planifique usted su programa) a sólo una vez durante la colisión detectada.

Cómo trabaja la función GetColision%

La Función [FUNCTION GetColision% (fila%, columna%)] es la encargada de detectar cuándo se produce una colisión del carácter-sprite con algún objeto dibujado en pantalla.

En el bucle Do/Loop, después de evaluar los movimientos del usuario con el bloque de instrucciones If/Then, el programa evalúa la nueva posición del carácter-sprite para ver si se produjo alguna colisión en la pantalla. Esto se hace invocando a la función enviando las coordenadas nuevas del carácter-sprite a través de los nuevos valores de las variables (c y f), mediante la invocación  [IF GetColision%(f, c) = Si THEN]

Si la función detecta que se produjo una colisión, devuelve SI, si no hay colisión, devuelve NO.

Para detectar la colisión, la función hace una evaluación de variables.

Las variables [Screen$(4 TO 20)] son las que tienen en su interior el dibujo completo de la pantalla del usuario. Fueron definidas en el bloque “Dibujar Pantalla Inicial” (vea la estructura de este programa en la nota anterior), justo antes del bucle Do/Loop.
 
Como puede ver en el código fuente, se han definido dentro de las variables a los obstáculos que hay en la pantalla como [X].

Las variables Screen$() están ajustadas exactamente para que el sistema, al calcular la fila actual del carácter-cursor, coincida con el valor de la variable Screen$(fila) que contiene los objetos dibujados en esa pantalla. 

Cuando el sistema hace la la evaluación IF de este modo: 

IF MID$(Screen$(fila%), columna%, 1) <> " " THEN
 
Lo que hace en realidad es mirar dentro de la variable Screen$(Fila) en el carácter de columna% para ver si NO está vacío (“ “) a través de la condición [<>]

En el caso que haya algun objeto dibujado en esa posición que es la posición fila-columna del carácter-sprite, devuelve un SI, indicando que se ha realizado una colisión en la pantalla.

En caso que la rutina determine que el espacio “apuntado” por fila-columna está vacío, retorna un NO indicando que la posición está libre y no se ha realizado una colisión en el juego.

Un desafío para usted: el pequeño BUG a depurar

Este programa aún no está completo. Tiene un pequeño “Bug” que no fue depurado para ver si usted es capaz de eliminar ese pequeño error por sus propios medios. No se enoje, si desea programar juegos, constantemente deberá depurar errores de diferente magnitud.
Depurar y corregir es un buen método para aprender a programar y mejorar como desarrollador.

¿En qué consiste el BUG de este programa? Bueno, como verá usted, en cada colisión, el carácter-sprite se “come” al obstáculo representado en este caso por la [X], y al hacerlo, se aumenta en uno la puntuación del usuario, pero resulta ser que por el método de dibujado de pantalla que hemos empleado en este programa, en realidad, las [X] se borran de la pantalla, pero “persisten” dentro de las variables Screen$(filas). 

Esto trae como consecuencia la presencia del BUG que deberemos “depurar”: cuando el carácter-sprite vuelve a pasar por la posición en donde había una X, la rutina de detección de colisiones vuelve a detectar una colisión aunque no hay nada en la pantalla. Reacciona ante el FANTASMA del obstáculo que persiste dentro de la variable.

La solución sería borrar la [X] también de las variables Screen$(filas) cuando se produce una colisión.

Bueno ahora ya sabe cuál es el bug del programa y lo que debe hacer para solucionarlo. ¿Se siente capaz de hacerlo? Espero que sí… y que sea capaz de eliminar el error.

La próxima entrega

En la próxima entrega, le daré una solución al bug planteado por si no lo logró.


Y más tarde ( cuando publiquemos la nota 7), haremos una pequeña modificación a este programa, transformándolo a un programa en donde un pequeño “minero” vaya excavando la montaña para llegar a depósitos de oro y piedras preciosas dentro de la mina, incrementando su fortuna.

Usaremos todas las técnicas planteadas hasta ahora y verá que con tan “poquitos” conocimientos, se pueden lograr programas de juegos rápidamente…. Y con sus propios medios, sin depender de librerías de terceros.


  ----------------------------------------------------------------------
  Curso Gratuito de Programación de VideoJuegos

http://profesorponce.blogspot.com.ar/p/libros-publicados.html


 

martes, 10 de mayo de 2016

Curso de Juegos (4)

Curso “Programación de Juegos”

Curso Gratuito.
Entrega 4.


El flujo de los programas

Este no pretende ser un curso académico de programación, sino un curso práctico que pretende ayudar a los programadores a definir las estructuras más importantes de un programa que les permitan crear rutinas de soporte para videojuegos.


Como también está pensado para programadores,  vamos a cambiar un poco el modo de abordar los problemas.


En lo primero que vamos a pensar, es en el FLUJO DE UN PROGRAMA (vea el primer grafico de la nota).


Sin entrar en demasiados detalles clásicamente existen dos tipos de flujo que permiten ejecutar las instrucciones individualmente:


1) Los programas de ejecución lineal. Aclaremos que en realidad, en lenguaje máquina todos los programas son de ejecución linea por línea y que la omisión, el salto y la búsqueda de subrutinas y funciones son en realidad saltos controlados por eventos o variables. Pero ahora nos referimos al alto nivel de un lenguaje.


En el alto nivel, lenguajes como Qbasic (que usaremos en la mayoría de los ejemplos de este curso) permiten iniciar el programa (declarando funciones, rutinas variables y constantes) y luego comienzan la ejecución del código instrucción por instrucción hasta terminar en la instrucción final para terminar saliendo del programa al sistema operativo.


En el medio del principio y fin, contamos con instrucciones, llamadas a funciones, llamadas a subrutinas pero finalmente se llega al final.


Para evitar llegar al final, se requiere que el programador establezca una instrucción de repetición para crear un bucle infinito pero controlado para que el usuario pueda terminar el programa.


Adicionalmente el bucle debe controlar eventos para poder generar las respuestas adecuadas (selecciones del usuario, errores, imprevistos, etc)


Este tipo de esctructuras y flujos venían bien en entornos uni-tareas porque el CPU trabajaba con un programa por vez y corresponde a la estructura más elegida por los primeros compiladores.




2) Pero cuando apareció windows y los entornos visuales con posibilidad de establecer multitareas (ejecutar más de un programa por vez), se hizo necesario crear estructuras de soporte porque los entornos y sistemas operativos comenzaron a trabajar de un modo diferente para permitir la ejecución multi-hilo.

En un entorno visual como windows en sus versiones 1.0 a 3.11 o incluso a sus versiones windows 95,98 y ME, la ejecución de tareas se realiza de un modo diferente: los entornos visuales son en realidad programas que se ejecutan en bucles infinitos hasta que el usuario decide terminar la ejecución del entorno (apagar windows) y se requiere que por cada aplicación ejecutada por el usuario (ventana) se abra un proceso que debe ser controlado por el bucle sin fin del sistema operativo, inaugurando así el proceso de multitarea. Es entonces que los compiladores incorporan el bucle MAIN, que deberá ser enlazado al bucle main del sistema operativo.

Si bien Qbasic trabaja con la primer estructura, no hay ningún inconveniente en la posibilidad de crear una estructura de bucle sin fin para la ejecución de nuestros programas de ejemplo.

La estructura principal
 
El programa que vamos a construir ahora comienza por:

Un bloque de instrucciones en el que se deben declarar las funciones, subrutinas y las variables empleadas

Un segundo bloque de instrucciones para dibujar la pantalla

Una estructura do-loop que generará el bucle infinito controlado que en su interior tendrá instrucciones para controlar los eventos y permitirá salir controladamente según la decisión del usuario

La idea general del primer programa es

1) Crear un programa en baja resolución (solo texto) para nuestro juego. Luego avanzaremos sobre programas gráficos, pero en esta etapa inicial veremos como se trabaja en una pantalla de baja resolución con técnicas que se usan también en entornos gráficos, pero que en definitiva son iguales, con la diferencia de cantidad de información que se procesa. En pantalla de caracteres la cantidad de información procesada es mínima.

2) Permitirle al usuario controlar con las teclas del cursor una carácter que podra mover libremente por la pantalla como si fuera un pequeño sprite. La técnica que emplearemos será la misma que permite controlar objetos de gran tamaño o gráficos, pero iniciando ahora con una muy pequeña cantidad de información que debe ser controlada y procesada.

3) Definir las áreas por las que el pequeño sprite-carácter puede moverse libremente. Para ello delimitaremos un tamaño en la pantalla, que deberá respetar el sprite-carácter y los controles del usuario.

4) Mover el sprite-carácter y verificar que todo se haga del modo correcto.

Bloque de declaraciones:
 
Variables c, f
Variables c y f enteras (fila y columna) que permiten saber cual es la posición actual del sprite-carácter en pantalla. Estas variables deben ser monitorizadas permanentemente por el programa para saber si se intenta salir de los límites de la pantalla que se han definido para el movimiento del sprite-carácter.

Se inicia el programa con posición en fila12, columna 40

DIM c AS INTEGER
DIM f AS INTEGER
c = 40
f = 12

Variable KeyPress
Variable KeyPress string que me indica en todo momento la tecla que el usuario ha presionado. El bucle verificará si es una tecla válida y procesará la acción cuando corresponda. En esta primer versión de nuestro juego, las teclas válidas con las cuatro teclas de cursos (para movimientos del usuario), y las teclas ESC (escape) y S (para salir) que facilitan que el juego termine.

DIM KeyPress AS STRING

Constantes MnFila, MxFila, MnCol, MxCol
La pantalla de baja resolución de texto está limitada a las columnas y filas definidas en estas variables. Con ellas, delimitamos el tablero de juego en la pantalla. Todos los movimientos de pantalla deben controlar que estos límites no sean excedidos por los movimientos del sprite-carácter

CONST MnFila = 3
CONST MxFila = 20
CONST MnCol = 1
CONST MxCol = 80



      Variables string dimensionadas Screen$(4 TO 19) 
    Es un array de variables que contienen el dibujo de la pantalla para la zona del tablero.
    Inicialmente no tienen contenido, pero en otras versiones de este mismo programa, agregaremos elementos para que colisione el sprite-carácter.

   Abarcan un total de 16 filas de caracteres que van desde la columna 1 a la 80.


·   Variable aux integer 
    Es una variable auxiliar que se usa en el bucle for-next para imprimir la pantalla del juego con las variables Screen$(x)



   '=================================================================================

'Bloque de pantalla

'Sección que define las variables que contienen el dibujo de la pantalla (Screen$())

'y dibuja la pantalla inicial del programa  '=================================================================================


   'Dibujar pantalla inicial

   CLS

   LOCATE 1, 1

   PRINT "Nuestro primer programa de juegos"

   LOCATE 2, 1

   PRINT "--------------------------------------------------------------------------------"

 

   Screen$(4) = "                                                                                "

   Screen$(5) = "                                                                                "

   Screen$(6) = "                                                                                "

   Screen$(7) = "                                                                                "

   Screen$(8) = "                                                                                "

   Screen$(9) = "                                                                                "

   Screen$(10) = "                                                                                "

   Screen$(11) = "                                                                                "

   Screen$(12) = "                                                                                "

   Screen$(13) = "                                                                                "

   Screen$(14) = "                                                                                "

   Screen$(15) = "                                                                                "

   Screen$(16) = "                                                                                "

   Screen$(17) = "                                                                                "

   Screen$(18) = "                                                                                "

   Screen$(19) = "                                                                                "

 

   LOCATE 21, 1

   PRINT "--------------------------------------------------------------------------------"

   LOCATE 23, 1

   PRINT "-----------------------------------Presiona [ESC] o [S] para salir del juego----"

 

   'Imprimir fondo de pantalla

   FOR aux = MnFila + 1 TO MxFila - 1

      LOCATE aux, 1

      PRINT Screen$(aux)

   NEXT aux

  
 '=================================================================================

   'Bloque del bucle infinito contrtolado del programa

   'Sección que el bucle do-loop que permite trabajar al programa hasta que el usuario

   'presiona la tecla S o ESC     
 '=================================================================================


   'Bucle principal del programa

   DO



      '===============================================================

      'detectar la tecla presionada

      KeyPress = UCASE$(INKEY$)  'espera por la tecla presionada por el usuario y la  
                                                         'asigna a KeyPress
    
      'observe las rutinas de detecci¢n de teclas detenidamente:

      'cada vez que el usuario decide mover el caracter-sprite elegido,

      'el programa hace lo siguiente:

      '

      ' 1.Valida el movimiento del usuario para asegurarse que el

      '   ese movimiento no se salga de los limites definidos del

      '   escenario (Constantes MnFila, MxFila, MnCol y MxCol)

      '   Esto se define en cada doble condicion AND que debe

      '   cumplirse para que el programa realice el movimiento:

      '

      '          IF KeyPress = CHR$(0) + CHR$(72) AND f > MnFila THEN

      '

      ' 2.Si el movimiento se realiza dentro de los limites del

      '   escenario, el programa procede a borrar la presencia

      '   actual del caracter-sprite en la pantalla posicionando

      '   al cursos en la fila y columna actual para eliminar la

      '   presencia del caracter [+]:

      '

      '            LOCATE f, c    'se posiciona en pantalla

      '            PRINT " "      'borra el carater

      '

      ' 3.Una vez borrada la pantalla, ahora deber  calcular la nueva

      '   posici¢n del caracter-sprite para dibujarla en la pantalla

      '   porque en caso contrario, el caracter-sprite desapareceria

      '   de la pantalla. Esto se hace con las lineas:

      '

      '            f = f - 1   'al mover arriba

      '            f = f + 1   'al mover abajo

      '            c = c - 1   'al mover a la izquierda

      '            c = c + 1   'al mover a la derecha

      '

      '   con esto se concreta el bloque pirncipal de calculo para

      '   cada tecla seleccionada que se evalua con la doble condicion

      '   IF+AND para cada caso.

      '

      ' 4.Finalmente, al haberse validado y calculado el movimiento y

      '   adem s de borrar la presencia del caracter-sprite, se debe

      '   proceder a redibujar al caracter-sprite en la nueva posicion

      '   en pantalla. Esto se hace fuera de las condiciones IF

      '   Vaya al final de la condicion IF (por debajo del END IF)

      '   para ver este 4 paso final

      '

      '===============================================================

      'si la tecla presionada es una tecla de cursor...

      IF KeyPress = CHR$(0) + CHR$(72) AND f > MnFila THEN

         'Tecla de cursor: Movimiento arriba

         'solo entra a estas instrucciones si la fila actual es mayor que el límite minimo

         'de la fila de pantalla definida. Por eso se ha colocado el AND. Se deben cumplir

         'las dos condiciones:

         '1.se presiono la tecla fecha arriba

         '2.a la variable f se le puede restar una unidad sin que sea menor a MnFila



         LOCATE f, c    'se posiciona en el lugar actual del caracter-sprite

         PRINT " "      'refresca la pantalla borrando el caracter-sprite actual

         f = f - 1      'mueve al caracter-sprite una linea hacia "arriba"

 

      ELSEIF KeyPress = CHR$(0) + CHR$(80) AND f < MxFila THEN

         'Tecla de cursor: Movimiento  abajo

         'solo entra a estas instrucciones si la fila actual es menor que el límite máximo

         'de la fila de pantalla definida. Por eso se ha colocado el AND. Se deben cumplir

         'las dos condiciones:

         '1.se presiono la tecla fecha abajo

         '2.a la variable f se le puede sumar una unidad sin que salga del valor de MxFila



         LOCATE f, c    'se posiciona en el lugar actual del caracter-sprite

         PRINT " "      'refresca la pantalla borrando el caracter-sprite actual

         f = f + 1      'mueve al caracter-sprite una linea hacia "abajo"



      ELSEIF KeyPress = CHR$(0) + CHR$(75) AND c > MnCol THEN

         'Tecla de cursor: Movimiento izquierda

         'solo entra a estas instrucciones si la columna actual es mayor que el límite mínimo

         'de la columna de pantalla definida. Por eso se ha colocado el AND. Se deben cumplir

         'las dos condiciones:

         '1.se presiono la tecla fecha izquierda

         '2.a la variable c se le puede restar una unidad sin que su valor sea menor a MnCol



         LOCATE f, c    'se posiciona en el lugar actual del caracter-sprite

         PRINT " "      'refresca la pantalla borrando el caracter-sprite actual

         c = c - 1      'mueve al caracter-sprite una columna hacia la "izquierda"



      ELSEIF KeyPress = CHR$(0) + CHR$(77) AND c < MxCol THEN

         'Tecla de cursor: Movimiento derecha

         'solo entra a estas instrucciones si la columna actual es menor que el límite máximo

         'de la columna de pantalla definida. Por eso se ha colocado el AND. Se deben cumplir

         'las dos condiciones:

         '1.se presiono la tecla fecha derecha

         '2.a la variable c se le puede sumar una unidad sin que su valor exceda MxCol



         LOCATE f, c    'se posiciona en el lugar actual del caracter-sprite

         PRINT " "      'refresca la pantalla borrando el caracter-sprite actual

         c = c + 1      'mueve al caracter-sprite una columna hacia la "derecha"



      '===============================================================

      'si la tecla presionada es ESC (escape)

      ELSEIF KeyPress = CHR$(27) THEN EXIT DO 'si presiona ESC sale del bucle y   
                                                                                  'termina el programa

      END IF



      '===============================================================

      'si la tecla presionada no es ninguna de las programadas, sigue

      'actualizando los datos de informacion de pantalla



      LOCATE 1, 1

      PRINT "Puntos"; Puntos; "   "

      LOCATE 22, 1

      PRINT "Posicion Fila"; f; " Columna"; c; "   "



      'Dibuja nuestro 'caracter-sprite'

      LOCATE f, c

      PRINT "+"



   LOOP UNTIL KeyPress = "S"



   END



  Cuando corra el programa, verá que el movimiento del carácter-cursor es muy fluído y no 
  requiere de una gran carga para el microprocesador.




  ---------------------------------------------------------------------- 
  Curso Gratuito de Programación de VideoJuegos

  1. Introducción

  2. A quienes va dirigido el curso
  3.Temario del curso / Herramientas
  4.Primer programa (codigo fuente)    

  5.Detectando colisiones y comiendo objetos
  6.Eliminando el bug de nuestro programa ----------------------------------------------------------------------



http://profesorponce.blogspot.com.ar/p/libros-publicados.html