Tìm bước khởi đầu tối ưu của Chomp


14

Chomp là một trò chơi hai người chơi với thiết lập hình chữ nhật của các mảnh. Mỗi người chơi lần lượt loại bỏ bất kỳ quân cờ nào, cùng với tất cả các quân cờ bên trên và bên phải. Bất cứ ai mất phần dưới cùng bên trái đều mất. Có thể chứng minh khá dễ dàng rằng người chơi đầu tiên luôn có một nước cờ chiến thắng (ngoại trừ hình chữ nhật 1 chọi 1); tìm nó.

  1. Đầu vào là kích thước của hình chữ nhật (hai số)
  2. Đầu ra là vị trí của nước cờ chiến thắng (hai số)
  3. Nếu có nhiều hơn một nước cờ chiến thắng, thì bạn có thể xuất ra bất kỳ bước nào trong số đó.

Đây là mã golf; mã ngắn nhất (bất kỳ ngôn ngữ) chiến thắng.

Ví dụ

Lưu ý: Các đầu ra chỉ là hai số; nghệ thuật ASCII dưới đây chỉ là để chứng minh ý nghĩa của các con số.

Đầu vào: 5 3 (chỉ số dựa trên 1 bắt đầu từ góc dưới bên trái)

Đầu ra: 4 3

XXX--
XXXXX
XXXXX

Đầu vào: 4 4

Đầu ra: 2 2

X---
X---
X---
XXXX

Tặng kem

Trừ 15 ký tự khỏi điểm số của bạn nếu bạn xuất ra tất cả các nước đi thắng. Mỗi cặp số phải được phân tách bằng một dòng mới.


Trong ví dụ đầu tiên của bạn, tôi nghĩ rằng bạn có quá nhiều dấu gạch ngang
kitcar2000 18/03/14

@Kitcar Bạn nói đúng; đã sửa.
Ypnypn

Tôi không thể hiểu định dạng đầu ra. Làm thế nào để những con số tương ứng với các vị trí?
undergroundmonorail

@undergroundmonorail Chỉ số dựa trên 1 từ dưới cùng bên trái. chỉ số đầu tiên là trục ngang và chỉ số thứ hai là chỉ số dọc.
Martin Ender

2
Đáp lại tiền thưởng của bạn: Trong Cờ vua, bạn có ít hơn 119 lần di chuyển có thể tại bất kỳ thời điểm nào (thường là ít hơn nhiều) và không có siêu máy tính nào cho đến ngày nay đã tiến gần đến việc giải Cờ vua bằng cách sử dụng ngay cả các thuật toán nổi tiếng nhất. Trên lưới 10 đến 10 Chomp, có 100 bước di chuyển đầu tiên có thể và mỗi bước có 1-99 bước di chuyển thứ hai tiềm năng. Điều gì làm cho bạn nghĩ rằng sẽ dễ dàng vũ phu? Tôi khuyên bạn nên giới hạn kích thước lưới của bạn nếu bạn muốn câu trả lời mạnh mẽ. EDIT: Nhưng đừng làm vậy. Thay đổi yêu cầu giữa cuộc thi là xấu.
Rainbolt

Câu trả lời:


7

GolfScript, 82 ( 108 97 ký tự - 15 phần thưởng)

