Kingdom Builderのゲームを獲得する


16

ここで新しい形のコードゴルフを試したいです。ボーナスと同様に、チャレンジのすべての部分を完了する必要はありませんが、各回答には特定のサイズのサブセットを実装する必要があります(すべての回答に実装する必要があるコアがあります)。したがって、ゴルフに加えて、この課題には、うまく機能する一連の機能を選択することも含まれます。

ルール

キングダムビルダーはボードゲームで、(先が尖った)ヘックスグリッドでプレイされます。ボードは4つの(ランダム化された)クアドラントで構成され、各クアドラントには10​​x10の16進数セルがあります(したがって、ボード全体は20x20になります)。この課題のために、各ヘックスセルには水(W)、山(M)、町(T)、城(C)、または空(.)が含まれています。したがって、象限は次のようになります

. . W . . . . . . .
 . M W W . . . . . .
. M . . W . . . T .
 M M . W . . . . . .
. . M . W W . . . .
 . . . . . W W W W W
. T . . . . . . . .
 . . W . . C . . . .
. . W W . . . . M . 
 . . . . . . . M M .

2番目の行は、常に最初の行から右にオフセットされます。プレーヤー1には、4(私たちはこの挑戦のために無視するいくつかのルール以下)空のセルに40の集落ごとまで置くことができます。ゲーム終了時に可能なボードは次のとおりです。

3 3 W . . . 4 . 4 . . 2 W . 4 . . 4 . 4
 3 M W W . 1 1 . . 4 2 W . 3 C 4 4 . . 4
3 M 2 2 W 1 1 1 T 3 2 W 4 3 . 1 4 . 4 .
 M M . W 2 2 . . . 2 2 W 3 . 1 1 1 . . .
. 4 M . W W 2 2 2 2 W W 3 . 1 4 . T . .
 . . . . . W W W W W . 3 C 1 . . 2 2 2 2
. T 1 1 1 1 . . 2 . . 4 . . . 2 2 M M M
 4 . W 4 . C 4 4 . . . . . . 2 M M M M M
. 4 W W . . . 4 M . . W . W . 2 2 2 M M
 . . . . . . . M M . . W W . . . . 2 M .
. . . 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 . 1
 M 3 3 . . . . . . . . 4 . T 2 . 2 4 1 .
M M . C . 4 . 4 . . . . . 1 2 4 2 1 1 .
 M . . 1 . 4 . . . . M M 1 2 . . 2 1 . .
. . . W 1 1 4 1 1 . . . 1 2 . . 2 W W W
 . . 1 1 W 1 T . 1 1 1 1 T . . 2 W . 4 .
. 1 1 W . 3 3 . . . . . . . . 2 W 4 C 3
 C 1 3 3 3 . 3 . 4 . 4 . 4 . . 2 W 1 1 M
4 3 3 4 . M 4 3 . . . . . . . 2 W . . .
 . . . 4 . M M 3 . . 4 4 . 4 . 2 W W . .

次のような象限にラベルを付けます

1 2
3 4

あなたの仕事は、そのようなボードを獲得することです。常に使用されるコアスコアが1つ、オプションのスコアが8 あり、そのうち3つが各ゲームで選択されます。以下では、9つのスコアすべてについて説明し、各プレイヤーが獲得するポイント数の例として上記の設定を使用します。

†実際のゲームには10のスコアがありますが、誰もゴルフをしたくないので2つ残します。

コアスコア。プレーヤーは、隣に決済がある各アトルに対して3ポイント獲得Cします。スコアの例:18、0、15、12

