elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Usando Git para manipular el directorio de trabajo, el índice y commits (segunda parte)


+  Foro de elhacker.net
|-+  Programación
| |-+  Scripting
| | |-+  Juego Python OhNO
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] Ir Abajo Respuesta Imprimir
Autor Tema: Juego Python OhNO  (Leído 4,762 veces)
dijsktra

Desconectado Desconectado

Mensajes: 110


Mr Edsger Dijsktra (Tribute to)


Ver Perfil
Juego Python OhNO
« en: 24 Mayo 2019, 14:18 pm »

Hola!
OhnO es un juego clásico de fichas sobre un tablero de origen japonés.  Sus reglas son:
EDITED: Credits NEBIRE
  • Una misma ficha azul puede ver a las otras azules contiguas en su misma fila y columna, excluyendo ella misma
  • Las fichas rojas rompen la contigüidad entre las azules
  • Haz click en las fichas para cambiar sus colores. Las fichas "clavadas" del principio no se pueden cambiar
  • Los numeros (A/B) describen cuantas fichas azules debe ver cada ficha azul (B) y cuantas ve relamente (A).
  • Como mínimo, cualquier ficha azul debe ver otra ficha azul

Es posible que haya miles de versiones por Internet.
Ofrezco la mía. Alguna valoración?
https://github.com/rafaelm53539600/OhnO






(Son 250 lineas, por eso no lo expongo en el Web)



« Última modificación: 29 Mayo 2019, 14:29 pm por dijsktra » En línea

Si la depuración es el proceso de eliminar fallos en el software, entonces programar debe ser el proceso de ponerlos dentro. (Edsger Dijsktra)
Serapis
Colaborador
***
Desconectado Desconectado

Mensajes: 3.391


Ver Perfil
Re: Juego Python OhNO
« Respuesta #1 en: 24 Mayo 2019, 18:48 pm »

No conocía este juego...

Me recuerda bastante a un viejo juego de casillas numéricas, que incluso en su versión más simple de un tablero de 3x3, en apariencia sencillo resulta en extremo complicado...
El caso es que parece una mezcla entre ese y el Mastermind...

En las reglas, hay un par de cosas que no me quedan claro:
- El número indica todas las que ve en su fila, columna o contiguas ?
- Cual es el objetivo final. ¿Quizás descubrir el mapa original usando como pistas los valores numéricos, y como interfaz el cambio de color en las fichas con 'toques'?.

Lo miraré más a fondo, mañana que saque un tiempito...



« Última modificación: 26 Mayo 2019, 19:00 pm por NEBIRE » En línea

Serapis
Colaborador
***
Desconectado Desconectado

Mensajes: 3.391


Ver Perfil
Re: Juego Python OhNO
« Respuesta #2 en: 26 Mayo 2019, 19:00 pm »

Me autorespondo...
Después de mirar el código, veo que la cuenta es de vecinas (contiguas) en la misma fila y columna, excluyéndose a sí misma de dicha cuenta. La aparición de una pieza roja, rompe la contigüidad.
Y el objetivo final es descubrir el color original de cada ficha en el mapa.

Personalmente creo que en la cuenta debiera contarse a sí misma y reflejarse así en el juego, es más fácil contar todas que saltarse una...

Buscando, por la red, básicamente no hay gran cosa (buscando OhN0), pero pude alcanzar a encontrar que el juego originalmente en japonés se llama Kuromasu y por ahí si que aparece ya bastante información...

He hecho una simple aproximación (en pseudocódigo). Pero me doy cuenta que al hacerlo de forma sencilla, para elegir las piezas de dar pistas, al azar, puede dar lugar a múltiples soluciones válidas (de momento he optado por dejar como piezas fijas, tantas como un valor al azar entre 1/4 y 1/3 del total de las piezas del tablero), originado precisamente a causa de aquellas piezas que no son 'tocadas' por ninguna de las fijas que ofrecen las pistas... pero bueno es una primera aproximación... ya lo miraré más en profundidad cuando pase a la implementación.

Mirando tu ejemplo 'solved', se alcanza a ver esto mismo que acabo de decir, por ejemplo la última fila... al no tener pistas, no se ve forzado, luego da igual si se ponen rojas o azules... Más aún las dos ultimas filas, salvo las dos rojas, no importa de qué color sea el resto, ofrece pués múltiples soluciones válidas.

En definitiva, lo que trato de decir es que el juego sería más interesante (y también más difícil) si no tuviera múltiples soluciones, o al menos restringirlo lo más posible.

En línea

dijsktra

Desconectado Desconectado

Mensajes: 110


Mr Edsger Dijsktra (Tribute to)


Ver Perfil
Re: Juego Python OhNO
« Respuesta #3 en: 27 Mayo 2019, 10:31 am »

Gracias NEBIRE, no veas lo que aprecio tu respuesta!
Veo que has comrpendido  a la perfección todo el sistema.


Me autorespondo...
Después de mirar el código, veo que la cuenta es de vecinas (contiguas) en la misma fila y columna, excluyéndose a sí misma de dicha cuenta. La aparición de una pieza roja, rompe la contigüidad.


Es así, y veo que tu redacción es mejor que la mía. Con tu permiso, lo cambiaré en el mensaje original.


Y el objetivo final es descubrir el color original de cada ficha en el mapa.


