⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chessboard.cpp

📁 用C++环境编写的五子棋小游戏
💻 CPP
字号:
// chessboard.cpp

#include "chessboard.h"
#include <cstdio>
#include <iostream>
#include <cmath>
#include <vector>
#include <ctime>
using namespace std;


//     Function Implement Beginning

unsigned int randSeed=time(0);
unsigned int random()
{
    int multiplier=2743;
    int adder=5923;
    randSeed=multiplier*randSeed+adder;
    return randSeed;
}
//    线性同余, 得随即数
chessboard::chessboard()
{
    int row, col;
    for( row=0; row<size; row++)
    for( col=0; col<size; col++)
        m_gridState[row][col]=blank;
    initiGridMark();
}

void chessboard::renew()
{
    int row, col;
    for( row=0; row<size; row++)
    for( col=0; col<size; col++)
        m_gridState[row][col]=blank;
    initiGridMark();
}


chessboard::~chessboard()
{
}

void chessboard::initiGridMark()
{
    int row, col;
    int sum, count;

    for( row=0; row<size; row++ )
    for( col=0; col<size; col++ )
    {
        sum=0;
        for( count=1; count<=4; count++)   //    扫描邻近的四个格子    */
        {
            
            if( row-count>=0 )
                sum++;
            if( col-count>=0 )
                sum++;
            if( row+count<size )
                sum++;
            if( col+count<size )
                sum++;
            if( row-count>=0 && col-count>=0 )
                sum++;
            if( row+count<size && col+count<size )
                sum++;
            if( row-count>=0 && col+count<size )
                sum++;
            if( row+count<size && col-count>=0 )
                sum++;
            //   八个方向的判断
            }        
        m_gridMark[row][col]=sum;
    } 
    //   这是棋盘格子的底分, 在相同的情况之下, 越在棋盘的中间, 底分越高,
    //    越处于有利的地位
    
}

void chessboard::outputStates() const
{

    int row, col;
   
    cout<<"   ";
    for( row=0; row<size; row++ )
    {
        putchar(' ');
        row<9 ? putchar('1'+row) : putchar('A'+row-9);
    }
    cout<<endl;
    // 输出横向的标志,方便输入 1 2 3 4 5 6 7 8 9 A B C D E F...
   
    for( row=0; row<size; row++ )
    {
        row<9 ? putchar('1'+row) : putchar('A'+row-9);
        //    竖向的标志,方便输入     
        cout<<"  |";
        for( col=0; col<size; col++ )
        {
            switch( m_gridState[row][col] )
            {
            case blank: cout<<' '<<'|';        //    空 | |   
                        break;
            case black: cout<<'*'<<'|';       //   黑 |#|    
                        break;
            case white: cout<<'O'<<'|';        //    白 |O|   
                        break;
            default:    break;
            }
        }
        cout<<endl;    
    }
}

void chessboard::setGridState(const point& pt, states gridState)
{
    m_gridState[pt.x][pt.y]=gridState;
}

states chessboard::getGridState( const point& pt)
{
    return m_gridState[pt.x][pt.y];
}

void chessboard::getMaxGridMark(point &pt) const
{
    vector< point > max_Mark;
    int max_mark=m_gridMark[0][0];
    for( int i=0; i<size; i++)
    for( int j=0; j<size; j++)
        if( m_gridMark[i][j]>max_mark )
            max_mark=m_gridMark[i][j];
    //    得到最大的分数
    
    for( i=0; i<size; i++)
        for( int j=0; j<size; j++)
            if( m_gridMark[i][j]==max_mark )
                max_Mark.push_back( point(i,j));
    //   将分数最大的坐标点, 放在一个向量里面    

    pt=max_Mark[random()%max_Mark.size()];
    //   分数相同的, 随机取点, 保证每次游戏, 电脑不会下在同一个地方    */
    max_Mark.clear();
    //    注:    上面的算法效率低下, 不过够简单. 对于这样的小程序, 也足够了
    
}

