关注

简易五子棋(包含开始、双人对战、简易AI、悔棋、认输、判断输赢)不含禁手

简易五子棋

先上效果图
在这里插入图片描述

一、问题分析

  1. 五子棋游戏分析:
    五子棋作为较为普遍且简易的娱乐游戏,受到众多人的热爱,且五子棋AI也是一个较为容易实现的AI。下面我们先来分析游戏规则。(哈哈,虽然大家都知道,但我还是想写写)双方分别使用黑白两色棋子,下在棋盘横线交叉处,先连成五子者胜利。(黑棋禁手啥的规则在我的程序里没加,就不赘述了)。

  2. 程序分析:
    (1)首先,五子棋开始,我们需要一个棋盘,15*15的棋盘,需要黑白棋子。
    (2)其次,我们需要实现棋子顺序的改变,就是实现先下黑棋,再下白棋,然后实现一个基本的修正功能,就是通过点击交叉点周围的位置,使棋子下到交叉处。
    (3)再之后呢,有了棋子棋盘,(其实这个时候已经能进行下棋了,自己判断胜负,哈哈),但是呢,我们接下来需要加一个判断输赢的功能。
    (4)接下来,我们就来丰富我们的五子棋游戏,加一些功能键,例如重新开始,悔棋,认输,计时啥啥啥的,只有你想不到,没有你做不到,哈哈。
    在这里插入图片描述

    (5)最后来一个高级点的,就是实现人机对战,实现AI下棋。

二、模块分析

  1. 棋盘棋子模块
    棋盘嘛就用直线画就好,横线15条,竖线15条,棋子也就两个,可以画得花哨一点,比如3D棋子,也可以简单一点就用填充圆就好。博主画了一个黑色3D棋子,白的没画。(这里继承了我之前的一个画板,实现直线的重绘,可以去翻一翻我的之前有关画板的博客)。创建一个二维数组,存放棋子,用1代表黑棋,2代表白棋,0代表没棋。用count变量来计算到谁下棋了,以及记录下了第几颗棋子了。

以下是窗体代码

