五子棋开发(一)---------P2P对战

      专门学习java已经有一个月了,舍友说:“对女生来说,学技术是条不归路啊!”做画图板时不这么觉得,但做五子棋时就感受深刻了,不是因为难学,是因为坎坷,很容易放弃……尤其是在做P2C(人机对战)的时候,纠结于算法好久,单单是用最笨的权值法,我就在分类、赋值的时候,从不同的角度考虑了各种方法。之前写了一个版本,我称之为智商为负。后来,考虑了很久,这样的赋值方法很欠缺考虑,以后P2C完成时的总结时会,共享给大家见笑见笑。总之,P2C的算法,我还没有纠结出来,所以,先对P2P对战总结一下。

      五子棋的开发,之前自己在做,基本实现了可以相互交换放棋子,和判断输赢。但代码结构十分的混乱,排错也比较困难,需要重头梳理一遍思路。龙哥后来给我们梳理了一下各个类之间的关系,令我茅塞顿开啊。原来,每一个项目的不能着急开始,都需要整体的规划。

      混乱版的就不拿出来献丑了,就总结一下整理后的版本。

      一、面板的制作。

      用三块面板将窗体分为控制区、游戏区、菜单栏。

代码如下:

     1、面板类

 

 写道
// 初始化窗体Ailse五子棋
public void init(javax.swing.JFrame fccp) {
// 设置窗体相关信息
this.setTitle("Ailse五子棋");
this.setSize(750, 633);

this.setResizable(false);
this.setDefaultCloseOperation(3);


//creat layut-fl(flowlayout)
java.awt.BorderLayout fl=new java.awt.BorderLayout();
this.setLayout(fl);

// 创建并定义一个菜单栏
javax.swing.JMenuBar menubar= new FCChessmenubar();
this.add(menubar);

//creat a jpanel-playfield
FCChessplayfield playfield=new FCChessplayfield();
this.add(playfield);


//creat a jpanel-playcontrl
JPanel playcontrl=new FCChesscontrlfield(playfield,fccp);
this.add(playcontrl);

 

 2、控制面板

 

	public FCChesscontrlfield(FCChessplayfield playfield,JFrame fccp){
		this.setSize( 150, 600);//(150,600);
	    this.setBackground(Color.GRAY);
		this.setLayout(null);
	
	//creat some button to playcontrl
	javax.swing.JButton undo=new javax.swing.JButton("悔棋");
    undo.setBounds(650, 200, 60, 25);
    this.add(undo);
    undo.setActionCommand("undo");
    javax.swing.JButton giveup=new javax.swing.JButton("认输");
    giveup.setBounds(650, 300, 60, 25);
	this.add(giveup);
	giveup.setActionCommand("giveup");
	javax.swing.JButton rebegin=new javax.swing.JButton("重玩");
	rebegin.setBounds(650, 400, 60, 25);
	this.add(rebegin);
	rebegin.setActionCommand("rebegin");
	
	//创建一个鼠标监听器,监听控制面板
	FCChesscontrlfieldListener rbml=new FCChesscontrlfieldListener(playfield,fccp,p);
	giveup.addMouseListener(rbml);
	undo.addMouseListener(rbml);
	rebegin.addMouseListener(rbml);
}
}
 

 

 3、游戏面板

 

public class FCChessplayfield extends JPanel implements FCCconfig{
	public FCChessplayfield(){
	this.setBounds(0, 25, 600, 600);
	this.setBackground(Color.LIGHT_GRAY);
	 this.setLayout(null);}
	
	// 屏幕重绘
		public void paint(Graphics g) {
			super.paint(g);
			System.out.println("jpanel重绘了");
			// 在屏幕重绘时重绘棋盘网格
			drawchess(g);

			// 在屏幕重绘时,重绘棋子
			redrawp(g);

		}

		// 绘制棋盘
		public void drawchess(Graphics g) {
			System.out.println("网格重绘了");
			g.setColor(Color.black);
			for (int i = 0; i < 11; i++) {
				g.drawLine(FCCconfig.x0, FCCconfig.gsize * (i + 1), FCCconfig.xe,
						FCCconfig.gsize * (i + 1));
			}
			for (int j = 0; j < 11; j++) {
				g.drawLine(FCCconfig.gsize * (j + 1), FCCconfig.y0, FCCconfig.gsize
						* (j + 1), FCCconfig.ye);
			}
		}

		// 遍历二维数组,为棋盘所有点标记为0

		public void remark() {
			System.out.println("标记恢复为0了");
			for (int i = 0; i < FCCconfig.row; i++) {
				for (int j = 0; j < FCCconfig.rank; j++) {
					CCmark[i][j] = 0;
				}
			}
		}