//    下面是整个程序最重要的函数, 是电脑下棋的根据.
//    这个算法是简易的,根据当前棋局的形势来分析哪个地方有利,只考虑了当前的
//    一步棋. 简单说来, 五子棋都是相连的棋子越多,就越有利.
//    对每个格子进行计分. 要是格子附近, 相连的格子越多, 分数越高.
//  考虑三个棋子相连, T表示要考虑的格子(T其实也是一个空格, 因为已经下了的棋子
//    不用考虑,**     B表示黑, W表示白, K表示空.
//    1) T B B B K, 第一个B记8分, 第二个B记16分, 第三个B记32分, T的分数是三个相加
//    这样就相当于, a=8, n=3的等比数列求和. 
//    再考虑 2) T K B B B K, T和B中间隔了个空格, 对比情形1), 1) 更有利, 第一个
//    B记5分, 第二个B记10分, 第三个B记20分, 相当于a=5, n=3的等比数列求和.
//    3) T B B B W, 这个时候因为有个W挡着, 原来n=3, 现在取n--, 相当于a=8, n=2
//    4) T K B B B W , 取a=5, n=2
// 5) T K B B W, 取 a=5, n=1. 
//    6) T B B B B W, 这个时候要另当别论, 因为已经有了4个B相连, 和 T B B B B K 是一样
//        这个时候, 当n=4的时候就立即跳出循环, 不再扫描了, 就没有机会 n--了,
//       这时候 a=8, n=4, 基本上要是有4个相同棋子相连, 就会下在那个地方.
//    7) T K B B B B W  要考虑的格子T, a=5, n=4, 本来会好大的分数了, 
//            不过第一个空格是 a=8, n=4, 分数更大.
//  8) T K K B B B W, 和 T K B B B W一样, 就是说隔一个空格和隔几个空格的情况一样.
//        因为我们是要找出最大分数的格子,不是要排列每一个格子的分数.只要找到最有利就
//        成利, 上面两种情况, 都不如T B B B W, 有利.

//  上面只是考虑了一个方向的情况, 但是五子棋是有8个方向的, 同一直线上的棋子会互相
//    影响, 比如向左的棋子会影响向右的棋子. 比如 B T B B B, 考虑T, 左有一个空格了,
//  使右边的棋子价值升高, 右边的棋子也会让左边的棋子分数变高. 
//    程序采用同一直线扫描两次的方法, 先向右扫描, n=3, 跟着,n保持不变,左扫描, n+=1=4.
//   所以第一次扫描的时候, 左边棋子, a=8, n=3, 右边棋子, a=8, n=4.第二次扫描先左
//    后右, 左边a=8, n=1, 右边n+=3, a=8, n=4. 另外要是有别的棋子挡路, 按上,n作调整.
//    向8个方向扫描, 每个方向两次, 实际上扫描16次, 将分数相加.得出格子分数.
//    上面是基本策略, 程序中还有一些对策略的微小调整, 好象边界问题 
//   程序中 a用 mark表示, n用N表示