public void outUI(){
		//设置标题
		this.setTitle("五子棋");
		this.setSize(1680,1380);
		this.setLayout(null);
		JButton btn = new  JButton();
		JButton btn1 = new  JButton();
		JButton btn2 = new  JButton();
		JButton btn3 = new  JButton();
		JButton btn4 = new  JButton();
		btn.setBounds(1340, 780, 210, 65);
		btn1.setBounds(1340,860,210, 65);
		btn2.setBounds(1340,940,210, 65);
		btn3.setBounds(320,1200,210, 65);
		btn4.setBounds(780,1200,210, 65);
		//获取一个图片
		ImageIcon square=new ImageIcon(this.getClass().getResource("JButton1.jpg"));
		ImageIcon square1=new ImageIcon(this.getClass().getResource("JButton.jpg"));
		ImageIcon square2=new ImageIcon(this.getClass().getResource("JButton2.jpg"));
		ImageIcon square3=new ImageIcon(this.getClass().getResource("JButton3.jpg"));
		ImageIcon square4=new ImageIcon(this.getClass().getResource("JButton4.jpg"));
		
		//设置图片的大小
		square.setImage(square.getImage().getScaledInstance(210, 65, 0));
		square1.setImage(square1.getImage().getScaledInstance(210, 65, 0));
		square2.setImage(square2.getImage().getScaledInstance(210, 65, 0));
		square3.setImage(square3.getImage().getScaledInstance(210, 65, 0));
		square4.setImage(square4.getImage().getScaledInstance(210, 65, 0));
		//把图片放到按钮上	
		btn.setIcon(square);
		btn1.setIcon(square1);
		btn2.setIcon(square2);
		btn3.setIcon(square3);
		btn4.setIcon(square4);
		btn.setText("开始");
		btn1.setText("悔棋");
		btn2.setText("认输");
		btn3.setText("人机对战");
		btn4.setText("人人对战");
		this.add(btn);
		this.add(btn1);
		this.add(btn2);
		this.add(btn3);
		this.add(btn4);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setVisible(true);
		MyGameListen bn=new MyGameListen();
		this.addMouseListener(bn);
		btn.addActionListener(bn);
		btn1.addActionListener(bn);
		btn2.addActionListener(bn);
		btn3.addActionListener(bn);
		btn4.addActionListener(bn);
		Graphics f= this.getGraphics();
		AI Ai=new AI();
		AI2 Ai2=new AI2();
		AIPlus Ai3=new AIPlus();
		bn.ai=Ai;
		bn.ai2=Ai2;
		bn.ai3=Ai3;
		bn.f1=f;
		Ai.f1=f;
		Ai2.f1=f;
		Ai3.f1=f;
		bn.sn=this;
		Ai.arr2=bn.arr;
		Ai2.arr2=bn.arr;
		Ai3.arr=bn.arr;
		arragain=bn.arr;
		showtime a=new showtime();
		a.count1=bn.count;
	}
	int[][] arragain;
	@Override
	public void paint(Graphics g) {
		// TODO Auto-generated method stub
		super.paint(g);
		//加背景图
		ImageIcon square5=new ImageIcon(this.getClass().getResource("game.jpeg"));
		ImageIcon square6=new ImageIcon(this.getClass().getResource("title.jpg"));
		square5.setImage(square5.getImage().getScaledInstance( 1200,1162,0));
		square6.setImage(square6.getImage().getScaledInstance( 366,683,0));
			g.drawImage(square5.getImage(),40,60,this);
			g.drawImage(square6.getImage(),1280,100,366,683,this);
		for(int i=1;i<=15;i++)
		{
			g.drawLine(80, i*80, 1200, i*80);
			g.drawLine(i*80,80,i*80,1200);
		}
		g.drawLine(60,60 , 1220, 60);
		g.drawLine(60,60 , 60, 1220);
		g.drawLine(60, 1220, 1220,1220);
		g.drawLine(1220, 1220, 1220,60);
		g.fillOval(630, 630, 20, 20);
		g.fillOval(310, 950, 20, 20);
		g.fillOval(310, 310, 20, 20);
		g.fillOval(950, 950, 20, 20);
		g.fillOval(950, 310, 20, 20);
		// 绘制棋盘
		
		
		
		MyGameListen bn1=new MyGameListen();
		bn1.f1=g;
		for(int i=0;i<arragain.length;i++) {
		for(int j=0;j<arragain.length;j++)
		{
			if(arragain[i][j]==1)
			{
			Blackchessman(i*80-40,j*80-40,g);
			}
			else if(arragain[i][j]==2)
			{
			whitechessman(i*80-40,j*80-40,g);
			}
		}
		}
		
	}
	public void Blackchessman(int x,int y,Graphics g)
	{
		for(int i=0;i<80;i++)
		{
		Color c=new Color(i*3,i*3,i*3);
		g.setColor(c);
		g.fillOval(x+i/3, y+i/3, 80-i, 80-i);
		}
	}
	public void whitechessman(int x,int y,Graphics g)
	{
		g.setColor(Color.white);
		g.fillOval(x,y,80,80);
	}

以下是鼠标监听器的代码,这里有部分变量没有给出,在文章末尾会附上完整代码

public void mousePressed(MouseEvent e) 
	{

		x1=e.getX();y1=e.getY();
		m=correct(x1);
		n=correct(y1);
		if(x1<=1240&&y1<=1240) 
		{
			if(arr[m/80][n/80]==0)
			{
			if(count%2!=0)
			{
				x1=e.getX();y1=e.getY();
				m=correct(x1);
				n=correct(y1);
				Blackchessman(m-40,n-40);
				arr[m/80][n/80]=1;
				arr1[m/80][n/80]=count;
				count++;
				if(	gobangiswin.isWin(arr, m/80, n/80)) {
					JOptionPane.showMessageDialog(null, "黑棋WIN!!");
				}
				//ai下棋
				if(who==2) {
				ai3.playchess();
				arr[ai3.q][ai3.w]=2;
				arr1[ai3.q][ai3.w]=count;
				count++;
				if(	gobangiswin.isWin(arr,ai3.q ,ai3.w )) {
					JOptionPane.showMessageDialog(null, "白棋WIN!!");
				}
				}
			}
			else
			{
				x1=e.getX();y1=e.getY();
				m=correct(x1);
				n=correct(y1);
				whitechessman(m-40,n-40);
				arr[m/80][n/80]=2;
				arr1[m/80][n/80]=count;
				count++;
					if(	gobangiswin.isWin(arr, m/80, n/80)) {
						JOptionPane.showMessageDialog(null, "白棋WIN!!");
					}
			}
			}
			else
				return;
		}
		else {
			return;}
	}
  1. 位置修正
    这个功能其实有很多种实现的方法,可以根据自己的棋盘位置啥的进行修正。
public int correct(int x)
	{
		a=x/80;
		b=x%80;
		if(b<=40)
		{
			return a*80;
		}
		else
			return (a+1)*80;
	}
  1. 输赢判断
    输赢判断的思路,大概是以下思路,通过点击,下了最后一颗棋子以后,判断该棋子是否使游戏结束了,就是下完这颗棋子后是否成功构成五子连珠。因为输赢的判断你 只需要判断最后一步即可,因为输赢的胜负就在最后一步。所以,通过判断最后一颗棋子的八个方向是否构成五子连珠即可。

以下是判断输赢的代码。

public class GobangIsWin {
	//判断输赢函数
			// 横向 
		public int left_right(int[][] arr,int x,int y) {
			int con=1;
			//向右遍历
			for(int i=x+1;i<arr.length;i++)
			{
				if(arr[x][y]==arr[i][y]){
					con++;
				}
				else
					break;
			}
			//向左遍历
			for(int i=x-1;i>0&&i<arr.length;i--)
			{
				if(arr[x][y]==arr[i][y]) {
					con++;
				}
				else
					break;
			}
			return con;
		}
		// 纵向 
		public int high_low(int[][] arr,int x,int y)
		{
			int con=1;
			//向下遍历
			for(int i=y+1;i<arr[0].length;i++)
			{
				if(arr[x][y]==arr[x][i]) {
					con++;
				}
				else
					break;
			}
			//向上遍历
			for(int i=y-1;i>0&&i<arr[0].length;i--)
			{
				if(arr[x][y]==arr[x][i]) {
					con++;
				}
				else
					break;
			}
			return con;
		}
		// 左斜
		public int towQudrant_three(int[][] arr,int x,int y)
		{
			int con=1;
			int i=x,j=y;
			//向右上遍历
			 for(int m=i+1,k=j-1;m<arr.length&&k>0&&m<i+5&&k>j-5;k--,m++)
			 {
				 if(x==15||y==1) {
					 break;
				 }
				 else if(arr[x][y]==arr[m][k])
					 con++;
				 else
					 break;
			 }
			 //向左下遍历
			 for(int m=i-1,k=j+1;k<arr.length&&m>0&&k<i+5&&m>j-5;k++,m--) 
			 {
				 if(x==1||y==15) {
					 break;
				 }
				 if(arr[x][y]==arr[m][k])
					 con++;
				 else
					 break;
			 }
			 return con;
		}
		// 右斜 
		public int one_fourwin(int[][] arr,int x,int y) 
		{
			int con=1;
			int i=x,j=y;
			 //向左上遍历
			 for(int m=i-1,k=j-1;m>0&&k>0&&m>i-5&&k>j-5;k--,m--)
			 {
				 if(x==1||y==1) {
					 break;
				 }
				 if(arr[x][y]==arr[m][k])
					 con++;
				 else
					 break;
			 }
			 //向右下遍历
			 for(int m=i+1,k=j+1;k<arr.length&&m<arr.length&&m<i+5&&k<j+5;k++,m++)
			 {
				 if(x==15||y==15) {
					 break;
				 }
				 if(arr[x][y]==arr[m][k])
					 con++;
				 else
					 break;
			 }
			 return con;
		}
		
