Pedra, Papel, Tesoura, Lagarto, Torneio de Spock

13

Dar um desafio envolvendo uma referência de Star Trek logo após 4 de maio pode ser desaprovado, mas aqui vai.

Você, Luke, Anakin, Palpatine, Yoda e Han Solo estão envolvidos em um torneio insano de Rock, Paper, Scissor, Lizard, Spock.

O problema aqui é que você só pode usar uma ordem fixa de movimentos. Se o seu pedido for "R", você deverá usar o Rock até perder ou vencer contra todos. Se o seu pedido for RRV, você deverá usar 2 Rocks seguidas por um Spock e continuar repetindo até ganhar ou perder.

Luke, Anakin, Palpatine, Yoda e Han Solo enviaram seus respectivos pedidos e você, sendo um hacker experiente, colocou as mãos em cada um deles!

Com esse conhecimento, você deve criar seus pedidos para o torneio. Como todos querem vencer, você deseja criar uma ordem para vencer o torneio vencendo todos. Mas isso pode não ser possível em todas as circunstâncias.

Caso haja uma possível ordem vencedora, imprima-a. Se não houver uma maneira possível de ganhar, imprima -1 (ou 0 ou Falso ou "impossível")

Entrada : uma lista de 5 pedidos

Saída : uma única ordem ou -1

Entrada de amostra 1

R
P
S
L
V

Saída de amostra 1

-1

Explicação 1

Não importa o que você jogue na sua primeira jogada, haverá pelo menos uma pessoa que o derrotará; portanto, não é possível que você vença.

Entrada de amostra 2

RPS
RPP
R
SRR
L

Saída de amostra 2

RPSP

Explicação 2

Depois de tocar Rock no seu primeiro movimento, você acaba batendo "L" e "SRR" e empatando com o resto. Isso ocorre porque o Lagarto e a Tesoura perdem para o Rock. Quando você jogar Paper em seguida, você vencerá "R" e empatará contra os 2. restantes. Isso ocorre porque o Rock perde para o Paper. Quando você jogar Scissors em seguida, você vencerá "RPP", pois Scissor bate Paper.

Por fim, você vencerá "RPS" com o seu Paper, já que o Paper vence o Rock.

Aqui está uma lista de notações (você pode usar 5 literais, mas especifique na sua resposta):

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

Aqui está uma lista de todos os resultados possíveis:

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

Isso é , e o menor número de bytes vence.

PS: Entre em contato se precisar de mais casos de teste.

Koishore Roy
fonte
4
Por favor, mude "Star Trek " para "Star Wars " em sua introdução;)
movatica
1
Este é um problema bastante difícil. Bem, ou eu sou ruim nesse tipo de programação.
CrabMan 5/05
@CrabMan Esse é um problema difícil para o golfe. especialmente em linguagens práticas.
Koishore Roy
1
várias obras, mas há no infinito teoria estratégias vencedoras, de modo a manter isso em mente
Koishore Roy
1
Relacionados , e também um KOTH (cc: @Arnauld)
DLosc

Respostas:

2

Gelatina , 29 bytes

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

Um link monádico que aceita uma lista de listas de números inteiros (cada qual é a estratégia de um oponente) que produz uma lista de listas de números inteiros - cada um dos quais é uma estratégia vencedora (portanto, uma lista vazia, se não for possível).
(Basta adicionar para gerar apenas uma única lista de estratégia ou, 0se impossível).

Experimente online! (os formatos de rodapé para mostrar sempre as listas)

Rock  Paper  Scissors  Spock  Lizard
0     1      2         3      4

Ou tente uma versão mapeada por carta (onde as estratégias são tomadas e mostradas em suas próprias linhas usando a RPSVLnotação).

Quão?

Os números são escolhidos de modo que qualquer número ímpar maior que outro módulo cinco vence (ou seja, eles são numerados, contornando a borda de um pentágono inscrito dos arremessos).

O código reproduz cada estratégia contra todas as estratégias (inclusive elas mesmas) por duas vezes mais arremessos do que a estratégia mais longa, de modo a garantir que os perdedores mantenham aqueles que não foram derrotados. A lista de estratégias resultante conterá uma única estratégia se houver um vencedor definitivo; nenhuma estratégia se não houvesse vencedor; ou várias estratégias se houver jogadores de desenho. Depois disso, um conjunto vencedor de movimentos é anexado a cada uma dessas estratégias.

