Chompの最適な開始移動を見つける


14

Chompは、長方形のピースが設定された2人用のゲームです。各プレイヤーは、その上とその右にあるすべてのピースと一緒に、任意のピースを削除するターンを取ります。左下のピースを取る人は負けます。最初のプレーヤーが常に勝ち手であることはかなり簡単に証明できます(1行1列の長方形を除く)。それを見つける。

  1. 入力は長方形の寸法です(2つの数字)
  2. 出力は、勝った動きの場所です(2つの数字)
  3. 複数の勝ち手がある場合、それらのいずれかを出力できます。

これはコードゴルフです。最短のコード(任意の言語)が勝ちます。

注:出力は2つの数値にすぎません。以下のASCIIアートは、数字の意味を示すためのものです。

入力:5 3(左下隅から始まる1から始まるインデックス)

出力:4 3

XXX--
XXXXX
XXXXX

入力:4 4

出力:2 2

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

ボーナス

勝利の動きをすべて出力する場合、スコアから15文字を引きます。数字の各ペアは、改行で区切る必要があります。


あなたの最初の例では、私はあなたが1つのあまりにも多くのダッシュ持っていると思います
kitcar2000

@Kitcarそうですね。修繕。
イプニプン14年

出力形式が理解できません。それらの番号は、それらの位置にどのように対応しますか?
地下

@undergroundmonorail左下からの1から始まるインデックス。最初のインデックスは水平軸で、2番目のインデックスは垂直インデックスです。
マーティンエンダー

2
賞金への対応:チェスでは、常に119未満の動きしかありません(通常ははるかに少ない)。今日まで、スーパーコンピューターは、最もよく知られているアルゴリズムを使用してもチェスを解くことができません。10 x 10のChompグリッドでは、100の可能な最初の動きがあり、それぞれに1〜99の潜在的な2番目の動きがあります。総当たり攻撃が簡単だと思われる理由は何ですか?総当たりの回答が必要な場合は、グリッドサイズを制限することをお勧めします。編集:しかし、それをしないでください。コンテスト中に要件を変更するのは悪いことです。
レインボルト

回答:


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

上記のように、実装は再帰に依存せず、サーチスペースの各ノードに1回だけアクセスします。以下に、ビルディングブロックをより詳細に記述するコードの注釈付きバージョンを見つけることができます。

サイズが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は

非常に素晴らしくよく考え抜かれた
Mouq 14年

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

ブルートフォースミニマックス検索。考えられるすべての動きについて、その動きをした後に対戦相手が勝つことができるかどうかを再帰的に確認します。かなり弱いゴルフ。他の誰かがもっとうまくやれるはずです。これはAPLの仕事のように感じます。

  • winパブリックインターフェイスです。ボードの寸法を取得し、ボード表現に変換し、それをに渡しwます。
  • wはミニマックスアルゴリズムです。ボードの状態を取得し、すべての動きを試み、勝った動きに対応する要素を持つリストを作成し、リストが空の場合に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は、2つの整数引数を取り、文句を言わない最短の関数です。
  • f(r+1,c+1)f印刷の場合、移動を印刷します。なんでもf、リストの長さを埋めるための値を生成します。
  • not [...]:空のリストと空でないリストをnot返します。TrueFalse

はるかに大きな入力を処理するメモ化を含む、完全に未使用のオリジナルの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..*]}

@ 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..*]
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.