一个古老的数学游戏,很早就知道了,但一直编程没编正确。今天终于正确了。
有1、2、3、4、5、6这6种牌,每种牌有3张。开始总点和记为24,然后两个人轮流选一张牌,然后从总点和里减去牌面值,直到总点和非正。谁取完牌后,总点和为0,则胜;若为负,则败。
例如:
24
A:3
21
B:6
15
A:1
14
B:2
12
A:5
7
B:3
4
A:4
A胜
问先取是否有必胜策略,或后取是否有?
下面是一段人和电脑玩的代码
#include <cstdio> #include <cstdlib> #include <ctime> const int N = 6; const int initV = 3; int A[N]; int maxDepth=0; int lastChoice= -1; void status() { printf("Remains : "); for (int i=0;i<N;++i) printf("%3d %2d | ", i+1, A[i]); printf("/n"); } // play 返回和为 sum 时的最佳策略和最大概率 double play(int sum, int depth) { if (depth>maxDepth) { maxDepth = depth; //status(); } if (sum==0) return 0.0; if (sum<0) return 1.0; double minHope=1; for (int i=0;i<N;++i) if (A[i]>0) { --A[i]; double hope = play(sum-1-i, depth+1); ++A[i]; if (hope<minHope) { minHope = hope; if (depth==0) lastChoice = i; if (minHope< 1E-6) break; } } return 1-minHope; } int main() { srand((unsigned)time(0)); for (int i=0;i<N;++i) A[i] = initV; int humFirst = 0; printf("Hum first(0,1) ? "); scanf("%d", &humFirst); humFirst %= 2; int sum = 24; while(sum>0) { printf("sum=%d /n", sum); int c=0; if (humFirst>0) { status(); while(!(c>=1 && c<=N && A[c-1]>0)) { if (c>=1 && c<=N) printf("%d leaves nothing!/n", c); printf("choose (1..%d) : ", N); scanf("%d", &c); } --c; } else { play(sum, 0); c = lastChoice; while(!(c>=0 && c<N && A[c]>0)) { c = rand()%N; } printf("Computer choose %d/n", c+1); } sum -= (c+1); --A[c]; humFirst = (humFirst+1)%2; } if ((sum==0) ^ (humFirst==0)) printf("YOU LOSE!/n"); else printf("YOU WIN!/n"); return 0; }
下面是加强的版本,表征胜败不是用1,0简单表达,而是用了-1.0~1.0之间的实数,符号表示胜败,绝对值大小表示胜或败的强度,这样可以让电脑选择败强度低的局面(复杂,人能获胜的支路相对整个博弈树来要小,需要仔细选择),这样人可能容易出错。
另外在选择方案时,加入了一点随机性,以免每次博弈都是同样选择顺序(当,然胜败应该相同)。
#include <cstdio> #include <cstdlib> #include <ctime> #include <cmath> const int N = 6; const int initV = 3; int A[N]; int maxDepth=0; int lastChoice= -1; void status() { printf("Remains : "); for (int i=0;i<N;++i) printf("%3d %2d | ", i+1, A[i]); printf("/n"); } // play 返回和为 sum 时的最佳策略和最大概率 double play(int sum, int depth) { if (depth>maxDepth) { maxDepth = depth; //status(); } if (sum==0) return -1.0; if (sum<0) return 1.0; const double T = 0.001; double minHope=1.0; double negHope=0.0; double posHope=0.0; for (int i=0;i<N;++i) if (A[i]>0) { --A[i]; double hope = play(sum-1-i, depth+1); ++A[i]; if (hope>0) posHope += hope; else negHope += hope; if (depth==0) { //printf("%d hope = %lf/n", i+1, hope); double chance=0.0; if (minHope>0 && hope<0) { minHope = hope; lastChoice = i; } else if (hope-minHope<-0.01 || ( fabs(hope-minHope)<0.01 && (chance=static_cast<double>(rand()%1024)/1024) > 0.6) ) { minHope = hope; lastChoice = i; } //printf("minhope %lf dhope %lf chance %lf/n", minHope, hope-minHope,chance); } } if (negHope < 0.0 && posHope> 0.0) { return -negHope/(posHope-negHope+1E-6); } else{ return -posHope/(posHope-negHope+1E-6); } } int main(int argc, char* argv[]) { srand((unsigned)time(0)); for(int i=rand()%13 + 3;i>0; --i) rand(); for (int i=0;i<N;++i) A[i] = initV; int humFirst = 0; printf("Hum first(0,1) ? "); scanf("%d", &humFirst); humFirst %= 2; int sum = 24; if (argc==2) { sscanf(argv[1],"%d", &sum); } while(sum>0) { printf("sum=%d /n", sum); int c=0; if (humFirst>0) { status(); while(!(c>=1 && c<=N && A[c-1]>0)) { if (c>=1 && c<=N) printf("%d leaves nothing!/n", c); printf("choose (1..%d) : ", N); scanf("%d", &c); } --c; } else { play(sum, 0); c = lastChoice; while(!(c>=0 && c<N && A[c]>0)) { c = rand()%N; } printf("Computer choose %d/n", c+1); } sum -= (c+1); --A[c]; humFirst = (humFirst+1)%2; } if ((sum==0) ^ (humFirst==0)) printf("YOU LOSE!/n"); else printf("YOU WIN!/n"); return 0; }