Alters

A buscar M!nas: MSAccess - preámbulo

Buenas de nuevo!

Como os dije, empezamos el tema de MSAccess.

La idea es crear una pequeña base de datos para mejorar la experiencia de juego de nuestro recién creado juego.

A diferencia de la primera parte, a partir de este punto yo no tengo nada hecho, así que iré haciendo las cosas sobre la marcha.

El planning para estas entradas es el siguiente:

MSAccess


  • Análisis
    • Requerimientos (textual)
    • Modelo entidad - relación
  • Diseño
    • Modelo relacional
      • PRETECAR
        • PRETECAR 1
        • PRETECAR 2
      • RETECAR
        • RETECAR 1
        • RETECAR 2
        • RETECAR 3
        • RETECAR 4
        • RETECAR 5
        • RETECAR 6
        • RETECAR 7
    • Normalización
      • Conceptos previos
        • Terminología
        • Dependencias funcionales
      • Normalizando:
        • 1FN
        • 2FN
        • 3FN
        • FNBC
        • 4FN
        • 5FN
  • Programación
    • SQL
    • Ampliando VB.net
      • ADO.net
      • Diseño de consultas
      • Hacer que funcione
    • Gestión desde MSAccess
      • Diseño de pantallas
      • Creando las pantallas
  • Update exe (link)
Como véis, en esta serie de entregas me dedicaré también un poco a profundizar más en ciertos conceptos teóricos; no iré tan al meollo de la cuestión. Esto me servirá para poder llevar al día los proyectos (es decir,  no tendré que improvisar tanto o estar días y días sin publicar nada) y también me vendrá bien para repasar la teoría del Grado Superior de Desarrollo de Aplicaciones Informáticas.

Una anotación: es posible que aquellos que conozcáis las técnicas de normalización no os suenen de nada las  "RETECAR" u otras cosas. Llegado su momento veréis que igual las conocéis con otro nombre... a mí me lo enseñaron así, y así es como os lo quiero enseñar yo a vosotros :-P

En fin... como esta entrada es bastante larga, empezaré con el primer punto en la próxima entrada.

Saludos a todos y...

¡Hasta la próxima!

Varios

Buenas a todos de nuevo!

Esta entrada será un poco de "bypass". Os haré un poco de resumen de las próximas entradas y os comentaré algunas cosas de más.

Primero las novedades; hoy me han comprado el teclado de mis sueños:

Razer Black Widow Elite

Os dejo aquí una imagen de la maravilla.

Mi nuevo teclado
Un teclado como cualquier otro, podéis pensar... la cosa es que quizás lo parece, pero no.

¿Qué tiene de bueno?

 - Sistema de tecleo mecánico: nunca me lo había pasado tan bien tecleando... es totalmente diferente a los teclados convencionales

 - Las teclas reaccionan a una fuerza de 50g: solo hace falta eso, un pequeño empujón de 50g para pulsar una tecla. Ahora escribo a la velocidad de la luz (jejeje)

 - Teclas iluminadas: ya no tendré que encender la luz para ver las teclas

 - Captura macros "On-the-fly": le puedes dar a "grabar", ejecutar un comando de teclas y asignarlo a una tecla. Y ya está!!

 - 5 Teclas de función adicional: cuenta con 5 teclas en el lateral. Son 100% configurables (en realidad TODAS las teclas son configurables)

 - Acceso a multimedia: puedes poner pause, play, subir y bajar el volumen... desde el teclado.

 - Modo gamer: con esta función puedes desactivar la tecla de inicio (a quién no le ha pasado...)

 - Plugs de audio, micro y USB en el lateral: pues eso... me he quedado a cuadros!

El único "inconveniente" es que el layer es el estándar de USA, por lo que no tengo el carácter ">", pero lo he asignado a una tecla de función :-P.

En fin... desde aquí darle las gracias a mi chica ( <3) por regalármelo.

Más novedades: he visto (por mediafire y google analytics) que la gente empieza a seguir mi blog, incluso se descarga los archivos que subo... ha sido una MUY MUY GRATA sorpresa verlo.

Desde este rincón de mi casa (de donde os escribo siempre) me gustaría agradeceros a todos los que habéis visto alguna entrada de mi blog, o lo seguís.

Bien bien bien... y ahora a por el tema de las próximas entradas. El tema está animado. Tuve una pequeña idea, así que os comento:

Por ahora tenemos visto el tema de crear el juego de las M!nas con VB.net. ¿Qué será lo siguiente? Os dejo una lista de "mini-proyectos" que se verán en próximas entradas para mejorar el juego.

 - MSAcces: crearemos una pequeña base de datos con Access para nuestro juego. La idea es poder almacenar usuarios (es decir, emular que hay usuarios "registrados" que juegan y puntúan).

