c++五子棋必胜ai
2018-10-09 10:12:33 qq_36306833 阅读数 952

使用方法:
输入二维坐标0~14落子,输入-1 -1可以悔棋
注:输入7 7 即会开始在坐标7 7落子

#include<bits/stdc++.h>
using namespace std;
#define bas 15
#define Dep_limit 3
#define inf 0x3f3f3f
int score[2][6]={{0,5,25,4000,inf,inf},{0,0,2,50,4000,inf}};///0活,1死
int mo[8][2]={{0,1},{0,-1},{1,-1},{1,0},{1,1},{-1,-1},{-1,0},{-1,1}};
int dir[4][2]={{1,0},{1,1},{0,1},{1,-1}};
struct opmem///操作记录
{
    int x,y;
    int kind;//黑1,白-1,空0
};
stack<opmem>s;
bool vis[bas][bas];
struct Mat///描述局面
{
    short a[bas][bas];
    void out()
    {
        printf("  ");
        for(int i=0;i<bas;i++)printf("%2d",i);
        printf("\n");
        for(int i=0;i<bas;i++)
        {
            printf("%2d ",i);
            for(int j=0;j<bas;j++)
            {
                if(a[i][j]==0)printf(". ");
                else if(a[i][j]==1)printf("* ");
                else printf("O ");
            }
            printf("\n");
        }
    }
    void add(int x,int y,int col,int add)
    {
        if(add==-1)a[x][y]=0;
        else a[x][y]=col;
    }
    bool in(int x,int y)
    {
        return x>=0&&y>=0&&x<bas&&y<bas;
    }
    bool win5(int x,int y)///返回该棋子是否可以组成五子
    {
        for(int i=0;i<4;i++)
        {
            int l=0,r=-1;
            int mx=dir[i][0],my=dir[i][1];
            int lx=x,ly=y,rx=x,ry=y,col=a[x][y];
            while(in(lx,ly)&&a[lx][ly]==col)
            {
                l++;
                lx-=mx;ly-=my;
            }
            while(in(rx,ry)&&a[rx][ry]==col)
            {
                r++;
                rx+=mx;ry+=my;
            }
            if(l+r>=5)return 1;
        }
        return 0;
    }
    int val(int aim)///接下来aim下,计算局面好坏
    {
        int ret=0;
        int num[6][2]={0};
        for(int xcx=0;xcx<4;xcx++)
        {
            memset(vis,0,sizeof(vis));
            int col=0;
            int mx=dir[xcx][0],my=dir[xcx][1];
            for(int i=0;i<bas;i++) for(int j=0;j<bas;j++)
            {
                if(vis[i][j]||a[i][j]==0)continue;
                col=a[i][j];
                int p=0,limit=0,x=i,y=j;
                if(!in(x-mx,y-my)||a[x-mx][y-my]!=0)limit++;
                while( in(x,y) && a[x][y]==col )
                {
                    vis[x][y]=1;
                    p++;
                    x+=mx;
                    y+=my;
                }
                if(!in(x,y)||a[x][y]!=0)limit++;
                if(p>=5)
                    if(col==aim)return inf;
                    else return -inf;
                if(limit==2)continue;
                if(p>=4&&col==aim)return inf;
                num[p][limit]+=col;
            }
        }
        for(int j=0;j<2;j++) for(int i=2;i<6;i++)
        {
            ret+=num[i][j]*score[j][i];
        }
        return aim*ret;
    }
    bool colse(int x,int y)
    {
        for(int i=0;i<8;i++)
        {
            int tx=x+mo[i][0],ty=y+mo[i][1];
            if(in(tx,ty)&&a[tx][ty]!=0)return 1;
        }
        return 0;
    }
}now;
void outlin()
{
    for(int i=0;i<30;i++)printf("-");printf("\n");
}
int dfs(int x,int y,int frd,int col,int deep)//下(x,y)点成col色,能得到的最大评分,兄弟节点最大值为frd。
{
    if(deep==Dep_limit)
    {
        return -now.val(-col);
    }
    int ret=-inf;
    for(int i=0;i<bas;i++) for(int j=0;j<bas;j++)
    {
        if( now.a[i][j]!=0 || !now.colse(i,j) )continue;
        now.add(i,j,-col,1);
        if(now.win5(i,j))
        {
            now.add(i,j,-col,-1);
            return -inf;
        }
        ret = max( dfs(i,j,-inf,-col,deep+1), ret);
        now.add(i,j,-col,-1);
        if(ret>=inf)break;
        if(ret<frd){return inf;}///abcd剪枝
        frd=ret;
    }
    return -ret;
}

