Турнир "Камень, Бумага, Ножницы, Ящерица, Спок"

13

Вызов, включающий ссылку на «Звездный путь» сразу после 4-го мая, может быть неодобрительным, но здесь идет.

Вы, Люк, Анакин, Палпатин, Йода и Хан Соло участвуете в безумном турнире Рок, Бумага, Ножницы, Ящерица, Спок.

Подвох в том, что вам разрешено использовать только фиксированный порядок ходов. Если ваш ордер "R", то вы должны использовать Рок, пока не проиграете или не выиграете у всех. Если ваш ордер RRV, то вы должны использовать 2 Камня, за которыми следует Спок, и повторять до тех пор, пока вы не выиграете или не проиграете.

Люк, Анакин, Палпатин, Йода и Хан Соло представили свои соответствующие заказы, и вы, будучи опытным хакером, получили в свои руки каждый из них!

Обладая этими знаниями, вы должны разработать свой заказ на турнир. Поскольку все хотят выиграть, вы хотите создать такой порядок, чтобы выиграть турнир, обыграв всех. Но это может быть невозможно при любых обстоятельствах.

Если есть возможный выигрышный ордер, распечатайте его. Если у вас нет возможности выиграть, выведите -1 (или 0 или Ложь или «невозможно»).

Вход : список из 5 заказов

Выход : один заказ или -1

Пример ввода 1

R
P
S
L
V

Пример вывода 1

-1

Объяснение 1

Независимо от того, что вы играете в своем первом движении, будет хотя бы один человек, который побьет вас, поэтому вы не сможете выиграть.

Пример ввода 2

RPS
RPP
R
SRR
L

Пример вывода 2

RPSP

Объяснение 2

После того, как вы сыграете Рок в своем первом движении, вы в конечном итоге выиграете «L» и «SRR» и сыграете вничью с остальными. Это потому, что Ящерица и Ножницы проигрывают Року. Когда вы будете играть в «Бумагу» дальше, вы будете бить «R» и связываться с оставшимися 2. Это потому, что «Рок» проигрывает «Бумаге». Когда вы будете играть Ножницами дальше, вы выиграете у «RPP», поскольку Ножницы бьют Бумагу.

Наконец, вы будете бить «RPS» с вашей бумагой, как бумага бьет рок.

Вот список обозначений (вы можете использовать любые 5 литералов, но, пожалуйста, укажите в своем ответе):

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

Вот список всех возможных результатов:

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

Это , поэтому выигрывает меньше байтов.

PS: Дайте мне знать, если вам нужно больше тестов.

Койшор Рой
источник
4
Пожалуйста, измените "Звездный путь " на "Звездные войны " в вашем вступлении;)
movatica
1
Это довольно сложная проблема. Ну, или я плохо умею программировать.
CrabMan
@CrabMan Это немного сложная проблема для гольфа. особенно на практических языках.
Койшор Рой
1
несколько работ, но в теории есть бесконечные выигрышные стратегии, так что имейте это в виду
Koishore Roy
1
Связанный , а также КОТ (cc: @Arnauld)
DLosc

Ответы:

2

Желе , 29 байт

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

Монадическая ссылка, которая принимает список списков целых чисел (каждый из которых является стратегией противника), который выдает список списков целых чисел, каждый из которых является выигрышной стратегией (поэтому пустой список, если ни один из них невозможен).
(Просто добавьте, чтобы получить только один список стратегий или, 0если это невозможно.)

Попробуйте онлайн! (форматы нижнего колонтитула всегда показывают списки)

Rock  Paper  Scissors  Spock  Lizard
0     1      2         3      4

Или попробуйте версию с буквенным отображением (где стратегии взяты и показаны в собственных строках с использованием RPSVLнотации).

Как?

Числа выбираются так, чтобы любое число, которое является нечетным числом больше, чем другой выигрыш по модулю пять (то есть они пронумерованы, проходя по краю вписанного пятиугольника бросков).

Код разыгрывает каждую стратегию против каждой стратегии (включая их самих) на два раза больше бросков, чем самая длинная стратегия, с тем чтобы найти проигравших с теми, кто не побежден. Результирующий список стратегий будет содержать одну стратегию, если есть явный победитель; нет стратегий, если не было победителя; или несколько стратегий, если есть игроки розыгрыша. После этого выигрышный набор ходов добавляется к каждой из этих стратегий.

