Bunny Hopping Bunny


16

Vào ngày 4 tháng 12 năm 2017, Google Doodle là một trò chơi lập trình đồ họa có hình chú thỏ . Các cấp độ sau này là không tầm thường và chúng có vẻ như là một ứng cử viên tuyệt vời cho một thách thức .

Chi tiết

Trò chơi

  • Có bốn động tác có sẵn: nhảy về phía trước, rẽ trái, rẽ phải và vòng lặp. Mỗi bước di chuyển này là một mã thông báo , tương ứng với thực tế là chúng là mỗi một ô trong trò chơi.
  • Con thỏ có thể phải đối mặt với bốn hướng trực giao (tức là bắc, nam, đông, tây).
  • Con thỏ có thể nhảy về phía trước (di chuyển một hình vuông theo hướng nó phải đối mặt) và rẽ trái hoặc phải.
  • Các vòng lặp có thể có bất kỳ số lần di chuyển nào khác bên trong chúng, bao gồm các vòng lặp khác và số lần lặp của chúng là bất kỳ số nguyên dương nào (mặc dù về mặt kỹ thuật trò chơi cho phép số lần lặp là 0).
  • Bảng là một tập hợp các ô vuông được sắp xếp theo lưới và chú thỏ có thể nhảy giữa các ô vuông liền kề.
  • Con thỏ không thể nhảy vào khoảng trống. Có nghĩa là một nỗ lực để nhảy ra khỏi bảng không làm gì cả. (Đây rõ ràng là một bất ngờ đối với một số người và gây thất vọng cho những người khác.)
  • Hình vuông được đánh dấu hoặc không đánh dấu. Khi chú thỏ nằm trên một hình vuông, nó trở nên được đánh dấu.
  • Mức độ hoàn thành khi tất cả các hình vuông được đánh dấu.
  • Bạn có thể giả sử một giải pháp tồn tại.

Ma cua ban

  • Mục tiêu: đưa ra một bảng, tìm một hoặc nhiều giải pháp ngắn nhất.
  • Đầu vào là danh sách các vị trí hình vuông tạo thành bảng (phân biệt các ô vuông được đánh dấu và không đánh dấu) và đầu ra là một danh sách các di chuyển. Định dạng đầu vào và đầu ra hoàn toàn không quan trọng, miễn là chúng có thể đọc được và dễ hiểu.
  • Tiêu chí chiến thắng: tổng số lần di chuyển của các giải pháp ngắn nhất được tìm thấy trong vòng một phút cho mỗi bảng. Nếu chương trình của bạn không tìm thấy giải pháp cho bất kỳ bảng cụ thể nào, điểm của bạn cho bảng đó là (5 * số ô vuông).
  • Xin vui lòng không giải pháp hardcode theo bất kỳ cách nào. Mã của bạn sẽ có thể lấy bất kỳ bảng nào làm đầu vào, không chỉ các bảng được đưa ra như các ví dụ dưới đây.

Ví dụ

Các giải pháp được ẩn trong các chiến lợi phẩm để cho bạn cơ hội chơi trò chơi trước và tự mình thử một số trong số chúng. Ngoài ra, chỉ có một giải pháp được cung cấp dưới đây cho mỗi.

Slà hình vuông bắt đầu của chú thỏ (hướng về phía đông), #là một hình vuông không Ođược đánh dấu và là một hình vuông được đánh dấu. Đối với di chuyển, ký hiệu của tôi là F= hop về phía trước, L= rẽ trái, R= rẽ phải và LOOP(<num>){<moves>}biểu thị một vòng lặp lặp lại <num>lần và thực hiện <moves>mỗi lần. Nếu vòng lặp có thể chạy bất kỳ số lần nào vượt quá số lượng tối thiểu, <num>có thể được bỏ qua (tức là vô cực hoạt động).

Cấp độ 1:

S##

FF

Cấp độ 2:

S##
  #
  #

LOOP (2) {FFR}

Cấp 3:

S##
# #
###

LOOP {FFR}

Cấp 4:

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

LOOP {F LOOP (7) {FL}} (được tìm thấy bởi DJMcMayhem)