Sobre estos usuarios guardaremos muy poca información personal: un email, nombre, nick y password.

El tema está en que haremos estadísticas globales sobre el juego (mejores jugadores, tiempos por nivel, porcentaje de aciertos/fallos...) y también haremos una utilidad que permita seleccionar imágenes personales para cada usuario.

 - COBOL: le sacaremos jugo al mini-curso de COBOL, y trasladaremos la base de datos a ficheros COBOL. Esto le dará mucho más rendimiento al juego a la hora de tratar datos, y servirá de refuerzo para nuestras lecciones de COBOL.

 - PHP: intentaré coger un pequeño hosting (de esos gratuitos) y haré un pequeño portal web. Ahí subiré el ejecutable completo, para que lo descarguéis. También tendrá un portal de acceso para usuarios registrados (ya no estarán en la base de datos de manera local) con sus típicas opciones; modificar datos, conectar con FB, ver los mejores jugones, galería de imágenes...

Como veis, es un proyecto ambicioso, ya que no se basa solo en el típico "vamos a hacer un programa", sino que lo haremos evolucionar.

Cómo se distribuirá cada apartado es algo que todavía no he terminado. Desde luego, antes de cada lote de entradas haré una entrada de "bypass" (como esta) para introducir a la siguiente fase.

Espero que el planning sea de vuestro agrado. Recordad que siempre podéis dejar comentarios para cualquier duda o sugerencia.

Y, una vez más, y como digo siempre...

¡Hasta la próxima!

A buscar M!nas: programación

Buenas!

Entramos en la recta final de la parte de VB.net; en esta entrada programaremos "a saco". Al final de ésta os dejaré un link para que podáis bajaros el proyecto y probar.

Os dejaré el código sin más. Este código es la "traducción" de los algoritmos previamente explicados con pseudo-código (hay un par de modificaciones, pero son poca cosa).

Dividiré los bloques por archivos, para que se lea un poco mejor.

BuscaminaX.vb


Public Class BuscaminaX
    Public time As Integer

    Private Sub BuscaminaX_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Diff.ShowDialog()

        If Global_Var.getGen.Equals(0) Then
            Me.Close()
        Else
            BX_module.Cargar()
            BX_module.Dibujar()
            Me.Timer1.Enabled = True
        End If
    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Application.DoEvents()

        Me.time += 1

        Me.ToolStripStatusLabel1.Text = "Time: " + Me.time.ToString()
        Me.ToolStripStatusLabel2.Text = "Mines: " + BX_module.minas.ToString()
    End Sub
End Class


Diff.vb

Public NotInheritable Class Diff
    Private Sub Diff_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Me.Button1.Enabled = False
    End Sub

    Private Sub RadioButton1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged, RadioButton2.CheckedChanged, _
                 RadioButton3.CheckedChanged, RadioButton4.CheckedChanged, _
                 RadioButton5.CheckedChanged
        Dim en As Boolean = False

        If (Me.RadioButton1.Checked Or Me.RadioButton2.Checked) And _
        (Me.RadioButton3.Checked Or Me.RadioButton4.Checked Or Me.RadioButton5.Checked) Then
            en = True
        End If

        Me.Button1.Enabled = en
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim gen As Integer = 1
        Dim dif As Integer = 0

        If Me.RadioButton2.Checked Then
            gen = 2
        End If

        If Me.RadioButton4.Checked Then
            dif = 1
        ElseIf Me.RadioButton5.Checked Then
            dif = 2
        End If

        Global_Var.setDif(dif)
        Global_Var.setGen(gen)

        Me.Close()
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Me.Close()
    End Sub
End Class



Global_var.vb

