得分游戏《王国建设者》


16

我想在这里尝试一种新的代码高尔夫形式。与奖金类似,并非必须完成挑战的所有部分,但每个答案都必须实现一定规模的子集(并且每个答案都必须实现一个核心)。因此,除了打高尔夫球之外,这项挑战还涉及选择一套可以完美搭配的功能。

规则

Kingdom Builder是一种棋盘游戏,在(尖顶)十六进制网格上播放。该评估板由四个(随机的)象限组成,每个象限具有10x10的十六进制单元(因此完整的评估板将为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 .

第二行将始终从第一行向右偏移。玩家14最多可以放置在空单元(以下一些规则,我们将迎接这一挑战忽略)每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

您的任务是为这样的董事会打分。始终使用一个核心分数,并且有8个可选分数,每个游戏选择3个在下文中,我将描述所有9个得分,并以上述设置为例,说明每个玩家将获得多少积分。

†在实际比赛中有10个得分,但我将遗漏2个得分,因为没人愿意打这些得分。

核心分数。玩家每获得一个毗邻的定居点,就会获得3分C分数示例: 18、0、15、12。

可选分数。

  1. 玩家获得至少一个结算的每一水平行可获得1分

    范例分数: 14、20、12、16。

  2. 对于每个玩家,找到他们大部分定居点所在的水平行(如果出现平局,则选择任意行)。玩家在该行上的每个结算获得2分

    分数示例: 14(第16行),8(第4、5或6行),28(第11行),10(第1行)。

  3. 玩家在靠近战场的每个定居点可获得1分W

    范例分数: 13、21、10、5。

  4. 一名玩家每获得一个结算将获得1点M积分。

    分数示例: 4、12、8、4。

  5. 计算每个象限中每个玩家的结算。每个象限中,拥有最多定居点的玩家每人获得12点,拥有第二数量的定居点的玩家每人获得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个可选分数,并根据核心分数和这三个分数为给定的棋盘评分。您的代码应列出4个分数。但是,在选择上有一个限制:我将分数分为3组,而您将实现每个组之一:

  • 实现1和2之一
  • 实现3,4,5和6之一
  • 实现7和8之一

您可以编写程序或函数,通过STDIN,命令行参数,提示符或函数参数进行输入。您可以返回结果或将其打印到STDOUT。

您可以为输入选择任何方便的1D或2D列表/字符串格式。您可能无法使用带有完整邻接信息的图。如果您需要灵感,可以在六角格上阅读一些不错的文章

您的输出也可以采用任何方便,明确的列表或字符串格式。

这是代码高尔夫球,因此最短的答案(以字节为单位)获胜。

进一步的假设

您可能会认为...

  • ...每个玩家至少有1个和解,每个玩家最多不超过40个和解。
  • ...每个象限包含一个城镇和两个城堡,或者两个城镇和一个城堡。
  • ...城镇和城堡之间的距离足够远,以至于没有一个定居点可以与其中两个相邻。

测试用例

仍然使用上面的面板,这是评分机制所有可能选择的个人分数:

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

是否有一个董事会,无论组合如何,总是有一位玩家获胜?
ThreeFx

@ThreeFx由于每个玩家的结算数量的下限是1,因此设置非常简单。;)但是对于每个玩家来说,相同数量的和解,我实际上并不知道。
马丁·恩德

Answers:


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]

处理六角格

由于我们处于十六进制网格中,因此我们必须以不同的方式对待邻居。如果我们使用传统的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。

lambda函数N采用一个坐标对,(row, col)并返回网格内单元的所有邻居。内在理解是通过从简单的base 3编码中提取上述偏移量来生成上述偏移量的,如果行为奇数,则增加第二个坐标,然后将偏移量添加到所讨论的单元格中以提供邻居。然后,外部理解会进行过滤,仅保留位于网格范围内的邻居。

不打高尔夫球

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

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 +求解器Clasp))实现。由于范例的限制,它不能直接将给定的板作为输出,因此必须对数据进行预处理(在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 

并且只会找到一个解决方案(它证明了只有一种分配点的方法):

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

易于解析以拥有精美的桌子!

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.