Stein, Papier, Schere, Eidechse, Spock-Turnier

13

Wenn Sie kurz nach dem 4. Mai eine Herausforderung mit einer Star Trek-Referenz angehen, kann dies verpönt sein.

Sie, Luke, Anakin, Palpatine, Yoda und Han Solo sind in ein verrücktes Turnier aus Rock, Paper, Scissor, Lizard, Spock verwickelt.

Der Haken hier ist, dass Sie nur eine feste Reihenfolge von Zügen verwenden dürfen. Wenn Ihr Befehl "R" lautet, müssen Sie Rock verwenden, bis Sie gegen alle verlieren oder gewinnen. Wenn Ihre Bestellung RRV ist, müssen Sie 2 Steine ​​gefolgt von einem Spock verwenden und so lange wiederholen, bis Sie gewonnen oder verloren haben.

Luke, Anakin, Palpatine, Yoda und Han Solo haben ihre jeweiligen Bestellungen eingereicht und Sie, ein erfahrener Hacker, haben jede ihrer Bestellungen in die Hände bekommen!

Mit diesem Wissen müssen Sie Ihre Bestellung für das Turnier gestalten. Da jeder gewinnen möchte, möchten Sie eine Bestellung erstellen, bei der Sie das Turnier gewinnen, indem Sie alle schlagen. Dies ist jedoch möglicherweise nicht unter allen Umständen möglich.

Wenn es eine mögliche Gewinnreihenfolge gibt, drucken Sie diese aus. Wenn Sie nicht gewinnen können, drucken Sie -1 (oder 0 oder False oder "impossible") aus.

Eingabe : eine Liste von 5 Bestellungen

Ausgabe : eine einzelne Bestellung oder -1

Probeneingabe 1

R
P
S
L
V

Beispielausgabe 1

-1

Erklärung 1

Egal, was Sie in Ihrem ersten Zug spielen, es wird mindestens eine Person geben, die Sie schlägt, daher ist es nicht möglich, dass Sie gewinnen.

Probeneingabe 2

RPS
RPP
R
SRR
L

Beispielausgabe 2

RPSP

Erklärung 2

Sobald Sie in Ihrem ersten Zug Rock spielen, schlagen Sie "L" und "SRR" und binden gegen den Rest. Dies liegt daran, dass Lizard und Scissors gegen Rock verlieren. Wenn Sie das nächste Mal Papier spielen, schlagen Sie "R" und binden gegen die restlichen 2. Dies liegt daran, dass Rock gegen Papier verliert. Wenn Sie als nächstes Schere spielen, gewinnen Sie gegen "RPP", wenn Schere Papier schlägt.

Schließlich schlagen Sie "RPS" mit Ihrem Papier, während Papier Rock schlägt.

Hier ist eine Liste von Notationen (Sie können 5 Literale verwenden, aber bitte in Ihrer Antwort angeben):

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

Hier ist eine Liste aller möglichen Ergebnisse:

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

Das ist , so dass nur wenige Bytes gewinnen.

PS: Lass es mich wissen, wenn du mehr Testfälle brauchst.

Koishore Roy
quelle
4
Bitte ändere "Star Trek " in deinem Intro auf "Star Wars ";)
movatica
1
Das ist ein ziemlich schwieriges Problem. Na ja, oder ich bin schlecht in dieser Art von Programmierung.
CrabMan
@CrabMan Dies ist ein schwieriges Problem beim Golfen. vor allem in praktischen Sprachen.
Koishore Roy
1
Mehrere Werke, aber theoretisch gibt es unendlich
viele Gewinnstrategien.
1
Verwandte , und auch ein KOTH (cc: @Arnauld)
DLosc

Antworten:

2

Gelee , 29 Bytes

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

Ein monadischer Link, der eine Liste von Ganzzahllisten akzeptiert (von denen jede die Strategie eines Gegners ist), die eine Liste von Ganzzahllisten liefert - von denen jede eine Gewinnstrategie ist (also eine leere Liste, wenn keine möglich ist).
(Fügen Sie einfach hinzu, um nur eine einzige Strategieliste zu erhalten, oder 0wenn dies unmöglich ist.)

