找到最佳的起步动作


14

Chomp是一款两人游戏,具有矩形的碎片设置。每个玩家轮流移除任何棋子,以及其上方和右侧的所有棋子。拿到左下角的人都会输。可以很容易地证明第一个玩家总是有获胜的举动(除非有1 x 1矩形);找到它。

  1. 输入为矩形的尺寸(两个数字)
  2. 输出是获胜举动的位置(两个数字)
  3. 如果有多个获胜举动,则可以输出其中任何一个。

这是代码高尔夫;最短的代码(任何语言)均获胜。

例子

注意:输出仅是两个数字。下面的ASCII文字只是为了演示数字的含义。

输入:5 3(从左下角开始以1为索引)

输出:4 3

XXX--
XXXXX
XXXXX

输入:4 4

输出:2 2

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

奖金

如果您输出所有获胜的动作,请从您的分数中减去15个字符。每对数字必须用换行符分隔。


在您的第一个示例中,我认为您有太多破折号
kitcar2000 2014年

@Kitcar你是对的;固定。
Ypnypn 2014年

我无法理解输出格式。这些数字与这些位置如何对应?
Undergroundmonorail

@undergroundmonorail 1从左下角开始的索引。第一个索引是水平轴,第二个索引是垂直索引。
Martin Ender 2014年

2
为了回应您的赏金大意:在国际象棋中,任何给定时间的动作可能少于119(通常少得多),而且迄今为止,没有超级计算机甚至无法使用最著名的算法来解决国际象棋。在10 x 10的Chomp网格上,有100个可能的第一步,而每个步都有1-99个潜在的第二步。是什么让您认为这很容易被暴力对待?如果您想要蛮力答案,我建议限制网格大小。编辑:但是不要那样做。在比赛中更改要求是不好的。
雨螺栓

Answers:


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的单板的表示形式由一系列w编号组成,范围在0h之间。每个数字给出了相应列中的件数。因此,有效的配置是一个列表,其中的数字从头到尾都没有增加(通过任何移动,您都可以确保右边的所有列最多与所选列一样高)。

~                   # 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 2014年

自下而上的动态编程,而不是自上而下的。真好 我考虑过这样做,但是与递归搜索相比,以自下而上的遍历的有效顺序生成状态会花费更多的工作并且更加混乱。我很惊讶代码结束了这么长时间;我希望像Golfscript这样的简洁语言可以提供更短的解决方案。
user2357112支持Monica 2014年

非常漂亮和深思熟虑的
Mouq

8

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

蛮力minimax搜索。对于每一个可能的举动,我们都会递归地查看对手做出该举动后是否可以获胜。打的很弱;别人应该可以做得更好。感觉就像是APL的工作。

  • win是公共接口。它采用了板子的尺寸,将其转换为板子表示,并将其传递给w
  • w是minimax算法。它处于棋盘状态,尝试所有移动,构建一个列表,该列表的元素与获胜移动相对应,如果列表为空,则返回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 2014年

@davidsbro:是的,这是任何大于1x1的正方形板的制胜法宝,但我的代码尚未计算出来。
user2357112支持Monica 2014年

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

它的工作方式类似于@ user2357112的Python实现(支持他/她,没有他/她的工作,我无法弄清楚!),只是win()使用一块Chomp板(数组)而不是宽度和长度。可以像这样使用:

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..*]
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.