void chessboard::updateGridMark(const point &pt, states chessmanColor)
{
    states beginState;       //    扫描方向第一个格子的状态, 以决定mark的值    */
    states lastState;       //    扫描方向上最后扫描的格子状态, 用来决定扫
                           //    描下一个格子时, 更改N的值.    */                            
    states temp;            // 放着向着一个方向时,    第一个格子的的状态.用来决定
                            //    扫描同一直线的下个方向时, 更改N的值.    */

//   考虑 B T B B B. 向左扫描时, temp=black, 向右扫描时, beginState=black, 
//    temp==beginState && temp!=blank
//             N++, 这样的话, 左边的棋子就会影响右边.
//    因为还有一次先由后左的扫描, 向右时, temp=black, 向左扫描时, beginState=black
//    temp==beginState, N++, 这样的话, 右边的棋子就会影响左边. 
//    上面说的是好的影响, 要是
//    W T B B B, 向左扫描是, temp=white, 向右扫描时, beginState=black, N--
//               向右扫描是, temp=black, 向左扫描时, beginState=white, N--
//    还有种情况, 要是temp或者beginState等于blank时, 左边和右边互相不影响. N不变.              
    int countA, countB, countC;
    int x, y;
    int N;
    int mark;

     
    if( m_gridState[pt.x][pt.y]!=blank)
    {
        m_gridMark[pt.x][pt.y]=0;
        return ;
    }
    //棋盘上已经有棋子了, 分数为0, 这样就不会下到这个地方.
    
    for( countA=1; countA<=2; countA++)        //   每个方向两次扫描    
    for( countB=1; countB<=8; countB++ )    //   扫描8个方向    
    {
        x=pt.x;        
        y=pt.y;
        N=0;
        if( countB%2==1)
            temp=blank;        //    一条直线要扫描两个方向
                            //   每扫描完一条直线时,将temp的值重新设置为blank.
                            //   保证不同直线上的棋子不会互相影响     

        for( countC=1; countC<=size; countC++)
       //   扫描一个方向, 虽然设置得要扫描的次数好大,不过通常扫描了4个格子就可以
        //   跳出循环     
        {
            if( countA==1 )
            {
               
                switch( countB )
                {
                case 1:        x--; break;
                case 2:        x++; break;
                case 3:        y--; break;
                case 4:        y++; break;
                case 5:        x--; y--; break;
                case 6:        x++; y++; break;
                case 7:        x++; y--; break;
                case 8:        x--; y++; break;
                }
                //    第一次扫描, 先左后右, 先上后下....   
             }
             else
             {
                 switch( countB )
                 {
                 case 1:        x++; break;
                 case 2:        x--; break;
                 case 3:        y++; break;
                 case 4:        y--; break;
                 case 5:        x++; y++; break;
                 case 6:        x--; y--; break;
                 case 7:        x--; y++; break;
                 case 8:        x++; y-- ; break;
                 }
                 //    第一次扫描, 先右后左, 先下后上....     
             }
             //       switch中 case的语句不可以顺便掉转
             
             if( !(x>=0 && x<size && y>=0 && y<size) )
                 break;
             //   遇到边界, 跳出循环    
             
             if( countC==1)
             {
                 beginState=lastState=m_gridState[x][y];
                //     初始这两个值,为扫描作准备     
                  
                 if( temp==beginState && temp!=blank)
                    N++;
                 if( temp!=beginState && temp!=blank && beginState!=blank)
                    N--;
                 temp=beginState;
                 //   同一直线的棋子互相影响, 见states temp下的注释
                 
                 if( beginState==blank )
                    mark=5;        
                 else mark=8;
                 //  根据附近第一个格子是不是空格, 记分, 见上面注释
                 
                 if( beginState==chessmanColor )
                     mark+=1;
                 //    假设轮到黑棋下的,要是附近也是黑棋,mark++, 使电脑
                 //    更具有攻击性.
                  
             }   

          
             if( m_gridState[x][y]!=lastState && lastState!=blank )
             {
                  if( m_gridState[x][y]!=blank )
                      N--;        
                  break;
                  //    黑被白挡, 或者白被黑挡, n--, 跳出循环
                  
             }
             //   遇到不同状态的格子挡路
             
             if( m_gridState[x][y]!=blank )
             {
                 N++;
                 lastState=m_gridState[x][y];
             }
             //    遇到己方棋子,n++, 因为要是黑遇白或者白遇黑, 在上面的语句已经
             //    处理过了, 并跳出了循环, 不会来到这个地方.
              

             if( N==4 )
                 break;  
             //        N为4时, 就跳出循环, 以免再遇到其他的棋子, 将N值减少    */

        }//endfor

         
        if( !(x>=0 && x<size && y>=0 && y<size) && N>0)
            N--;
        //    边界情形, N--, 相当于不同棋子挡路.
        
        m_gridMark[pt.x][pt.y]+=(mark*pow(3,N)-mark)/2+3*N;
        //    根据等比公式, 本来是 mark*pow(2,N)-mark.
        //    改成上面的形式,是为了让连得越多的棋,得到的分数越高.
        //   使得只要有一个N=4, 就会下到那个地方. 可以更改其他值试验,得出
        //    最合理的值.
         
   }//endfor_for
}

void chessboard::updateMarks(states chessmanColor)
{
    int row, col;
    initiGridMark();        //   底分     
    for( row=0; row<size; row++)
    for( col=0; col<size; col++)
        updateGridMark(point(row,col), chessmanColor);
}
//     程序只考虑了当前的形势, 不是成熟的策略.
//        另外, 每走一步棋, 都要更新整个棋盘的格子分数,也是不合理的,
//       因为只有和那步棋处在同一条直线上的格子才有可能改变分数.
//        要是根据这个条件来改写, 程序的效率会成倍数增加.
//        不过这样会会问题复杂, 对于这样的小程序,已经快到你看不见了,也没有必要

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -