Conecta 4: ¡Encuentra lo falso!

34

El banco ha sido forzado, y todos los matones de la mafia local tienen una coartada inusual: ¡estaban en casa jugando Connect 4! Para ayudar con la investigación, se le pide que escriba un programa para validar todos los paneles de Connect 4 que se han incautado con el fin de verificar que las posiciones son realmente posiciones de un juego válido de Connect 4 y que no se han reunido apresuradamente tan pronto como la policía llamó a la puerta.

Las reglas para conectar 4: jugadores Ry Yturnarse para soltar fichas de su color en columnas de una cuadrícula de 7x6. Cuando un jugador deja caer una ficha en la columna, se cae para ocupar la posición más baja sin llenar en esa columna. Si un jugador logra obtener una carrera horizontal, vertical o diagonal de cuatro fichas de su color en el tablero, entonces gana y el juego termina inmediatamente.

Por ejemplo (con el Rinicio), la siguiente es una posición imposible de Connect 4.

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |R| | | | |
| | |Y| | | | |
|R| |Y| | | | |

Su programa o función debe incluir una placa Connect 4 y devolver

  • Un valor falso, que indica que la posición es imposible o
  • Una cadena de números de 1 a 7, lo que indica una posible secuencia de movimientos que conducen a que la posición (las columnas se numeran 1a 7de izquierda a derecha, y así la secuencia 112, por ejemplo, indica un movimiento rojo en la columna 1, seguido por un movimiento amarillo en columna 1, seguido de un movimiento rojo en columna 2). Si lo desea, puede elegir una numeración de columnas que no sea 1234567, siempre que lo especifique en su solución. Si desea devolver la lista en algún otro formato; por ejemplo, como una matriz [2, 4, 3, 1, 1, 3], eso también está bien, siempre que sea fácil ver cuáles son los movimientos.

Puede elegir leer el tablero en cualquier formato razonable, incluido el uso de letras que no sean Ry Ypara los jugadores, pero debe especificar qué jugador va primero. Puedes asumir que el tablero siempre será 6x7, con dos jugadores.

Puede suponer que las posiciones que recibe son al menos físicamente posibles de crear en una placa Connect 4 estándar; es decir, que no habrá piezas 'flotantes'. Puede suponer que el tablero no estará vacío.

Este es el código de golf, por lo que gana la respuesta más corta. Se aplican lagunas estándar.

Ejemplos

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | | --> 1234567 (one possible answer)
| | | | | | | |
|R|Y|R|Y|R|Y|R|

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | |R| | | | | --> false
| | |Y| | | | |
|R| |Y| | | | |

| | | | | | | |
| | |Y| | | | |
| | |R| | | | |
| | |Y| | | | | --> 323333 (only possible answer)
| | |R| | | | |
| |Y|R| | | | |

| | | | | | | |
| | | | | | | |
| | | | | | | |     
| | | | | | | | --> false (this is the position arising after
| |Y|Y|Y|Y| | |     the moves 11223344, but using those moves
| |R|R|R|R| | |     the game would have ended once R made a 4)

| | | | | | | |
| | | | | | | |
|Y| | | | | | |     
|R|Y| | | | | | --> 2134231211 (among other possibilities)
|R|R|Y| | | | |
|Y|R|R|Y| | | |

| | | | | | | |
| | | | | | | |
|Y| | | | | | |     
|R|Y| | | | | | --> false (for example, 21342312117 does not
|R|R|Y| | | | |     work, because Y has already made a diagonal 4)
|Y|R|R|Y| | |R|

| | | | | | | |
| | | | | | | |
| | | | | | | |     
| | | | | | | | --> 112244553 or similar
|Y|Y| |Y|Y| | |
|R|R|R|R|R| | |
John Gowers
fuente
John, por curiosidad, ¿sabes si existe un algoritmo de fuerza no bruta?
Jonás

Respuestas:

9

Jalea , 57 bytes

ŒṪŒ!µ0ịŒṬ¬a³ZU,Ɗ;ŒD$€Ẏṡ€4Ḅo1%15;Ḋ€ṢṚ$Ƒƙ$Ȧȧœị³$2R¤ṁ$ƑµƇṪṪ€

Toma una matriz donde 0no se llena, se 1juega primero y se 2juega segundo. Produce una lista de columnas indexadas en 1, vacía si se identificó un falso.

Pruébalo en línea! (demasiado ineficiente para que se ejecuten más de 7 piezas en menos de un minuto)

Nota:

  1. Asume que no hay piezas "flotantes" presentes (arregle esto anteponiendo ZṠṢ€Ƒȧ+6 bytes)
  2. Asume que el tablero vacío es falso
Jonathan Allan
fuente
11

JavaScript (ES6),  202 194 187  183 bytes

Toma la entrada como una matriz con para rojo, para amarillo y para vacío. Devuelve una cadena de movimientos indexados a 0 (o una cadena vacía si no hay solución). Los rojos comienzan el juego.240