		public  boolean isWin(int[][] arr,int x,int y) {
			
			if(left_right(arr, x, y)>=5||high_low(arr, x, y)>=5||towQudrant_three(arr, x, y)>=5||one_fourwin(arr, x, y)>=5) {
				return true;
			}else {
				return false;
			}
		}
}

4.我们再来加一点功能,这里有悔棋,重新开始,认输的功能。

重新开始:我们清空我们存放棋子的数组即可,然后重新绘制棋盘,将之前的棋盘覆盖即可。

public void start() {
		sn.repaint();
		for(int i=0;i<arr.length;i++)
		{
			for(int j=0;j<arr.length;j++) {
				arr[i][j]=0;
			}
		}
		count=1;
	}

悔棋:悔棋的思路:悔棋,我们可以和开始一样,先把之前的棋盘覆盖掉,然后去除存放数组里的最后一个棋子即可,然后在新的棋盘上重新放下棋子(除了最后一个棋子)。

public void huiqi()
	{
		sn.repaint();
		//去除最后一颗棋子
		for(int i=0;i<arr1.length;i++)
		{
			for(int j=0;j<arr1.length ;j++)
			{
				if(arr1[i][j]==count-1)
				{
					arr1[i][j]=0;
					arr[i][j]=0;
				}
				if(arr1[i][j]==count-2&&who==2)
				{
					arr1[i][j]=0;
					arr[i][j]=0;
				}
			}
		}
		//重放棋子
		for(int i=0;i<arr.length;i++)
		{
			for(int j=0;j<arr.length ;j++)
			{
				if(arr[i][j]==1)
				{
					Blackchessman(i*80-40,j*80-40);
				}
				else if(arr[i][j]==2)
				{
					whitechessman(i*80-40,j*80-40);
				}
			}
		}
		//控制下一颗棋子颜色
		if(who==2) {
			count=count-2;
		}
		else if(who==1)
		{
			count=count-1;
		}
	}

认输功能:这个很简单就不说了,判断一下到谁下点击了认输就谁输就好。

5.最后来一个高级的,实现AI的功能,人机对战。
这里有很多种实现人工智能的方法:
(1)权值法
(2)博弈树
(3)机器学习
博主现在水平有限,只会权值算法,等博主学会后面两种方法再来加~

权值法:这里用到了权值表,创建一个新的二维数组chessvalue[][]用于存放该点的权值。

 HashMap<String,Integer> map = new HashMap<String,Integer>();
 map.put("1",10);
 map.put("11",100);
 map.put("111",1000);
 map.put("1111",10000);

这里的HashMap类是一种特殊的类,可以通过map.get(),将字符串放进get方法中,通过比较map中的所有字符串,找到与之相对应的权值,并返回这个值,这里指的注意的是,这里返回的数据类型不是int型,而是Integer型,但是不必担心,这个类型也能实现累加。

接下来是查找棋型,通过查找该位置的八个方向的棋型,并对八个方向的权值进行累加。最后通过找出最大的权值的位置,将棋子下到这个位置就好,这样就基本实现AI功能了。

保存棋局的方法:通过String类型的拼接功能实现棋局的保存。

注:在写这部分AI的时候,对于部分棋型没有考虑到,难度水平大概处于低阶至中阶,对于部分难以通过遍历八个方向进行查找的,采用了联合算法。例如,对于“22022”这样的棋型,显然需要通过左右两边的棋型综合考虑,这里采用了联合算法,遍历完左右(上下)后进行一次联合判断。

AI代码

