博弈问题 tic-tac-toe(井字棋) 人机对战 C++

难得今天校园活动日,学校放假一天,可以好好做近期计划做但一直没有时间做的事了~比如写博客 【斜眼笑
最近好忙,好多ddl,下周还有三门考试,下下周还有英语pre。。。但是窝真的很想记录一下近期的学习成果,要不然以后可能就忘了。

P.S.这学期的数据结构课真是深得朕心,肥肠喜欢这种用上课学的知识成功解决实际问题的感觉٩(๑❛ᴗ❛๑)۶

C++ 极小极大算法 实现井字棋人机对战

概述:

因为本题的核心是博弈的实现,所以在这里对如何下棋,如何判断游戏是否结束之类的问题不做赘述。

本题中博弈的实现思路如下:

  1. 设置一个评价机制,为当前局势赋值
    设置两个相反的比较机制,比较两个局势的评价值,决定哪个当前方最有利
  2. 遍历——试放,评价
  3. 比较,返回最有利位置

解释一下:

  1. 因为就客观而言,博弈双方面临同一盘棋,所以设置一个评价机制为当前局势赋值。
    但对于任一方而言,对对手最有利的情况就是对自己最不利的。所以设置两个相反的比较机制。
  2. 使用递归,在每一层,遍历所有可以放棋的位置,评估对当前放棋方而言这个位置放棋之后的局势。
  3. 在同层进行比较,返回对当前放棋方最有利的位置。

具体代码&讲解:

  1. 头文件 =_=
#include<iostream>
#include<stdio.h>
#include"windows.h"
#define Stack_entry Move
#define maxstack 100
#define success true
#define underflow false
#define overflow false

using namespace std;
  1. Move类的定义(每一步都是一个Move)
class Move{
public:
    Move();
    Move(int r,int c);
    int row;
    int col;
};
Move::Move()
{
    row=3;
    col=3;
}
Move::Move(int r,int c)
{
    row=r;
    col=c;
}

  1. 栈的定义(用来存储所有可行的步(Move))
class Stack{
public:
    Stack();
    bool empty()const;
    bool pop();
    bool top(Stack_entry &item);
    bool push(const Stack_entry &item);
private:
    int count;
    Stack_entry entry[maxstack];
};
Stack::Stack()
{
    count=0;
}
bool Stack::push(const Stack_entry &item)
{
    bool outcome=success;
    if(count>=maxstack)
        return overflow;
    else
        entry[count++]=item;
    return outcome;
}
bool Stack::pop()
{
    bool outcome=success;
    if(count==0)
        outcome=underflow;
    else --count;
    return outcome;
}
bool Stack::top(Stack_entry &item)
{
    bool outcome=success;
    if(count==0)
        return underflow;
    else
        item=entry[count-1];
    return outcome;
}
bool Stack::empty()const
{
    bool outcome=true;
    if(count>0)
        outcome=false;
    return outcome;
}

  1. Board类的定义(定义棋盘和对棋盘的操作)
    重点是better函数,worst_case函数evaluate函数。
class Board{
public:
    Board();
    bool done()const;
    bool is_ok(Move try_it);
    void print()const;
    bool better(int value,int old_value)const;
    void play(Move try_it);
    int worst_case()const;
    int evaluate()const;
    int legal_moves(Stack &moves)const;
    void show_the_board();
private:
    int square[3][3];
    int moves_done;
    int the_winner()const;
};

Board::Board()
/**初始化棋盘,没有棋的地方存的数是0**/
{
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
        square[i][j]=0;
    moves_done=0;
}

void Board::print()const
/**打印棋盘**/
{
    cout<<endl<<"    0   1   2"<<endl;
    for(int i=0; i<3; i++)
    {
        cout<<endl<<i;
        for(int j=0; j<3; j++)
        {
            if(square[i][j]==1)
                cout<<"   O";
            else if(square[i][j]==2)
                cout<<"   X";
            else cout<<"   -";
            if(j==2) cout<<endl;
        }
    }
    cout<<endl;
}

void Board::play(Move try_it)
/**把“步”放到棋盘上**/
{
        square[try_it.row][try_it.col]=moves_done%2+1;
        moves_done++;
}

int Board::the_winner()const
/**返回代表赢家的数字(1或2),若胜负未定则返回0**/
{
    int i;
    for(i=0 ; i<3 ; i++)
        if(square[i][0]&&square[i][0]==square[i][1]
                       &&square[i][0]==square[i][2])
            return square[i][0];
            
    for(i=0 ; i<3 ; i++)
        if(square[0][i]&&square[0][i]==square[1][i]
                       &&square[0][i]==square[2][i])
            return square[0][i];
            
    if(square[0][0]&&square[0][0]==square[1][1]
                   &&square[0][0]==square[2][2])
            return square[0][0];
            
    if(square[0][2]&&square[0][2]==square[1][1]
                   &&square[0][2]==square[2][0])
            return square[0][2];
    return 0;
}

bool Board::done()const
/**判断游戏是否结束**/
{
    return (moves_done==9||the_winner()>0);
}

int Board::legal_moves(Stack &moves)const
/**把 所有可行的步 存到 传入的栈 里**/
{
    int count=0;
    while(!moves.empty())
        moves.pop();
    for(int i=0; i<3; i++)
        for(int j=0; j<3; j++)
            if(square[i][j]==0)
            {
                Move can_play(i,j);
                moves.push(can_play);
                count++;
            }
    return count;
}

