如何使用 Google OR 工具解决流游戏?

Posted

技术标签:

【中文标题】如何使用 Google OR 工具解决流游戏?【英文标题】:How to solve flow game using Google OR tools? 【发布时间】:2021-05-06 05:46:39 【问题描述】:

我尝试使用 google-OR 工具为流游戏制作求解器。

我为角落制定了一些规则,只包含角落管道,但除此之外,我不知道如何使管道相互连接,也不知道如何告诉模型制作连接到的管道彼此。

几个sn-p

pipe_types = 
0: " ",
1: "-",
2: "|",
3: "┗" ,
4: "┛" ,
5: "┓",
6: "┏",
7: "●" 

model = cp_model.CpModel()
filled_map = [[0,0,0,0],
             [0,0,7,0],
             [0,0,0,0],
             [0,7,0,0]]

mesh_size = int(np.sqrt(len(np.array(filled_map).flatten())))

target_map = [[model.NewIntVar(1, 6, 'column: %i' % i) for i in range(mesh_size)] for j in range(mesh_size)]

flow_map = init_map(model, target_map, filled_map)

for i in range(len(flow_map)):
    for j in range(len(flow_map[0])):
        
        # check if top or bottom side
        if (i == 0) or (i == len(flow_map)-1):
            model.Add(flow_map[i][j] != 2)
        
        # check if left or right side
        if (j == 0) or (j == len(flow_map[0])-1):
            model.Add(flow_map[i][j] != 1)
        
        # left up corner
        if (i == 0) & (j == 0):
            model.Add(flow_map[i][j] != 3)
            model.Add(flow_map[i][j] != 4)
            model.Add(flow_map[i][j] != 5)
        
        
        # right up corner
        if (i == 0) & (j == len(flow_map[0])-1):
            model.Add(flow_map[i][j] != 3)
            model.Add(flow_map[i][j] != 4)
            model.Add(flow_map[i][j] != 6)
        
        
        # left bottom corner
        if (i == len(flow_map)-1) & (j == 0):
            model.Add(flow_map[i][j] != 4)
            model.Add(flow_map[i][j] != 5)
            model.Add(flow_map[i][j] != 6)
        
        
        # right bottom corner
        if (i == len(flow_map)-1) & (j == len(flow_map[0])-1):
            model.Add(flow_map[i][j] != 3)
            model.Add(flow_map[i][j] != 5)
            model.Add(flow_map[i][j] != 6)
# Solving
status = solver.Solve(model)

res = []
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    for i in range(len(flow_map)):
        for j in range(len(flow_map[0])):
            res.append(solver.Value(flow_map[i][j]))
            print(solver.Value(flow_map[i][j]), end=" ")
        print()

这将导致网格中心的水平管道。稍后,我也必须弄清楚如何在此上添加颜色等。

是否有任何关于如何在 OR 工具上进行此操作的指针?

编辑 1:

根据 David Eisenstat 的回答,我可以找到解决方案。根据 JohanC 的回答可视化这个解决方案,我得到了这个结果。

我可以通过 google-OR 工具获取路径吗?

编辑 2:

使用来自"Hamiltonian" path using Python 的汉密尔顿路径 我可以生成一些正确的路径。

但是感觉很奇怪,因为 OR 工具已经计算了路径,我必须重新计算路径。从"Hamiltonian" path using Python 生成的路径并未显示所有可能的组合。如果我可以从 OR 工具中走出来,我认为这将是我的最大兴趣。

【问题讨论】:

请注意,我的着色代码假定路径中没有任何不必要的折叠(求解器仅生成该类型的路径,并允许空单元格)。如果需要额外的折叠,不仅每个单元格需要颜色,而且连接需要由数据结构表示。 【参考方案1】:

由于我没有使用 OR-tools 的经验,所以这里有一种使用 Z3 的方法。

初始棋盘由端点数字表示,每种颜色一个数字。这个想法有点类似于how Sudoku is represented。 板上的每个其他单元格将获得一个零值或一个数字。这个数字应该正好等于它的两个邻居。 初始端点应该只有一个具有颜色的邻居。
from z3 import Solver, Sum, Int, If, And, Or, sat