オプションのスコア。

  1. プレイヤーは、少なくとも1つの集落がある水平列ごとに1ポイントを獲得します。

    スコアの例:14、20、12、16

  2. 各プレイヤーについて、彼らが彼らの集落のほとんどで横の列を見つけます(同点の場合はどれでも選びます)。プレーヤーは、その列の各集落に対して2ポイント獲得します。

    スコアの例: 14(行16)、8(行4、5または6)、28(行11)、10(行1)。

  3. プレイヤーは、aterの隣に構築される各集落に対して1ポイントを獲得しWます。

    スコアの例:13、21、10、5

  4. プレイヤーは、泉の隣の集落ごとに1ポイントM獲得します。

    スコアの例:4、12、8、4

  5. 各象限の各プレイヤーの居留地を数えます。象限ごとに、最大数の集落を持つプレイヤーはそれぞれ12ポイントを獲得し、2番目に多い集落を持つプレイヤーはそれぞれ6ポイントを獲得します。

    スコアの例: 18(6 + 0 + 6 + 6)、36(12 + 12 + 0 + 12)、12(0 + 0 + 12 + 0)、18(12 + 6 + 0 + 0)。

  6. 各プレイヤーについて、居住地の数が最も少ない象限を決定します。プレーヤーは、その象限の各集落に対して3ポイント獲得します。

    スコアの例: 18(象限2)、0(象限3)、15(象限1または2)、27(象限3)。

  7. プレーヤーは、接続された決済グループごとに1ポイントを獲得します。

    スコアの例:7、5、6、29

  8. プレイヤーは、接続された決済の最大のグループ内の2つの決済ごとに1ポイントを獲得します。

    スコアの例:4、10、8、2

チャレンジ

ゲームの場合と同様に、オプションのスコアを3つ選択し、コアスコアとそれらの3つのスコアに基づいて特定のボードにスコアを付けます。コードで4つのスコアのリストを作成する必要があります。ただし、選択には1つの制限があります。スコアを3つのグループにグループ化し、各グループの1つを実装します。

  • 1と2のいずれかを実装します。
  • 3、4、5および6のいずれかを実装します。
  • 7と8のいずれかを実装します。

STDIN、コマンドライン引数、プロンプトまたは関数パラメーターを介して入力を取得して、プログラムまたは関数を作成できます。結果を返すか、STDOUTに出力できます。

入力に便利な1Dまたは2Dリスト/文字列形式を選択できます。完全な隣接情報を含むグラフを使用することはできません。インスピレーションが必要な場合は、16進グリッドに関するいくつかの良い読み物があります。

出力は、便利で明確なリスト形式または文字列形式でもかまいません。

これはコードゴルフなので、最短の回答(バイト単位)が勝ちです。

さらなる仮定

あなたはそれを仮定するかもしれません...

  • ...各プレイヤーには少なくとも1つの居留地があり、各プレイヤーの居留地は40以下です。
  • ...各象限には、1つの町と2つの城、または2つの町と1つの城のいずれかが含まれます。
  • ...町と城は十分に離れており、2つの村に隣接する集落はありません。

テストケース

上記のボードを引き続き使用して、スコアリングメカニズムのすべての可能な選択に対する個々のスコアを以下に示します。

Chosen Scores      Total Player Scores
1 3 7              52 46 43 62
1 3 8              49 51 45 35
1 4 7              43 37 41 61
1 4 8              40 42 43 34
1 5 7              57 61 45 75
1 5 8              54 66 47 48
1 6 7              57 25 48 84
1 6 8              54 30 50 57
2 3 7              52 34 59 56
2 3 8              49 39 61 29
2 4 7              43 25 57 55
2 4 8              40 30 59 28
2 5 7              57 49 61 69
2 5 8              54 54 63 42
2 6 7              57 13 64 78
2 6 8              54 18 66 51

組み合わせに関係なく、常に1人のプレイヤーが勝つボードがありますか?
ThreeFx

@ThreeFxプレーヤーごとの決済数の下限は1なので、設定はかなり簡単です。;)しかし、各プレイヤーの同数の和解では、私は実際には知りません。
マーティンエンダー

回答:


5

Python 2、367バイト

T=range(20)
N=lambda r,c:{(a,b)for a,b in{(r+x/3-1,c+x%3-1+(x/3!=1)*r%2)for x in[0,1,3,5,6,7]}if-1<b<20>a>-1}
def S(B):
 def F(r,c):j=J[r][c]!=i;J[r][c]*=j;j or map(F,*zip(*N(r,c)));return j
 J=map(list,B);X=lambda r,c,x,y:x+y in{B[r][c]+B[a][b]for a,b in N(r,c)};return[sum((i in B[r])+20*(3*X(r,c,"C",i)-~X(r,c,i,"W")-F(r,c))for r in T for c in T)/20for i in"1234"]

