大二中级实训总结
摘要:为期四周的软件工程中级实训也结束了,这次实训的内容是完成一个GridWrld。GridWorld案例提供了一个图形化环境用于可视化对象在二维网格中的交互。在这个案例中,你将设计和制造各种Actor的对象,将它们添加到一个网格中,并且根据一定的规则决定Actor的行为。此外我们还增加了三个扩展任务,ImageReader、N-Puzzle(华容道)和MazeBug(迷宫)。
回顾过去四周的内容,第一周主要是学习vim,java和ant以及junit的使用,同时理解了项目的大概内容,同时完成了sonar环境的配置,编写了第一个java的小程序并且使用junit和sonar进行了检测。
vim,java,ant 和Junit 的学习报告:
vim的学习报告:
首先,不得不说,Vim是一个十分强大的编辑器工具。因为我一开始使用vim的时候我还拿他跟其他的文本编辑器或者程序编辑器进行了比较。因为我之前一直使用的是vscode和eclipse两款编辑器,当然这两款功能也很强大,导致我一开始觉得vim用起来反而没有什么。严格来说,vscode更接近于IDE,vim是在各大平台都能高效使用的编辑器,只要一个键盘基本能够完成各项任务,如移动光标、删除文本、插入文本、置换文本、搜索文本、执行外部命令等等简单的命令,用熟了估计效率会很高,当然我本人比较懒,小程序我可能练练vim,一旦涉及目录结构相对复杂的大项目我还是会倾向于回到vscode去编辑。在学习和掌握这一部分的时候更多的是在学习一种新的编辑方式,没有遇到很大的障碍,所以这一部分主要写的都是感想而已。
java的学习报告:
我大二上选修了web2.0的开发,并没有选择java的课程。所以这次算是第一次比较完整的去接触java语言。其实跟c++有区别,但不是很大,应该说在很多地方都比c++有了改进。更重要的是java的面向对象的编程思想基本上就是全方位覆盖的,c压根不谈面向对象,c++的时候接触了面向对象的编程,之后也陆陆续续接受了更多的时候,但相比较其java而言感觉c++的面向对象并不彻底。再者,没有了指针的java学起来不要太爽,大一疯狂的内存泄漏几乎就是一个令人崩溃的存在,基本上都是指针惹的祸。推荐一个学习很适合菜鸟的java学习网站:java学习网站。
ant的简单使用:
首先介绍一下一般而言一个项目的格式
MyProject |--lib --库目录
|--classes --目的文件目录
|--src --源文件目录
|--doc --api文档目录
|--build.xml (ant默认的执行文件)
ant这个部分开始有了一些小问题的出现,因为之前是完全没有接触过ant的,所以连ant能做什么都不知道,看了ant的功能之后发现其实现在计算机并没有类似的做得很好的功能,上个实训我们接触过Makefile是第一个处理项目文件的小功能,这一次学习了ant之后也算是在这一块有了一些小认识。> 首先,Ant的核心就是配置文件build.xml,在build.xml文件中配置相关的任务后,使用ant命令调用功能即可自动执行。当然如果build.xml文件我们没有这么命名,那我们也可以通过在命令行操作的时候添加一些命令达到同样的效果。
ant的元素有很多,并且因为功能的不同在使用方法和对象方面也有不同,这里主要总结最常见的几种还有我们编译和运行的两个元素。ant的构件文件都是XML格式的。每个构件文件包含一个project元素和至少一个target。一个 project 元素可以有多个 target 元素,一个 target 元素可以有多个 task。其中每个元素又有它自己的属性,刚入门ant想要记住这么多元素和属性是很困难的。多练多学。
junit学习报告:
JUnit是一个Java语言的单元测试框架。这部分最开始学习的时候有点迷,因为网上的教程比较散乱,由于自己本身也是完全的小白开始接触,所以连判断一个教程的好坏能力都没有,走了许多弯路。、首先,我们得知道他的功能和优点,众所周知,debug是很多程序员都很头疼的东西。通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。在博客上发现了一个很不错的junit的教程,这也是让我快速找到运用方法的路径。博客本身很详细,我也不需要去重复里面扥内容,这里贴出来给大家分享一下好的资源优秀的juint教程。至于我自己在使用junit这一块其实熟练度是远远不够的,我清除并且肯定junit 的强大功能,那么只能是我日后继续努力去更好的掌握这些很不多的东西,包括这里的junit,包括上面的vim和ant。
第二周我们主要是完成GridWorld的主要部分的完成,
这一部分分为Part2,part3,part4,part5.
part2完成了Bug的四个拓展类CircleBug
、SpiralBug
、ZBug
、DancingBug,
CircleBug只是将Bug中的turn()由执行一次转化为执行两次。
DancingBug会一次读进一个数组,并且按照数组中的数值转动固定的方向并且行走,
当数组读取完毕时候重新回到数组首个数值重新开始,从而能够实现不停地跳舞。
ZBug是一个能走Z字型的Bug的拓展类,这个类实现比较容易,但要传入一个参数来决定Z的大小。
SpiralBug螺旋类,按照螺旋从中心向外拓展,最好在无界网格中运行,效果更好。
part3主要是实现一个jumper类拓展Actor类,并且做了相应的juint测试,这一部分可以自己规定自己的jumper类的动作行为,关键是juint的测试要写到位。
Part4主要实现 ModifiedChameleonCritter、ChameleonKid、RockHound、BlusterCritter、QuickCrab、KingCrab,这一部分是拓展一个crriter的小动物类,并且这些小动物分别具有不同的功能。具体实现也比较容易。
Part5主要是拓展Grid接口类,由于Grid接口有两个类,一个有界,一个无界。这一部分拓展了网格Grid类,并且实现了用哈希存储,动态数组拓展数组存储空间等功能。
第三周和第四周我们要实现一些拓展功能,包括ImageReader、N-Puzzle(华容道)和MazeBug(迷宫)三部分。
其中ImageReader要求实现二进制的读取图片并且实现色彩通道的一些改变从而实现图像的一些改变,还要能够利用java自带的API来写图片,还有写一个测试类。
其中主要的是在二进制的读取过程花费了很多时间,因为要理解这部分还是需要花很多功夫,毕竟之前都没有接触过。
其中读写重要部分的代码如下:
/* 保存位图信息 * 字节 #14-17 定义以下用来描述影像的区块(bitmapinfoheader)的大小。它的值是:40 - windows 3.2、95、nt、12 - os/2 1.x、240 - os/2 2.x * 字节 #18-21 保存位图宽度(以像素个数表示)。 * 字节 #22-25 保存位图高度(以像素个数表示)。 * 字节 #26-27 保存所用彩色位面的个数。不经常使用。 * 字节 #28-29 保存每个像素的位数,它是图像的颜色深度。常用值是1、4、8(灰阶)和24(彩色)。 * 字节 #30-33 定义所用的压缩算法。允许的值是0、1、2、3、4、5。 * 0 - 没有压缩(也用bi_rgb表示) * 1 - 行程长度编码 8位/像素(也用bi_rle8表示) * 2 - 行程长度编码4位/像素(也用bi_rle4表示) * 3 - bit field(也用bi_bitfields表示) * 4 - jpeg图像(也用bi_jpeg表示) * 5 - png图像(也用bi_png表示) * 然而,由于大多数位图文件都是不压缩的,所以最常用的值是0。 * 字节 #34-37 保存图像大小。这是原始(:en:raw)位图数据的大小,不要与文件大小混淆。 * 字节 #38-41 保存图像水平方向分辨率。 * 字节 #42-45 保存图像竖值方向分辨率。 * 字节 #46-49 保存所用颜色数目。 * 字节 #50-53 保存所用重要颜色数目。当每个颜色都重要时这个值与颜色数目相等。 **/ public class myImageIO implements IImageIO{ private static final int FILEHEADER = 14; private static final int INFOHEADER = 40; private static final int FOUR_BYTE = 4; private static final int MULTICOLOUR = 24; private static final int GRAY = 8; private static final int TWOFOUR = 24; private static final int EIGHT = 8; private static final int SIXTEEN = 16; private static final int ZERO = 0; private int bitCount; public Image myRead(String filePath) throws IOException { try { FileInputStream file = new FileInputStream(filePath); byte bHead[] = new byte[FILEHEADER]; byte bInfo[] = new byte[INFOHEADER]; int width = 0 , height = 0, size = 0; //读取位图头 file.read(bHead, ZERO, FILEHEADER); //读取位头信息 file.read(bInfo, ZERO, INFOHEADER); // 图像宽度(像素点) width = ((bInfo[7] << TWOFOUR) & 0xff000000)| ((bInfo[6] << SIXTEEN) & 0x00ff0000)| ((bInfo[5] << EIGHT) & 0x0000ff00)| bInfo[4] & 0x000000ff; // 图像高度(像素点) height = ((bInfo[11] << TWOFOUR) & 0xff000000)| ((bInfo[10] << SIXTEEN) & 0x00ff0000)| ((bInfo[9] << EIGHT) & 0x0000ff00)| bInfo[EIGHT] & 0x000000ff; // 图像位数1 4 8 24 bitCount = ((bInfo[15] << EIGHT) & 0x0000ff00) | (bInfo[14] & 0x000000ff); // 图像大小。原始(:en:raw)位图数据的大小,不要与文件大小混淆 size = ((bInfo[23] << TWOFOUR) & 0xff000000)| ((bInfo[22] << SIXTEEN) & 0x00ff0000)| ((bInfo[21] << EIGHT) & 0x0000ff00)| bInfo[20] & 0x000000ff; int pixelSize =0; int npad = 0; byte originalRGB[]; int RGBDate[]; Image image = null; if (bitCount == MULTICOLOUR) { // 计算空字节 npad = (size / height) - width * 3; if (npad == FOUR_BYTE) { npad = ZERO; } // 计算 pixel 大小 pixelSize = (width + npad) * 3 * height; if (npad != ZERO) { originalRGB = new byte[pixelSize]; } else { originalRGB = new byte[size]; } // 读取所有RGB数据 file.read(originalRGB, ZERO, pixelSize); RGBDate = new int[height * width]; int index = 0; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { RGBDate[width * (height - j - 1) + i] = (255 & 0xff) << TWOFOUR | (((int)originalRGB[index + 2] & 0xff) << SIXTEEN) | (((int)originalRGB[index + 1] & 0xff) << EIGHT) | (int)originalRGB[index] & 0xff; index += 3; } index += npad; } image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, RGBDate, 0, width)); } if (bitCount == GRAY) { // 计算空字节 npad = (size / height) - width; if (npad == FOUR_BYTE) { npad = ZERO; } // 计算 pixel 大小 pixelSize = (width + npad) * height; if (npad != ZERO) { originalRGB = new byte[pixelSize]; } else { originalRGB = new byte[size]; } originalRGB = new byte[pixelSize]; // 读取所有RGB数据 file.read(originalRGB, ZERO, pixelSize); RGBDate = new int[height * width]; int index =ZERO; for (int j = ZERO; j < height; j++) { for (int i = ZERO; i < width; i++) { if (index >= pixelSize){ index = ZERO; } RGBDate[width * (height - j - 1) + i] = (255 & 0xff) << TWOFOUR | (((int)originalRGB[index] & 0xff) << SIXTEEN) | (((int)originalRGB[index] & 0xff) << EIGHT) | (int)originalRGB[index] & 0xff; index += 1; } index += npad; } image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, RGBDate, 0, width)); } file.close(); return image; } catch (Exception e) { ; } return (Image)null; } public Image myWrite(Image image, String file) throws IOException { try { int height = image.getHeight(null); int width = image.getWidth(null); int fileType; if (bitCount == MULTICOLOUR){ fileType = BufferedImage.TYPE_3BYTE_BGR; } else{ fileType = BufferedImage.TYPE_BYTE_GRAY; } // 创建图片 BufferedImage bi = new BufferedImage(width, height, fileType); bi.getGraphics().drawImage(image, 0, 0, null); // 打开文件 File iFile= new File(file + ".bmp"); ImageIO.write(bi, "bmp", iFile); } catch (Exception e) { ; } return image; } }
MazeBug类要求我们实现一个能用深度优先搜索的Bug类能够自动的寻找迷宫的寻找路径,并且在拓展部分增加方向参数能够选择更大可能性的方向行走。
其中最重要的是一个结构的理解,
public Stack<ArrayList<Location>> crossLocation = new Stack<ArrayList<Location>>();
我本来是觉得应该使用Stack<Location>的,所以比较不理解为什么使用上面那样的一个结构,后来我看了一遍大概的代码和实现过程,发现原来这里用来存储分支路径,而不是存储单个Loction。
还有一个最后的内容就是实现华容道,这里要求我们实现用广度优先搜索和A*算法来减少对不要要结点的访问,从而实现更快的找到目标节点,更少的访问不必要的结点。其中要求我们写的程序能够通过简单的测试:test.sh。
这部分的代码如下:(我已经尽自己最大努力将无关结点的访问量降到最低了)
/** * 在此类中填充算法,完成重拼图游戏(N-数码问题) */ public class Solution extends Jigsaw { private Queue<JigsawNode> exploreList; // 用以保存已发现但未访问的节点 private Set<JigsawNode> visitedList; // 用以保存已发现的节点 private List<JigsawNode> solutionPath;// 解路径:用以保存从起始状态到达目标状态的移动路径中的每一个状态节点 private int searchedNodesNum; /** * 拼图构造函数 */ public Solution() { } /** * 拼图构造函数 * @param bNode - 初始状态节点 * @param eNode - 目标状态节点 */ public Solution(JigsawNode bNode, JigsawNode eNode) { super(bNode, eNode); } /** *(实验一)广度优先搜索算法,求指定5*5拼图(24-数码问题)的最优解 * 填充此函数,可在Solution类中添加其他函数,属性 * @param bNode - 初始状态节点 * @param eNode - 目标状态节点 * @return 搜索成功时为true,失败为false */ public boolean BFSearch(JigsawNode bNode, JigsawNode eNode) { /** * 初始化一些数值, * 选择具体的queue类型和hashset */ searchedNodesNum = 0; solutionPath = null; beginJNode = new JigsawNode(bNode); endJNode = new JigsawNode(eNode); currentJNode = new JigsawNode(bNode); exploreList = new LinkedList<>(); visitedList = new HashSet<>(); int flag = 0; /** * 将第一个节点压入队列 */ exploreList.add(beginJNode); /** * 如果exploreLis列表为空,则搜索失败,问题无解;否则重复以下步骤: * a. 访问exploreLis列表中的第一个节点v,若v为目标节点,则搜索成功,退出。 * b. 从exploreLis列表中删除节点v,放入visitedList列表中。 * c. 将所有与v邻接且未曾被访问的节点放入exploreLis列表中。 */ while (!exploreList.isEmpty()) { //计算访问的节点数,节点数超过29000将放弃 searchedNodesNum++; if(searchedNodesNum>=29000){ flag = 1; break; } //访问结点 currentJNode = exploreList.poll(); //找到了结点就退出 if (currentJNode.equals(endJNode)) { getPath(); break; } JigsawNode[] nextNodes = new JigsawNode[] { new JigsawNode(currentJNode), new JigsawNode(currentJNode), new JigsawNode(currentJNode), new JigsawNode(currentJNode) }; /** * 邻接点并且未访问的结点 */ for (int i = 0; i < 4; i++) { if (nextNodes[i].move(i) && !visitedList.contains(nextNodes[i])) { exploreList.add(nextNodes[i]); visitedList.add(nextNodes[i]); } } } if(flag == 1){ System.out.println("Jigsaw BFSearch Result:"); System.out.println("Begin state:" + this.getBeginJNode().toString()); System.out.println("End state:" + this.getEndJNode().toString()); System.out.println("The number of node is more than 29000! "); }else{ System.out.println("Jigsaw BFSearch Result:"); System.out.println("Begin state:" + this.getBeginJNode().toString()); System.out.println("End state:" + this.getEndJNode().toString()); // System.out.println("Solution Path: "); // System.out.println(this.getSolutionPath()); System.out.println("Total number of searched nodes:" + searchedNodesNum); System.out.println("Depth of the current node is:" + this.getCurrentJNode().getNodeDepth()); } return this.isCompleted(); } /** *(Demo+实验二)计算并修改状态节点jNode的代价估计值:f(n) * 如 f(n) = s(n). s(n)代表后续节点不正确的数码个数 * 此函数会改变该节点的estimatedValue属性值 * 修改此函数,可在Solution类中添加其他函数,属性 * @param jNode - 要计算代价估计值的节点 */ public void estimateValue(JigsawNode jNode) { int s = 0; // 后续节点不正确的数码个数 int wrong_num = 0; // 放错位的数码个数 int mafuman_Distance = 0; // 曼哈顿距离 //int squre_Distance = 0; int dimension = JigsawNode.getDimension(); //我们测试的默认维数是5 //注意第一个是空白格的位置,最后一个理论上是0,如果已经完成的话 for(int index = 1; index < dimension*dimension; index++){ if(jNode.getNodesState()[index] + 1 != jNode.getNodesState()[index+1]) s++; } //判断放错位置的结点 for(int index = 1; index <= dimension*dimension; index++){ if(jNode.getNodesState()[index] != index) wrong_num++; } //判断距离,这里使用哈夫曼距离 for(int index = 1; index <= dimension*dimension; index++){ int realNum = jNode.getNodesState()[index]; if (realNum != index && realNum != 0) { int realX = (realNum - 1) % dimension; int realY = (realNum - 1) / dimension; int indexX = (index - 1) % dimension; int indexY = (index - 1) / dimension; int distance = Math.abs(realX - indexX) + Math.abs(realY - indexY); //double distance1 = Math.sqrt(Math.pow(realX - indexX, 2) + Math.pow(realY - indexY , 2)); mafuman_Distance += distance; //squre_Distance += distance1; } } jNode.setEstimatedValue( 15 * s + wrong_num + 10 * mafuman_Distance + jNode.getNodeDepth()); return ; } }
最后的一点总结和心得体会:这次实训还是学到了蛮多东西的,对于linux上使用也更加熟悉了一点,加深了对深度优先算法和广度优先算法的理解,短期时间学会了一门编程语言java,学会简单实用ant进行简单的编译运行打包等功能,学会用juint对类功能进行测试,学会使用sonar对代码语言风格进行了匡正和规范。总而言之,对于需要知识的我们是很有用的。当然这四周很累,还是希望四周结束之后能够给自己放一个小假,我想出去踏青了。最后的最后,希望大家都能享受愉快的生活!
中山大学数据科学与计算机学院
软件工程大二学生的实训总结报告
2018年5月5日19点56分