def plot_solution(S):
    import matplotlib.pyplot as plt

    ax = plt.gca()
    colors = plt.cm.tab10.colors
    for i in range(M):
        for j in range(N):
            if board[i][j] != 0:
                ax.scatter(j, i, s=500, color=colors[board[i][j]])
            if S[i][j] != 0:
                for k in range(M):
                    for l in range(N):
                        if abs(k - i) + abs(l - j) == 1 and S[i][j] == S[k][l]:
                            ax.plot([j, l], [i, k], color=colors[S[i][j]], lw=15)
    ax.set_ylim(M - 0.5, -0.5)
    ax.set_xlim(-0.5, N - 0.5)
    ax.set_aspect('equal')
    ax.set_facecolor('black')
    ax.set_yticks([i + 0.5 for i in range(M - 1)], minor=True)
    ax.set_xticks([j + 0.5 for j in range(N - 1)], minor=True)
    ax.grid(b=True, which='minor', color='white')
    ax.set_xticks([])
    ax.set_yticks([])
    ax.tick_params(axis='both', which='both', length=0)
    plt.show()

board = [[1, 0, 0, 2, 3],
         [0, 0, 0, 4, 0],
         [0, 0, 4, 0, 0],
         [0, 2, 3, 0, 5],
         [0, 1, 5, 0, 0]]
M = len(board)
N = len(board[0])
B = [[Int(f'B_i_j') for j in range(N)] for i in range(M)]
s = Solver()
s.add(([If(board[i][j] != 0, B[i][j] == board[i][j], And(B[i][j] >= 0, B[i][j] < 10))
        for j in range(N) for i in range(M)]))
for i in range(M):
    for j in range(N):
        same_neighs_ij = Sum([If(B[i][j] == B[k][l], 1, 0)
                              for k in range(M) for l in range(N) if abs(k - i) + abs(l - j) == 1])
        if board[i][j] != 0:
            s.add(same_neighs_ij == 1)
        else:
            s.add(Or(same_neighs_ij == 2, B[i][j] == 0))

if s.check() == sat:
    m = s.model()
    S = [[m[B[i][j]].as_long() for j in range(N)] for i in range(M)]
    print(S)
    plot_solution(S)

解决方案:

[[1, 2, 2, 2, 3],
 [1, 2, 4, 4, 3],
 [1, 2, 4, 3, 3],
 [1, 2, 3, 3, 5],
 [1, 1, 5, 5, 5]]

如 cmets 中所述,可能的要求是所有单元格都需要着色。这将需要更复杂的方法。这是一个这样的配置示例,上面的代码可以创建一个连接所有端点而不接触所有单元格的解决方案:

board = [[0, 1, 2, 0, 0, 0, 0],
         [1, 3, 4, 0, 3, 5, 0],
         [0, 0, 0, 0, 0, 0, 0],
         [0, 2, 0, 4, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0],
         [0, 5, 0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0]]

【讨论】:

如果橙色和绿色的下端点高出一个平方,那么据我了解这个问题,仍然会有一个独特的解决方案,但这个公式是不可行的。 在这种情况下,此代码将创建一个左下角两个单元格为空的解决方案。如果我理解正确,您希望橙色曲线掉头以填满整个电路板吗?您是否有更详细的规则描述的链接?是否知道配置何时具有独特的解决方案?我也在考虑一个允许例如的变体。 4 个相同颜色的端点,让求解器决定应该连接哪些对。 这是游戏:bigduckgames.com/flowfree 好的。似乎这些和其他版本都要求所有单元格都被填满。 cs.stackexchange.com 也有一个帖子,对更复杂的方法进行了一般性描述。【参考方案2】:

最好的方法可能是AddCircuit。此约束采用有向图,其中每个弧都用文字标记,并要求标记为 true 的弧形成一个子图,其中每个节点的入度和出度为 1,并且最多有一个不是自我的循环-环形。通过强制一条从头到尾的弧,我们可以使用这种约束类型来要求从头到尾只有一条路径。

文档有点差,所以这里有一个工作代码示例。我会把绘图部分留给你。

import collections
from ortools.sat.python import cp_model


