Pentago板上的获奖者

时间:2011-03-03 07:16:48

标签: performance matlab math

对于那些不知道Pentago是什么的人来说,问题并不是那么重要,但足以说你有一个带有四个象限的6x6电路板。每个玩家轮流放置一块然后旋转一个象限。当一个玩家连续五次(在玩家的旋转阶段之前或之后)赢得游戏。

我正在编写一个算法来播放许多不同的随机 Pentago游戏。然而,由于它是完全随机的,我认为没有好办法绕过检查是否有人在转弯的位置和旋转阶段之间获胜(否则,你可能会意外地旋转获胜的动作)。最后,我计划将其重写为涉及更多策略而不是完全随机的地方,但这是出于统计目的,因此随机性很好(事实上在某些方面非常有用)。

无论如何,目前我在Matlab编程,空板看起来像这样

eeeeee
eeeeee
eeeeee
eeeeee
eeeeee
eeeeee

随着游戏的进行,董事会将填充wb。我检查获胜板的方式是通过对每个列和每一行(以及每个对角线)进行迭代,通过对返回的“字符串”执行正则表达式检查来查看是否有赢家。

简而言之,我的问题是:

是否有更有效的方法来确定Pentago董事会的获胜者?

5 个答案:

答案 0 :(得分:3)

使用6x6数字数组表示游戏板,零表示空位,1表示黑色,-1表示白色。然后通过以下方式初始化电路板:

>> board = zeros(6, 6)

board =

     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0

要检查获胜的电路板,请使用在电路板阵列列上运行的SUM。减去列的MIN将解决列包含来自两个玩家的片段的情况。使用SUM和MIN函数的dimension参数对行执行相同的检查。使用DIAG创建三个候选对角线的数组,并用零填充两个较短的对角线。对此数组的列执行相同的检查。

function result = checkBoard(board)

result = 'No winner';
diagonals = [diag(board, 0) [diag(board, 1); 0] [diag(board, -1); 0]];
if any(sum(board) - min(board) == 5) ...
        || any(sum(board, 2) - min(board, [], 2) == 5) ...
        || any(sum(diagonals) - min(diagonals) == 5)
    result = 'Black wins!';
elseif any(sum(-board) - min(-board) == 5) ...
        || any(sum(-board, 2) - min(-board, [], 2) == 5) ...
        || any(sum(-diagonals) - min(-diagonals) == 5)
    result = 'White wins!';
end

现在,您可以通过拨打checkBoard

来查看电路板
>> x = [-1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; 0 0 0 0 0 0]

x =

    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
     0     0     0     0     0     0

>> checkBoard(x)

ans =

White wins!

>> x = [-1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; -1 0 0 0 0 0; 1 0 0 0 0 0]

x =

    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
    -1     0     0     0     0     0
     1     0     0     0     0     0

>> checkBoard(x)

ans =

White wins!

>> x = [1 1 1 1 1 0; 0 0 0 0 0 0; 0 0 0 0 0 0; 0 0 0 0 0 0; 0 0 0 0 0 0; 0 0 0 0 0 0]

x =

     1     1     1     1     1     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0
     0     0     0     0     0     0

>> checkBoard(x)

ans =

Black wins!

>> x = [1 0 0 0 0 0; 0 1 0 0 0 0; 0 0 1 0 0 0; 0 0 0 1 0 0; 0 0 0 0 1 0; 0 0 0 0 0 0]

x =

     1     0     0     0     0     0
     0     1     0     0     0     0
     0     0     1     0     0     0
     0     0     0     1     0     0
     0     0     0     0     1     0
     0     0     0     0     0     0

>> checkBoard(x)

ans =

Black wins!

答案 1 :(得分:3)

修改

我提出了两种解决方案:一种基于卷积(使用函数CONV2),另一种基于蛮力索引,用于所有可能的5个元素的字符串(类似于{{3} })。他们在这里:

卷积:

function winner = pentago_winner_conv(board)
%# Input should be a 6-by-6 matrix with:
%#   - 0 for an empty space
%#   - 1 for pieces from one player
%#   - -1 for pieces from the other player
%# The return value is 0 for no winner, -1 or 1 for the winning player,
%#   or 2 if there is a draw.

  metric = [conv2(board,ones(1,5),'same') ...     %# Check horizontal strings
            conv2(board,ones(5,1),'same') ...     %# Check vertical strings
            conv2(board,eye(5),'same') ...        %# Check diagonal strings
            conv2(board,fliplr(eye(5)),'same')];  %# Check anti-diagonal strings
  limits = [min(metric(:)) max(metric(:))];  %# Find the min and max values
  limits = fix(limits/5);                    %# Convert them to -1, 0, or 1
  if all(limits)
    winner = 2;            %# A draw condition
  else
    winner = sum(limits);  %# Find the winner, if any
  end

