Java第八课——实现五子棋

Java第八课——实现五子棋

上节课讲了数组,这节课利用数组完成五子棋,当然想完成其他的棋也可以借这节课的内容当作参考。
首先在完成作品之前,先明确思路。需要一个界面,监听器,以及可能用到的其他类。在窗体上需要画出棋盘和棋子。监听器中要MouseListener和ActionListener(按钮可以用于悔棋)。其他类可以是判断输赢的类,或是用于将鼠标点下的位置转换成棋盘坐标的方法

第一步:窗体和监听器的雏形

1、因为窗体上需要画出棋盘和棋子,所以在创造窗体时可以继承(extends)父类,更方便作图。另外在设置窗体和添加组件时可以直接用this表示当前窗体
2、创建一个二维数组用于记录棋子位置和种类(下面的代码中我创建的是18×18的棋盘,可自行调整)

public class Frame extends JFrame{
	int [ ] [ ] type=new int [18][18]; 
	public void show(){
		this.setSize(1000,1000);
		this.setVisible(true); 
		this.setLocationRelativeTo(null);//使窗体位置居中
	}
	public static void main(String[] args){
		Frame f=new Frame();
		f.show();
}

监听器,添加完监听器类之后记得在show()中添加监听器:this.addMouseListener(l);

public class Listener implements MouseListener{
	public void mousePressed(MouseEvent e){ }
	...//其他的方法用不到就省略了
}

那么要把棋盘和棋子画出来才算是完成雏形,因为继承了父类,那么要重写paint方法用于画棋盘和棋子

public class Frame extends JFrame{
	int [ ] [ ] type=new int [18][18];
	public void show(){ ... }
	public void paint(Graphics g){
		super.paint(g);//这条语句的作用是能在原有图像上继续画图
		//画棋盘
		for (int i=0;i<18;i++){//这里棋盘格子大小可以自己调整,我定的是50
			g.drawLine(75+i*50, 75, 75+i*50, 925);//竖线
			g.drawLine(75, 75+i*50, 925, 75+i*50);//横线
		}
	}
	public static void main(String[] args){ ... }
}

接下来画棋子,那么我们规定1表示黑棋,2表示白棋。因为type[ ] [ ]在初始化时全部元素都为0,那么当type=0时表示为空,没有落子;type=1时表示落了黑子;type=2时表示落了白子。

public void paint(Graphics g){
	super.paint(g);//这条语句的作用是能在原有图像上继续画图
	//画棋盘
	for (int i=0;i<18;i++){//这里棋盘格子大小可以自己调整,我定的是50
		g.drawLine(75+i*50, 75, 75+i*50, 925);//竖线
		g.drawLine(75, 75+i*50, 925, 75+i*50);//横线
	}
	for(int i=0;i<18;i++){//遍历数组
		for(int j=0;j<18;j++){
			if(t==1){//黑棋
				g.setColor(Color.BLACK);
				g.fillOval(75+i*50-10, 75+j*50-10, 20, 20);
			}else if(t==2){//白棋
				g.setColor(Color.WHITE);
				g.fillOval(75+i*50-10, 75+j*50-10, 20, 20);
			}
		}
	}
}

(不建议直接抄代码,要自己设计过棋盘大小和棋子大小才会对后面的操作得心应手)
到这里,棋子算是可以画出来了,只是还需要在监听器里获取坐标,那么需要x,y来存储坐标

public class Listener implements MouseListener{
	int x,y;
	public void mousePressed(MouseEvent e){
		x=e.getX();
		y=e.getY();
	}
}

雏形到这里就算完成了,下一步便是把棋子画在棋盘上,就要创建一个新的类,将x,y转换成棋盘上的坐标,并实现黑白子交替落子

第二步:Tool类的添加并实现落子

Tool类的作用便是将监听器中按下时获取的绝对坐标放进棋盘,先创建一个Tool类,在这个类中需要x,y来接收从监听器中传过来的按下时的x,y坐标。另外还需要numx,numy将转化后的坐标传回监听器

public class Tool{
	int x,y;
	int numx,numy;
	public void location(){
	}
}

那么问题来了
1、如何转化坐标,比如按下时的坐标是125,怎么判断应该在哪个点位上落子
2、有时按下的位置可能偏离棋盘上的点,那么就要一个区间来判断落子
3、如果没有按在棋盘上,按在了棋盘外,那么该如何判断,比如按下的点位置是(999,999)距棋盘比较远,超出落子区间,这时又怎么做?

那么一条一条解决。首先可以同时解决第1、2个问题
(这里提供一个思路,我以我自己的棋盘为例)
格子的x坐标区间:75-125,125-175,175-225,225,275,325
每个点落子x区间:50-100,100-150,150-200,200,250,300
落子区间

也就是说按下时x坐标是125时,就应该在第二个点落子;按下时x坐标时270时,应该在第五个点落子。
了解大致落子位置之后开始推理,首先棋盘最左端的落子区间距离窗体最左端距离为50(意思是只要x坐标大于50就进入可以落子的区间了),那么先把鼠标点下时的坐标减去50,再除以50取商,如果有余数则加一,如果没有则不用加。但因为在编程中以0为第一个点的话,就可以不用管余数了(那么50<=x<950都在区间内了)
第三个问题就只需要限制x,y的范围就好,在if中实现

public void location(){
	if(x>=50&&x<=950&&y>=50&&y<=950){
		numx=(x-50)/50;
		numy=(y-50)/50;
	}
}

解决了落子区间就要实现点击鼠标落子了,mousePressed中原有的基础上继续,这时要把获取的坐标传给Tool类,那么就要实例化对象,并且也需要numx,numy来接收Tool传回来的值

public class Listener implements MouseListener{
	int x,y;
	int numx,numy;
	public void mousePressed(MouseEvent e){
		x=e.getX();
		y=e.getY();
		Tool tool=new Tool();
		tool.location();
		numx=tool.numx;
		numy=tool.numy;
	}
}

接下来就又有几个问题
1、如何判断应该落白子还是黑子
2、如果点击位置本来就已经落子了,会出现什么冲突?
3、如果已经有一方胜利了,就应该不能再落子了,那么该如何知道或者在何时判断胜利?

第一个问题很好解决,建立一个int count用于计数,当count为偶数时落黑子,反之为白子。
第二个问题,可以在落子之前先判断type[numx][numy]是否为0来确定能不能落子,那么冲突便是当不能落子时,count不能增加,否则会出现同一颜色连续落子。
第三个问题可以在加入一个int win,当win不为0时便不能再落子了。而判断输赢的位置应是每一步落子完毕之后判断,至于判断方法后面会讲(win为1时黑子胜,2为白子胜)

public class Listener implements MouseListener{
	int x,y;
	int numx,numy;
	int count;
	int win;
	public void mousePressed(MouseEvent e){
		x=e.getX();
		y=e.getY();
		Tool tool=new Tool();
		tool.location();
		numx=tool.numx;
		numy=tool.numy;
		if(win==0){
			if(type[numx][numy]==0){
				if(count%2==0){//黑子
					type[numx][numy]=1;
				}else{
					type[numx][numy]=2;
				}
				count++;
			}
		}
	}
}

接下来就又有一个小问题,那就是点击棋盘并没有落子,但最小化窗体后再打开便会出现落子,因为窗体的paint方法只在出现窗体时调用了一次,后面点击之后并没有调用paint方法,最小化之后打开相当于打开窗体,便会调用一次paint。那么如何解决?用repaint方法,每次点击都会重新画一次,但repaint方法是窗体frame的方法,那么便需要传进来

public class Listener implements MouseListener{
	int x,y;
	int numx,numy;
	int count;
	int win;
	JFrame JFr;//创建一个空壳用于接收
	public void mousePressed(MouseEvent e){ ... }
}

在窗体的show方法中

public void show(){
	...
	Listener l=new Listener();
	this.addMouseListener(l);
	l.JFr=this;
}

于是在count++之前(后)调用repaint方法

public void mousePressed(MouseEvent e){
	...
	if(win==0){
		if(type[numx][numy]==0){
			...
			JFr.repaint();
			count++;
		}
	}
}

第三步:判断输赢

可以另外创造一个类,也可以在Listener类中创建一个新的方法
这里我以创建新方法为例

public void towin(){ }

那么我们知道,要想判断输赢是必须要遍历数组,把所有子的位置找出来来判断的,那么就要获取type数组

public class Listener implements MouseListener{
	...
	Frame JFr;//创建一个空壳用于接收
	int type[][]=new int[18][18];
	public void mousePressed(MouseEvent e){ ... }
	public void towin(){ }

在窗体的show方法中

public void show(){
	...
	Listener l=new Listener();
	this.addMouseListener(l);	
	l.JFr=this;
	l.type=type;
}

然后就可以开始写如何判断输赢了,首先用双重循环遍历数组

public void towin(){
	for(int i=0;i<18;i++){
		for(int j=0;j<18;j++){
		}
	}
}

赢有四种连起来的方法
1.横着5个 2.竖着5个 3.向左倾斜5个 4.向右倾斜5个
要注意的是不要越界:
1、横着时:j<=13才能保证type[i][j],type[i][j+1],type[i][j+2],type[i][j+3],type[i][j+4]中的type[i][j+4]不会越界
2、竖着时,同理i<=13使得type[i+4][j]不越界
3、左斜时:i<=13&&j<=13使得type[i+4][j+4]不越界
4、右斜时:i<=13&&j>=4使得type[i+4][j-4]不越界
那么找到注意的点之后便简单了,胜利可以用弹窗 JOptionPane.showConfirmDialog(null, “胜利”, “结束”, JOptionPane.CLOSED_OPTION) 做提醒

public void towin(){
	for(int i=0;i<18;i++){
		for(int j=0;j<18;j++){
			if(j<=13){
				if(type[i][j]==1&&type[i][j+1]==1&&type[i][j+2]==1&&type[i][j+3]==1&&type[i][j+4]==1){
					win=1;
				}else if(j<=13&&type[i][j]==2&&type[i][j+1]==2&&type[i][j+2]==2&&type[i][j+3]==2&&type[i][j+4]==2){			
					win=2;
				}
			}
			if(i<=13){
				if(type[i][j]==1&&type[i+1][j]==1&&type[i+2][j]==1&&type[i+3][j]==1&&type[i+4][j]==1){					
					win=1;
				}else if(i<=13&&type[i][j]==2&&type[i+1][j]==2&&type[i+2][j]==2&&type[i+3][j]==2&&type[i+4][j]==2){					
					win=2;
				}
			}
			if(i<=13&&j<=13){
				if(type[i][j]==1&&type[i+1][j+1]==1&&type[i+2][j+2]==1&&type[i+3][j+3]==1&&type[i+4][j+4]==1){
					win=1;
				}else if(i<=13&&j<=13&&type[i][j]==2&&type[i+1][j+1]==2&&type[i+2][j+2]==2&&type[i+3][j+3]==2&&type[i+4][j+4]==2){
					win=2;
				}
			}
			if(i<=13&&j>=4){
				if(type[i][j]==1&&type[i+1][j-1]==1&&type[i+2][j-2]==1&&type[i+3][j-3]==1&&type[i+4][j-4]==1){
					win=1;
				}else if(i<=13&&j>=4&&type[i][j]==2&&type[i+1][j-1]==2&&type[i+2][j-2]==2&&type[i+3][j-3]==2&&type[i+4][j-4]==2){
					win=2;
				}
			}
		}
	}
	if(win==1){
		JOptionPane.showConfirmDialog(null, "黑子胜", "结束", JOptionPane.CLOSED_OPTION);
	}else if(win==2){
		JOptionPane.showConfirmDialog(null, "白子胜", "结束", JOptionPane.CLOSED_OPTION);
	}
}

那么调用towin方法的时候便是落子之后

public void mousePressed(MouseEvent e){
	...
	if(win==0){
		if(type[numx][numy]==0){
			...
			JFr.repaint();
			towin()
			count++;
		}
	}
}

那么判断输赢就完成了,最后便是悔棋了

第四步:悔棋

同判断输赢一样,可以另写一个类,也可以在Listener类下面创造新方法
这里我以创建新方法为例

public void retract(){ }

那么在写之前要知道如何实现悔棋
1、上一步是谁走的?哪个位置?
2、当悔到不能再悔了如何处理?

1、要知道是谁走的就看count的值即可,因为每按一次count+1,所以要知道上一步谁走的就要先减一,记录位置可以用数组,创建两个新的一维数组,一个记录行位置,一个记录列位置。当悔棋时,先让count减一,再获取落子位置使type变为0,最后让记录位置的两个数组在当前位置清零
2、当count>0时可以悔棋,反之则弹出提示框
先创建两个一维数组:

public class Listener implements MouseListener{
	int x,y;
	int count=0;//控制黑白棋落子,偶数为黑棋
	int type[][]=new int[18][18];
	int row[]=new int[350];//在悔棋中记录行位置
	int col[]=new int[350];//在悔棋中记录列位置
	int numx,numy;
	JFrame JFr;
	int win;

然后再mousePressed函数中把落子位置放进去

public void mousePressed(MouseEvent e){
	x=e.getX();
	y=e.getY();
	Tool tool=new Tool();
	tool.location();
	numx=tool.numx;
	numy=tool.numy;
	row[count]=numx;
	col[count]=numy;
	if ...
}

最后便是再retract函数里实现

public void retract(){
	if(count>0){
		count--;
		type[row[count]][col[count]]=0;
		row[count]=0;//记得把原位置清零
		col[count]=0;
	}else{
		JOptionPane.showConfirmDialog(null, "不能再悔了", "提示", JOptionPane.CLOSED_OPTION);
	}
		JFr.repaint();//改变了棋子布局,要调用repaint
}

当然写完之后要记得添加按钮,以及给按钮添加动作监听器

public class Listener implements ActionListener,MouseListener{ ... }

show方法里添加按钮

public void show(){
	JButton btn=new JButton("悔棋");
	this.add(btn);
	btn.addActionListener(l);
	...
}

再在监听其中补充actionPerformed方法

public void actionPerformed(ActionEvent e){
	String action = e.getActionCommand();
	if ("悔棋".equals(action)) {
		retract();
	}
}

那么到这里五子棋就全部完成了,五子棋是棋类中最好实现的棋类,大家可以以这个为模板去实现其他棋类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值