Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

jueves, 9 de junio de 2022

Error "Clase no registrada" al usar Microsoft.Speech en Visual Studio 2019 y Windows 10

Un día quise jugar con las librerías de Microsoft Speech para hacer hablar a mi laptop. Las descargas fueron las siguientes:

Microsoft Speech Platform Runtime. Descargué el instalador de 64 y 32 bits, bastan los instaladores de la plataforma en la que se compila la aplicación, pero por el error que explicaré más abajo tuve que probar ambos:

https://www.microsoft.com/en-us/download/details.aspx?id=27225

Microsoft Speech Platform Runtime Languages:

https://www.microsoft.com/en-us/download/details.aspx?id=27224

Los paquetes para reconocimiento de audio son los que dicen TELE, los paquetes para convertir texto a voz son los que tienen nombres de personas. Yo descargué los paquetes de español México y español España. En total fueron 4 paquetes de idioma.

Lo siguiente que hice fue crear un proyecto de C# con .Net Framework. Referencié el archivo Microsoft.Speech.dll en mi proyecto:

Para 64 bits, la dll está en C:\Program Files\Microsoft SDKs\Speech\v11.0\Assembly

Para 32 bits, está en C:\Program Files (x86)\Microsoft SDKs\Speech\v11.0\Assembly

Finalmente copié y pequé un poco del código de esta web: https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/december/voice-recognition-speech-recognition-with-net-desktop-applications

        static void Main(string[] args)
        {
            try
            {
                ss.SetOutputToDefaultAudioDevice();
                Console.WriteLine("\n(Speaking: I am awake)");
                ss.Speak("He despertado");
                CultureInfo ci = new CultureInfo("es-PE");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
            }
        }

Y apenas empezar el debugueo me saltó este error:

Retrieving the COM class factory for component with CLSID {D941651C-44E6-4C17-BADF-C36826FC3424} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).

Luego de intentar varias soluciones: cambiar a 32 bits, cambiar a .Net Framework 4, iniciar Visual Studio como administrador, lo único que funcionó fue lo propuesto en este foro


En 64 bits el instalador está en C:\Program Files\Microsoft SDKs\Speech\v11.0\Redist

En 32 bits está en C:\Program Files (x86)\Microsoft SDKs\Speech\v11.0\Redist

Al ejecutar los instaladores se pueden ver los mensajes de que se están registrando las dlls.

Lugo volví a referenciar la dll de 64 bits, cambié a .Net Framework 4.5, puse que se trata de una aplicación de 64 bits, y probé.

Funcionó, la aplicación puede decir "He despertado" con la voz de una mujer (apodada Hilda).

Ya puedo ponerle voz a mis aplicaciones de C# :)

jueves, 28 de abril de 2022

"Curlicue Fractals" con Visual C#

Otra entrada migrada de la vieja web, la cual ha perecido con dignidad. De a pocos voy a ir copiando mis viejos tutoriales a este blog, ¡hay que tener paciencia!

Rod Stephens, autor de http://csharphelper.com/ tenía un tutorial sobre cómo dibujar estos fractales, pero él también ha tenido problemas con su web y el tutorial ya no está disponible. Voy a poner aquí lo que yo tengo, mi código está basado en el de Rod, así que los créditos van para él.

Al revisar la documentación en http://mathworld.wolfram.com/CurlicueFractal.html noté que sólo menciona dos ecuaciones cuando en el código fuente de Rod (ya no disponible) se usan cuatro:


Al seguir examinando se puede ver que las ecuaciones de la documentación de Mathworld generan ángulos, y lo que se dibujan son líneas que necesitan las coordenadas de los puntos inicial y final. 

La traducción del algoritmo es la siguiente: Se tiene una línea de longitud 1, paralela al eje x. Se calcula un número theta con la ecuación, luego, con la ecuación se calcula el nuevo ángulo de la siguiente línea de longitud 1, cuyo punto inicial será el extremo final de la línea anterior.
Al final resulta un ángulo. Su módulo es 1. Para calcular las coordenadas se multiplica por el coseno (coordenada x) y el seno (coordenada y).


Mi programa está basado en el algoritmo de Rob, pero con algunas cosas añadidas, como zoom y poder posicionar el fractal, además emplea el objeto Graphics de una forma diferente para dibujar el fractal, también añadí un número aleatorio para jugar con los colores:


