Google'ın Atlamalı Tavşan


16

4 Aralık 2017'de Google Doodle, bir tavşan içeren grafiksel bir programlama oyunuydu . Daha sonraki seviyeler önemsiz değildi ve için harika bir aday gibi görünüyordu mücadelesi .

ayrıntılar

oyun

  • Dört hareket vardır: ileri atla, sola dön, sağa dön ve döngü. Bu hareketlerin her biri, oyundaki her bir karo oldukları gerçeğine karşılık gelen bir jeton .
  • Tavşan dört dikey yöne bakabilir (yani kuzey, güney, doğu, batı).
  • Tavşan ileri atlayabilir (bir kareyi baktığı yönde hareket ettirebilir) ve sola veya sağa dönebilir.
  • Döngüler içinde diğer döngüler de dahil olmak üzere herhangi bir sayıda başka hareket olabilir ve yineleme sayıları herhangi bir pozitif tamsayıdır (oyun teknik olarak 0 yineleme sayısına izin verir).
  • Tahta bir dizi ızgara hizalanmış karedir ve tavşan bitişik kareler arasında atlayabilir.
  • Tavşan boşluğa atlayamaz. Yani tahta atlamak için bir girişim hiçbir şey yapmaz. (Bu görünüşe göre bazı insanlar için bir sürpriz ve diğerleri için bir hayal kırıklığıydı.)
  • Kareler işaretlenir veya işaretlenmez. Tavşan bir kare üzerindeyken işaretlenir.
  • Tüm kareler işaretlendiğinde seviye tamamlanmıştır.
  • Bir çözümün var olduğunu varsayabilirsiniz.

Senin kodun

  • Amaç: bir tahta verildiğinde, bir veya daha fazla kısa çözüm bulun.
  • Girdi, tahtayı oluşturan kare konumların bir listesidir (işaretli ve işaretsiz kareleri ayırt eder) ve çıktı bir hamle listesidir. Giriş ve çıkış formatı , insanlar tarafından okunabilir ve anlaşılabilir olduğu sürece hiç önemli değildir.
  • Kazanan kriter: her anakart için bir dakika içinde bulunan en kısa çözümlerin hamle sayısı. Programınız herhangi bir kart için bir çözüm bulamazsa, o kart için puanınız (5 * kare sayısı) olur.
  • Lütfen çözümleri hiçbir şekilde sabit kodlamayın. Kodunuz, yalnızca aşağıdaki örneklerde verilenleri değil, herhangi bir kartı giriş olarak alabilmelidir.

Örnekler

Çözümler, önce oyunu oynama ve bunlardan bazılarını kendiniz denemeniz için spoiler içinde gizlidir. Ayrıca, her biri için aşağıda yalnızca bir çözüm sunulmaktadır.

Stavşanın başlangıç ​​karesi (doğuya bakan), işaretlenmemiş bir karedir #ve Oişaretli bir karedir. Hareketler için, gösterimim F= ileri atla, L= sola dön, R= sağa dön ve her seferinde tekrarlayan ve LOOP(<num>){<moves>}yapan bir döngüyü gösterir . Döngü minimum sayıdan daha fazla sayıda çalışabiliyorsa, atlanabilir (örn. Sonsuz çalışır).<num><moves><num>

Seviye 1:

S##

FF

Seviye 2:

S##
  #
  #

Döngü (2) {FFR}

3. seviye:

S##
# #
###

DÖNGÜ {FFR}

Seviye 4:

###
# #
##S##
  # #
  ###

LOOP {F LOOP (7) {FL}} (DJMcMayhem tarafından bulundu)

Seviye 5:

#####
# # #
##S##
# # #
#####

LOOP (18) {LOOP (10) {FR} L}
Kaynak: Reddit

Seviye 6:

 ###
#OOO#
#OSO#
#OOO#
 ###

DÖNGÜ {DÖNGÜ (3) {F} L}

Büyük tahtalar: (şu anda bilinmeyen en kısa çözümler)

12x12:

S###########
############
############
############
############
############
############
############
############
############
############
############

Seviye 5 ama çok daha büyük:

#############
# # # # # # #
#############
# # # # # # #
#############
# # # # # # #
######S######
# # # # # # #
#############
# # # # # # #
#############
# # # # # # #
#############

Daha delikli tahtalar:

S##########
###########
## ## ## ##
###########
###########
## ## ## ##
###########
###########
## ## ## ##
###########
###########