プログラムはスコア1、3、7を使用します。入力は、各セルを表す文字のリストのリストです。サンプルボードを簡単にテストするには、次のようにします。

board = """
3 3 W . . . 4 . 4 . . 2 W . 4 . . 4 . 4
 3 M W W . 1 1 . . 4 2 W . 3 C 4 4 . . 4
3 M 2 2 W 1 1 1 T 3 2 W 4 3 . 1 4 . 4 .
 M M . W 2 2 . . . 2 2 W 3 . 1 1 1 . . .
. 4 M . W W 2 2 2 2 W W 3 . 1 4 . T . .
 . . . . . W W W W W . 3 C 1 . . 2 2 2 2
. T 1 1 1 1 . . 2 . . 4 . . . 2 2 M M M
 4 . W 4 . C 4 4 . . . . . . 2 M M M M M
. 4 W W . . . 4 M . . W . W . 2 2 2 M M
 . . . . . . . M M . . W W . . . . 2 M .
. . . 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 . 1
 M 3 3 . . . . . . . . 4 . T 2 . 2 4 1 .
M M . C . 4 . 4 . . . . . 1 2 4 2 1 1 .
 M . . 1 . 4 . . . . M M 1 2 . . 2 1 . .
. . . W 1 1 4 1 1 . . . 1 2 . . 2 W W W
 . . 1 1 W 1 T . 1 1 1 1 T . . 2 W . 4 .
. 1 1 W . 3 3 . . . . . . . . 2 W 4 C 3
 C 1 3 3 3 . 3 . 4 . 4 . 4 . . 2 W 1 1 M
4 3 3 4 . M 4 3 . . . . . . . 2 W . . .
 . . . 4 . M M 3 . . 4 4 . 4 . 2 W W . .
"""

board = [row.split() for row in board.strip().split("\n")]
print S(board)

# [52, 46, 43, 62]

六角グリッドの処理

私たちは16進グリッド上にいるので、隣人とは少し異なる方法で対処する必要があります。従来の2Dグリッドを表現として使用する場合、次のよう(1, 1)になります。

. N N . .       . N N . .                (0, 1), (0, 2)            (-1, 0), (-1, 1)
 N X N . .  ->  N X N . .  -> Neighbours (1, 0), (1, 2) -> Offsets (0, -1), (0, 1)
. N N . .       . N N . .                (2, 1), (2, 2)            (1, 0), (1, 1)

詳細に調べると、オフセットは現在の行のパリティに依存することがわかります。上記の例は奇数行用ですが、偶数行ではオフセットは

(-1, -1), (-1, 0), (0, -1), (0, 1), (1, -1), (1, 0)

変更された唯一のことは、1番目、2番目、5番目、および6番目のペアの2番目の座標が1ずつ減少したことです。

ラムダ関数Nは座標のペア(row, col)を取り、グリッド内のセルのすべての近傍を返します。内部の内包表記は、単純なベース3エンコードからそれらを抽出して上記のオフセットを生成し、行が奇数の場合は2番目の座標をインクリメントし、問題のセルにオフセットを追加して隣接セルを提供します。次に、外側の内包がフィルタリングされ、グリッドの境界内にある近傍のみが残されます。

非ゴルフ

def neighbours(row, col):
    neighbour_set = set()

    for dr, dc in {(-1,-1), (-1,0), (0,-1), (0,1), (1,-1), (1,0)}:
        neighbour_set.add((row + dr, col + dc + (1 if dr != 0 and row%2 == 1 else 0)))

    return {(r,c) for r,c in neighbour_set if 20>r>-1 and 20>c>-1}

