无论如何要做这个谜团不是蛮力(你怎么蛮力)?

时间:2012-03-14 17:50:15

标签: algorithm puzzle

你正试图决定谁将赢得一场假设最佳选择的比赛。这个想法是有多个空格,_ _ _ _ _ _ _。玩家一上去并标记两个连续点。然后玩家2去做同样的事情。第一个不能去胜利的球员。因此,考虑到之前的董事会,这是一个可能性:

P1:x x _ _ _ _ _

P2:x x _ x x _ _

P1:x x _ x x x x

所以玩家2获胜。您将获得一个数组,其中0表示可用空间,1表示标记点。阵列可能已经标记了一些斑点。

我能想到的唯一方法是检查每一个动作,并检查每次动作是否在获胜后检查每一个可能的动作。我甚至无法弄清楚我会怎么做,但我希望有更好的方法。有什么想法吗?

4 个答案:

答案 0 :(得分:3)

你应该向后解决这样的问题。

考虑到所有可能的游戏状态,请通过并确定哪个是玩家1(W)的胜利,哪个是玩家2(L)的胜利。最初,你只知道没有人可以玩的州的答案。在这种情况下答案是

  • W如果没有两个连续点和总点数为4k
  • L如果没有两个连续点和总点数为4k+2

现在倒退了。如果玩家1可以移动到W的任何棋盘位置,请将其标记为W,如果有任何棋盘位置,玩家2可以移动到L,将其标记为L。同样,这不会立即标记所有位置,但迭代这将。迭代部分是这个

  • W如果有4k+2个点和两个连续点,当填充时,会给出标记为W的位置
  • L如果有4k个点和两个连续点,当填充时,会给出标记为L的位置

示例:考虑董事会_ _ _ _ _ _。从最后的状态进行评估

第二名球员:

X X X X X X (L - terminal)

玩家一动:

X X _ X X _ (W - terminal)
_ X X _ X X (W - terminal)
_ X X X X _ (W - terminal)
X X X X _ _ (L - must move to X X X X X X)
_ _ X X X X (L - must move to X X X X X X)

第二名球员:

X X _ _ _ _ (L - can move to X X X X _ _) 
_ X X _ _ _ (W - must move to _ X X _ X X or _ X X X X _)
_ _ X X _ _ (L - can move to X X X X _ _)
_ _ _ X X _ (W - must move to X X _ X X _ or _ X X X X _)
_ _ _ _ X X (L - can move to X X _ _ X X)

玩家一动:

_ _ _ _ _ _ (W - can move to _ X X _ _ _)

您可以递归编程,以便将每个位置评估为WL。让每个董事会职位P由长度为n的二进制向量表示,其中1表示采用的点,0表示开放点。这是doesPlayerOneWin的伪代码:

// STORE NUMBER OF ONES
int m = 0;
for (int i=0; i<n; ++i)
    m += P[i];
// LOOK FOR LEGAL MOVES
bool canMove = false;
for (int i=0; i<n-1; ++i)
    int[] newP = P;
    if (P[i]+P[i+1] == 0) {
        canMove = true;
        newP[i] = 1;
        newP[i+1] = 1;
        // PLAYER ONE CAN MOVE TO WIN
        if (m % 4 == 0 && doesPlayerOneWin(newP))
            return true;
        // PLAYER TWO CAN MOVE TO WIN
        if (m % 4 == 2 && !doesPlayerOneWin(newP))
            return false;
     }
}
// IF NO LEGAL MOVES, PLAYER TO MOVE WINS
if (!canMove && m % 4 == 0)
    return true;
else if (!canMove && m % 4 == 2)
    return false;
// OTHERWISE IF LOOP RUNS, PLAYER TO MOVE LOSES
if (m % 4 == 0)
    return false;
else
    return true;

答案 1 :(得分:2)

有一种理论允许解决此类游戏。

你的游戏是一个公正的游戏 - 两个玩家从每个位置都有相同的动作。国际象棋不公正,因为白色只能控制白人。当玩家没有移动时游戏结束,然后他输了。假设每场比赛都在有限的时间内结束。

你可以分析位置,并像PengOne建议的那样标记它们,L和W.失败的位置是所有可能的移动导致获胜位置,并且获胜位置是至少有一个移动位置的位置失败的位置。一种递归的,但定义明确的标签。当玩家没有移动时,所有连续的位置都会获胜(vacuous truth),所以这被标记为失败位置。

您可以计算更多信息,这对您有所帮助。将mex(A)称为不在A中的最小非负整数。例如,mex({0,1,5})= 2且mex({1,2,3})= 0。现在,您可以使用所有标签的mex标记每个位置。这也是一个递归的,定义明确的标签。如果一个位置的值为0,则该位置正在丢失。在此分类下,标记为0的位置正在丢失,但您对获胜位置的细粒度分类为1,2,....