void work(int &x,int &y,int col)//传入下棋位置,颜色(黑1,白-1),传出决策位置
{
    int best=-inf*2;
    for(int i=0;i<bas;i++) for(int j=0;j<bas;j++)
    {
        if(now.a[i][j]==0){x=i;y=j;break;}
    }
    for(int i=0;i<bas;i++)
    {
        for(int j=0;j<bas;j++)
        {
            if( now.a[i][j]!=0 || !now.colse(i,j) )continue;
            now.add(i,j,-col,1);
            if(now.win5(i,j))
            {
                now.add(i,j,-col,-1);
                x=i,y=j;
                return;
            }
            if(now.val(col)!=inf)
            {
                int temp= dfs(i,j,-inf,-col,1);
                if(temp>best)
                {
                    best=temp;
                    x=i,y=j;
                }
            }
            now.add(i,j,-col,-1);
        }
    }
}
void outway()//输出棋局
{
    int x[200],y[200],cnt=0;
    while(!s.empty())
    {
        x[cnt]=s.top().x;
        y[cnt]=s.top().y;
        cnt++;
        s.pop();
        if(!s.empty())s.pop();
    }
    for(int i=cnt-1;i>=0;i--)
    {
        printf("%d %d\n",x[i],y[i]);
    }
}
int main()
{
    //test();
    int x,y;
    memset(now.a,0,sizeof(now.a));
    while(scanf("%d%d",&x,&y)!=EOF)
    {
        opmem temp;
        if(x==-2&&y==-2)break;
        if(x==-1&&y==-1)
        {
            printf("back:\n");
            //scanf("%d%d",&x,&y);
            now.add(s.top().x,s.top().y,s.top().kind,-1);
            s.pop();
            now.add(s.top().x,s.top().y,s.top().kind,-1);
            s.pop();
            now.out();
            continue;
        }
        if(!now.in(x,y)||now.a[x][y]!=0)
        {
            printf("error %d %d\n",x,y);continue;
        }
        now.add(x,y,1,1);
        now.out();outlin();
        temp.kind=1;    temp.x=x;   temp.y=y;
        s.push(temp);
        if(now.win5(x,y)){printf("you win\n");break;}

        work(x,y,1);
        now.add(x,y,-1,1);
        now.out();outlin();
        temp.kind=-1;    temp.x=x;   temp.y=y;
        s.push(temp);
        printf("%d %d\n",x,y);
        if(now.win5(x,y)){printf("ai win\n");}
    }
    outway();
    return 0;
}
2018-02-17 00:39:04 black_kyatu 阅读数 2586

设计思路:通过接口获取信息来确定颜色,通过set_chess函数来确定落点。

  • 对每个点位给出两种颜色棋子的打分,分别存在两个15*15的数组里,数组下标代表点的位置。
  • 确定最大值所在数组之后,遍历该数组找出所有最大值对应的位置,然后对这些位置统计另一种颜色的棋子的分数,再选取一次最大值,从而确定要落点的位置。
  • 打分函数的设计:在四个方向分别统计然后相加。对于某一个方向的分数统计,则分为正反两个方向进行,统计的时候如果有连成5个则直接返回一个最大值(最高分)。其他情况则按不同情况设置不同的权重,触发结束某一个方向上的统计的事件如下:遇到异色棋子;空白格子超过两个;遇到棋盘边界。其中遇到异色棋子和棋盘边界均视为一边被堵死,相比空白来说适当减分,而1个空白相比于完全连续则应再适当减分,最后取10的次幂,以保证不同情况的优先级,即不至于出现因为下到位置A可以形成4个活2而放弃下可以形成1个活4的位置B。
    具体代码如下:
#pragma once
#ifndef AI_H
#define AI_H
#include "renju.h"
#include <vector>
#include <math.h>

class Ai
{
public:
    Ai(chessboard &bd, state hm)
    {
        ms.set_color(hm);
        this->p_bd = &bd;
    }
    chess set_chess();

private:
    int evaluate(position pos, state color, position (*pf)(position ,bool ));//给出落子位置和方向移动函数,返回该落子位置在该方向上的评分

    int point(position pos, state color);//给出一个落子位置,返回该落子的得分

    void whole_points(int points[][15], state color );//给定颜色 ,记录该颜色棋子下在每一处的得分

    int best_posits(const int points[][15], position p_s[], int& count); //给出分数数组,找出最大值对应的位置(可能不止一个),返回分数最大值

    chess ms;
    const chessboard *p_bd;
};

//确定落子
chess Ai:: set_chess()
{
    int points_b[15][15];       //记录黑棋各落点分数
    int points_w[15][15];       //记录白棋各落点分数
    position best_b[20];        //记录黑棋最大分数对应的落点位置
    position best_w[20];        //记录白棋最大分数对应的落点位置
    int s_black = 0, s_white = 0;       //记录黑白棋分别的最大分数值
    int count_b = 0,count_w = 0;            //记录黑白棋最大分数对应的落点位置个数

    whole_points(points_b, black);
    whole_points(points_w, white);
    s_white = best_posits(points_w, best_w,count_w);
    s_black = best_posits(points_b, best_b,count_b);

    if( s_black > s_white )     //黑棋最高分高过白棋,在黑棋最高分对应的位置中选出白棋分数最大的位置落子
    {
    sb: int a[20];
        for(int i = 0;i < count_b;i++)
        {
            a[i] = point(best_b[i],white);
        }
        int max_w = MAX(a, count_b);
        for(int i = 0;i < count_b;i++)
        {
            if(a[i] == max_w)
            {
                ms.set_point(best_b[i]);
                return ms;
            }
        }
    }
    if( s_black < s_white )     //白棋最高分高过黑棋,在白棋最高分对应的位置中选出黑棋分数最大的位置落子
    {
    sw: int a[20];
        for(int i = 0;i < count_w;i++)
        {
            a[i] = point(best_w[i],black);
        }
        int max_b = MAX(a, count_b);
        for(int i = 0;i < count_w;i++)
        {
            if(a[i] == max_b)
            {
                ms.set_point(best_w[i]);
                return ms;
            }
        }
    }
    if( s_black == s_white )    
    {
        if(ms.get_color() == white)
            goto sw;
        if(ms.get_color() == black)
            goto sb;
    }
}

//给出分数数组,找出最大值对应的位置(可能不止一个),返回分数最大值
int Ai::best_posits(const int points[][15], position p_s[], int& count)
{
    int max_row[15];
    int max_all;
    for(int i = 0;i < 15;i++)
    max_row[i] = MAX(points[i],15);
    max_all = MAX(max_row,15);
    cout<<"maxall"<<max_all;
    count = 0;
    for(int i = 0;i < 15;i++)
    {
        for(int j =0;j < 15;j++)
        {
            if(points[i][j] == max_all)
            {
                position x(i,j);
                p_s[count] = x;
                count++;
            }
        }
    }
    return max_all;
}

