Trucos sobre manejo de softwarelibre y gnu linux. También a los programas que hago en gambas3, python, java, etc.... Consejos y trucos sobre otros programas: excel, word, internet, instalación de programas, Arduino y demás....
Muchos de los que empezaron con la programación era por el interés que les desperto los videojuegos, y la idea de hacer sus propios juegos.
He encontrado un libro gratuito llamado Programacion de videojuegos con sdl para windows y linux (os lo podeis descargar pulsando en el enlace), donde el autor Alberto Garcia Serrano, explica como realizar varios juegos, detallando los pasos a seguir, esquemas, y codigos fuentes.
Os recomiendo su lectura para los que os querrais iniciaros o simplemente, tengais curiosidad en como se hacen los videojuegos.
El primer ejemplo del libro lo he pasado a Gambas 3.1.1, ya que los ejemplos del libro estan en C, no os desanimeis por ello, ya que no resulta complicado entender los ejemplos del libro, porque el autor los comenta muy detalladamente.
Os podeis descargar el código fuente en Gambas, de mi versión, en este enlace: EnlaceEjecutable en Gambas
Se trata de un juego tipo texto, donde tienes que moverte por las habitaciones de una mansión cogiendo objetos y usandolos, escribiendo las ordenes en un textbox.
He añadido una mejora, para que las habitaciones y objetos se coloquen aleatoriamente cada vez que jugamos. (haciendo que el juego nunca se repita :) )
Para ello, genero una lista que contiene un entero que indica el indice del array que va a contener cada habitación , y la desordeno aleatoriamente, obteniendo una disposicion siempre diferente ya que la llamo cada vez que se inicia el juego:
PublicFunctionGeneraOrdenHabitaciones()AsString[]
DimlistaAsNewInteger[] DimaAsInteger Dimobtener1AsInteger Dimobtener2AsInteger DimtemporalAsInteger 'creo
la lista Fora=0To9 lista.Add(a) Next
'barajo
la lista Fora=1To50'barajo 50
veces 'obtengo
nuevas posiciones... obtener1=Int(Rnd(1,10))
obtener2=obtener1'hacemos
esto para que el bucle se ejecute por lo menos una vez While(obtener1=obtener2) obtener2=Int(Rnd(1,10)) Wend
Next Print"Lista
obtenida de habitaciones: " Fora=1To9 Printlista[a];" "; Next Returnlista
End
Esta funcion, devuelve una lista, que usare como el indice del array de habitaciones.
Todo el programa sigue el paradigma de programación estructural, son sus limitaciones y complicaciones para posteriores ampliaciones (algo muy tipico en los juegos..).
En una próxima entrega, realizaré este mismo juego basandome en el paradigma de Programación Orientada en Objetos, donde os mostraré que beneficios trae a la hora de programar y podreis aprecias las diferencias y beneficios (por ejemplo, sera muy facil añadir más objetos, ordenes, etc) al juego.
Es habitual que cuando llamemos a un formulario que necesitemos la información que haya introducido el usuaria para usarla en el programa principal.
Os voy a explicar un método para hacer esto usando el paradigma de programación orientada a objetos (POO). (ver nota 1)
Bien, imaginaos que tengamos dos formularios:
a) El formulario Fmain, donde esta el programa principal
b) El formulario FormIntroduceDatos, que es el encargado de pedirle al usuario que introduzca sus datos, por ejemplo nombre de usuario y clave.
El programa principal necesita en algún momento de su ejecución que el usuario introduzca su nombre y clave, y que según esos datos le permita continuar o no en el programa. Por lo tanto necesitamos que nos devuelva información el formulario FormIntroduceDatos al programa principal para operar con ella.
¿como lo hacemos?
El formulario FormIntroduceDatos, necesita tener una referencia del programa principal (formulario Fmain), para informale de los datos recibidos. Para ello le vamos a crear una propiedad que sea una referencia al objeto que le ha llamado.
Vamos a ver como sería el código del FormIntroduceDatos:
'
Gambas class file
PropertyrefAsObject'creamos una
propiedad ref, que va a contener la referencia del objeto que le llama
PrivatehrefAsObject' href, es
la forma de trabajar con ref, de forma privada dentro de la clase
PublicSubForm_Open()
End
PrivateFunctionref_Read()AsObject
Returnhref'retornamos
ref
End
PrivateSubref_Write(ValueAsObject)
href=value'asignamos a
ref
End
PublicSubToolButtonCancelar_Click()
Me.close 'cerramos el formulario
End
PublicSubToolButtonAceptar_Click()
'Hemos
pulsado Aceptar, los datos introducidos los vamos a asignar al
objeto de referencia
href.nombre=TextBoxNombre.Text
href.password=TextBoxPassword.text
'ya
hemos pasados los datos al programa principal....
'De esta forma hemos conseguido "recuperar", la información del formulario antes de cerrarlo
'cerramos
el formulario
Me.close
End
Ahora vamos a ver como llamamos al formulario FormIntroduceDatos, en el programa principal:
Property
nombre
As String 'propiedad que contiene el nombre de usuario
Private
hnombre
As String
Property
password
As String 'propiedad que contiene la contraseña
Private
hpassword
As String
Private
Function nombre_Read()
As String
Return
hnombre
End
Private
Sub nombre_Write(Value
As String)
hnombre
= Value
End
Private
Function password_Read()
As String
Return
hpassword
End
Private
Sub password_Write(Value
As String)
hpassword
= value
End
'aqui empieza lo interesante....
Public
Sub ToolButtonIntroduce_Click()
Dim
f
As New FormIntroduceDatos
'creamos
una instancia del FormIntroduceDAtos
f.ref
= Me
'a
la propiedad que tiene nuestra instancia (.ref) le vamos a asignar
la referencia del objeto actual (osea el programa principal,
formulario Fmain)
f.ShowDialog
'mostramos
el formulario y esperamos a que se cierre
'aqui
vamos a ver los datos que se han actualizado de nuestras variable
privadas hnombre y hpassword
TextLabelDatos.text
= "El
nombre de usurio es <u>"
&
hnombre
& "
</u><br> y su contrasena es <u>"
& hpassword
& "
</u>"
Nota 1:
Podríamos usar variables globales (definir en un módulo una variable publica, y usarla donde queramos). A la larga este método nos traerá problemas al querer reutilizar el código, ampliar el programa, etc...
Esta vez, vamos a hacer el paso contrario: Pasar "ordenes" desde Gambas a Arduino, y que este las interprete y ejecute lo que le estemos diciendo (mueva el motor indicado una serie de grados).
Para ello la tramo de datos, (protocolo) es la siguiente:
12333
siendo:
1: Letra que indica cual dispositivo es sobre hay que actuar. Usamos la "M" para indicar servomotor
2: Numero que indica cual de los dispositivos vamos a mover. (si hay varios servomotores, el 0, sera el primero, el 1 será el segundo... etc, el limite son 10 (de 0 a 9)
333: Indica los grados que se van a girar los servomotores.
Por ejemplo, si le mandamos desde gambas a arduino este comando:
M0180 -> Girara, el servomotor (M), nº 0, 180º grados.
Para facilitar al usuario, esta labor, lo que hacemos es usar unos botones gráficos tipo "DIAL"
y el programa en Gambas, crea y manda las ordenes a Arduino, que se encarga de leer la orden, interpretarla y ejecutarla.
Aqui teneis el montaje:
Nota: se usan los puertos digitales (PWM) para conectar los servomotores con arduino. Es importante no usar el pin nº 0 ni el pin nº 1, ya que ambos se usan para la comunicación en Serie-USB con el ordenador.
Aqui teneis el código del programa en Gambas:
'
Gambas class file
PublicsportAsNewSerialPort
PublicconectadoAsBoolean
PublicmensajeAsString
PublicSub_new()
End
PublicSubForm_Open()
Me.Center
conectado=False
End
PublicSubToolButton1_Click()
Ifconectado=FalseThen
IfSport.Status=Net.ActiveThen
CloseSport
Else
sport.PortName="/dev/ttyUSB0"'esto puede
cambiar segun al puerto al que conectes tu arduino
sport.speed=9600
'tiene que
ser la misma velocidad que tenga el programa de arduino.
sport.Parity=SerialPort.None
sport.DataBits=SerialPort.Bits8
sport.StopBits=SerialPort.Bits1
sport.FlowControl=SerialPort.None
Trysport.Open()
IfErrorThen
Message.Error("Error
al intentar conectar con puerto: "&sport.PortName)
conectado=False
Else
'le
mando el comando "S" para que me conteste con los
sensores que me va a devolver...
TryPrint#sport,"S"
IfErrorThen
Message.Error("Error
al intentar enviar datos...")
Print"No he
podido conectarme"
conectado=False
Else
Print"ok...Conectado"
ToolButton1.picture=Picture["icon:/16/stop"]
conectado=True
mensaje="M00"
EscribeEnArduino(mensaje)
Endif
'creo
las clases para graficar los datos que voy a recibir
Endif
Endif
Else
conectado=False
ToolButton1.picture=Picture["icon:/16/play"]
CloseSport
Endif
End
PublicSubDialServo1_Change()
LCDNumber1.value=DialServo1.Value
'mortor
0, lo giro lo que indique el LCDNumber1
mensaje="M0"&Str$(LCDNumber1.Value)
EscribeEnArduino(mensaje)
End
PublicSubDialServo2_Change()
LCDNumber2.value=DialServo2.Value
'mortor
1, lo giro lo que indique el LCDNumber1
mensaje="M1"&Str$(LCDNumber2.Value)
EscribeEnArduino(mensaje)
End
PublicSubDialServo3_Change()
LCDNumber3.value=DialServo3.Value
DialServo1.Value=LCDNumber3.value
DialServo2.Value=LCDNumber3.value
'mortor
1, lo giro lo que indique el LCDNumber1
mensaje="M0"&Str$(LCDNumber3.Value)
EscribeEnArduino(mensaje)
Wait0.1
mensaje="M1"&Str$(LCDNumber3.Value)
EscribeEnArduino(mensaje)
End
PublicSubEscribeEnArduino(mensajeAsString)
TryPrint#sport,mensaje
IfErrorThen
Message.Error("Error
al intentar enviar datos...")
Print"No he
podido conectarme"
Endif
PrintMENSAJE
End
Y el código de Arduino:
#include<Servo.h>
constintTotalServos=2;// indico el
numero de servos que tengo conectados
Servoservo[TotalServos];//defino un
array de N elementos de instancia de la clase Servo
intpos[TotalServos];// guarda
posicion de los servos
bytea;// contador
voidsetup()
{
Serial.begin(9600);
Serial.println("Arduino
conectado...");
Serial.println("
a la espera de ordenes");
//
Definos los pin donde ira conectado cada servo: TotalServos
//0=Nº
del motor (si recibo N entoces son todos los motores)
//180=
grados de giro..
intdato;// leo
bytecontador=0;
if(Serial.available()){
dato=Serial.read();
Serial.println("Recibido:
");
Serial.println(dato);
if(dato==83){
Serial.println("Recibido
S");
}
if(dato==77)
{
//
entrada de orden de motor
Serial.println("Recibido
M");
dato=Serial.read();
vermotor(dato);
}
//
entrada para ampliar a otras ordenes
//
… (para añadir mas sensores... por ejemplo)
}
}
voidvermotor(intdato){
//
convierto el caracter en numero (que indicara el servo a mover)
intvalor=0;
intb=0;
if(dato==78){
Serial.println("Recibido
N");
Serial.print("Todos
los motores los muevos grados: ");
valor=recibirnumero();
Serial.println(valor);
//
coloco a los servos al valor que me han pasado...
for(b=0;b<=TotalServos-1;b++)
{
pos[b]=valor;
Serial.println("Servo
(");
Serial.println(b);
Serial.println("):
");
Serial.println(valor);
servo[b].write(valor);
}
}
if((dato>=48)&&(dato<=57)){
Serial.println("escribo
en motor");
movermotor(dato);
}
}
intrecibirnumero()
{
booleanentrada=true;
charcadena[24];
intn;
a=0;
while(Serial.available()>0)
{
//
delay(5);
cadena[a]=Serial.read();
a++;
}
cadena[a]='\0';
Serial.println("En
letra :");
Serial.println(cadena);
n=atoi(cadena);
Serial.println("En
numero :");
Serial.println(n);
returnn;
}
voidmovermotor(intdato){
intvalor;
Serial.print("Muevo
el motor (");
Serial.print(dato-48);
Serial.print("):
");
valor=recibirnumero();
Serial.println("valor:
");
Serial.println(valor);
pos[dato-48]=valor;
servo[dato-48].write(valor);
delay(150);
dato=0;
}
Como veis tiene varias subrutinas (vermotor, movermotor y recibirnumero), que ayudan a que sea más claro el programa.
Lo que me costó más trabajo, fue la lectura de las ordenes del puerto serie, ya que Arduino lee caracteres, o mejor dicho la conversión a ASCII de ellos, por eso cuando analizo la orden, no analizo caracteres sino números:
if(dato==78){
Serial.println("Recibido N");
N en ASCII es el nº 78,
Además teneia que "unir" los caracteres que formaban el nº que indica los grados a girar, eso lo solucioné usando un array del tipo Char, como podeis ver en el código de la subrutina recibirnumero.
Os dejo un video donde muestro como se usa el programa:
Os dejo los enlaces de descarga del cógido fuente de los dos programas, ya que el "copiar y pegar" os puede dar muchos problemas.