~),1/{{:F$0=),{F\+}/}%}@(*(0*\{1${1$\{\(@<},=},{1$\{\(@>},+(-!},:Y!{.,/+0}*;}/;Y{.-1=.@?)' '@)n}/

Vì tôi không biết đối với bất kỳ phương pháp phỏng đoán nào, giải pháp này thực hiện tìm kiếm toàn diện trên không gian giải pháp. Bạn có thể thử mã trực tuyến . Mặc dù việc triển khai rất hiệu quả, không gian tìm kiếm phát triển rất nhanh với việc tăng đầu vào.

Ví dụ:

> 5 3
4 3

> 5 4
3 3

> 6 6
2 2

Như đã đề cập ở trên, việc thực hiện không dựa vào đệ quy mà chỉ truy cập vào mỗi nút của không gian tìm kiếm một lần. Dưới đây bạn có thể tìm thấy một phiên bản mã chú thích mô tả các khối xây dựng chi tiết hơn.

Việc biểu diễn một bảng có kích thước w * h được đưa ra bởi một danh sách các số w trong phạm vi từ 0 đến h . Mỗi số cho số lượng mảnh trong cột tương ứng. Do đó, cấu hình hợp lệ là một danh sách trong đó các số không tăng dần từ đầu đến cuối (với bất kỳ di chuyển nào bạn đảm bảo rằng tất cả các cột ở bên phải đều cao nhất so với số được chọn).

~                   # Evaluate the input (stack is now w h)

# BUILDING THE COMPLETE STATE SPACE
# Iteratively builds the states starting with 1xh board, then 2xh board, ...

),1/                # Generate the array [[0] [1] ... [h]] which is the space for 1xh
{                   # This loop is now ran w-1 times and each run adds all states for the 
                    # board with one additional column
  {                 # The {}/] block simply runs for each of the existing states
    :F$0=           #   Take the smallest entry (which has to be the last one)
    ),              #   For the last column all values 0..x are possible
    {F\+}/          #     Append each of these values to the smaller state
  }%
}@(*

# The order ensures that the less occupied boards are first in the list.
# Thus each game runs from the end of the list (where [h h ... h] is) to 
# the start (where [0 0 ... 0] is located).

# RUN THROUGH THE SEARCH SPACE
# The search algorithm therefore starts with the empty board and works through all
# possible states by simply looping over this list. It builds a list of those states
# which are known as non-winning states, i.e. those states where a player should 
# aim to end after the move

(                   # Skips the empty board (which is a winning configuration)
0*\                 # and makes an empty list out of it (which will be the list of
                    # known non-winning states (initially empty))
{                   # Loop over all possible states
  1$                #   Copy of the list of non-winning states
  {                 #   Filter those which are not reachable from the current state,
                    #   because at least one column has more pieces that the current
                    #   board has
    1$\{\(@<},=
  },
  {                 #   Filter those which are not reachable from the current state,
                    #   because no valid move exists
    1$\{\(@>},+     #     Filter those columns which are different between start and
                    #     end state
    (-!             #     If those columns are all of same height it is possible to move
  },
  :Y                #   Assign the result (list of all non-winning states which are
                    #   reachable from the current configuration within one move)
                    #   to variable Y

  !{                #   If Y is non-empty this one is a winning move, otherwise 
                    #   add it to the list
    .,/+
    0               #     Push dummy value
  }*;
}/
;                   # Discard the list (interesting data was saved to variable Y)

# OUTPUT LOOP
# Since the states were ordered the last one was the starting state. The list of 
# non-winning states were saved to variable Y each time, thus the winning moves 
# from the initial configuration is contained in this variable.

Y{                  # For each item in Y
  .-1=.@?)          #   Get the index (1-based) of the first non-h value
  ' '               #   Append a space
  @)                #   Get the non-h value itself (plus one)
  n                 #   Append a newline
}/

+1 Cho chính giải pháp và cho mã nhận xét rất độc đáo
Xuntar

Lập trình động từ dưới lên, thay vì từ trên xuống. Đẹp. Tôi đã xem xét việc đó, nhưng việc tạo ra các trạng thái theo thứ tự hợp lệ cho giao dịch từ dưới lên là công việc nhiều hơn và khó hiểu hơn so với tìm kiếm đệ quy. Tôi ngạc nhiên khi mã kết thúc quá lâu; Tôi mong đợi một ngôn ngữ ngắn gọn như Golfscript có thể tạo ra một giải pháp ngắn hơn nhiều.
user2357112 hỗ trợ Monica

Rất đẹp và được
cân nhắc kỹ lưỡng

8

Trăn 2 3, 141-15 = 126

def win(x,y):w([y]*x)
w=lambda b,f=print:not[f(r+1,c+1)for r,p in enumerate(b)for c in range(p)if(r+c)*w(b[:r]+[min(i,c)for i in b[r:]],max)]

Brax-lực tìm kiếm tối thiểu. Đối với mọi di chuyển có thể, chúng tôi xem xét đệ quy xem đối thủ có thể giành chiến thắng sau khi chúng tôi thực hiện di chuyển đó không. Khá yếu chơi golf; người khác sẽ có thể làm tốt hơn nhiều. Cảm giác này giống như một công việc cho APL.

  • winlà giao diện công cộng. Nó lấy kích thước của bảng, chuyển đổi nó thành một bảng đại diện và chuyển nó sang w.
  • wlà thuật toán minimax. Nó có một trạng thái bảng, thử tất cả các di chuyển, xây dựng một danh sách có các yếu tố tương ứng với các nước đi chiến thắng và trả về True nếu danh sách trống. Với mặc định f=print, việc xây dựng danh sách có tác dụng phụ là in các bước đi chiến thắng. Tên hàm được sử dụng để có ý nghĩa hơn khi nó trả về một danh sách các bước di chuyển chiến thắng, nhưng sau đó tôi di chuyển notphía trước danh sách để tiết kiệm một khoảng trống.
  • for r,p in enumerate(b)for c in xrange(p) if(r+c): Lặp đi lặp lại tất cả các động thái có thể. 1 1được coi là không phải là một động thái hợp pháp, đơn giản hóa trường hợp cơ sở một chút.
  • b[:r]+[min(i,c)for i in b[r:]]: Xây dựng trạng thái của bảng sau khi di chuyển được biểu thị bằng tọa độ rc.
  • w(b[:r]+[min(i,c)for i in b[r:]],max): Recurse để xem liệu trạng thái mới là trạng thái mất. maxlà hàm ngắn nhất tôi có thể tìm thấy sẽ có hai đối số nguyên và không phàn nàn.
  • f(r+1,c+1): Nếu flà in, in di chuyển. Dù flà gì , nó tạo ra một giá trị để đệm chiều dài danh sách.
  • not [...]: nottrả về Truecho danh sách trống và Falsecho nonempty.

Mã Python 2 gốc, hoàn toàn không được mã hóa, bao gồm ghi nhớ để xử lý các đầu vào lớn hơn nhiều:

def win(x, y):
    for row, column in _win(Board([y]*x)):
        print row+1, column+1

class MemoDict(dict):
    def __init__(self, func):
        self.memofunc = func
    def __missing__(self, key):
        self[key] = retval = self.memofunc(key)
        return retval

def memoize(func):
    return MemoDict(func).__getitem__

def _normalize(state):
    state = tuple(state)
    if 0 in state:
        state = state[:state.index(0)]
    return state

class Board(object):
    def __init__(self, state):
        self.state = _normalize(state)
    def __eq__(self, other):
        if not isinstance(other, Board):
            return NotImplemented
        return self.state == other.state
    def __hash__(self):
        return hash(self.state)
    def after(self, move):
        row, column = move
        newstate = list(self.state)
        for i in xrange(row, len(newstate)):
            newstate[i] = min(newstate[i], column)
        return Board(newstate)
    def moves(self):
        for row, pieces in enumerate(self.state):
            for column in xrange(pieces):
                if (row, column) != (0, 0):
                    yield row, column
    def lost(self):
        return self.state == (1,)

@memoize
def _win(board):
    return [move for move in board.moves() if not _win(board.after(move))]

Bản giới thiệu:

>>> for i in xrange(7, 11):
...     for j in xrange(7, 11):
...         print 'Dimensions: {} by {}'.format(i, j)
...         win(i, j)
...
Dimensions: 7 by 7
2 2
Dimensions: 7 by 8
3 3
Dimensions: 7 by 9
3 4
Dimensions: 7 by 10
2 3
Dimensions: 8 by 7
3 3
Dimensions: 8 by 8
2 2
Dimensions: 8 by 9
6 7
Dimensions: 8 by 10
4 9
5 6
Dimensions: 9 by 7
4 3
Dimensions: 9 by 8
7 6
Dimensions: 9 by 9
2 2
Dimensions: 9 by 10
7 8
9 5
Dimensions: 10 by 7
3 2
Dimensions: 10 by 8
6 5
9 4
Dimensions: 10 by 9
5 9
8 7
Dimensions: 10 by 10
2 2

Để 13x13thực hiện 2x2và bạn sẽ giành chiến thắng.
davidsbro

@davidsbro: Vâng, đó là động thái chiến thắng cho bất kỳ bảng vuông nào lớn hơn 1x1, nhưng mã của tôi chưa tính toán được.
user2357112 hỗ trợ Monica

2

Perl 6: 113 108 ký tự - 15 = 93 điểm

Điều này thật khó khăn! Dưới đây là phiên bản uncached, đó là chính xác về mặt kỹ thuật nhưng sẽ mất một rất thời gian dài vì những đóng góp không tầm thường.

sub win(*@b){map ->\i,\j{$(i+1,j+1) if @b[i][j]&&!win @b[^i],@b[i..*].map({[.[^j]]})},(^@b X ^@b[0])[1..*]}

Nó hoạt động giống như triển khai Python của @ user2357112 (nâng cấp anh ấy / cô ấy, tôi không thể tìm ra điều này mà không có công việc của anh ấy / cô ấy!) Ngoại trừ việc win () lấy bảng Chomp (mảng) thay vì chiều rộng và chiều dài. Nó có thể được sử dụng như:

loop {
    my ($y, $x) = get.words;
    .say for @(win [1 xx $x] xx $y)
}

Một phiên bản với khả năng ghi nhớ, thực sự có thể xử lý các đầu vào tốt (mặc dù không được tối ưu hóa cho khả năng đọc):

my %cache;
sub win (*@b) {
    %cache{
        join 2, map {($^e[$_]??1!!0 for ^@b[0]).join}, @b
    } //= map ->\i,\j{
        $(i+1,j+1) if @b[i][j] and not win
            @b[^i], @b[i..*].map({[.[^(* min j)]]}).grep: +*;
    },(^@b X ^@b[0])[1..*]
}
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.