Chomp의 최적 시작 움직임 찾기


14

Chomp 은 사각형 조각으로 구성된 2 인용 게임입니다. 각 플레이어는 한 조각을 제거하고 그 위와 오른쪽의 모든 조각을 제거합니다. 왼쪽 아래 조각을 가져간 사람이집니다. 첫 번째 플레이어가 항상 1x1 사각형을 제외하고는 승리를 거두었다는 것이 상당히 쉽게 입증 될 수 있습니다. 그것을 찾아라.

  1. 입력은 사각형의 치수입니다 (두 숫자).
  2. 출력은이긴 이동의 위치입니다 (두 숫자).
  3. 이기는 이동이 여러 개인 경우 그 중 하나를 출력 할 수 있습니다.

이것은 코드 골프입니다. 가장 짧은 코드 (모든 언어)가 이깁니다.

참고 : 출력은 두 숫자 일뿐입니다. 아래의 ASCII 기술은 숫자의 의미를 보여주기위한 것입니다.

입력 : 5 3 (왼쪽 하단부터 시작하여 1부터 시작)

출력 : 4 3

XXX--
XXXXX
XXXXX

입력 : 4 4

출력 : 2 2

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

보너스

승리 한 움직임을 모두 출력하면 점수에서 15자를 뺍니다. 각 숫자 쌍은 줄 바꿈으로 구분해야합니다.


첫 번째 예에서, 난 당신이 너무 많은 대시를 가지고 있다고 생각
kitcar2000

@Kitcar 당신이 맞아요; 결정된.
Ypnypn

출력 형식을 이해할 수 없습니다. 그 숫자는 그 위치와 어떻게 일치합니까?
undergroundmonorail

@undergroundmonorail 왼쪽에서 1부터 시작하는 인덱스입니다. 첫 번째 인덱스는 가로 축이고 두 번째 인덱스는 세로 인덱스입니다.
Martin Ender

2
현상금에 대한 응답으로 : Chess에서는 주어진 시간에 119 개 미만의 이동이 가능하며 (대개 훨씬 적음) 오늘날까지 슈퍼 컴퓨터는 가장 잘 알려진 알고리즘을 사용하여 체스를 풀지 못하고 있습니다. 10 x 10 Chomp 그리드에는 100 개의 가능한 첫 번째 이동이 있으며 각 이동에는 1-99 개의 잠재적 인 두 번째 이동이 있습니다. 그것이 무차별 대항하기 쉬운 이유는 무엇입니까? 무차별 대입 답변을 원한다면 그리드 크기를 제한하는 것이 좋습니다. 편집 : 그러나 그렇게하지 마십시오. 컨테스트 중 변경 요구 사항이 잘못되었습니다.
Rainbolt

답변:


7

GolfScript, 82 ( 108 97 자-15 보너스)

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

휴리스틱을 알지 못했기 때문에이 솔루션은 솔루션 공간을 철저히 검색합니다. 온라인으로 코드를 사용해보십시오 . 구현이 매우 효율적이지만 입력이 증가함에 따라 검색 공간이 매우 빠르게 커집니다.

예 :

> 5 3
4 3

> 5 4
3 3

> 6 6
2 2

위에서 언급했듯이 구현은 재귀에 의존하지 않고 검색 공간의 각 노드를 한 번만 방문합니다. 아래에서 빌딩 블록을 자세히 설명하는 주석이 달린 코드 버전을 찾을 수 있습니다.

크기가 w * h 인 단일 보드의 표현은 0 에서 h 사이w 번호 목록으로 제공됩니다 . 각 숫자는 해당 열의 조각 수를 나타냅니다. 따라서 유효한 구성은 시작부터 끝까지 숫자가 증가하지 않는 목록입니다 (이동하면 오른쪽의 모든 열이 선택한 열보다 많아야합니다).

~                   # 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 솔루션 자체와 매우 주석 처리 된 코드
Xuntar

하향식이 아닌 상향식 동적 프로그래밍. 좋은. 나는 그렇게하는 것을 고려했지만 상향식 순회를 위해 올바른 순서로 상태를 생성하는 것이 재귀 검색보다 더 많은 작업과 혼란을 주었다. 코드가 너무 길어서 놀랐습니다. Golfscript와 같은 간결한 언어가 훨씬 짧은 솔루션을 생산할 것으로 예상했습니다.
user2357112는 Monica

아주 좋고 잘 생각
Mouq

8

파이썬 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)]

무차별 미니맥 검색. 가능한 모든 움직임에 대해, 우리는 그 움직임을 한 후에 상대방이 이길 수 있는지 재귀 적으로 봅니다. 꽤 약한 골프; 다른 사람은 훨씬 더 잘할 수 있어야합니다. 이것은 APL의 직업처럼 느껴집니다.

  • win공용 인터페이스입니다. 보드의 크기를 가져와 보드 표현으로 변환 한 후로 전달합니다 w.
  • wminimax 알고리즘입니다. 보드 상태를 취하고 모든 이동을 시도하고 승리 한 이동에 해당하는 요소를 가진 목록을 작성하고 목록이 비어 있으면 True를 반환합니다. 기본값을 사용하면 f=print목록을 작성하면 승리 한 동작을 인쇄하는 부작용이 있습니다. 함수 이름은이기는 동작 목록을 반환 할 때 더 의미가 있었지만 not공간을 절약하기 위해 목록 앞을 이동했습니다 .
  • for r,p in enumerate(b)for c in xrange(p) if(r+c): 가능한 모든 동작을 반복합니다. 1 1법적 소송이 아닌 것으로 간주되어 기본 사례를 약간 단순화합니다.
  • b[:r]+[min(i,c)for i in b[r:]]: 좌표로 표시된 이동 후 보드의 상태를 구성 r하고c .
  • w(b[:r]+[min(i,c)for i in b[r:]],max): 새 상태가 손실 상태인지 확인하기 위해 되풀이합니다. max내가 찾을 수있는 가장 짧은 함수는 두 개의 정수 인수를 취하고 불평하지 않습니다.
  • f(r+1,c+1): f가 인쇄되면 이동을 인쇄합니다. 도대체 무엇이f 목록 길이를 채우는 값을 생성합니다.
  • not [...]: 빈 목록을 not반환 True하고False 비어 있지 않은 경우 .

훨씬 더 큰 입력을 처리하기위한 메모를 포함하여 완전히 압축되지 않은 원래 Python 2 코드 :

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

데모:

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

를 들어 13x13걸릴 2x2당신은 이길 것입니다.
davidsbro

@ davidsbro : 그렇습니다 .1x1보다 큰 사각형 보드의 승리 움직임이지만 내 코드는 아직 계산하지 않았습니다.
user2357112는

2

Perl 6 : 113108 자-15 = 93 점

이것은 힘들었다! 여기에 기술적으로 정확하지만 소요됩니다 캐시되지 않은 버전의 아주 사소하지 않은 입력을위한 긴 시간.

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..*]}

win ()은 너비와 길이 대신 Chomp 보드 (배열)를 사용한다는 점을 제외하고 @ user2357112의 Python 구현 과 동일하게 작동합니다 (그녀의 작업없이 이것을 알아낼 수 없었습니다!). 다음과 같이 사용할 수 있습니다.

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

적절한 입력을 실제로 처리 할 수있는 메모가 포함 된 버전입니다 (가독성에 최적화되지는 않음).

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