ve

S#########
##########
##  ##  ##
##  ##  ##
##########
##########
##  ##  ##
##  ##  ##
##########
##########

Son olarak, asimetri popoda gerçek bir ağrı olabilir:

#######
# ##  #
#######
###S###
# ##  #
# ##  #
#######

ve

#########
# ##  ###
###S  ###
# #######
###    ##
#####   #
####  ###
#########
#########


"bir veya daha fazla kısa çözüm bulun" Durma sorununun bunu yasakladığını düşündüm
Leaky Nun

@Leaky Nun Bu, durma problemiyle ilgili değildir. Bu bir grafik arama
WhatToDo

Ama döngüye izin verilir ...
Leaky Nun

4
Bence geçerli değil çünkü tahta sınırlı. Her döngü için sonsuza kadar çalışır veya durur. İçinde döngüsüz bir döngü, yalnızca yineleme sayısı argümanı düşürüldüğünde sonsuza kadar döngü yapar. Bu durumda, sınırlı sayıda kart durumu, döngünün kontrol edilebilecek durumları tekrarlamaya başlayacağını garanti eder.
WhatToDo

Yanıtlar:


12

Python 3, 67 simgeleri

import sys
import time

class Bunny():
    def __init__(self):
        self.direction = [0, 1]
        self.coords = [-1, -1]

    def setCoords(self, x, y):
        self.coords = [x, y]

    def rotate(self, dir):
        directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]
        if dir == 'L':
            self.direction = directions[(directions.index(self.direction) + 1) % 4]
        if dir == 'R':
            self.direction = directions[(directions.index(self.direction) - 1) % 4]

    def hop(self):
        self.coords = self.nextTile()

    # Returns where the bunny is about to jump to
    def nextTile(self):
        return [self.coords[0] + self.direction[0], self.coords[1] + self.direction[1]]

class BoardState():
    def __init__(self, map):
        self.unvisited = 0
        self.map = []

        self.bunny = Bunny()
        self.hopsLeft = 0

        for x, row in enumerate(map):
            newRow = []
            for y, char in enumerate(row):
                if char == '#':
                    newRow.append(1)
                    self.unvisited += 1

                elif char == 'S':
                    newRow.append(2)

                    if -1 in self.bunny.coords:
                        self.bunny.setCoords(x, y)
                    else:
                        print("Multiple starting points found", file=sys.stderr)
                        sys.exit(1)

                elif char == ' ':
                    newRow.append(0)

                elif char == 'O':
                    newRow.append(2)

                else:
                    print("Invalid char in input", file=sys.stderr)
                    sys.exit(1)

            self.map.append(newRow)

        if -1 in self.bunny.coords:
            print("No starting point defined", file=sys.stderr)
            sys.exit(1)

    def finished(self):
        return self.unvisited == 0

    def validCoords(self, x, y):
        return -1 < x < len(self.map) and -1 < y < len(self.map[0])

    def runCom(self, com):
        if self.finished():
            return

        if self.hopsLeft < self.unvisited:
            return

        if com == 'F':
            x, y = self.bunny.nextTile()
            if self.validCoords(x, y) and self.map[x][y] != 0:
                self.bunny.hop()
                self.hopsLeft -= 1

                if (self.map[x][y] == 1):
                    self.unvisited -= 1
                self.map[x][y] = 2

        else:
            self.bunny.rotate(com)

class loop():
    def __init__(self, loops, commands):
        self.loops = loops
        self.commands = [*commands]

    def __str__(self):
        return "loop({}, {})".format(self.loops, list(self.commands))

    def __repr__(self):
        return str(self)


def rejectRedundantCode(code):
    if isSnippetRedundant(code):
        return False

    if type(code[-1]) is str:
        if code[-1] in "LR":
            return False
    else:
        if len(code[-1].commands) == 1:
            print(code)
            if code[-1].commands[-1] in "LR":
                return False

    return True