Probieren Sie es online! (die Fußzeilenformate, um die Listen immer anzuzeigen)

Rock  Paper  Scissors  Spock  Lizard
0     1      2         3      4

Oder probieren Sie eine buchstabierte Version aus (bei der Strategien genommen und in eigener RPSVLNotation angezeigt werden ).

Wie?

Die Zahlen werden so gewählt, dass alle Zahlen, die ungerade größer als ein anderes Modulo Five sind, gewinnen (dh sie werden um den Rand eines eingeschriebenen Fünfecks der Würfe herum nummeriert).

Der Code spielt jede Strategie gegen jede Strategie (einschließlich sich selbst) für doppelt so viele Würfe wie die längste Strategie aus, um sicherzustellen, dass Verlierer gefunden werden, die diejenigen behalten, die nicht besiegt werden. Die resultierende Liste von Strategien enthält eine einzelne Strategie, wenn es einen direkten Gewinner gibt. keine Strategien, wenn es keinen Gewinner gibt; oder mehrere Strategien, wenn es Zeichnungsspieler gibt. Danach wird jeder dieser Strategien eine Reihe von Gewinnzügen beigefügt.

_%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
quelle
Ich bin völlig neu zu Gelee, aber es scheint , dass Sie ein Byte durch Ersetzen gewinnen können ZLḤdurch .
Robin Ryder
@RobinRyder Das funktioniert nicht - es funktioniert nur mit den Beispieldaten, weil es genügend Gegner und wenige Würfe gibt - dies ist ein Beispiel für einen, der nicht funktioniert . Wir müssen doppelt so viele Würfe analysieren wie die längste Gegnerstrategie. (Ihr Code ist tatsächlich gleichwertig diese )
Jonathan Allan
... tatsächlich Ɗtut es aufgrund der Aktion in Ihrem Code nicht einmal das, was Sie vielleicht gedacht haben - es formt jedes wie seine eigene Länge und erhält dann die kumulativen Summen dieser Ergebnisse, so dass es auch falsche Werte vergleicht. Versuchen Sie dies zum Beispiel - es dauert [[1,2,3,4,5],[6,7],[8]], Formen jeweils nach der Länge der gesamten Liste (3) zu erhalten, [[1,2,3],[6,7,6],[8,8,8]]und führt dann eine Akkumulation durch, um [[1,1+2,1+2+3],[6,6+7,6+7+8],[8,8+8,8+8+8]]= zu erhalten [[1,3,6],[6,13,19],[8,16,24]].
Jonathan Allan
Ah ja, ich wusste, ich habe etwas falsch verstanden!
Robin Ryder
7

JavaScript (ES6),  122 115  112 Byte

Nimmt Eingaben als Array von Ziffernfolgen auf, mit:

  • 0
  • 1
  • 2
  • 3
  • 4

feinlse

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

Probieren Sie es online!

Wie?

Dies ist eine Breitensuche: Wir versuchen zuerst alle Züge in einem bestimmten Schritt, um zu sehen, ob wir das Spiel gewinnen können. Wenn wir jetzt nicht gewinnen können, versuchen wir, jedem nicht verlierenden Zug einen weiteren Zug hinzuzufügen.

AB(BA)mod5

AB

(S)(P)(R)(L)(V)01234(S) 01234(P) 14123(R) 23412(L) 32341(V) 41234-

ABAB

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

wo andund xorsind bitweise Operatoren.

Kommentiert

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
quelle
Ihr Code schlägt für den Testfall fehl: test(['P','P','S','P','P']) Die Antwort sollte "SR" oder "SV" sein.
Koishore Roy
@KoishoreRoy Jetzt behoben.
Arnauld
1
Dies ist eigentlich ein brillanter Ansatz. Ich habe nicht einmal daran gedacht, es als Grafik zu betrachten. Ich benutzte Wörterbücher und Reverse-Look-ups in meinem ursprünglichen Ansatz (ohne Spock oder Eidechsen)
Koishore Roy
3