//给定颜色 ,记录该颜色棋子下在每一处的得分
void Ai::whole_points(int points[][15], state color )
{
    for( int i =0;i < 15;i++)
    {
        for(int j = 0;j < 15;j++)
        {
            position temp(i,j);
            points[i][j] = point(temp,color);
        }
    }
}

//位置函数,用于上下移动棋子并判断是否越界
position up(position pos,bool dir)
{
    position r;
    if(dir)
    {
        while(pos.y > 0)
        {
            r.x = pos.x;
            r.y = pos.y - 1;
            return r;
        }
        throw 0;
    }
    else
    {
        while(pos.y < 14)
        {
            r.x = pos.x;
            r.y = pos.y + 1;
            return r;
        }
        throw 0;
    }
}

//位置函数,用于左右移动棋子并判断是否越界
position left(position pos,bool dir)
{
    position r;
    if(dir)
    {
        while(pos.x > 0)
        {
            r.x = pos.x - 1;
            r.y = pos.y;
            return r;
        }
        throw 0;
    }
    else
    {
        while(pos.x < 14)
        {
            r.x = pos.x + 1;
            r.y = pos.y;
            return r;
        }
        throw 0;
    }
}

//位置函数,用于左上右下移动棋子并判断是否越界
position left_up(position pos,bool dir)
{
    position r;
    if(dir)
    {
        while(pos.x > 0 && pos.y > 0)
        {
            r.x = pos.x - 1;
            r.y = pos.y - 1;
            return r;
        }
        throw 0;
    }
    else
    {
        while(pos.x < 14 && pos.y < 14)
        {
            r.x = pos.x + 1;
            r.y = pos.y + 1;
            return r;
        }
        throw 0;
    }
}

//位置函数,用于右上左下移动棋子并判断是否越界
position right_up(position pos,bool dir)
{
    position r;
    if(dir)
    {
        while(pos.x < 14 && pos.y > 0)
        {
            r.x = pos.x + 1;
            r.y = pos.y - 1;
            return r;
        }
        throw 0;
    }
    else
    {
        while(pos.x > 0 && pos.y < 14)
        {
            r.x = pos.x - 1;
            r.y = pos.y + 1;
            return r;
        }
        throw 0;
    }
}

int Ai::evaluate(position pos, state color, position (*pf)(position ,bool ))
{
    int sum = 0;
    position p_i = pos;
    int count = 0,mc = 1;
    bool flag = true;
    int c_blank = 0;
    state judge_t;

    try
    {
        do
        {
            p_i = pf(p_i, flag);
            judge_t = p_bd -> viewboard(p_i);
            if(judge_t == color)
            {
                if(c_blank == 1)
                {
                    count += 1;
                }
                else
                {
                    mc++;
                    if(mc == 5)
                        return 100000000000;
                    count += 2;
                }
            }
            else 
            {
                if(judge_t == blank)
                {
                    if(c_blank >= 1)
                        flag = false;
                    else
                    {
                        c_blank++;
                    }
                }
                else
                {
                    count-=2;
                    flag = false;
                }
            }
        }while(flag);
    }
    catch(int key)
    {
        flag = false;
        if(c_blank == 0)count-=2;
    }

    p_i = pos;
    int b_blank = 0;//记录另一半的空白格子
    try
    {
        do
        {
            p_i = pf(p_i, flag);
            judge_t = p_bd -> viewboard(p_i);
            if(judge_t == color)
            {
                if(b_blank == 1)
                {
                    count += 1;
                }
                else
                {
                    if(c_blank == 0 && b_blank == 0)
                        mc++;
                    if(mc == 5)
                        return 100000000000;
                    count += 2;
                }
            }
            else 
            {
                if(judge_t == blank)
                {
                if(b_blank >= 1)
                        flag = true;
                    else
                    {
                        b_blank++;
                    }
                }
                else
                {
                    count-=2;
                    flag = true;
                }
            }
        }while(!flag);
    }
    catch(int key)
    {
        if(b_blank == 0)count-=2;
        return pow(10,count);
    }
    return pow(10,count);
}
//给出一个落子位置,返回该落子的得分
int Ai::point(position pos, state color)
{
    if(p_bd -> viewboard(pos) != blank)
    {
        return 0;
    }

    position (*p_f)(position,bool) = NULL;
    int sum = 0;

    p_f = up;
    sum += evaluate(pos, color, p_f);
    p_f = left;
    sum += evaluate(pos, color, p_f);
    p_f = left_up;
    sum += evaluate(pos, color, p_f);
    p_f = right_up;
    sum += evaluate(pos, color, p_f);

    return sum;
}
#endif

其中所需要的头文件在上一篇文章中有提到:
http://blog.csdn.net/black_kyatu/article/details/79327680

2015-10-31 17:37:05 shenmirenLcy 阅读数 3307

         今天我想要分享一下我做五子棋AI的思路。因为在做这个之前,我没有接触过任何像这种类似的东西。通过这一次,我也算是有所了解,我的思路也是来自很多网络上的博客,看了很多,最终总结出了自己的这样一个。

         那我的五子棋是15*15的大小(一般也就是这样的一个大小)。我的AI算法要求每一次落子之后都要去计算每一个空暇的位置的“分值”,简单的说,我们需要一个存放棋子的数组,表示是否存放了棋子,还要一个计算每一个空格的数组来记录“分数”,这个分数是后期AI用来运算的基础,也是你AI难度控制的点。

         我现有的思路就是分两部分。首先是如果是玩家先落子,那么要求电脑AI随即在你落子的地方的任意一个方向,随机落子,这是第一步。接下来以后就正式进入到算法中去。