这些数字可以让你计算两场比赛之和的价值。您可以通过独立播放来添加两个游戏。在移动过程中,您可以在第一场比赛或第二场比赛中进行比赛。游戏___X__X__中的位置实际上是三个游戏_______的总和。

Sprague-Grundy定理。 N个游戏的总和值a_1,a_2,...,a_N的值为a_1 x或a_2 xor ... a_N。因此,如果他们的值xor为0,则N场比赛的总和正在失去。

您的初始位置是K个独立游戏的总和,以X分隔。你需要找到每个空条___...__的Sprague-Grundy值,xor它们,如果结果是0,则返回。我想如果你试图计算前50个,你可能会得到一个如何计算值的提示它们。

由于我不喜欢用这个网站作为工作的替代品,所以我就此止步。希望你能够完成,如果你遇到困难,请提出问题。

答案 2 :(得分:1)

Assuming optimal choices实际上是一个最佳的策略。

为玩家“选择”移动的常用方法是使用min-max algorithm。 [它实际上是deep blue用来赢得卡斯帕罗夫的算法]。 Min-Max为每个玩家“选择”最佳移动,以便选择它将产生的移动。

min-max算法通常是启发式算法,并对游戏的每个状态进行评估,但是 - 在您的情况下,由于您正在寻找最佳移动和暴力解决方案,因此您需要在每个状态中使用min-max迭代直到游戏以赢家/输家结束,这是对董事会的唯一评价。

使用此方法,您可以使用起始状态的值确定哪个玩家将获胜 - 它将指示您认为是“我”的玩家是赢家还是输家。

请注意,对于这种特殊情况,只根据最终状态对电路板进行评估,min-max会衰减成与@ PengOne解决方案非常相似的东西。

答案 3 :(得分:1)

对于它的价值,这是一个用Python编写的蛮力解算器。有趣!您甚至可以使用N&gt;运行它。 2用于每回合放下更大的块。请注意,失败的玩家只能进行最左边的有效移动。

首先是输出。我以不同的棋盘尺寸(这里从2到16)运行游戏。

Size = 2
..
11
Player 1 Wins!

Size = 3
...
11.
Player 1 Wins!

Size = 4
....
.11.
Player 1 Wins!

Size = 5
.....
11...
1122.
Player 2 Wins!

Size = 6
......
..11..
2211..
221111
Player 1 Wins!

Size = 7
.......
11.....
1122...
112211.
Player 1 Wins!

Size = 8
........
.11.....
.1122...
.112211.
Player 1 Wins!

Size = 9
.........
11.......
1122.....
112211...
11221122.
Player 2 Wins!

Size = 10
..........
....11....
22..11....
22..1111..
22221111..
2222111111
Player 1 Wins!

Size = 11
...........
11.........
1122.......
112211.....
11221122...
1122112211.
Player 1 Wins!

Size = 12
............
.11.........
.1122.......
.112211.....
.11221122...
.1122112211.
Player 1 Wins!

Size = 13
.............
...11........
22.11........
22.11.11.....
22.11.1122...
22.11.112211.
Player 1 Wins!

Size = 14
..............
......11......
22....11......
22....1111....
2222..1111....
2222..111111..
222222111111..
22222211111111
Player 1 Wins!

Size = 15
...............
11.............
11...22........
1111.22........
1111.22.22.....
1111.22.2211...
1111.22.221122.
Player 2 Wins!

Size = 16
................
.....11.........
22...11.........
2211.11.........
2211.1122.......
2211.112211.....
2211.11221122...
2211.1122112211.
Player 1 Wins!

以下是代码:

N = 2 # number of pieces placed per turn

CACHE = {}

def compute_moves(board):
    gaps = [0] * len(board)
    previous = 0
    for i in range(len(board) - 1, -1, -1):
        if board[i]:
            previous = 0
            gaps[i] = 0
        else:
            previous += 1
            gaps[i] = previous
    return [i for i, gap in enumerate(gaps) if gap >= N]

def do_move(board, index, player):
    for i in range(N):
        board[index + i] = player

def undo_move(board, index):
    for i in range(N):
        board[index + i] = 0

def search(board):
    key = tuple(board)
    if key in CACHE:
        return CACHE[key]
    moves = compute_moves(board)
    for move in moves:
        do_move(board, move, 1)
        a, _ = search(board)
        undo_move(board, move)
        if not a:
            result = (True, move)
            break
    else:
        result = (False, moves[0] if moves else None)
    CACHE[key] = result
    return result

def play(board):
    player = 0
    while True:
        print ''.join(str(x or '.') for x in board)
        _, index = search(board)
        if index is None:
            break
        do_move(board, index, player + 1)
        player = int(not player)
    print 'Player %d Wins!' % (int(not player) + 1)

for size in range(2, 17):
    print 'Size = %d' % size
    board = [0] * size
    play(board)
    print