输出界面如图所示:
tic-tac-toe源代码:
/*
* File: tictac.c
* --------------
* This program plays a game of tic-tac-toe with the user.
* The program is designed to emphasize the separation between
* those aspects of the code that are common to all games and
* those that are specific to tic-tac-toe.
*/
#include <stdio.h>
#include "stdgen.h"
#include "stdstr.h"
/*
* Constants: WinningPosition, NeutralPosition, LosingPosition
* -----------------------------------------------------------
* These constants define a rating system for game positions.
* A rating is an integer centered at 0 as the neutral score:
* ratings greater than 0 are good for the cuurent player,
* ratings less than 0 are good for the opponent. The
* constants WinningPosition and LosingPosition are opposite
* in value and indicate a position that is a forced win or
* loss, respectively. In a game in which the analysis is
* complete, no intermediate values ever arise. If the full
* tree is too large to analyze, the EvaluatePosition function
* returns integers that fall between the two extremes.
*/
#define WinningPosition 1000
#define NeutralPosition 0
#define LosingPosition (-WinningPosition)
/*
* Type: playerT
* -------------
* This type is used to distinguish the human and computer
* players and keep track of who has the current turn.
*/
typedef enum { Human, Computer } playerT;
/*
* Type: moveT
* -----------
* For any particular game, the moveT type must keep track of the
* information necessary to describe a move. For tic-tac-toe,
* a moveT is simply an integer identifying the number of one of
* the nine squares.
*/
typedef int moveT;
/*
* Type: stateT
* ------------
* For any game, the stateT structure records the current state
* of the game. For tic-tac-toe, the main component of the
* state record is the board, which is an array of characters
* using 'X' for the first player, 'O' for the second, and ' '
* for empty squares. Although the board array is logically
* a two-dimensional structure, it is stored as a linear array
* so that its indices match the numbers used by the human
* player to refer to the squares, as follows:
*
* 1 | 2 | 3
* ---+---+---
* 4 | 5 | 6
* ---+---+---
* 7 | 8 | 9
*
* Note that element 0 is not used, which requires allocation
* of an extra element.
*
* In addition to the board array, the code stores a playerT
* value to indicate whose turn it is. In this example, the
* stateT structure also contains the total number of moves
* so that functions can check this entry without counting
* the number of occupied squares.
*/
typedef struct {
char board[(3 * 3) + 1];
playerT whoseTurn;
int turnsTaken;
} *stateT;
/*
* Constant: MaxMoves
* ------------------
* This constant indicates the maximum number of legal moves
* available on a turn and is used to allocate array space for
* the legal move list. This constant will change according
* to the specifics of the game. For tic-tac-toe, there are
* never more than nine possible moves.
*/
#define MaxMoves 9
/*
* Constant: MaxDepth
* ------------------
* This constant indicates the maximum depth to which the
* recursive search for the best move is allowed to proceed.
* The use of a very large number for this constant ensures
* that the analysis is carried out to the end of the game.
*/
#define MaxDepth 10000
/*
* Constant: FirstPlayer
* ---------------------
* This constant indicates whether the human or the computer
* player goes first and should be one of the enumerated
* constants: Human or Computer.
*/
#define FirstPlayer Computer
/*
* Private variable: winningLines
* ------------------------------
* This two-dimensional array contains the index numbers of
* the cells in each of the winning combinations. Although
* it is easy for the program to compute these values as it
* runs, storing them in advance speeds up the execution.
*/
static int winningLines[][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 },
{ 1, 4, 7 },
{ 2, 5, 8 },
{ 3, 6, 9 },
{ 1, 5, 9 },
{ 3, 5, 7 }
};
static int nWinningLines = sizeof winningLines
/ sizeof winningLines[0];
/* Private function prototypes */
static moveT FindBestMove(stateT state, int depth, int *pRating);
static int EvaluatePosition(stateT state, int depth);
static stateT NewGame(void);
static void DisplayGame(stateT state);
static void DisplayMove(moveT move);
static void GiveInstructions(void);
static char PlayerMark(playerT player);
static moveT GetUserMove(stateT state);
static bool MoveIsLegal(moveT move, stateT state);
static moveT ChooseComputerMove(stateT state);
static int GenerateMoveList(stateT state, moveT moveArray[]);
static void MakeMove(stateT state, moveT move);
static void RetractMove(stateT state, moveT move);
static void AnnounceResult(stateT state);
static bool GameIsOver(stateT state);
static int EvaluateStaticPosition(stateT state);
static bool CheckForWin(stateT state, playerT player);
static playerT WhoseTurn(stateT state);
static playerT Opponent(playerT player);
/*
* Main program
* ------------
* The main program, along with the functions FindBestMove and
* EvaluatePosition, are general in their design and can be
* used with most two-player games. The specific details of
* tic-tac-toe do not appear in these functions and are instead
* encapsulated in the stateT and moveT data structures and a
* a variety of subsidiary functions.
*/
main()
{
stateT state;
moveT move;
GiveInstructions();
state = NewGame();
while (!GameIsOver(state)) {
DisplayGame(state);
switch (WhoseTurn(state)) {
case Human:
move = GetUserMove(state);
break;
case Computer:
move = ChooseComputerMove(state);
DisplayMove(move);
break;
}
MakeMove(state, move);
}
AnnounceResult(state);
}
/*
* Function: FindBestMove
* Usage: move = FindBestMove(state, depth, pRating);
* --------------------------------------------------
* This function finds the best move for the current player, given
* the specified state of the game. The depth parameter and the
* constant MaxDepth are used to limit the depth of the search
* for games that are too difficult to analyze in full detail.
* The function returns the best move and stores its rating in
* the integer variable to which pRating points.
*/
static moveT FindBestMove(stateT state, int depth, int *pRating)
{
moveT moveArray[MaxMoves], move, bestMove;
int i, nMoves, rating, minRating;
nMoves = GenerateMoveList(state, moveArray);
if (nMoves == 0) Error("No moves available");
minRating = WinningPosition + 1;
for (i = 0; i < nMoves && minRating != LosingPosition; i++) {
move = moveArray[i];
MakeMove(state, move);
rating = EvaluatePosition(state, depth + 1);
if (rating < minRating) {
bestMove = move;
minRating = rating;
}
RetractMove(state, move);
}
*pRating = -minRating;
return (bestMove);
}
/*
* Function: EvaluatePosition
* Usage: rating = EvaluatePosition(state, depth);
* -----------------------------------------------
* This function evaluates a position by finding the rating of
* the best move in that position. The depth parameter and the
* constant MaxDepth are used to limit the depth of the search.
*/
static int EvaluatePosition(stateT state, int depth)
{
int rating;
if (GameIsOver(state) || depth >= MaxDepth) {
return (EvaluateStaticPosition(state));
}
(void) FindBestMove(state, depth, &rating);
return (rating);
}
/*
* Function: NewGame
* Usage: state = NewGame();
* -------------------------
* This function starts a new game and returns a stateT that
* has been initialized to the defined starting configuration.
*/
static stateT NewGame(void)
{
stateT state;
int i;
state = New(stateT);
for (i = 1; i <= 9; i++) {
state->board[i] = ' ';
}
state->whoseTurn = FirstPlayer;
state->turnsTaken = 0;
return (state);
}
/*
* Function: DisplayGame
* Usage: DisplayGame(state);
* --------------------------
* This function displays the current state of the game.
*/
static void DisplayGame(stateT state)
{
int row, col;
if (GameIsOver(state)) {
printf("\nThe final position looks like this:\n\n");
} else {
printf("\nThe game now looks like this:\n\n");
}
for (row = 0; row < 3; row++) {
if (row != 0) printf("---+---+---\n");
for (col = 0; col < 3; col++) {
if (col != 0) printf("|");
printf(" %c ", state->board[row * 3 + col + 1]);
}
printf("\n");
}
printf("\n");
}
/*
* Function: DisplayMove
* Usage: DisplayMove(move);
* -------------------------
* This function displays the computer's move.
*/
static void DisplayMove(moveT move)
{
printf("I'll move to square %d.\n", move);
}
/*
* Function: GiveInstructions
* Usage: GiveInstructions();
* --------------------------
* This function gives the player instructions about how to
* play the game.
*/
static void GiveInstructions(void)
{
printf("Welcome to tic-tac-toe. The object of the game\n");
printf("is to line up three symbols in a row, vertically,\n");
printf("horizontally, or diagonally. You'll be %c and\n",
PlayerMark(Human));
printf("I'll be %c.\n", PlayerMark(Computer));
}
/*
* Function: PlayerMark
* Usage: mark = PlayerMark(player);
* ---------------------------------
* This function returns the mark used on the board to indicate
* the specified player. By convention, the first player is
* always X, so the mark used for each player depends on who
* goes first.
*/
static char PlayerMark(playerT player)
{
if (player == FirstPlayer) {
return ('X');
} else {
return ('O');
}
}
/*
* Function: GetUserMove
* Usage: move = GetUserMove(state);
* ---------------------------------
* This function allows the user to enter a move and returns the
* number of the chosen square. If the user specifies an illegal
* move, this function gives the user the opportunity to enter
* a legal one.
*/
static moveT GetUserMove(stateT state)
{
moveT move;
printf("Your move.\n");
while (TRUE) {
printf("What square? ");
move = GetInteger();
if (MoveIsLegal(move, state)) break;
printf("That move is illegal. Try again.\n");
}
return (move);
}
/*
* Function: MoveIsLegal
* Usage: if (MoveIsLegal(move, state)) . . .
* ------------------------------------------
* This function returns TRUE if the specified move is legal
* in the current state.
*/
static bool MoveIsLegal(moveT move, stateT state)
{
return (move >= 1 && move <= 9 && state->board[move] == ' ');
}
/*
* Function: ChooseComputerMove
* Usage: move = ChooseComputerMove(state);
* ----------------------------------------
* This function chooses the computer's move and is primarily
* a wrapper for FindBestMove. This function also makes it
* possible to display any game-specific messages that need
* to appear at the beginning of the computer's turn. The
* rating value returned by FindBestMove is simply discarded.
*/
static moveT ChooseComputerMove(stateT state)
{
int rating;
printf("My move.\n");
return (FindBestMove(state, 0, &rating));
}
/*
* Function: GenerateMoveList
* Usage: n = GenerateMoveList(state, moveArray);
* ----------------------------------------------
* This function generates a list of the legal moves available in
* the specified state. The list of moves is returned in the
* array moveArray, which must be allocated by the client. The
* function returns the number of legal moves.
*/
static int GenerateMoveList(stateT state, moveT moveArray[])
{
int i, nMoves;
nMoves = 0;
for (i = 1; i <= 9; i++) {
if (state->board[i] == ' ') {
moveArray[nMoves++] = (moveT) i;
}
}
return (nMoves);
}
/*
* Function: MakeMove
* Usage: MakeMove(state, move);
* -----------------------------
* This function changes the state of the game by making the
* indicated move.
*/
static void MakeMove(stateT state, moveT move)
{
state->board[move] = PlayerMark(state->whoseTurn);
state->whoseTurn = Opponent(state->whoseTurn);
state->turnsTaken++;
}
/*
* Function: RetractMove
* Usage: RetractMove(state, move);
* --------------------------------
* This function changes the state of the game by "unmaking" the
* indicated move.
*/
static void RetractMove(stateT state, moveT move)
{
state->board[move] = ' ';
state->whoseTurn = Opponent(state->whoseTurn);
state->turnsTaken--;
}
/*
* Function: AnnounceResult
* Usage: AnnounceResult(state);
* -----------------------------
* This function announces the result of the game.
*/
static void AnnounceResult(stateT state)
{
DisplayGame(state);
if (CheckForWin(state, Human)) {
printf("You win\n");
} else if (CheckForWin(state, Computer)) {
printf("I win\n");
} else {
printf("Cat's game\n");
}
}
/*
* Function: GameIsOver
* Usage: if (GameIsOver(state)) . . .
* -----------------------------------
* This function returns TRUE if the game is complete.
*/
static bool GameIsOver(stateT state)
{
return (state->turnsTaken == 9
|| CheckForWin(state, state->whoseTurn)
|| CheckForWin(state, Opponent(state->whoseTurn)));
}
/*
* Function: EvaluateStaticPosition
* Usage: rating = EvaluateStaticPosition(state);
* ----------------------------------------------
* This function gives the rating of a position without looking
* ahead any further in the game tree. Although this function
* duplicates much of the computation of GameIsOver and therefore
* introduces some runtime inefficiency, it makes the algorithm
* somewhat easier to follow.
*/
static int EvaluateStaticPosition(stateT state)
{
if (CheckForWin(state, state->whoseTurn)) {
return (WinningPosition);
}
if (CheckForWin(state, Opponent(state->whoseTurn))) {
return (LosingPosition);
}
return (NeutralPosition);
}
/*
* Function: CheckForWin
* Usage: if (CheckForWin(state, player)) . . .
* --------------------------------------------
* This function returns TRUE if the specified player has won
* the game. The check on turnsTaken increases efficiency,
* because neither player can win the game until the fifth move.
*/
static bool CheckForWin(stateT state, playerT player)
{
int i;
char mark;
if (state->turnsTaken < 5) return (FALSE);
mark = PlayerMark(player);
for (i = 0; i < nWinningLines; i++) {
if (mark == state->board[winningLines[i][0]]
&& mark == state->board[winningLines[i][1]]
&& mark == state->board[winningLines[i][2]]) {
return (TRUE);
}
}
return (FALSE);
}
/*
* Function: WhoseTurn
* Usage: player = WhoseTurn(state);
* ---------------------------------
* This function returns whose turn it is, given the current
* state of the game.
*/
static playerT WhoseTurn(stateT state)
{
return (state->whoseTurn);
}
/*
* Function: Opponent
* Usage: opp = Opponent(player);
* ------------------------------
* This function returns the playerT value corresponding to the
* opponent of the specified player.
*/
static playerT Opponent(playerT player)
{
switch (player) {
case Human: return (Computer);
case Computer: return (Human);
}
return 0;
}
测试结果