구글의 도약 토끼


16

2017 년 12 월 4 일 Google 기념일 로고는 토끼 가 포함 된 그래픽 프로그래밍 게임 이었습니다 . 이후의 레벨은 아주 사소한 것이었고 도전에 대한 훌륭한 후보처럼 보였습니다 .

세부

경기

  • 앞으로 홉, 좌회전, 우회전, 루프의 4 가지 이동이 있습니다. 이러한 움직임은 각각 하나의 토큰으로 게임에서 하나의 타일이라는 사실에 해당합니다.
  • 토끼는 네 개의 직교 방향 (즉, 북쪽, 남쪽, 동쪽, 서쪽)을 향할 수 있습니다.
  • 토끼는 앞으로 뛸 수 있고 (향하는 방향으로 한 칸 이동) 왼쪽이나 오른쪽으로 돌릴 수 있습니다.
  • 루프는 다른 루프를 포함하여 내부에 여러 가지 다른 움직임을 가질 수 있으며 반복 횟수는 양의 정수입니다 (기술적으로 반복 횟수는 0입니다).
  • 보드는 그리드로 정렬 된 사각형 세트이며 토끼는 인접한 사각형 사이를 이동할 수 있습니다.
  • 토끼는 공허에 뛰어들 수 없습니다. 보드에서 뛰어 내리려는 시도는 아무 것도하지 않는다는 것을 의미합니다. (일부 사람들에게는 놀랍고 다른 사람들에게는 실망한 것 같습니다.)
  • 사각형은 표시되거나 표시되지 않습니다. 토끼가 사각형 위에 있으면 표시됩니다.
  • 모든 사각형이 표시되면 레벨이 완료됩니다.
  • 해결책이 있다고 가정 할 수 있습니다.

귀하의 코드

  • 목표 : 보드가 주어지면 가장 짧은 솔루션을 하나 이상 찾으십시오.
  • 입력은 보드를 형성하는 사각형 위치의 목록 (표시된 사각형과 표시되지 않은 사각형을 구별)이며 출력은 이동 목록입니다. 입력 및 출력 형식 은 사람이 읽고 이해할 수있는 한 전혀 중요하지 않습니다.
  • 선정 기준 : 각 보드에 대해 1 분 이내에 발견 된 최단 솔루션의 이동 수입니다. 프로그램이 특정 보드에 대한 솔루션을 찾지 못하면 해당 보드의 점수는 (5 * 제곱 수)입니다.
  • 어떤 방식 으로든 솔루션을 하드 코딩하지 마십시오. 코드는 아래 예제와 같이 보드를 입력으로 사용할 수 있어야합니다.

스포일러에 솔루션이 숨겨져 있으므로 먼저 게임을하고 직접 시도해 볼 수 있습니다. 또한 각 솔루션마다 하나의 솔루션 만 제공됩니다.

S토끼의 시작 광장 (동쪽을 향함) #이며, 표시되지 않은 사각형이며, O표시된 사각형입니다. 이동의 경우, 내 표기법은 F= 홉 앞으로, L= 좌회전, R= 우회전이며 시간 LOOP(<num>){<moves>}을 반복 <num>하고 <moves>매번 수행 하는 루프를 나타냅니다 . 루프가 최소 수 이상으로 여러 번 실행될 <num>수있는 경우 생략 할 수 있습니다 (예 : 무한대 작동).

레벨 1:

S##

FF

2 단계:

S##
  #
  #

루프 (2) {FFR}

3 단계 :

S##
# #
###

루프 {FFR}

레벨 4 :

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

루프 {F 루프 (7) {FL}} (DJMcMayhem에 의해 발견됨)

5 단계 :

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

LOOP (18) {LOOP (10) {FR} L}
출처 : 레딧

6 단계 :

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

루프 {루프 (3) {F} L}

거대한 보드 : (현재 알려진 가장 짧은 솔루션)

12x12 :

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

5 단계지만 더 큰

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

더 홀리 보드 :

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

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

마지막으로 비대칭은 엉덩이에 심각한 고통이 될 수 있습니다.

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

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


"하나 이상의 최단 솔루션을 찾으십시오"중지 문제로 인해이 문제가 금지된다고 생각했습니다
Leaky Nun

@Leaky Nun 정지 문제와 관련이 없습니다. 이 그래프 검색입니다
WhatToDo

그러나 루핑은 허용됩니다 ...
새기다

4
보드가 유한하기 때문에 적용되지 않는 것 같습니다. 각 루프마다 영구적으로 실행되거나 중지됩니다. 내부에 루프가없는 루프는 반복 횟수에 대한 인수가 삭제 된 경우에만 영원히 루프됩니다. 이 경우, 한정된 수의 보드 상태는 루프가 반복되는 상태를 시작하도록 보장하므로 확인할 수 있습니다.
WhatToDo

답변:


12

파이썬 3, 67 토큰

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

이 프로그램은 단일 입력 보드를 테스트하지만 테스트 이 테스트 드라이버가 더 유용하다는. 모든 단일 보드를 동시에 테스트하고 해당 솔루션을 찾는 데 걸린 시간을 인쇄합니다. 내 컴퓨터에서 해당 코드를 실행하면 (Intel i7-7700K 쿼드 코어 CPU @ 4.20GHz, 16.0GB RAM) 다음과 같은 결과가 나타납니다.

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