首先初始化你的分数数组,让他们全部为零。然后在每一次落子之后进行全盘的遍历,如果发现该处为空白,于是检查其四周八个方向(当然如果是边缘位置就相对修改,判断是否出了边界)。若在空白处,且发现在某一对角线方向发现有一个其他颜色的棋子,那么相对的给这个空白区域的分数数组加上一定的分值,然后继续往这个方向检测是否还有连续的同一颜色的棋子,若没有则检查其他方向或者检测下一个空白位置。若是还在同一方向上面找到了相同颜色的棋子,那么第二个棋子的出现,你可以给改空白处加上双倍的分值,表明这个空白位置更加重要。一次类推,继续检测。(PS因为最终AI棋子落在什么地方,依靠的是最后遍历整个分数数组,然后根据分数的高低来进行判断落子落在哪里的,在下面讲)。

         经过上一遍的遍历,每一次落子都会使得分数数组得到一些变化,每一次都会导致AI判断的变化。在这个基础上,每一次落子还要进行一次对自己本身棋子颜色的一个遍历,判断自己的情况,同时加分加在分数数组之中,这样一来,电脑就会根据自己的棋子的情况以及玩家的落子情况进行判断,哪一个地方更加适合落子。

         因为我是第一次做AI,网络上搜到的一些思想一般也是这种类似的遍历思想。理解了以后写代码就比较方便。最后可能会有一些点的分数是相同的,所以还有设置一下随机落子。把分数相同的地点随机落子。

         个人感觉AI的强弱是根据你每一次给他增加分数的多少来确定的。这个我的AI有时候也会抽风,不过一般情况比较正常,可能运气也占了一部分,当初设计加分的时候其实没想那么多,现在却发现好像还不错。

         大家要多去实践练习,多改改分数可能就会出来不错的AI了,o(^▽^)o。

         下面贴上我的代码!

 

void GameScene::Robot(int *x, int *y, int *Sum)
{
	ExWhile1 = true;
	if (*Sum == 1)
	{
		while (ExWhile1)
		{
			ChessOne(*x, *y);
			if (ch[*x][*y] == 2){ ExWhile1 = false; }
		}
		ch[*x][*y] = tp;													//记录这个点
		printpart(*x, *y, tp);												//打印出电脑AI第一次落子
		isTouch = true;
		tp++;
		tp = tp % 2;
	}
	else																    //从第2步开始,使用评分系统
	{
		Findscore(*x, *y);
	}
}


void GameScene::Findscore(int &x, int &y)									//查找评分最高的坐标
{
	srand((unsigned)time(NULL));
	int i, j, x1, x2, y1, y2, lx;
	int Max = 0;
	ChessScore();															//调用评分函数
	for (i = 0; i<15; i++)
	{
		for (j = 0; j<15; j++)
		{
			if (Score[i][j]>Max)
			{
				Max = Score[i][j];											//获取所有点中,评分最高的
				x1 = i;
				y1 = j;
			}
		}
	}
	x2 = x1; y2 = y1;
	for (i = 0; i<15; i++)													//可能的话,有评分相同的多个点
	{
		for (j = 0; j<15; j++)
		{
			if (Score[i][j] == Max&&i != x2&&j != y2)						//在这么多个相同分数的点中,随机找一个
			{
				lx = rand() % 10;
				if (lx<5)
				{
					x2 = i, y2 = j;
					break;
				}
			}
		}
	}
	if (x2 != x1 || y2 != y1)												//棋盘上有2个最高分
	{
		lx = rand() % 10;													//随机一个
		if (lx>6)
		{
			x = x1, y = y1;
		}
		else
		{
			x = x2, y = y2;
		}
	}
	else																	//棋盘上只有一个最高分
	{
		x = x1, y = y1;
	}
	Max = 0;																//清空最大值
	ch[x][y] = tp;															//记录这个点
	printpart(x, y, tp);													//打印出电脑AI落子
	if (winerValue==2)
	{
		isTouch = true;
	}
	
	tp++;
	tp = tp % 2;
}


inline void GameScene::ChessOne(int &x, int &y)								//玩家走第1步时的落子
{
	int i, j;
	srand((unsigned)time(NULL));											//随机数随着时间的改变而改变
	for (i = 0; i<15; i++)
	{
		for (j = 0; j<15; j++)
		{
			if (ch[i][j] == 0)												//如果找到了玩家的棋子,在它的8个方的任意一点落子
			{
				int lx = rand() % 7;
				if (lx == 0)
				{
					x = i + 1; y = j + 1;
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 1)
				{
					x = i + 1; y = j - 1;
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 2)
				{
					x = i - 1; y = j - 1;
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 3)
				{
					x = i - 1; y = j + 1;
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 4)
				{
					x = i - 1; y = j;										//上
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 5)
				{
					x = i; y = j - 1;										//左
					if (ch[x][y] == 2){ break; }
				}
				else if (lx == 6)
				{
					x = i; y = j + 1;										//右
					if (ch[x][y] == 2){ break; }
				}
				else
				{
					x = i + 1; y = j;										//下
					if (ch[x][y] == 2){ break; }
				}
			}
		}
	}
}