Module Global_Var
    Private dif As Integer
    Private gen As Integer
    Private min As Integer
    Private anc As Integer
    Private alt As Integer
    Private img As Integer
    Private anB As Double
    Private alB As Double
    Private fil As Integer
    Private col As Integer
    Private pos(99) As Integer
    Private coM() As Integer
    Private imF As Image

    Public Sub setDif(ByVal pDif As Integer)
        dif = pDif
    End Sub

    Public Sub setGen(ByVal pGen As Integer)
        gen = pGen
    End Sub

    Public Sub setMin(ByVal pMin As Integer)
        min = pMin
    End Sub

    Public Sub setAnc(ByVal pAnc As Integer)
        anc = pAnc
    End Sub

    Public Sub setAlt(ByVal pAlt As Integer)
        alt = pAlt
    End Sub

    Public Sub setImg(ByVal pImg As Integer)
        img = pImg
    End Sub

    Public Sub setAnB(ByVal pAnB As Double)
        anB = pAnB
    End Sub

    Public Sub setAlB(ByVal pAlB As Double)
        alB = pAlB
    End Sub

    Public Sub setFil(ByVal pFil As Integer)
        fil = pFil
    End Sub

    Public Sub setCol(ByVal pCol As Integer)
        col = pCol
    End Sub

    Public Sub setPos(ByVal pPos() As Integer)
        pos = pPos
    End Sub

    Public Sub setCoM(ByVal pCoM() As Integer)
        coM = pCoM
    End Sub

    Public Sub setImF(ByVal pImF As Image)
        imF = pImF
    End Sub

    Public Function getDif() As Integer
        Return dif
    End Function

    Public Function getGen() As Integer
        Return gen
    End Function

    Public Function getMin() As Integer
        Return min
    End Function

    Public Function getAnc() As Integer
        Return anc
    End Function

    Public Function getAlt() As Integer
        Return alt
    End Function

    Public Function getImg() As Integer
        Return img
    End Function

    Public Function getAnB() As Double
        Return anB
    End Function

    Public Function getAlB() As Double
        Return alB
    End Function

    Public Function getFil() As Integer
        Return fil
    End Function

    Public Function getCol() As Integer
        Return col
    End Function

    Public Function getPos() As Integer()
        Return pos
    End Function

    Public Function getCoM() As Integer()
        Return coM
    End Function

    Public Function getImF() As Image
        Return imF
    End Function
End Module


BX_module.vb

Imports System.IO

