最近沉迷於《NetHack》、《DCSS》等字符界面游戲,對其很感興趣,於是用C語言寫了個字符界面的井字棋小游戲,作為練手。代碼寫得不好。歡迎大家指教。
編寫時遇到了一些問題,我原先准備用循環,直到讀取到正確的輸入。可該死的getchar函數,在讀取后,又把回車又傳給下次循環,我不得不對其進行處理。
設定井字棋的AI時,有個有趣的地方就是,先下四個角比先下中心優勢更大,這違背了我以前的直覺。
1 #include <stdio.h> 2 #include <ctype.h> 3 #include <string.h> 4 #include <stdlib.h> 5 #include <time.h> 6 7 void drawBoard(char *board) //繪制棋盤 8 { 9 printf("%c|%c|%c\n", board[7], board[8], board[9]); 10 puts("-+-+-"); 11 printf("%c|%c|%c\n", board[4], board[5], board[6]); 12 puts("-+-+-"); 13 printf("%c|%c|%c\n", board[1], board[2], board[3]); 14 puts("-+-+-"); 15 } 16 17 char inputPlayerLetter() //玩家選擇棋子 18 { 19 char letter; 20 puts("你想用X還是O?"); 21 do{ 22 letter = toupper(getchar()); 23 if (letter == '\n') 24 continue; 25 if (letter != 'X' && letter != 'O') 26 puts("你想用X還是O?"); 27 }while (letter != 'X' && letter != 'O'); 28 29 return letter; 30 31 } 32 33 void makeMove(char *board,char letter,int move) //落子 34 { 35 board[move] = letter; 36 } 37 38 _Bool isWinner(char *bo, char le) //判定是否獲勝 39 { 40 return ((bo[7] == le && bo[8] == le && bo[9] == le) || 41 (bo[4] == le && bo[5] == le && bo[6] == le) || 42 (bo[1] == le && bo[2] == le && bo[3] == le) || 43 (bo[7] == le && bo[4] == le && bo[1] == le) || 44 (bo[8] == le && bo[5] == le && bo[2] == le) || 45 (bo[9] == le && bo[6] == le && bo[3] == le) || 46 (bo[7] == le && bo[5] == le && bo[3] == le) || 47 (bo[9] == le && bo[5] == le && bo[1] == le)); 48 } 49 50 const char getBoardCopy(char *board) //復制棋盤,讓電腦預判可能出現的情況 51 { 52 char boardCopy[10]; 53 for (int i = 1; i < 10; i++) 54 boardCopy[i] = board[i]; 55 return *boardCopy; 56 } 57 58 _Bool isSpaceFree(char *board,int move) //判斷棋盤上是否為空 59 { 60 return board[move] == ' '; 61 } 62 63 int getPlayerMove(char *board) //讀取玩家棋子移動 64 { 65 puts("你下一步走哪里?(1-9)"); 66 int move; 67 do { 68 move = getchar() - '0'; 69 if (move == '\n' - '0') 70 continue; 71 if (move < 1 || move > 9 || !isSpaceFree(board, move)) 72 puts("你下一步走哪里?(1-9)"); 73 }while (move < 1 || move > 9 || !isSpaceFree(board, move)); 74 return move; 75 } 76 77 int chooseRandomMoveFromList(char *board,char *movelist, int n) //隨機讀取計算機可移動的位置 78 { 79 int possibleMove[4]; //每輪選擇最多只有四個 80 int j = 0; 81 for (int i = 0; i < n; i++) 82 if (isSpaceFree(board, movelist[i] - '0')) 83 possibleMove[j++] = movelist[i] - '0'; 84 85 if (j != 0) 86 return possibleMove[rand()%j]; 87 else 88 return 0; 89 } 90 91 int getComputerMove(char board[], char computerLetter) //獲得計算機的移動 92 { 93 char playerLetter; 94 char boardCopy[10]; 95 if (computerLetter == 'X') //根據計算機的棋子,判斷玩家棋子 96 playerLetter = 'O'; 97 else 98 playerLetter = 'X'; 99 100 for (int i = 1; i < 10; i++){ //如果下一步可獲勝,下那一步 101 strcpy(boardCopy, board); 102 if (isSpaceFree(boardCopy, i)) { 103 makeMove(boardCopy, computerLetter,i); 104 if (isWinner(boardCopy, computerLetter)) 105 return i; 106 } 107 } 108 109 for (int i = 1; i < 10; i++){ //如果下一步玩家會獲勝,占那個位置 110 strcpy(boardCopy,board); 111 if (isSpaceFree(boardCopy, i)) { 112 makeMove(boardCopy, playerLetter,i); 113 if (isWinner(boardCopy, playerLetter)) 114 return i; 115 } 116 } 117 118 int move; //如果下一步不是決勝步 119 move = chooseRandomMoveFromList(board, "1379", 4); //四個角優先 120 if (move != 0) 121 return move; 122 123 move = chooseRandomMoveFromList(board, "5", 1); //中間 124 if (move != 0) 125 return move; 126 127 return chooseRandomMoveFromList(board, "2468", 4); //剩下的位置 128 } 129 130 _Bool isBoardFull(char *board) //判斷棋盤是否滿了 131 { 132 for (int i = 1; i < 10; i++) 133 if (isSpaceFree(board, i)) 134 return 0; 135 return 1; 136 } 137 138 _Bool isAgain() //再來一局 139 { 140 char again; 141 do{ 142 again = tolower(getchar()); 143 if (again == '\n') 144 continue; 145 if (again != 'n' && again != 'y') 146 puts("請輸入y或n。"); 147 }while (again != 'n' && again != 'y'); 148 149 if (again == 'y') 150 return 1; 151 else 152 return 0; 153 } 154 155 156 int main() 157 { 158 puts("歡迎來玩井字棋!") ; 159 160 while(1) { //游戲 161 char theBoard[10]; 162 for (int i = 1; i < 10; i++) //將棋盤設為空白 163 theBoard[i] = ' '; 164 char playerLetter = inputPlayerLetter(); //獲得玩家所選的棋子 165 char computerLetter = (playerLetter == 'X')?'O': 'X' ; 166 //獲得計算機的棋子 167 _Bool isTurnPlayer; //設定是否是玩家回合 168 int move; 169 srand((unsigned)time(NULL)); //隨機先后手 170 if (rand() % 2) { 171 isTurnPlayer = 0; 172 puts("電腦先走。"); 173 }else { 174 isTurnPlayer = 1; 175 puts("玩家先走。"); 176 } 177 _Bool gameIsPlaying = 1; //設定游戲是否進行 178 179 while (gameIsPlaying) { 180 181 if (isTurnPlayer) { //如果是玩家回合 182 drawBoard(theBoard); 183 move = getPlayerMove(theBoard); 184 makeMove(theBoard, playerLetter, move); 185 186 if (isWinner(theBoard, playerLetter)) { //如果獲勝 187 drawBoard(theBoard); 188 puts("太棒了!你獲勝了!"); 189 gameIsPlaying = 0; 190 } 191 192 else { //如果平局 193 if (isBoardFull(theBoard)) { 194 drawBoard(theBoard); 195 puts("平局了!"); 196 break; 197 } 198 else //設定輪到計算機 199 isTurnPlayer = 0; 200 } 201 } 202 203 else { //輪到計算機了 204 move = getComputerMove(theBoard, computerLetter); 205 makeMove(theBoard, computerLetter, move); 206 if (isWinner(theBoard, computerLetter)) { //如果獲勝 207 drawBoard(theBoard); 208 puts("電腦打敗了你!你輸了。"); 209 gameIsPlaying = 0; 210 } else { //平局 211 if (isBoardFull(theBoard)) { 212 drawBoard(theBoard); 213 puts("平局了!"); 214 break; 215 } else // 設定輪到玩家 216 isTurnPlayer = 1; 217 } 218 } 219 } 220 puts("再來一局?(yes或no)"); 221 if (!isAgain()) 222 break; 223 } 224 return 0; 225 }