이 마지막 테스트는 미세한 제한을받지 않습니다.

배경

이것은 내가 대답 한 가장 재미있는 도전 중 하나였습니다! 나는 폭발 패턴 사냥을하고 물건을 줄일 휴리스틱을 찾고있었습니다.

일반적으로 여기 PPCG에서는 비교적 쉬운 질문에 대답하는 경향이 있습니다. 태그는 일반적으로 내 언어에 매우 적합하기 때문에 특히 좋아 합니다. 어느 날, 약 2 주 전에 배지를 살펴보면서 부흥 배지를 받지 못했다는 것을 깨달았습니다 . 그래서 나는 답을 찾지 못했습니다어떤 것이 내 눈에 띄는 지 확인하기 위해이 질문을 찾았습니다. 나는 비용에 관계없이 대답하겠다고 결정했다. 내가 생각했던 것보다 조금 더 어려워졌지만 마침내 내가 자랑스러워 할 수있는 무차별 대항 답변을 얻었습니다. 그러나 나는 보통 한 번의 답변으로 한 시간 이상을 소비하지 않기 때문에이 도전은 완전히 표준에서 벗어났습니다. 이 대답은 조심스럽게 진행하지는 않았지만 2 주가 조금 넘었고 마침내이 단계에 도달하는 데 10+ 이상의 작업이 필요했습니다.

첫 번째 반복은 순수한 무차별 대입 솔루션이었습니다. 다음 코드를 사용하여 길이 N 까지의 모든 스 니펫을 생성했습니다 .

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

이 시점에서 가능한 모든 단일 답변을 테스트하는 것이 너무 느릴 것이라고 확신했기 때문에 isSnippetRedundant더 짧은 스 니펫으로 작성할 수있는 스 니펫을 필터링했습니다. 예를 들어, ["F", "F", "F"]동일한 효과를 너무 느리게 달성 할 수 있기 때문에 스 니펫 생성을 거부합니다 . 테스트 사례 12는이 방법을 사용하여 3,000 초 이상이 걸렸습니다. 이것은 분명히 너무 느립니다. 그러나이 정보와 많은 컴퓨터주기를 사용하여 모든 보드에 짧은 솔루션을 강제로 적용하면 새로운 패턴을 찾을 수 있습니다. 발견 된 거의 모든 솔루션은 일반적으로 다음과 같습니다.[Loop(3, ["F"]) 길이 3 스 니펫을 테스트하는 지점에 도달하면 길이 3 스 니펫이 현재 보드를 해결할 수 없다는 것을 알고 있습니다. 이것은 좋은 니모닉을 많이 사용했지만 궁극적으로 waaaay였습니다.

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

여러 층으로 중첩되어 있으며 각 측면의 단일 통신은 선택 사항입니다. 이는 다음과 같은 솔루션을 의미합니다.

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

절대 나타나지 않을 것입니다. 실제로, 3 개 이상의 비 루프 토큰 시퀀스는 없었습니다. 이를 활용하는 한 가지 방법은 이들을 모두 걸러 내고 테스트하는 데 귀찮게하지 않는 것입니다. 그러나 그것들을 생성하는 것은 여전히 ​​무시할 수없는 시간을 소비했으며, 이와 같은 수백만 개의 스 니펫을 필터링하면 시간이 거의 단축되지 않습니다. 대신이 패턴에 따라 스 니펫 만 생성하도록 코드 생성기를 대폭 재 작성했습니다. 의사 코드에서 새 생성기는 다음과 같은 일반적인 패턴을 따릅니다.

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

가장 긴 테스트 케이스를 140 초로 줄였습니다. 그러나 여기서부터 개선해야 할 부분이 여전히있었습니다. 중복 / 무가치 코드를보다 적극적으로 필터링하고 코드가 이전에 테스트되었는지 확인하기 시작했습니다. 이것은 그것을 더 줄 였지만 충분하지 않았습니다. 결국, 누락 된 마지막 부분은 루프 카운터였습니다. 내 고급 알고리즘 (읽기 : 임의 시행 착오 )을 통해 루프를 실행할 수있는 최적의 범위가 [3-8]이라고 결정했습니다. 그러나 거기에 큰 개선이 있습니다. 우리가 그것을 알고 있다면 3-7에서 루프 카운트로 해결할 수 있습니다. 따라서 3 ~ 8의 모든 루프 크기를 반복하는 대신 외부 루프의 루프 수를 최대로 설정합니다. 결과적으로 검색 공간을[loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])] 우리가 보드를 해결할 수 없다는 , 그 방법은 절대 없습니다.[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]maxLoop - minLoop또는이 경우 6입니다.

이것은 많은 도움이되었지만 점수가 부풀려졌습니다. 무차별 대입으로 내가 찾은 특정 솔루션 (예 : 보드 4 및 6)을 실행하려면 더 많은 루프 번호가 필요합니다. 따라서 외부 루프 카운트를 8로 설정하는 대신 외부 루프 카운트를 17로 설정했습니다.이 수치는 고급 알고리즘에 의해 계산 된 마법의 숫자이기도합니다. 가장 바깥 쪽 루프의 루프 수를 늘려도 솔루션의 유효성에 영향을 미치지 않기 때문에이 작업을 수행 할 수 있습니다. 이 단계는 실제로 우리의 최종 점수를 13만큼 줄였습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.