def solve(board):
    def flood_fill(char, row, col):
        # Logic negated in golfed code to save a few bytes
        is_char = (dummy[row][col] == char)
        dummy[row][col] = "" if is_char else dummy[row][col]

        if is_char:
            for neighbour in neighbours(row, col):
                flood_fill(char, *neighbour)

        return is_char

    def neighbour_check(row, col, char1, char2):
        return board[row][col] == char1 and char2 in {board[r][c] for r,c in neighbours(row, col)}

    dummy = [row[:] for row in board] # Need to deep copy for the flood fill
    scores = [0]*4

    for i,char in enumerate("1234"):
        for row in range(20):
            for col in range(20):
                scores[i] += (char in board[row])                        # Score 1
                scores[i] += 20 * 3*neighbour_check(row, col, "C", char) # Core score
                scores[i] += 20 * neighbour_check(row, col, char, "W")   # Score 3
                scores[i] += 20 * flood_fill(char, row, col)             # Score 7

        # Overcounted everything 20 times, divide out
        scores[i] /= 20

    return scores

def F内部関数ではなく、別個の関数にすることはできませんか?kから削除できませんdef F:か?
ジャスティン

@Quincunx Fは塗りつぶし関数であり、へのアクセスが必要なJのでJ、パラメーターとして渡すことを節約するために内部にあります(ディープコピーを回避できるかどうかを確認するために少し実験します)。あなたは正しいですkが、ありがとう:)(ただし、短絡に依存しているため、新しいコードは少しファンキーに見えます)
-Sp3000

2

回答セットプログラミング、629バイト

d(X,Y):-b(X,Y,_).p(1;2;3;4).n(X,Y,(((X-2;X+2),Y);((X-1;X+1),(Y-1;Y+1)))):-d(X,Y).n(X,Y,I,J):-n(X,Y,(I,J));d(I,J).t(X,Y,P):-n(X,Y,I,J);b(I,J,P).s(c,P,S*3):-S={t(X,Y,P):b(X,Y,"C")};p(P).s(1,P,S*1):-S=#count{r(Y):b(_,Y,P)};p(P).s(3,P,S):-S={b(X,Y,P):t(X,Y,"W")};p(P).o(X,Y,Y+X*100):-d(X,Y).h(P,X,Y,I,J):-o(X,Y,O);o(I,J,Q);O<Q;n(X,Y,I,J);b(X,Y,P);b(I,J,P);p(P).h(P,X,Y,I,J):-o(X,Y,O);o(I,J,Q);O<Q;h(P,X,Y,K,L);n(K,L,I,J);b(I,J,P);p(P).c(P,X,Y):-h(P,X,Y,_,_);not h(P,_,_,X,Y).c(P,X,Y):-{h(P,X,Y,_,_);h(P,_,_,X,Y)}0;b(X,Y,P);p(P).s(7,P,S):-S=#count{c(P,X,Y):c(P,X,Y)};p(P).s(t,P,C+S+T+U):-s(c,P,C);s(1,P,S);s(3,P,T);s(7,P,U).#shows/3.

ASPはロジックプログラミング言語ファミリに属します。ここでは、 Potasscoフレームワーク、特にClingo(グランダGringo +ソルバークラスプ)ます。パラダイムの制限のため、与えられたボードを出力として直接取ることができないため、データの前処理が必要です(ここではpythonで実行されます)。この前処理は、合計バイトスコアにはカウントされません。

私の最初のコードゴルフであり、目的は、実際にゲームに勝つことよりも、今までゴルフで見たことのない愛する言語を示すことです。さらに、私はASPの専門家とはほど遠いので、より少ないバイトで結果を得るためにコードの多くの最適化を確実に実行できます。

知識表現

アトムのボードを変換するPythonコードがあります:

def asp_str(v):
    return ('"' + str(v) + '"') if v not in '1234' else str(v)

with open('board.txt') as fd, open('board.lp', 'w') as fo:
        [fo.write('b('+ str(x) +','+ str(y) +','+ asp_str(v) +').\n')
         for y, line in enumerate(fd)
         for x, v in enumerate(line) if v not in ' .\n'
        ]

たとえば、ボード例の最初の行に指定されたアトムb(__b__oardの場合)は次のとおりです。

b(0,0,3).
b(2,0,3).
b(4,0,"W").
b(12,0,4).
b(16,0,4).
b(22,0,2).
b(24,0,"W").
b(28,0,4).
b(34,0,4).
b(38,0,4).

ここで、b(0,0,3)は、プレーヤー3が座標(0; 0)に居住していることを表すアトムです。

ASP解決

多くのオプションのスコアが実装されたASPコードがあります。