m=>(p=[...'5555555'],g=(c,s=o='')=>/2|4/.test(m)?['',0,2,4].some(n=>m.join``.match(`(1|3)(.{1${n}}\\1){3}`))?o:p.map((y,x)=>m[m[y][x]--^c||p[g(c^6,s+x,p[x]--),x]++,y][x]++)&&o:o=s)(2)

Pruébalo en línea!

¿Cómo?

La función recursiva intenta reemplazar todos los 'y ' en la matriz de entrada con 'y ' respectivamente.g2413

Al hacerlo, se asegura de que no tengamos ninguna ejecución de cuatro valores impares consecutivos hasta que todos los valores pares hayan desaparecido (es decir, si un lado gana, debe ser el último movimiento).

La fila de la siguiente ranura disponible para cada columna se almacena en .yxp[x]

Comentado

m => (                            // m[] = input matrix
  p = [...'5555555'],             // p[] = next row for each column
  g = (c,                         // g = recursive function taking c = color,
          s = o = '') =>          //     s = current solution, o = final output
    /2|4/.test(m) ?               // if the matrix still contains at least a 2 or a 4:
      ['', 0, 2, 4]               //   see if we have four consecutive 1's or 3's
      .some(n =>                  //   by testing the four possible directions
        m.join``                  //   on the joined matrix, using
        .match(                   //   a regular expression where the number of characters
          `(1|3)(.{1${n}}\\1){3}` //   between each occurrence is either 1, 10, 12 or 14
        )                         //   (horizontal, diagonal, vertical, anti-diagonal)
      ) ?                         //   if we have a match:
        o                         //     abort and just return the current value of o
      :                           //   else:
        p.map((y, x) =>           //     for each cell at (x, y = p[x]):
          m[                      // 
            m[y][x]--             //       decrement the value of the cell
            ^ c ||                //       compare the original value with c
            p[                    //       if they're equal:
              g(                  //         do a recursive call with:
                c ^ 6,            //           the other color
                s + x,            //           the updated solution
                p[x]--            //           the updated row for this column
              ),                  //         end of recursive call
              x                   //         then:
            ]++,                  //         restore p[x]
            y                     //         and restore m[y][x]
          ][x]++                  //         to their initial values
        ) && o                    //     end of map(); yield o
    :                             // else:
      o = s                       //   we've found a solution: copy s to o
)(2)                              // initial call to g() with c = 2
Arnauld
fuente
Tenga en cuenta que he preguntado "¿Podemos suponer que el tablero vacío no se dará como entrada?" - Si tenemos que manejar esto, entonces su código necesitará un ajuste.
Jonathan Allan
no sé por qué, f([ [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,2,0,2,0,0], [0,2,2,0,2,2,0], [1,1,1,1,1,1,1] ])termina 0 y f([ [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,2,0,2,0,0], [2,2,2,0,2,2,1], [1,1,1,1,1,1,1] ])debería ser cierto
Nahuel Fouilleul
@NahuelFouilleul Gracias por informar esto. He arreglado el código agregado y agregué estos casos de prueba.
Arnauld
2

Python 2 , 295 285 bytes

def f(a):
 if 1-any(a):return[]
 p=sum(map(len,a))%2
 for i in R(7):
	if a[i][-1:]==`p`:
	 b=a[:];b[i]=b[i][:-1];L=f(b)
	 if L>1>(`1-p`*4in','.join([J((u[j]+' '*14)[n-j]for j in R(7))for n in R(12)for u in[b,b[::-1]]]+b+map(J,zip(*[r+' '*7for r in b])))):return L+[i]
R=range;J=''.join

Pruébalo en línea!

-10 gracias a Jo King .

La entrada es una lista de cadenas que representan las columnas; con '1' para rojo y '0' para amarillo. Las cadenas no están acolchadas. Entonces el caso (falsey):

| | | | | | | |
| | | | | | | |
|Y| | | | | | |
|R|Y| | | | | |
|R|R|Y| | | | |
|Y|R|R|Y| | |R|

se ingresa como:

[
  '0110',
  '110',
  '10',
  '0',
  '',
  '',
  '1'
]

La salida es una lista de índices de columna, indexados en 0, que podrían formar parte del tablero; o Nonesi no es válido

Acepta el tablero vacío como válido (devuelve la lista vacía en []lugar de None).

Este enfoque es recursivo desde el último movimiento hasta el primer movimiento: en función de la paridad del número total de movimientos tomados, eliminamos el último movimiento Rojo o el último movimiento Amarillo (o fallamos si eso no es posible); verifica el tablero resultante para ver si el oponente tiene 4 en raya (en cuyo caso falla, porque el juego ya debería haberse detenido); de lo contrario, repita hasta que el tablero esté vacío (lo cual es válido).

El código 4 en una fila es la parte más hinchada. Todas las cadenas diagonales para la matriz bson generadas por:

[
    ''.join(
        (u[j]+' '*14)[n-j] for j in range(7)
    )
    for u in[b,b[::-1]]for n in range(12) 
]

que primero enumera las diagonales de 'pendiente descendente' y luego las de 'pendiente ascendente'.

Chas Brown
fuente