Module BX_module
    Public minas As Integer
    Private cuadros As Integer

    Public Sub Cargar()
        Dim min As Integer = 10
        Dim fil As Integer = 9
        Dim col As Integer = 9
        Dim anc As Integer
        Dim alt As Integer
        Dim img As Integer
        Dim anB As Double
        Dim alB As Double
        Dim pos(99) As Integer
        Dim res() As Integer
        Dim imF As Image
        Dim Random As New Random()

        img = Random.Next(0, 12)

        If Global_Var.getGen = 2 Then
            img += 12
        End If

        imF = Image.FromFile(Application.StartupPath.ToString + "\BX_IMG\BX_" + img.ToString + ".jpg")

        anc = imF.Width
        alt = imF.Height

        If Global_Var.getDif = 1 Then
            min = 40
            fil = 16
            col = 16
        ElseIf Global_Var.getDif = 2 Then
            min = 99
            fil = 16
            col = 30
        End If

        anB = anc / col
        alB = alt / fil
        pos = LlenarMinas(fil * col, min)
        res = ComprobarMinas(fil * col, col, pos, min)
        minas = min
        cuadros = (fil * col) - min

        Global_Var.setMin(min)
        Global_Var.setFil(fil)
        Global_Var.setCol(col)
        Global_Var.setAnc(anc)
        Global_Var.setAlt(alt)
        Global_Var.setImg(img)
        Global_Var.setAnB(anB)
        Global_Var.setAlB(alB)
        Global_Var.setPos(pos)
        Global_Var.setImF(imF)
        Global_Var.setCoM(res)
    End Sub

    Private Function LlenarMinas(ByVal max As Integer, ByVal min As Integer) As Integer()
        Dim pos(99) As Integer
        Dim num As Integer
        Dim pass As Boolean
        Dim Random As New Random()

        For i = 0 To min - 1
            pass = True
            num = Random.Next(0, max + 1)

            For j = 0 To pos.Length - 1
                If pos(j) = num Then
                    pass = False
                End If
            Next

            If pass Then
                pos(i) = num
            Else
                i -= 1
            End If
        Next

        Return pos
    End Function

    Private Function ComprobarMinas(ByVal max As Integer, ByVal col As Integer, ByVal pos() As Integer, ByVal min As Integer) As Integer()
        Dim ret(max) As Integer

        For i = 0 To max - 1
            Dim num As Integer

            For j = 0 To min - 1
                If i Mod col = 0 Then
                    If pos(j) = i - col Or pos(j) = i - (col - 1) Or _
                       pos(j) = i + 1 Or _
                       pos(j) = i + col Or pos(j) = i + (col + 1) Then
                        num += 1
                    ElseIf pos(j) = i Then
                        num = 10
                        Exit For
                    End If
                ElseIf i Mod col = col - 1 Then
                    If pos(j) = i - (col + 1) Or pos(j) = i - col Or _
                       pos(j) = i - 1 Or _
                       pos(j) = i + (col - 1) Or pos(j) = i + col Then
                        num += 1
                    ElseIf pos(j) = i Then
                        num = 10
                        Exit For
                    End If
                Else
                    If pos(j) = i - (col + 1) Or pos(j) = i - col Or pos(j) = i - (col - 1) Or _
                       pos(j) = i - 1 Or pos(j) = i + 1 Or _
                       pos(j) = i + (col - 1) Or pos(j) = i + col Or pos(j) = i + (col + 1) Then
                        num += 1
                    ElseIf pos(j) = i Then
                        num = 10
                        Exit For
                    End If
                End If
            Next

            ret(i) = num
            num = 0
        Next

        Return ret
    End Function

    Public Sub Dibujar()
        Dim max As Integer = Global_Var.getFil() * Global_Var.getCol()
        Dim t As Double = 0
        Dim l As Double = 0
        Dim h As Double = Global_Var.getAlB
        Dim w As Double = Global_Var.getAnB
        Dim c As Integer = Global_Var.getCol
        Dim f As Integer = Global_Var.getFil
        Dim o As Label
        Dim p As Button
        Dim con(max * 2) As Control

        For i = 0 To max - 1
            o = New Label()
            p = New Button()

            o.Top = t
            o.Left = l
            o.Height = h
            o.Width = w
            o.Name = "L" + i.ToString
            o.Visible = True
            o.BackColor = Color.Transparent
            o.Font = New Font("Times new Roman", 10, FontStyle.Regular, GraphicsUnit.Pixel)

            p.Top = t
            p.Left = l
            p.Height = h
            p.Width = w
            p.Name = "B" + i.ToString
            p.Visible = True

            AddHandler p.MouseDown, AddressOf p_click
            AddHandler o.MouseEnter, AddressOf o_mouseEnter
            AddHandler o.MouseLeave, AddressOf o_mouseLeave

            con(i) = p
            con(i + max) = o

            If i Mod c = c - 1 Then
                l = 0
                t += h
            Else
                l += w
            End If
        Next

        BuscaminaX.Controls.AddRange(con)

        BuscaminaX.Height = Global_Var.getAlt() + 57
        BuscaminaX.Width = Global_Var.getAnc() + 13
        BuscaminaX.MaximumSize = BuscaminaX.Size
        BuscaminaX.MinimumSize = BuscaminaX.Size
        BuscaminaX.BackgroundImage = Global_Var.getImF()
        BuscaminaX.ToolStripStatusLabel1.Text = Global_Var.getMin.ToString
    End Sub

    Private Sub p_click(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
        If e.Button = MouseButtons.Left Then
            cuadros -= 1

            If cuadros = 0 Then
                MsgBox("Has ganado")
                BuscaminaX.Controls.Clear()
            End If

            sender.Visible = False

            If Global_Var.getCoM()(sender.name.SubString(1)) = 10 Then
                MsgBox("PUM!")
                Fail.ShowDialog()
            End If
        ElseIf e.Button = MouseButtons.Right Then
            If sender.text.Equals("X") Then
                sender.text = ""
                sender.backColor = Color.WhiteSmoke
                minas += 1
            Else
                sender.Text = "X"
                sender.backColor = Color.Red
                minas -= 1
            End If

            BuscaminaX.ToolStripStatusLabel1.Text = "Time: " + BuscaminaX.time.ToString()
            BuscaminaX.ToolStripStatusLabel2.Text = "Mines: " + minas.ToString()
        End If
    End Sub

    Private Sub o_mouseEnter(ByVal sender As Object, ByVal e As System.EventArgs)
        sender.BackColor = Color.White
        sender.text = Global_Var.getCoM()(sender.name.SubString(1))
    End Sub

    Private Sub o_mouseLeave(ByVal sender As Object, ByVal e As System.EventArgs)
        sender.text = ""
        sender.backcolor = Color.Transparent
    End Sub
End Module

Con esto queda plasmado todo el código respectivo al proyecto de VB.net

Os dejo un link de descarga para el proyecto completo. Recordad leer el "LEEME.TXT", y si compartís el paquete recordad ser buenos y citar el post original (este) y/o su autor orignial (yo).

http://www.mediafire.com/?8y7d8old8blrxh2

La próxima serie de entradas se basarán en una serie de ampliaciones del proyecto, usando MSAccess (por ahora...).

Saludos, y

¡Hasta la próxima!

A buscar M!nas: Conceptos previos

Buenas otra vez!

Esta es la primera entrada con respecto a la programación. Con este punto daremos por finalizado el tema de construir el juego usando VB.net.

Creo que me voy a arriesgar y haré el punto de conceptos previos como una sola entrada...

  • Conceptos previos
    • Estructuración de código
    • "Multi-handler"
    • Controles dinámicos (en tiempo de ejecución)
Estructuración de código

A la hora de programar con orientación a sucesos u orientación a objetos, es muy común la segregación de código, es decir, "aislar" el código por zonas o por utilidades.

Yo soy partidario de estas separaciones, de manera que, en VB.net me gusta tener separado el código propio de las llamadas a eventos de la función del evento. Para ello uso módulos, que son archivos planos destinados a estas mismas funciones.

En dichos módulos se "apilan" las funciones, como (por ejemplo) se pueden "apilar" en un include de PHP.

El comportamiento de los módulos es sencillo: llamas a una función o sub, se ejecuta, y retorna el control. También se pueden comportar como objetos, si así lo deseamos. ¿Cómo? Simplemente creando, en vez de un módulo, una clase. El tipo de archivo es el mismo (*.vb), pero cambian los encabezados del archivo.

Realmente no veo mucha diferencia entre un objeto simple (sin herencia ni nada similar) con un módulo bien definido, por lo que generalmente (siempre que no tenga que usar herencia o cosas más típicas de objetos) uso siempre módulos con estructura de objetos, es decir:

Module mi_modulo
    private var1 as String
    private var2 as Integer
    public var3 as Object

    public sub setVar1(byVal str as String)
        var1 = str
    End Sub

    public function getVar1() as String
        return var1
    End Function
End Module

Parece un objeto en su definición, pero es un simple módulo. A efectos prácticos básicos nos sirve igual.

Otra división que suelo hacer es la siguiente: en un módulo incluyo todas las variables "globales" que va a usar el programa, y en otro todas las funciones "globales".

En el primer módulo hago todas las variables privadas, y les creo "getters" y "setters", tal como sería en una OOP. El segundo módulo tiene partes privadas y partes públicas, por el tema del encapsulamiento.

Para dejar el asunto como zanjado, en este proyecto usaré la jerarquía que muestra la imágen "img1"

img1
Adicionalmente, para que todo quede completo, debéis dirigiros al directorio de la aplicación (BuscaminaX/BuscaminaX/bin/debug/) y crear el directorio "BX_IMG". Ahí tenéis que subir 25 imágenes, a saber:

 - 12 imágenes para la primera opción (a)
 - 12 imágenes para la segunda opción (b)
 - 1 imágen para el fallo (c)

La nomenclatura que yo uso es:

(a) BX_0 hasta BX_11
(b) BX_12 hasta BX_23
(c) Fail

En el SplashScreen, como fondo va la imágen "Fail". Esto creará un link en el explorador de soluciones (img1), en la carpeta "bin > debug> BX_IMG > Fail".

Multi-handler

El tema de los multi-handler consiste en asociar más de un evento a un mismo sub. En JAVA, usando AWT sería como asignar varios controles a un Listener.

En VB.net podemos hacerlo de una manera sencilla. Para ver cómo se consigue lo haremos mediante un ejemplo:

Supongamos que tenemos el siguiente evento genérico:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    'Cosas que hacer
End Sub

Revisemos la sintaxis:


  • Private Sub: crea un Sub, con visibilidad privada
  • Button1_Click: nombre genérico para el sub. Éste se crea mediante la fórmula nombreObjeto_Evento
  • ByVal sender As System.Object: primer parámetro llamado "sender", pasado por valor, como un Objeto
  • ByVal e as System.EventArgs: segundo parámetro llamado "e", pasado por valor, como un objeto
  • Handles Button1.Click: ¡OJO!, aquí está el meollo del asunto. Esta cláusula "junta" el evento Click de Button1 con este sub.
Ahora imaginad que tenemos un Button2, y queremos que se incluya en este sub. Sería tan sencillo como añadir "Button2.Click" al final de este sub.

Podemos añadir cualquier evento a un mismo Handles, siempre separados por comas. Pero, ¿De qué puede servir?

Bien, imaginad 10 buttons, llamados b0  a b9, respectivamente. Podríamos poner los diez Buttons en el mismo handler, y hacer algo como:

Me.TextBox1.Text += sender.substring(1)

Y automáticamente se va el número al TextBox. El otro aspecto del multi-handler es el parámetro "e".

"e" es un parámetro al que podemos acceder para saber la acción que se ha realizado sobre el objeto "sender". Un ejemplo es (sacado del juego):

If e.Button = MouseButtons.Left Then

Esta línea sirve para saber si es un "click" derecho o izquierdo.

Con esto queda por concluido el tema de multi-handler

Controles dinámicos (en tiempo de ejecución)

Crear controles en tiempo de ejecución es un alivio para todas esas circunstancias en las que no sabemos qué tendremos que generar (como en nuestro juego, ya que los controles varían dependiendo de la dificultad).

Crear un control no es demasiado difícil. El tema está en saber tratarlo, adecuarlo e incrustarlo en el panel que toca.

 o = New Label()

 o.Top = t
 o.Left = l
 o.Height = h
 o.Width = w
 o.Name = "L" + i.ToString
 o.Visible = True
 o.BackColor = Color.Transparent
 o.Font = New Font("Times new Roman", 10, FontStyle.Regular, GraphicsUnit.Pixel)

Esta parte debería sonar de la entrada anterior... Comentemos:

La Label se crea en la primera línea, llamando a su constructor. Adicionalmente se le cambian atributos, como el nombre, el ancho, el largo...

Hay un par de sentencias que son prácticamente obligatorias, que son el Visible = True, el Top y el Left.

No obstante, con estas líneas no se agrega al contenedor. Para ello podemos usar algo como

MiForm.Controls.Add(Control)

o

MiForm.Controls.AddRange(Array(Control))

Con esto estamos añadiendo el control (o controles) al formulario especificado.

Y, con este último apartado ha concluido el tema de los conceptos previos.

En la próxima entrega programaremos y os dejaré un link de descarga.

Hasta la próxima!

A buscar M!nas: Diseñando las pantallas del juego

Buenas de nuevo!

Ésta es la segunda entrega de diseño, y también la última. Hoy veremos qué tenemos que insertar en nuestro proyecto de VB.net, y en las próximas entradas nos pondremos a acabar nuestro juego.

Cuando acabe la parte de VB.net, empezaremos con una pequeña parte de "bases de datos" Access (ya que para cosas sencillas nos va bien). Luego ya veréis lo que hacemos ;-)