% input : b(X,Y,V) with X,Y the coordinates of the V value

domain(X,Y):- b(X,Y,_).
player("1";"2";"3";"4").

% neighbors of X,Y
neighbors(X,Y,((X-2,Y);(X+2,Y);((X-1;X+1),(Y-1;Y+1)))) :- domain(X,Y).
neighbors(X,Y,I,J):- neighbors(X,Y,(I,J)) ; domain(I,J).

% Player is next to X,Y iff has a settlement next to.
next(X,Y,P):- neighbors(X,Y,I,J) ; b(I,J,P).


% SCORES

% Core score : 3 point for each Castle "C" with at least one settlement next to.
score(core,P,S*3):- S={next(X,Y,P): b(X,Y,"C")} ; player(P).

% opt1: 1 point per settled row
score(opt1,P,S*1):- S=#count{row(Y): b(_,Y,P)} ; player(P).

% opt2: 2 point per settlement on the most self-populated row
% first, defines how many settlements have a player on each row
rowcount(P,Y,H):- H=#count{col(X): b(X,Y,P)} ; domain(_,Y) ; player(P).
score(opt2,P,S*2):- S=#max{T: rowcount(P,Y,T)} ; player(P).

% opt3: 1 point for each settlements next to a Water "W".
score(opt3,P,S):- S={b(X,Y,P): next(X,Y,"W")} ; player(P).

% opt4: 1 point for each settlements next to a Mountain "M".
score(opt4,P,S):- S={b(X,Y,P): next(X,Y,"M")} ; player(P).

% opt5:
%later…

% opt6:
%later…

% opt7: 1 point for each connected component of settlement
% first we need each coord X,Y to be orderable.
% then is defined path/5, that is true iff exists a connected component of settlement of player P
%   that links X,Y to I,J
% then is defined the connected component atom that give the smaller coords in each connected component
% then computing the score.
order(X,Y,Y+X*100):- domain(X,Y).
path(P,X,Y,I,J):- order(X,Y,O1) ; order(I,J,O2) ; O1<O2 ; % order
                  neighbors(X,Y,I,J) ; b(X,Y,P) ; b(I,J,P) ; player(P). % path iff next to
path(P,X,Y,I,J):- order(X,Y,O1) ; order(I,J,O2) ; O1<O2 ; % order
                  path(P,X,Y,K,L) ; neighbors(K,L,I,J) ; % path if path to next to
                  b(I,J,P) ; player(P).
concomp(P,X,Y):- path(P,X,Y,_,_) ; not path(P,_,_,X,Y). % at least two settlements in the connected component
concomp(P,X,Y):- 0 { path(P,X,Y,_,_) ; path(P,_,_,X,Y) } 0 ; board(X,Y,P) ; player(P). % concomp of only one settlements
score(opt7,P,S):- S=#count{concomp(P,X,Y): concomp(P,X,Y)} ; player(P).

% opt8: 0.5 point for each settlement in the bigger connected component
%later…


% total score:
score(total,P,C+S1+S2+S3):- score(core,P,C) ; score(opt1,P,S1) ; score(opt3,P,S2) ; score(opt7,P,S3).

#show. # show nothing but the others show statements
#show total_score(P,S): score(total,P,S).
%#show score/3. % scores details

このプログラムは次のコマンドで起動できます:

clingo board.lp golf.lp 

そして、唯一の解決策を見つけます(ポイントを配布する方法が1つしかないことの証明):

s(c,1,18) s(c,2,0) s(c,3,15) s(c,4,12) s(1,1,14) s(1,2,20) s(1,3,12) s(1,4,16) s(3,1,13) s(3,2,21) s(3,3,10) s(3,4,5) s(7,1,7) s(7,2,5) s(7,3,6) s(7,4,29) s(t,1,52) s(t,2,46) s(t,3,43) s(t,4,62)

s(7,3,6)はプレーヤー3がオプションのスコア7で6ポイントを獲得することを示し、s(t、4,62)はプレーヤー4が合計62ポイントを獲得することを示します(コア+ 1 + 3 + 7)。

簡単に解析できるので、派手なテーブルを作成できます!

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