void GameScene::ChessScore()
{
	int x, y, i, j, k;														//循环变量
	int number1 = 0, number2 = 0;											//number用来统计玩家或电脑棋子连成个数
	int empty = 0;															//empty用来统计空点个数
	memset(Score, 0, sizeof(Score));										//把评分数组先清零
	for (x = 0; x<15; x++)
	{
		for (y = 0; y<15; y++)
		{
			if (ch[x][y] == 2)												//如果这个点为空 
			{
				for (i = -1; i <= 1; i++)
				{
					for (j = -1; j <= 1; j++)								//判断8个方向 
					{
						if (i != 0 || j != 0)								//若是都为0的话,那不就是原坐标嘛
						{
							//对玩家落点评分
							for (k = 1; i <= 4; k++)						//循环4次
							{												//这点没越界   且这点存在黑子(玩家)
								if (x + k*i >= 0 && x + k*i <= 14 && 
									y + k*j >= 0 && y + k*j <= 14 && 
									ch[x + k*i][y + k*j] == 0)
								{ 
									number1++;
								}
								else if (ch[x + k*i][y + k*j] == 2)			//这点是个空点,+1后退出
								{ 
									empty++; 
									break; 
								}     
								else										//否则是墙或者对方的棋子了 
								{ 
									break; 
								}        
							}
							for (k = -1; k >= -4; k--)						//向它的相反方向判断 
							{												//这点没越界   且这点存在黑子(玩家)
								if (x + k*i >= 0 && x + k*i <= 14 &&
									y + k*j >= 0 && y + k*j <= 14 && 
									ch[x + k*i][y + k*j] == 0)
								{ 
									number1++;
								}
								else if (ch[x + k*i][y + k*j] == 2)			//这点是个空点,+1后退出
								{ 
									empty++; 
									break; 
								}    
								else
								{ 
									break;
								}
							}
							if (number2 == 1)								//2个棋子  
							{ 
								Score[x][y] += 1; 
							}   
							else if (number1 == 2)							//3个棋子 
							{
								if (empty == 1)       
								{
									Score[x][y] += 5;						//有一个空点+5分  死3 
								}         
								else if (empty == 2)  
								{ 
									Score[x][y] += 10;						//有两个空点+10分 活3
								}  
							}
							else if (number1 == 3)							//4个棋子 
							{
								if (empty == 1)      
								{ 
									Score[x][y] += 20;						//有一个空点+20分  死4 
								}    
								else if (empty == 2)
								{ 
									Score[x][y] += 100;						//有2个空点+100分  活4
								}   
							}
							else if (number1 >= 4)    
							{ 
								Score[x][y] += 1000;						//对方有5个棋子,分数要高点,先堵
							}   

							empty = 0;										//统计空点个数的变量清零 

							//对电脑落点评分
							for (k = 1; i <= 4; k++)						//循环4次
							{												//这点没越界   且这点存在白子(电脑)
								if (x + k*i >= 0 && x + k*i <= 14 && 
									y + k*j >= 0 && y + k*j <= 14 && 
									ch[x + k*i][y + k*j] == 1)
								{ 
									number2++;
								}
								else if (ch[x + k*i][y + k*j] == 2)
								{ 
									empty++; break;							//空点
								}  
								else 
								{
									break;
								}
							}
							for (k = -1; k >= -4; k--)						//向它的相反方向判断 
							{
								if (x + k*i >= 0 && x + k*i <= 14 &&
									y + k*j >= 0 && y + k*j <= 14 && 
									ch[x + k*i][y + k*j] == 1)
								{ 
									number2++;
								}
								else if (ch[x + k*i][y + k*j] == 2)
								{ 
									empty++; break;
								}
								else
								{ 
									break;									//注释与上面玩家版相同
								}           
							}
							if (number2 == 0)      
							{ 
								Score[x][y] += 1;							//1个棋子
							}  
							else if (number2 == 1)   
							{
								Score[x][y] += 2;							//2个棋子 
							}     
							else if (number2 == 2)							//3个棋子
							{
								if (empty == 1)      
								{ 
									Score[x][y] += 8;						//死3
								}   
								else if (empty == 2) 
								{ 
									Score[x][y] += 30;						//活3 
								} 
							}
							else if (number2 == 3)							//4个棋子
							{
								if (empty == 1)      
								{ 
									Score[x][y] += 50;						//死4
								}   
								else if (empty == 2)  
								{ 
									Score[x][y] += 200;						//活4
								}   
							}
							else if (number2 >= 4)  
							{ 
								Score[x][y] += 10000;						//自己落在这点能形成5个,也就能胜利了,分数最高
							}  

							number1 = 0;									//清零,以便下次重新统计
							number2 = 0;									
							empty = 0;
						}
					}
				}
			}
		}
	}
}


2014-08-07 18:53:54 u014158072 阅读数 850

下午在开源网上看到一个用c语言写的五子棋的程序,于是想着自己也写一个。一来练习练习基础,二是上次写的贪吃蛇中,类和类之间的通讯方式采用的是依赖关系,但总感觉那样做各个类之间的独立性还不是太好,正好可以拿这个试试。其实上次的贪吃蛇中可以见到观察者模式的影子,只不过通常的观察者模式中有多个观察者,而那里只有一个观察者。五子棋的结果表明,类与类之间采取组合的方式更有效(至少在这里是这样的)。

五子棋编程中的重点是如何去判断四个方向上五子是否连成线。这里的思想是,以玩家当前放下的棋子为中心点,依次从四个方向上去判断。对于其中某个方向,比如水平方向,假设玩家放下的棋子的坐标为(x,y),则判断方法为:从点(x,y-4)开始,一直到点(x,y),看这五个点是否连成线,否则判断从点(x,y-3)一直到点(x,y+1)是否连成线,以此类推,如果从(x,y-4)到(x,y+4)这九个点中都没有连续的五个点连成线,说明水平方向上找不到五点成线。其他三个方向也可以按这种方式去判断。当然这里实现了对错误输入的处理。源代码见这里

补充一个从键盘上获取特定类型的数据方法:

int a,b;
cin>>a>>b;
while(cin.fail())
{
	cout<<"input error"<<endl;
	cin.clear();    //清除fail状态    
	cin.sync();   //清除缓冲区
	cin>>a>>b;
}



2018-12-15 11:41:00 weixin_34090643 阅读数 55

本人从事在线教育c++十年工作经验现在精心整理了一套从小白到项目实践开发各种学习资料如果你想学想加入我们请关注我在私信回复“编程”就可以领取学习资料!!群号:775356268

15121623-d9f7ca0a400e7c35

前言:有了用C++开发象棋及其AI的经验后,我就萌生了再用C++开发五子棋及其AI的想法。有了想法还等什么?付诸实施呗!

15121623-6cf723df7211f172

首先明确一盘五子棋需要什么属性:一盘五子棋有黑棋有白棋,与象棋不同的是,五子棋的棋子数目不是确定的,而是随着下棋随着增加的,所以,需要一个存放整盘棋棋子数目的整型变量num_of_Stone。五子棋下棋的有两方,黑方和白方,所以需要一个存放当前该谁走的布尔型变量_bBlackTurn,黑棋先行,顾名思义,初始化时应该将它的值置为true。还需要一个存放棋盘上存在的所有棋子的数组_s,由于不确定棋子的数目,所以将这个数组的类型设置为QVector。此外,为了更方便地处理棋盘信息,再引入存放棋盘信息的二维数组map[15][15],将横竖撇捺四个方向上线段化之后形成的一维数组的集合L。

给大家看一下棋盘类:

#ifndef BOARD_H

#define BOARD_H

#include

#include "Stone.h"

#include "Step.h"

#include

#include

class Board : public QWidget

{

Q_OBJECT

public:

//棋盘类的构造函数

explicit Board(QWidget *parent = 0);

//棋盘类的析构函数

~Board();

//棋子半径

int _r;

//棋子数目

int num_of_stone;

//存放所有棋子的数组

QVector _s;

//是不是应该黑棋走

bool _bBlackTurn;

//棋盘地图

int map[15][15];

//线段化的棋盘信息

struct

{

int line[15];

}L[21+15+15+21];

//画界面的函数

void paintEvent(QPaintEvent *);

//鼠标释放的响应函数

void mouseReleaseEvent(QMouseEvent *);

//点击屏幕上某个点的响应函数

void click(QPoint pt);

virtual void click(int id,int row,int col);

//绘制棋子的函数

void drawStone(QPainter& painter,int id);

//判断行走是否合法的函数

bool canMove(int row,int col);

//走棋函数

void moveStone(int moveid, int row, int col);

//悔棋函数

void reliveStone(int row,int col);

//输入行列坐标获取棋子id的函数

int getStoneId(int row,int col);

//判断谁胜谁负的函数

int whowin();

//将棋盘信息线段化

void turnLine();

//保存棋子的行棋信息的函数

void saveStep(int moveid, int row, int col, QVector& steps);

//获取用户点击位置的行列坐标的函数

bool getRowCol(QPoint pt,int &row,int &col);

//输入行列坐标返回像素坐标的函数

QPoint center(int row,int col);

//输入棋子的id 返回像素坐标

QPoint center(int id);

};

#endif // BOARD_H

首先使用棋盘类的构造函数初始化棋盘信息:

Board::Board(QWidget *parent) : QWidget(parent)

{

int i,j;

//将棋子个数初始化为0

num_of_stone=0;

//黑棋先行

_bBlackTurn=true;

//初始化地图 二维数组的元素的值为-1代表该位置上没有棋子 值为0代表该位置上有黑棋 值为1代表该位置上有白棋

for(i=0;i<15;i++)

for(j=0;j<15;j++)

{

map[i][j]=-1;

}

}

然后在paintEvent函数里绘制整个棋盘:

void Board::paintEvent(QPaintEvent *)