Bueno, empecemos!

Primero abrimos nuestro Visual Studio (yo uso el 9, supongo que si tenéis otra versión no pasara nada, porque no usaremos controles "raros" ni nada...) y creamos un proyecto nuevo. Yo lo llamé "BuscaminaX". Vosotros le podéis poner el nombre que queráis...

Ahora tendremos nuestro Form1 abierto. Le añadimos un StatusStrip y un Timer. Debería quedar algo así (img1).

img1
El siguiente paso es configurar el Form. Recomiendo que hagáis los siguientes cambios:

  • Name: BuscaminaX
  • Locked: true
  • MaximizeBox: false
  • Text: BuscaminaX
Ahora ya tenemos configurada la ventana principal.

Toca ahora crear los pop-up. A mí me gusta hacerlo con las llamadas splashScreen. Para hacer una solo tenemos que ir al menú de "Explorador de soluciones", pinchar en el nombre del proyecto, "Agregar" > "Elemento existente" > "Pantalla de presentación".

Le ponemos el nombre de diff (diminutivo de "Dificultad"), y aceptamos. Nos saldrá en el editor una pantalla con un diseño por defecto, tal como la imagen muestra (img2). Seleccionamos los elementos y los borramos (img3). Después 
hacemos doble click sobre el panel, y accedemos a la vista de código.
Como se puede ver (img4), tiene algún código por defecto; pero al haber borrado los controles estará plagado de errores. Lo borramos y punto (img5).