def isSnippetRedundant(code):
    joined = "".join(str(com) for com in code)

    if any(redCode in joined for redCode in ["FFF", "RL", "LR", "RRR", "LLL"]):
        return True

    for com in code:
        if type(com) is not str:
            if len(com.commands) == 1:
                if com.loops == 2:
                    return True

                if type(com.commands[0]) is not str:
                    return True

                if com.commands[0] in "LR":
                    return True

            if len(com.commands) > 1 and len(set(com.commands)) == 1:
                return True

            if isSnippetRedundant(com.commands):
                return True

    for i in range(len(code)):
        if type(code[i]) is not str and len(code[i].commands) == 1:
            if i > 0 and code[i].commands[0] == code[i-1]:
                return True
            if i < len(code) - 1 and code[i].commands[0] == code[i+1]:
                return True

        if type(code[i]) is not str:
            if i > 0 and type(code[i-1]) is not str and code[i].commands == code[i-1].commands:
                return True
            if i < len(code) - 1 and type(code[i+1]) is not str and code[i].commands == code[i+1].commands:
                return True

            if len(code[i].commands) > 3 and all(type(com) is str for com in code[i].commands):
                return True

    return False

def flatten(code):
    flat = ""
    for com in code:
        if type(com) is str:
            flat += com
        else:
            flat += flatten(com.commands) * com.loops

    return flat

def newGen(n, topLevel = True):
    maxLoops = 9
    minLoops = 2
    if n < 1:
        yield []

    if n == 1:
        yield from [["F"], ["L"], ["R"]]

    elif n == 2:
        yield from [["F", "F"], ["F", "L"], ["F", "R"], ["L", "F"], ["R", "F"]]

    elif n == 3:
        for innerCode in newGen(n - 1, False):
            for loops in range(minLoops, maxLoops):
                if len(innerCode) != 1 and 0 < innerCode.count('F') < 2:
                    yield [loop(loops, innerCode)]

        for com in "FLR":
            for suffix in newGen(n - 2, False):
                for loops in range(minLoops, maxLoops):
                    if com not in suffix:
                        yield [loop(loops, [com])] + suffix

    else:
        for innerCode in newGen(n - 1, False):
            if topLevel:
                yield [loop(17, innerCode)]
            else:
                for loops in range(minLoops, maxLoops):
                    if len(innerCode) > 1:
                        yield [loop(loops, innerCode)]

        for com in "FLR":
            for innerCode in newGen(n - 2, False):
                for loops in range(minLoops, maxLoops):
                    yield [loop(loops, innerCode)] + [com]
                    yield [com] + [loop(loops, innerCode)]

def codeLen(code):
    l = 0
    for com in code:
        l += 1
        if type(com) is not str:
            l += codeLen(com.commands)

    return l


def test(code, board):
    state = BoardState(board)
    state.hopsLeft = flatten(code).count('F')

    for com in code:
        state.runCom(com)


    return state.finished()

def testAll():
    score = 0
    for i, board in enumerate(boards):
        print("\n\nTesting board {}:".format(i + 1))
        #print('\n'.join(board),'\n')
        start = time.time()

        found = False
        tested = set()

        for maxLen in range(1, 12):
            lenCount = 0
            for code in filter(rejectRedundantCode, newGen(maxLen)):
                testCode = flatten(code)
                if testCode in tested:
                    continue

                tested.add(testCode)

                lenCount += 1
                if test(testCode, board):
                    found = True

                    stop = time.time()
                    print("{} token solution found in {} seconds".format(maxLen, stop - start))
                    print(code)
                    score += maxLen
                    break

            if found:
                break

    print("Final Score: {}".format(score))

def testOne(board):
    start = time.time()
    found = False
    tested = set()
    dupes = 0

    for maxLen in range(1, 12):
        lenCount = 0
        for code in filter(rejectRedundantCode, newGen(maxLen)):
            testCode = flatten(code)
            if testCode in tested:
                dupes += 1
                continue

            tested.add(testCode)

            lenCount += 1
            if test(testCode, board):
                found = True
                print(code)
                print("{} dupes found".format(dupes))
                break

        if found:
            break

        print("Length:\t{}\t\tCombinations:\t{}".format(maxLen, lenCount))

    stop = time.time()
    print(stop - start)

#testAll()
testOne(input().split('\n'))

Bu program tek bir giriş kartını test edecek, ancak bu test sürücüsünü daha kullanışlı buluyorum . Her bir tahtayı aynı anda test edecek ve bu çözümü bulmanın ne kadar sürdüğünü yazdıracaktır. Bu kodu makinemde çalıştırdığımda (Intel i7-7700K dört çekirdekli CPU @ 4.20 GHz, 16.0 GB RAM), aşağıdaki çıktıyı alıyorum:

Testing board 1:
2 token solution found in 0.0 seconds
['F', 'F']


Testing board 2:
4 token solution found in 0.0025103092193603516 seconds
[loop(17, [loop(3, ['F']), 'R'])]


