Torneo de piedra, papel, tijera, lagarto, Spock

13

Dar un desafío con una referencia de Star Trek justo después del 4 de mayo puede estar mal visto, pero aquí va.

Usted, Luke, Anakin, Palpatine, Yoda y Han Solo están involucrados en un loco torneo de Rock, Paper, Scissor, Lizard, Spock.

El problema aquí es que solo puedes usar un orden fijo de movimientos. Si su orden es "R", entonces debe usar Rock, hasta que pierda o gane contra todos. Si su orden es RRV, entonces debe usar 2 rocas seguidas de un Spock y seguir repitiendo hasta que haya ganado o perdido.

¡Luke, Anakin, Palpatine, Yoda y Han Solo han enviado sus respectivos pedidos y tú, como experto hacker, tienes en tus manos cada uno de ellos!

Con este conocimiento, debe diseñar su pedido para el torneo. Dado que todos quieren ganar, desea crear un pedido para ganar el torneo al vencer a todos. Pero esto puede no ser posible en todas las circunstancias.

En caso de que haya un posible pedido ganador, imprímalo. Si no hay una forma posible de ganar, imprima -1 (o 0 o Falso o "imposible")

Entrada : una lista de 5 pedidos

Salida : un solo pedido o -1

Entrada de muestra 1

R
P
S
L
V

Salida de muestra 1

-1

Explicacion 1

No importa lo que juegues en tu primer movimiento, habrá al menos una persona que te gane, por lo tanto, no es posible que ganes.

Entrada de muestra 2

RPS
RPP
R
SRR
L

Salida de muestra 2

RPSP

Explicacion 2

Una vez que juegas Rock en tu primer movimiento, terminas venciendo a "L" y "SRR" y empatas contra el resto. Esto se debe a que Lizard y Scissors pierden ante Rock. Cuando juegues Paper a continuación, vencerás a "R" y empatarás contra los 2 restantes. Esto se debe a que Rock pierde contra Paper. Cuando juegues a Scissors a continuación, ganarás contra "RPP" cuando Scissor venza a Paper.

Finalmente, vencerás a "RPS" con tu Paper como Paper beats Rock.

Aquí hay una lista de anotaciones (puede usar 5 literales, pero especifique en su respuesta):

R : Rock
P : Paper
S : Scissor
L : Lizard
V : Spock

Aquí hay una lista de todos los resultados posibles:

winner('S', 'P') -> 'S'
winner('S', 'R') -> 'R'
winner('S', 'V') -> 'V'
winner('S', 'L') -> 'S'
winner('S', 'S') -> Tie
winner('P', 'R') -> 'P'
winner('P', 'V') -> 'P'
winner('P', 'L') -> 'L'
winner('P', 'S') -> 'S'
winner('P', 'P') -> Tie
winner('R', 'V') -> 'V'
winner('R', 'L') -> 'R'
winner('R', 'S') -> 'R'
winner('R', 'P') -> 'P'
winner('R', 'R') -> Tie
winner('L', 'R') -> 'R'
winner('L', 'V') -> 'L'
winner('L', 'S') -> 'S'
winner('L', 'P') -> 'L'
winner('L', 'L') -> Tie
winner('V', 'R') -> 'V'
winner('V', 'L') -> 'L'
winner('V', 'S') -> 'V'
winner('V', 'P') -> 'P'
winner('V', 'V') -> Tie

Este es el , por lo que ganan menos bytes.

PD: Avíseme si necesita más casos de prueba.

Koishore Roy
fuente
44
Cambie "Star Trek " a "Star Wars " en su introducción;)
movatica
1
Este es un problema bastante difícil. Bueno, o soy malo en este tipo de programación.
CrabMan
@CrabMan Este es un problema un poco difícil para el golf. especialmente en lenguajes prácticos.
Koishore Roy
1
varios trabajos, pero en teoría existen infinitas estrategias ganadoras, así que
tenlo
1
Relacionado , y también un KOTH (cc: @Arnauld)
DLosc

Respuestas:

2

Jalea , 29 bytes