img2
Ahora que ya tenemos nuestro panel de dificultad preparado, le añadimos los controles necesarios:

   - 2 GroupBox
   - 5 RadioButton
   - 2 Button

Tal como os comenté, mi juego era "temático", por eso hay 5 RadioButton (3 para la dificultad, 2 para las opciones).

Insertamos los controles de manera que quede bien distribuido. La imágen "img6" muestra la distribución que le dí yo.


img3
Ahora vamos al siguiente panel: Otra SplashScreen, para cuando el jugador falle. Borramos el contenido y el código, y hacemos algo gracioso; dejad volar vuestra imaginación.

Yo puse una imágen un tanto desagradable y bien grande...

El tema está en que esta pantalla se mostrará y no se podrá cerrar, y evitará que el juego se pueda cerrar (solo se podrá hacer abriendo el administrador de tareas).


Ya tenemos todo el tema del diseño listo. Sólo nos queda programar y en unos pocos ratos tendremos diversión asegurada.
img4

img5
Con esto terminamos la sección de diseño. Las próximas entregas tratarán sobre programación. Todavía no se si haré la primera parte en tres secciones o en una entrada muy muy larga (tampoco se con cuanta profundidad entraré en los conceptos...) De todas maneras, falta bien poco para tener nuestro propio juego de M!nas, jejeje.

En fin, sed buenos, y ¡felicidades a todos los programadores!; hoy en España es Santa Tecla, que (almenos para mi) es el patrón de los programadores (chiste muy muy malo, jejeje).







img6












Hasta la próxima!