def validate_board_and_count_colors(board):
    assert isinstance(board, list)
    assert all(isinstance(row, list) for row in board)
    assert len(set(map(len, board))) == 1
    colors = collections.Counter(square for row in board for square in row)
    del colors[0]
    assert all(count == 2 for count in colors.values())
    num_colors = len(colors)
    assert set(colors.keys()) == set(range(1, num_colors + 1))
    return num_colors


def main(board):
    num_colors = validate_board_and_count_colors(board)
    model = cp_model.CpModel()
    solution = [
        [square or model.NewIntVar(1, num_colors, "") for (j, square) in enumerate(row)]
        for (i, row) in enumerate(board)
    ]
    true = model.NewBoolVar("")
    model.AddBoolOr([true])
    for color in range(1, num_colors + 1):
        endpoints = []
        arcs = []
        for i, row in enumerate(board):
            for j, square in enumerate(row):
                if square == color:
                    endpoints.append((i, j))
                else:
                    arcs.append(((i, j), (i, j)))
                if i < len(board) - 1:
                    arcs.append(((i, j), (i + 1, j)))
                if j < len(row) - 1:
                    arcs.append(((i, j), (i, j + 1)))
        (i1, j1), (i2, j2) = endpoints
        k1 = i1 * len(row) + j1
        k2 = i2 * len(row) + j2
        arc_variables = [(k2, k1, true)]
        for (i1, j1), (i2, j2) in arcs:
            k1 = i1 * len(row) + j1
            k2 = i2 * len(row) + j2
            edge = model.NewBoolVar("")
            if k1 == k2:
                model.Add(solution[i1][j1] != color).OnlyEnforceIf(edge)
                arc_variables.append((k1, k1, edge))
            else:
                model.Add(solution[i1][j1] == color).OnlyEnforceIf(edge)
                model.Add(solution[i2][j2] == color).OnlyEnforceIf(edge)
                forward = model.NewBoolVar("")
                backward = model.NewBoolVar("")
                model.AddBoolOr([edge, forward.Not()])
                model.AddBoolOr([edge, backward.Not()])
                model.AddBoolOr([edge.Not(), forward, backward])
                model.AddBoolOr([forward.Not(), backward.Not()])
                arc_variables.append((k1, k2, forward))
                arc_variables.append((k2, k1, backward))
        model.AddCircuit(arc_variables)
    solver = cp_model.CpSolver()
    status = solver.Solve(model)
    if status == cp_model.OPTIMAL:
        for row in solution:
            print("".join(str(solver.Value(x)) for x in row))


if __name__ == "__main__":
    main(
        [
            [1, 0, 0, 2, 3],
            [0, 0, 0, 4, 0],
            [0, 0, 4, 0, 0],
            [0, 2, 3, 0, 5],
            [0, 1, 5, 0, 0],
        ]
    )

【讨论】:

谢谢,我想我必须制作多个多维数组来表示颜色和方向。所以我可以将问题表示为多维数组上的布尔值。你能给我一个关于如何打印每个可能的解决方案的样本吗? @DarwinHarianto SearchForAllSolutions 带有回调。见developers.google.com/optimization/cp/queens 谢谢。抱歉,还有 1 个问题。有没有办法在这个配置上放置一个“不可通过”的网格? @DarwinHarianto 当然,公式不依赖于邻接结构。最简单的方法可能是将solution 的相应条目设置为-1(或其他一些未使用的整数)。 @DarwinHarianto AddBoolOr([true]) 强制 true (注意小写)是一个真正的文字。我们用它来强制电路包含从路径末端到起点的弧线,这使得路径成为一个循环。我为k1 == k2 解释了edge。当k1 != k2 时,变量edge 等于forward or backward。如果我们在一个方向上使用边,变量forward 为真;如果我们在另一个方向使用边缘,backward 为真。布尔 OR 强制使用我为这三个变量描述的真值表。

以上是关于如何使用 Google OR 工具解决流游戏?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Google App Engine 推送 Android 应用程序中的事件流

解锁 2022 Google 游戏开发者峰会 | 打造高质量的游戏体验

绿色加载屏幕后登录 Google Play 消失。如何解决?

如何在 Unity 3D 中使用 google play 服务?

请求 google Ads Api 时如何解决 DEVELOPER_TOKEN_PROHIBITED 错误?

如何使用我的游戏进入 Google Play“多人游戏”部分?