Cấp 5:

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

LOOP (18) {LOOP (10) {FR} L}
Nguồn: Reddit

Cấp 6:

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

LOOP {LOOP (3) {F} L}

Bảng lớn: (giải pháp ngắn nhất hiện chưa rõ)

12x12:

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

Cấp 5 nhưng lớn hơn:

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

Thêm bảng holey:

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

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

Cuối cùng, sự bất cân xứng có thể là một nỗi đau thực sự ở mông:

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

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


"Tìm một hoặc nhiều giải pháp ngắn nhất" Tôi nghĩ rằng vấn đề tạm dừng ngăn cấm điều này
Leaky Nun

@Leaky Nun Điều này không liên quan đến vấn đề tạm dừng. Đây là một tìm kiếm đồ thị
WhatToDo

Nhưng vòng lặp được cho phép ...
Leaky Nun

4
Tôi nghĩ rằng nó không áp dụng vì bảng là hữu hạn. Đối với mỗi vòng lặp, nó sẽ chạy mãi mãi hoặc dừng lại. Một vòng lặp không có vòng lặp bên trong nó sẽ chỉ lặp lại mãi mãi nếu đối số cho số lần lặp bị loại bỏ. Trong trường hợp đó, số lượng trạng thái bảng hữu hạn đảm bảo vòng lặp sẽ bắt đầu lặp lại trạng thái, có thể được kiểm tra.
WhatToDo

Câu trả lời:


12

Python 3, 67 mã thông báo

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

Chương trình này sẽ kiểm tra một bảng đầu vào duy nhất, nhưng tôi thấy trình điều khiển thử nghiệm này hữu ích hơn . Nó sẽ kiểm tra từng bảng một cùng một lúc và in ra mất bao lâu để tìm ra giải pháp đó. Khi tôi chạy mã đó trên máy của mình (CPU lõi tứ Intel i7-7700K @ 4,20 GHz, RAM 16,0 GB), tôi nhận được đầu ra sau:

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

Thử nghiệm cuối cùng này chỉ vừa mới rít lên trong giới hạn phút.

Lý lịch

Đây là một trong những thử thách thú vị nhất mà tôi từng trả lời! Tôi đã có một mô hình vụ nổ săn bắn và tìm kiếm heuristic để cắt giảm mọi thứ.

Nói chung, ở đây trên PPCG tôi có xu hướng trả lời các câu hỏi tương đối dễ. Tôi đặc biệt thích thẻ vì nó thường khá phù hợp với ngôn ngữ của tôi. Một ngày, khoảng hai tuần trước, tôi đã xem qua các huy hiệu của mình và tôi nhận ra rằng tôi chưa bao giờ nhận được huy hiệu hồi sinh . Vì vậy, tôi nhìn qua chưa được trả lờitab để xem có gì lọt vào mắt tôi không, và tôi đã tìm thấy câu hỏi này. Tôi quyết định rằng tôi sẽ trả lời nó bất kể chi phí. Cuối cùng, nó khó hơn một chút so với tôi nghĩ, nhưng cuối cùng tôi cũng nhận được câu trả lời tàn bạo mà tôi có thể nói là tôi tự hào. Nhưng thử thách này hoàn toàn không phù hợp với tôi vì tôi thường không dành hơn một giờ cho một câu trả lời. Câu trả lời này đã khiến tôi mất hơn 2 tuần và ít nhất 10+ công việc để cuối cùng đến giai đoạn này, mặc dù tôi đã không theo dõi cẩn thận.

Lặp lại đầu tiên là một giải pháp vũ lực thuần túy. Tôi đã sử dụng đoạn mã sau để tạo tất cả các đoạn có độ dài 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