El fractal se genera sólo a partir de un número irracional. Si se utiliza un número racional sólo aparecerá una línea horizontal.

La aplicación se puede descargar de aquí (para Visual C# 2008).

viernes, 25 de marzo de 2022

Dibujando arbolitos fractales con la tortuga de Python

Éste fue un pequeño ejercicio para practicar recursividad. La idea es dibujar un arbolito fractal con tres ramitas. Ya antes había revisado el código para dibujar estos árboles, pero quise tener mi propia implementación. Después de un buen rato probando, dibujando en un papel cómo debían salir las ramitas, y dónde debía poner la bifurcación recursiva, terminé con este código:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if n > 0:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle - 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle, lenght / 1.3, n-1)
    else:
        t.hideturtle()
        return 0


drawit(0, -100, 90, 100, 7)
t.mainloop()


la variable n indica la profundidad hasta la que se dibujarán las ramas, otra forma es dibujar las ramas hasta que llegan a una longitud mínima. Las variables x, y, angle, lenght indican las coordenadas desde dónde se dibujarán las ramas, el ángulo y la longitud respectivamente. La función seth() de la tortuga setea el ángulo en el que se empezará a dibujar. Las funciones xcor() y ycor() capturan las coordenadas actuales de la tortuga.

La tortuga primero dibuja el tronco del árbol en la posición (0, -100) en un ángulo de 90 grados. A continuación se bifurca en tres ramas, cada una dibujando un "tronco" en el ángulo y la longitud indicadas a partir de las coordenadas donde la tortuga terminó de dibujar. Para evitar que se confunda, le ordeno ir a esas coordenadas, antes de empezar a dibujar, con la función goto().

El resultado es:


En esta web también dibujan un árbol fractal, y además le ponen color, voy a usar el mismo truco para colorear mi árbol:

import turtle as t
import math

def drawit(x, y, angle, lenght, n):
    if n > 0:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)

        t.pensize(math.log(lenght,2)/3)
        if n < 2:
            t.color('forest green')
        else:
            t.color('gray')

        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle - 30, lenght / 1.3, n-1)
        drawit(cx, cy, angle, lenght / 1.3, n-1)
    else:
        t.hideturtle()
        return 0


drawit(0, -100, 90, 100, 7)
t.mainloop()

Ahora mi árbol ya no parece muerto:


Luego me encontré con este otro arbolito, cuyo código es muy parecido al mío. En lugar de un contador, usa la longitud de las ramas para ponerle un límite a la profundidad de las recursiones. No fue difícil modificar mi código para dibujar un arbolito curvo, y después de probar un poco noté que en este caso es mejor usar una longitud mínima en lugar de la variable n para detener las recursiones. Al final, mi código es el siguiente:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if lenght > 2:
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 25, lenght * 0.9, n-1)
        drawit(cx, cy, angle - 90, lenght * 0.45, n-1)
    else:
        t.hideturtle()
        return 0


drawit(200, -200, 90, 100, 10)
t.mainloop()


El resultado es:

Necesita algunos arreglos:

import turtle as t

def drawit(x, y, angle, lenght, n):
    if lenght > 2:
        t.penup()
        t.goto(x, y)

        if lenght < 10:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        t.seth(angle)
        t.forward(lenght)
        cx = t.xcor()
        cy = t.ycor()
        drawit(cx, cy, angle + 25, lenght * 0.9, n-1)
        drawit(cx, cy, angle - 90, lenght * 0.45, n-1)
    else:
        t.hideturtle()
        return 0


drawit(180, -200, 90, 100, 10)
t.mainloop()


Finalmente queda así:

miércoles, 9 de marzo de 2022

Instalando Glassfish como servicio de Windows (versiones 4.1 y 5)

Estos días han sido días de Glassfish en mi laptop. Jugando con las versiones 4.1 y 5, y rebuscando en internet, encontré que se puede instalar como un servicio de Windows. Como mis Glassfish son todos para pruebas, trasteo, más trasteo y depuración, mi dominio es el que se usa por defecto: domain1. Los puertos son también los que vienen por defecto: 4848 para la consola de administración y 8080 para la conexión http.

Dos buenos tutoriales han sido:

