井字棋算法
緒言
說到井字棋,也許都想起了自己小時候的時光吧。
井字棋其實很簡單,只要你去認真分析它,你就能明白什么叫做“先手不輸,后手不贏”。
算法
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<<"請輸入模式:\n【0】雙人對戰\n【1】人機easy\n【2】人機mid\n【3】人機hard\n【4】神仙打架\n【5】學習神仙\n"; 144 do{ 145 mode=getch()-'0'; 146 }while(mode<0||mode>5); 147 diff=max(min(mode,3),1); 148 cout<<"游戲中 按下r鍵可以重新選擇\n"; 149 system("pause"); 150 } 151 const string cls[9]={"sssss sssss sssss\n","ss7ss ss8ss ss9ss\n","sssss sssss sssss\n\n","sssss sssss sssss\n","ss4ss ss5ss ss6ss\n","sssss sssss sssss\n\n","sssss sssss sssss\n","ss1ss ss2ss ss3ss\n","sssss sssss sssss\n"}; 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\n沒有使用搜索算法而先把所有情況列了出來\n這是一個先手不會輸,后手不會贏的游戲。\n先手所能做的就是嘗試贏,后手所能做的就是嘗試不輸。\n新手建議嘗試easy上手,大佬也別急着玩hard哦!\n祝您游戲愉快!\n"; 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
當你如此認真的分析完井字棋之后,你就會發現,這真是一個無聊的游戲啊……