Tại thời điểm này, tôi chắc chắn rằng việc kiểm tra mọi câu trả lời có thể sẽ quá chậm, vì vậy tôi đã sử dụng isSnippetRedundantđể lọc các đoạn có thể được viết bằng một đoạn ngắn hơn. Ví dụ: tôi sẽ từ chối cung cấp đoạn trích ["F", "F", "F"]vì có thể đạt được các hiệu ứng tương tự chính xác [Loop(3, ["F"]), vì vậy nếu chúng tôi đạt đến điểm mà chúng tôi kiểm tra đoạn mã dài 3, chúng tôi biết rằng không có đoạn mã dài 3 nào có thể giải được bảng hiện tại. Điều này đã sử dụng rất nhiều khả năng ghi nhớ tốt, nhưng cuối cùng là waaaayquá chậm. Testcase 12 chỉ mất hơn 3.000 giây bằng cách sử dụng phương pháp này. Điều này rõ ràng là quá chậm. Nhưng bằng cách sử dụng thông tin này và một loạt các chu kỳ máy tính để đưa ra các giải pháp ngắn cho mọi bảng, tôi có thể tìm thấy một mẫu mới. Tôi nhận thấy rằng hầu hết mọi giải pháp được tìm thấy thường trông giống như sau:

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

lồng nhiều lớp sâu, với các coms ở mỗi bên là tùy chọn. Điều này có nghĩa là các giải pháp như:

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

sẽ không bao giờ xuất hiện. Trên thực tế, không bao giờ có một chuỗi gồm hơn 3 mã thông báo không lặp. Một cách để sử dụng điều này sẽ là lọc ra tất cả những thứ này và không bận tâm để kiểm tra chúng. Nhưng việc tạo ra chúng vẫn mất một lượng thời gian không đáng kể và việc lọc qua hàng triệu đoạn như thế này sẽ giúp giảm thời gian. Thay vào đó, tôi viết lại mạnh mẽ trình tạo mã để chỉ tạo các đoạn mã theo mẫu này. Trong mã giả, trình tạo mới tuân theo mẫu chung này:

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

Điều này cắt trường hợp thử nghiệm dài nhất xuống còn 140 giây, đó là một cải tiến vô lý. Nhưng từ đây, vẫn còn một số điều tôi cần cải thiện. Tôi bắt đầu mạnh mẽ hơn để lọc mã dự phòng / vô giá trị và kiểm tra xem mã đã được kiểm tra trước đó chưa. Điều này cắt giảm hơn nữa, nhưng nó không đủ. Cuối cùng, mảnh cuối cùng bị thiếu là bộ đếm vòng lặp. Thông qua thuật toán rất tiên tiến của tôi (đọc: thử nghiệm ngẫu nhiên và lỗi ) Tôi xác định rằng phạm vi tối ưu để cho phép các vòng lặp chạy trong là [3-8]. Nhưng có một cải tiến rất lớn ở đó: Nếu chúng ta biết rằng [loop(8, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]không thể giải quyết hội đồng quản trị của mình, thì hoàn toàn không có cách nào[loop(3, [loop(8, ['F', loop(5, ['F', 'L'])]), 'L'])]hoặc bất kỳ số vòng lặp từ 3 - 7 có thể giải quyết nó. Vì vậy, thay vì lặp qua tất cả các kích thước vòng lặp từ 3-8, chúng tôi đặt số vòng lặp trên vòng lặp bên ngoài thành tối đa. Điều này kết thúc việc cắt không gian tìm kiếm xuống theo hệ số maxLoop - minLoop, hoặc 6 trong trường hợp này.

Điều này đã giúp rất nhiều, nhưng cuối cùng đã làm tăng điểm số. Một số giải pháp tôi đã tìm thấy trước đó bằng vũ lực đòi hỏi số vòng lặp lớn hơn để chạy (ví dụ, bảng 4 và 6). Vì vậy, thay vì đặt số vòng lặp bên ngoài thành 8, chúng tôi đặt số vòng lặp bên ngoài thành 17, một con số kỳ diệu cũng được tính theo thuật toán rất tiên tiến của tôi. Chúng tôi biết rằng chúng tôi có thể làm điều này bởi vì việc tăng số vòng lặp của vòng lặp ngoài cùng không ảnh hưởng đến hiệu lực của giải pháp. Bước này thực sự làm giảm điểm số cuối cùng của chúng tôi xuống 13. Vì vậy, không phải là một bước nhỏ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.