https://www.asesoriaensig.com.mx/instalar-glassfish-4-como-servicio-en-windows-2008/

https://www.luv2code.com/2013/11/13/install-glassfish-4-as-a-windows-service/

Lo que me sucedió es que con el comando asadmin start-domain me inicia el glassfish pero después de un rato tiende a detenerse. Este comando es para pruebas o depuración, no para producción.

Para producción en Windows, el Glassfish debe arrancarse como servicio. Para eso existe este comandito el cual debe ejecutarse como Administrador o pasará lo que se menciona aquí:

asadmin create-service

Lo que no ponen los tutoriales es lo siguiente: tengo un millón de servicios en Windows ¿cómo ubico el de Glassfish? Bueno, en Windows se crea como nombre-del-dominio Glassfish Server:

Lo malo: este servicio funciona, Glassfish 5 se ejecuta en background, pero Windows 10 no lo administra bien, reinicié mi laptop y el servicio estaba detenido así que tuve que iniciarlo de forma manual, además la información de si se está ejecutando o no no se muestra correctamente. Esto no me ocurrió con Glassfsih 4.1 corriendo en Windows Server 2013.

Volviendo a mi Glassfish 5 corriendo en Windows 10, no me permitía depurar webservices en Netbeans apuntando a ese mismo Glassfish 5. Como sólo es un Glassfish de pruebas, lo mejor en este caso es eliminar el servicio.

Deshabilité el servicio, ejecuté el Windows Powershell como administrador e intenté eliminar el servicio de glassfish con el comando:

sc domain1

No se eliminó, luego intenté con el comando:

sc delete "domain1 GlassFish Server"

Y tampoco se eliminó. Busqué en internet que estaba pasando y en dos segundos hallé la solución. El comando es:

sc.exe delete "domain1"

Lo que debe mostrar al ejecutarse es:


Luego se deben borrar los archivos que deja el servicio en esta ruta:

glassfish5\glassfish\domains\domain1\bin

Lo malo es que eliminar el servicio de glassfish no detiene los procesos de glassfish, para esto debemos ir al Task Manager (taskmgr.msc), hacer clic derecho en las cabeceras de las columnas y elegir "Línea de comandos". Luego se deben matar todos los procesos de Java que tengan "glassfish" en algún lado de su línea de comandos:

Suele haber más de uno, hay que matarlos todos. Después de hacer todo esto, ya pude debuggear mis webservices desde Netbeans usando Glassfish 5.

Por otro lado, el ejecutable asadmin está en esta ruta:

C:\glassfish5\glassfish\bin

miércoles, 9 de febrero de 2022

Arreglando el Error: "No appropriate protocol (protocol is disabled or cipher suites are inappropriate)" en Jdk 1.8.0_301

 Quise conectarme desde una aplicación chiquita de java a una base de datos en Sql Server 2017 via el driver jdbc sqljdbc4-2.0.jar. La aplicación la estaba depurando en Netbeans 11.1, corriendo en Windows 10. Todo estaba bien hasta que actualicé a la versión del Jdk a la 1.8.0_301, entonces en la línea:

Connection connection = DriverManager.getConnection(connectionString);

me arrojó esta excepción:

El controlador no pudo establecer una conexión segura con SQL Server con el cifrado de Capa de sockets seguros (SSL). Error: "No appropriate protocol (protocol is disabled or cipher suites are inappropriate)"

En inglés:

The driver could not establish a secure connection to SQL Server by using Secure Sockets Layer (SSL) encryption. Error: “No appropriate protocol (protocol is disabled or cipher suites are inappropriate)”.

Lueo de rebuscar en foros raruchos, hallé en éste la solución:


Es hora de ir a buscar ese archivo. Primero debo cerrar todas las aplicaciones de java: jars y Netbeans incluídos. En mi caso el archivo a modificar está en C:\Program Files\Java\jdk1.8.0_301\jre\lib\security\java.security. Las líneas que debo comentar son las 723, 724 y 725:


Debe quedar así:


En mi caso, basta volver a arrancar las aplicaciones de java (Netbeans incluídos) para resolver el problema y poder conectarme a mi base de datos. Puede ser que en algunos casos sea necesario reiniciar el sistema operativo.

jueves, 25 de noviembre de 2021

Resolviendo el proyecto Pagerank del curso "CS50’s Introduction to Artificial Intelligence with Python"