Bueno, esto no es exacto. El objetivo final es cumplir las restricciones de las fichas azules iniciales, llenado todo el tablero al 100% y esto no es siempre una solución única (más abajo te explico)

Personalmente creo que en la cuenta debiera contarse a sí misma y reflejarse así en el juego, es más fácil contar todas que saltarse una...

Yo también pensaba así, pero las reglas no dicen eso. Por eso puse la ayuda del par de números (A/B), (las que veo, las que debo ver). Esta claro que los maestros orientales del Japón valoraban la paciencia del conteo como forma de "concentración"  ;D ;D  (Vamos, eso creo yo...)

...

He hecho una simple aproximación (en pseudocódigo). Pero me doy cuenta que al hacerlo de forma sencilla, para elegir las piezas de dar pistas, al azar, puede dar lugar a múltiples soluciones válidas (de momento he optado por dejar como piezas fijas, tantas como un valor al azar entre 1/4 y 1/3 del total de las piezas del tablero), originado precisamente a causa de aquellas piezas que no son 'tocadas' por ninguna de las fijas que ofrecen las pistas... pero bueno es una primera aproximación... ya lo miraré más en profundidad cuando pase a la implementación.

Mirando tu ejemplo 'solved', se alcanza a ver esto mismo que acabo de decir, por ejemplo la última fila... al no tener pistas, no se ve forzado, luego da igual si se ponen rojas o azules... Más aún las dos ultimas filas, salvo las dos rojas, no importa de qué color sea el resto, ofrece pués múltiples soluciones válidas.

TOTALMENTE de acuerdo, tienes razón.
En definitiva, lo que trato de decir es que el juego sería más interesante (y también más difícil) si no tuviera múltiples soluciones, o al menos restringirlo lo más posible.


Pero el juego no descarta que haya multiples soluciones... Naturalmente, si restringes como dices las fichas, no es que es sea más difícil, al contrario, es más fácil, porque las posiciones se pueden "deducir" ("forzar", en tus propios términos) y no especular.

El problema entonces, es que, jugando con las restricciones de las fichas azules, su posición y la aritmética, puedes llegar a restringirlo tanto QUE NO TENGA solución.

Fijate en el siguiente ejemplo, surgido con fichas al azar, cambiando el código en la línea 180.

Código
  1.        blues,reds,r =  set(),set(), Random()
  2.        # An eye: there is no warrant to have unique solution for OhNO
  3.        # ever one in case!
  4.        # This is random... Hard to justify loop's termination (statistics)
  5.        while (len(blues)<(pow(self.N,2) / 4)):
  6.            blues.add((r.choice(range(self.N)),r.choice(range(self.N))))
  7.       while (len(reds)<(pow(self.N,2) / 8)):
  8.            reds.add((r.choice(range(self.N)),r.choice(range(self.N))))
  9.            reds.difference_update(blues)
  10.        #
  11.        #blues=[(0,1), (0,2), (1,1)]  <===============   Commented
  12.        #reds =[(0,0)]    <===============   Commented
  13.  
  14.  

