本章游戏项目将介绍如何使用AI(Artificial Intelligence,人工智能)来创建计算机对手。在该游戏中,玩家和计算机进行一场高赌注的人机Tic-Tac-Toe决战。计算机的棋艺令人生畏(尽管不是很完美),并且通过使其人格化来让比赛变得更有趣
一、游戏规划
1.编写伪代码
伪代码的每一行就像是一个函数的调用。之后,所有要做的事情就是编写伪代码所暗示的函数
Create an empty Tic-Tac-Toe board
Display the game instructions
Determine who gose first
Display the board
While nobody has won and it's not a tie
If it's the human's turn
Get the human's move
Update the board with the human's move
Otherwise
Calculate the conputer's move
Update the board with the computer's move
Display the board
Switch turns
Congratulate the winner or declare a tie
2.数据的表示
现在有了一个不错的规划,但它相当抽象,并且涉及还没有真正定义的元素。将一招棋看作是在游戏棋盘上放置的一枚棋子。但是具体要如何表示游戏棋盘?如何表示棋子和一招棋?
既然要在屏幕上显示游戏棋盘,为何不就将棋子表示成单个字符:一个X或一个O?空位置可以是空白字符。因此,棋盘本身可以上字符向量。井字棋游戏的棋盘上有9个方格,因此向量应当有9个元素,棋盘上的每个方格对应向量中的每个元素,如下所示。
0 | 1 | 2
3 | 4 | 5
6 | 7 | 8
棋盘上的每个方格或位置由数字0 ~ 8表示。也就是说,向量有9个元素,分别对应位置0 ~ 8。因为每一招棋表示放置棋子的一个方格,所以一招棋也只是一个0 ~ 8的数字,即可以用int型值表示。
和棋子一样,玩家和计算机也能用char型值表示:‘X’或‘O’。表示当前走棋一方的变量也是char型值‘X’或‘O’。
3.创建函数列表
伪代码暗示了所需的不同函数。为它们创建一个列表,考虑各自的功能、拥有的参数以及返回值。结果如下表所示
一个简单的表格是这么创建的:
函数 | 描述 |
---|---|
void instructions() | 显示游戏的操作指南 |
char askYesNo(string question) | 询问是否。接受一个问题作为参数,返回‘y’或者‘n’ |
int askNumber(string question, int high, int low = 0) | 询问一定范围内的数字。接受一个问题、一个范围下限和一个范围上限作为参数,返回low到high之间的数字 |
char humanPiece() | 确定玩家的棋子,返回‘X’或‘O’ |
char oppenent(char piece) | 计算给定棋子的应对棋子,接受‘X’或‘O’,返回‘O’或‘X’ |
void displayBoard(const vector& board) | 在屏幕上显示棋盘。接受棋盘作为参数 |
char winner(const vector& board) | 确定游戏的胜利者。接受棋盘作为参数,返回‘X’、‘O’、‘T’(表示和棋)或‘N’(表示还没有哪一方胜出) |
bool isLegal(const vector& board, int move) | 确定一招棋的合法性。接受一个棋盘和一招棋作为参数,返回true或false |
int humanMove(const vector& board, char human) | 获取人类玩家的一招棋。接受一个棋盘与人类玩家的棋子作为参数,返回玩家的一招棋 |
int computerMove(vector board, char computer) | 计算计算机的一招棋。接受一个棋盘与计算机的棋子作为参数,返回计算机的一招棋 |
void announceWinner(char winner, char computer, char human) | 恭喜胜者或宣布和棋。接受胜出方、计算机的棋子与人类玩家的棋子作为参数 |
二、程序源码
全部代码如下:
//Tic-Tac_Toe
//Plays the game of tic-tac-toe against a human opponent
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
//global constants
const char X = 'X';
const char O = 'O';
const char EMPTY = ' ';
const char TIE = 'T';
const char NO_ONE = 'N';
//function prototypes
void instructions();
char askYesNo(string question);
int askNumber(string question, int high, int low = 0);
char humanPiece();
char opponent(char piece);
void displayBoard(const vector<char>& board);
char winner(const vector<char>& board);
bool isLegal(const vector<char>& board, int move);
int humanMove(const vector<char>& board, char human);
int computerMove(vector<char> board, char computer);
void announceWinner(char winner, char computer, char human);
//main function
int main()
{
int move;
const int NUM_SQUARES = 9;
vector<char> board(NUM_SQUARES, EMPTY);
instructions();
char human = humanPiece();
char computer = opponent(human);
char turn = X;
displayBoard(board);
while (winner(board) == NO_ONE)
{
if (turn == human)
{
move = humanMove(board, human);
board[move] = human;
}
else
{
move = computerMove(board, computer);
board[move] = computer;
}
displayBoard(board);
turn = opponent(turn);
}
announceWinner(winner(board), computer, human);
return 0;
}
void instructions()
{
cout << "Welcome to the ultimate man-machine showdown: Tic-Tac-Toe.\n";
cout << "--where human brain is pit against silicon processor\n\n";
cout << "Make your move known by entering a number, 0-8. The number\n";
cout << "corresponds to the desired board position, as illustrated:\n\n";
cout << " 0 | 1 | 2\n";
cout << " ---------\n";
cout << " 3 | 4 | 5\n";
cout << " ---------\n";
cout << " 6 | 7 | 8\n";
cout << "Prepare yourself, human. The battle is about to begin.\n\n";
}
char askYesNo(string question)
{
char response;
do
{
cout << question << " (y/n): ";
cin >> response;
} while (response != 'y' && response != 'n');
return response;
}
int askNumber(string question, int high, int low)
{
int number;
do
{
cout << question << " (" << low << " - " << high << "): ";
cin >> number;
} while (number > high || number < low);
return number;
}
char humanPiece()
{
char go_first = askYesNo("Do you require the first move?");
if (go_first == 'y')
{
cout << "\nThen take the first move, You will need it.\n";
return X;
}
else
{
cout << "\nYour bravery will be your undoing... I will go first.\n";
return O;
}
}
char opponent(char piece)
{
if (piece == X)
{
return O;
}
else
{
return X;
}
}
void displayBoard(const vector<char>& board)
{
cout << "\n\t" << board[0] << " | " << board[1] << " | " << board[2];
cout << "\n\t" << "---------";
cout << "\n\t" << board[3] << " | " << board[4] << " | " << board[5];
cout << "\n\t" << "---------";
cout << "\n\t" << board[6] << " | " << board[7] << " | " << board[8];
cout << "\n\n";
}
char winner(const vector<char>& board)
{
//all possible winning rows
const int WINNING_ROWS[8][3] = { {0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6} };
const int TOTAL_ROWS = 8;
//if any winning row has three values that are the same (and not EMPTY),
//them we have a winner
for (int row = 0; row < TOTAL_ROWS; ++row)
{
if ((board[WINNING_ROWS[row][0]] != EMPTY) &&
(board[WINNING_ROWS[row][0]] == board[WINNING_ROWS[row][1]]) &&
(board[WINNING_ROWS[row][1]] == board[WINNING_ROWS[row][2]]))
{
return board[WINNING_ROWS[row][0]];
}
}
//since nobody has won, check for a tie (no empty squares left)
if (count(board.begin(), board.end(), EMPTY) == 0)
{
return TIE;
}
//since nobody has won and it isn't a tie, the game ain't over
return NO_ONE;
}
inline bool isLegal(int move, const vector<char>& board)
{
return (board[move] == EMPTY);
}
int humanMove(const vector<char>& board, char human)
{
int move = askNumber("Where will you move?", (board.size() - 1));
while (!isLegal(move, board))
{
cout << "\nThat square is already occupied, foolish human.\n";
move = askNumber("Where will you move", (board.size() - 1));
}
cout << "Fine...\n";
return move;
}
int computerMove(vector<char> board, char computer)
{
unsigned int move = 0;
bool found = false;
//if computer can win on the next move, that's the move to make
while (!found && move < board.size())
{
if (isLegal(move, board))
{
board[move] = computer;
found = winner(board) == computer;
board[move] = EMPTY;
}
if (!found)
{
++move;
}
}
//otherwise, if human can win on the next move, that's the move to make
if (!found)
{
move = 0;
char human = opponent(computer);
while (!found && move < board.size())
{
if (isLegal(move, board))
{
board[move] = human;
found = winner(board) == human;
board[move] = EMPTY;
}
if (!found)
{
++move;
}
}
}
//otherwise, moving to the best open square is the move to make
if (!found)
{
move = 0;
unsigned int i = 0;
const int BEST_MOVES[] = { 4,0,2,6,8,1,3,5,7 };
//pick best open square
while (!found && i < board.size())
{
move = BEST_MOVES[i];
if (isLegal(move, board))
{
found = true;
}
++i;
}
}
cout << "I shall take square number " << move << endl;
return move;
}
void announceWinner(char winner, char computer, char human)
{
if (winner == computer)
{
cout << winner << "'s won!\n";
cout << "As I predicted, human, I am triumphant once more -- proof\n";
cout << "that computers are superior to humans in all regards.\n";
}
else if (winner == human)
{
cout << winner << "'s won!\n";
cout << "No, no! It cannot be! Somehow you tricked me, human.\n";
cout << "But never again! I, the computer, so swear it!\n";
}
else
{
cout << "It's a tie.\n";
cout << "You were most lucky, human, and somehow managed to tie me.\n";
cout << "Celebrate...for this is the best you will ever achieve.\n";
}
}
运行结果如下:
参考:C++游戏编程入门(第4版) [美]Michael Dawson 著