Scala , 764 bytes
object B{
def main(a: Array[String]):Unit={
val v=false
val (m,l,k,r,n)=(()=>print("\033[H\033[2J\n"),a(0)toInt,a(1)toInt,scala.util.Random,print _)
val e=Seq.fill(k, l)(v)
m()
(0 to (l*k)/2-(l*k+1)%2).foldLeft(e){(q,_)=>
val a=q.zipWithIndex.map(r => r._1.zipWithIndex.filter(c=>
if(((r._2 % 2) + c._2)%2==0)!c._1 else v)).zipWithIndex.filter(_._1.length > 0)
val f=r.nextInt(a.length)
val s=r.nextInt(a(f)._1.length)
val i=(a(f)._2,a(f)._1(s)._2)
Thread.sleep(1000)
m()
val b=q.updated(i._1, q(i._1).updated(i._2, !v))
b.zipWithIndex.map{r=>
r._1.zipWithIndex.map(c=>if(c._1)n("X")else if(((r._2 % 2)+c._2)%2==0)n("O")else n("_"))
n("\n")
}
b
}
}
}
Como funciona
O algoritmo preenche primeiro uma sequência 2D com valores falsos. Ele determina quantas iterações (caixas abertas) existem com base nos argumentos da linha de comando inseridos. Ele cria uma dobra com esse valor como limite superior. O valor inteiro da dobra é usado apenas implicitamente como uma maneira de contar quantas iterações o algoritmo deve executar. A sequência preenchida que criamos anteriormente é a sequência inicial da dobra. Isso é usado na geração de uma nova sequência 2D de valores falsos com suas indecisões de resposta correspondente.
Por exemplo,
[[false, true],
[true, false],
[true, true]]
Será transformado em
[[(false, 0)], [(false, 1)]]
Observe que todas as listas que são completamente verdadeiras (têm um comprimento de 0) são omitidas da lista de resultados. O algoritmo pega essa lista e escolhe uma lista aleatória na lista mais externa. A lista aleatória é escolhida para ser a linha aleatória que escolhemos. A partir dessa linha aleatória, encontramos novamente um número aleatório, um índice de coluna. Depois de encontrarmos esses dois índices aleatórios, adormecemos o segmento em que estamos por 1000 milissegundos.
Depois que terminamos de dormir, limpamos a tela e criamos um novo quadro com um true
valor atualizado nos índices aleatórios que criamos.
Para imprimir isso corretamente, usamos map
e compactamos com o índice do mapa, para que o tenhamos em nosso contexto. Usamos o valor verdadeiro da sequência para determinar se devemos imprimir um X
ou um O
ou _
. Para escolher o último, usamos o valor do índice como nosso guia.
Coisas interessantes a serem observadas
Para descobrir se deve imprimir um O
ou um _
, ((r._2 % 2) + c._2) % 2 == 0
é utilizado o condicional . r._2
refere-se ao índice de linha atual, enquanto c._2
refere-se à coluna atual. Se um estiver em uma linha ímpar, r._2 % 2
será 1, compensando, portanto c._2
, um na condicional. Isso garante que, nas linhas ímpares, as colunas sejam movidas por 1 como pretendido.
A impressão da string "\033[H\033[2J\n"
, de acordo com algumas respostas do Stackoverflow que li, limpa a tela. Está escrevendo bytes no terminal e fazendo algumas coisas descoladas que eu realmente não entendo. Mas eu achei a maneira mais fácil de fazer isso. Porém, ele não funciona no emulador de console do Intellij IDEA. Você terá que executá-lo usando um terminal regular.
Outra equação pode ser estranha de se ver quando se olha pela primeira vez para esse código (l * k) / 2 - (l * k + 1) % 2
. Primeiro, vamos desmistificar os nomes das variáveis. l
refere-se aos primeiros argumentos passados para o programa enquanto k
refere-se ao segundo. Para traduzir (first * second) / 2 - (first * second + 1) % 2
,. O objetivo dessa equação é criar a quantidade exata de iterações necessárias para obter uma sequência de todos os Xs. A primeira vez que fiz isso, fiz (first * second) / 2
como fazia sentido. Para todos os n
elementos de cada sub-lista, existem n / 2
bolhas que podemos abrir. No entanto, isso é interrompido ao lidar com entradas como(11 13)
. Precisamos calcular o produto dos dois números, torná-lo ímpar, se for par, e mesmo se for ímpar, e então pegar o mod disso em 2. Isso funciona porque as linhas e colunas ímpares exigirão uma iteração a menos para chegar ao resultado final.
map
é usado em vez de a forEach
porque possui menos caracteres.
Coisas que provavelmente podem ser melhoradas
Uma coisa que realmente me incomoda nessa solução é o uso frequente de zipWithIndex
. Está ocupando tantos personagens. Tentei fazer isso para que eu pudesse definir minha própria função de um caractere que apenas executaria zipWithIndex
com o valor passado. Mas acontece que Scala não permite que uma função anônima tenha parâmetros de tipo. Provavelmente existe outra maneira de fazer o que estou fazendo sem usar, zipWithIndex
mas não pensei muito em uma maneira inteligente de fazer isso.
Atualmente, o código é executado em duas passagens. O primeiro gera uma nova placa enquanto a segunda passagem a imprime. Eu acho que se alguém combinasse essas duas passagens em uma, isso economizaria alguns bytes.
Este é o primeiro código de golfe que eu fiz, então tenho certeza de que há muito espaço para melhorias. Se você gostaria de ver o código antes de eu otimizar o máximo possível de bytes, aqui está ele.
1
e em0
vez deO
eX
?