_%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)
Джонатан Аллан
источник
Я совершенно новичок в Желе, но кажется, что вы можете получить байт, заменив ZLḤна .
Робин Райдер
@RobinRyder Это не сработает - это работает только с данными примера, потому что есть достаточно противников и мало бросков - это пример того, что не сработает . Нам нужно проанализировать вдвое больше бросков, чем стратегию самого длинного противника. (Ваш код на самом деле эквивалентен этому )
Джонатан Аллан
... на самом деле из-за действия Ɗв вашем коде он даже не делает то, о чем вы, возможно, думали - он формирует каждый как его собственную длину, а затем получает кумулятивные суммы этих результатов, поэтому также сравнивает неверные значения. Попробуйте это, например, - он принимает [[1,2,3,4,5],[6,7],[8]], формирует каждую по длине всего списка (3), чтобы получить, [[1,2,3],[6,7,6],[8,8,8]]затем выполняет накопление, чтобы получить [[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]].
Джонатан Аллан
Ах, да, я знал, что что-то неправильно понял!
Робин Райдер
7

JavaScript (ES6),  122 115  112 байт

Принимает ввод как массив строк цифр, с:

  • 0
  • 1
  • 2
  • 3
  • 4

еaLsе

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

Попробуйте онлайн!

Как?

Это поиск в ширину: сначала мы пробуем все ходы на данном шаге, чтобы посмотреть, сможем ли мы выиграть игру. Если мы не можем выиграть прямо сейчас, мы пытаемся добавить еще один ход к каждому не проигрышному ходу.

AВ(В-A)модификация5 является нечетным.

AВ

(S),(П)(Р)(Л)(В)01234(S), 0-1234(П) 14-123(Р) 234-12(Л) 3234-1(В) 41234-

AВAВ

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

где andи xor- побитовые операторы.

комментарии

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
источник
Ваш код не test(['P','P','S','P','P']) подходит для теста: ответ должен быть «SR» или «SV».
Койшор Рой
@KoishoreRoy Теперь исправлено.
Арно
1
Это на самом деле блестящий подход. Я даже не думал рассматривать это как график. Я использовал словари и обратный поиск в своем оригинальном подходе (без Спока или Ящеров, т.
Е.
3

R , 213 190 байт

-23 байта благодаря Джузеппе.

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]]}

Попробуйте онлайн!

Если решение существует, оно выводит одно. Если решения не существует, выводится строкаNA . Если этот формат вывода не является приемлемым, я могу изменить его стоимостью в несколько байтов.

Ходы кодируются как 1 = R, 2 = S, 3 = P, 4 = L, 5 = V, так что матрица результатов

     [,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 = нет победителя; 1 = игрок 1 выигрывает; 2 = игрок 2 выигрывает)

Верхняя граница длиной решения , если оно существует, n=sum(lengths(L))где Lсписок ходов противников. Код создает все возможные стратегии длины n(хранится в матрицеv ), пробует их все и отображает все выигрышные стратегии.

Обратите внимание, что это значение nделает код очень медленным на TIO, поэтому я жестко закодировал в TIO, n=4что достаточно для тестовых случаев.

Для первого тестового случая вывод

     1 4 2 4

соответствующий решению RLSL.

Для второго контрольного примера вывод

 NA NA NA NA

Это означает, что нет решения.

Объяснение предыдущей версии (обновлю, когда смогу):

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
}

whichНеобходимо избавиться от ВПЛ , которые происходят , когда два игрока нарисовать навсегда.

Я не уверен, что это самая эффективная стратегия. Даже если это так, я уверен, что код для mможет быть немного в гольфе.

Робин Райдер
источник
почему lengths()псевдоним всегда возвращаться 4?
Джузеппе
1
В любом случае, пока я жду вашего ответа, я довел его до 197 , в основном, сосредоточившись на v...
Джузеппе
@Giuseppe Я навязал TIO, lengthsчтобы заставить его n=4работать, потому что в противном случае все это занимает слишком много времени.5N (Nзнак равно11) стратегии.
Робин Райдер
ах, имеет смысл, должен был знать. 187 байт
Джузеппе
@Giuseppe Спасибо, впечатляющий гольф! Я добавил 3 байта, чтобы сделать вывод более читабельным (в противном случае мы получим одно и то же решение, напечатанное много раз).
Робин Райдер
0

Emacs Lisp, 730 байт

(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)))

Я не нашел онлайн-переводчика Emacs Lisp :( Если у вас установлен Emacs, вы можете скопировать код в .elфайл, скопируйте несколько строк тестирования ниже

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

и запустить его $ emacs --script filename.el.

Как это устроено

Моя программа выполняет поиск в глубину, иногда выясняя, что невозможно выиграть, и заканчивая ветку, в которой она находится.

Вы можете увидеть полное объяснение в не сокращенной версии кода:

(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
источник
1
tio.run/##S81NTC7WzcksLvgPBAA Вы можете вставить свой код здесь и попробовать запустить его?
Койшор Рой
@KoishoreRoy Я попробовал tio.run и не мог понять, почему он не работает. Там написано: «Конечный мусор после выражения», и я понятия не имею, что это такое, и 5 минут поиска в Google не помогли мне это исправить.
CrabMan