Vamos jogar Rummikub!

11

Nota: Isso está relacionado a uma variação do jogo Rummikub


Antecedentes e Regras

Rummikub é um jogo baseado em blocos. Existem quatro cores: vermelho, laranja, azul e preto. Para cada cor, existem 13 peças (rotuladas de 1 a 13) e também 2 Coringas que são independentes de cor, portanto, existem 54 peças no total. Nesta variação de Rummikub, cada jogador recebe 14 peças e deve obter mais uma peça e largar outra a cada rodada, de modo que a contagem de peças seja constante. Os jogadores não vêem as peças um do outro. O objetivo é agrupar as peças, de modo que todas as peças pertençam a pelo menos um grupo (veja abaixo). Quando um jogador tem todas as peças agrupadas, eles largam o tabuleiro e revelam suas peças. Os outros então verificam se todas as combinações são válidas e, se forem, o jogador vence a rodada.

Como os ladrilhos podem ser agrupados?

Existem apenas dois tipos de grupos:

  • Grupos multicoloridos :

    • Eles consistem em 3 ou 4 peças.
    • Eles contêm apenas peças com o mesmo número nelas.
    • Todos os azulejos são de cores diferentes.
    • Exemplo: RED 9, BLUE 9, BLACK 9.
  • Grupos monocromáticos :

    • Eles consistem em pelo menos 3 peças.
    • Eles não podem conter mais de 13 peças.
    • Eles contêm apenas peças com números consecutivos diferentes, em ordem crescente.
    • Todas as peças têm a mesma cor.
    • Os ladrilhos marcados com 1 podem não ser lugares depois dos ladrilhos rotulados 13.
    • Exemplo: RED 5, RED 6, RED 7.

Espere, o que os curingas fazem?

Os curingas podem substituir qualquer peça do jogo. Por exemplo, nosso primeiro exemplo pode se tornar JOKER, BLUE 9, BLACK 9, RED 9, JOKER, BLACK 9ou RED 9, BLUE 9, JOKER. O mesmo se aplica ao nosso outro exemplo. No entanto, não se pode colocar dois Coringas no mesmo grupo, então coisas como JOKER, ORANGE 8, JOKERsão proibidas.


Tarefa

Dado um grupo de blocos Rummikub, determine se é válido. Você tem a garantia de que nenhum bloco duplicado aparecerá, exceto os 2 curingas e que os blocos recebidos como entrada são válidos (por exemplo, itens como 60não aparecerão).

Entrada / Saída

Você pode receber e fornecer a saída por qualquer método padrão.

Alguns formatos de entrada válidos: lista de strings, lista de tuplas, listas aninhadas, strings ou qualquer outra coisa que você achar adequado. As cores podem ser tomadas como Strings (por exemplo:) "Blue","Red", etc., como abreviações de String (por favor, diferencie os azulejos azuis e pretos) ou como números inteiros correspondentes a uma cor. Quando se trata de Jokers, você deve mencionar a maneira como seu programa os recebe como entrada. Se você escolher Strings, poderá ter algo parecido RED 9, JOKER, ..., se você escolher tuplas que poderá ter (9,"RED"), ("JOKER")ou algo equivalente. Se ajudar, você pode receber uma cor para esse Coringa (o que não deve afetar a saída do seu programa). Por exemplo, você pode ter ("JOKER","RED")ou ("JOKER","BLUE"), mas isso não deve influenciar a saída de forma alguma.

Em relação à saída, aplicam-se regras padrão para um .

Exemplos trabalhados

Vamos dar um exemplo, que, com sorte, facilitaria o entendimento. Dado um grupo da seguinte forma, em que cada tupla representa um bloco:

[(9, "VERMELHO"), (9, "LARANJA"), ("JOKER"), (9, "PRETO")]

Isso deve retornar um valor verdadeiro, porque a entrada é válida. Nesse caso, o Coringa substitui (9, "BLUE")e eles formam um grupo multicolorido.

Se você receber o seguinte grupo:

[(9, "AZUL"), (9, "LARANJA"), (9, "VERMELHO"), (9, "PRETO"), ("JOKER")]