_%5ḟ0ḢḂ¬
ṁ€ZLḤƊçþ`Ạ€Tị;‘%5Ɗ$€

Un enlace monádico que acepta una lista de listas de enteros (cada una de las cuales es la estrategia de un oponente) que produce una lista de listas de enteros, cada una de las cuales es una estrategia ganadora (por lo tanto, una lista vacía si no es posible).
(Solo agregue para obtener solo una lista de estrategia única o, 0si es imposible).

Pruébalo en línea! (los formatos de pie de página para mostrar siempre las listas)

Rock  Paper  Scissors  Spock  Lizard
0     1      2         3      4

O pruebe una versión mapeada de letras (donde las estrategias se toman y se muestran en sus propias líneas usando la RPSVLnotación).

¿Cómo?

Los números se eligen de manera tal que cualquiera que sea un número impar mayor que otro módulo cinco gana (es decir, están numerados alrededor del borde de un pentágono inscrito de los lanzamientos).

El código juega cada estrategia en contra de cada estrategia (incluidos ellos mismos) para el doble de lanzamientos que la estrategia más larga para garantizar que los perdedores se queden con los que no son derrotados. La lista resultante de estrategias contendrá una estrategia única si hay un ganador absoluto; sin estrategias si no hubo ganador; o múltiples estrategias si hay jugadores de dibujo. Después de esto, se agrega un conjunto ganador de movimientos a cada una de estas estrategias.

_%5ḟ0ḢḂ¬ - Link 1, does B survive?: list A, list B (A & B of equal lengths)
                              e.g. RPSR vs RPVL ->  [0,1,2,0], [0,1,3,4]
_        - subtract (vectorises)                    [0,0,-1,-4]
 %5      - modulo five (vectorises)                 [0,0,4,1]   ...if all zeros:
   ḟ0    - filter discard zeros (ties)              [4,1]                       []
     Ḣ   - head (zero if an empty list)             4                           0
      Ḃ  - modulo two                               0                           0
       ¬ - logical NOT                              1                           1

ṁ€ZLḤƊçþ`Ạ€Tị;‘%5Ɗ$€ - Main Link: list of lists of integers
ṁ€                   - mould each list like:
     Ɗ               -   last three links as a monad
  Z                  -     transpose
   L                 -     length
    Ḥ                -     double  (i.e. 2 * throws in longest strategy)
        `            - use left as both arguments of:
       þ             -   table using:
      ç              -     last Link (1) as a dyad
         Ạ€          - all for each (1 if survives against all others, else 0)
           T         - truthy indices
            ị        - index into the input strategies
                  $€ - last two links as a monad for each:
             ;       -   concatenate with:
                 Ɗ   -     last three links as a monad:
              ‘      -       increment (vectorises)
               %5    -       modulo five (vectorises)
Jonathan Allan
fuente
Soy completamente nuevo en Jelly, pero parece que puedes ganar un byte reemplazándolo ZLḤpor .
Robin Ryder
@RobinRyder Eso no funcionará, solo funciona con los datos de ejemplo porque hay suficientes oponentes y pocos lanzamientos suficientes, este es un ejemplo de uno que no funcionaría . Necesitamos analizar el doble de lanzamientos que la estrategia de oponente más larga. (Su código es realmente equivalente a esto )
Jonathan Allan
... en realidad, debido a la acción de Ɗsu código, ni siquiera está haciendo lo que podría haber pensado: está moldeando cada uno como si fuera su propia longitud y luego obteniendo las sumas acumulativas de esos resultados, por lo que también comparará valores incorrectos. Pruebe esto por ejemplo: toma [[1,2,3,4,5],[6,7],[8]], moldea cada uno por la longitud de la lista completa (3) para obtener y [[1,2,3],[6,7,6],[8,8,8]]luego realiza la acumulación para obtener [[1,1+2,1+2+3],[6,6+7,6+7+8],[8,8+8,8+8+8]]= [[1,3,6],[6,13,19],[8,16,24]].
Jonathan Allan
Ah sí, ¡sabía que estaba malentendiendo algo!
Robin Ryder
7

JavaScript (ES6),  122 115  112 bytes

Toma la entrada como una matriz de cadenas de dígitos, con:

  • 0 0
  • 1
  • 2
  • 3
  • 4 4

Funlsmi

f=(a,m='',x=0,o,b=a.filter(a=>(y=a[m.length%a.length])-x?o|=y-x&1^x<y:1))=>b+b?x<4&&f(a,m,x+1)||!o&&f(b,m+x):m+x

Pruébalo en línea!

¿Cómo?

Esta es una búsqueda de amplitud: primero intentamos todos los movimientos en un paso dado para ver si podemos ganar el juego. Si no podemos ganar en este momento, intentamos agregar otro movimiento a cada movimiento no perdedor.

UNsi(si-UN)modificación5 5

UNsi

(S)(PAG)(R)(L)(V)0 01234 4(S) 0 0-1234 4(PAG) 14 4-123(R) 234 4-12(L) 3234 4-1(V) 4 41234 4-

UNsiUNsi

((A - B) and 1) xor (B < A)

donde andy xorson operadores bit a bit.

Comentado

f = (                        // f is a recursive function taking:
  a,                         //   a[] = input
  m = '',                    //   m   = string representing the list of moves
  x = 0,                     //   x   = next move to try (0 to 4)
  o,                         //   o   = flag set if we lose, initially undefined
  b =                        //   b[] = array of remaining opponents after the move x
    a.filter(s =>            //     for each entry s in a[]:
    ( y =                    //       define y as ...
      s[m.length % s.length] //         ... the next move of the current opponent
    ) - x                    //       subtract x from y
    ?                        //       if the difference is not equal to 0:
      o |=                   //         update o using the formula described above:
        y - x & 1 ^ x < y    //           set it to 1 if we lose; opponents are removed
                             //           while o = 0, and kept as soon as o = 1
    :                        //       else (this is a draw):
      1                      //         keep this opponent, but leave o unchanged
  )                          //     end of filter()
) =>                         //
  b + b ?                    // if b[] is not empty:
    x < 4 &&                 //   if x is less than 4:
      f(a, m, x + 1)         //     do a recursive call with x + 1 (going breadth-first)
    ||                       //   if this fails:
      !o &&                  //     if o is not set:
        f(b, m + x)          //       keep this move and do a recursive call with b[]
  :                          // else (success):
    m + x                    //   return m + x
Arnauld
fuente
su código falla para el caso de prueba: test(['P','P','S','P','P']) la respuesta debe ser "SR" o "SV".
Koishore Roy
@KoishoreRoy Ahora corregido.
Arnauld
1
Este es realmente un enfoque brillante. Ni siquiera pensé en considerarlo como un gráfico. Estaba usando diccionarios y búsquedas inversas en mi enfoque original sin golf (sin Spock o Lizards, es decir)
Koishore Roy
3

R , 213 190 bytes

-23 bytes gracias a Giuseppe.

function(L){m=matrix(rep(0:2,1:3),5,5)
m[1,4]=m[2,5]=1
v=combn(rep(1:5,n),n<-sum(lengths(L)))
v[,which(apply(v,2,function(z)all(sapply(L,function(x,y,r=m[cbind(x,y)])r[r>0][1]<2,z)))>0)[1]]}

Pruébalo en línea!

Si existe una solución, genera una. Si no hay solución, genera una fila de NA. Si este formato de salida no es aceptable, puedo cambiarlo a un costo de unos pocos bytes.

Los movimientos se codifican como 1 = R, 2 = S, 3 = P, 4 = L, 5 = V, de modo que la matriz de resultados es

     [,1] [,2] [,3] [,4] [,5]
[1,]    0    2    2    1    1
[2,]    1    0    2    2    1
[3,]    1    1    0    2    2
[4,]    2    1    1    0    2
[5,]    2    2    1    1    0

(0 = sin ganador; 1 = el jugador 1 gana; 2 = el jugador 2 gana)

Un límite superior en la longitud de la solución, si existe, es n=sum(lengths(L))dónde Lestá la lista de movimientos de los oponentes. El código crea todas las estrategias posibles de longitud n(almacenadas en la matriz v), las prueba todas y muestra todas las estrategias ganadoras.

Tenga en cuenta que este valor de nhace que el código sea muy lento en TIO, por lo que he codificado en el TIO, n=4que es suficiente para los casos de prueba.

Para el primer caso de prueba, la salida es

     1 4 2 4

correspondiente a la solución RLSL.

Para el segundo caso de prueba, la salida es

 NA NA NA NA

lo que significa que no hay solución.

Explicación de una versión anterior (se actualizará cuando pueda):

function(L){
  m = matrix(rep(0:2,1:3),5,5);
  m[1,4]=m[2,5]=1                      # create matrix of outcomes
  v=as.matrix(expand.grid(replicate(   # all possible strategies of length n
    n<-sum(lengths(L))                 # where n is the upper bound on solution length
    ,1:5,F)))             
  v[which(    
    apply(v,1,                         # for each strategy
          function(z)                  # check whether it wins
            all(                       # against all opponents
              sapply(L,function(x,y){  # function to simulate one game
                r=m[cbind(x,y)];       # vector of pair-wise outcomes
                r[r>0][1]<2            # keep the first non-draw outcome, and verify that it is a win
              }
              ,z)))
    >0),]                              # keep only winning strategies
}

los which es necesario deshacerse de AN que se producen cuando los dos jugadores roban siempre.

No estoy convencido de que esta sea la estrategia más eficiente. Incluso si es así, estoy seguro de que el código para mpodría jugarse bastante.

Robin Ryder
fuente
¿Por qué tiene el lengths()alias de volver siempre 4?
Giuseppe
1
De todos modos, mientras espero su respuesta, bajé a 197 , principalmente concentrándome en v...
Giuseppe
@Giuseppe, he alias lengthspara forzar n=4el TIO, porque de lo contrario lleva demasiado tiempo repasar todo5 5norte (norte=11) estrategias.
Robin Ryder
Ah, tiene sentido, debería haberlo sabido. 187 bytes
Giuseppe
@Giuseppe Gracias, ¡golf impresionante! Agregué 3 bytes para hacer que la salida sea más legible (de lo contrario, terminaremos con las mismas soluciones impresas muchas veces).
Robin Ryder
0

Emacs Lisp, 730 bytes

(require 'cl-extra)
(require 'seq)
(defun N (g) (length (nth 1 g)))
(defun M (g) (mapcar (lambda (o) (nth (% (N g) (length o)) o)) (car g)))
(defun B (x y) (or (eq (% (1+ x) 5) y) (eq (% (+ y 2) 5) x)))
(defun S (g) (seq-filter (lambda (m) (not (seq-some (lambda (v) (B v m)) (M g)))) '(0 1 2 3 4)))
(defun F (g) (cond ((null (car g)) (reverse (nth 1 g))) ((null (S g)) nil) ((>= (nth 3 g) (seq-reduce (lambda (x y) (calc-eval "lcm($,$$)" 'raw x y)) (mapcar 'length (car g)) 1)) nil) (t (cl-some (lambda (m) (F   (let ((r (seq-filter 'identity (mapcar* (lambda (v o) (and (not (B m v)) o)) (M g) (car g))))) (list r (cons m (nth 1 g)) (1+ (N g)) (if (eq (car g) r) (1+ (nth 3 g)) 0))))) (S g)))))
(defun Z (s) (F (list s () 0 0)))

No encontré un intérprete en línea de Emacs Lisp :( Si tiene Emacs instalado, puede copiar el código en un .elarchivo, copie algunas líneas de prueba a continuación

;; 0 = rock, 1 = lizard; 2 = spock;
;; 3 = scissors; 4 = paper
(print (Z '((0) (1) (2) (3) (4))))
; output: nil
(print (Z '((0) (4) (3) (1))))
; output: nil
(print (Z '((0 4 3) (0 4 4) (0) (3 0 0) (1))))
; output: (0 4 3 0 1)
(print (Z '((4) (4) (3) (4) (4))))
; output: (3 0)
(print (Z '((4 3 2 1 0) (2 1 0 4 3))))
; output: (1)
(print (Z '((2) (2) (3) (0) (2) (3) (0) (0))))
; output: (2 1)
(print (Z '((2) (2 0) (3) (0) (2 1) (3) (0) (0))))
; output: nil

y ejecutarlo $ emacs --script filename.el.

Cómo funciona

Mi programa primero busca en profundidad, a veces descubriendo que es imposible ganar y terminando la rama en la que está.

Puede ver la explicación completa en la versión no abreviada del código:

(require 'seq)
(require 'cl-extra)

;; This program does depth first search with sometimes figuring out
;; that it's impossible to win and terminating the branch it's on.
;;

;; A move is a number from 0 to 4. 
;; https://d3qdvvkm3r2z1i.cloudfront.net/media/catalog/product/cache/1/image/1800x/6b9ffbf72458f4fd2d3cb995d92e8889/r/o/rockpaperscissorslizardspock_newthumb.png
;; this is a nice visualization of what beats what.
;; Rock = 0, lizard = 1, spock = 2, scissors = 3, paper = 4.

(defun beats (x y) "Calculates whether move x beats move y"
  (or (eq (% (1+ x) 5) y)
      (eq (% (+ y 2) 5) x)))

;; A gamestate is a list with the following elements:
(defun get-orders (gamestate)
  "A list of orders of players who haven't lost yet. Each order is a list of moves.
For example, ((2) (2 0) (3) (0) (2 1) (3) (0) (0)) is a valid orders list.
This function gets orders from the gamestate."
  (car gamestate))

;; At index 1 of the gamestate lies a list of all moves we have made so far in reverse order
;; (because lists are singly linked, we can't push back quickly)
(defun get-num-moves-done (gamestate)
  "Returns the number of moves the player has done so far"
  (length (nth 1 gamestate)))

(defun get-rounds-since-last-elim (gamestate)
  "The last element of a gamestate is the number of rounds passed since an opponent
was eliminated. We use this to determine if it's possible to win from current
gamestate (more about it later)."
  (nth 2 gamestate))

;; next go some utility functions
;; you can skip their descriptions, they are not very interesting
;; I suggest you skip until the next ;; comment

(defun get-next-move (order num-rounds-done)
  "Arguments: an order (e.g. (1 0 1)); how many rounds have passed total.
Returns the move this opponent will make next"
  (nth (% num-rounds-done (length order)) order))

(defun moves-of-opponents-this-round (gamestate)
  "Returns a list of moves the opponents will make next"
  (mapcar (lambda (order) (get-next-move order (get-num-moves-done gamestate)))
          (get-orders gamestate)))

(defun is-non-losing (move opponents-moves)
  "Calculates if we lose right away by playing move against opponents-moves"
  (not (seq-some (lambda (opponent-move) (beats opponent-move move))
                 opponents-moves)))

(defun non-losing-moves (gamestate)
  "Returns a list of moves which we can play without losing right away."
  (seq-filter
   (lambda (move) (is-non-losing move (moves-of-opponents-this-round gamestate)))
   '(0 1 2 3 4)))

(defun advance-gamestate (gamestate move)
  "If this move in this gamestate is non-losing, returns the next game state"
  (let ((new-orders (seq-filter
                    'identity (mapcar* (lambda (opp-move order)
                                         (and (not (beats move opp-move)) order))
                                       (moves-of-opponents-this-round gamestate)
                                       (get-orders gamestate)))))
  (list new-orders
        (cons move (nth 1 gamestate))
        (if (eq (get-orders gamestate) new-orders) (1+ (get-rounds-since-last-elim gamestate)) 0))))

;; How do we prevent our depth first search from continuing without halting?
;; Suppose 3 players (except us) are still in the game and they have orders of lengths a, b, c
;; In this situation, if least_common_multiple(a, b, c) rounds pass without an elimination
;; we will be in the same situation (because they will be playing the same moves they played
;; lcm(a, b, c) rounds ago)
;; Therefore, if it's possible to win from this gamestate,
;; then it's possible to win from that earlier game state,
;; hence we can stop exploring this branch

(defun get-cycle-len (gamestate)
  "Returns a number of rounds which is enough for the situation to become the same
if the game goes this long without an elimination."
  (seq-reduce (lambda (x y) (calc-eval "lcm($,$$)" 'raw x y))
              (mapcar 'length (get-orders gamestate)) 1))

(defun unwinnable-cycle (gamestate)
  "Using the aforementioned information, returns t if we are in such a
suboptimal course of play."
  (>= (get-rounds-since-last-elim gamestate) (get-cycle-len gamestate)))

(defun find-good-moves (gamestate)
  "Given gamestate, if it's possible to win
returns a list of moves, containing all moves already done + additional moves which lead to win.
Otherwise returns nil"
  (cond ((null (get-orders gamestate)) ; if no opponents left, we won, return the list of moves
         (reverse (nth 1 gamestate)))
        ((null (non-losing-moves gamestate)) ; if no non-losing moves available, this gamestate
         nil) ; doesn't lead to a win, return nil
        ((unwinnable-cycle gamestate) ; either it's impossible to win, or
         nil) ; it's possible to win from an earlier position, return nil
        (t (cl-some (lambda (move) ; otherwise return the first non-losing move which leads
                      (find-good-moves (advance-gamestate gamestate move))) ; to a non-nil result
                    (non-losing-moves gamestate)))))

(defun make-initial-gamestate (orders)
  "Given an orders list, create initial gamestate"
  (list orders () 0))
Cangrejo
fuente
1
tio.run/##S81NTC7WzcksLvgPBAA ¿ puede insertar su código aquí e intentar ejecutarlo?
Koishore Roy
@KoishoreRoy Probé tio.run y no pude entender por qué no funciona. Dice "Trailing basura después de la expresión" y no tengo idea de qué es eso y 5 minutos de búsqueda en Google no me ayudaron a solucionarlo.
CrabMan