bool Board::is_ok(Move try_it)
/**判断这一步是否可行**/
{
    if(try_it.row>=0&&try_it.row<3&&try_it.col>=0&&try_it.col<3
                                  &&square[try_it.row][try_it.col]==0)
        return true;
    else return false;
}

int Board::evaluate()const
/**返回一个数,这个数反映了 胜负 和 一共走了多少步,
   用于在下面的递归函数里 计算每个可行的步最后结果**/
{
    int winner=the_winner();
    if(winner==1)
        return 10-moves_done;//赢家是1,则返回值为正,数越大对玩家1越有利

    else if(winner==2)
        return moves_done-10;//赢家是2,则返回值为负,数越小对玩家2越有利
    else
        return 0;//平局
}

int Board::worst_case()const
/**返回最坏的情况,方便在递归中的比较**/
{
    if(moves_done%2)	//对玩家2而言
        return 10;
    else return -10;	//对玩家1而言
}

bool Board::better(int value,int old_value)const
/**比较两个值哪个最好**/
{
    if(moves_done%2)	//对玩家2而言
        return value<old_value;
    else				//对玩家1而言
        return value>old_value;
}

放上草图一张以便理解:
博弈问题 tic-tac-toe(井字棋) 人机对战 C++_第1张图片
5. 非常重要的函数。
负责找出对当前放棋方最有利的一步。

int look_ahead(const Board &game, int depth, Move &recommended)
{
    if(game.done()||depth==0)
        return game.evaluate();
    else
    {
        Stack moves;
        game.legal_moves(moves);
        int value;
        int best_value=game.worst_case();
        while(!moves.empty())
        {
            Move try_it,reply;
            moves.top(try_it);
            Board new_game=game;
            new_game.play(try_it);
            value=look_ahead(new_game,depth-1,reply);
            if(game.better(value,best_value))
            {
                best_value=value;
                recommended=try_it;
            }
            moves.pop();
        }
        return best_value;
    }
}
  1. 打印相关信息
void instructions()
{
    cout<<"欢迎来到井字棋游戏!您将开始与这台电脑博弈。"<<endl
            <<"游戏规则很简单,只需输入放置棋子的坐标。"<<endl
            <<"温馨提示:O代表您,X代表您的对手。"<<endl<<endl;
}

void print_the_result(int result)
{
    if(result>0)
        cout<<"\n恭喜您获胜了!\n"<<endl;
    else if(result<0)
        cout<<"\noops!您输了!\n"<<endl;
    else cout<<"\n本局和局。\n"<<endl;
}

  1. 玩一局棋
int play_game(int intel)
{
    Board game;
    Move player1_choice;
    Move player2_choice;

    cout<<"请输入电脑的智商:"<<endl;
    while(intel<1||intel>9)
    {
        cout<<"智商应大于等于1,小于等于9。"<<endl;
        cin>>intel;
    }
    system("cls");
    game.print();
    while(!game.done())
    {
        cout<<"请输入位置坐标:\n";                  //玩家下棋
        cin>>player1_choice.row>>player1_choice.col;
        while(!game.is_ok(player1_choice))
        {
            cout<<"这个地方不可以放棋喔。"<<endl;
            cin>>player1_choice.row>>player1_choice.col;
        }
        game.play(player1_choice);
        if(game.done())
        {
            system("cls");
            game.print();
            break;
        }

        look_ahead(game , intel , player2_choice);  //电脑下棋
        game.play(player2_choice);

        system("cls");
        game.print();
    }
    print_the_result(game.evaluate());
    return game.evaluate();
}
  1. 主函数。可以下多局棋,并统计胜负情况。
int main(){
    int win0=0,win1=0,win2=0;
    int winner;
    char command;
    int intel=0;

    instructions();
    cout<<"是否开始游戏?输入任意非N字符以开始游戏。"<<endl;
    cin>>command;
    if(command!='N'&&command!='n')
    {
        cout<<"请输入电脑的智商:"<<endl;
        while(intel<1||intel>9)
        {
            cout<<"智商应大于等于1,小于等于9。"<<endl;
            cin>>intel;
        }
        system("cls");
    }

    while(command!='N'&&command!='n')
    {
        winner=play_game(intel);
        if(winner==0) win0++;
        else if(winner>0) win1++;
        else win2++;
        cout<<"是否继续游戏?输入任意非N字符以继续。"<<endl;
        cin>>command;
        system("cls");
    }
    if((win0+win1+win2)!=0)
        cout<<"在与智商为"<<intel<<"的电脑的博弈中:"<<endl
               <<"  您赢了 "<<win1<<" 次"<<endl
               <<"电脑赢了 "<<win2<<" 次"<<endl
               <<"双方平局 "<<win0<<" 次"<<endl;
    getchar();
    return 0;
}

最后想说:

不写不知道,一写吓一跳。
一个看似简单的小程序,竟然要写这么多。
所以,,路漫漫其修远兮,吾将上下而求索。

好了,这就算写完了8,时间过得好快,竟然在不知不觉中错过了饭点,电脑也即将没电orz
但不得不说这感觉真好【逃

——2019.4.18 14:32 于图书馆

你可能感兴趣的:(大一下,数据结构,数据结构课内作业,回溯)