Em 4 de dezembro de 2017, o Google Doodle era um jogo de programação gráfica com um coelho . Os níveis posteriores não eram triviais e pareciam um ótimo candidato para um desafio de golfe atômico .
Detalhes
jogos
- Existem quatro movimentos disponíveis: pule para frente, vire à esquerda, vire à direita e faça um loop. Cada um desses movimentos é um símbolo , correspondendo ao fato de que eles são cada um dos blocos no jogo.
- O coelho pode enfrentar quatro direções ortogonais (ou seja, norte, sul, leste, oeste).
- O coelho pode pular para frente (mover um quadrado na direção em que está voltado) e virar à esquerda ou à direita.
- Os loops podem ter vários outros movimentos dentro deles, incluindo outros loops, e sua contagem de iterações é qualquer número inteiro positivo (embora o jogo permita tecnicamente uma contagem de iterações de 0).
- O tabuleiro é um conjunto de quadrados alinhados à grade e o coelho pode pular entre quadrados adjacentes.
- O coelho não pode pular no vazio. Significando que uma tentativa de pular do quadro não faz nada. (Isso aparentemente foi uma surpresa para algumas pessoas e uma decepção para outras.)
- Os quadrados são marcados ou desmarcados. Quando o coelho está em um quadrado, ele fica marcado.
- O nível está completo quando todos os quadrados estão marcados.
- Você pode assumir que existe uma solução.
Seu código
- Objetivo: dado um quadro, encontre uma ou mais soluções mais curtas.
- Entrada é uma lista de locais quadrados que formam o quadro (distinguindo quadrados marcados e não marcados) e saída é uma lista de movimentos. O formato de entrada e saída não importa, desde que sejam legíveis por humanos e compreensíveis.
- Critério de vitória: soma do número de jogadas das soluções mais curtas encontradas dentro de um minuto para cada placa. Se o seu programa não encontrar uma solução para um quadro em particular, sua pontuação nesse quadro é (5 * número de quadrados).
- Por favor, não codifique soluções de nenhuma maneira. Seu código deve poder receber qualquer placa como entrada, não apenas as fornecidas como exemplos abaixo.
Exemplos
As soluções estão ocultas nos spoilers para lhe dar a chance de jogar o jogo primeiro e experimentar algumas delas você mesmo. Além disso, apenas uma solução é fornecida abaixo para cada um.
S
é o quadrado inicial do coelho (voltado para o leste), #
é um quadrado não marcado e O
é um quadrado marcado. Para movimentos, minha notação é F
= pular para frente, L
= virar à esquerda,R
= virar à direita e LOOP(<num>){<moves>}
denota um loop que itera <num>
vezes e faz a <moves>
cada vez. Se o loop puder executar qualquer número de vezes além de um número mínimo, <num>
poderá ser omitido (ou seja, o infinito funciona).
Nível 1:
S##
FF
Nível 2:
S##
#
#
LOOP (2) {FFR}
Nível 3:
S##
# #
###
LOOP {FFR}
Nível 4:
###
# #
##S##
# #
###
LOOP {F LOOP (7) {FL}} (encontrado por DJMcMayhem)
Nível 5:
#####
# # #
##S##
# # #
#####
LOOP (18) {LOOP (10) {FR} L}
Fonte: Reddit
Nível 6:
###
#OOO#
#OSO#
#OOO#
###
LOOP {LOOP (3) {F} L}
Placas enormes: (as menores soluções atualmente desconhecidas)
12x12:
S###########
############
############
############
############
############
############
############
############
############
############
############
Nível 5, mas muito maior:
#############
# # # # # # #
#############
# # # # # # #
#############
# # # # # # #
######S######
# # # # # # #
#############
# # # # # # #
#############
# # # # # # #
#############
Mais placas holey:
S##########
###########
## ## ## ##
###########
###########
## ## ## ##
###########
###########
## ## ## ##
###########
###########
e
S#########
##########
## ## ##
## ## ##
##########
##########
## ## ##
## ## ##
##########
##########
Finalmente, a assimetria pode ser uma verdadeira dor de cabeça:
#######
# ## #
#######
###S###
# ## #
# ## #
#######
e
#########
# ## ###
###S ###
# #######
### ##
##### #
#### ###
#########
#########
fonte
Respostas:
Python 3, 67 tokens
Este programa testará uma única placa de entrada, mas acho esse driver de teste mais útil . Ele testará todas as placas ao mesmo tempo e imprimirá quanto tempo levou para encontrar essa solução. Quando executo esse código na minha máquina (CPU Intel i7-7700K quad core a 4,20 GHz, 16,0 GB de RAM), recebo a seguinte saída:
Este último teste mal chega abaixo da restrição de minutos.
fundo
Este foi um dos desafios mais divertidos que já respondi! Eu tinha um padrão de explosão caçando e procurando heurísticas para reduzir as coisas.
Geralmente, aqui no PPCG eu tendem a responder perguntas relativamente fáceis. Gosto especialmente da tag string, porque geralmente é muito adequada para meus idiomas. Um dia, cerca de duas semanas atrás, eu estava examinando meus distintivos e percebi que nunca havia conseguido o distintivo de avivamento . Então eu olhei através da respostaguia para ver se alguma coisa chamou minha atenção, e eu encontrei esta pergunta. Eu decidi que iria responder, não importa o custo. Acabou sendo um pouco mais difícil do que eu pensava, mas finalmente recebi uma resposta de força bruta da qual posso dizer que me orgulho. Mas esse desafio está totalmente fora do normal para mim, pois geralmente não passo mais de uma hora em uma única resposta. Essa resposta levou um pouco mais de duas semanas e pelo menos 10 ou mais de trabalho para finalmente chegar a esse estágio, embora eu não estivesse acompanhando atentamente.
A primeira iteração foi uma solução pura de força bruta. Usei o código a seguir para gerar todos os trechos com comprimento N :
Nesse ponto, eu tinha certeza de que testar todas as respostas possíveis seria muito lento, então eu costumava
isSnippetRedundant
filtrar trechos que poderiam ser escritos com um trecho mais curto. Por exemplo, eu me recusaria a produzir o trecho["F", "F", "F"]
porque os mesmos efeitos poderiam ser alcançados[Loop(3, ["F"])
; portanto, se chegarmos ao ponto em que testamos trechos de comprimento 3, sabemos que nenhum trecho de comprimento 3 poderia resolver o quadro atual. Isso usou muitas boas mnemônicas, mas acabou sendo ótimomuito devagar. O Testcase 12 levou pouco mais de 3.000 segundos usando essa abordagem. Isso é claramente significativamente muito lento. Mas, usando essas informações e vários ciclos de computador para soluções breves de força bruta para todas as placas, pude encontrar um novo padrão. Percebi que quase todas as soluções encontradas geralmente se parecem com as seguintes:aninhou várias camadas de profundidade, com as coms individuais de cada lado sendo opcionais. Isso significa que soluções como:
nunca apareceria. De fato, nunca houve uma sequência de mais de três tokens sem loop. Uma maneira de utilizar isso seria filtrar todos eles e não se preocupar em testá-los. Mas gerá-los ainda levava um tempo não desprezível e filtrar milhões de trechos como esse dificilmente reduziria o tempo. Em vez disso, reescrevi drasticamente o gerador de código para gerar apenas trechos seguindo esse padrão. No pseudo-código, o novo gerador segue esse padrão geral:
Isso reduziu o caso de teste mais longo para 140 segundos, o que é uma melhoria ridícula. Mas a partir daqui, ainda havia algumas coisas que eu precisava melhorar. Comecei a filtrar de forma mais agressiva o código redundante / sem valor e a verificar se o código já havia sido testado antes. Isso reduziu ainda mais, mas não foi suficiente. No final, a peça final que faltava era o contador de voltas. Através do meu algoritmo altamente avançado (leia-se: tentativa e erro aleatórios ), determinei que o intervalo ideal para permitir a execução de loops é [3-8]. Mas há uma grande melhoria: se sabemos que isso
[loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
não pode resolver nossa diretoria, então não há absolutamente nenhuma maneira de[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]
ou qualquer contagem de loop de 3 a 7 pode resolvê-lo. Portanto, em vez de percorrer todos os tamanhos de loop de 3 a 8, definimos a contagem de loop no loop externo para o máximo. Isso acaba reduzindo o espaço de pesquisa por um fator demaxLoop - minLoop
, ou 6 nesse caso.Isso ajudou muito, mas acabou inflando o placar. Certas soluções que eu encontrei anteriormente por força bruta exigem um número maior de loop (por exemplo, placas 4 e 6). Então, em vez de definir a contagem de loop externo como 8, definimos a contagem de loop externo como 17, um número mágico também calculado pelo meu algoritmo altamente avançado. Sabemos que podemos fazer isso porque aumentar a contagem do loop mais externo não afeta a validade da solução. Este passo reduziu nossa pontuação final em 13. Portanto, não é um passo trivial.
fonte