Ahora el problema es irresoluble! La casilla 1,2 requiere ver tres fichas. Una por arriba, (hecho) , pero por abajo no puede (LOCK) y por la izquierda tampoco (poruqe  a lo podr'a llegar a ver una!)




Y es ahí donde  quiero llegar... No puedo poner unos valores de entrada para que el esforzado humano, después de jugar 4 horas, se de cuenta de que no tiene solución.

Tendré que diseñar un algoritmo de inteligencia artificial de expliración de estados (vamos, un backtraking de to la vida, que es cualquier cosa menos inteligente, es pura fuerza bruta) y exponer una combinación que tenga solución.

Se me está ocurriendo luego poner un boton de "Me rindo!!!" en el que pulsando salga UNA  de las posibles soluciones....


Un abrazo!
« Última modificación: 27 Mayo 2019, 10:51 am por dijsktra » En línea

Si la depuración es el proceso de eliminar fallos en el software, entonces programar debe ser el proceso de ponerlos dentro. (Edsger Dijsktra)
Serapis
Colaborador
***
Desconectado Desconectado

Mensajes: 3.391


Ver Perfil
Re: Juego Python OhNO
« Respuesta #4 en: 27 Mayo 2019, 22:59 pm »

Como te decía buscando por Kuromaso (o Kurodoku) encontré incluso las reglas originales, ellos añaden dos reglas que francamente no veo de interés aunque si que (en principio, por intuición), parecen dejar menos opciones a múltiples posibles soluciones...

- Las fichas 'rojas' (las de bloqueo), no pueden tocarse sino solo diagonalmente.
- Todas las fichas 'azules' , deben estar conectadas entre sí (es decir impide un grupo aislado por su cuenta).
También utilizan la cuenta total de las fichas vecinas (incluyendo la afectada), pero vamos, respecto de esto es suficiente con que el jugador sepa cual es la cuenta (si se aclara en las normas, es suficiente). A cada cual le parece más natural una cifra o la otra... pero sin más importancia que saberlo para atenerse a ello, en el juego.

...éste juego fue publicado por la revista japonesa Nikoli dedicada a pasatiempos, puzzles... allá por el 1991... el nombre del juego traducido literalmente viene a decir: 'donde están las casillas negras' (ellos juegan con blancas y negras, algo razonable al ser una publicación en papel)...
https://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html

Cita de: dijsktra
Pero el juego no descarta que haya multiples soluciones... Naturalmente, si restringes como dices las fichas, no es que es sea más difícil, al contrario, es más fácil, porque las posiciones se pueden "deducir" ("forzar", en tus propios términos) y no especular.
Me parece bien que haya más de una solución... Y sí, mi interés viene dado en determinar si es factible fijar niveles de dificultad en base a ello.

Establecer niveles de dificultad en los juegos, suele ser a menudo quizás la parte más complicada de fijar de forma sólida y congruente en un juego, por ejemplo hace escaso días me descargué un juego de ajedrez para el móvil y la sensación clara es que la dificultad la basa en "dejarse comer piezas tontamente" durante el juego (movimientos que una persona no hace salvo un despiste fortuito).

Cita de: dijsktra
El problema entonces, es que, jugando con las restricciones de las fichas azules, su posición y la aritmética, puedes llegar a restringirlo tanto QUE NO TENGA solución.
Bueno, esto depende de la implementación que se haga, la forma en que lo tengo planteado no se daría ese caso. Aunque sí que habría de requerir más código para suplir esas restricciones.

Cita de: dijsktra
Tendré que diseñar un algoritmo de inteligencia artificial de expliración de estados (vamos, un backtraking de to la vida, que es cualquier cosa menos inteligente, es pura fuerza bruta) y exponer una combinación que tenga solución.
No lo veo necesario. Ni siquiera un backtracking...
Te expongo mi pseudocódigo...y mejor, también debajo un código que hice ayer en un rato partiendo del mismo (eso sí, en vb6, que es lo que me gusta usar para cositas así).


Paso 0: Establecer cantidades iniciales  y el tamaño de los 2 arrays que se usarán...
Partiendo del tamaño del tablero (aunque por defecto sea cuadrado, nada impide que sea rectangular), se calcula la cantidad de bolas/piezas/fichas del tabero.
- Y también cuantas de ellas podrían ser Rojas: (de entrada para probar he puesto un valor elegible al azar entre la raíz cuadrada y 1/3 de la cantidad de piezas del tablero. Si el tablero es cuadrado, lógicamente la raízcuadrada = filas = columnas.
- Así por ejemplo, para un tablero de 6x6=36 bolas, las rojas serían entre 6 y 12... Si salieran 10, entonces las 26 restantes serían azules.
Código:
CantidadPiezas = (filas * columnas)
CantidadRojas = random (entre (sqr(Cantidadpiezas) y (Cantidadpiezas\3)) )
CantidadAzules = (CantidadPiezas - CantidadRojas)

// este array se declara del tipo de datos: 'integer' es suficiente.
array Bolas(CantidadPiezas)  // unidimensional
// este array se declara del tipo de datos de la enumeración (que pongo) más abajo: ValorCasillas
array Mapa(filas, Columnas) // bidimensional

Paso 1:  Llenar el 'bombo de la lotería con todas sus bolas'...
Ahorra lleno un array (unidimensional), con todas las bolas que han de componer el tablero, es decir metemos en el saco todos los ingredientes en la cantidad que cada uno deba tener.
Si por ejemplo rojas= 10, entonces azules = 36-10 = 26
Luego el array primero se llena con las rojas en posiciones de 0 a 9, las azules desde 10 hasta 35

Código:
Enumeracion ValorCasillas
   VACIA=0            // Casilla vacía, cambable por el jugador.
   AZUL = 1            // Casilla Azul, cambiable por el jugador.
   ROJA = 2           // Casilla Roja, cambiable por el jugador.
   AZULFIJA = 3     // Casilla Azul fija (que mostrará el número de contiguas). No es cambiable por el jugador.
   ROJAFIJA = 4     // Casilla Roja fija. No es cambiable por el jugador.
   AZULFALLO = 5   // Casilla azul un valor transitorio, para indicar al jugador que falla la cuenta.
fin enumeracion

Array bolas(cantidadPiezas)
Bucle para desde 0 hasta Cantidadrojas -1
   Bolas(k) = ROJA
siguiente
Bucle para desde Cantidadrojas hasta CantidadPiezas -1
   Bolas(k) = AZUL
siguiente

Paso 2: Barajar las bolas del array
Ya están todas en el saco, pero estan ordenadas, así que toca barajarlas, lo hacemos una o mas veces con el algoritmo de Fisher-Yates-Durstenfeld
Para barajar más de 1 vez... basta meterlo dentro de otro bucle.
Código:
bucle para k desde (CantidadPiezas - 1) hasta 1 regresivamente 
    index = random (entre 1 y k)
    tmp = Bolas(k): Bolas(k) = Bolas(index): Bolas(index) = tmp
Siguiente


Paso 3: Pasarlas al array bidimensional...
Ya están barajadas, pero las tenemos en un array unidimensional, para el juego acomoda tenerlas en uno bidimensional, para no estar contínuamente convirtiendo fias y columnas a un indice absoluto... aunque aclaro que al final se mantienen ambos arrays, cada uno con su propósito (total son muy pequeños y el espacio ocupado es perfectamente asumible).
Código:
bucle para j desde 0 hasta filas-1
    bucle para k desde 0 hasta columnas-1        
        Mapa(j, k) = Bolas(n)
        n = (n + 1)
    Siguiente
Siguiente


Paso 4: Calcular la 'contigüidad' del mapa...
Una vez que se han pasado las piezas al array bidimensional se puede dibujar para ver como queda, y entender mejor el siguiente paso.
Ahora lo que vamos a hacer es contar las vecinas de cada azul, y el resultado se deja almacenado en el array unidimensional.
También calcula los puntos de la partida: Sería la suma dle valor que dan todas las casillas con pistas (tomar como puntuación la cantidad de casillas dle mapa, es algo soso).
Código:
n = 0: PuntosPartida = 0
Bucle para j desde 0 hasta filas - 1
    bucle para k desde 0 hasta columnas-1
        Si (Bolas(n) = AZUL)
            cuenta = ContarVecinas(j, k)  // esta es una función que se pone debajo seguido...
            Si (cuenta = 0) // se trata de una bola azul aislada, se convierte a roja.
                Mapa(j, k) = ROJA   // de momento solo son rojas o azules, no hay fijas ni vacías.
            fin si
            Bolas(n) = cuenta  
            PuntosPartida = (PuntosPartida + cuenta)
        Sino
            Bolas(n) = 0
        Fin si
        n = (n + 1)
    siguiente    
Siguiente

La función contarvecinas, simplemente recibe como parámetros fila y columna para contar las azules contiguas a la casilla referida por la intersección de fila y columna.
El algoritmo puede hacerse recursivo, pero resulta más claro de asimilar dejando 4 secciones iterativas.
Cada sección recorre una dirección y sentido de los 4 puntos cardinales, no veo necesario crear una enumeración para esto, pero tampoco sobra si lo deja más claro.
Cada bucle acaba cuando la bola deje de ser 'pseudoAzul' (azul o azulFija) o cuando se alcance el tope del tablero (en la dirección de avance).
Forzamos la entrada en cada bucle, por lo que la casilla de referencia será contada 4 veces (por ello inicialmente la cuenta se establece a -4)
 ...pero así bastan las condiciones dentro del propio bucle para salir fuera (sin un condicional delante de cada bucle).
En lenguajes donde s epermite especificar límites inferoes a 0, dmensionar el array desde -1 a 1 más de la cantidad, simplifica los bucles, pués las condiciones pueden ponerse juntas al comienzo del bucle, ya que no incurriríamos en un error fuera de rango del array
Código:
// Propósito: Cuenta las 'bolas' azules vecinas en la fila y columna para la bola en dicha ubicación, lo que llaman 'visibilidad'...
entero = Funcion ContarVecinas(entero Fila, entero Columna)
    entero k, limite, n
    
    n = -4  // se descontará en cada bucle...hasta llegar a 0.
    
    // ------------- Horizontales ----------------
    
    k = Columna
    limite = 0
    Hacer  // Contar vecinas a izquierda...
        n = (n + 1)
        k = (k - 1)
        Si (k < limite) salir del bucle
    Repetir Mientras (Mapa(Fila, k) And AZUL)   // mientras bola sea azul o azulfija
    
    k = Columna
    limite = (Columnas - 1)
    Hacer // Contar vecinas a derecha...
        n = (n + 1)
        k = (k + 1)
        Si (k > limite) Salir del bucle
    Repetir Mientras (Mapa(Fila, k) And AZUL)
    
    // ------------ Verticales -------------------
    
    k = Fila
    limite = 0
    Hacer // Contar vecinas hacia arriba...
        n = (n + 1)
        k = (k - 1)
        Si (k < limite) salir del bucle
    Repetir Mientras (Mapa(k, Columna) And AZUL)
  
    k = Fila
    limite = (Filas - 1)
    Hacer // Contar vecinas hacia abajo...
        n = (n + 1)
        k = (k + 1)
        Si (k > limite) salir del bucle
    Repetir Mientras (Mapa(k, Columna) And AZUL)
    
    Devolver n
Fin Funcion

El array 'Bolas' ahora tiene por contenido cuantas vecinas a la azul en dicha posición tiene el mapa a la hora de generarlo... y el array mapa, el valor de las casillas actual (roja/azul)

Paso 5: Decidir cuantas y cuales se hacen 'fijas'...
 El número de fijas (antes) era: entre 1/3 y 1/4 del total (rojas + azules). Cambiado ahora a un poquito más: ((1/4  y (1/3 + filas\2))    
Ejemplos:
---- para 04x04= 1/4 = 004; 1/3 = 005, luego se elige un valor entre 004 y (005 + 2) bolas fijas.
---- para 06x06= 1/4 = 009; 1/3 = 012, luego se elige un valor entre 009 y (012 + 03) bolas fijas.
---- para 09x09= 1/4 = 020; 1/3 = 027, luego se elige un valor entre 020 y (027 + 04) bolas fijas.
---- para 25x25= 1/4 = 156; 1/3 = 218, luego se elige un valor entre 156 y (254 + 12) bolas fijas.

Las bolas a mantener fijas se eligen al azar, con las restricciones:
  A - Si es azul, el valor de vecindad no puede ser 0 (se elige pués otra). No es preciso aplicarla (se deja comentada) porque en el paso anterior estas se forzaron a ROJA.
  B - Si ya está marcada como fija, se elige otra.
  C - Fijas azules debe ser mayor que fijas rojas (los tableros más pequeños pueden tener una proporción más similar entre ellas), y proporcionarían pocos datos (esta opción no se refleja en el pseudocódigo, peo si en el código en VB6 más abajo)
  
Elige al azar un valor entre 1/4 y 1/3. por ejemplo para 6x6= 1/4= 9; 1/3=12, luego se eleige un valor entre 9 y 12 bolas fijas.

Código:
    Max = (Cantidad \ 3) // en el paso 1, ó 2 ya se calculó esto con el mismo valor.
    Min = ((Cantidad \ 4) - 4)  
    NumPistas = Random (entre Min y Max) + Sqr(Cantidad)
    n = 0
    Hacer
        Hacer
            index = Random (entre 0 y Cantidad-1)
            // pasar indice absoluto a fila y columna:
            j = (index \ Columnas): k = (index Modulo Columnas)
            color = s_Mapa(j, k)
        Repetir Mientras (color >= AZULFIJA)  // la condición B, anotada
                                         // ó ((color = AZUL) y (Bolas(index) = 0))  // la condición A anotada

        // Si es azul queda en azul fija, si es roja queda en rojafija
        Mapa(j, k) = (Mapa(j, k) + 2)  
        n = (n + 1)
    Repetir Mientras (n < NumPistas)


Paso 6 (y último): Borrar las no fijas del mapa.
Ya casi está listo, solo falta borrar bolas para despejar el mapa con bolas vacías. Vacías son pués todas las que no sean fijas...
Los pasos 1 y a 5, son todos de la función MapaGenerar (tablero), este paso también puede ir dentro, pero si se deja fuera porque así puede ser reinvocado por el jugador para borrar todo de un plumazo y volverlo a intentar... por ello se describe como una función aparte:
Código
  1. // Propósito: Borra el estado actual del mapa y lo muestra como al inicio de la partida.
  2. funcion MapaReset()
  3.    entero j, k, n
  4.  
  5.    bucle para j desde 0 hasta filas - 1
  6.        bucle para k desde 0 hasta Columnas - 1
  7.            si (Mapa(j, k) < AZULFIJA) Then
  8.                Mapa(j, k) = VACIA
  9.                Bolas(n) = 0  // esto s epone a 0, para tolerar disferentes soluciones.
  10.            fin si
  11.            n = (n + 1)
  12.        Siguiente
  13.    Siguiente
  14.  
  15.    NumVacias = (n - NumPistas)
  16.  
  17.    llamada a MapaDibujar   //ya se puede jugar...
  18. fin funcion
  19.  

Aclarar también que el código anterior al paso 0, puede dejarse aparte, pués el tamaño de los arrays y por tanto la cantidad de bolas, solo cambia cuando el jugador decide otro valor para filas y/o columnas.


El código es más 'breve' sin tantas explicaciones, claro...
Código
  1. ' Crea las bolas, las baraja, las asigna al mapa,
  2. '  calcula los valores y decide cuales muestra (y borra el resto).
  3. Private Sub MapaGenerar()
  4.    Dim j As Integer, k As Integer, n As Integer, t As Integer
  5.    Dim Cantidad As Integer, Max As Integer, Min As Integer
  6.    Dim col As ValorCasillas
  7.  
  8.  
  9.    Cantidad = (p_Columnas * p_Filas)
  10.    ReDim r_Bolas(0 To Cantidad - 1)
  11.  
  12.    ' 0-Decidir cuantas serán rojas (el resto azules).
  13.    Min = Int(Sqr(Cantidad) + 1): Max = (Cantidad / 3)
  14.    n = Int((Max - Min + 1) * Rnd + Min) - 1
  15.  
  16.    ' 1-Generar las bolas
  17.    For k = 0 To (n - 1)
  18.        r_Bolas(k) = CASILLA_ROJA ' al principio las las rojas.
  19.    Next
  20.    For k = n To (Cantidad - 1)
  21.        r_Bolas(k) = CASILLA_AZUL  ' luego las azules.
  22.    Next
  23.  
  24.    ' 2-Barajar (2 veces):
  25.    For j = 0 To 1
  26.        For k = (Cantidad - 1) To 1 Step -1
  27.            t = Int((k + 1) * Rnd)
  28.            n = r_Bolas(k): r_Bolas(k) = r_Bolas(t): r_Bolas(t) = n
  29.        Next
  30.    Next
  31.  
  32.    ' 3-Pasarlas al mapa bidimensional...
  33.    n = 0
  34.    For j = 0 To p_Filas - 1
  35.        For k = 0 To p_Columnas - 1
  36.            s_Mapa(j, k) = r_Bolas(n)
  37.            n = (n + 1)
  38.        Next
  39.    Next
  40.  
  41.    ' para debug: ver el estado actual y compararlo una vez resuelto (haciendo captura de éste y aquel)
  42.    #If DebugOn = True Then
  43.        Call MapaDibujar: Me.Show
  44.    #End If
  45.  
  46.    ' 4-Calcular el mapa (los valores de suma se guardan en el array 'bolas')
  47.    '   También calcula los puntos de la partida...
  48.    n = 0 ': p_PuntosPartida = 0
  49.    For j = 0 To p_Filas - 1
  50.        For k = 0 To p_Columnas - 1
  51.            If r_Bolas(n) = CASILLA_AZUL Then
  52.                t = ContarVecinas(j, k)
  53.                If (t = 0) Then  ' se trata de una bola azul aislada, se convierte pués a roja.
  54.                    s_Mapa(j, k) = CASILLA_ROJA
  55.                End If
  56.                r_Bolas(n) = t
  57.                ' p_PuntosPartida = (p_PuntosPartida + t) ' si s eprefiere suma de valores más grandes por partida tomar este y comentar el siguiente.
  58.            Else
  59.                r_Bolas(n) = 0
  60.            End If
  61.            n = (n + 1)
  62.        Next
  63.    Next
  64.  
  65.    ' 5-Decidir cuantas y cuales se hacen 'fijas'...
  66.    Min = (Cantidad \ 4)
  67.    Max = ((Cantidad\3) + (Sqr(Cantidad) \ 2))
  68.    s_NumPistas = Int((Max - Min + 1) * Rnd + Min)
  69.    n = 0: rj=1: p_PuntosPartida = 0
  70.    Do
  71.        Do
  72.            t = Int(s_Cantidad * Rnd)
  73.            j = (t \ p_Columnas): k = (t Mod p_Columnas)
  74.            Color = s_Mapa(j, k)
  75.        Loop While ((Color >= CASILLA_AZUL_FIJA) Or ((Color = CASILLA_ROJA) And (az <= rj)))
  76.         ' Or ((Color = CASILLA_AZUL) And (r_Bolas(t) = 0)) ' esto ya se garantiza por en un paso previo las azules de vecindad 0 se cambiaron a rojas.
  77.        s_Mapa(j, k) = (Color + 2) ' azul+2 = azulFija; roja+2 = rojafija
  78.  
  79.        ' Lleva la cuenta de fijas rojas y azules, si la proporción entre ellas no satisface (para la restriccción 'C' )
  80.        ' Solo resulta de interés en tableros muy pequeños, donde la probabilidad de elgir al azar 4-6 fijas elija demasiadas rojas (nótese que rojas se establece a 1, aún cuando es 0.
  81.        ' Así se evita que en un tablero de 4x4, salgan 4 fijas y 2 sean rojas).
  82.        If (Color = CASILLA_AZUL) Then
  83.            az = (az + 1)
  84.        Else
  85.            rj = (rj + 1)
  86.        End If
  87.        p_PuntosPartida = (p_PuntosPartida + r_Bolas(t))
  88.  
  89.        n = (n + 1)
  90.    Loop While (n < s_NumPistas)
  91.  
  92.    ' 6-Borrar las no fijas del mapa.
  93.    Call MapaReset
  94. End Sub
  95.  
  96. ' Borra el estado actual del mapa y lo dibuja como al inicio de la partida.
  97. ' 6-Borrar las no fijas del mapa.
  98. Private Sub MapaReset()
  99.    Dim j As Integer, k As Integer, n As Integer
  100.  
  101.    For j = 0 To p_Filas - 1
  102.        For k = 0 To p_Columnas - 1
  103.            If (s_Mapa(j, k) < CASILLA_AZUL_FIJA) Then
  104.                s_Mapa(j, k) = CASILLA_VACIA
  105.                r_Bolas(n) = 0
  106.            End If
  107.            n = (n + 1)
  108.        Next
  109.    Next
  110.  
  111.    s_NumVacias = (n - s_NumPistas)
  112.  
  113.    Call MapaDibujar
  114. End Sub
  115.  

A medida que se pulsan las bolas, se descuentan las vacías, esto es, si una es roja al pulsar pasa a vacía, (sumamos 1 vacía) y al revés cuando es vacía al pulsar pasa a azul, restamos 1 vacía). Así solo se comprueba la solución si no quedan vacías.

Esta sería la función cuando se pulsa en el tablero...
Código
  1. Private Sub PicTablero_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  2.    Dim Fila As Integer, Columna As Integer, color As ValorCasillas
  3.  
  4.    If (s_Jugando = True) Then
  5.        Call VerificarBolaFallo   // Si había una bola (azul) indicando fallo de suma, se redibuja normal...
  6.  
  7.        Columna = (X \ SIZECASILLA)  ' una constante que manrtiene cuantos píxeles ocupa una casilla.
  8.        Fila = (Y \ SIZECASILLA)
  9.  
  10.        color = s_Mapa(Fila, Columna)
  11.        ' vacía, roja y azul, pueden ser cambiadas.
  12.        If (color < CASILLA_AZUL_FIJA) Then
  13.            ' Actualiza la cuenta de casillas vacías...
  14.            If (color = CASILLA_VACIA) Then
  15.                s_NumVacias = (s_NumVacias - 1)
  16.            ElseIf (color = CASILLA_ROJA) Then
  17.                s_NumVacias = (s_NumVacias + 1)
  18.            End If
  19.  
  20.            color = ((color + 1) Mod 3)
  21.            s_Mapa(Fila, Columna) = color
  22.            Call DibujarBola(Fila, Columna, color )
  23.            Call MapaValidar  // La validación solo se realizará si numvacias = 0
  24.        Else
  25.            Beep
  26.        End If
  27.    End If
  28. End Sub

Validar si se cumple el mapa.
Aborta si el mapa no está completo aún...
Si llega al final de los bucles, quedó validado, la partida acaba y anota puntos.
Código
  1. Private Sub MapaValidar()
  2.    Dim j As Integer, k As Integer, n As Integer
  3.  
  4.    ' Es preferible validar solo cuando esté completo, para así saber que ahora se puede dibujar 'Azul_Fallo'...
  5.    If (s_NumVacias = 0) Then
  6.        For j = 0 To p_Filas - 1
  7.            For k = 0 To p_Columnas - 1
  8.                ' OJO: 'AND' no '=', ya que puede ser Azul=1 o AzulFija =3, azulFallo solo es para dibujar, el estado nunca se establece en el mapa.
  9.                If (s_Mapa(j, k) And CASILLA_AZUL) Then  ' azul, azulfija, azulfallo
  10.                    If (r_Bolas(n) > 0) Then
  11.                        If (ContarVecinas(j, k) <> r_Bolas(n)) Then
  12.                            Call MarcarBolaFallo(k, j)  ' esta bola se dibuja ahora de un modo especial..
  13.                            Exit Sub
  14.                        End If
  15.                    Else ' fuerza una supuesta solución para bolas sin numeración.
  16.                        ' NOTA: el valor asignado en negativo, así con reset no estorban a los valores de fijas pués la suma de su vecindad es positiva (y mayor que 0)
  17.                        r_Bolas(n) = -ContarVecinas(j, k)
  18.                    End If
  19.                End If
  20.                n = (n + 1)
  21.            Next
  22.        Next
  23.  
  24.        ' Al llegar aquí: MapaValidado
  25.        Call PartidaFin  ' sumará los puntos de partida y solicitará si se quiere jugar otra partida...
  26.    End If
  27. End Sub
  28.  

Un mapa se dibuja (completo) dos veces durante una partida (luego solo se dibuja la bola pulsada):
 - La primera vez (justo al incio de partida), mostrará los valores elegidos al azar.
 - La segunda vez (justo al final de partida), mostrará (además) los valores ficticios de la solución alternativa (valores menores que 0).
 - Tantas otras veces como el jugador haga un 'reset'.
Código
  1. Private Sub MapaDibujar()
  2.    Dim j As Integer, k As Integer, n As Integer, t As Integer
  3.  
  4.    For j = 0 To p_Filas - 1
  5.        For k = 0 To p_Columnas - 1
  6.            If (s_Mapa(j, k) And CASILLA_AZUL) Then
  7.                n = Abs(r_Bolas(t))  ' Valor absoluto porque: (las azules no fijas se puso valor negativo al validar).
  8.            Else
  9.                n = 0
  10.            End If
  11.  
  12.            Call DibujarBola(j, k, s_Mapa(j, k), n)  ' eel parámetro 'n' indicará que si es mayor que 0, se dibujo también el valor encima de la casilla.
  13.            t = (t + 1)
  14.        Next
  15.    Next
  16. End Sub

En fin, se pueden generar tableros hacia adelante de forma simple sin 'reintentos' ni backtracking... En vez de inventarse valores y luego tratar de buscar un mapa que encaje con los valores se genera un mapa y luego se calculan sus valores, tal como señalaba más arriba en el pseudocódigo y (el código en vb6)

Una modificación interesante (como opción) sería cumplir el requisito de la regla japonesa que especifica que las rojas no pueden estar '4-contiguas', tan solo admitida como diagonal... al caso podría intercalarse entre el paso 3 y el 4, otro bucle que verifique si hay dos rojas contiguas en cuyo caso se puede intercambiar una de ellas por una azul a una fila (en la misma columna) o  a una columna (en la misma fila) libre... Dado que el número de rojas es muy inferior al de azules se puede asegurar que siempre puede lograrse...

Y al final unas imágenes de como queda...
La primera imagen es forzado a ser dibujada, tras el paso 3, para ver como es el mapa original (los valores numéricos no reflejan la realidad porque es prematuro disponer de dicho valor):


La segunda imagen es como queda tras el paso 5 (elegir al azar las fijas) y el paso 6 (borrar el resto), listo para empezar la partida:


La tercera es cuando está casi resuelto, las casillas vacías, que se han dejado, puede verse que libremente pueden elegirse si rojas o azules (excepto la de la esquina inferior derecha, que al parecer una regla dice que si una bola azul, no tiene vecinas (está rodeada de rojas, debe ser roja). En mi opinión 'don't care'...


La 4 imagen es la del tablero totalmente resuelto, y puede compararse con la primera, y ver que el mapa es ligeramente distinto (y podría ser lo más si se hubiera elegido otros valores paras las vacías señaladas en la imagen 3). He optado por poner de blanco los números d elas pistas y de negro los números de las 'autocuenta'... (aquellos donde pusimos un valor negativo en las cuentas).
Dicho de otro modo, si se resuelven bien los valores de las pistas se considera resuelto el tablero...




Cita de: dijsktra
Se me está ocurriendo luego poner un boton de "Me rindo!!!" en el que pulsando salga UNA  de las posibles soluciones....
No es mala idea... a mi me pareció interesante que si uno se lía en exceso en vez de tener que borrar una a una, mejor un boton/tecla para borrar todo y dejar el tablero como estaba al inicio (que pueda volver as intentar el mismo tablero)... Útil sobre todo si un mapa es ya de cierto tamaño (10, 20,25 x...).

También debe notarse que el algoritmo de buscar vecinas no está optimizado es simple y funciona bien para tableros de tamaño 'real' con los que un jugador vaya a enfrentarse, pero para tableros de pongamos 1000x1000 casillas sería bastante ineficiente...
Un modo de hacerlo más óptimo (a base de usar más memoria como casi siempre), es disponer de sendos arrays de cuentas adicionales...
Entonces en uno se hace un barrido por filas (en el otro igual pero por columnas): en la fila 1, tras una roja o el comienzo y hasta la siguiente roja o final, todas ellas tienen las vecinas recién calculadas, luego a todo ese grupo se las anota el mismo valor, ejemplo:

AAARRAAAAARARAAARAA
333--55555-1-333-22



Código:
Bucle para j desde hasta filas-1
    n=0
    Hacer
        Si mapa(j, n) = azul
            inicio = n
            Hacer
                n +=1
                Si (n = columnas)
                    Mapafilas(j, n) = 0 // casilla roja
                    salir del bucle
                fin si
            Repetir mientras mapa(j, n) = azul
            cuenta = (n - inicio)
            Hacer
                MapaFilas(j, inicio) = cuenta    
                inicio +=1
            mientras (inicio < n)
        sino
            Mapafilas(j, n) = 0 // casilla roja
        Fin si
        n +=1
    Repetir mientras (n < columnas)
Siguiente
Igualmente hacer lo mismo en otro array para las columnas, es decir verticalmente.
Ahora la cuenta de vecinas de cada casilla será la suma que arroja la casilla de ambos arrays, para cada casila (-2, pués se ha contado a sí misma 2 veces).

Ahora al validar, se invoca previamente 1 sola vez dicha función (ContarVecinas)...y luego:
Código:
Si (Bolas(n) > 0) Then
    Si ((MapaFilas(j, k) +MapaColumnas(j,k) -2) <> Bolas(n))
         llamada a MarcarBolaFallo(j,k)
         Devover false // Salir, validación no pasada...
    fin si
....    
Así para 1000*1000 bolas, basta una suma, en vez de una búsqueda para contar en una fila de 1000 y en una columna de 1000... (esta búsqueda se hará por tanto 1000+1000 veces y no 1000*1000 veces)

...al final entre código y pesudocódigo,  imágenes y explicaciones ha salido un mensaje largo...


p.d.: Editado para reponer 2 imágenes caídas y corregir un pequeño gazapo:
Donde pone:
Código
  1. k = (index \ Filas): j = (index Modulo Columnas)
Debiera poner:
Código
  1. j = (index \ Columnas): k = (index Modulo Columnas)
Primero porque estaban intercambiados los indices ('j' por 'k') y segundo porque si el tablero resultara no ser cuadrado, estaría apuntado a una casilla distinta...
« Última modificación: 29 Mayo 2019, 17:39 pm por NEBIRE » En línea

dijsktra

Desconectado Desconectado

Mensajes: 110


Mr Edsger Dijsktra (Tribute to)


Ver Perfil
Re: Juego Python OhNO
« Respuesta #5 en: 3 Junio 2019, 16:56 pm »

Citar
Tendré que diseñar un algoritmo de inteligencia artificial de expliración de estados (vamos, un backtraking de to la vida, que es cualquier cosa menos inteligente, es pura fuerza bruta) y exponer una combinación que tenga solución.
Citar
No lo veo necesario. Ni siquiera un backtracking...

Hola! Tus comentarios fueron muy útiles...! En otro correo cuando pueda respondo cómo me ayudó.
Como tu dices, no era necesario.

Vayan aquí unas fotos del juego que YA es practicable.  https://github.com/rafaelm53539600/OhnO

Os lo descargáis con el botón verde, que dice "Download zip".
 Después sólo que usar python 3, (no vale con python 2), y se invoca así.

Código:
python ohno.py








Recuerdo las reglas:

  • Una misma ficha azul puede ver a las otras azules contiguas en su misma fila y columna, excluyendo ella misma
  • Las fichas rojas rompen la contigüidad entre las azules
  • Haz click en las fichas para cambiar sus colores. Las fichas "clavadas" del principio no se pueden cambiar
  • Los numeros (A/B) describen cuantas fichas azules debe ver cada ficha azul (B) y cuantas ve relamente (A).
  • Como mínimo, cualquier ficha azul debe ver otra ficha azul
« Última modificación: 4 Junio 2019, 10:05 am por dijsktra » En línea

Si la depuración es el proceso de eliminar fallos en el software, entonces programar debe ser el proceso de ponerlos dentro. (Edsger Dijsktra)
Páginas: [1] Ir Arriba Respuesta Imprimir 

Ir a:  

Mensajes similares
Asunto Iniciado por Respuestas Vistas Último mensaje
Juego Python « 1 2 »
Scripting
SirLanceCC 11 8,985 Último mensaje 11 Julio 2006, 23:27 pm
por Division-x
Juego de la vida en Python
Scripting
SirLanceCC 1 6,366 Último mensaje 9 Diciembre 2006, 14:39 pm
por SirLanceCC
Ver OHnO con el VLC atraves de volcado de red
Multimedia
hellracer0 0 2,727 Último mensaje 30 Enero 2008, 21:26 pm
por hellracer0
Juego ejercicios [Python] « 1 2 ... 15 16 »
Ejercicios
Masita 150 110,840 Último mensaje 30 Mayo 2011, 20:44 pm
por caro.bello
Juego de damas en python
Ejercicios
alex284 3 7,121 Último mensaje 20 Marzo 2014, 05:55 am
por Mitsu
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines