`

从代码的修改来看代码的编写之道

阅读更多

        最近在学习Java中的IO流时,拿着以前写的五子棋来练手,就是实现五子棋的游戏存档和存档读取。经过几次修改代码后,忽然对代码的编写之道有了一些自己的看法。

        首先介绍一下我的界面:



 

这是一张15*15的五子棋标准棋盘,我找了一张棋盘的照片,然后用ps测出他的颜色为:

new Color(241,193,121)
 还有一些基础数据为:

 

 

private int length_i = 15;//此为行数
private int length_j = 15;//此为列数
private int edge_length = 30;//此为边界长度
private int cell_length = 40;//此为单元格长度
private int[][] chessArray = new int[length_i][length_j];//存储棋子的数组
 对了,我新建了一个JPanel对象来当棋子的画板,并把它加到界面上作为中间面板,这样就可以避免界面上这个框对我们实际得到的x坐标,y坐标的影响。(new Dimension(780, 680):这是界面的大小)

 
GoListener gl=new GoListener(chessArray,this,edge_length,cell_length);
this.addMouseListener(gl);
 为我们的画板(即中间面板)加上鼠标监听器。
public class GoListener implements MouseListener,ActionListener{
  private int x,y,i,j,count=1;//count为棋子次序
  private int edge_length,cell_length;
  private  Graphics g;
  private int[][]array;//0是空,大于0的,奇数为黑子,偶数为白子
  private GoBang frame;
  private boolean flag=false;//是否有胜利者的标识符
  
  /**
   * 
   * @param array
   * @param frame
   * @param edge_length 边界长度
   * @param cell_length 单元格长度
   */
  public GoListener(int[][] array,GoBang frame, int edge_length, int cell_length){
	  this.array=array;
	  this.frame=frame;
	  this.edge_length=edge_length;
	  this.cell_length=cell_length;
  }
  public void setGraphics(Graphics g){
	  this.g=g;
  }
 妥妥的,我们的监听器类,大家可以看到它还继承操作监听器,所以我把传递画笔的代码写到了构造方法之外。有人可能会问,为什么不让使用这几行代码来获得画笔g?
public GoListener(int[][] array,GoBang frame, int edge_length, int cell_length){
	  this.array=array;
	  this.frame=frame;
	  this.edge_length=edge_length;
	  this.cell_length=cell_length;
          this.g=frame.getGraphics();
  }
 哈哈,眼尖的读者肯定看到了,我们传递的是整个界面:GoBang frame;如果写成上面那样,我们就还要传递个中间面板过来,然后再在其上获取画笔,这岂不是多此一举?
public void mouseClicked(MouseEvent e) {
		// 将g强制转换为Graphics2D类型的对象
		Graphics2D g2d = (Graphics2D) g;
		
		if(flag==false){
			//将棋子下在交叉点
			x = e.getX();y = e.getY();//获得鼠标点击点的x坐标和y坐标
			for (j = 0; j < 15; j++) {//行
				b = 30 + 40 * j;
				if (Math.abs(x - b) < 25)
					break;
			}
			for (i = 0; i < 15; i++) {//列
				a = 30 + 40 * i;
				if (Math.abs(y - a) < 25)
					break;
			}
			x = b;y = a;
			if(i>=0&&i<=14&&j>=0&&j<=14){//棋子需下在棋盘内
				if(array[i][j]==0){//只有下在空的交叉点上
					if(count%2==1){
						//black棋
						g2d.setColor(new Color(65,52,43));
						array[i][j]=count;//记录棋子的次序
						count++;//次序加1
					}else{
						//white棋
						g2d.setColor(new Color(248,248,238));
						array[i][j]=count;
						count++;
					}
					g2d.fillOval(x - 20, y - 20, 40, 40);
					//Judge为判定输赢得类,需要传入存储数据的数组
					Judge judge=new Judge(array);
					flag=judge.isWin(i,j);
				        //Judge类中的isWin()方法,需要传入所下棋子的行数和列数,如果有胜利者则返回true;没有则返回false;
					if(flag){
						if(array[i][j]%2==1)//次序为奇数,下的是黑棋
							JOptionPane.showMessageDialog(null, "黑棋赢了!");
						if(array[i][j]%2==0)//次序为非零偶数,下的是白棋
							JOptionPane.showMessageDialog(null, "白棋赢了!");
					}//判定输赢
				}//如果交叉点上有棋子,就什么事都不做。
			}//如果鼠标点击点不在棋盘内,就什么事也不做。
		}else{
		    int str=JOptionPane.showConfirmDialog(null,"是否开始新游戏" );
		   	if(str==0)
		   		Restart();
	    }		
	}
 
 这里我用了两个for循环来获得棋子所想要的行数 i 和列数 j ,然后将count存在array[i][j]中,count即正在下的棋子的次序,画完棋子后,count++。
好的,于是我们的第一个变革点来了
public void mouseClicked(MouseEvent e) {
		// 将g强制转换为Graphics2D类型的对象
		Graphics2D g2d = (Graphics2D) g;
		
		if(flag==false){
			x = e.getX();y = e.getY();//获得鼠标点击点的x坐标和y坐标
			//除法公式
			i = (y-edge_length+cell_length/2)/cell_length;
			j = (x-edge_length+cell_length/2)/cell_length;
			//将棋子下在交叉点
			x = edge_length+j*cell_length;y = edge_length+i*cell_length;
			
			if(i>=0&&i<=14&&j>=0&&j<=14){
				if(array[i][j]==0){
					System.out.println(i+">>>"+j);
					if(count%2==1){
						//black
						g2d.setColor(new Color(65,52,43));
						array[i][j]=count;
						count++;
					}else{
						//white
						g2d.setColor(new Color(248,248,238));
						array[i][j]=count;
						count++;
					}
					g2d.fillOval(x - cell_length/2, y - cell_length/2, cell_length, cell_length);
					//判断棋子输赢
					Judge judge=new Judge(array);
					flag=judge.isWin(i,j);
				
					if(flag){
						if(array[i][j]%2==1)
							JOptionPane.showMessageDialog(null, "黑棋赢了!");
						if(array[i][j]%2==0)
							JOptionPane.showMessageDialog(null, "白棋赢了!");
					}//判定输赢
				}//如果交叉点上有棋子,就什么事都不做。
			}//如果鼠标点击点不在棋盘内,就什么事也不做。
		}else{
		    int str=JOptionPane.showConfirmDialog(null,"是否开始新游戏" );
		   	if(str==0)
		   		Restart();
	    }		
	}
 是的,我们改变了判定行数和列数的代码,之前的代码的时间复杂度为O(n),现在的时间复杂度为O(1)。
小小的一个改变,收益巨大,也许你现在感觉不到什么,但是以后你写大型程序的时候,你就会知道代码所需要的高效性有多重要了。
我们接着往下,接着就是我们的游戏存档了:
new ArrayToFile(array);//传入存储数据的数组
 简简单单的一行代码,干完我们所需要的所有事情,这是大家在团队合作中的最好的工作结果,别人只要创建对象,就能做完交给你的所有事情(我交给ArrayTOFile类的事情就是让它把数组存成文件)。
继续当然就是存档读取:
public class FileToArray {
	
	private int[][] array=new int[15][15];
	private InputStream ins;
	private DataInputStream read_file;
	private int i,j;
	//此行代码是为了能够一直读下去,而不是从头开始。
	public FileToArray(){
		try{
			/**检测D盘是否存在GoBang这个文件。*/
			File filefound=new File("D://Go.tx");
			if(filefound.exists()){
				ins=new FileInputStream(filefound);
				read_file=new DataInputStream(ins);
				FileRead();
				System.out.println(">>>");
			}
			else{
				JOptionPane.showMessageDialog(null, "没有存档!请存档。");
			}
		}catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	/**
	 * 构造函数
	 * @throws IOException 
	 */
	public void FileRead() throws IOException{
		while((i=read_file.readInt())!=99 ){
			j=read_file.readInt();
			array[i][j]=read_file.readInt();
			System.out.println(i+"  "+j+"  "+array[i][j]);
		}
		System.out.println("输出完毕!\n\n\n");
	}
	public int[][] getArray() {
		return array;
	}
	
}
 可以看到,我在这个类中new了一个15*15的数组对象来存储从文件中读取的数据。然后用一个getArray()方法来返回这个数组。
调用出的代码如下:
array=new FileToArray().getArray();
		  count=getMax(array)+1;
		  //判断棋子输赢
		  Judge judge=new Judge(array);
		  judge.isWin(getMax_i(array),getMax_j(array));
		  frame.setChessArray(array);
		  //将数组设置回去。其余的不用设置是因为他们在内存中的地址和从GoBang传来的数组一样,而这里改变了数组array的地址。
 这是我的paint(Graphics g)方法:
public void paint(Graphics g){
		super.paint(g);

		// 将g强制转换为Graphics2D类型的对象
		Graphics2D g2d = (Graphics2D) g;
		g2d.setBackground(new Color(190,145,103));

		// 棋盘的起始坐标是30,30;横线和竖线各15条;格子的宽度高度是40。
		for (int i = 0; i < 15; i++) {
			if (i == 0 || i == 14)
				g2d.setStroke(new BasicStroke(5));// 设置线条的粗细
			else
				g2d.setStroke(new BasicStroke(2));// 设置线条的粗细

			// 绘制横线和竖线
			g2d.drawLine(edge_length, edge_length + cell_length * i, edge_length + 14 * cell_length, edge_length + cell_length * i);
			g2d.drawLine(edge_length + cell_length * i, edge_length, edge_length + cell_length * i, edge_length + 14 * cell_length);
		}
		// 绘制矩形
		g2d.setStroke(new java.awt.BasicStroke(4));
		g2d.drawRect(edge_length, edge_length, cell_length*14, cell_length*14);
		g2d.fillRect(145, 145, 10, 10);
		g2d.fillRect(465, 145, 10, 10);
		g2d.fillRect(465, 465, 10, 10);
		g2d.fillRect(145, 465, 10, 10);
		g2d.fillRect(305, 305, 10, 10);

		// 把下过的棋子重绘一次
		for(int i=0;i<chessArray.length;i++){
			for(int j=0;j<chessArray[i].length;j++){
				if(chessArray[i][j]!=0){
					if(chessArray[i][j]%2==1){
						g.setColor(new Color(65,52,43));
					}else{
						g.setColor(new Color(248,248,238));
					}
					g2d.fillOval(10+cell_length*j, 10+cell_length*i, cell_length, cell_length);
				}
			}
		}
	}
 
大家按照上面的判定标准肯定可以看出我这个FileToArray类写的很水,为什么呢?因为别人(GoListener对象调用我还需要让GoBang类增加一个setArray(int[][] array)方法(如果没有此函数,那么重绘时就没有我们从文件中读取的数组的事情了),如此兴师动众。
于是我将代码改成了这样:
public class FileToArray {
	private Reader read_file;
	private int i,j;
	//此行代码是为了能够一直读下去,而不是从头开始。
	public FileToArray(int[][] array){
		Array_clear(array);
		try{
			/**检测D盘是否存在GoBang这个文件。*/
			File filefound=new File("D://Go.tx");
			if(filefound.exists()){
				read_file=new FileReader(filefound);
				FileRead(array);
			}
			else{
				JOptionPane.showMessageDialog(null, "没有存档!请存档。");
			}
		}catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	/**
	 * 构造函数
	 * @param array 
	 * @throws IOException 
	 */
	public void FileRead(int[][] array) throws IOException{
		while((i=read_file.read())!=99 ){
			j=read_file.read();
			array[i][j]=read_file.read();
			System.out.println(i+"  "+j+"  "+array[i][j]);
		}
		System.out.println("输出完毕!\n\n\n");
	}
	/**
	 * 清空一个数组
	 */
	public void Array_clear(int[][] array){
		for(int i=0;i<array.length;i++){
			for (int j=0;j<array[i].length;j++){
				array[i][j]=0;
			}
		}
	}
}
 我传入了存储数据的数组,哈哈,如此,我们在这边对数组做的改变,GoBang类里就可以直接感受到了(因为是传址的)。
那样调用处的代码就改成了这样:
new FileToArray(array);//这里用的是传址发
count=getMax(array)+1;
//判断棋子输赢
Judge judge=new Judge(array);
judge.isWin(getMax_i(array),getMax_j(array));
frame.repaint();
 good!现在我们完成任务同样只需要创建一个对象了。
而且现在的FileToArray类不仅仅只能读取15*15的棋子数组了,它能读取任何行数和任何列数的二维数组
我将我的ArrayToFile类和FileToArray类复制到我的2048游戏里,同样只通过创建一个对象,就完成了我的2048的游戏存档和存档读取功能(因为我的2048数据也是用数组来存储数据的)。
从这几次修改代码我们可以明白,作为一个程序员,我们写的代码不能只成为一次性的消费品,我们应该写能够在其他程序中也能使用的代码,还应该对我们写的代码的时间复杂度和空间复杂度负责,并且我们写的代码应该要做好我们接到的任务中的所有事情,避免对其他类的代码修改需要。
这就是我从我这几次修改我的代码中获得的总结,谢谢大家的阅读!也欢迎大家提出自己的意见!我们一起交流进步,成为最强大的程序员!
 
我把我的代码打包贴了上来,大家可以下载去看。
  • 大小: 77.8 KB
  • 大小: 20.3 KB
分享到:
评论

相关推荐

    千年完整代码,千年最早版本,Pascal

    从压缩包子文件的文件名称列表来看,我们可以推测以下几个关键点: 1. **FastNet.v5.6.3.D5-7.FS**:这可能是一个网络库的文件,用于处理TCP/IP通信。版本号5.6.3表明它是经过多次迭代和改进的,专为Delphi 5到7...

    自己动手写操作系统(含源代码).part2

    在第二版的编写过程中,我同样要感谢许多人。感谢我的父母和爷爷对我的爱,并希望爷爷不要为我担心,写书是件辛苦的事,但同时也使我收获良多。爸爸在第二版的最后阶段帮我订正文字,这本书里有你的功劳。我要感谢...

    自己动手写操作系统(含源代码).part1

    在第二版的编写过程中,我同样要感谢许多人。感谢我的父母和爷爷对我的爱,并希望爷爷不要为我担心,写书是件辛苦的事,但同时也使我收获良多。爸爸在第二版的最后阶段帮我订正文字,这本书里有你的功劳。我要感谢...

    程序设计之道

    《程序设计之道》是一本深入探讨软件开发中...通过阅读《程序设计之道》,读者不仅可以了解这些模式,还能学习如何在实际项目中有效地应用它们,提高代码的可读性和可复用性,降低维护成本,从而提升整体的编程技艺。

    《C语言程序设计》C语言期末上机考试复习题+答案50道,顺序加选择,循环,数组,函数,指针

    首先,我们来看"01顺序加选择"部分,这部分主要涉及基本的C语言语法和逻辑控制。顺序结构是程序的基本执行顺序,按照代码行的顺序依次执行。而选择结构则包括条件判断(if...else语句)和switch语句,它们用于根据...

    cy68013A USB高速数据采集的FPGA程序源码

    从压缩包的文件名“usb”来看,可能包含的文件有:USB控制器的配置文件(如.bit或.vhd)、USB设备驱动程序、与FPGA交互的软件API、可能的硬件描述语言(HDL)源码文件、数据采集系统的用户手册或者测试脚本等。...

    单片机IO端口CBuilder6源码

    CBuilder6是一款基于C++的集成开发环境(IDE),由Borland公司开发,特别适合于嵌入式系统开发,包括单片机程序的编写。它提供了强大的编译器和调试工具,使得开发者能够方便地编写、编译和调试针对单片机的C/C++...

    本人亲自制作透明flash动画燃烧的火焰带fla源文件

    通过FLA文件,我们可以看到每个帧的详细内容,调整动画的时间轴,修改元件属性,以及编写ActionScript代码来实现更复杂的交互。 对于这个“燃烧的火焰带”动画,我们可以从以下几个方面学习和探索: 1. **矢量图形...

    16道嵌入式C语言经典面试题

    这样可以确保该变量或对象在其生命周期内不会被修改,这对于保证代码的安全性和可维护性非常有用。 2. **增强代码的可读性**:使用 `const` 可以提高代码的可读性和可维护性,使其他开发者更容易理解代码的意图。 ...

    考试阅卷机卡型文件

    从标签“卡型文件”来看,这份文件很可能是一个标准模板或者是一个样例文件,用于指导或检验卡式答题卡的信息录入格式。卡型文件的格式通常需要根据特定的机器标准来设置,以确保机器能够正确地进行扫描和阅卷。 接...

    AD-Keypad-Code_fewerjem_open_readkeypadonSTM32_keypadstm32_

    从压缩包子文件名"AD-Keypad-Code.7z"来看,这包含的应该是一系列与键盘ADC读取相关的源代码文件,可能包括C或C++编程语言的源码文件、头文件、配置文件等。 STM32是意法半导体(STMicroelectronics)推出的一款...

    CANape教程:从入门到精通

    ### CANape教程:从入门到精通 #### 一、CANape工具详解 **(1)CANape工具基本应用** CANape是一款由Vector公司开发的强大工具,主要用于车载网络系统的开发、测试与诊断。它支持多种总线协议,如CAN、LIN、...

    findtrip-master.zip

    从文件名 "findtrip-master" 来看,"master" 常常在版本控制系统(如Git)中用于表示主分支,这意味着这是一个项目的主线代码库。 在这个压缩包里,我们预计会看到以下几部分: 1. **项目结构**:通常,一个完整的...

    mcbsp.rar_2812 mcbsp_2812的mcbsp_MCBSP_mcbsp 2812

    从压缩包内的文件名"mcbsp"来看,这可能是一个包含多个文件的目录,如头文件(.h)、源代码文件(.c或.asm)、配置文件(.cfg)或其他支持文档,用于实现或解释MCBSP的功能和用法。 MCBSP的主要特点和知识点包括: ...

    16道C语言面试题

    综上所述,`const` 关键字是C语言中非常重要的特性之一,它可以增强代码的安全性、可靠性和可维护性。 #### 题目八:易变关键字的理解 **题目描述**: 解释C语言中 `volatile` 关键字的作用。 **解析**: 1. **易...

    基于JSP的Web安全问题及其研究

    从安全角度来看,服务器端的Web应用程序存在多种潜在的弱点,这些弱点主要源自于其高度的交互性和开放的传输通道。 当攻击者试图寻找并利用这些安全漏洞时,系统安全面临着巨大的挑战。通常,一种普遍有效的防御...

    ps1:CS4414的问题集1的起始代码

    从描述来看,这个压缩包包含的是问题集1的起始代码,意味着学生可以下载这些代码作为基础,然后在此基础上进行开发以完成作业。 【标签】"Rust"表明这个问题集与Rust编程语言有关。Rust是一种系统级编程语言,以其...

Global site tag (gtag.js) - Google Analytics