井字棋算法
Posted send-off-a-friend
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了井字棋算法相关的知识,希望对你有一定的参考价值。
井字棋算法
绪言
说到井字棋,也许都想起了自己小时候的时光吧。
井字棋其实很简单,只要你去认真分析它,你就能明白什么叫做“先手不输,后手不赢”。
算法
V1
随机算法。
扫描全局找出所有空位。
随机一个空位,下子。
V2
先看看自己有没有已经构成两个一空的
即
O O
X
X X O
(只是打个比方)
标红的地方都是
有的话就下子
如果没有再看看敌人是否已经构成了两子一空。
如果敌人构成了则要将其破坏(下子)
如果敌人也没有,就走V1
V3
首先搭载V2
在判断完没有两子一空之后,就可以开始动笔了。
大量实验证明,先手下角赢的概率最大
(伪)
这是为什么呢?
因为这是一个套路:
如果你的棋盘是这样的
先下角
对手走4或2
那我就继续走一个邻角
这时候2P就必须走4否则2P就输了
这时候,我只需要继续走另一个邻角就好
这时,789和159都构成了两子一空,无论对方走哪里,只要补齐另一个空就行了
要是先手走角,后手也走角呢?
那我就继续走角
这是2P只能走4
接着我继续走剩下的一个角9
2P又陷入了僵局
那要是先手走角,后手走中间呢?
那先手就走对角9(走邻角7或3会平局,自己试试咯)
此时,后手可以走角37或者边2468
如果后手走角3
那先手继续走角即可,如图,147,789都构成了两空一子
如果后手走边4 那先手根据V2只能走6
后手根据V2只能走3
先手根据V2只能走7
后手根据V2只能走8
结果就只有平局的份了……
所以,先手走边时,后手唯一造成平局的机会就是
先走中心,再走边
Code
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cstring> 5 #include<cmath> 6 #include<set> 7 #include<queue> 8 #include<vector> 9 #include<windows.h> 10 #include<sstream> 11 #include<ctime> 12 #include<conio.h> 13 #define IL inline 14 #define re register 15 #define LL long long 16 using namespace std; 17 /* 18 sssss sssss sssss 19 ss7ss ss8ss ss9ss 20 sssss sssss sssss 21 22 sssss sssss sssss 23 ss4ss ss5ss ss6ss 24 sssss sssss sssss 25 26 sssss sssss sssss 27 ss1ss ss2ss ss3ss 28 sssss sssss sssss 29 s==space 30 */ 31 32 int mode=0;//0 2p 1 easy 2 mid 3 hard 33 int diff; 34 int map[3][3];//0 空 1 1P 2 2P||AI 35 int stats[3];//0 ping 1 1P 2 2P 36 void set_stats(){ 37 stringstream ss; 38 ss<<"title total:"<<stats[0]+stats[1]+stats[2]<<";stats:1P:P:2P="<<stats[1]<<":"<<stats[0]<<":"<<stats[2]<<"="<<stats[1]*1.0/stats[0]<<":1:"<<stats[2]*1.0/stats[0]<<":"<<stats[1]*1.0/stats[2]; 39 system(ss.str().c_str()); 40 if(stats[0]+stats[1]+stats[2]==10000) system("pause"); 41 } 42 struct xy{ 43 int x; 44 int y; 45 int num(){ 46 return (2-x)*3+y+1;//7-3x+y=num() 3x-y=7-num() 3x=7-num()+y 47 } 48 bool operator!=(xy z){ 49 if(x==z.x&&y==z.y) return 0; 50 return 1; 51 } 52 bool operator==(xy z){ 53 if(x==z.x&&y==z.y) return 1; 54 return 0; 55 } 56 int value(){ 57 return map[x][y]; 58 } 59 xy eof(){ 60 return {-1,-1}; 61 } 62 xy up(){ 63 xy ans=*this; 64 if(x<2&&x>=0) ans.x++; 65 else ans={-1,-1}; 66 return ans; 67 } 68 xy down(){ 69 xy ans=*this; 70 if(x>0&&x<3) ans.x--; 71 else ans=eof(); 72 return ans; 73 } 74 xy left(){ 75 xy ans=*this; 76 if(y>0&&y<3) ans.y--; 77 else ans=eof(); 78 return ans; 79 } 80 xy right(){ 81 xy ans=*this; 82 if(y<2&&y>=0) ans.y++; 83 else ans=eof(); 84 return ans; 85 } 86 xy edge(int w){ 87 if(*this==eof()) return eof(); 88 xy ans=*this; 89 if(w<3) ans=ans.up(); 90 if(w>=2&&w<=4) ans=ans.right(); 91 if(w>=4&&w<=6) ans=ans.down(); 92 if((w>=6&&w<8)||w==0) ans=ans.left(); 93 return ans; 94 } 95 int point(){ 96 if(num()==5)return 0; 97 if(num()%2) return 2; 98 return 1; 99 } 100 }; 101 xy turn(int num){ 102 xy ans; 103 ans.y=(num-1)%3; 104 ans.x=(7-num+ans.y)/3; 105 return ans; 106 } 107 xy eof(){ 108 return {-1,-1}; 109 } 110 unsigned short lb,lf; 111 const int A=10,B=11,C=12,D=13,E=14,F=15; 112 void SetColor(unsigned short BackGroundColor,unsigned short ForeColor) 113 { 114 HANDLE hCon=GetStdHandle(STD_OUTPUT_HANDLE); 115 SetConsoleTextAttribute(hCon,(ForeColor%16)|(BackGroundColor%16*16)); 116 } 117 int lx,ly; 118 void getxy() 119 { 120 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 121 CONSOLE_SCREEN_BUFFER_INFO csbi; 122 GetConsoleScreenBufferInfo(hConsole, &csbi); 123 lx=csbi.dwCursorPosition.X,ly=csbi.dwCursorPosition.Y; 124 } 125 void gotoxy(int x, int y) 126 { 127 COORD pos; 128 pos.X = x - 1; 129 pos.Y = y - 1; 130 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos); 131 } 132 IL void backxy() 133 { 134 COORD pos; 135 pos.X = lx; 136 pos.Y = ly; 137 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos); 138 } 139 int main(); 140 void init(){ 141 for(int i=1;i<10;i++) map[turn(i).x][turn(i).y]=0; 142 system("cls"); 143 cout<<"请输入模式: 【0】双人对战 【1】人机easy 【2】人机mid 【3】人机hard 【4】神仙打架 【5】学习神仙 "; 144 do{ 145 mode=getch()-‘0‘; 146 }while(mode<0||mode>5); 147 diff=max(min(mode,3),1); 148 cout<<"游戏中 按下r键可以重新选择 "; 149 system("pause"); 150 } 151 const string cls[9]={"sssss sssss sssss ","ss7ss ss8ss ss9ss ","sssss sssss sssss ","sssss sssss sssss ","ss4ss ss5ss ss6ss ","sssss sssss sssss ","sssss sssss sssss ","ss1ss ss2ss ss3ss ","sssss sssss sssss "}; 152 void show(){ 153 system("cls"); 154 for(int i=0;i<9;i++) cout<<cls[i]; 155 memset(map,sizeof(map),0); 156 } 157 int fp;//先手 158 vector<int>step; 159 xy algo(int p,int m=diff){//1 随机 2 找2排2 3 特殊 160 int dr=p==1?2:1; 161 vector<xy>line; 162 if(m>1){ 163 //找出自己二连 164 for(int n=1;n<10;n++) 165 for(int k=0;k<8;k++){ 166 if(turn(n).value()==0){ 167 if(turn(n).edge(k)!=eof()&&turn(n).edge(k).value()==p&&turn(n).edge(k).edge(k)!=eof()&&turn(n).edge(k).edge(k).value()==p) return turn(n); 168 if(turn(n).edge(k)!=eof()&&turn(n).edge(k).value()==p&&turn(n).edge((k+4)%8)!=eof()&&turn(n).edge((k+4)%8).value()==p) return turn(n); 169 } 170 } 171 //排除敌人二连 172 for(int n=1;n<10;n++) 173 for(int k=0;k<8;k++){ 174 if(turn(n).value()==0){ 175 if(turn(n).edge(k)!=eof()&&turn(n).edge(k).value()==dr&&turn(n).edge(k).edge(k)!=eof()&&turn(n).edge(k).edge(k).value()==dr) return turn(n); 176 if(turn(n).edge(k)!=eof()&&turn(n).edge(k).value()==dr&&turn(n).edge((k+4)%8)!=eof()&&turn(n).edge((k+4)%8).value()==dr) return turn(n); 177 } 178 } 179 if(m==3||(m==2&&rand()%10<4)){ 180 /* 181 第一步走角 无明显差异 182 第二步走角 无明显差异 183 优先走角 优 1.3427:1:0.8555 1.5694 184 除前两步走角 优 1.3582:1:0.8667 1.5670 185 优先走中心 优 2.0000:8:1.0000 2.0000 186 中心->角 优 0.4090:1:0.1468 2.7849 187 角->中心 优 1.4097:1:0.8786 1.6044 188 */ 189 if(step.size()+1==2)//如果是第二步 190 { 191 if(turn(step[0]).point()==2) return turn(5);//先手走角就走中间 192 } 193 if(step.size()+1==3)//如果是第三步 194 { 195 if(turn(step[0]).point()==2)//并且第一步走的是角 196 { 197 if(turn(step[1]).point()==0)//如果后手走中心 198 { 199 for(int k=0;k<8;k+=2) if(turn(step[0]).edge(k).edge(k)!=eof()) return turn(step[0]).edge(k).edge(k); 200 } 201 } 202 } 203 if(step.size()+1==4)//如果是第四步 204 { 205 if(turn(step[0]).point()==2&&turn(step[1]).point()==0&&turn(step[2]).point()==2&&step[0]+step[2]==10)//1角2中3对角则4边 206 { 207 if(!turn(2).value()) line.push_back(turn(2)); 208 if(!turn(4).value()) line.push_back(turn(4)); 209 if(!turn(6).value()) line.push_back(turn(6)); 210 if(!turn(8).value()) line.push_back(turn(8)); 211 random_shuffle(line.begin(),line.end()); 212 if(!line.empty()) return line.front(); 213 } 214 } 215 216 { 217 //角 218 if(!turn(1).value()) line.push_back(turn(1)); 219 if(!turn(3).value()) line.push_back(turn(3)); 220 if(!turn(7).value()) line.push_back(turn(7)); 221 if(!turn(9).value()) line.push_back(turn(9)); 222 random_shuffle(line.begin(),line.end()); 223 if(!line.empty()) return line.front(); 224 } 225 { 226 //中心 227 if(!turn(5).value()) return turn(5); 228 } 229 { 230 if(!turn(2).value()) line.push_back(turn(2)); 231 if(!turn(4).value()) line.push_back(turn(4)); 232 if(!turn(6).value()) line.push_back(turn(6)); 233 if(!turn(8).value()) line.push_back(turn(8)); 234 random_shuffle(line.begin(),line.end()); 235 if(!line.empty()) return line.front(); 236 } 237 238 //找到最优 239 } 240 } 241 //随机 242 for(int x=0;x<3;x++) 243 for(int y=0;y<3;y++) 244 if(map[x][y]==0) line.push_back({x,y}); 245 random_shuffle(line.begin(),line.end()); 246 return line.front(); 247 } 248 249 void edit(xy p,int c){ 250 // return;//////////////////////////////////////////////////////////////////////////// 251 getxy(); 252 if(c==1) SetColor(7,A); 253 if(c==2) SetColor(A,7); 254 if(c==3) SetColor(F,C); 255 for(int i=p.x*3;i<p.x*3+3;i++) 256 for(int j=p.y*6;j<p.y*6+5;j++) 257 { 258 gotoxy(j+1,i+1+p.x);cout<<cls[i][j]; 259 } 260 backxy(); 261 SetColor(0,7); 262 } 263 264 void win(int n,int k,int p){ 265 step.clear(); 266 gotoxy(1,13); 267 if(p==-1){ 268 cout<<"平局!"; 269 stats[0]++; 270 } 271 if(p==1){ 272 edit(turn(n),3); 273 edit(turn(n).edge(k),3); 274 edit(turn(n).edge(k).edge(k),3); 275 cout<<"1P wins!"; 276 stats[1]++; 277 } 278 if(p==2){ 279 edit(turn(n),3); 280 edit(turn(n).edge(k),3); 281 edit(turn(n).edge(k).edge(k),3); 282 cout<<"2P wins!"; 283 stats[2]++; 284 } 285 set_stats(); 286 if(mode!=4) system("pause"); 287 system("cls"); 288 for(int i=1;i<10;i++) map[turn(i).x][turn(i).y]=0; 289 show(); 290 } 291 292 int check(){ 293 for(int n=1;n<10;n++) 294 for(int k=0;k<8;k++) 295 if(turn(n).value()) 296 if(turn(n).edge(k)!=eof()&&turn(n).edge(k).edge(k)!=eof()) 297 if(turn(n).value()==turn(n).edge(k).value()&&turn(n).edge(k).value()==turn(n).edge(k).edge(k).value()) 298 { 299 win(n,k,turn(n).value()); 300 return turn(n).value(); 301 } 302 bool flag=1; 303 for(int n=1;n<10;n++){ 304 if(turn(n).value()==0){ 305 flag=0; 306 break; 307 } 308 } 309 if(flag){ 310 win(-1,-1,-1); 311 return 3; 312 } 313 return 0; 314 } 315 316 void first(){ 317 if(mode==4||mode==5){ 318 gotoxy(1,13); 319 cout<<"AI:"; 320 xy n; 321 if(mode!=4) Sleep((5-mode)*100); 322 if(mode==5) Sleep(1000); 323 n=algo(1); 324 step.push_back(n.num()); 325 edit(n,1); 326 map[n.x][n.y]=1; 327 } 328 else{ 329 int n; 330 do{ 331 gotoxy(1,13); 332 cout<<"1P:"; 333 n=getch(); 334 if(n==‘r‘){ 335 main(); 336 exit(0); 337 } 338 if(n==‘a‘){ 339 n=algo(1,3).num()+‘0‘; 340 } 341 if(n==‘d‘){ 342 n=algo(1,2).num()+‘0‘; 343 } 344 n-=‘0‘; 345 }while(n<1||n>9||map[turn(n).x][turn(n).y]!=0); 346 step.push_back(n); 347 edit(turn(n),1); 348 map[turn(n).x][turn(n).y]=1; 349 } 350 351 } 352 void second(){ 353 if(mode!=0){ 354 gotoxy(1,13); 355 cout<<"AI:"; 356 xy n; 357 if(mode!=4) Sleep((5-mode)*100); 358 if(mode==5) Sleep(1000); 359 n=algo(2);////////////////////////////可调整AI2的难度 360 step.push_back(n.num()); 361 edit(n,2); 362 map[n.x][n.y]=2; 363 } 364 else{ 365 int n; 366 gotoxy(1,13); 367 cout<<"2P:"; 368 do{ 369 n=getch(); 370 if(n==‘r‘){ 371 main(); 372 exit(0); 373 } 374 if(n==‘a‘){ 375 n=algo(1,3).num()+‘0‘; 376 } 377 if(n==‘d‘){ 378 n=algo(1,2).num()+‘0‘; 379 } 380 n-=‘0‘; 381 }while(n<1||n>9||map[turn(n).x][turn(n).y]!=0); 382 step.push_back(n); 383 edit(turn(n),2); 384 map[turn(n).x][turn(n).y]=2; 385 } 386 387 388 } 389 390 void about() 391 { 392 system("cls"); 393 cout<<"井字棋 最终版 code by SOAF 没有使用搜索算法而先把所有情况列了出来 这是一个先手不会输,后手不会赢的游戏。 先手所能做的就是尝试赢,后手所能做的就是尝试不输。 新手建议尝试easy上手,大佬也别急着玩hard哦! 祝您游戏愉快! "; 394 system("pause"); 395 system("cls"); 396 } 397 398 int main() 399 { 400 srand(clock()); 401 about(); 402 init(); 403 show(); 404 set_stats(); 405 while(true){ 406 fp=1; 407 while(true){ 408 first(); 409 if(check()==1){ 410 break; 411 } 412 second(); 413 if(check()==2){ 414 break; 415 } 416 } 417 if(kbhit()){ 418 if(getch()==‘r‘) main(),exit(0); 419 } 420 fp=2; 421 while(true){ 422 second(); 423 if(check()){ 424 break; 425 } 426 first(); 427 if(check()){ 428 break; 429 } 430 } 431 } 432 return 0; 433 }
代码较长但功能齐全。不需要的请适当删减!
End
当你如此认真的分析完井字棋之后,你就会发现,这真是一个无聊的游戏啊……
以上是关于井字棋算法的主要内容,如果未能解决你的问题,请参考以下文章