El curso se encuentra en este enlace: https://cs50.harvard.edu/ai/2020/

Aquí se deben hacer dos implementaciones para calcular el Pagerank:
  • La primera implementación es el Modelo del Surfista Aleatorio (Random Surfer Model). En resumen, el algoritmo debe iterar a través de todas las páginas, escoger una de las páginas enlazadas, y contar cuántas veces se escogió cada página. Las reglas de cómo se debe escoger cada página están en la función transition_model, que devuelve la probabilidad con la que se debe escoger cada página. Los valores devueltos por la función transition_model deben sumar 1, para esto se tienen dos conjuntos de valores, las páginas enlazadas desde una página determinada (el conjunto de páginas que se está evaluando), y todas las otras páginas. Si una página pertenece al conjunto de páginas que estamos evaluando, la distribución de probabilidad es el factor damping_factor/Número de páginas en el conjunto. Si una página no está en el conjunto, la probabilidad es de (1 - el factor damping_factor)/el total de páginas que no pertenecen al conjunto. Lo que debe devolver esta función es una proporción, por ello al final se dividen los resultados entre el total de páginas.

La función transition_model es:

def transition_model(corpus, page, damping_factor):
    """
    Return a probability distribution over which page to visit next,
    given a current page.

    With probability `damping_factor`, choose a link at random
    linked to by `page`. With probability `1 - damping_factor`, choose
    a link at random chosen from all pages in the corpus.
    """
    N = len(corpus[page])
    M = len(corpus)
    distr = { }

    if N == 0:
        distr = { c: 1 / M for c in corpus.keys() }
    else:
        for p in corpus.keys():
            if p in corpus[page]:
                distr[p] = damping_factor / N
            else:
                distr[p] = (1 - damping_factor) / (M - N)

    return distr


La función para esta primera implementación es:

def sample_pagerank(corpus, damping_factor, n):
    """
    Return PageRank values for each page by sampling `n` pages
    according to transition model, starting with a page at random.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """    
    pageranks = {p : 0 for p in corpus.keys()}
    page = random.choice(list(corpus.keys()))
    trans = transition_model(corpus, page, damping_factor)
    pageranks[page] = 1

    for _ in range(1, n):
        links = []
        weights_ = []

        for p in trans.keys():
            links.append(p)
            weights_.append(trans[p])

        page = random.choices(links, weights = weights_, k = 1)
        pageranks[page[0]] += 1
        trans = transition_model(corpus, page[0], damping_factor)

       
    return {p : pageranks[p] / n for p in pageranks.keys()}


  • La segunda implementación es el Método Iterativo, el cual escoge una página cualquiera para empezar a calcular el valor de Page_rank mediante la fórmula:


La iteración se detiene cuando la diferencia entre PR(p) calculado en la iteración actual (variable pagerank) y el PR(p) calculado en la iteración anterior (variable pagerank2) es muy pequeña. La variable temp almacena el valor dentro de la sumatoria (cantidad de páginas enlazadas desde la página p). Si el valor resulta cero (no hay páginas enlazadas) se le asigna el valor 1/N.

La función en este caso es iterative_pagerank:

def iterate_pagerank(corpus, damping_factor):
    """
    Return PageRank values for each page by iteratively updating
    PageRank values until convergence.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """
    N = len(corpus)
    pagerank = { p : 1 / N for p in corpus.keys() }
    pagerank2 = { p : 0 for p in corpus.keys() }

    stop = False

    while not stop:
        for p in corpus.keys():
            temp = 0

            for i in corpus:
                if p in corpus[i]:
                    temp += pagerank[i] / len(corpus[i])

            if temp == 0:
                temp = 1 / N

            pagerank[p] = (1 - damping_factor) / N + temp * damping_factor

        stop = True
        for p in corpus.keys():
            if abs(pagerank[p] - pagerank2[p]) > 0.001:
                stop = False
                break

        pagerank2 = copy.deepcopy(pagerank)

    return pagerank



jueves, 14 de octubre de 2021

Configurando Sql Server 2017 para que escuche en el puerto por defecto 1433

No me podía conectar desde Java ejecutándose en Windows 10 a mi Sql Server ejecutándose en localhost a pesar de poner todos los parámetros correctos como indican en esta web: https://www.codejava.net/java-se/jdbc/connect-to-microsoft-sql-server-via-jdbc