		// 棋子的重绘
		public void redrawp(Graphics g) {
			System.out.println("棋子重绘了!");
			for (int i = 0; i < FCCconfig.row; i++) {
				for (int j = 0; j < FCCconfig.rank; j++) {
					System.out.print(CCmark[i][j] + "\t");
					if (CCmark[i][j] != 0) {
						if (CCmark[i][j] == 1) {
							g.setColor(Color.BLACK);
						} else if (CCmark[i][j] == -1) {
							g.setColor(Color.BLUE);
						}
						g.fillOval((i + 1) * FCCconfig.gsize - FCCconfig.CCPsize
								/ 2, (j + 1) * FCCconfig.gsize - FCCconfig.CCPsize
								/ 2, 40, 40);
					}
				}
				System.out.println();
			}

		}
	}
 

 

 4、菜单面板

 

public FCChessmenubar(){
		this.setBounds(0, 0, 750, 25);
        //为菜单栏加布局
		java.awt.FlowLayout menubarfl=new java.awt.FlowLayout(0);
		this.setLayout(menubarfl);	
		
	
	// add menu to menubar
	javax.swing.JMenu File = new javax.swing.JMenu("File");
	File.setPreferredSize(new Dimension(100, 25));
	this.add(File);

	// add menuitem to menu-File
	javax.swing.JMenuItem open = new javax.swing.JMenuItem("open");
	open.setSize(100, 25);
	javax.swing.JMenuItem save = new javax.swing.JMenuItem("save");
	save.setSize(100, 25);
	javax.swing.JMenuItem exit = new javax.swing.JMenuItem("exit");
	exit.setSize(100, 25);
	File.add(open);
	File.add(save);
	File.add(exit);
	//System.out.println("FILE按钮添加了");
	
	javax.swing.JMenu about = new javax.swing.JMenu("about");
	about.setPreferredSize(new Dimension(100, 25));
	this.add(about);
	//add menuitem to menu-about
	javax.swing.JMenuItem aboutgame = new javax.swing.JMenuItem("aboutgame");
	aboutgame.setSize(100, 25);
	javax.swing.JMenuItem abouteditor= new javax.swing.JMenuItem("abouteditor");
	abouteditor.setSize(100, 25);
	about.add(aboutgame);
	about.add(abouteditor);
	

	//创建动作监听器,监听按钮aboutgame
	ActionListener alis = new ActionListener(){
		public void  actionPerformed(ActionEvent e){
			javax.swing.JFrame aboutgame=new javax.swing.JFrame("about game");
			aboutgame.setSize(300, 300);
			aboutgame.setBackground(Color.blue);
			javax.swing.JLabel jlab1=new javax.swing.JLabel("made by Ailse");
			jlab1.setBounds(25, 25, 200,200 );
			aboutgame.add(jlab1);
			aboutgame.setVisible(true);
		}
		
	};
	aboutgame.addActionListener(alis);
	}
}

 

效果图:


五子棋开发(一)---------P2P对战_第1张图片

 

 

 

 

       二、监听

    监听时相应的,分为三个部分,和三块面板对应。如上图示。

但是游戏面板的监听却放在了窗体类里了。

// 创建并鼠标监听器,通过鼠标监听棋盘面板
		FCChessplayfieldListener pillms = new  FCChessplayfieldListener(g, fccp,CCmark);
		playfield.addMouseListener(pillms);

 这是因为,鼠标监听器需要画布,但画布的定义要在窗体可见后,而游戏面板却要在窗体定义时就要定义。所以只能放在窗体类中定义监听器。同样的问题出现在了窗体重绘时,这时调用了超父类的paint()方法-----super.paint();

    1、控制面板监听

 

public void mousePressed(MouseEvent e) {
			JButton btn = (JButton)e.getSource();
			String command=btn.getActionCommand();
			if(command=="rebegin"){
				cp.remark();
		        cp.repaint();
		    }
			if(command=="giveup"){
				javax.swing.JOptionPane.showMessageDialog(jf2, "你太厉害了,甘拜下风啊!");
			}
			if(command=="undo"){
				javax.swing.JOptionPane.showMessageDialog(jf2, "呵呵呵!下错位置了,我要悔棋!");
				int x=0, y=0;
				if(list.size()-1<1){
					javax.swing.JOptionPane.showMessageDialog(jf2, "请按重玩按钮!!");
//				System.out.println(x+","+y+","+CCmark[x][y]);
				//System.out.println("棋子数目:"+list.size());
//				System.out.println("重新标记棋盘了");
				
				}else{
					p=list.get(list.size()-1);
				    x=p.getx();
					y=p.gety();
					CCmark[x][y]=0;
					list.delect(list.size()-1);
					p=list.get(list.size()-1);
			        x=p.getx();
				    y=p.gety();
				    CCmark[x][y]=0;
				    list.delect(list.size()-1);
				}
				cp.repaint();
			}

		}

 

 对于重玩的实现,还是比较费力的。要想悔棋,就要让我们的计算机记得下棋的顺序和下的棋子的颜色。我用了一个棋子类将每放的一颗棋子的坐标和棋子颜色记录,然后用队列(大小可变数组)顺序存放这些棋子类的棋子对象。

 