Seria inválido e, portanto, o programa deve retornar um valor falso, porque não resta mais nada para o coringa substituir, porque o número máximo de cartões em um grupo multicolorido é 4.

Casos de teste adicionais

São para um conjunto de testes estendido que cobre quase todas as situações possíveis:

Entrada -> Saída 

[(1, "AZUL"), (2, "AZUL"), (3, "AZUL"), (4, "AZUL"), (5, "AZUL"), (6, "AZUL")] - > verdade

[(6, "AZUL"), (6, "VERMELHO"), (6, "PRETO)] -> verdade

[(5, "PRETO"), (6, "PRETO"), (7, "PRETO"), (8, "PRETO"), (9, "PRETO"), (10, "PRETO"), ( "JOKER"), (12, "BLACK")] -> verdade 

[("JOKER"), (3, "AZUL"), (3, "VERMELHO")] -> verdade

[(8, "PRETO"), (2, "VERMELHO"), (13, "AZUL")] -> falsidade

[(4, "VERMELHO"), (3, "VERMELHO"), (5, "VERMELHO")] -> falsidade

[(5, "PRETO"), (6, "PRETO)] -> falsidade

[("JOKER"), (5, "RED"), ("JOKER")] -> falsy

[(4, "VERMELHO"), (5, "VERMELHO"), (6, AZUL ")] -> falsidade

[(4, "VERMELHO"), ("JOKER"), (5, "VERMELHO")] -> falsy

[(12, "PRETO"), (13, "PRETO), (1," PRETO ")] -> falsy

Isso é , então o código mais curto em bytes em todos os idiomas vence!

Mr. Xcoder
fonte
Relacionado
Peter Taylor
Roubar é a melhor parte do rummikub. Mesmo sem isso, isso parece um desafio divertido.
197 Josiah
[] É uma entrada válida?
V. Courtois
@ V.Courtois Claro.
Mr. Xcoder
11
@ V.Courtois não se pode colocar dois Coringas no mesmo grupo , então duas entradas contendo 2 Coringas são falsas.
Sr. Xcoder

Respostas:

6

APL (Dyalog) , 58 bytes

Leva a lista de cores (1-4) como argumento da direita e a lista de números como argumento da esquerda. O número de um Coringa é indicado, o (⍳4)que equivale (1 2 3 4)a indicar que poderia ser qualquer um deles. Da mesma forma, sua cor é indicada (⍳13)para indicar que poderia ser qualquer um dos números de 1 a 13.

{(3≤≢⍺)∧((s⍵)∧⍺≡∪⍺)∨((s←{1∊≢∘∪¨⊃,¨/⍵})⍺)∧∨/∊(⊃,¨/⍵)⍷¨⊂⍳13}

Experimente online!

Algoritmo

Existem três condições, das quais as duas últimas têm duas condições cada:

  1. A corrida deve ter comprimento maior ou igual a 3

E TAMBÉM

    1. um único número E

    2. cores únicas

OU

    1. uma única cor e
    2. números seqüenciais

para que a execução seja válida.

Ordem de leitura

3≤3 é menor ou igual ao ≢⍺número de peças

e

   s⍵ todos os números são iguais

   e

   ⍺≡∪⍺ as cores são únicas

ou

   1∊1 está entre ≢∘∪¨o número de  cores ⊃,¨/expandidas exclusivas

   e

   ∨/existe pelo menos um dentre todos os ⊃,¨/⍵números expandidos, ⍷¨⊂um que é encontrado em⍳13 1 a 13

Explicação completa do código

{} Função anônima onde é argumento à esquerda e argumento à direita

3.2

⍳13 os números de 1 a 13

()⍷¨Encontre as posições iniciais de cada uma das seguintes execuções:

  ,¨/⍵ juntar cada elemento dos números (cria uma execução para cada valor do Joker)

   divulgar (porque /reduz a classificação)

  ε nlist (achatar)

∨/ OU redução (ou seja, é verdade?)

()∧ E:

3.1.

  ()⍺ O resultado da aplicação da seguinte função na lista de cores:

   s←{... }s (para s ame), que é a seguinte função anônima ( é o seu argumento):

    ,¨/⍵ juntar cada elemento (cria uma execução para cada valor do Joker)

     divulgar (porque /reduz a classificação)

    ≢∘∪¨ o número de elementos exclusivos em cada lista

    1∊ é um membro? (ou seja, existem listas iguais?)

()∨OU:

2.2

  ∪⍺ as cores únicas

  ⍺≡ são idênticas às cores (ou seja, são únicas)

  ()∧ E:

2.1

   s⍵ os números são todos iguais

  (... )∧E

1

   ≢⍺ o número de cores (ou seja, o número de peças)

   3≤ três é menor ou igual a esse

Adão
fonte
11
Uau, parece que a APL é uma grande ferramenta para este desafio
Mr. Xcoder
3

Geléia , 41 40 38 36 bytes

EȧI=1ȦȯE
0,W€yµZç/ɓQ⁼⁸ȧ
L>2ȧ4p13ðç€Ṁ

Experimente online! (vem com um rodapé da suíte de teste)

Recebe a entrada como uma matriz de (color, value)peças comuns e 0de coringas. As cores são representadas como números inteiros (embora eu não tenha certeza se isso importa para o código atual).

Saídas 1(verdade) ou 0(falsas).

Explicação

L>2ȧ4p13ðç€Ṁ    Main link, checks if a sequence is valid. Args: sequence
L                 Get the length of the sequence.
 >2               Check if it's at least 3 tiles.
   ȧ4             And: yield 4 if it is, 0 otherwise.
     p13          Cartesian product: yield all possible tiles if
                  result was 4, empty array otherwise.
        ð         Begin a new dyadic chain with args (tiles, sequence).
         ç€       Call the first helper link for each tile with args (tile, sequence).

0,W€yµZç/ɓQ⁼⁸ȧ    First helper link, checks if a sequence is valid if jokers
                  are substituted for the given tile. Args: tile, sequence
0,                  Make a pair [0, tile].
  W€                Turn that into [[0], [tile]].
    y               Map all 0's (jokers) into tile in the sequence.
     µ              Begin a new monadic chain with args (sequence).
      Z             Transpose to get list [colors, values].
       ç/           Call the second helper link with args (colors, values).
         ɓ          Begin a new dyadic chain with args (sequence, valid).
          Q         Remove duplicate tiles from the sequence.
           ⁼⁸       Check if the sequence is unchanged (i.e. there were no duplicates).
             ȧ      And with the output of the second helper.

EȧI=1ȦȯE    Second helper link, checks if a sequence is valid assuming no duplicates.
            Args: colors, values
E             Check if all the colors are the same.
 ȧ            Logical and with the values array.
              Yields the values if they were, 0 if not.
  I           Find the differences between each value.
              Yields [] if the colors differed.
   =1         See if each difference is equal to 1.
              Yields [] if the colors differed.
     Ȧ        Check if the list was nonempty and all values were truthy.
              Yields 1 for valid mono-colors, 0 otherwise.
      ȯ       Logical or with the values array.
              Yields 1 for valid mono-colors, the values otherwise.
       E      Check if all the values are the same. For valid mono-colors
              this tests if all items of [1] are equal (obviously true).
              Yields 1 for valid sequences, 0 otherwise.
PurkkaKoodari
fonte
Eu acho que você tem que produzir uma verdade / falsidade consistente.
Adám
@ Adám Editado, felizmente não afetou a contagem de bytes.
PurkkaKoodari
2

Python 2 , 371 370 362 341 329 325 bytes

  • @ Mr.Xcoder economizou 1 byte: em str.split()vez delist literal
  • 8 bytes salvos: abreviação de len(x)-1
  • 19 bytes salvos: J O BK B Rpara Joker, Orange, Black, Blue, Redliterais
  • @ Mr.Xcoder salvou mais 12 bytes, obrigado!
  • Mais 4 bytes graças a @ Mr.Xcoder
def f(x):
 j=sum("J"in i for i in x);z=len(x)-1
 if j>1or z<2:return False
 if j<1:return(all(i[0]==x[0][0]for i in x)and sum(i[1]==x[0][1]for i in x)<2)or(all(i[1]==x[0][1]for i in x)and sum(int(x[m+1][0])==int(x[m][0])+1for m in range(z))==z)
 return any(f([[k,(i+1,j)]["J"in k]for k in x])for j in'RBbO'for i in range(13))

Experimente online!

officialaimm
fonte
2
370 bytes
Sr. Xcoder
11
Na verdade, isso economiza muito mais bytes do que eu pensava: 329 .
Mr. Xcoder
11
325 bytes . Desculpe pela melhoria muito tardia .
Mr. Xcoder
1

Javascript (ES6), 286 bytes

var testcases = [[{n:1,c:"BLUE"},{n:2,c:"BLUE"},{n:3,c:"BLUE"},{n:4,c:"BLUE"},{n:5,c:"BLUE"}, {n:6,c:"BLUE"}],[{n:6,c:"BLUE"},{n:6,c:"RED"},{n:6,c:"BLACK"}],[{n:5,c:"BLACK"},{n:6,c:"BLACK"},{n:7,c:"BLACK"},{n:8,c:"BLACK"},{n:9,c:"BLACK"},{n:10,c:"BLACK"},{n:0,c:"JOKER"},{n:12,c:"BLACK"}],[{n:0,c:"JOKER"},{n:3,c:"BLUE"},{n:3,c:"RED"}],[{n:8,c:"BLACK"},{n:2,c:"RED"},{n:13,c:"BLUE"}],[{n:4,c:"RED"}, {n:3,c:"RED"}, {n:5,c:"RED"}],[{n:5,c:"BLACK"}, {n:6,c:"BLACK"}],[{n:0,c:"JOKER"},{n:5,c:"RED"},{n:0,c:"JOKER"}],[{n:4,c:"RED"},{n:5,c:"RED"},{n:6,c:"BLUE"}],[{n:4,c:"RED"},{n:0,c:"JOKER"},{n:5,c:"RED"}],[{n:12,c:"BLACK"},{n:13,c:"BLACK"},{n:1,c:"BLACK"}],[{n:11,c:"BLACK"},{n:12,c:"BLACK"},{n:0,c:"JOKER"}],[{n:1,c:"BLACK"},{n:2,c:"BLACK"},{n:3,c:"BLACK"},{n:1,c:"BLUE"},{n:2,c:"BLUE"},{n:3,c:"BLUE"}]];

g=a=>a.length
j=a=>a.n==0
l=(x,y)=>x.c==y.c||j(x)||j(y)
a=s=>g(s)>2&&([q=[0],x=s[0],s.map(y=>q[0]+=x==y||((l(x,y)||x.n==y.n)&&!(j(x)&&j(y)))&&(([n=s.indexOf(y),n<1||([x=s[n-1],!l(x,y)||y.n>0&&x.n<y.n])[1]||(n<g(s)-1&&x.n+1<s[n+1].n)||(n==g(s)-1&&y.n==0&&x.n<13)])[1])?1:0)])[0][0]==g(s)

testcases.forEach(H=>console.log(a(H)));

(Observe que os casos de teste acima contêm 2 casos de teste adicionais que não estão na pergunta: eles são verdadeiros e falsos, respectivamente: consulte a versão não destruída para facilitar a leitura).

Processo áspero:

 Using first tile x:
   For each tile y:
     count for x: can group with y
 return: x matches n tiles, where n is the number of tiles

Os curingas são indicados por ter um 0como valor numérico (um número negativo também funcionaria); isso mantém a estrutura de entrada consistente (tem uma cor e um valor) e não depende de ter que verificar se c=="JOKER", economizando 7 bytes.

É possível que alguns parênteses possam ser removidos, talvez não seja possível q como uma matriz (tentei e o valor ficou 0 ou causou demônios nasais ).

Ungolfed:

var testcases = [
[{n:1,c:"BLUE"},{n:2,c:"BLUE"},{n:3,c:"BLUE"},{n:4,c:"BLUE"},{n:5,c:"BLUE"}, {n:6,c:"BLUE"}],//true
[{n:6,c:"BLUE"},{n:6,c:"RED"},{n:6,c:"BLACK"}],//true
[{n:5,c:"BLACK"},{n:6,c:"BLACK"},{n:7,c:"BLACK"},{n:8,c:"BLACK"},{n:9,c:"BLACK"},{n:10,c:"BLACK"},{n:0,c:"JOKER"},{n:12,c:"BLACK"}],//true
[{n:0,c:"JOKER"},{n:3,c:"BLUE"},{n:3,c:"RED"}],//true
[{n:8,c:"BLACK"},{n:2,c:"RED"},{n:13,c:"BLUE"}],//false
[{n:4,c:"RED"}, {n:3,c:"RED"}, {n:5,c:"RED"}],//false
[{n:5,c:"BLACK"}, {n:6,c:"BLACK"}],//false
[{n:0,c:"JOKER"},{n:5,c:"RED"},{n:0,c:"JOKER"}],//false
[{n:4,c:"RED"},{n:5,c:"RED"},{n:6,c:"BLUE"}],//false
[{n:4,c:"RED"},{n:0,c:"JOKER"},{n:5,c:"RED"}],//false
[{n:12,c:"BLACK"},{n:13,c:"BLACK"},{n:1,c:"BLACK"}],//false
[{n:11,c:"BLACK"},{n:12,c:"BLACK"},{n:0,c:"JOKER"}],//true
[{n:1,c:"BLACK"},{n:2,c:"BLACK"},{n:3,c:"BLACK"},{n:1,c:"BLUE"},{n:2,c:"BLUE"},{n:3,c:"BLUE"}]
];

g=a=>a.length
i=(a,v)=>a.indexOf(v)
j=x=>x.n==0
m=(x,y)=>
       (l(x,y)||x.n==y.n)
    &&!(j(x)&&j(y))
l=(x,y)=>x.c==y.c||j(x)||j(y)
c=(a,v)=>([n=i(a,v),
      n<1
    ||([x=a[n-1],!l(x,v)||v.n>0&&x.n<v.n])[1]
    ||(n<g(a)-1&&x.n+1<a[n+1].n)
    ||(n==g(a)-1&&v.n==0&&x.n<13)])[1]
a=s=>g(s)>2&&([q=[0],x=s[0],s.map(y=>q[0]+=x==y||m(x,y)&&c(s,y)?1:0)])[0][0]==g(s)

testcases.forEach(H=>console.log(a(H)));

Versão em que trabalhei para obter a lógica correta. Lambdas descartáveis ​​foram alinhadas; aqui está a função correspondente:

g() -> string.length
i() -> indexof
j() -> isJoker
m() -> do tiles match
l() -> do colors match
c() -> same-color isConsecutiveOrder
a() -> main lambda
Draco18s não confia mais no SE
fonte
1

C # (.NET Core) , 198 bytes

using System.Linq;(C,N)=>{int l=C.Length,j=C.Count(x=>x<1),c=C.Distinct().Count(),n=N.Distinct().Count(),u=N.Min();foreach(var x in N)u*=0<(u&x)?2:0;return l>2&((u>0&n==l&c<2+j)|(n<2+j&c==l&l<5));};

Toma as cores dos azulejos e os números neles como listas separadas de números inteiros. As especificidades desse mapeamento não importam, desde que cada cor tenha um número inteiro diferente e Jokers sejam representados como 0.

O formato para inserir números é bastante especial. O número que precisa ser inserido para um número né 2 ^ n, enquanto o número usado para representar um curinga deve ser (2 ^ 14) -1. Isso habilita o bit a bit e u&xavalia para u se o bloco x tem valor igual a u ou é um curinga.

C # (.NET Core) , 200 bytes

using System.Linq;(C,N)=>{int l=C.Length,j=N.Count(x=>x<1),c=C.Distinct().Count(),n=N.Distinct().Count(),u=N.Min();foreach(var x in N)u=u==x|x<1?u+1:0;return l>2&((u>0&n==l&c<2+j)|(n<2+j&c==l&l<5));};

Uma solução de 2 bytes a mais que não é eclética quanto à entrada. Acontece que o uso de um estojo especial para brincalhões no local em que eles eram difíceis de lidar não demorou muito mais do que a operação inteligente que eu tanto orgulho. Aqui estão Jokers (0,0), outros números são os esperados e as cores são representadas com 4 valores que são distintos entre si pela comparação padrão do C # (especificamente, o LinqDistinct() operação deve considerar valores para a mesma cor como 'não distintos' e valores para cores diferentes como 'distintos').

Algo que pode ser útil para outros idiomas u*=!u++^x*xseria equivalente u=u==x|x<1?u+1:0em alguns idiomas; u ^ x é 0 se u == x e 0 vezes qualquer int é 0, então u ^ x * x seria 0 para u == x ou x == 0 se C # não fizesse operações bit a bit com precedência menor do que matemáticos. O C # também não pode interpretar ints como bools sem conversão explícita. Uma linguagem que tenta mais difícil de fazer tipos de trabalho pode converter os valores 0e not 0para falsee trueantes de aplicar !a eles, porém, e em seguida, quando voltar para um int interpretar !falsecomo 1 e !truecomo 0. Tudo o que disse, eu não posso garantir outra língua seria, na verdade, tirar proveito do restante do algoritmo, para que ele nem seja exibido.

Kamil Drakari
fonte
1

Scala, 491 477 caracteres, 491 477 bytes

Esse desafio foi divertido; obrigado.

var c=Seq("O","B","b","R")
t match{case _ if t.length<3=>false
case _ if t.exists(x=>x._1==0)=>{var b=false
if(t.filter(q=>q._1!=0).exists(q=>q._1==0))b else{for(y<-1 to 13)for(u<-c)b=b|f(t.takeWhile(q=>q._1!=0)++:(y,u)+:t.reverse.takeWhile(q=>q._1!=0).reverse)
b}}
case _::(x,_)::_ if t.forall(_._1==x)=>true
case _ if t.forall(_._2==c(0))|t.forall(_._2==c(1))|t.forall(_._2==c(2))|t.forall(_._2==c(3))=>(t(0)._1 to t(0)._1+t.length-1).toList equals t.map(_._1)
case _=>false}

Então, fna linha 4, há uma chamada recursiva em que tento substituir "JOKER" por todos os outros blocos. Consulte a seção para uma visão mais clara do código. Eu escolhi tomar como entrada uma sequência de 2 tuplas (Int, String) - chamada tno meu código, veja tio - para que "JOKER" seja representado por uma 2-tupla (0, "JOKER").

EDIT: 14 bytes salvos graças aos comentários, tomo OB b R para ORANGE BLACK BLUE RED.

Experimente Online!

EDIT: -2 ​​bytes, excluídos inúteis em (torno das condições do case _ ifs

V. Courtois
fonte
Você não pode usar em O,B,b,Rvez de ORANGE,BLUE,BLACK,REDsalvar bytes? Não tenho ideia de como Scala funciona, mas acho que você pode.
Sr. Xcoder
Eu tentei; na verdade, ele salva bytes dessa maneira (uma sequência de seqüências de caracteres). Faz var (O,B,b,R)=("ORANGE","BLACK","BLUE","RED")e chama são O B b R, para um total de 49 bytes; onde var c=Seq("ORANGE","BLACK","BLUE","RED")e chamadas c(...)totalizam 58 bytes. MAS o primeiro caso permite for(u<-c)no lugar de for(u<-Seq(O,B,b,R)), então o custo não é -9, mas +2. Obrigado por tentar embora.
V. Courtois
@ V.Courtois Acredito que o que o Sr. Xcoder estava sugerindo está usando var c=Seq("O","B","b","R")e tomando esses caracteres como suas entradas, em vez de seqüências completas de cores. Como mencionado na postagem original, "As cores podem ser consideradas como ... Abreviações de sequência".
Kamil Drakari
ohh ~ eu entendo o que você quer dizer, obrigado @ vocês dois
V. Courtois