Lo peor es que se trata de una instalación fresca de Sql Server, además el SQL Server Management Studio no tiene problemas en conectarse. Pero al intentar conectarme desde una aplicación en Java, siempre me arrojaba la siguiente excepción:

com.microsoft.sqlserver.jdbc.SQLServerException: The TCP/IP connection to the host localhost, port 1433 has failed. Error: "Connection refused: connect. Verify the connection properties. Make sure that an instance of SQL Server is running on the host and accepting TCP/IP connections at the port. Make sure that TCP connections to the port are not blocked by a firewall.

El error también puede decir que se ha agotado el tiempo de espera.

Luego de comprobar que no fuera el firewall (activándolo y desactivándolo), reinicié los servicios que aparecen listados en Services.msc, eso tampoco resolvió el problema:

 

En el caso de Sql Server Express, no interesa si el SQL Server Agent no se ejecuta, por ahí leí que la versión Express no soporta el SQL Server Agent, de modo que se detiene apenas se inicia. Esto no interesa.

Pasa saber qué etá pasando, se debe revisar si hay alguna aplicación escuchando el puerto 1433; para esto se debe abrir el Windows Powershell o el cmd e ingresar el comando netstat -a que listará todos los puertos en escucha:



Es una lista bastante larga, pero el puerto 1433 debe aparecer por algún lado. En mi caso no aparece, para resolverlo se deben seguir las instrucciones de https://edywerder.ch/sql-server-remote-port/

Primero se debe abrir el SQL Server Configuration Manager, si no se tiene el acceso directo, hay que ir a Inicio y ejecutar el archivo respectivo dado en este enlace https://docs.microsoft.com/en-us/sql/relational-databases/sql-server-configuration-manager?view=sql-server-ver15 

Esta es la lista para cada versión de SQL Server: 

VersionPath
SQL Server 2019 C:\Windows\SysWOW64\SQLServerManager15.msc
SQL Server 2017 C:\Windows\SysWOW64\SQLServerManager14.msc
SQL Server 2016 C:\Windows\SysWOW64\SQLServerManager13.msc
SQL Server 2014 (12.x) C:\Windows\SysWOW64\SQLServerManager12.msc
SQL Server 2012 (11.x)C:\Windows\SysWOW64\SQLServerManager11.msc

No hay que poner la ruta completa, basta el nombre del archivo msc respectivo. En mi caso es SQLServerManager14.msc

A continuación se debe ir a Protocolos de SQLEXPRESS (el nombre de mi Instancia) y hacer click derecho a TCP/IP->Propiedades:


Las opciones que se deben configurar son IPV3 e IPV4, se debe borrar el cero en TCP Dinamic Ports y poner Enabled en Yes, la opción TCP Port se deba en blanco:

 

 La última opción es IPALL donde se debe ingresar el puerto 1433 en TCP Port:


Luego se deben reiniciar todos los servicios de SQL Server en Services.msc. Se debe comprobar que el puerto 1433 aparece listado al ejecutar el comando netstat -a:


Esto me resolvió el problema de conexión desde Java.

viernes, 10 de septiembre de 2021

Resolviendo el proyecto Knights del curso "CS50’s Introduction to Artificial Intelligence with Python"

El curso es accesible libremente en este link: https://cs50.harvard.edu/ai/2020/

El proyecto está en este link: https://cs50.harvard.edu/ai/2020/projects/1/knights/

En resumen, el proyecto trata de personajes que pueden ser Caballeros (Knights) o Truhanes (Knaves) pero no ambos a la vez. Los Caballeros siempre dicen la verdad, y los Truhanes siempre mienten. El objetivo es, dada cierta información, averiguar si los personajes son Caballeros o Truhanes.

El primer problema dice:

# Puzzle 0
# A says "I am both a knight and a knave."

Mi solución en este caso es:

knowledge0 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Implication(Not(And(AKnightAKnave)), AKnave)
)

La afirmación de lo que dice A es falsa ("Soy ambos, Caballero y Truhán"). Como es falsa, implica que A es un Truhán.

El segundo problema es:

# Puzzle 1
# A says "We are both knaves."
# B says nothing.