_%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
fonte
Eu sou completamente novo no Jelly, mas parece que você pode obter um byte substituindo ZLḤpor .
Robin Ryder
@RobinRyder Isso não vai funcionar - ele está trabalhando apenas com os dados de exemplo porque há oponentes suficientes e poucos lances suficientes - este é um exemplo de um que não funcionaria . Precisamos analisar o dobro de lances que a estratégia mais longa do oponente. (O código é realmente equivalente a este )
Jonathan Allan
... na verdade, devido à ação do Ɗseu código, ele nem está fazendo o que você pensou - está moldando cada um como seu próprio comprimento, obtendo as somas cumulativas desses resultados, então também comparará valores incorretos. Tente isso, por exemplo - ele absorve [[1,2,3,4,5],[6,7],[8]], molda cada um pelo comprimento de toda a lista (3) para obter e, em [[1,2,3],[6,7,6],[8,8,8]]seguida, realiza a acumulação para obter [[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, sim, eu sabia que estava entendendo mal alguma coisa!
Robin Ryder
7

JavaScript (ES6),  122 115  112 bytes

Recebe a entrada como uma matriz de cadeias de dígitos, com:

  • 0 0
  • 1
  • 2
  • 3
  • 4

fumaeuse

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

Experimente online!

Quão?

Esta é uma pesquisa abrangente: primeiro tentamos todos os movimentos em uma determinada etapa para ver se podemos ganhar o jogo. Se não podemos vencer agora, tentamos adicionar outra jogada a cada jogada não perdida.

UMAB(B-UMA)mod5

UMAB

(S)(P)(R)(EU)(V)0 01234(S) 0 0-1234(P) 14-123(R) 234-12(EU) 3234-1(V) 41234-

UMABUMAB

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

onde ande xorsão 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
fonte
seu código falhará no caso de teste: test(['P','P','S','P','P']) A resposta deve ser "SR" ou "SV".
Koishore Roy 5/05/19
@KoishoreRoy Agora corrigido.
Arnauld
1
Esta é realmente uma abordagem brilhante. Eu nem pensei em considerá-lo como um gráfico. Eu estava usando dicionários e pesquisas reversas na minha abordagem original e sem uso de armas (sem Spock ou Lizards, por exemplo)
Koishore Roy
3

R , 213 190 bytes

-23 bytes graças 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]]}

Experimente online!

Se existe uma solução, ela gera uma. Se não houver solução, ela gera uma linha de NA. Se esse formato de saída não for aceitável, eu posso alterá-lo a um custo de alguns bytes.

Os movimentos são codificados como 1 = R, 2 = S, 3 = P, 4 = L, 5 = V, de modo que a matriz de resultados é

     [,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 = nenhum vencedor; 1 = jogador 1 vence; 2 = jogador 2 vence)

Um limite superior do comprimento da solução, se existir, é n=sum(lengths(L))onde Lestá a lista de movimentos dos oponentes. O código cria todas as estratégias possíveis de comprimento n(armazenadas na matriz v), tenta todas elas e exibe todas as estratégias vencedoras.

Observe que esse valor ntorna o código muito lento no TIO, portanto, eu o codifiquei no TIO, o n=4que é suficiente para os casos de teste.

Para o primeiro caso de teste, a saída é

     1 4 2 4

correspondente à solução RLSL.

Para o segundo caso de teste, a saída é

 NA NA NA NA

o que significa que não há solução.

Explicação de uma versão anterior (será atualizada quando puder):

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
}

o which é necessário para se livrar da AN que ocorrem quando os dois jogadores desenhar para sempre.

Não estou convencido de que essa seja a estratégia mais eficiente. Mesmo se for, tenho certeza de que o código para mpoderia ser um pouco golfado.

Robin Ryder
fonte
por que o lengths()alias sempre retorna 4?
Giuseppe
1
De qualquer forma, enquanto eu estou esperando por sua resposta, eu golfed lo para baixo para 197 , a maioria concentrando-se em v...
Giuseppe
lengthsn=45nn=11
ah, faz sentido, deveria ter sabido. 187 bytes
Giuseppe
@ Giuseppe Obrigado, golfe impressionante! Adicionei 3 bytes para tornar a saída mais legível (caso contrário, acabamos com as mesmas soluções impressas várias vezes).
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)))

Não encontrei um intérprete on-line do Emacs Lisp :( Se você possui o Emacs instalado, pode copiar o código em um .elarquivo, copie algumas linhas de teste abaixo

;; 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

e execute $ emacs --script filename.el.

Como funciona

Meu programa faz uma pesquisa profunda com as vezes descobrindo que é impossível vencer e encerrar o ramo em que está.

Você pode ver uma explicação completa na versão não abreviada do 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))
CrabMan
fonte
1
tio.run/##S81NTC7WzcksLvgPBAA, você pode inserir seu código aqui e tentar executá-lo?
Koishore Roy
@KoishoreRoy Eu tentei o tio.run e não conseguia descobrir por que ele não funciona. Ele diz "Rastreando lixo após a expressão" e não tenho idéia do que é isso, e 5 minutos de pesquisa no Google não me ajudaram a corrigi-lo.
CrabMan