c#五子棋ai

先来一张图先把。自己和自己下棋无聊的最高 境界

恩恩,还是我比较厉害一点的。

--------

我们首先看下界面,这是一个wpf 的程序,因为在界面写上更加的方便一些,当然里面的核心代码,可以跟换成语言。

下面我们将一点点的编写 一个下棋程序()

 

1、先部个局画个棋盘先呗。

这个没有什么好说的,直接上代码吧

<Window x:Class="wuziqi3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:wuziqi3"
        Title="五子棋V1.1" Height="950" Width="1050" Loaded="MainWindow_Loaded" >
    <Grid >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="auto"/>
        </Grid.ColumnDefinitions>
        <Grid x:Name="gridLeft" Grid.Column="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Vertical" Grid.Row="0" Width="100">
                <Button Click="Button_Click" Height="50" Content="从新开始"></Button>
                <ComboBox x:Name="xianxiacbb" SelectionChanged="xianxiacbb_Selected">
                    <ComboBoxItem>我要先下</ComboBoxItem>
                    <ComboBoxItem IsSelected="True">电脑先下</ComboBoxItem>
                    <ComboBoxItem >交替着来</ComboBoxItem>
                </ComboBox>
                <CheckBox x:Name="noConputer" Content="不要电脑哦" IsChecked="True"/>
                <Button x:Name="btnhuiqi" Click="Button_Click_2" Height="50" Content="悔棋"></Button>
                <!--<Button Click="Button_Click_3" Height="50" Content="导出"></Button>-->
            </StackPanel>
            <Grid Grid.Row="1">
                <TextBox x:Name="txtHistory"  />
                <Label x:Name="lblgailu" Content="" VerticalAlignment="Bottom"/>
            </Grid>
        </Grid>
        <Border x:Name="bor" Grid.Column="1" Height="900" Width="900" BorderBrush="Black" BorderThickness="2" Background="#C57747" MouseLeftButtonDown="Border_MouseLeftButtonDown">
            <Grid>
                <Canvas x:Name="canqipan"></Canvas>
                <Canvas x:Name="can">
                </Canvas>
                <Grid>
                    <ItemsControl x:Name="ItemsCtrlx" VerticalAlignment="Top">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel Orientation="Horizontal"></StackPanel>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Grid Height="60" Width="60">
                                    <Label Content="{Binding}" FontSize="15"></Label>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                    <ItemsControl x:Name="ItemsCtrly" HorizontalAlignment="Left">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Grid Height="60" Width="60">
                                    <Label Content="{Binding}" FontSize="15"></Label>
                                </Grid>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid>
                <Canvas x:Name="cancurpoint">
                    <Path x:Name="pathCurPoint" SnapsToDevicePixels="False" StrokeThickness="2" Data="M20,30 L40,30 M30,20 L30,40" Stroke="Black" />
                </Canvas>
            </Grid>
        </Border>
    </Grid>
</Window>

简单的对xaml 说明下,

      1)gridLeft 主要存放对 棋盘控制的一些按钮。里面的一些按钮的点击事件我们在后面进行添加。

      2)bor 是五子棋的整个棋盘,其中 canqipan 只是五子棋棋盘的条条框框,can 存放的是棋子,下面的2个itemscontrol 是2个记录或者调试用的编号,最下面的 pathcurpoint 其实是 记录当前落子的位置的,至于 canqipan 和can 为什么分开来,因为棋子在从新开始下的时候回对所有下面的子元素进行清空,但是棋盘不需要。

在xaml 只是简单的写了下界面的布局。我们需要在后台界面中编写,棋局的条条框框。还有参数的一些初始化。

        int qiziwidth = 30;
        int qiziheight = 30;
        int hei = 15;
        int wid = 15;
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            qiziheight = (int)this.bor.Height / hei;
            qiziwidth = (int)this.bor.Width / wid;
            this.bor.Height = hei * qiziheight;
            this.bor.Width = wid * qiziwidth;
            initqipan(wid, hei);

            rd = new Random((int)DateTime.Now.Ticks);

            shuixianxia();
        }

        void initqipan(int wid,int hei)
        {
            List<int> lix = new List<int>();
            for (int i = 0; i < wid; i++)
            {
                lix.Add(i);
                Line l = new Line() { X1 = qiziwidth / 2, X2 = qiziwidth / 2, Y1 = qiziwidth / 2, Y2 = bor.Width - qiziwidth / 2, Stroke = Brushes.Black };
                Canvas.SetLeft(l, (i * qiziwidth));
                canqipan.Children.Add(l);
            }
            for (int i = 0; i < hei; i++)
            {
                Line l = new Line() { Y1 = qiziheight / 2, Y2 = qiziheight / 2, X1 = qiziheight / 2, X2 = bor.Height - qiziheight / 2, Stroke = Brushes.Black };
                Canvas.SetTop(l, (i * qiziheight));
                canqipan.Children.Add(l);
            }
            Ellipse el = new Ellipse()
            {
                Height = 10,
                Width = 10,
                Fill = Brushes.Black
            };
            Canvas.SetLeft(el, (3.5 * qiziwidth) - el.Width / 2);
            Canvas.SetTop(el, (3.5 * qiziheight) - el.Height / 2);
            canqipan.Children.Add(el);
            Ellipse el2 = new Ellipse()
            {
                Height = 10,
                Width = 10,
                Fill = Brushes.Black
            };
            Canvas.SetLeft(el2, (3.5 * qiziwidth) - el.Width / 2);
            Canvas.SetTop(el2, (11.5 * qiziheight) - el.Height / 2);
            canqipan.Children.Add(el2);
            Ellipse el3 = new Ellipse()
            {
                Height = 10,
                Width = 10,
                Fill = Brushes.Black
            };
            Canvas.SetTop(el3, (3.5 * qiziheight) - el.Height / 2);
            Canvas.SetLeft(el3, (11.5 * qiziwidth) - el.Width / 2);
            canqipan.Children.Add(el3);
            Ellipse el4 = new Ellipse()
            {
                Height = 10,
                Width = 10,
                Fill = Brushes.Black
            };
            Canvas.SetTop(el4, (11.5 * qiziheight)-el.Height/2);
            Canvas.SetLeft(el4, (11.5 * qiziwidth)-el.Width/2);
            canqipan.Children.Add(el4);
            Ellipse el5 = new Ellipse()
            {
                Height = 15,
                Width = 15,
                Fill = Brushes.Black
            };
            Canvas.SetLeft(el5, (7.5 * qiziwidth) - el5.Width / 2);
            Canvas.SetTop(el5, (7.5 * qiziheight) - el5.Height / 2);
            canqipan.Children.Add(el5);
            ItemsCtrlx.ItemsSource = lix;
            ItemsCtrly.ItemsSource = lix;

            initqizi(wid, hei);
            inithuiqi();
        }

        void shuixianxia()
        {
            if ((xianxiacbb.SelectedIndex == 1) ||(xianxiacbb.SelectedIndex == 2 && curiscouputerfirst))
            {
                ConputerLoad(false);
            }
            curiscouputerfirst = !curiscouputerfirst;
        }
        bool curiscouputerfirst = true;
        void initqizi(int wid,int hei)
        {
            lli = new List<bool?[]>();
            for (int i = 0; i < wid; i++)
            {
                lli.Add(new bool?[hei]);
            }
        }
        static public Random rd;
        List<bool?[]> lli ;

做一些参数的说明

首先是全局变量,lli 当前棋盘的状态,他是一个二维数组吧,他的第一维表示棋子的x,第二维表示棋子的y 里面存放一个bool? 让棋子有3个属性  true false null ,我们人为的给他定义下, true 表示黑色棋子,false 表示白色棋子, null 表示这个位置没有落子

hei 和wid 表示棋子可以落子的范围,五子棋的棋盘是15*15 为了方便我将它设置为全局变量

函数的说明

initqipan 主要是对 canqipan 的条条框框还有上面的5个小圆点进行添加。

initqizi 主要是对所有lli 进行初始化, 并没有做删除can 里面内容的操作。

shuixianxia 就是设置 落子的循序。

代码写到这里的时候我们的准备工作做完了。后面我们需要对棋盘落子进行操作了。

2、落子

        bool isblack = true;
        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Point p = Mouse.GetPosition(this.bor);
            int x = (int)p.X / qiziwidth;
            int y = (int)p.Y / qiziheight;
            if(lli[x][y] != null)
            {
                return;
            }

            qiziPoint curpoint = new qiziPoint(x, y,isblack);

            if (!loadqizi(curpoint) && noConputer.IsChecked.Value)
            {
                ConputerLoad(!curpoint.Color);
            }
        }

这是一个 xaml 里面绑定的方法, 我们可以暂时先不去管ConputerLoad (这是电脑落子的一个函数)

我们先来看看

loadqizi 这个函数,这个函数主要提供了,对 棋盘上棋子的绘制,绘制结束后,对是否连成5同样颜色的五子进行判断。

哦哦 ,我在这边定义了2个类,qiziPoint ZeroPoint 2个类一个是带棋子颜色的一个是不带棋子颜色的。会做说明

    class ZeroPoint
    {
        public ZeroPoint()
        {
        }
        public ZeroPoint(int x, int y)
        {
            X = x;
            Y = y;
        }
        public ZeroPoint(ZeroPoint zp)
        {
            X = zp.X;
            Y = zp.Y;
        }
        public int X { get; set; }
        public int Y { get; set; }

        public static ZeroPoint operator +(ZeroPoint zp1, ZeroPoint zp2)
        {
            return new ZeroPoint(zp1.X + zp2.X, zp1.Y + zp2.Y);
        }
    }

    class qiziPoint
    {
        public qiziPoint(int x,int y,bool color)
        {
            this.X = x;
            this.Y = y;
            this.Color = color;
        }

        public int X { get; set; }
        public int Y { get; set; }
        public bool Color { get; set; }
        public static qiziPoint operator +(qiziPoint zp1, ZeroPoint zp2)
        {
            return new qiziPoint(zp1.X + zp2.X, zp1.Y + zp2.Y,zp1.Color);
        }

        public static bool operator ==(qiziPoint zp1, qiziPoint zp2)
        {
            return ((zp1.X == zp2.X)&& (zp1.Y == zp2.Y));
        }
        public static bool operator !=(qiziPoint zp1, qiziPoint zp2)
        {
            return ((zp1.X != zp2.X) || (zp1.Y != zp2.Y));
        }
    }

我们看看

        bool loadqizi(qiziPoint zp)
        {
            int x = zp.X;
            int y = zp.Y;
            lli[x][y] = zp.Color;
            historyload.Add(zp);

            Ellipse ell = new Ellipse() { Margin = new Thickness(qiziwidth * 1 / 20),Width = qiziwidth * 9 / 10, Height = qiziheight * 9 / 10 };

            if (zp.Color)
            {
                ell.Fill = Brushes.Black;
                pathCurPoint.Stroke = Brushes.White;
            }
            else
            {
                ell.Fill = Brushes.White;
                pathCurPoint.Stroke = Brushes.Black;
            }
            Canvas.SetLeft(pathCurPoint, x * qiziwidth);
            Canvas.SetTop(pathCurPoint, y * qiziheight);

            isblack = !isblack;
            Canvas.SetLeft(ell, (x * qiziwidth));
            Canvas.SetTop(ell, (y * qiziheight));
            RegisterName("el" + historyload.Count, ell);
            can.Children.Add(ell);
            string zhongwen = (zp.Color ? "黑" : "白");
            this.txtHistory.AppendText(zhongwen + ":" + x + "," + y + "\r\n");
            if (finishLoad(zp))
            {
                this.txtHistory.AppendText(zhongwen + "赢了");
                MessageBox.Show( zhongwen + "赢了");
                return true;
            }
            return false;
        }
        bool finishLoad(qiziPoint zp)
        {
            bool tempcol = zp.Color;

            qiziPoint zpTemp = zp;
            int halfpoint = lzp.Count() / 2;
            for (int curpoint = 0; curpoint < halfpoint; curpoint++)
            {

                int curindex = 0;
                qiziPoint tzp;
                for (int i = 0; i < 4; i++)
                {
                    tzp = zp + lzp[curpoint];
                    if (!pointishefa(tzp))
                        break;
                    if ((lli[tzp.X][tzp.Y] != tempcol))
                    {
                        break;
                    }
                    curindex++;
                    zp = tzp;
                }
                zp = zpTemp;
                for (int i = 0; i < 4; i++)
                {
                    tzp = zp + lzp[curpoint + halfpoint];
                    if (!pointishefa(tzp))
                        break;
                    if ((lli[tzp.X][tzp.Y] != tempcol))
                    {
                        break;
                    }
                    curindex++;
                    zp = tzp;
                }
                zp = zpTemp;
                if (curindex >= 4)
                {
                    return true;
                }
            }
            return false;
        }

loadqizi很简单,对鼠标点击的xy 进行再对应位置添加一个 颜色的园,当然可以用图片啦。并且修改lli 里面值的属性。并且移动pathCurPoint 这个来显示 当前下子的位置。并且调换了下当前落子的颜色,并且在结束的时候调用 finishLoad 

finishLoad 这个函数主要用来,当前落子的是否连成了5个子。 这里有一个lzp  主要是对一个棋子的八个方向进行迭代循环。

        List<ZeroPoint> lzp = new List<ZeroPoint>() {
                        new ZeroPoint(-1,0),
                        new ZeroPoint(-1,-1),
                        new ZeroPoint(0,-1),
                        new ZeroPoint(1,-1),
                        new ZeroPoint(1,0),
                        new ZeroPoint(1,1),
                        new ZeroPoint(0,1),
                        new ZeroPoint(-1,1)
                        };

lzp 有着一定规律 lzp[0] 和lzp[4] 是刚好相反的,这很容易明白,一个子左边 同色的2个右边同色的2个,加上自己就是5个了,没有必要同一边有4个的情况才算5个吧。所以我们在只用循环lzp 的一半,另一半在在循环 里面 + 4 再次循环即可,如果里面2个小循环得到同样颜色的子大于等于4  就表示成功,(加上自己刚好是5) zpTemp 是用于迭代位置保存传进来棋子的位置。(我感觉代码写的比我说的可能清楚点吧)。

写到这里的时候,已经可以打开,2个人下棋的目的了。

但是本篇的目的才刚刚开始。。。我们需要写一个可以下棋的电脑ai程序来。。。

 

我们回到上面的

ConputerLoad 这是电脑落子的一个函数,

我们思考一个问题,

我们下棋 为什么下这里的优先级是什么呢?

1】为了达到5子,然后是为了达到4子,然后是3子,然后又2子,

2】那么问题来了,假如我们是电脑(毕竟我们要写一个可以下五子棋的电脑嘛。。)玩家当前是3个子了,电脑下完当前的才是3个子了, 那么我们优先的循序应该是选择防守,

3】还有一点,是这样的,我们下棋将要凑成了4个子了,但是这一边又一边已经被被人下了,我们下下去并不能胜利,但是如果下下去 2遍都没有被下,那么我们很可能胜利。

总结上面的3点,可以得到一个优先级,

五子》活四》死四》活3》死3》活2》死2》活1》死1

    //下下去后的状态,也就是,
    enum qijutype:int
    {
        wu = 9,
        huosi = 8, //4*2
        sishi = 7, //4*2-1
        huosan = 6,//3*2
        sisan = 5,  //3*2
        huoer = 4,  //2*2
        sier = 3,   //2*2 -1
        huoyi = 2,  //1*2
        siyi = 1,   //1*2-1
    }

活 死就 是否有一边被堵死,如果有,就是死,如果没有就是活。(你会不会问

那么2边都被堵死了呢?  他的优先级是最低的。无论当前已经是几个了。五子除外)

并且,如果当前我达到了五子的优先级比你达到了五子的优先级搞。,毕竟进攻才是最好的防守嘛。

说了这么多我们来写代码吧。

        List<qiziPoint> historyload = new List<qiziPoint>();

        private void ConputerLoad(bool color)
        {
            var mhl = historyload.Where((c) => c.Color == color).ToList();
            var thl = historyload.Where((c) => c.Color == !color).ToList();

            List<qiziPoint> mmosttype = new List<qiziPoint>();
            List<qiziPoint> tmosttype = new List<qiziPoint>();
            qijutype mqjt = mabytest(mhl, ref mmosttype);
            qijutype tqjt = mabytest(thl, ref tmosttype);

            this.lblgailu.Content = (int)mqjt + ":" + mmosttype.Count + "/" + (int)tqjt + ":" + tmosttype.Count;
            qiziPoint tzp;
            if (mmosttype.Count == 0)
            {
                //要一个虚拟的。然后计算,效果。
                #region temp 随机给个位置被
                if (tmosttype.Count == 0)
                {
                    tzp = new qiziPoint(7, 7, isblack);
                }
                else
                {
                    var ttts = tmosttype[rd.Next(lzp.Count())];
                    tzp = new qiziPoint(ttts.X, ttts.Y, color);
                }

            }
            else
            {
                if (tqjt > mqjt)
                {
                    //var tt = Conputerdanglu(tmosttype[0]);
                    tzp = new qiziPoint(tmosttype[0].X, tmosttype[0].Y, mhl[0].Color);
                }
                else
                {
                    int rdnet = rd.Next(mmosttype.Count());
                    tzp = mmosttype[rdnet];
                }
            }
            loadqizi(tzp);
            #endregion
        }

这里我们定义一个,historyload 来记录历史下子的位置,可能在loadqizi 中就出现过。

开始我们或许所有的我们下过棋子的位置,和别人下过棋子的位置

然后获取我们下的位置最高的级别是什么下什么地方(mabytest),如果我们的级别比对方高,就下我们的,如果我们的级别比对方低,就下对方的位置,让他没有地方可以走(这就是防守啦)

这里有一个小插入,如果我们没下怎么办,我的处理很简单,看下对方有没有下,如果有,就他的8个方向随便放一个就好了,如果对方也没有,那就固定下7,7(也就是中间拉)

最后的loadqizi 也就是落子就不多说了。

我们详细看看mabytest 这个函数吧。

 

2个参数, 历史下过的位置,还有个返回值参数,表示高的类型可以下的位置。

        private qijutype mabytest(List<qiziPoint> mhl, ref List<qiziPoint> mosttype)
        {
            List<qiziPoint> comvirload1 = new List<qiziPoint>();
            foreach (var h in mhl)
            {
                foreach (var fzp in lzp)
                {
                    qiziPoint tqzp = h + fzp;
                    if (!pointishefa(tqzp))
                        continue;
                    if (lli[tqzp.X][tqzp.Y] == null && (comvirload1.Where(cv => cv == tqzp).ToList().Count == 0))
                    {
                        comvirload1.Add(tqzp);
                    }
                }
            }
            //可以先不要。下一步的。深度什么的再说吧。

            qijutype myqjt = qijutype.siyi;
            foreach (var qzp in comvirload1)
            {
                qijutype qjtt = visLoadqiziMax(qzp);
                if (myqjt == qjtt)
                {
                    mosttype.Add(qzp);
                }
                else if (myqjt < qjtt)
                {
                    myqjt = qjtt;
                    mosttype.Clear();
                    mosttype.Add(qzp);
                }
            }
            return myqjt;
        }

先获取了下,根据历史下过的位置,判断出,后面可以下的位置(有效的),并排除了重复的位置,突然来个最边边肯定不科学也不可能

然后调用visLoadqiziMax 进行对下这个位置最高类型的获取。继续看visLoadqiziMax 吧

        private qijutype visLoadqiziMax(qiziPoint zp)
        {
            List<qijutype> vislist = visLoadqizi(zp);
            qijutype tqjt = qijutype.siyi;
            foreach(var vis in vislist)
            {
                if(vis > tqjt)
                {
                    tqjt = vis;
                }
            }
            return tqjt;
        }

        private List<qijutype> visLoadqizi(qiziPoint zp)
        {
            List<qijutype> vislist = new List<qijutype>();
            qiziPoint zpTemp = zp;
            int halfpoint = lzp.Count() / 2;

            for (int curpoint = 0; curpoint < halfpoint; curpoint++)
            {
                bool issi = false;
                int curindex = 0;
                qiziPoint tzp;
                //bool iskuazi = false;
                for (int i = 0; i < 4; i++)
                {
                    tzp = zp + lzp[curpoint];
                    if (!pointishefa(tzp))
                        break;
                    if ((lli[tzp.X][tzp.Y] == !zp.Color))
                    {
                        issi = true;
                        break;
                    }
                    else if (lli[tzp.X][tzp.Y] == null)
                    {
                        break;
                    }
                    curindex++;
                    zp = tzp;
                    
                }
                zp = zpTemp;
                for (int i = 0; i < 4; i++)
                {
                    tzp = zp + lzp[curpoint + halfpoint];
                    if (!pointishefa(tzp))
                        break;
                    if ((lli[tzp.X][tzp.Y] == !zp.Color))
                    {
                        if (issi)
                        {
                            curindex = 0;
                            break;
                        }
                        else
                        {
                            issi = true;
                        }
                        break;
                    }
                    else if (lli[tzp.X][tzp.Y] == null)
                    {
                        break;
                    }
                    curindex++;
                    zp = tzp;
                }
                zp = zpTemp;
                vislist.Add((qijutype)((curindex + 1) * 2 - (issi? 1:0)));
            }
            return vislist;
        }

visLoadqiziMax 这个并不是重点,

visLoadqiziMax 这个函数里面只是处理了,visLoadqizi 返回过来的最高类型并返回。

visLoadqizi 这个函数,乍一看怎么和,finishLoad 这么像,道理也很简单,当然是根据怎么赢怎么判断啦。

只不过visLoadqizi 这个函数的返回值是一个最高的类型,而不是true false,

里面加了一个issi的 表示,表示,是否一遍被堵死了,如果是的话,那么他就是一个死的4,如果没有那么他就是活的4,如果2遍都被堵死了,那么他就是0了最低的登记。

其实代码还有一些的的比如边上的按钮。

第一次写讲解的代码,不是很习惯。就这样吧。好东西拿出来分享分享嘛。道理说起来很简单,写起来调试起来的话,会比较的麻烦。最重要的是有一个清晰的思路。

后面吧,源码加上吧。其实已经放了所有的程序源码了。

 

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值