Luego de analizar las condiciones del problema, hallé dos bicondicionales: Si un personaje miente, es un Truhán, y si es un Truhán, entonces miente. Si un personaje dice la verdad ,es un Caballero, y si es un Caballero, debe decir la verdad.

Mi solución:

knowledge1 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Implication(Not(And(AKnaveBKnave)), AKnave),
    Biconditional(And(Or(AKnaveBKnave), Not(And(AKnaveBKnave))), AKnave)
)

La implicación significa que A y B no pueden ser Truhanes, porque en este caso A estaría diciendo la verdad y sería un Caballero. Tampoco A puede ser un Caballero, porque estaría mintiendo.

El bicondicional indica que uno de los dos es un Truhán, pero no pueden serlo ambos, sí y sólo si A es Truhán.

El tercer problema es:

# Puzzle 2
# A says "We are the same kind."
# B says "We are of different kinds."

Mi solución:

knowledge2 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Biconditional(And(AKnightBKnight), AKnight),
    Biconditional(And(AKnaveBKnight), AKnave),
    Biconditional(And(AKnightBKnight), BKnave),
    Biconditional(And(AKnaveBKnight), BKnight)
)

Aquí agoto las cuatro opciones, todas como bicondicionales. Si ambos son caballeros, A dice la verdad y viceversa. En este caso B estaría mintiendo y sería un Truhán, y viceversa. Si A es un Caballero, B es necesariamente un Truhán.

El último problema es:

# Puzzle 3
# A says either "I am a knight." or "I am a knave.", but you don't know which.
# B says "A said 'I am a knave'."
# B says "C is a knave."
# C says "A is a knight."

Aquí recibí un poco de ayuda de: https://cs50.stackexchange.com/questions/41635/help-with-cs50ai-project-1-knights-puzzle-3

Mi solución es:

knowledge3 = And(
    Or(AKnightAKnave),
    Not(And(AKnightAKnave)),
    Or(BKnightBKnave),
    Not(And(BKnightBKnave)),
    Or(CKnightCKnave),
    Not(And(CKnightCKnave)),
    Biconditional(Or(AKnightAKnave), AKnight),
    Biconditional(CKnaveBKnight),
    Biconditional(CKnightBKnave),
    Biconditional(AKnightCKnight),
    Biconditional(AKnaveCKnave)

Lo que podría decir A está en el primer bicondicional. La última bicondicional codifica lo que sería A en caso de que C sea un Truhán, la penúltima codifica loq ue será A si C fuera un Caballero. Las otras dos bicondicionales codifican lo que sería C según lo que dice B en caso de ser Caballero o Truhán.

Para resolver este problema tuve que hacer una tabla de verdad con lo que pueden ser A, B, C (Caballero y Truhán) y lo que están diciendo, y también verificar que mi respuesta era la correcta.

Encontré este otro código https://github.com/iron8kid/Knights/blob/main/puzzle.py donde el autor no usa el bicondicional, haciendo que sus soluciones sean mucho más complejas que las mías.

Los archivos del proyecto con mis soluciones, una copia de las soluciones de iron8kid (en el archivo puzzle2.py) y los ejemplos dados en el curso (subcarpeta src) se pueden descargar de aquí.

IDE: Visual Studio Code 1.60 con Python 3.8

miércoles, 18 de agosto de 2021

Modificando los Atributos de los Archivos con Visual Basic 6

Otra entrada migrada de la vieja web Electrónica a Martillazos, porque desde hace una década no hago más que programar y programar, asi que de electrónica ya nada. Y de paso me deshago de la fuente Comic Sans :D


De vez en cuando los virus infectan las computadoras de la facultad. Hubo uno muy molesto que modificaba los atributos de los archivos de tal forma que no pudieran "desocultarse":

Desde la ventana de "propiedades" es imposible "desocultar" el archivo. Intenté hallar cómo modificar esta propiedad, rebusqué hasta en el registro y nada.

Hasta que me topé con esta web: http://www.recursosvisualbasic.com.ar/htm/tutoriales/tutorial-basico7.htm#getattr-setattr

Dado que desde Visual Basic es muy pero muy fácil modificar los atributos de los archivos decidí hacerme mi propio programa para quitar las modificaciones que hizo el condenado virus:

Se abre el Visual y se carga un proyecto exe estándar. Luego, a la barra de controles, se le carga el control Conmon Dialog. Cómo hacerlo está acá: http://www.recursosvisualbasic.com.ar/htm/tutoriales/control-commondialog.htm

Éste control servirá para elegir el archivo cuyos atributos se van a modificar, ésta es la ventana que creé:

 


Ésta es la lista de los controles:

- Un Label para ver la ruta del archivo: label1

- Un Conmon Dialog: cmd

- Dos botones, uno para llamar al Conmon Dialog y escoger el archivo, y el otro para modificar sus atributos: Command2 y Command1 respectivamente.

- Seis OptionButton para escoger los atributos que se deseen modificar, sus nombres, de arriba a abajo y muy explícitos, son:

normal, lectura, oculto, sistema, s_oculto, s_oculto_lectura.

Cada OptionButton asigna un número a una variable tipo Integer llamada "attr" ya que los atributos están definidos por números, acá la lista de todos los que pude encontrar:

0 - Archivo Normal

1 - Archivo de Sólo Lectura

2 - Archivo Oculto

4 - Archivo de Sistema

16 - Directorio o Carpeta

32 - Archivo Modificado

38 - Archivo de Sistema Oculto

39 - Archivo de Sistema Oculto y Sólo Lectura

18 - Carpeta Oculta

6 - Oculto (en la caja ventana de propiedades no está el check en "archivo")

7 - Archivo? de sistema oculto y sólo lectura (en la caja ventana de propiedades no está el check en "archivo", es posible que sea aplicable para carpetas, no lo he comprobado)

Y el código fuente es (un par de líneas comentadas se colaron por ahí):

Option Explicit

Dim ruta As String
Dim ret As Long
Dim attr As Integer

Private Sub Command1_Click()
'cmd.Action = 1

If ruta <> "" Then
SetAttr ruta, attr
End If

End Sub

Private Sub Command2_Click()

cmd.DialogTitle = "Seleccione un archivo"
cmd.ShowOpen

If cmd.FileName <> "" Then
ruta = cmd.FileName
Label1 = ruta
End If

End Sub

Private Sub Form_Load()

'ret = GetAttr("c:\IO.SYS")
Me.Top = Screen.Height / 2 - Me.Height / 2
Me.Left = Screen.Width / 2 - Me.Width / 2
attr = 0

End Sub

Private Sub lectura_Click()
attr = 1
End Sub

Private Sub normal_Click()
attr = 0
End Sub

Private Sub oculto_Click()
attr = 2
End Sub

Private Sub s_oculto_Click()
attr = 38
End Sub

Private Sub s_oculto_lectura_Click()
attr = 39
End Sub

Private Sub sistema_Click()
attr = 4
End Sub

 

Y para quienes no quieren programarse esto, les dejo el ejecutable que me llevó 15 minutos de mi vida hacerlo.

miércoles, 16 de junio de 2021

Las Apis Copyfile y Shellexecute, y sus Equivalentes en .Net Con C#

Otra entrada migrada de la vieja web :)

Después de mucho programar Apis en Visual Basic 6, se me hizo difícil dejar esa costumbre al pasar al C Sharp. Con la plataforma .Net,  ya no son necesarias muchas de las llamadas a Apis, pero dado que el Visual Basic 6 está tan bien documentado suele ser más sencillo hallar en internet cómo hacer algo usando una Api que usando .Net. Lo malo: llamar a las apis hace depender nuestras aplicaciones del sistema operativo Windows.  

Con ShellExecute para C Sharp:

En .Net tenemos la clase Process que hace todo lo que hace la api ShellExecute así que no tendríamos porqué necesitarla más. Por ejemplo, para ejecutar un programa, se pone:

Process p = new Process();
p.StartInfo.FileName = "iexplore.exe";
p.StartInfo.Arguments = "http://www.microsoft.com";
p.Start();


o abrir el Notepad:

Process p = new Process();
p.StartInfo.FileName = "notepad.exe";
p.Start();


Si quiero abrir un archivo el código sería:

Process pr = new Process();
try
{
    pr.StartInfo.FileName = @"C:\un_archivo_cualquiera";
    pr.Start();
}
catch (NullReferenceException ex)
{
    MessageBox.Show(ex.Message, "Error");
}


Que reemplaza a este otro código (dentro de una clase):