A buscar M!nas: diseño de algoritmos

Buenas!

Hoy empezamos con el diseño; esta sección durará dos entradas: una dedicada al diseño de algoritmos, y otra dedicada al diseño de pantallas.

En la entrada anterior explicamos el comportamiento básico de nuestro juego de las minas. En esta profundizaremos en ello hasta conseguir buenos algoritmos en pseudo-código.

El modus operandi que uso para generar algoritmos se basa en funciones de profundidad. Es decir: creo una base muy simple (dos o tres instrucciones), y después para cada instrucción voy ampliando el nivel de profundidad del algoritmo. Con esta técnica se consiguen algoritmos compactos y muy modulares.

Empezaremos con la parte del backend, es decir, dibujar minas, colocarlas...:

[Carga la pantalla]

 muestra(pantalla_inicial)

 si gen = 0 cierra
 sino
   carga()
   dibuja()
  fin

Tenemos el aspecto general de carga terminado. Una anotación: los token del pseudo-código los he puesto en rojo y negrita; las variables en azul y las funciones propias en verde.

Sigamos ahora con el algoritmo de la función carga() (no os preocupéis si veis variables "inconexas"; luego lo juntamos todo).

[Función carga]

carga(){
    min = 10
    fil = 9
    col = 9
    img =Random(min, max)

    si gen = 2 img += max

    imF = Imagen(concat(ruta, string(img), '.jpg'))
    anc = width(imF)
    alt = height(imF)

    si dif = 1
        min = 40
        fil = 16
        col = 16
    sino si dif = 2
        min = 99
        fil = 16
        col = 30
    fin

    anB = anc / col
    alB = alt / fil
    pos = LlenarMinas(fil*col, min)
    res = ComprobarMinas(fil*col, col, pos, min)
    minas = min
    cuadros = (fil * col) - min
}

Revisemos rápidamente como funciona esta función:

Primero asumimos los valores para la dificultad fácil (tres primeras líneas). Acto seguido creamos un aleatorio para la imágen de fondo. Si la opción es 2, le sumamos max (esto es porque hay los mismos fondos de la primera opción como de la segunda; los fondos están numerados. Si es la segunda opción, el número más bajo será max, o lo que es lo mismo, el primer fondo de la opción 2).

Lo siguiente es crear la imágen en imF, y capturar su ancho y largo.

Seguidamente consultamos la dificultad. Si no está en fácil variamos los datos pertinentes.
Finalmente, calculamos el ancho y el largo de los controles (anB y alB), llenamos las minas y las comprobamos.

Las funciones LlenarMinas() y ComprobarMinas() las veremos después de la función dibuja(), la cual veremos a continuación:

[Función dibuja]

dibuja(){
    max = fil * col
    h = alB
    w = anB
    c = col
    f = fil
   con = array(max*2)

    de 0 hasta max-1 usando i
        o = label()
        p = button()

        top(o) = t
        left(o) = l
        width(o) = w
        height(o) = h
        name(o) = concat('L', string(i))
        visible(o) = 1
        backColor(o) = color('transparent')
        font(o) = font('Times new Roman', 10, 'regular', 'pixel')
        top(p) = t
        left(p) = l
        width(p) = w
        height(p) = h
        name(p) = concat('B', string(i))
        visible(p) = 1

        Evento(p.MouseDown)
        Evento(o.MouseEnter)
        Evento(o.MouseLeave)

        con(i) = p
        con(i+max) = o

        si i%c = c-1
            l = 0
            t += h
        sino
            l += w
         fin
    sigue

    add(ventana, con)
    height(ventana) = alt + 57
    width(ventana) = anc + 13
    maxSize(ventana) = size(ventana)
    minSize(ventana) = size(ventana)
    backgroundImage(ventana) = imF
    text(strip1) = string(min)
}

Si en la anterior función predominaban la asignación de variables, en esta predominan las asignaciones a controles. Explico rápidamente el algoritmo:

Se reocgen datos (ya se que parece que sean inútiles las asignaciones del principio).

Después se entra en un bucle, en el que vamos definiendo, en cada vuelta, un button y un label. Les damos altura, anchura, color, posición... y los agregamos al array, de manera que quedan agrupados todos los button con los button y las label con las label.

El siguiente paso es calcular la siguiente posición, y volver a empezar.

Cuando termina el bucle, incluimos todos los controles en la ventana y le fijamos un ancho y un alto. Después hacemos que no se pueda modificar el tamaño, y ponemos el número de minas abajo (en un strip).

El siguiente paso es definir la función LlenarMinas():

[Función LlenarMinas]