{

//初始化画笔painter

QPainter painter(this);

//初始化棋子直径

int d=30;

//初始化棋子半径

_r=d/2;

//整盘棋局的胜负信息 flag=1时黑方胜利 flag=2时白方胜利

int flag=whowin();

if(flag==1)

{

//将画笔painter的颜色设置为黑色

painter.setPen(Qt::black);

painter.drawText(rect(), Qt::AlignCenter, QStringLiteral("黑方胜利!"));

}

else if(flag==2)

{

//将画笔painter的颜色设置为红色

painter.setPen(Qt::red);

painter.drawText(rect(), Qt::AlignCenter, QStringLiteral("白方胜利!"));

}

else

{

//画10条横线

for(int i=1;i<=15;i++)

painter.drawLine(QPoint(d,i*d),QPoint(15*d,i*d));

//画9条竖线

for(int i=1;i<=15;i++)

painter.drawLine(QPoint(i*d,d),QPoint(i*d,15*d));

//绘制棋子

for(int i=0;i

{

drawStone(painter,i);

}

}

}

五子棋的棋盘共15行15列。paintEvent函数是用来画界面的,每调用一次update函数就会被自动调用,paintEvent函数里也需要绘制棋子,因此在paintEvent函数里点用了drawStone函数,drawStone函数在下面介绍。此外,调用whowin函数是用来判断棋局是否已经分出胜负的,如果已经分出胜负当然要展示对局结果给用户看啦,whowin函数在下文中介绍。

另外,棋子也需要单独划分成一类,给大家看一下棋子类:

#ifndef STONE_H

#define STONE_H

class Stone

{

public:

Stone();

bool _black;

int _row;

int _col;

int _id;

void init(int id);

};

#endif // STONE_H

五子棋里的棋子类就比象棋的简单多了,棋子的属性基本上只有只有行列坐标和棋子属于哪一方。​​​​​​

棋子有个初始化函数init:

void Stone::init(int id)

{

_id=id;

if(_id%2==0)

{

_black=true;

}

else

{

_black=false;

}

}

这里就有必要说一下对棋子类型的判断了。前面讲到了一个存放全盘棋子数目的变量num_of_Stone,这里就巧妙运用这个变量对棋子进行初始化,棋子的id就是该棋子放到棋盘上之前的num_of_Stone的值。举个例子,第一个下上去的棋子是黑棋,该棋子的id为该棋子放到棋盘上之前的num_of_Stone的值,也就是0,id除以2的余数为0,所以以id除以2的余数为标志,初始化棋子类型,余数为0的是黑棋。余数为1的是白棋;第二个下上去的棋子是白棋。该棋子的id便是1,id除以2的余数是1,也就初始化该枚棋子白棋。

上drawStone函数的源代码:

void Board::drawStone(QPainter& painter,int id)

{

painter.setPen(Qt::black);

if(_s[id]._black)

painter.setBrush(QBrush(Qt::black));

else

painter.setBrush(QBrush(Qt::white));

//画圆

painter.drawEllipse(center(id),_r,_r);

}

给大家看一下一系列鼠标事件响应函数的源代码:

void Board::mouseReleaseEvent(QMouseEvent *ev)

{

if(ev->button() != Qt::LeftButton)

{

return;

}

click(ev->pos());

}

void Board::click(QPoint pt)

{

int row, col;

bool bClicked = getRowCol(pt, row, col);

if(!bClicked) return;

if(canMove(row,col))

{

int id=num_of_stone;

click(id, row, col);

}

}

//获取用户点击位置的行列坐标 如果点击在合法范围内返回true 点击在合法范围外返回false

bool Board::getRowCol(QPoint pt,int &row,int &col)

{

for(row=0;row<15;row++)

for(col=0;col<15;col++)

{

QPoint c = center(row,col);

int dx = c.x() - pt.x();

int dy = c.y() - pt.y();

int dist = dx*dx + dy*dy;

if(dist<_r*_r)

return true;

}

return false;

}

getRowCol函数有两个引用的参数,因此该函数的功能不仅仅是返回了一个布尔类型的值,其主要功能还有确定用户点击位置所属的行列坐标。以所有行列坐标为圆心,假想一个个圆形区域,用户点击在圆形区域范围内便确定了一个行列坐标。

//输入行列坐标 返回像素坐标

QPoint Board::center(int row,int col)

{

QPoint ret;

ret.rx()=(col+1)*_r*2;

ret.ry()=(row+1)*_r*2;

return ret;

}

//输入棋子的id 返回像素坐标

QPoint Board::center(int id)

{

return center(_s[id]._row,_s[id]._col);

}

形参为id的center函数在drawStone函数中有调用。

void Board::click(int id, int row, int col)

{

moveStone(id,row,col);

update();

}

//行棋函数

void Board::moveStone(int moveid, int row, int col)

{

Stone p;

p.init(moveid);

//将新生成的棋子压入vector

_s.append(p);

_s[moveid]._row = row;

_s[moveid]._col = col;

//转换行棋的一方

_bBlackTurn=!_bBlackTurn;

//全盘棋子数目增加1

num_of_stone++;

//修改相应的地图信息

map[row][col]=moveid%2;

}

在moveStone函数里,要生成一个棋子并将其压入已存在的棋子的集合中,转换行棋方,将棋盘上已有的棋子数加1,最后修改相应的地图信息。

接下来,重点介绍将棋盘信息线段化的turnLine函数和判断棋局胜负的whowin函数,turnLine函数是whowin函数的基础。

turnLine函数的作用是在棋盘所有的可放棋子的位置个数大于等于5的横竖撇捺四个方向上分别形成单独的一维数组,并将所有的一维数组存放在board类的结构体数组L中。

//将棋盘坐标线段化

void Board::turnLine()

{

int i,row,col,pos,start_row=0,start_col=10;

int t;

for(i=0;i<21+15+15+21;i++)

{

//走捺

if(i>=0&&i<10)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col++,pos++)

{

L[i].line[pos]=map[row][col];

}

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_col--;

}

//走捺

else if(i<21)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col++,pos++)

L[i].line[pos]=map[row][col];

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_row++;

if(i==20)

{

start_row=14;

start_col=0;

}

}

//走竖

else if(i<21+15)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row--,pos++)

L[i].line[pos]=map[row][col];

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_col++;

if(i==21+14)

{

start_row=14;

start_col=14;

}

}

//走横

else if(i<21+15+15)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;col--,pos++)

L[i].line[pos]=map[row][col];

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_row--;

if(i==21+15+14)

{

start_row=0;

start_col=4;

}

}

//走撇

else if(i<21+15+15+10)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col--,pos++)

L[i].line[pos]=map[row][col];

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_col++;

}

//走撇

else if(i<21+15+15+21)

{

pos=0;

for(row=start_row,col=start_col;row<15&&row>=0&&col<15&&col>=0;row++,col--,pos++)

L[i].line[pos]=map[row][col];

for(t=pos;t<15;t++)

{

L[i].line[t]=-1;

}

start_row++;

}

}

}

一维数组中的元素值为-1代表没有棋子,值为0代表有黑子,值为1代表有白子。turnLine的过程就是初始化结构体数组L的过程。这就为判断胜负提供了遍历,判断胜负的时候只需要一行一行遍历结构体数组L,如果L中有形成五子连珠的情况则返回胜负信息并break。

上whowin函数的源代码:

//获取棋局胜负信息的函数

int Board::whowin()

{

//初始化结构体数组L

turnLine();

int sum_black,sum_white;

int i,j;

//看看是否有黑棋五子连珠的情况

for(i=0;i<21+15+15+21;i++)

{

sum_black=0;

for(j=0;j<15;j++)

{

if(L[i].line[j]==0)

sum_black++;

else

//计数器重新置零

sum_black=0;

if(sum_black>=5)

{

return 1;

}

}

}

//看看是否有白棋五子连珠的情况

for(i=0;i<21+15+15+21;i++)

{

sum_white=0;

for(j=0;j<15;j++)

{

if(L[i].line[j]==1)

sum_white++;

else

//计数器重新置零

sum_white=0;

if(sum_white>=5)

{

return 2;

}

}

}

return 0;

}