class executeIt{

/*
Public Declare Function ShellExecute _
Lib "shell32.dll" _
Alias "ShellExecuteA" ( _
ByVal hwnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) _
As Long
*/

    [System.Runtime.InteropServices.DllImport("shell32.dll", EntryPoint = "ShellExecute")]
    public static extern int ShellExecuteA(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory,
    int nShowCmd);
}



Lo que está comentado es la llamada a la api para Visual Basic 6, debajo está la "traducción" para C Sharp. Así se puede ir deduciendo cómo "traducir" el código que llama apis para VB6 a C Sharp.

Para usar la api se hace:

executeIt.ShellExecuteA(0, "OPEN","E:\\mundodisco_1-27.zip","", "", 0);
executeIt.ShellExecuteA(0, "OPEN","F:\\Músi-k","", "", 1);


La primera línea abre mis libros zipeados del Mundodisco de Terry Pratchett. La segunda abre mi carpeta con mis mp3 descargados (y de paso dejo información sobre las particiones de mi disco duro).

Si en Visual Basic 6 las variables se declaraban como Long, en C Sharp su equivalente es Int. Esto es debido a cuántos bytes ocupan estas variables en cada lenguaje. En esta página se puede ver una comparación de los tipos de variables, su tamaño y otras características.

Usando la api CopyFile:

Si se compila el siguiente código:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        /* Public Declare Function CopyFile Lib "kernel32.dll" Alias
        * "CopyFileA" (ByVal lpExistingFileName As String,
        * ByVal lpNewFileName As String, ByVal bFailIfExists As Long) As Long
        */


        [System.Runtime.InteropServices.DllImport("kernel32.dll", EntryPoint = "CopyFile")]
        public static extern int CopyFileA(string lpExistingFileName, string
        lpNewFileName, int bFailIfExists);

        private void replicar()
        {
            int count, rad;
            string copia = " ";
            string nombre_archivo = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;

            // Fuente: http://blogs.msdn.com/brada/archive/2004/04/14/113620.aspx  por Luc Cluitmans

            count = 0;
            System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(@"C:\");
            foreach (System.IO.FileInfo file in dir.GetFiles("CopyIt*.exe"))
                count++; // si es cero es que no existe el archivo.

            for (int i = count; i <= count * 2+1; i++)
            {
                copia = String.Concat("C:\\", "CopyIt", i.ToString(), ".exe");
                rad = CopyFileA(nombre_archivo, copia, 0);
            }
        }
    
        static void Main(string[] args)
        {
            Program pp = new Program();
            pp.replicar();
        }
    }
}


se tendrá un programa que simplemente busca cuántas copias existen en C:\ (los archivitos "CopyIt*.exe") y luego con un bucle se copia el doble de veces más uno. No interesa cómo se llame el programa original o dónde se encuentre, siempre sus copias terminarán en C:\ y siempre contará cuántas de ellas existen. Así, después de unos pocos clicks, el disco duro (poniendo el ícono apropiado) se verá así:


Y como todos son el mismo programa, se puede ejecutar cualquiera de ellos y harán lo mismo: copiarse y copiarse.

Y ahora, su equivalente para .Net:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        private void replicar()
        {
            int count;
            string copia = " ";
            string nombre_archivo = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
            // Fuente: http://blogs.msdn.com/brada/archive/2004/04/14/113620.aspx  por Luc Cluitmans
            count = 0;

            DirectoryInfo dir = new DirectoryInfo(@"C:\");
           
            foreach (FileInfo file in dir.GetFiles("CopyIt*.exe"))
                count++; // si es cero es que no existe el archivo.

            for (int i = count; i <= count * 2+1; i++)
            {
                copia = String.Concat("C:\\", "CopyIt", i.ToString(), ".exe");
                File.Copy(nombre_archivo, copia);
            }
        }

        static void Main(string[] args)
        {
            Program pp = new Program();
            pp.replicar();
        }
    }
}


Si se usan las rutas al estilo Visual basic en C Sharp se debe poner una arroba antes para indicarle que es una ruta de directorio, pues el formato de las rutas a los directorios es:

Rutas de archivos en Visual Basic: por ejemplo: "c:\mi archivo"
Rutas de archivos en C, C++ y C Sharp: por ejemplo: "c:\\mi archivo"