Performance Pentago板上的赢家
对于那些不知道Pentago是什么的人来说,这对问题来说并不是那么重要,但只需说你有一个6x6的四象限板就足够了。每个玩家轮流放置一个棋子,然后旋转一个象限。当一名球员连续获得五分(在球员轮换阶段之前或之后)时,比赛获胜 我正在写一个算法来玩许多不同的随机Pentago游戏。然而,由于它是完全随机的,我看不出有什么好办法可以绕过检查,看看是否有人在回合的位置和旋转阶段之间获胜(否则,您可能会意外地旋转获胜的移动)。最后,我打算重写它,使其包含更多的策略,而不是完全随机的,但这是出于统计目的,所以随机性很好(事实上在某些方面非常有用) 不管怎么说,目前我正在用Matlab编程,一个空板看起来像这样Performance Pentago板上的赢家,performance,matlab,math,Performance,Matlab,Math,对于那些不知道Pentago是什么的人来说,这对问题来说并不是那么重要,但只需说你有一个6x6的四象限板就足够了。每个玩家轮流放置一个棋子,然后旋转一个象限。当一名球员连续获得五分(在球员轮换阶段之前或之后)时,比赛获胜 我正在写一个算法来玩许多不同的随机Pentago游戏。然而,由于它是完全随机的,我看不出有什么好办法可以绕过检查,看看是否有人在回合的位置和旋转阶段之间获胜(否则,您可能会意外地旋转获胜的移动)。最后,我打算重写它,使其包含更多的策略,而不是完全随机的,但这是出于统计目的,所以
eeeeee
eeeeee
eeeeee
eeeeee
eeeeee
eeeeee
随着游戏的进行,棋盘上会填满w
,以及b
。我检查获胜棋盘的方法实际上是通过对返回的“字符串”执行正则表达式检查,遍历每一列和每一行(以及每一条对角线),以查看是否有获胜棋盘
简言之,我的问题是:
是否有更有效的方法来确定Pentago棋盘的赢家?使用6x6数字数组表示游戏棋盘,0表示空位置,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 您现在可以通过调用
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!
>>x=[-10000;-10000;-10000;-10000;-10000;-10000;-10000;-10000;-10000;0000]
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)
ans=
白人赢了!
>>x=[-10000;-10000;-10000;-10000;-10000;-10000;-10000;-10000;10000;10000]
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)
ans=
白人赢了!
>>x=[11 11 10;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]
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)
ans=
黑人赢了!
>>x=[1 0 0 0;0 1 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 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
>>棋盘(x)
ans=
黑人赢了!
编辑:
我提出了两种解决方案:一种是基于卷积(使用函数)的解决方案,另一种是基于5个元素的所有可能字符串的强力索引(类似于)。这是:
卷积:
索引:
出于好奇,我想测量一下这些解决方案的速度和准确性。我创建了4个测试板:-1胜,无胜,1胜,双方都赢(平局)。然后,我为每个板运行每个解决方案10000次,并计时。结果如下:
| 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的解决方案无法检测抽签。尽管基于卷积的解决方案的代码最短,也最容易实现(我不必手动创建索引列表),但我上面给出的索引解决方案最终是最快的。此解决方案使用蛮力式方法将董事会与可能获胜的条件进行比较:
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
编辑:使用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
仅供参考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
使用预先计算的查找表,可以检测一个播放器在大约18条指令中是否有5条指令。以下例程将一名玩家的所有石头作为输入打包成64位整数:
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
}
内联象限(uint64\t状态,int q){
断言(016*q)&0xffff;
}
//确定一侧是否连续有5个
内联布尔元(侧边){
/*为了测试一个位置是否是一个给定球员的胜利,我们注意到
*有3*4*2+4+4=32种不同的方法可以在屏幕上获得一行5
*因此,64位int可以为每个可能的值存储一个2位字段
*方法。然后,我们预计算一个映射每个象限状态的查找表
*对获胜可能性的贡献。28种方式
*赢的概率发生在两个棋盘之间,赢的概率发生在4个棋盘之间,所以是一个总和
*还有一些无聊的c
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
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
}