先调用turnLine函数初始化结构体数组L,再分别对黑白两方判断胜利与否。在判断的时候以行为单位,出现胜利的一方立刻跳出循环。若黑方胜利返回1;若白方胜利返回2;若两方都没有胜利,则最后返回0。

还有另外一种形式的whowin函数:

int Board::whowin()

{

int sum;

int i,j,k;

for(i=0;i

{

if(i%2==0)

{

sum=0;

for(j=_s[i]._row;j>=0&&j<15&&j<_s[i]._row+5;j++)

{

if(getStoneId(j,_s[i]._col)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._row;j>=0&&j<15&&j>_s[i]._row-5;j--)

{

if(getStoneId(j,_s[i]._col)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._col;j>=0&&j<15&&j<_s[i]._col+5;j++)

{

if(getStoneId(_s[i]._row,j)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._col;j>=0&&j<15&&j>_s[i]._col-5;j--)

{

if(getStoneId(_s[i]._row,j)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k<_s[i]._col+5;j++,k++)

{

if(getStoneId(j,k)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k>_s[i]._col-5;j++,k--)

{

if(getStoneId(j,k)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k<_s[i]._col+5;j--,k++)

{

if(getStoneId(j,k)%2==0)

sum++;

}

if(sum==5)

return 1;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k>_s[i]._col-5;j--,k--)

{

if(getStoneId(j,k)%2==0)

sum++;

}

if(sum==5)

return 1;

}

else

{

sum=0;

for(j=_s[i]._row;j>=0&&j<15&&j<_s[i]._row+5&&j<15;j++)

{

if(getStoneId(j,_s[i]._col)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._row;j>=0&&j<15&&j>_s[i]._row-5&&j<15;j--)

{

if(getStoneId(j,_s[i]._col)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._col;j>=0&&j<15&&j<_s[i]._col+5&&j<15;j++)

{

if(getStoneId(_s[i]._row,j)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._col;j>=0&&j<15&&j>_s[i]._col-5&&j<15;j--)

{

if(getStoneId(_s[i]._row,j)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k<_s[i]._col+5;j++,k++)

{

if(getStoneId(j,k)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j<_s[i]._row+5&&k>_s[i]._col-5;j++,k--)

{

if(getStoneId(j,k)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k<_s[i]._col+5;j--,k++)

{

if(getStoneId(j,k)%2==1)

sum++;

}

if(sum==5)

return 2;

sum=0;

for(j=_s[i]._row,k=_s[i]._col;j>=0&&j<15&&k>=0&&k<15&&j>_s[i]._row-5&&k>_s[i]._col-5;j--,k--)

{

if(getStoneId(j,k)%2==1)

sum++;

}

if(sum==5)

return 2;

}

}

return 0;

}

该函数的函数体较为庞杂,因为它采用了一个棋子一个棋子判断的方法,遍历到一个棋子,那么由该棋子向其左上、左、左下、右上、右、右下延伸判断,出现五子连珠则跳出循环。

最后给大家看一下程序效果图:

15121623-aac2df0d544114f6

本人从事在线教育c++十年工作经验现在精心整理了一套从小白到项目实践开发各种学习资料如果你想学想加入我们请关注我在私信回复“编程”就可以领取学习资料!!群号:775356268

五子棋c++代码~~AI只能还算比较高的。。。。。。。。

阅读数 4797

说明1:如果你用VS,可能会编译不过。建议改用codeblocks或者vc++。说明2:你可以直接点击屏幕落子。说明3:AI采用防守优先的方法。所以当你看到AI已经赢了却不这么走的情况,千万别惊讶~~它防守可是很不错的。。说明4:这个游戏,我走了5盘只赢了3盘。。。。。 #include#include#include#includeusingnamespac

博文 来自: ckcz123

C++五子棋算法Ai 简单的电脑智能博弈

阅读数 812

111,博客开通有一段时间了,因为太low所以一直没敢写博客,今天再次鼓足勇气把几个月前的五子棋Ai与大家分享一下,讲得不好请见谅(0v0)。 首先,我们要知道五子棋是两个人之间的博弈,双方轮流下子,肯定每次要将子下到最有用处的地方才是最有利于自己的(感觉说了堆废话。。)。我们将五子棋棋盘看成一个矩阵,用二维数组表示。然后我们可以在人落子后对场上所有空点进行评分,然后将电脑的棋子下在分最高的

博文 来自: qq_37629746

五子棋C++代码

阅读数 1464

小学期完成老师交代的五子棋游戏,现在把代码上传

博文 来自: u013240543

【C++】五子棋 简单实现

阅读数 345

文章目录游戏效果预览功能模块源代码展示未实现功能游戏效果预览功能模块  界面显示  游戏逻辑  判断输赢  游戏流程控制  玩家行动  电脑随机行动源代码展示界面展示类#pragmaonce#include&lt;stdio.h&gt;#include&lt;iostream&gt;#include&lt;vector&gt;#...

博文 来自: qq_41866437

c++小游戏——五子棋

阅读数 235

#include&lt;iostream&gt;#include&lt;iomanip&gt;#include&lt;cstring&gt;usingnamespacestd;constintX=21;//棋盘行数constintY=21;//棋盘列数charp[X][Y];//定义棋盘intm=0;//定义临时点,保存输入坐标int...

博文 来自: kkkksc03
没有更多推荐了,返回首页