Testing board 3:
4 token solution found in 0.0010025501251220703 seconds
[loop(17, [loop(3, ['F']), 'L'])]


Testing board 4:
5 token solution found in 0.012532949447631836 seconds
[loop(17, ['F', loop(7, ['F', 'L'])])]


Testing board 5:
5 token solution found in 0.011022329330444336 seconds
[loop(17, ['F', loop(5, ['F', 'L'])])]


Testing board 6:
4 token solution found in 0.0015044212341308594 seconds
[loop(17, [loop(3, ['F']), 'L'])]


Testing board 7:
8 token solution found in 29.32585096359253 seconds
[loop(17, [loop(4, [loop(5, [loop(6, ['F']), 'L']), 'L']), 'F'])]


Testing board 8:
8 token solution found in 17.202533721923828 seconds
[loop(17, ['F', loop(7, [loop(5, [loop(4, ['F']), 'L']), 'F'])])]


Testing board 9:
6 token solution found in 0.10585856437683105 seconds
[loop(17, [loop(7, [loop(4, ['F']), 'L']), 'F'])]


Testing board 10:
6 token solution found in 0.12129759788513184 seconds
[loop(17, [loop(7, [loop(5, ['F']), 'L']), 'F'])]


Testing board 11:
7 token solution found in 4.331984758377075 seconds
[loop(17, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]


Testing board 12:
8 token solution found in 58.620323181152344 seconds
[loop(17, [loop(3, ['F', loop(4, [loop(3, ['F']), 'R'])]), 'L'])]

Final Score: 67

Bu son test, dakika sınırlamasının altında zorlukla gıcırdıyor.

Arka fon

Bu, şimdiye kadar cevapladığım en eğlenceli zorluklardan biriydi! Bir av patlaması yaşadım ve işleri kısmak için buluşsal yöntemler arıyordum.

Genel olarak, burada PPCG'de nispeten kolay soruları cevaplama eğilimindeyim. Özellikle etiketine düşkünüm çünkü genellikle dillerim için oldukça uygun. Bir gün, yaklaşık iki hafta önce, rozetlerime bakıyordum ve bir için canlanma hiç almadığımı fark ettim rozetine . Bu yüzden cevapsızlara baktımBir şey gözümü yakalayıp yakalamadığını görmek için bu soruyu buldum. Maliyeti ne olursa olsun cevaplamaya karar verdim. Düşündüğümden biraz daha zor oldu, ama sonunda gurur duyduğumu söyleyebileceğim kaba bir cevap aldım. Ancak bu zorluk benim için tamamen norm dışıdır, çünkü genellikle tek bir cevaba bir saatten fazla zaman harcamıyorum. Bu cevap beni 2 haftadan biraz fazla sürdü ve nihayet bu aşamaya gelmek için en az 10+ iş aldı, ancak dikkatli bir şekilde takip etmiyordum.

İlk yineleme saf bir kaba kuvvet çözeltisiydi. Uzunluğa kadar tüm parçacıkları oluşturmak için aşağıdaki kodu kullandımN :

def generateCodeLenN(n, maxLoopComs, maxLoops, allowRedundant = False):
    if n < 1:
        return []

    if n == 1:
        return [["F"], ["L"], ["R"]]

    results = []

    if 1:
        for com in "FLR":
            for suffix in generateCodeLenN(n - 1, maxLoopComs, maxLoops, allowRedundant):
                if allowRedundant or not isSnippetRedundant([com] + suffix):
                    results.append([com] + suffix)

    for loopCount in range(2, maxLoopComs):
        for loopComs in range(1, n):
            for innerCode in generateCodeLenN(loopComs, maxLoopComs, maxLoops - 1, allowRedundant):
                if not allowRedundant and isSnippetRedundant([loop(loopCount, innerCode)]):
                    continue

                for suffix in generateCodeLenN(n - loopComs - 1, maxLoopComs, maxLoops - 1, allowRedundant):
                    if not allowRedundant and isSnippetRedundant([loop(loopCount, innerCode)] + suffix):
                        continue

                    results.append([loop(loopCount, innerCode)] + suffix)

                if loopComs == n - 1:
                    results.append([loop(loopCount, innerCode)])

    return results

Bu noktada, olası her cevabı test etmenin çok yavaş olacağından emindim, bu yüzden çok yavaş kullandım . Testcase 12, bu yaklaşımı kullanarak 3.000 saniyenin biraz üzerinde sürdü. Bu açıkça çok yavaş. Ancak bu bilgileri ve her bilgisayar için kısa çözümleri zorlamak için bir grup bilgisayar döngüsü kullanarak yeni bir model bulabilirim. Bulunan hemen hemen her çözümün genellikle aşağıdaki gibi görüneceğini fark ettim:isSnippetRedundant daha kısa bir pasajla yazılabilecek parçacıkları filtreliyordum. Örneğin, pasajı vermeyi reddederim ["F", "F", "F"]çünkü tam olarak aynı etkilerle elde edilebilirdi [Loop(3, ["F"]), bu yüzden uzunluk-3 parçacığını test ettiğimiz noktaya ulaşırsak, hiçbir uzunluk-3 parçacığının mevcut tahtayı çözemeyeceğini biliyoruz. Bu çok iyi anımsatıcılar kullandı, ama sonuçta waaaay oldu

[<com> loop(n, []) <com>]

her iki taraftaki tek koçanlar isteğe bağlı olacak şekilde birkaç katman derinliğinde yuvalanmıştır. Bu, aşağıdaki gibi çözümler anlamına gelir:

["F", "F", "R", "F", "F", "L", "R", "F", "L"]

asla görünmezdi. Aslında, 3'ten fazla döngü olmayan tokenden oluşan bir dizi asla yoktu. Bunu kullanmanın bir yolu, tüm bunları filtrelemek ve onları test etmekten rahatsız etmemek olacaktır. Ancak onları üretmek hala ihmal edilemeyecek bir zaman alıyordu ve bu gibi milyonlarca parçacıktan süzülmek zamanını neredeyse kesecekti. Bunun yerine, yalnızca bu kalıbı takip eden parçacıklar oluşturmak için kod oluşturucuyu büyük ölçüde yeniden yazdım. Sahte kodda, yeni jeneratör şu genel kalıbı takip eder:

def codeGen(n):
    if n == 1:
        yield each [<com>]

    if n == 2:
        yield each [<com>, <com>]

    if n == 3:
        yield each [loop(n, <com length 2>]
        yield each [loop(n, <com>), <com>]

    else:
        yield each [loop(n, <com length n-1>)]
        yield each [loop(n, <com length n-2>), <com>]
        yield each [<com>, loop(n, <com length n-2>)]

        # Removed later
        # yield each [<com>, loop(n, <com length n-3>), <com>]
        # yield each [<com>, <com>, loop(n, <com length n-3>)]
        # yield each [loop(n, <com length n-3>), <com>, <com>]

Bu, en uzun test senaryosunu 140 saniyeye indirdi, bu da saçma bir gelişme. Ama buradan, hala iyileştirmem gereken bazı şeyler vardı. Daha agresif bir şekilde gereksiz / değersiz kodu filtrelemeye ve kodun daha önce test edilip edilmediğini kontrol etmeye başladım. Bu onu daha da azalttı, ama yeterli değildi. Sonunda, eksik olan son parça döngü sayacıydı. Oldukça gelişmiş algoritmam sayesinde (okuma: rastgele deneme ve hata ) Döngülerin çalışmasına izin verecek en uygun aralığın [3-8] olduğunu belirledim. Ancak orada büyük bir gelişme var: Tahtamızı [loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]çözemediğini biliyorsanız, kesinlikle[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]veya 3-7 arasındaki herhangi bir döngü sayısı bunu çözebilir. Bu nedenle, 3-8 arasındaki tüm döngü boyutlarını yinelemek yerine, dış döngüdeki döngü sayısını maks. Bu, arama alanını maxLoop - minLoopbu durumda 6 veya 6 kat azaltmaktadır .

Bu çok yardımcı oldu, ancak skoru şişirdi. Daha önce kaba kuvvetle bulduğum bazı çözümler için daha büyük döngü sayıları gerekir (örneğin, pano 4 ve 6). Dış döngü sayısını 8 olarak ayarlamak yerine, dış döngü sayısını 17 olarak ayarladık, son derece gelişmiş algoritmam tarafından da hesaplanan büyülü bir sayı. Bunu yapabileceğimizi biliyoruz çünkü en dıştaki döngünün döngü sayısının artırılmasının çözümün geçerliliği üzerinde bir etkisi yoktur. Bu adım aslında son puanımızı 13 azalttı. Bu yüzden önemsiz bir adım değil.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.