end

索引:

function winner = pentago_winner_brute(board)
%# Input should be a 6-by-6 matrix with:
%#   - 0 for an empty space
%#   - 1 for pieces from one player
%#   - -1 for pieces from the other player
%# The return value is 0 for no winner, -1 or 1 for the winning player,
%#   or 2 if there is a draw.

  index = reshape(1:36,6,6);
  index = [index(1:5,:).'; index(2:6,:).'; ...  %# Vertical string indices
           index(:,1:5); index(:,2:6); ...      %# Horizontal string indices
           1:7:29; 2:7:30; 7:7:35; 8:7:36; ...  %# Diagonal string indices
           5:5:25; 6:5:26; 11:5:31; 12:5:32];   %# Anti-diagonal string indices
  metric = sum(board(index),2);
  limits = [min(metric) max(metric)];  %# Find the min and max values
  limits = fix(limits/5);              %# Convert them to -1, 0, or 1
  if all(limits)
    winner = 2;            %# A draw condition
  else
    winner = sum(limits);  %# Find the winner, if any
  end

end

出于好奇,我认为我会衡量这些解决方案与b3's newer answer的速度和准确性。我创建了4个测试板:-1胜,没有赢家,1胜,都赢了(抽奖)。然后我为每个板运行每个解决方案10,000次并计时。结果如下:

               |   Calculated winner
---------------+-----------------------
convolution    |  -1     0     1     2
indexing       |  -1     0     1     2
b3 solution    |  -1     0     1    -1

               |   Running time for 10,000x (seconds)
---------------+---------------------------------------
convolution    |  0.4863    0.5305    0.5248    0.4787
indexing       |  0.1706    0.1770    0.1755    0.1889
b3 solution    |  0.6607    1.3958    1.4223    0.7507

请注意,b3的解决方案无法检测到平局。尽管基于卷积的解决方案的代码是最短且最容易实现的(我不必手动创建索引列表),但我上面给出的索引解决方案最终是最快的。

答案 2 :(得分:2)

使用预先计算的查找表,可以检测一个玩家是否在大约18条指令中连续五行。以下例程将一个玩家的所有石头打包成64位int:

inline quadrant_t quadrant(uint64_t state, int q) {
  assert(0<=q && q<4);
  return (state>>16*q)&0xffff;
}

// Determine if one side has 5 in a row
inline bool won(side_t side) {
  /* To test whether a position is a win for a given player, we note that
   * there are 3*4*2+4+4 = 32 different ways of getting 5 in a row on the
   * board. Thus, a 64-bit int can store a 2 bit field for each possible
   * method. We then precompute a lookup table mapping each quadrant state
   * to the number of win-possibilities it contributes to. 28 of the ways
   * of winning occur between two boards, and 4 occur between 4, so a sum
   * and a few bit twiddling checks are sufficient to test whether 5 in a
   * row exists. See helper for the precomputation code. */
  uint64_t c = win_contributions[0][quadrant(side,0)]
             + win_contributions[1][quadrant(side,1)]
             + win_contributions[2][quadrant(side,2)]
             + win_contributions[3][quadrant(side,3)];
  return c&(c>>1)&0x55 // The first four ways of winning require contributions from three quadrants
      || c&(0xaaaaaaaaaaaaaaaa<<8); // The remaining 28 ways require contributions from only two
}

完整的源代码在这里:

https://github.com/girving/pentago/blob/9c9dedbb8dd3a5cf8dd778d28f351419126bbb7c/engine.cpp#L94

答案 3 :(得分:1)

编辑:使用Gnovices新算法更新(总共3个吧?你使用的是filter(),现在你正在使用conv2())。

新号码:

                     Apus     B3    B3   Gnovice 
                             Old   New   Orig  Conv Index
Player One Wins:        97   197    91    97    97    97 
Player Two Wins:       102   181   114   102   118   118 
Both Players Win:       16     0     0    16     0     0 
Neither Player Win:    785   622   795   785   785   785 
Execution Time:      0.081 0.037 0.144 0.317 0.068 0.036

这太有趣了,不能抗拒。如果没有B3和Gnovice的代码,我的错误就会充满。据我所知,Gnovice的代码似乎有100%的准确性。 B3提交了两个功能,如果连续4个,一个空格,还有一个,则第一个错误地授予胜利者。 B3的第二次进入未​​能从右上角到左下角检测对角线上的获胜者。 B3也没有考虑到两名球员都有获胜位置的情况。 (据我了解游戏,象限轮换可能会让两位玩家立刻获胜?)

这是1000个随机电路板的阵容。

                     Apus     B3    B3   Gnovice
Player One Wins:       106   207   104   106 
Player Two Wins:       103   180   105   103 
Both Players Win:        6     0     0     6 
Neither Player Win:    785   613   791   785 
Execution Time:      0.082 0.037 0.146 0.322 