public class pill {
	private int x;
	private int y;
	private int color;
	//标记
	public pill(int x, int y, int color){
		this.color=color;
		this.x=x;
		this.y=y;
	}
	public int getx(){	
		return x;
	}
	public int gety(){
		return y;
	}
 

 

    2、游戏面板监听

 

public void mouseReleased(MouseEvent e) {
		// 定义一个变量用于控制放置黑棋和白棋交替放置
		// count为奇数时,黑棋下;count为偶数时,蓝棋下
		x1 = e.getX();
		y1 = e.getY();
		// 通过count,判断黑棋行还是白棋行
		if (count % 2 == 1) {
			g.setColor(Color.BLACK);
			t = 1;
		} else if (count % 2 == 0) {
			g.setColor(Color.blue);
			t = -1;
		}
		// 判断点击位置是否在棋盘上
		if ((x1 > 50 && x1 < 550) && (y1 > 50 && y1 < 550)) {

			// 判断点击位置,并在离的最近的交叉点放置棋子
			// FCCconfig.gsize:格宽;FCCconfig.CCPsize/2:棋半径;FCCconfig.gsize/2:格中间的位置
			// x1,y1为鼠标按下处点的坐标
			// x2,y2为离按下处最近的交叉点的坐标

			if ((x1 % FCCconfig.gsize) < FCCconfig.gsize / 2) {
				x2 = x1 - (x1 % FCCconfig.gsize) - FCCconfig.CCPsize / 2;
			} else if ((x1 % FCCconfig.gsize) >= FCCconfig.gsize / 2) {
				x2 = x1 + (FCCconfig.gsize - (x1 % FCCconfig.gsize))
						- FCCconfig.CCPsize / 2;
			}
			if ((y1 % FCCconfig.gsize) < FCCconfig.gsize / 2) {
				y2 = y1 - (y1 % FCCconfig.gsize) - FCCconfig.CCPsize / 2;
			} else if ((y1 % FCCconfig.gsize) >= FCCconfig.gsize / 2) {
				y2 = y1 + (FCCconfig.gsize - (y1 % FCCconfig.gsize))
						- FCCconfig.CCPsize / 2;
			}
			i = x2 / FCCconfig.gsize;
			j = y2 / FCCconfig.gsize;
			if (CCmark[i][j] == 0) {

				// 绘制棋子
				g.fillOval(x2, y2, FCCconfig.CCPsize, FCCconfig.CCPsize);
				count++;
				CCmark[i][j] = t;
				pill pill = new pill(i,j,t);
				list.add(pill);

 棋子是通过在对应点画圆实现的。但是画圆时需要的参数是(x1,y1,height,wide),且x1,y1是圆外切矩形的左上顶点。而鼠标监听只能获得按下的点,且我们不是总能恰好按到那个点。于是我们就要得到棋盘上纵横线的交点。我选取了比较笨的方法,就是用小学数学加减乘除法,将点击时获取的点换算到我们需要的离其最近的交叉点。

 

    3、菜单栏监听 

    操作较少,暂不赘述。

 

三、判断输赢

    判断输赢放在了游戏面板监听器中。

 

// 判断是否获胜
				successcheck check=new successcheck( i, j, CCmark);
				if (check.checkrow() >= 5 || check.checkrank() >=5
						|| check.checkl() >= 5 || check.checkr() >= 5) {
					if(CCmark[i][j]==1){
						javax.swing.JOptionPane.showMessageDialog(jf, "黑方获胜了!!");
					}else 
					if(CCmark[i][j]==-1){
					javax.swing.JOptionPane.showMessageDialog(jf, "蓝方获胜了!!");
				}

 

 而判断方法则单另写了一个类,方便超找排错。而且分为了横、竖、左斜和右斜四个方法。以方便调用。

 

/**
	 * 用于检验行
	 * @return
	 */
	public int checkrow() {
		int pcount = 1;// 定义一个棋子个数计数器
		for (int i = x + 1; i <= CCmark.length; i++) {// 向右
			if (i < 11 && CCmark[i][y] == CCmark[x][y]) {
				pcount++;
			} else
				break;
		}
		for (int i = x - 1; i >= 0; i--) {// 向左
			if (i < 11 && CCmark[i][y] == CCmark[x][y]) {
				pcount++;
			} else
				break;
		}
		return pcount;
	}
	
	/**
	 * 用于检验列
	 */
	public int checkrank() {
		int pcount = 1;// 定义一个棋子个数计数器
		for (int i = y + 1; i <= CCmark.length; i++) {// 向下
			if (i < 11 && CCmark[x][i] == CCmark[x][y]) {
				pcount++;
			} else
				break;
		}
		for (int i = y - 1; i >= 0; i--) {// 向上
			if (i < 11 && CCmark[x][i] == CCmark[x][y]) {
				pcount++;
			} else
				break;
		}
		return pcount;
	}
	
	/**
	 * 用于检验左斜线方向
	 */
	public int checkl() {
		int pcount = 1;// 定义一个棋子个数计数器
		for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {// 向左上

			if (CCmark[i][j] == CCmark[x][y]) {
				pcount++;

			} else {
				break;
			}
		}
		// System.out.println(pcount);
		for (int i = x + 1, j = y + 1; i < 11 && i < CCmark.length && j < 11
				&& j < CCmark.length; i++, j++) {// 向右下
			if (CCmark[i][j] == CCmark[x][y]) {
				pcount++;
			} else {
				break;
			}

		}
		// System.out.println(pcount);
		return pcount;
	}
	
	/**
	 * 用于检验右斜线方向
	 */
	public int checkr() {
		int pcount = 1;// 定义一个棋子个数计数器
		for (int i = x + 1, j = y - 1; i < 11 && i < CCmark.length && j >= 0; i++, j--) {// 向右上
			if (CCmark[i][j] == CCmark[x][y]) {
				pcount++;
			} else {
				break;
			}

		}
		// System.out.println(pcount);
		for (int i = x - 1, j = y + 1; i >= 0 && j < 11 && j < CCmark.length; i--, j++) {// 向左下
			if (CCmark[i][j] == CCmark[x][y]) {
				pcount++;
			} else {
				break;
			}

		}
		// System.out.println(pcount);
		return pcount;
	}

 

 想要判断输赢,就要计算机可以记得棋盘上棋子的位置和颜色。所以,我选择了用二维数组存储这些信息,刚好下标可以存交叉点坐标,而数组的值也可以存棋子颜色,个人认为利用率比较高,比起用队列存,应该要方便些。

    四、配置类。

   经常玩游戏,知道可以设置许多的配置,原来,这就算配置类的图形化。这样在人性化和可移植性都比较好,所以也学来:

 

public interface FCCconfig {
	public final static int row = 11;// 棋盘的行
	public final static int rank = 11;// 棋盘的列
	public final static int CCPsize = 40;// 棋子直径
	public final static int gsize = 50;// 格间距
	public final static int x0 = 50;// 棋盘左边缘x坐标
	public final static int y0 = 50;// 棋盘上边缘y坐标
	public final static int ye = 550;// 棋盘下边缘y坐标
	public final static int xe = 550;// 棋盘右边缘x坐标

	// 定义一个二维数组用来标记下过棋的位置
	public final static int[][] CCmark = new int[FCCconfig.row][FCCconfig.rank];
   
	//定义一个队列用来记录棋子的情况
	ListImp<pill> list=new ListImp<pill>();

 

 定义为接口确实调用也方便,修改也方便。

 


五子棋开发(一)---------P2P对战_第2张图片


五子棋开发(一)---------P2P对战_第3张图片
 
五子棋开发(一)---------P2P对战_第4张图片
 
五子棋开发(一)---------P2P对战_第5张图片

 

 

 

 

 

 

 

我的收获:

1、果然,好的框架结构,会为后面的开发提供莫大的方便。

2、要充分理解各个类之间的结构,这样可以避免混乱和代码的重复。

3、学会利用接口,和很有实际意义的Config类。

4、数据结构这门课真是大有用处啊!好的结构不仅可以省下不少代码,还可以提高运行效率。

5、罗马不是一天建成的。浮躁是我的弊病,只有一步一步的走才能爬上山,不要期望自己可以瞬间漂移到山顶,这只会是神话。空想主义者并不会比实干家过的好多少!

你可能感兴趣的:(五子棋,p2p,结构,收获)