R , 213.190 Bytes

-23 Bytes dank 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]]}

Probieren Sie es online!

Wenn eine Lösung vorhanden ist, wird eine ausgegeben. Wenn es keine Lösung gibt, wird eine Reihe von ausgegeben NA. Wenn dieses Ausgabeformat nicht akzeptabel ist, kann ich es auf Kosten einiger Bytes ändern.

Bewegungen werden als 1 = R, 2 = S, 3 = P, 4 = L, 5 = V codiert, so dass die Matrix der Ergebnisse lautet

     [,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 = kein Gewinner; 1 = Spieler 1 gewinnt; 2 = Spieler 2 gewinnt)

Eine Obergrenze für die Länge der Lösung, falls vorhanden, ist, n=sum(lengths(L))wo Lsich die Liste der Züge des Gegners befindet. Der Code erstellt alle möglichen Längenstrategien n(in Matrix gespeichert)v ), probiert sie alle aus und zeigt alle Gewinnstrategien an.

Beachten Sie, dass dieser Wert von nden Code auf TIO sehr langsam macht, so dass ich den TIO fest codiert habe, n=4was für die Testfälle ausreicht.

Für den ersten Testfall ist die Ausgabe

     1 4 2 4

entsprechend der Lösung RLSL.

Für den zweiten Testfall ist die Ausgabe

 NA NA NA NA

was bedeutet, dass es keine Lösung gibt.

Erklärung einer früheren Version (wird aktualisiert, wenn ich kann):

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
}

Das whichist notwendig, um NAs loszuwerden, die auftreten, wenn die beiden Spieler für immer ziehen.

Ich bin nicht davon überzeugt, dass dies die effizienteste Strategie ist. Selbst wenn es so ist, bin ich sicher, dass der Code für meiniges golfen werden könnte.

Robin Ryder
quelle
Warum ist lengths()Alias ​​immer zurückzukehren 4?
Giuseppe
1
Wie auch immer, während ich auf Ihre Antwort warte, habe ich es auf 197 geschafft , hauptsächlich , indem ich mich auf v...
Giuseppe
@ Giuseppe Ich habe mich darauf geeinigt lengths, n=4TIO zu erzwingen , weil es sonst zu lange dauert, alles durchzugehen5n (n=11) Strategien.
Robin Ryder
ah, macht Sinn, hätte es wissen müssen. 187 Bytes
Giuseppe
@ Giuseppe Danke, beeindruckendes Golfen! Ich habe 3 Bytes hinzugefügt, um die Ausgabe besser lesbar zu machen (andernfalls werden die gleichen Lösungen mehrmals gedruckt).
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)))

Ich habe keinen Online-Interpreter für Emacs Lisp gefunden :( Wenn Sie Emacs installiert haben, können Sie Code in ein kopieren .el Datei kopieren und einige der folgenden Testzeilen kopieren

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

und lass es laufen $ emacs --script filename.el .

Wie es funktioniert

Mein Programm führt zuerst eine Tiefensuche durch, wobei es manchmal herausfindet, dass es unmöglich ist, den Zweig zu gewinnen und zu beenden, in dem es sich befindet.

Sie können die vollständige Erklärung in der ungekürzten Version des Codes sehen:

(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
quelle
1
tio.run/##S81NTC7WzcksLvgPBAA Können Sie Ihren Code hier einfügen und versuchen, ihn auszuführen?
Koishore Roy
@KoishoreRoy Ich hatte tio.run ausprobiert und konnte nicht herausfinden, warum es nicht läuft. Es steht "Müll nach Ausdruck" und ich habe keine Ahnung, was das ist und 5 Minuten googeln haben mir nicht geholfen, es zu beheben.
CrabMan