Gnovice是第一个达到100%准确度的人,但是执行时间最长。 B3可能会修改他的代码来修复错误,然后拥有最快的代码。我可能会得到一个生命,而不是相互竞争连接4代码。

这是我的代码:

function winner = testboard_apus(board)
answersheet1 = true(6,6);
answersheet1(6,:) = false(1,6);
answersheet2 = true(6,6);
answersheet2(1,:) = false(1,6);
winner = 0;
for player = 1:2
    if      any(sum((board==player) & answersheet1)==5)  ||...
            any(sum((board==player) & answersheet2)==5)  ||...
            any(sum((board'==player) & answersheet1)==5) ||...
            any(sum((board'==player) & answersheet2)==5) ||...
            all(diag(board(1:5,1:5))==player) ||...
            all(diag(board(2:6,2:6))==player) ||...
            all(diag(board(1:5,2:6))==player) ||...
            all(diag(board(2:6,1:5))==player) ||...
            all(diag(board(1:5,5:-1:1))==player) ||...
            all(diag(board(2:6,6:-1:2))==player) ||...
            all(diag(board(1:5,6:-1:2))==player) ||...
            all(diag(board(2:6,5:-1:1))==player)

        winner = winner + player;
    end
end
end

这是驱动程序代码

function testboard_wrapper

total = zeros(4,1);
agree = false(1000,1);
winner = zeros(1000,4);
for i = 1:1000
    board = floor(rand(6)*3);

    t(1) = tic;
    winner(i,1) = testboard_apus(board);
    total(1) = total(1)+toc(t(1));

    board2 = board;
    board2(board2==2) = -1;

    t(2) = tic;
    winner(i,2) = testboard_b3(board2);
    total(2) = total(2)+toc(t(2));

    t(3) = tic;
    winner(i,3) = testboard_b3_2nd(board2);
    total(3) = total(3)+toc(t(3));

    t(4) = tic;
    winner(i,4) = testboard_gnovice(board2);
    total(4) = total(4)+toc(t(4));

    agree(i) = all(winner(i,:)==0) || all(winner(i,:)==1) || all(winner(i,:)==2) ||all(winner(i,:)==3);

end

fprintf('                     Apus     B3    B3   Gnovice\n')
fprintf('Player One Wins:     %5i %5i %5i %5i \n',sum(winner==1))
fprintf('Player Two Wins:     %5i %5i %5i %5i \n',sum(winner==2))
fprintf('Both Players Win:    %5i %5i %5i %5i \n',sum(winner==3))
fprintf('Neither Player Win:  %5i %5i %5i %5i \n',sum(winner==0))
fprintf('Execution Time:      %1.3f %1.3f %1.3f %1.3f \n',total)
end

FYI B3这是您的代码无法检测到的示例板。

 0     0     0     0     0     2
 1     0     1     0     2     0
 1     2     1     2     1     0
 1     1     2     2     1     0
 0     2     2     0     0     2
 1     1     0     0     1     1

以及

 0     2     2     2     0     2
 1     2     1     2     1     1
 0     0     2     0     0     2
 2     0     1     2     0     0
 2     0     1     0     2     0
 1     0     1     1     1     2

答案 4 :(得分:0)

此解决方案使用强力 - 风格方法将电路板与可能的获胜条件进行比较:

function winner = pentago_winner(board)

if connectFive(-1)
    winner = -1;
elseif connectFive(1)
    winner = 1;
else
    winner = 0;
end

    function result = connectFive(player)
        result = find([all(~(board(1:5,:) - ones(5,6) * player)) ...                            % Check top 5 rows
            all(~(board(2:6,:) - ones(5,6) * player)) ...                                       % Check bottom 5 rows
            all(~(board(:,1:5)' - ones(5,6) * player)) ...                                      % Check left 5 columns
            all(~(board(:,2:6)' - ones(5,6) * player)) ...                                      % Check right 5 columns
            all(~([diag(board, 1) diag(board, -1)] - ones(5, 2) * player)) ...                  % Check minor diagonals
            all(~([diag(fliplr(board), 1) diag(fliplr(board), -1)] - ones(5, 2) * player)) ...  % Check minor anti-diagonals
            all(~(board(1:7:29)' - ones(5, 1) * player)) ...                                    % Check first 5 elements of major diagonal
            all(~(board(6:5:29)' - ones(5, 1) * player)) ...                                    % Check last 5 elements of major diagonal
            all(~(board(5:5:25)' - ones(5, 1) * player)) ...                                    % Check first 5 elements of major anti-diagonal
            all(~(board(12:5:32)' - ones(5, 1) * player))], 1) * player;                        % Check last 5 elements of major anti-diagonal
    end

end