中级实训扩展任务
刚刚实训答辩结束,在答辩上说了一句“我是C++的忠实粉丝”,于是TA们就都问我问题了。
这次实训很水,基础部分几乎不需要让人了解底层,所以格外轻松。扩展部分设计的不大合适,所以经常会出现各种问题。写点东西,贴点代码,让后人乘荫吧。
第三部分扩展部分第一天:

实验要求
1. 利用二进制流读取Bitmap位图文件。注意,这里要求不能使用Java提供的API直接读取图像,根据二进制数据创建Image时可以使用API;
2. 把读取彩色图像转换成灰度图像;
3. 提取并且显示彩色图像各个色彩通道;
4. 把处理完的图像保存为bmp格式图像。注意,这里可以使用Java提供的API完成,但本文档不提供,希望各位同学自行上网查找资料自学。如果学有余力的同学,可以实现按照二进制流输出保存bmp图像;
5. 编写Junit测试程序,测试输出的图片是否与goal文件夹下的图片一致。(比较位图宽度、位图高度以及像素值)
个人补充:Bitmap是windows下的格式,比较无聊地被拿到Linux下去读取,wiki上给的链接介绍Bitmap的格式,网上再稍微搜一下就对它有更深的理解了。我对bitmap有点了解后,就到MSDN搜索bitmap的数据结构,模仿其数据结构去保存数据。代码如下:
1 package ImageIOImplement; 2 import java.awt.*; 3 import java.awt.image.*; 4 import javax.imageio.*; 5 import java.io.*; 6 import java.lang.String; 7 import imagereader.IImageIO; 8 9 public class ImageIOImplement implements IImageIO 10 { 11 /* 位图文件头,模仿windows下的结构体, 12 * 详细见:http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx 13 */ 14 public class BITMAPFILEHEADER 15 { 16 public int bfType; // 位图文件的类型,必须为BM 17 public int bfSize; // 位图文件的大小,以字节为单位 18 public int bfReserved1; // 位图文件保留字,必须为0 19 public int bfReserved2; // 位图文件保留字,必须为0 20 public int bfOffBits; // 位图数据的起始位置,以相对于位图 21 22 /*保存位图头 23 * 字节 #0-1 保存位图文件的标识符,这两个字节的典型数据是BM。 24 * 字节 #2-5 使用一个dword保存位图文件大小。 25 * 字节 #6-9 是保留部分,留做以后的扩展使用,对实际的解码格式没有影响。 26 * 字节 #10-13 保存位图数据位置的地址偏移,也就是起始地址。 27 * 注:由以上可知,只有字节#2-5是需要保存的。 28 */ 29 public BITMAPFILEHEADER(FileInputStream pFile) 30 { 31 int BitMapHeadRSize = 14; 32 byte pBITMAPFILEHEADER[] = new byte[BitMapHeadRSize]; 33 try 34 { 35 pFile.read(pBITMAPFILEHEADER, 0, BitMapHeadRSize); 36 bfType = 16973; //ox424D, BM 37 //为何要&呢?因为byte(8位)转int(32位)时,需要补24位,当byte数据是无符号数时,则补0,有符号时,则补1 38 bfSize = (((int)pBITMAPFILEHEADER[5] & 0xff) << 24) 39 | (((int)pBITMAPFILEHEADER[4] & 0xff) << 16) 40 | (((int)pBITMAPFILEHEADER[3] & 0xff) << 8) 41 | ((int)pBITMAPFILEHEADER[2] & 0xff); 42 bfReserved1 = 0; 43 bfReserved2 = 0; 44 //bfOffBits暂时不进行处理 45 } 46 //IO读取异常,FileInputStream和FileOutStream必须使用异常处理,略无奈 47 catch(IOException e) 48 { 49 e.printStackTrace(); 50 } 51 } 52 53 } 54 55 /* 位图信息头,模仿windows下的结构体 56 * 详细见:http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx 57 */ 58 public class BITMAPINFOHEADER 59 { 60 public int biSize; // 本结构所占用字节数 61 public int biWidth; // 位图的宽度,以像素为单位 62 public int biHeight; // 位图的高度,以像素为单位 63 public int biPlanes; // 目标设备的级别,必须为1 64 public int biBitCount;// 每个像素所需的位数,必须是1(双色), 65 // 4(16色),8(256色)或24(真彩色)之一 66 public int biCompression; // 位图压缩类型,必须是 0(不压缩), 67 // 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一 68 public int biSizeImage; // 位图的大小,以字节为单位 69 public int biXPelsPerMeter; // 位图水平分辨率,每米像素数 70 public int biYPelsPerMeter; // 位图垂直分辨率,每米像素数 71 public int biClrUsed;// 位图实际使用的颜色表中的颜色数 72 public int biClrImportant;// 位图显示过程中重要的颜色数 73 74 // 75 public int ColorModel[]; 76 /* 保存位图信息 77 * 字节 #14-17 定义以下用来描述影像的区块(BitmapInfoHeader)的大小。它的值是:40 - Windows 3.2、95、NT、12 - OS/2 1.x、240 - OS/2 2.x 78 * 字节 #18-21 保存位图宽度(以像素个数表示)。 79 * 字节 #22-25 保存位图高度(以像素个数表示)。 80 * 字节 #26-27 保存所用彩色位面的个数。不经常使用。 81 * 字节 #28-29 保存每个像素的位数,它是图像的颜色深度。常用值是1、4、8(灰阶)和24(彩色)。 82 * 字节 #30-33 定义所用的压缩算法。允许的值是0、1、2、3、4、5。 83 * 0 - 没有压缩(也用BI_RGB表示) 84 * 1 - 行程长度编码 8位/像素(也用BI_RLE8表示) 85 * 2 - 行程长度编码4位/像素(也用BI_RLE4表示) 86 * 3 - Bit field(也用BI_BITFIELDS表示) 87 * 4 - JPEG图像(也用BI_JPEG表示) 88 * 5 - PNG图像(也用BI_PNG表示) 89 * 然而,由于大多数位图文件都是不压缩的,所以最常用的值是0。 90 * 字节 #34-37 保存图像大小。这是原始(:en:raw)位图数据的大小,不要与文件大小混淆。 91 * 字节 #38-41 保存图像水平方向分辨率。 92 * 字节 #42-45 保存图像竖值方向分辨率。 93 * 字节 #46-49 保存所用颜色数目。 94 * 字节 #50-53 保存所用重要颜色数目。当每个颜色都重要时这个值与颜色数目相等。 95 * */ 96 public BITMAPINFOHEADER(FileInputStream pFile) 97 { 98 int bitMapInfoHeaderSize = 40; 99 byte pBITMAPINFOHEADER[] = new byte[bitMapInfoHeaderSize]; 100 try 101 { 102 pFile.read(pBITMAPINFOHEADER, 0, bitMapInfoHeaderSize); 103 104 biSize = (((int)pBITMAPINFOHEADER[3] & 0xff) << 24) 105 | (((int)pBITMAPINFOHEADER[2] & 0xff) << 16) 106 | (((int)pBITMAPINFOHEADER[1] & 0xff) << 8) 107 | (int)pBITMAPINFOHEADER[0] & 0xff; 108 109 biWidth = (((int)pBITMAPINFOHEADER[7] & 0xff) << 24) 110 | (((int)pBITMAPINFOHEADER[6] & 0xff) << 16) 111 | (((int)pBITMAPINFOHEADER[5] & 0xff) << 8) 112 | (int)pBITMAPINFOHEADER[4] & 0xff; 113 114 biHeight = (((int)pBITMAPINFOHEADER[11] & 0xff) << 24) 115 | (((int)pBITMAPINFOHEADER[10] & 0xff) << 16) 116 | (((int)pBITMAPINFOHEADER[9] & 0xff) << 8) 117 | (int)pBITMAPINFOHEADER[8] & 0xff; 118 119 120 biPlanes = (((int)pBITMAPINFOHEADER[13] & 0xff) << 8) 121 | (int)pBITMAPINFOHEADER[12] & 0xff; 122 123 124 biBitCount = (((int)pBITMAPINFOHEADER[15] & 0xff) << 8) 125 | (int)pBITMAPINFOHEADER[14] & 0xff; 126 127 128 biCompression = (((int)pBITMAPINFOHEADER[19]) << 24) 129 | (((int)pBITMAPINFOHEADER[18]) << 16) 130 | (((int)pBITMAPINFOHEADER[17]) << 8) 131 | (int)pBITMAPINFOHEADER[16]; 132 133 biSizeImage = (((int)pBITMAPINFOHEADER[23] & 0xff) << 24) 134 | (((int)pBITMAPINFOHEADER[22] & 0xff) << 16) 135 | (((int)pBITMAPINFOHEADER[21] & 0xff) << 8) 136 | (int)pBITMAPINFOHEADER[20] & 0xff; 137 138 biXPelsPerMeter = (((int)pBITMAPINFOHEADER[27] & 0xff) << 24) 139 | (((int)pBITMAPINFOHEADER[26] & 0xff) << 16) 140 | (((int)pBITMAPINFOHEADER[25] & 0xff) << 8) 141 | (int)pBITMAPINFOHEADER[24] & 0xff; 142 143 144 biYPelsPerMeter = (((int)pBITMAPINFOHEADER[31] & 0xff) << 24) 145 | (((int)pBITMAPINFOHEADER[30] & 0xff) << 16) 146 | (((int)pBITMAPINFOHEADER[29] & 0xff) << 8) 147 | (int)pBITMAPINFOHEADER[28] & 0xff; 148 149 biClrUsed = (((int)pBITMAPINFOHEADER[35] & 0xff) << 24) 150 | (((int)pBITMAPINFOHEADER[34] & 0xff) << 16) 151 | (((int)pBITMAPINFOHEADER[33] & 0xff) << 8) 152 | (int)pBITMAPINFOHEADER[32] & 0xff; 153 154 biClrImportant = (((int)pBITMAPINFOHEADER[39] & 0xff) << 24) 155 | (((int)pBITMAPINFOHEADER[38] & 0xff) << 16) 156 | (((int)pBITMAPINFOHEADER[37] & 0xff) << 8) 157 | (int)pBITMAPINFOHEADER[36] & 0xff; 158 159 if(biBitCount == 24) 160 { 161 //构建位数组 162 //由于像素使用的字节若不是4的倍数,则会自动扩大,由此产生空白。因此我们需要在一开始计算出空白的大小 163 int biPad = (biSizeImage / biHeight) - biWidth * 3; 164 if(biPad == 4) 165 { 166 biPad = 0; 167 } 168 ColorModel = new int[biHeight * biWidth]; 169 //由此构建数组 170 byte brgb[] = new byte[biSizeImage]; 171 pFile.read(brgb, 0, biSizeImage); 172 173 int index = 0; 174 for(int j = 0; j < biHeight; j++) 175 { 176 for(int i = 0; i < biWidth; i++) 177 { 178 //为何要&255?透明度呀卧槽... 179 ColorModel[biWidth * (biHeight - j - 1) + i] = 180 (255 & 0xff) << 24 181 | (((int)brgb[index + 2] & 0xff) << 16) 182 | (((int)brgb[index + 1] & 0xff) << 8) 183 | (int)brgb[index] & 0xff; 184 index += 3; 185 } 186 index += biPad; 187 } 188 } 189 else 190 { 191 //抛出异常或打印提醒 192 System.out.println("不是24bit,暂时不能处理"); 193 } 194 } 195 //IO读取异常,FileInputStream和FileOutStream必须使用异常处理,略无奈 196 catch(IOException e) 197 { 198 e.printStackTrace(); 199 } 200 } 201 202 public Image getImage() 203 { 204 //MemoryImageSouce来自ImageProducer 205 Image pImage = null; 206 pImage = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource( 207 this.biWidth, this.biHeight, 208 this.ColorModel, 0, this.biWidth)); 209 return pImage; 210 } 211 } 212 213 public Image myRead(String bmpFileName) 214 { 215 //我们要读取到Image 216 Image pImage = null; 217 try 218 { 219 //读取文件 220 FileInputStream pFile = new FileInputStream(bmpFileName); 221 222 //保存位图头,弱弱地说这个暂时没用到,在保存文件时用了API 223 BITMAPFILEHEADER pBITMAPFILEHEADER = new BITMAPFILEHEADER(pFile); 224 225 // 保存位图信息 226 BITMAPINFOHEADER pBITMAPINFOHEADER = new BITMAPINFOHEADER(pFile); 227 228 pImage = pBITMAPINFOHEADER.getImage(); 229 230 pFile.close(); 231 232 return pImage; 233 } 234 //IO读取异常,FileInputStream和FileOutStream必须使用异常处理,略无奈 235 catch(IOException e) 236 { 237 e.printStackTrace(); 238 } 239 return null; 240 } 241 242 //根据二进制数据创建Image时可以使用API 243 public Image myWrite(Image pImage, String saveFileName) 244 { 245 try 246 { 247 File pImgFile = new File(saveFileName + ".bmp"); 248 BufferedImage pBufferedImage = new BufferedImage(pImage.getWidth(null), 249 pImage.getHeight(null), 250 BufferedImage.TYPE_INT_RGB); 251 Graphics pGraphics = pBufferedImage.getGraphics(); 252 pGraphics.drawImage(pImage, 0, 0, null); 253 pGraphics.dispose(); 254 ImageIO.write(pBufferedImage, "bmp", pImgFile); 255 } 256 catch (Exception e) 257 { 258 e.printStackTrace(System.out); 259 } 260 261 return pImage; 262 } 263 }
至于如何提取不同颜色通道,看下面的代码注释就了解了。如果大家学完第一第二部分,能够去看一下GridWorld的WorldFrame,看看GUIController和Grid如何分离,我记得里面有一个转灰色的方法,就可以模仿他的实现。
1 package ImageIOImplement; 2 import imagereader.IImageProcessor; 3 4 import java.awt.*; 5 import java.awt.image.*; 6 7 /* 8 * 根据不同颜色通道的提取实现不同的filterRGB方法。到处查看声明发现filterRGB是一个abstract方法。 9 * 查看调用发现是在filterIndexColorModel和filterRGBPixels中调用。 10 * 查看setColorModel,发现这个条件canFilterIndexColorModel && (model instanceof IndexColorModel)决定了是否调用自己实现的提取色彩通道的方法。 11 * 貌似必须这样做,所以就 12 * */ 13 public class ImageProcessor implements IImageProcessor 14 { 15 //R 16 public Image showChanelR(Image sourceImage) 17 { 18 RedFilter filter = new RedFilter(); 19 Toolkit kit = Toolkit.getDefaultToolkit(); 20 Image newimg = kit.createImage(new FilteredImageSource(sourceImage.getSource(), filter)); 21 return newimg; 22 } 23 //G 24 public Image showChanelG(Image sourceImage) 25 { 26 GreenFilter filter = new GreenFilter(); 27 Toolkit kit = Toolkit.getDefaultToolkit(); 28 Image newimg = kit.createImage(new FilteredImageSource(sourceImage.getSource(), filter)); 29 return newimg; 30 } 31 //B 32 public Image showChanelB(Image sourceImage) 33 { 34 BlueFilter filter = new BlueFilter(); 35 Toolkit kit = Toolkit.getDefaultToolkit(); 36 Image newimg = kit.createImage(new FilteredImageSource(sourceImage.getSource(), filter)); 37 return newimg; 38 } 39 //Gray 40 public Image showGray(Image sourceImage) 41 { 42 GrayFilter filter = new GrayFilter(); 43 Toolkit kit = Toolkit.getDefaultToolkit(); 44 Image newimg = kit.createImage(new FilteredImageSource(sourceImage.getSource(), filter)); 45 return newimg; 46 } 47 //实现自己的红色通道提取 48 class RedFilter extends RGBImageFilter 49 { 50 public RedFilter() 51 { 52 //查看RGBImageFilter的setColorModel方法,发现 53 //这个条件canFilterIndexColorModel && (model instanceof IndexColorModel) 54 //决定着是否调用自己的提取方法,所以将canFilterIndexColorModel设置为true 55 canFilterIndexColorModel = true; 56 } 57 public int filterRGB(int x, int y, int rgb) 58 { 59 return (rgb & 0xffff0000); 60 } 61 } 62 //Green通道提取 63 class GreenFilter extends RGBImageFilter 64 { 65 public GreenFilter() 66 { 67 canFilterIndexColorModel = true; 68 } 69 public int filterRGB(int x, int y, int rgb) 70 { 71 return (rgb & 0xff00ff00); 72 } 73 } 74 //Blue提取 75 class BlueFilter extends RGBImageFilter 76 { 77 public BlueFilter() 78 { 79 canFilterIndexColorModel = true; 80 } 81 public int filterRGB(int x, int y, int rgb) 82 { 83 return (rgb & 0xff0000ff); 84 } 85 } 86 //Gray提取 87 class GrayFilter extends RGBImageFilter 88 { 89 public GrayFilter() 90 { 91 canFilterIndexColorModel = true; 92 } 93 public int filterRGB(int x, int y, int rgb) 94 { 95 int gray = (int)(((rgb & 0x00ff0000)>>16)*0.299 + 96 ((rgb & 0x0000ff00)>>8)*0.587 + 97 (rgb & 0x000000ff)*0.114); 98 return (rgb & 0xff000000)+(gray<<16)+(gray<<8)+gray; 99 } 100 } 101 }
Runner:
package ImageIOImplement; import imagereader.Runner; public class MyRunner { public static void main(String[] args) { ImageIOImplement imgIO = new ImageIOImplement(); ImageProcessor processor = new ImageProcessor(); Runner.run(imgIO, processor); } }
单元测试嘛,我从开始学Junit的时候就去GitHub上查看官方文档(刚开始查到一个乱七八糟的文档然后被坑了),查看里面的参数化测试方法(链接:https://github.com/junit-team/junit/wiki/Parameterized-tests),里面给了个例子,非常容易懂,有一个方法定义了不同情境下的数据,然后在构造函数里面获取数据,然后有几组数据就会测试几次。
下面的代码也写得很清楚了,不过记得在读取图片的时候,要使用绝对地址。想要使用相对地址的貌似需要改点东西~
package ImageIOImplement; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import java.io.FileInputStream; import java.util.Collection; import java.util.Arrays; import java.awt.*; import javax.imageio.*; import java.lang.String; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class ImageReaderTest { //private String filePath0; //private String filePath1; private FileInputStream pFile0; private FileInputStream pFile1; private Image pImage0; private Image pImage1; @Parameters public static Collection<Object[]> prepareData() { return Arrays.asList(new Object[][]{ {"/home/gumc/Bitmap/bmptest/my/1_blue_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/1_blue_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/1_red_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/1_red_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/1_green_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/1_green_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/1_gray_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/1_gray_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/2_blue_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/2_blue_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/2_red_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/2_red_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/2_green_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/2_green_goal.bmp"}, {"/home/gumc/Bitmap/bmptest/my/2_gray_goal.bmp", "/home/gumc/Bitmap/bmptest/goal/2_gray_goal.bmp"}}); } public ImageReaderTest(String file1, String file2) throws Exception { //this.filePath0 = file1; //this.filePath1 = file2; this.pFile0 = new FileInputStream(file1); this.pFile1 = new FileInputStream(file2); this.pImage0 = ImageIO.read(pFile0); this.pImage1 = ImageIO.read(pFile1); } @Before public void setUp() throws Exception { } //比较分辨率(长宽像素值都相等) @Test public void testResolution() { assertEquals(pImage0.getHeight(null), pImage1.getHeight(null)); assertEquals(pImage0.getWidth(null), pImage1.getWidth(null)); } //比较像素值 @Test public void testPixValue() throws Exception { int sizeImage = pImage0.getHeight(null) * pImage1.getWidth(null); //将两个图片的位图数据部分全部读出,比较。 byte imageColor0[] = new byte[sizeImage]; byte imageColor1[] = new byte[sizeImage]; //跳过不属于位图信息的部分 pFile0.skip(54); pFile1.skip(54); pFile0.read(imageColor0, 0, sizeImage); pFile1.read(imageColor1, 0, sizeImage); //是否相等 assertEquals(imageColor1, imageColor1); } }
第三部分扩展部分第二天:

1. 无环路迷宫在数据结构上表现为一棵树,采用深度优先搜索算法就可以走出迷宫。本实验的目的是让同学们学习、理解和应用深度优先搜索算法。本实验要求同学们在改进的Grid World软件装置中实现深度优先搜索算法,从而使虫子走出迷宫。
就是用栈实现深搜嘛,刚开始一直纠结为何是Stack<ArrayList<Location>>来保存状态,而不是Stack<Location>来保存状态。想了很久才知道,原来他在栈里面就实现了保存路径和剪枝,这样的话就不用再去记录是否走过可否再走。例如我下面用栈的变化来解释为何要使用ArrayList<Location>:
0: (0, 0) //栈的初始状态
1: (0, 0), (0, 1) | (0,1) //走到(0,1)是的栈状态,我用|分割不同的ArrayList<Location>
2: (0, 0), (0, 1) | (0, 1), (0, 2) | (0, 2) //走到(0,2)是的栈状态
3: (0, 0), (0, 1) | (0, 1), (0, 2) //假如不能再走,则pop一下
4: (0, 0), (0, 1) | (0, 1), (0, 2), (1, 1) | (1, 1) //取出(0, 1)即栈顶链表的第一个元素,找到可以走的点(1, 1),为何不是(0, 2)了呢?因为已经在链表中,证明已经走过了。
这样就对为何使用ArrayList有所理解了。
下面是我实现的代码,写得很丑,非常丑,因为那天状态挺糟糕的,所以几乎写不出代码来。
1 package info.gridworld.maze; 2 3 import info.gridworld.actor.Actor; 4 5 /** 6 * A <code>MazeBug</code> can find its way in a maze. <br /> 7 * The implementation of this class is testable on the AP CS A and AB exams. 8 */ 9 public class MazeBug extends Bug 10 { 11 //public Location next; 12 ///public Location last; 13 //public Location last; 14 public boolean isEnd = false; 15 public Stack<ArrayList<Location>> crossLocation = new Stack<ArrayList<Location>>(); 16 public Integer stepCount = 0; 17 boolean hasShown = false;//final message has been shown 18 19 private int direction[] = {Location.NORTH, Location.EAST, Location.SOUTH, Location.WEST}; 20 class Probability 21 { 22 //North, East, South, West 23 public int dire[] = {1, 1, 1, 1}; 24 public Location getNextLocation(ArrayList<Location> array, Location nowLoc) 25 { 26 int size = array.size(); 27 int k = nowLoc.getDirectionToward(array.get(0)) / 90; 28 int max = dire[k]; 29 30 for (int i = 1; i < size; i++) 31 { 32 int j = nowLoc.getDirectionToward(array.get(i)) / 90; 33 int tmp = dire[j]; 34 if (max < tmp) 35 { 36 max = tmp; 37 k = j; 38 } 39 } 40 return nowLoc.getAdjacentLocation(direction[k]); 41 } 42 43 public void increase(int oneDirection) 44 { 45 if (oneDirection < 0) 46 throw new IllegalArgumentException("Direction " + oneDirection + " is not valid"); 47 dire[oneDirection / 90]++; 48 } 49 public void descrease(int oneDirection) 50 { 51 if (oneDirection < 0) 52 throw new IllegalArgumentException("Direction " + oneDirection + " is not valid"); 53 dire[oneDirection / 90]--; 54 } 55 } 56 //四个方向的概率 57 Probability prob = new Probability(); 58 59 /** 60 * Constructs a box bug that traces a square of a given side length 61 * 62 * @param length 63 * the side length 64 */ 65 public MazeBug() 66 { 67 setColor(Color.GREEN); 68 } 69 70 /** 71 * Moves to the next location of the square. 72 */ 73 public void act() 74 { 75 if (stepCount == 0) 76 { 77 ArrayList<Location> pLocs = new ArrayList<Location>(); 78 pLocs.add(this.getLocation()); 79 crossLocation.push(pLocs); 80 } 81 boolean willMove = canMove(); 82 if (isEnd == true) 83 { 84 //结束 85 if (hasShown == false) 86 { 87 String msg = stepCount.toString() + " steps"; 88 JOptionPane.showMessageDialog(null, msg); 89 hasShown = true; 90 } 91 } 92 else if (willMove) 93 { 94 //移动 95 move(); 96 stepCount++; 97 } 98 else 99 { 100 //回溯 101 backtracking(); 102 stepCount++; 103 } 104 } 105 106 /** 107 * Find all positions that can be move to. 108 * 109 * @param loc 110 * the location to detect. 111 * @return List of positions. 112 */ 113 //取得除了来时的Location外的所有下一步可走的Location 114 public ArrayList<Location> getValid(Location loc) 115 { 116 Grid<Actor> gr = getGrid(); 117 if (gr == null) 118 return null; 119 120 ArrayList<Location> valid = new ArrayList<Location>(); 121 if (crossLocation.size() > 0) 122 { 123 ArrayList<Location> first = crossLocation.pop(); 124 ArrayList<Location> second = null; 125 if (crossLocation.size() > 0) 126 second = crossLocation.peek(); 127 for (int i = 0; i < 4; i++) 128 { 129 Location next = loc.getAdjacentLocation(direction[i]); 130 Actor pActor = null; 131 if (gr.isValid(next)) 132 { 133 pActor = (Actor)gr.get(next); 134 if (pActor instanceof Rock && pActor.getColor().equals(new Color(255, 0, 0))) 135 { 136 isEnd = true; 137 valid.add(next); 138 this.setDirection(this.getLocation().getDirectionToward(next)); 139 this.moveTo(next); 140 } 141 else if (pActor == null) 142 { 143 if (!first.contains(next)) 144 { 145 valid.add(next); 146 } 147 } 148 } 149 } 150 if (valid.size() == 0) 151 { 152 153 for (int i = 0; i < 4; i++) 154 { 155 Location next = loc.getAdjacentLocation(direction[i]); 156 if (gr.isValid(next)) 157 { 158 Actor pActor = (Actor)gr.get(next); 159 if (pActor instanceof Flower) 160 { 161 if (second != null && !second.contains(next) && !first.contains(next)) 162 { 163 valid.add(next); 164 } 165 else if (second == null && !first.contains(next)) 166 { 167 valid.add(next); 168 } 169 } 170 } 171 } 172 } 173 crossLocation.add(first); 174 } 175 176 return valid; 177 } 178 179 /** 180 * Tests whether this bug can move forward into a location that is empty or 181 * contains a flower. 182 * 183 * @return true if this bug can move. 184 */ 185 public boolean canMove() 186 { 187 if (this.getValid(this.getLocation()).size() > 0) 188 return true; 189 return false; 190 } 191 /** 192 * Moves the bug forward, putting a flower into the location it previously 193 * occupied. 194 */ 195 public void move() 196 { 197 Grid gr = this.getGrid(); 198 if (gr == null) 199 return; 200 //保存原来的位置 201 Location loc = this.getLocation(); 202 //获取所有可移动的点 203 ArrayList<Location>pLocs = this.getValid(this.getLocation()); 204 //根据概率获取概率最大的点 205 Location next = prob.getNextLocation(pLocs, this.getLocation()); 206 //移动到该点 207 if (gr.isValid(next)) 208 { 209 this.setDirection(this.getLocation().getDirectionToward(next)); 210 this.moveTo(next); 211 prob.increase(this.getDirection()); 212 } 213 else 214 { 215 removeSelfFromGrid(); 216 return; 217 } 218 219 Flower flower = new Flower(getColor()); 220 flower.putSelfInGrid(gr, loc); 221 222 pLocs = crossLocation.pop(); 223 pLocs.add(next); 224 crossLocation.push(pLocs); 225 226 pLocs = new ArrayList<Location>(); 227 pLocs.add(next); 228 crossLocation.push(pLocs); 229 } 230 231 private void backtracking() 232 { 233 Grid gr = this.getGrid(); 234 if (gr == null) 235 return ; 236 if (crossLocation.size() > 0) 237 { 238 crossLocation.pop(); 239 if (crossLocation.size() > 0) 240 { 241 Location loc = this.getLocation(); 242 ArrayList<Location> pLocs = crossLocation.peek(); 243 Location next = pLocs.get(0); 244 if (gr.isValid(next)) 245 { 246 this.setDirection(loc.getDirectionToward(next)); 247 this.moveTo(next); 248 } 249 else 250 { 251 removeSelfFromGrid(); 252 return; 253 } 254 Flower flower = new Flower(getColor()); 255 flower.putSelfInGrid(gr, loc); 256 } 257 } 258 } 259 }
2. 参考已实现的MazeBug,定义一个继承Bug类的MazeDigBug类,需要实现以下功能。
(1)MazeDigBug能在一张N*N的布满石头的grid中挖出一个迷宫。注意虫子每次只能前进一步,不能出现闪烁飞行的情况。
(2)参考FinalMaze01.txt中的格式,自行学习Java的文件IO,定义好迷宫的终点并将迷宫输出到文件。然后将迷宫加载,并使用第一部分的MazeBug走出迷宫。
输出可以在地图生成时IO将文件地图输出(不需要GUI参与);也可以修改WorldFrame.java,仿造其中Load Map的实现添加Save Map按钮(保存地图时可以借助其中的saveMap方法将地图输出)。
注:因为状态太差了,所以几乎写不出代码。所以我就随意实现了一个只能挖奇数迷宫的代码,也就是一次走两格。本来想继续改成能挖出偶数的,但是好困...每个月总有那么一些天不想突然像来大姨爸一样。
1 package info.gridworld.maze; 2 3 import javax.swing.JOptionPane; 4 5 //迷宫生成虫,把石头当作路径,所以每次应该尝试走两步。一步是穿过墙,一步是到达新可能开辟通道的地方 6 public class MazeDigBug extends Bug 7 { 8 private Location next; 9 private boolean isEnd = false; 10 boolean hasShown = false; 11 private Stack<Location> pathLocation = new Stack(); 12 private boolean isContinuing = false; 13 private boolean isBacktracing = false; 14 private int direction[] = {Location.NORTH, Location.EAST, Location.SOUTH, Location.WEST}; 15 16 public MazeDigBug() 17 { 18 setColor(Color.green); 19 } 20 21 public void act() 22 { 23 boolean willMove = canMove(); 24 if (this.isEnd) 25 { 26 if (!this.hasShown) 27 { 28 String msg = "OK"; 29 JOptionPane.showMessageDialog(null, msg); 30 this.hasShown = true; 31 } 32 } 33 else if (willMove) 34 { 35 move(); 36 } 37 } 38 39 public ArrayList<Integer> getValid(Location loc) 40 { 41 Grid gr = getGrid(); 42 if (gr == null) 43 return null; 44 ArrayList valid = new ArrayList(); 45 for (int i = 0; i < 4; i++) 46 { 47 Location nextLoc0 = loc.getAdjacentLocation(direction[i]); 48 Location nextLoc1 = nextLoc0.getAdjacentLocation(direction[i]); 49 if (gr.isValid(nextLoc0) && gr.isValid(nextLoc1)) 50 { 51 Actor pActor0 = (Actor)gr.get(nextLoc0); 52 Actor pActor1 = (Actor)gr.get(nextLoc1); 53 if (pActor1 != null && pActor0 instanceof Rock && pActor1.getColor().equals(Color.black)) 54 { 55 valid.add(new Integer(direction[i])); 56 } 57 } 58 } 59 return valid; 60 } 61 62 public boolean canMove() 63 { 64 if (this.isContinuing == true) 65 { 66 this.isContinuing = false; 67 this.next = this.getLocation().getAdjacentLocation(this.getDirection()); 68 if (this.isBacktracing) 69 { 70 this.pathLocation.pop(); 71 } 72 return true; 73 } 74 75 ArrayList directions = this.getValid(this.getLocation()); 76 if (directions.size() >= 1) 77 { 78 this.isContinuing = true; 79 this.isBacktracing = false; 80 Random rand = new Random(); 81 int randNum = rand.nextInt(directions.size()); 82 int nextDir = ((Integer)directions.get(randNum)).intValue(); 83 next = this.getLocation().getAdjacentLocation(nextDir); 84 85 } 86 else if (!this.pathLocation.empty()) 87 { 88 this.isContinuing = true; 89 this.isBacktracing = true; 90 this.next = (Location)this.pathLocation.pop(); 91 } 92 else 93 { 94 this.isEnd = true; 95 return false; 96 } 97 return true; 98 } 99 100 public void move() 101 { 102 Grid gr = getGrid(); 103 if (gr == null) 104 return; 105 Location loc = getLocation(); 106 if (gr.isValid(this.next)) 107 { 108 setDirection(getLocation().getDirectionToward(this.next)); 109 moveTo(this.next); 110 if (!this.isBacktracing) 111 this.pathLocation.push(loc); 112 } 113 else 114 { 115 removeSelfFromGrid(); 116 } 117 } 118 }
弱弱地说一句,wiki上给了一个挖迷宫的jar包,可以去下载一下然后找些工具打开..因为java是翻译成字节码,然后再由jvm解释,所以想从class转成java是非常容易的。
第三天:

拼图部分很无聊,就是让我们写广搜和启发式搜索的估值函数,而且是照着伪代码去写。答辩的时候,我跟TA建议,这部分就不要给代码,直接给文档,里面说明一下各种数据结构及设计,然后由我们自己去编码。不要让我们在别人写的半个类中改代码,并且运用到别人写的另外的类。这样不小心就非常蛋疼。
直接上代码吧:
1 package jigsaw; 2 3 import java.io.FileWriter; 4 import java.io.IOException; 5 import java.io.PrintWriter; 6 import java.util.Iterator; 7 import java.util.Vector; 8 9 /** 在此类中填充算法,完成重拼图游戏(N-数码问题) 10 * @author abe 11 * 12 */ 13 public class Jigsaw { 14 JigsawNode beginJNode; // 拼图的起始状态节点 15 JigsawNode endJNode; // 拼图的目标状态节点 16 JigsawNode currentJNode; // 拼图的当前状态节点 17 private Vector<JigsawNode> openList; // open表 :用以保存已发现但未访问的节点 18 private Vector<JigsawNode> closeList; // close表:用以保存已访问的节点 19 private Vector<JigsawNode> solutionPath;// 解路径 :用以保存从起始状态到达目标状态的移动路径中的每一个状态节点 20 private boolean isCompleted; // 完成标记:初始为false;当求解成功时,将该标记至为true 21 private int searchedNodesNum; // 已访问节点数: 用以记录所有访问过的节点的数量 22 23 /**拼图构造函数 24 * @param bNode - 初始状态节点 25 * @param eNode - 目标状态节点 26 */ 27 public Jigsaw(JigsawNode bNode, JigsawNode eNode) { 28 this.beginJNode = new JigsawNode(bNode); 29 this.endJNode = new JigsawNode(eNode); 30 this.currentJNode = new JigsawNode(bNode); 31 this.openList = new Vector<JigsawNode>(); 32 this.closeList = new Vector<JigsawNode>(); 33 this.solutionPath = null; 34 this.isCompleted = false; 35 this.searchedNodesNum = 0; 36 } 37 38 /**此函数用于打散拼图:将输入的初始状态节点jNode随机移动len步,返回其打散后的状态节点 39 * @param jNode - 初始状态节点 40 * @param len - 随机移动的步数 41 * @return 打散后的状态节点 42 */ 43 public static JigsawNode scatter(JigsawNode jNode, int len) { 44 int randomDirection; 45 len += (int) (Math.random() * 2); 46 JigsawNode jigsawNode = new JigsawNode(jNode); 47 for (int t = 0; t < len; t++) { 48 int[] movable = jigsawNode.canMove(); 49 do{randomDirection = (int) (Math.random() * 4);} 50 while(0 == movable[randomDirection]); 51 jigsawNode.move(randomDirection); 52 } 53 jigsawNode.setInitial(); 54 return jigsawNode; 55 } 56 57 /**获取拼图的当前状态节点 58 * @return currentJNode - 拼图的当前状态节点 59 */ 60 public JigsawNode getCurrentJNode() { 61 return currentJNode; 62 } 63 64 /**设置拼图的初始状态节点 65 * @param jNode - 拼图的初始状态节点 66 */ 67 public void setBeginJNode(JigsawNode jNode) { 68 beginJNode = jNode; 69 } 70 71 /**获取拼图的初始状态节点 72 * @return beginJNode - 拼图的初始状态节点 73 */ 74 public JigsawNode getBeginJNode() { 75 return beginJNode; 76 } 77 78 /**设置拼图的目标状态节点 79 * @param jNode - 拼图的目标状态节点 80 */ 81 public void setEndJNode(JigsawNode jNode) { 82 this.endJNode = jNode; 83 } 84 85 /**获取拼图的目标状态节点 86 * @return endJNode - 拼图的目标状态节点 87 */ 88 public JigsawNode getEndJNode() { 89 return endJNode; 90 } 91 92 /**获取拼图的求解状态 93 * @return isCompleted - 拼图已解为true;拼图未解为false 94 */ 95 public boolean isCompleted() { 96 return isCompleted; 97 } 98 99 /**计算解的路劲 100 * @return 若有解,则将结果保存在solutionPath中,返回true; 若无解,则返回false 101 */ 102 private boolean calSolutionPath() { 103 if (!this.isCompleted()) { 104 return false; 105 } else { 106 JigsawNode jNode = this.currentJNode; 107 solutionPath = new Vector<JigsawNode>(); 108 while (jNode != null) { 109 solutionPath.addElement(jNode); 110 jNode = jNode.getParent(); 111 } 112 return true; 113 } 114 } 115 116 /**获取解路径文本 117 * @return 解路径solutionPath的字符串,若有解,则分行记录从初始状态到达目标状态的移动路径中的每一个状态节点; 118 * 若未解或无解,则返回提示信息。 119 */ 120 public String getSolutionPath() { 121 String str = new String(); 122 str += "Begin->"; 123 if (this.isCompleted) { 124 for (int i = solutionPath.size()-1; i>=0; i--) { 125 str += solutionPath.elementAt(i).toString() + "->"; 126 } 127 str+="End"; 128 } else 129 str = "Jigsaw Not Completed."; 130 return str; 131 } 132 133 /**获取访问过的节点数searchedNodesNum 134 * @return 返回所有已访问过的节点总数 135 */ 136 public int getSearchedNodesNum() { 137 return searchedNodesNum; 138 } 139 140 /**将搜索结果写入文件中,同时显示在控制台 141 * 若搜索失败,则提示问题无解,输出已访问节点数; 142 * 若搜索成功,则输出初始状态beginJnode,目标状态endJNode,已访问节点数searchedNodesNum,路径深度nodeDepth和解路径solutionPath。 143 * @param pw - 文件输出PrintWriter类对象,如果pw为null,则写入到D://Result.txt 144 * @throws IOException 145 */ 146 public void printResult(PrintWriter pw) throws IOException{ 147 boolean flag = false; 148 if(pw == null){ 149 pw = new PrintWriter(new FileWriter("Result.txt"));// 将搜索过程写入D://BFSearchDialog.txt 150 flag = true; 151 } 152 if (this.isCompleted == true) { 153 // 写入文件 154 pw.println("Jigsaw Completed"); 155 pw.println("Begin state:" + this.getBeginJNode().toString()); 156 pw.println("End state:" + this.getEndJNode().toString()); 157 pw.println("Solution Path: "); 158 pw.println(this.getSolutionPath()); 159 pw.println("Total number of searched nodes:" + this.getSearchedNodesNum()); 160 pw.println("Length of the solution path is:" + this.getCurrentJNode().getNodeDepth()); 161 162 163 // 输出到控制台 164 System.out.println("Jigsaw Completed"); 165 System.out.println("Begin state:" + this.getBeginJNode().toString()); 166 System.out.println("End state:" + this.getEndJNode().toString()); 167 System.out.println("Solution Path: "); 168 System.out.println(this.getSolutionPath()); 169 System.out.println("Total number of searched nodes:" + this.getSearchedNodesNum()); 170 System.out.println("Length of the solution path is:" + this.getCurrentJNode().getNodeDepth()); 171 172 173 } 174 else { 175 // 写入文件 176 pw.println("No solution. Jigsaw Not Completed"); 177 pw.println("Begin state:" + this.getBeginJNode().toString()); 178 pw.println("End state:" + this.getEndJNode().toString()); 179 pw.println("Total number of searched nodes:" 180 + this.getSearchedNodesNum()); 181 182 // 输出到控制台 183 System.out.println("No solution. Jigsaw Not Completed"); 184 System.out.println("Begin state:" + this.getBeginJNode().toString()); 185 System.out.println("End state:" + this.getEndJNode().toString()); 186 System.out.println("Total number of searched nodes:" 187 + this.getSearchedNodesNum()); 188 } 189 if(flag) 190 pw.close(); 191 } 192 193 /**探索所有与jNode邻接(上、下、左、右)且未曾被访问的节点 194 * @param jNode - 要探索的节点 195 * @return 包含所有与jNode邻接且未曾被访问的节点的Vector<JigsawNode>对象 196 */ 197 private Vector<JigsawNode> findFollowJNodes(JigsawNode jNode) { 198 Vector<JigsawNode> followJNodes = new Vector<JigsawNode>(); 199 JigsawNode tempJNode; 200 for(int i=0; i<4; i++){ 201 tempJNode = new JigsawNode(jNode); 202 if(tempJNode.move(i) && !this.closeList.contains(tempJNode) && !this.openList.contains(tempJNode)) 203 followJNodes.addElement(tempJNode); 204 } 205 return followJNodes; 206 } 207 208 /**排序插入openList:按照节点的代价估值(estimatedValue)将节点插入openList中,估值小的靠前。 209 * @param jNode - 要插入的状态节点 210 */ 211 private void sortedInsertOpenList(JigsawNode jNode) { 212 this.estimateValue(jNode); 213 for (int i = 0; i < this.openList.size(); i++) { 214 if (jNode.getEstimatedValue() < this.openList.elementAt(i) 215 .getEstimatedValue()) { 216 this.openList.insertElementAt(jNode, i); 217 return; 218 } 219 } 220 this.openList.addElement(jNode); 221 } 222 223 224 225 // **************************************************************** 226 // *************************实验任务************************ 227 /**实验任务一:广度优先搜索算法,求指定3*3拼图(8-数码问题)的最优解 228 * 要求:填充广度优先搜索算法BFSearch(),执行测试脚本RunnerPart1 229 * 主要涉及函数:BFSearch() 230 */ 231 /**实验任务二:启发式搜索算法,求解随机5*5拼图(24-数码问题) 232 * 要求:1.修改启发式搜索算法ASearch()和代价估计函数estimateValue(),执行测试脚本RunnerPart2 233 * 2.访问节点总数不超过25000个 234 * 主要涉及函数:ASearch(),estimateValue() 235 */ 236 // **************************************************************** 237 238 /**(实验一)广度优先搜索算法,求解指定3*3拼图(8-数码问题)的最优解。 239 * 要求函数结束后:1,isCompleted记录了求解完成状态; 240 * 2,closeList记录了所有访问过的节点; 241 * 3,searchedNodesNum记录了访问过的节点数; 242 * 4,solutionPath记录了解路径。 243 * @return isCompleted, 搜索成功时为true,失败为false 244 * @throws IOException 245 */ 246 public boolean BFSearch() throws IOException { 247 // 将搜索过程写入D://BFSearchDialog.txt 248 String filePath = "BFSearchDialog.txt"; 249 PrintWriter pw = new PrintWriter(new FileWriter(filePath)); 250 // ************************************* 251 252 // Write your code here. 253 //(1)将起始节点放入一个open列表中。 254 this.openList.add(this.beginJNode); 255 256 //(2)如果open列表为空,则搜索失败,问题无解;否则重复以下步骤: 257 while (openList.size() > 0) 258 { 259 // 1.访问open列表中的第一个节点v,若v为目标节点,则搜索成功,退出。 260 this.currentJNode = openList.firstElement(); 261 if (currentJNode.equals(this.endJNode)) 262 { 263 this.isCompleted = true; 264 this.calSolutionPath(); 265 break; 266 } 267 268 // 2.从open列表中删除节点v,放入close列表中。 269 this.openList.removeElementAt(0); 270 closeList.add(currentJNode); 271 searchedNodesNum++; 272 273 // 3.将所有与v邻接且未曾被访问的节点放入open列表中。 274 Vector<JigsawNode>v = this.findFollowJNodes(currentJNode); 275 int size = v.size(); 276 for (int i = 0; i < size; i++) 277 { 278 openList.add(v.get(i)); 279 } 280 } 281 282 // ************************************* 283 this.printResult(pw); 284 pw.close(); 285 System.out.println("Record into " + filePath); 286 return isCompleted; 287 } 288 289 /**(Demo+实验二)启发式搜索。访问节点数大于30000个则认为搜索失败。 290 * 函数结束后:isCompleted记录了求解完成状态; 291 * closeList记录了所有访问过的节点; 292 * searchedNodesNum记录了访问过的节点数; 293 * solutionPath记录了解路径。 294 * 搜索过程和结果会记录在D://DemoASearchDialog.txt中。 295 * @return 搜索成功返回true,失败返回false 296 * @throws IOException 297 */ 298 299 /*修改Jigsaw类中的启发式搜索算法ASearch()和代价估计函数estimateValue(), 300 * 对随机生成的5*5拼图初始状态进行求解。 301 * 具体要求: 302 * 1,修改Jigsaw类中的ASearch()和estimateValue()两个函数,可添加其他函数; 303 * 2,要求访问节点总数不超过25000个; 304 * 3,要求算法结束后: 305 * 1.isCompleted记录了求解完成状态; 306 * 2.closeList记录了所有访问过的节点; 307 * 3.searchedNodesNum记录了访问过的节点数; 308 * 4.solutionPath记录了解路径。 309 * 4,必须通过测试脚本RunnerPart2的测试检查。 310 * 注意:解此题前必须先把JigsawNode中的拼图维度dimension改为5。 311 * 5,进行多次测试(次数>=20),画出频率分布折线图,在文档中提交。如图6所示, 312 * X轴表示步数(<5000, <10000, ... , <25000, >25000),Y轴为步数在该范围内的频率。 313 * TA会对程序进行抽查,因此不要捏造数据或故意选取跑的好的结果。 314 * */ 315 /* 估价函数f(n)用来估计节点n的重要性,表示为:从起始节点,经过节点n,到达目标节点的代价。 316 * f(n)越小,表示节点n越优良,应该优先访问它的邻接节点。可参考的估价方法有: 317 1) 所有 放错位的数码 个数 318 2) 所有 放错位的数码与其正确位置的距离 之和 319 3) 后续节点不正确的数码个数 320 4) ..... 321 可以同时使用多个估价方法,f(n) = a*f1(n) + b*f2(n) „„,通过适当调整权重(a、b、„„), 322 能够加快搜索速度。 323 * */ 324 public boolean ASearch() throws IOException{ 325 // 将搜索过程写入ASearchDialog.txt 326 String filePath = "ASearchDialog.txt"; 327 PrintWriter pw = new PrintWriter(new FileWriter(filePath)); 328 329 // 访问节点数大于30000个则认为搜索失败 330 int maxNodesNum = 25000; 331 332 // 用以存放某一节点的邻接节点 333 Vector<JigsawNode> followJNodes = new Vector<JigsawNode>(); 334 335 // 重置求解完成标记为false 336 isCompleted = false; 337 338 // (1)将起始节点放入openList中 339 this.sortedInsertOpenList(this.beginJNode); 340 341 // (2) 如果openList为空,或者访问节点数大于maxNodesNum个,则搜索失败,问题无解;否则循环直到求解成功 342 while (this.openList.isEmpty() != true && searchedNodesNum <= maxNodesNum) { 343 344 // (2-1)访问openList的第一个节点N,置为当前节点currentJNode 345 // 若currentJNode为目标节点,则搜索成功,设置完成标记isCompleted为true,计算解路径,退出。 346 this.currentJNode = this.openList.elementAt(0); 347 if (this.currentJNode.equals(this.endJNode)){ 348 isCompleted = true; 349 this.calSolutionPath(); 350 break; 351 } 352 353 // (2-2)从openList中删除节点N,并将其放入closeList中,表示以访问节点 354 this.openList.removeElementAt(0); 355 this.closeList.addElement(this.currentJNode); 356 searchedNodesNum++; 357 358 // 记录并显示搜索过程 359 pw.println("Searching.....Number of searched nodes:" + this.closeList.size() + " Current state:" + this.currentJNode.toString()); 360 System.out.println("Searching.....Number of searched nodes:" + this.closeList.size() + " Current state:" + this.currentJNode.toString()); 361 362 // (2-3)寻找所有与currentJNode邻接且未曾被访问的节点,将它们按代价估值从小到大排序插入openList中 363 followJNodes = this.findFollowJNodes(this.currentJNode); 364 while (!followJNodes.isEmpty()) { 365 this.sortedInsertOpenList(followJNodes.elementAt(0)); 366 followJNodes.removeElementAt(0); 367 } 368 } 369 370 this.printResult(pw); // 记录搜索结果 371 pw.close(); // 关闭输出文件 372 System.out.println("Record into " + filePath); 373 return isCompleted; 374 } 375 376 /**(Demo+实验二) 377 * @param jNode - 要计算代价估计值的节点;此函数会改变该节点的estimatedValue属性值。 378 */ 379 private void estimateValue(JigsawNode jNode) 380 { 381 int s = 0 * getEstimateValue0(jNode) + 382 1 * getEstimateValue1(jNode) + 383 1 * getEstimateValue2(jNode); 384 jNode.setEstimatedValue(s); 385 } 386 //所有 放错位的数码 个数 387 private int getEstimateValue0(JigsawNode jNode) 388 { 389 int s = 0; 390 int dimension = JigsawNode.getDimension(); 391 int currentNodeState[] = jNode.getNodesState(); 392 int endNodeState[] = endJNode.getNodesState(); 393 for (int i = 1; i <= dimension * dimension; i++) 394 { 395 if (currentNodeState[i] != endNodeState[i]) 396 s++; 397 } 398 return s; 399 } 400 //所有 放错位的数码与其正确位置的距离 之和 401 private int getEstimateValue1(JigsawNode jNode) 402 { 403 int s = 0; 404 int col0, row0, col1, row1; 405 406 int dimension = JigsawNode.getDimension(); 407 int currentNodeState[] = jNode.getNodesState(); 408 int endNodeState[] = endJNode.getNodesState(); 409 410 for (int i = 1; i <= dimension * dimension; i++) 411 { 412 if (currentNodeState[i] != 0 && currentNodeState[i] != endNodeState[i]) 413 { 414 row0 = (int) (i - 1) / dimension; 415 col0 = (int) (i + 4) % dimension; 416 for (int j = 0; j <= dimension * dimension; j++) 417 { 418 if (currentNodeState[i] == endNodeState[j]) 419 { 420 row1 = (int) (j - 1) / dimension; 421 col1 = (int) (j + 4) % dimension; 422 s += (Math.abs(row1 - row0) + Math.abs(col1 - col0)); 423 break; 424 } 425 } 426 } 427 } 428 return s; 429 } 430 // 后续节点不正确的数码个数 431 //计算并修改状态节点jNode的代价估计值:f(n)=s(n)。 432 // s(n)代表后续节点不正确的数码个数 433 private int getEstimateValue2(JigsawNode jNode) 434 { 435 int s = 0; 436 int dimension = JigsawNode.getDimension(); 437 for(int index =1 ; index<dimension*dimension; index++){ 438 if(jNode.getNodesState()[index]+1!=jNode.getNodesState()[index+1]) 439 s++; 440 } 441 return s; 442 } 443 }
第四天:

答辩呀,感谢我们有一个做得了学霸写得了代码,写得了文档做得了ppt的队友~
有问题欢迎提问。看到这篇文章的各位,希望大家还是专心写点代码,不要总想着找一些可以参考的,这样会养成自己非常不严谨和不擅长思考的习惯,渐渐地就会变成菜鸟。前两部分有时间的话,记得多看一下WorkFrame,GUI,Grid这三大块(其实就只有数据和UI两块)。如果大家对写游戏有兴趣,欢迎联系我,发邮件给我。

浙公网安备 33010602011771号