LlenarMinas(){
    de 0 hasta min-1 usando i
        pass = 1
        num = Random(0, max+1)

        de 0 hasta length(pos)-1 usando j
            si pos(j) = num pass = 0
        sigue

            si pass pos(i) = num
            sino -= 1
    sigue
}

Este algoritmo es corto y conciso: hace "min-1" iteraciones, en cada una de la cual busca un aleatorio que no esté en "pos". En caso de estar, retrocedemos en una unidad el contador, para volver a la misma posición.

 Para el backend solo falta definir el algoritmo ComprobarMinas(). Pero, además, faltan los eventos que definimos en dibuja(). Éstos entrarán en juego cuando el usuario pulse un button, entre en una label o salga de ella.

Por ahora, vamos a por ComprobarMinas():

[Función ComprobarMinas]

ComprobarMinas(){
    de 0 hasta max-1 usando i
        de 0 hasta min-1 usando j
            si i%col = 0
                si (pos(j) = i - col o pos(j) = i - (col - 1) o
                    pos(j) = i + 1 o pos(j) = i + col o pos(j) = i + (col + 1))
                    num++
                sino
                    num = 10
                    break
                fin
            sino si i%col = col - 1
                si (pos(j) = i - (col + 1) o pos(j) = i - col o
                    pos(j) = i - 1 o pos(j) = i + (col - 1) o pos(j) = i + col)
                    num++
                sino
                    num = 10
                    break
                fin
            sino
                si (pos(j) = i - (col + 1) o pos(j) = i - col o pos(j) = i - (col - 1) o
                    pos(j) = i - 1 o pos(j) = i + 1 o   pos(j) = i + (col - 1) o
                    pos(j) = i + col o pos(j) = i + (col + 1))
                    num++
                sino
                    num = 10
                    break
                fin
            fin
        sigue

        ret(i) = num
        num = 0
    sigue
}

Esta función parece complicada, pero es bastante sencilla: recorremos el array "pos", y por cada posición buscamos dentro de él los lugares colindantes (como si fuera un array de dos dimensiones pero en uno). El complejo de condiciones simplemente sirven para delimitar los "bordes" de la martiz.

Entonces, si encuentra una coincidencia, incrementa "num". En caso contrario deja "num" con un valor de "10" (ya veremos para qué sirve esto).

NOTA: Sé que no es precisamente óptimo, pero esto está hecho de hace un año, y, sinceramente, como funciona no me atrevo a tocarlo...

Finalmente nos faltan los pseudo-códigos de los eventos (tranquilos, ya acabo :-P). Empezamos por los eventos de las label (más cortos):

[Label::mouseEnter]

backColor(s) = color('white')
text(s) = coM(substring(name(s), 1))

Simplemente le ponemos un fondo de color blanco y un texto con las minas que tiene alrededor.

Ahora el otro evento de la Label:

[Label::mouseLeave]

text(s) = ''
backColor(s) = color('transparent')

Aquí volvemos a dejar la label en el mismo estado que cuando la definimos.

Por último, el evento del button:

[Button::mouseDown]

si button(e) = mouseButtons(left)
    cuadros--

    si cuadros = 0
        msg('Has ganado')
        clear(ventana, controls)
    fin

    visible(s) = 0

    si coM(substring(name(s), 1)) = 10
        msg('PUM!')
        muestra(fail)
    fin
sino si button(e) = mousebuttons(right)
    si text(s) = 'X'
        text(s) = ''
        backColor(s) = Color('white')
        minas++
    sino
        text(s) = 'X'
        backColor(s) = Color('red')
        minas--
    fin

    text(strip) = string(minas)
fin

Como escribí en la anterior entrada; comprueba el click hecho (izquierdo o derecho). Si es derecho, permuta entre un texto "X" y un color, indicando que está marcado o no como mina. También modifica el texto del strip (que lleva la cuenta de las minas que nos faltan por marcar).

En cambio, si el click es izquierdo, destapa el cuadro, y busca el valor de la label. OJO he marcado el "10" en negrita para que lo veáis; es el misterioso "10" de CompruebaMinas(). ¿Cómo funciona? Sencillo:

Si os fijáis los buttons y los labels tienen nombres del estilo "B1", "L25". En el evento se extrae el número, y se coteja con el array (por eso es necesariamente unidimensional) y recoge el número del array que CompruebaMinas() introjudo (que es el valor de las minas que tiene alrededor; 10 si es mina). ¡Volia!

Ahora, por fin tenemos todos los algoritmos necesarios. Solo nos falta diseñar las pantallas. Esto nos llevará más bien poco, ya que la mitad del trabajo de diseño está aquí incluido (función dibuja()).

Os dejo, como siempre,
Hasta la próxima!