public class AIPlus {
	int arr[][]=null;
	int weightArray[][]=new int [16][16];
	Graphics f1=null;
	//棋子相连情况的划分
	 HashMap<String,Integer> map = new HashMap<String,Integer>();//设置不同落子情况和相应权值的数组
		public AIPlus() {
			
			//被堵住
			map.put("01", 17);//眠1连
			map.put("02", 12);//眠1连
			map.put("001", 17);//眠1连
			map.put("002", 12);//眠1连
			map.put("0001", 17);//眠1连
			map.put("0002", 12);//眠1连
			
			map.put("0102",17);//眠1连,15
			map.put("0201",12);//眠1连,10
			map.put("0012",15);//眠1连,15
			map.put("0021",10);//眠1连,10
			map.put("01002",19);//眠1连,15
			map.put("02001",14);//眠1连,10
			map.put("00102",17);//眠1连,15
			map.put("00201",12);//眠1连,10
			map.put("00012",15);//眠1连,15
			map.put("00021",10);//眠1连,10
	 
			map.put("01000",21);//活1连,15
			map.put("02000",16);//活1连,10
			map.put("00100",19);//活1连,15
			map.put("00200",14);//活1连,10
			map.put("00010",17);//活1连,15
			map.put("00020",12);//活1连,10
			map.put("00001",15);//活1连,15
			map.put("00002",10);//活1连,10
	 
			//被堵住
			map.put("0101",65);//眠2连,40
			map.put("0202",60);//眠2连,30
			map.put("0110",65);//眠2连,40
			map.put("0220",60);//眠2连,30
			map.put("011",65);//眠2连,40
			map.put("022",60);//眠2连,30
			map.put("0011",65);//眠2连,40
			map.put("0022",60);//眠2连,30
			
			map.put("01012",65);//眠2连,40
			map.put("02021",60);//眠2连,30
			map.put("01102",65);//眠2连,40
			map.put("02201",60);//眠2连,30
			map.put("00112",65);//眠2连,40
			map.put("00221",60);//眠2连,30
	 
			map.put("01010",75);//活2连,40
			map.put("02020",70);//活2连,30
			map.put("01100",75);//活2连,40
			map.put("02200",70);//活2连,30
			map.put("00110",75);//活2连,40
			map.put("00220",70);//活2连,30
			map.put("00011",75);//活2连,40
			map.put("00022",70);//活2连,30
			
			//被堵住
			map.put("0111",1500);//眠3连,100
			map.put("0222",1400);//眠3连,80
			
			map.put("01112",1500);//眠3连,100
			map.put("02221",1400);//眠3连,80
			
			map.put("01101",10000);//活3连,130
			map.put("02202",8000);//活3连,110
			map.put("01011",10000);//活3连,130
			map.put("02022",8000);//活3连,110
			map.put("01110", 10000);//活3连
			map.put("02220", 8000);//活3连
			
			map.put("01111",300000);//4连,300
			map.put("02222",350000);//4连,280
		}
		public void printArray(int[][] arr) {
			for(int i=1;i<arr.length;i++)
			{
				for(int j=1;j<arr.length ;j++)
				{
					System.out.print(arr[i][j]+" ");
				}
				System.out.println();
			}
		}
		public Integer unionWeight(Integer a,Integer b ) {
			//必须要先判断a,b两个数值是不是null
			if((a==null)||(b==null)) return 0;
			//一一
		    else if((a>=10)&&(a<=25)&&(b>=10)&&(b<=25)) return 60;
			//一二、二一
			else if(((a>=10)&&(a<=25)&&(b>=60)&&(b<=80))||((a>=60)&&(a<=80)&&(b>=10)&&(b<=25))) return 800;
			//一三、三一、二二
			else if(((a>=10)&&(a<=25)&&(b>=140)&&(b<=1000))||((a>=140)&&(a<=1000)&&(b>=10)&&(b<=25))||((a>=60)&&(a<=80)&&(b>=60)&&(b<=80)))
				return 300000;
			//二三、三二
			else if(((a>=60)&&(a<=80)&&(b>=140)&&(b<=1000))||((a>=140)&&(a<=1000)&&(b>=60)&&(b<=80))) return 300000;
			else return 0;
		}
		public void getvalue() {
		for(int i=0;i<arr.length;i++) {
			  for(int j=0;j<arr[i].length;j++) {
				  //首先判断当前位置是否为空
				  if(arr[i][j]==0) {
					  //往左延伸
					  String ConnectType="0";
					  int jmin=Math.max(0, j-4);
					  for(int positionj=j-1;positionj>=jmin;positionj--) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+arr[i][positionj];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueleft=map.get(ConnectType);
					  if(valueleft!=null) weightArray[i][j]+=valueleft;
					  
					  //往右延伸
					  ConnectType="0";
					  int jmax=Math.min(14, j+4);
					  for(int positionj=j+1;positionj<=jmax;positionj++) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+arr[i][positionj];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueright=map.get(ConnectType);
					  if(valueright!=null) weightArray[i][j]+=valueright;
					  
					  //联合判断,判断行
					  weightArray[i][j]+=unionWeight(valueleft,valueright);
					  
					  //往上延伸
					  ConnectType="0";
					  int imin=Math.max(0, i-4);
					  for(int positioni=i-1;positioni>=imin;positioni--) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+arr[positioni][j];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valueup=map.get(ConnectType);
					  if(valueup!=null) weightArray[i][j]+=valueup;
					  
					  //往下延伸
					  ConnectType="0";
					  int imax=Math.min(14, i+4);
					  for(int positioni=i+1;positioni<=imax;positioni++) {
						  //依次加上前面的棋子
						  ConnectType=ConnectType+arr[positioni][j];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置中
					  Integer valuedown=map.get(ConnectType);
					  if(valuedown!=null) weightArray[i][j]+=valuedown;
					  
					  //联合判断,判断列
					  weightArray[i][j]+=unionWeight(valueup,valuedown);
					  
					  //往左上方延伸,i,j,都减去相同的数
					  ConnectType="0";
					  for(int position=-1;position>=-4;position--) {
						  if((i+position>=0)&&(i+position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+arr[i+position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueLeftUp=map.get(ConnectType);
					  if(valueLeftUp!=null) weightArray[i][j]+=valueLeftUp;
					  
					 //往右下方延伸,i,j,都加上相同的数
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i+position>=0)&&(i+position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+arr[i+position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueRightDown=map.get(ConnectType);
					  if(valueRightDown!=null) weightArray[i][j]+=valueRightDown;
					  
					  //联合判断,判断行
					  weightArray[i][j]+=unionWeight(valueLeftUp,valueRightDown);
					  
					  //往左下方延伸,i加,j减
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i+position>=0)&&(i+position<=14)&&(j-position>=0)&&(j-position<=14))
						  ConnectType=ConnectType+arr[i+position][j-position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueLeftDown=map.get(ConnectType);
					  if(valueLeftDown!=null) weightArray[i][j]+=valueLeftDown;
					  
					  //往右上方延伸,i减,j加
					  ConnectType="0";
					  for(int position=1;position<=4;position++) {
						  if((i-position>=0)&&(i-position<=14)&&(j+position>=0)&&(j+position<=14))
						  ConnectType=ConnectType+arr[i-position][j+position];
					  }
					  //从数组中取出相应的权值,加到权值数组的当前位置
					  Integer valueRightUp=map.get(ConnectType);
					  if(valueRightUp!=null) weightArray[i][j]+=valueRightUp;
					  
					  //联合判断,判断行
					  weightArray[i][j]+=unionWeight(valueLeftDown,valueRightUp);
				  }
			  }
		  }
		}
		int q=0,w=0;
		public void playchess()
		{
			getvalue();
			  //取出最大的权值
			  int weightmax=0;
			  for(int i=0;i<arr.length;i++) {
				  for(int j=0;j<arr.length;j++) {
					  if(weightmax<weightArray[i][j]) {
						  weightmax=weightArray[i][j];
						  q=i;
						  w=j;
					  }
				  }
			  }
			  whitechessman(q*80-40, w*80-40);
				for(int i=0;i<weightArray.length;i++)
				{
					for(int j=0;j<weightArray.length;j++) {
						weightArray[i][j]=0;
					}
				}
		}
		public void whitechessman(int x,int y)
		{
			f1.setColor(Color.white);
			f1.fillOval(x,y,80,80);
		}
}

三、总结

本次是第一次做游戏类的项目,仍然存在许多不足,但是很开心最后成功了,然后最后AI部分的权值表以及联合算法借鉴了其他博主的思路,如有侵权,请联系我删除。后期我会再加上博弈树算法的思想。
在这里插入图片描述

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/yexiann/article/details/117629340

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--