中级实训总结报告
目錄【閱讀時間:約30分鐘】
- 中級實訓總結報告
- 姓名:隱藏敏感信息 學號:隱藏敏感信息
- 一、階段1:項目啟動
- 1、Vi/Vim
- 2、Java
- 3、Ant
- 4、Junit
- 5、SonarQube
- 6、 編譯運行BugRunner
- 二、階段2:基本任務
- 1. part2的工作
- (1)CircleBug
- (2)SpiralBug
- (3)ZBug
- (4)DancingBug
- 2. part3的工作
- 1. Test for `act()`
- 2. Test for `jump()`
- 3. Test for `canJump()`
- 4. Result
- 3. part4的工作
- (1)ModifiedChameleonCritter
- (2)ChameleonKid
- (3)RockHound
- (4)BlusterCritter
- (5)QuickCrab
- (6)KingCrab
- 4. part5的工作
- (1)SparseGridNode
- (2)SparseBoundedGrid
- (3)SparseBoundedGrid2
- (4)UnboundedGrid2
- (5)SparseGridRunner
- 三、階段3:擴展任務
- 1. imagereader的工作
- 2. imagereader的readme文件
- (1)ImagaReaderRunner.java
- (2)ImplementImageIO.java
- (3)ImplementImageProcessor.java
- (4)ImageProcessorTest.java
- 3. mazebug的工作
- 4. mazebug的readme文件
- (1)MazeBugRunner.java
- (2)MazeBug.java
- (3)Examples
- 5. jigsaw的工作
- 四、總結和心得體會
中級實訓總結報告
姓名:隱藏敏感信息 學號:隱藏敏感信息
持續了五周的中級實訓終于快結束啦,今天筆者來總結一下整個中級實訓過程中的付出與收獲。不妨回顧了一下中級實訓的架構,可以說中級實訓安排還算比較合理,從Vim+Java+Ant+Junit等工具的使用,到初步了解girdworld的架構和設計思想,再到自己手動完成girdworld的一部分代碼,這一段時間是一個循序漸進的時期,對于不太熟悉整個軟件開發流程的筆者而言,收獲是非常大的。最后還有一個擴展任務,個人認為是基本任務的兩倍難度,不過得益于網絡資料和TA們的指導,筆者還是順利地完成了三個擴展任務,可謂是很滿足了~
此外,在檢查階段TA們也很盡心盡責,會詢問各個階段的代碼以及其實現思想,也會指出代碼存在哪些問題,再次感謝~
一、階段1:項目啟動
在階段1,我們主要是圍繞Vim+Java+Ant+Junit展開,具體流程可概括為:使用Vim來編寫Java代碼, 利用Ant實現Java代碼的自動編譯,利用Junit來進行單元測試。由于在階段1并沒有進行過多與gridworld相關的代碼實現,此處就簡述一下各個部分的學習心得吧。
1、Vi/Vim
Vim編譯器是一款久負盛名的編譯器,常常被稱為“編譯器之神”或“大神用的編譯器”,它是linux中最經典的文本編輯器,支持代碼補全、編譯及錯誤跳轉等功能。對于雖然不能說完全掌握Vim但也有幾年使用經驗的筆者來說,筆者更傾向于在CLI界面下使用Vim,比如實驗室服務器往往就通過ssh連接linux terminal,這時使用Vim編輯器可以很好地編寫各種代碼。而在GUI界面下,筆者更傾向于使用sublime或eclipse來編寫java代碼,特別是后者在設置了熟悉的自動補全后,可以提升地提升工作效率。當然,在此次實訓中筆者會盡量使用Vim來熟悉項目開發流程。
Vim編譯器一共有命令模式、末行模式和編輯模式這三種工作模式,這三種模式的區別可以用以下一段話來概括。使用Vim打開文件時首先進入的就是命令模式,可以看做Vim編譯器的入口,可以進行復制粘貼等操作;在命令模式按 i 便會進入編輯模式,可以進行各種代碼或文字編輯操作,再按esc便會退回到命令模式;在命令模式按輸入 : 進入末行模式,可以進行保存退出等操作。
筆者常用的Vim指令有以下這些:
? ①打開文件:在terminal輸入vim filename
? ②保存并退出:在末行模式輸入wq
? ③復制一行代碼:在命令行模式輸入yy
? ④粘貼:在命令行模式輸入p
? ⑤刪除一行代碼:在命令行模式輸入dd
? ⑥撤銷操作:在命令行模式輸入u
? ⑦在編輯模式下:與文本編輯類似,方向鍵移動,正常鍵盤輸入
2、Java
由于在大二時筆者已選修了Java語言開發的相關課程,因此在進行這部分的自學時比較輕松,主要是溫故了Java的基本語法和使用命令行編譯運行Java程序。
在筆者看來Java是是一門典型的面向對象編程語言,它盡可能地進行封裝,擁有豐富的庫函數與接口支持,對于協同開發效率很高,這也是Java常年位于編程語言排行榜前列的原因之一。Java程序的編寫流程也不復雜,以熟悉的eclipse工具為例,主要流程是創建一個Java project,創建相應的package和class,然后寫main函數與要調用的函數即可。此處便省略基礎的Java語法介紹了,直接用一個簡單的helloworld例子來說明如何使用命令行創建、編譯并運行Java程序吧~~
首先創建并書寫代碼:
然后使用命令行編譯運行Java程序:
此外,雖然云桌面的環境變量已經配置了很多,但是直接執行java helloworld還是會報錯: could not find or load main class helloworld 。
經搜索相關資料,是java的環境設置不完全,只要在 ~/ .bashrc和~/ .profile添加以下的環境變量設置即可:
進入 ~/.bashrc和~/.profile的指令如下:
gedit ~/.bashrc
gedit ~/.profile要添加的環境變量如下:
export JAVA_HOME=~/Desktop/java1.8
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
3、Ant
正如上文所述,Ant可用于實現Java代碼的自動編譯,Ant是一個將軟件編譯、測試、部署等步驟聯系在一起加以自動化的一個工具,大多用于Java環境中的軟件開發。根據筆者的自學體會,它的作用與C/C++的makefile類似,一次性編譯多個文件,大大方便了項目編譯的流程,能減少很多重復的操作。
Ant使用xml格式來編寫,若要在項目中使用Ant,首先要在項目文件下面創建build.xml文件,并在xml文件中編寫與項目相關的指令。編寫完畢后,在項目文件下執行ant指令即可。
Ant中主要的元素有三種,分別是、和。
(1)project 元素是 Ant 構件文件的根元素, Ant 構件文件至少應該包含一個 project 元素,否則會發生錯誤。在每個 project 元素下,可包含多個 target 元素。 project 元素有如下屬性:
? ①name 屬性:用于指定 project 元素的名稱
? ②default 屬性:用于指定 project 默認執行時所執行的 target 的名稱
? ③basedir 屬性:用于指定基路徑的位置。該屬性沒有指定時,使用 Ant 的構件文件的附目錄
作為基準目錄。
(2)target 元素是Ant 的基本執行單元,它可以包含一個或多個具體的任務。多個 target 可以存在相互依賴關系。target 元素有如下屬性:
? ①name 屬性:指定 target 元素的名稱,這個屬性在一個 project 元素中是唯一的。我們可以通過指定 target 元素的名稱來指定某個 target
? ②depends 屬性:用于描述 target 之間的依賴關系,若與多個 target 存在依賴關系時,需
要以“,”間隔。 Ant 會依照 depends 屬性中 target 出現的順序依次執行每個target。被依賴的 target 會先執行
? ③if 屬性:用于驗證指定的屬性是否存在,若不存在,所在 target 將不會被執行
? ④unless 屬性:該屬性的功能與 if 屬性的功能正好相反,它也用于驗證指定的屬性是否存在,若不存在,所在 target 將會被執行
? ⑤description 屬性:該屬性是關于 target 功能的簡短描述和說明
(3)property 元素:該元素可看作參量或者參數的定義,類似于C/C++中的宏定義,可以用一些我們便于理解的變量名來代替復雜的路徑,提高代碼的可讀性。
project 的屬性可以通過 property元素來設定,也可在 Ant 之外設定。若要在外部引入某文件,例如 build.properties 文件,可以通過如下內容將其引入: <property file=” build.properties”/>
下面不妨借助【二、Java】中的helloworld代碼,編寫一個簡單的build.xml來體驗Ant工具。在項目目錄helloworld-ant中,將helloworld.java放在src文件夾中,在項目目錄下創建build.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?><!-- Created by 18342026 --><project name="helloworld" default="run" basedir="."><property name="src_path" value="src"/><property name="class_path" value="classes"/><target name="clean"><delete dir="${class_path}"/></target><target name="compile" depends="clean"><mkdir dir="${class_path}"/><javac srcdir="${src_path}" destdir="${class_path}"/></target><target name="run" depends="compile"><java classname="helloworld" classpath="${class_path}" /></target></project>
在項目目錄下運行指令ant,即可自動編譯并運行如下:
4、Junit
Junit是Java的一種單元測試框架,它所做的測試是白盒測試,常用于測試驅動開發(Test-Driven Development,簡稱TDD) 。由于之前選修的Java課程已接觸過Junit框架,并且最近也通過Golang課程學習了TDD開發流程的思維,其實就是通過函數的輸入輸出來確定函數的正確性。
要使用Junit,需要配置好環境,并將junit的jar文件放到項目目錄中。由于筆者查看的junit教程版本為junit 4.10,仍然以簡單的helloworld程序為例,在項目目錄helloworld-junit中,放入junit-4.10.jar、編寫helloworld.java和Test_helloworld.java文件。
其中helloworld.java代碼為:
public class helloworld{public static void main(String[] argv) {System.out.println(sayhello());} public static String sayhello() {return "Hello World By 18342026!";}
}
Test_helloworld.java代碼為:
import static org.junit.Assert.*;
import org.junit.Test;public class Test_helloworld{@Test public void test_helloworld() {String res = helloworld.sayhello();assertEquals("Hello World By 18342026!", res);}
}
最后執行以下指令即可運行junit進行單元測試:
javac -classpath .:junit-4.10.jar Test_helloworld.java
java -classpath .:junit-4.10.jar -ea org.junit.runner.JUnitCore Test_helloworld
運行截圖:
5、SonarQube
由于中級實訓階段一要用到SonarQube,雖然沒有要求寫相應的報告,但筆者感覺書寫報告還是對提高軟件的熟練度有幫助的。
Sonar 是一個用于代碼質量管理的開源平臺,用于管理源代碼的質量,可以從七個維度檢測代碼質量。通過插件形式,可以支持包括 java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy 等等二十幾種編程語言的代碼質量管理與檢測。
經檢查,云桌面已配置好Sonar了:
在 shell 里面鍵入 cd $SONAR_HOME, 可以直接進入啟動目錄。 在 shell 里面鍵入
./sonar.sh start 啟動服務
./sonar.sh stop 停止服務
./sonar.sh restart 重啟服務
訪問 http:\localhost:9000, 如果顯示 Sona輸入rQube 的項目管理界面,表示安裝成功:
以階段一的calculator程序為例,在項目目錄下創建sonar-project.properties,其中只需要將sonar.projectKey、sonar.projectName和java-module.sonar.projectBaseDir的值修改為源碼所在的文件夾即可,具體內容如下:
然后在當前目錄下輸入sonar-runner,再進入http:\localhost:9000查看程序的代碼分析即可:
經過Major的提示修改后,學到了不少代碼風格的知識,修改后如下:
6、 編譯運行BugRunner
在倉庫( https://github.com/se-2020/se-2020.github.io/tree/master/resources )中下載gridworld.zip并解壓,再將里面的GridWorldCode.zip解壓出來,進入目錄GridWorldCode\projects\firstProject\中,即可看到BugRunner.java代碼。
由于環境在之前已配置完畢,直接在當前目錄下運行以下指令,即可進入BugRunner程序中:
javac -classpath .:./../../gridworld.jar BugRunner.java
java -classpath .:./../../gridworld.jar BugRunner
再來簡單介紹一下BugRunner,首先用戶可以自行在格子中放置可移動的蟲子以及不可移動的花和石頭,然后點擊step是移動一步,點擊run是自動開始移動多步,slow/fast的游標可以調節移動速度,stop則是停止。其他更詳細的信息可看Part1的回答。
二、階段2:基本任務
在階段2,我們正式進入了gridworld的代碼世界,從part2到part5,一共需要完成14個子模塊的代碼,代碼量還是比較大的。不過由于在實訓博客都給出了詳細的設計介紹,并且還有相應的示例,所以往往在寫完一個子模塊的代碼后,就能遷移至其他子模塊,編寫代碼的速度也快了不少。此外,筆者還學習了如何寫規范的readme文件便于其他人快速了解程序的使用,雖然階段2的代碼簡單,但文檔和sonnar-qube的修改等,還是花了筆者三四天的時間。
下面還是按照part2到part5來介紹一下這個階段所做的工作(因為寫了英文的readme,此處就直接引用啦):
1. part2的工作
This project can be run using Eclipse. Its introduction and running screenshots are listed below. We can also use Sonar-Qube for code analysis of project files.
(1)CircleBug
Write a class CircleBug that is identical to BoxBug, except that in the act method the turn method is called once instead of twice.
(2)SpiralBug
Write a class SpiralBug that drops flowers in a spiral pattern. Hint: Imitate BoxBug, but adjust the side length when the bug turns.
?
(3)ZBug
Write a class ZBug to implement bugs that move in a “Z” pattern, starting in the top left corner. After completing one “Z” pattern, a ZBug should stop moving. In any step, if a ZBug can’t move and is still attempting to complete its “Z” pattern, the ZBug does not move and should not turn to start a new side. Supply the length of the “Z” as a parameter in the constructor. The following image shows a “Z” pattern of length 4. Hint: Notice that a ZBug needs to be facing east before beginning its “Z” pattern.
(4)DancingBug
Write a class DancingBug that “dances” by making different turns before each move. The DancingBug constructor has an integer array as parameter. The integer entries in the array represent how many times the bug turns before it moves. For example, an array entry of 5 represents a turn of 225 degrees (recall one turn is 45 degrees). When a dancing bug acts, it should turn the number of times given by the current array entry, then act like a Bug. In the next move, it should use the next entry in the array. After carrying out the last turn in the array, it should start again with the initial array value so that the dancing bug continually repeats the same turning pattern. The DancingBugRunner class should create an array and pass it as aparameter to the DancingBug constructor.
2. part3的工作
這部分我們要涉及一個jumper對象,其設計如下:
#### 1. What will a jumper do if the location in front of it is empty, but the location two cells in front contains a flower or a rock?**Answer:**①If the location contains a flower, it will jump onto the flower without leaving a flower in the previous location.②If the location contains a rock, it will turn 45 degrees to the right.#### 2. What will a jumper do if the location two cells in front of the jumper is out of the grid?**Answer:**①If called by `jump()` method, it will remove the jumper as if it jumps out of the grid.②If called by `act()` methods, it will turn 45 degrees to the right.#### 3. What will a jumper do if it is facing an edge of the grid?**Answer:**①If called by `jump()` method, remove the jumper as if it jumps out of the grid.②If called by `act()` methods, turn 45 degrees to the right.#### 4. What will a jumper do if another actor (not a flower or a rock) is in the cell that is two cells in front of the jumper?**Answer:**It will turn 45 degrees to the right.#### 5. What will a jumper do if it encounters another jumper in its path?**Answer:**①If the location one cells in front of the jumper is another jumper, it will jump.②If the location two cells in front of the jumper is another jumper, it will turn 45 degrees to the right.#### 6. Are there any other tests the jumper needs to make?**Answer:**When the jumper jump onto a flower, then it leaves, the flower will disappear.When the location in front of the jumper contains a bug, it can’t jump onto the bug.
此外,我們還要編寫單元測試來檢測設計的正確性,此處筆者根據jumper的特性設計了19個單元測試(主要分為act()、jump()和 canJump()函數的測試):
For testing for Jumper, the main methods to be tested are act(), jump() and canJump(). According to the design report, I designed 9 tests for act(), 2 tests for jump(), and 8 tests for canJump().
1. Test for act()
(1)test jump over when the location one cells in front contains a rock
@Testpublic void testAct0() {//test jump over when the location one cells in front contains a rockworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(4, 5), new Rock());world.step();assertEquals(new Location(3, 5), jumper.getLocation());}
(2)test jump over when the location one cells in front contains a flower
@Testpublic void testAct1() {//test jump over when the location one cells in front contains a flowerworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(4, 5), new Flower());world.step();assertEquals(new Location(3, 5), jumper.getLocation());}
(3)test turn when the location two cells in front contains a rock
@Testpublic void testAct2() {//test turn when the location two cells in front contains a rockworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Rock());world.step();assertEquals(45, jumper.getDirection());}
(4)test jump when the location two cells in front contains a flower
@Testpublic void testAct3() {//test jump when the location two cells in front contains a flowerworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Flower());world.step();assertEquals(new Location(3, 5), jumper.getLocation());}
(5)test turn when the location two cells in front of the jumper is out of the grid
@Testpublic void testAct4() {//test turn when the location two cells in front of the jumper is out of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(1, 5), jumper);world.step();assertEquals(45, jumper.getDirection());}
(6)test turn when the the jumper is facing an edge of the grid
@Testpublic void testAct5() { //test turn when the the jumper is facing an edge of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(0, 5), jumper);world.step();assertEquals(45, jumper.getDirection());}
(7)test turn when another actor (not a flower or a rock) is in the cell that is two cells in front of the jumper
@Testpublic void testAct6() {//test turn when another actor (not a flower or a rock) is in the cell that is two cells in front of the jumperworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Actor());world.step();assertEquals(45, jumper.getDirection());}
(8)test turn when jumper encounters another jumper in its path
@Testpublic void testAct7() {//test turn when jumper encounters another jumper in its pathworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);Jumper jumper2 = new Jumper();world.add(new Location(3, 5), jumper2);jumper2.setDirection(180);world.step();assertEquals(45, jumper.getDirection());assertEquals(225, jumper2.getDirection());}
(9)test no left flower when jumer leaves on a flower
@Testpublic void testAct8() {//test no left flower when jumer leaves on a flowerworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Flower());world.step();assertEquals(new Location(3, 5), jumper.getLocation());world.step();assertEquals(null, jumper.getGrid().get(new Location(3, 5)));}
2. Test for jump()
(1)test remove when jumper is facing an edge of the grid
@Testpublic void testJump0() {//test remove when jumper is facing an edge of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(0, 5), jumper);jumper.jump();assertEquals(null, jumper.getGrid());}
(2)test remove when the location two cells in front of the jumper is out of the grid
@Testpublic void testJump1() {//test remove when the location two cells in front of the jumper is out of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(1, 5), jumper);jumper.jump();assertEquals(null, jumper.getGrid());}
3. Test for canJump()
(1)test true when jump over when the location one cells in front contains a rock
@Testpublic void testCanJump0() {//test true when jump over when the location one cells in front contains a rockworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(4, 5), new Rock());assertEquals(true, jumper.canJump());}
(2)test true when jump over when the location one cells in front contains a flower
@Testpublic void testCanJump1() {//test true when jump over when the location one cells in front contains a flowerworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(4, 5), new Flower());assertEquals(true, jumper.canJump());}
(3)test false when the location two cells in front contains a rock
@Testpublic void testCanJump2() {//test false when the location two cells in front contains a rockworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Rock());assertEquals(false, jumper.canJump());}
(4)test true when the location two cells in front contains a flower
@Testpublic void testCanJump3() {//test true when the location two cells in front contains a flowerworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Flower());assertEquals(true, jumper.canJump());}
(5)test false when the location two cells in front of the jumper is out of the grid
@Testpublic void testCanJump4() {//test false when the location two cells in front of the jumper is out of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(1, 5), jumper);assertEquals(false, jumper.canJump());}
(6)test false when the the jumper is facing an edge of the grid
@Testpublic void testCanJump5() {//test false when the the jumper is facing an edge of the gridworld = new ActorWorld();jumper = new Jumper();world.add(new Location(0, 5), jumper);assertEquals(false, jumper.canJump());}
(7)test false when another actor (not a flower or a rock) is in the cell that is two cells in front of the jumper
@Testpublic void testCanJump6() {//test false when another actor (not a flower or a rock) is in the cell that is two cells in front of the jumperworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);world.add(new Location(3, 5), new Actor());assertEquals(false, jumper.canJump());}
(8)test false when encounters another jumper in its path
@Testpublic void testCanJump7() {//test false when encounters another jumper in its pathworld = new ActorWorld();jumper = new Jumper();world.add(new Location(5, 5), jumper);Jumper jumper2 = new Jumper();world.add(new Location(3, 5), jumper2);jumper2.setDirection(180);assertEquals(false, jumper.canJump());assertEquals(false, jumper2.canJump());}
4. Result
3. part4的工作
This project can be run using Eclipse. Its introduction and running screenshots are listed below. We can also use Sonar-Qube for code analysis of project files.
(1)ModifiedChameleonCritter
Modify the processActors method in ChameleonCritter so that if the list of actors to process is empty, the color of the ChameleonCritter will darken (like a flower).
(2)ChameleonKid
Create a class called ChameleonKid that extends ChameleonCritter as modified in exercise 1. A ChameleonKid changes its color to the color of one of the actors immediately in front or behind. If there is no actor in either of these locations, then the ChameleonKid darkens like the modified ChameleonCritter.
(3)RockHound
Create a class called RockHound that extends Critter. A RockHound gets the actors to be processed in the same way as a Critter. It removes any rocks in that list from the grid. A RockHound moves like a Critter.
?
(4)BlusterCritter
Create a class BlusterCritter that extends Critter. A BlusterCritter looks at all of the neighbors within two steps of its current location. (For a BlusterCritter not near an edge, this includes 24 locations). It counts the number of critters in those locations. If there are fewer than c critters, the BlusterCritter’s color gets brighter (color values increase). If there are c or more critters, the BlusterCritter’s color darkens (color values decrease). Here, c is a value that indicates the courage of the critter. It should be set in the constructor.
(5)QuickCrab
Create a class QuickCrab that extends CrabCritter. A QuickCrab processes actors the same way a CrabCritter does. A QuickCrab moves to one of the two locations, randomly selected, that are two spaces to its right or left, if that location and the intervening location are both empty. Otherwise, a QuickCrab moves like a CrabCritter.
(6)KingCrab
Create a class KingCrab that extends CrabCritter. A KingCrab gets the actors to be processed in the same way a CrabCritter does. A KingCrab causes each actor that it processes to move one location further away from the KingCrab. If the actor cannot move away, the KingCrab removes it from the grid. When the KingCrab has completed processing the actors, it moves like a CrabCritter.
4. part5的工作
This project can be run using Eclipse. Its introduction and running screenshots are listed below. We can also use Sonar-Qube for code analysis of project files.
(1)SparseGridNode
A SparseGridNode is a basic node in the grid, namely a small lattice.
(2)SparseBoundedGrid
A SparseBoundedGrid is a rectangular grid with bounded edges and sparse objects in the gird implemented by sparse array.
(3)SparseBoundedGrid2
A SparseBoundedGrid2 is a rectangular grid with bounded edges and sparse objects in the gird implemented by HashMap.
(4)UnboundedGrid2
An UnboundedGrid is a rectangular grid with unbounded number of rows and columns. When the access exceeds the current size, its size will be doubled.
(5)SparseGridRunner
This class runs a world with additional grid choices. We can use the SparseBoundedGrid, SparseBoundedGrid2 or UnboundedGrid2 methods to build a grid world.
三、階段3:擴展任務
階段3的擴展任務分為imagereader、mazebug和jigsaw,這三者毫無疑問是整個中級實訓最難的一部分,其中主要的難點在于理解任務與框架,java的編程實現也比階段2按模板“填鴨式”的編程要難上許多。
但好在各個part都至少有一個現成的示例和詳細的設計框架,比如mazebug給出了編程的全過程和灰度圖的算法,只差實現了;mazebug給出了深度優先搜索的思路,然后自己在這基礎上再完成一個方向預測的小設計即可順利完成;jigsaw給出了啟發式搜索算法的代碼,我們只需要借鑒實現廣度優先搜索和設計啟發式搜索算法的cost函數即可。
由于階段3也完善了readme文件,此處筆者簡單總結各個任務的核心算法,并結合readme文件進行介紹如下:
1. imagereader的工作
在實訓的博客中,介紹了位圖文件的存儲結構,為了提取主要的像素色彩信息,我們需要從位圖中將它們分離出來:
class ImplementImageIO implements IImageIO{/** 利用二進制流讀取Bitmap位圖文件* 注意,這里要求不能使用Java提供的API直接讀取圖像* IImageIO必須實現myRead函數
*/public Image myRead(String filepath) throws IOException {try {FileInputStream imageStream = new FileInputStream(filepath);
/** 丟棄無用信息* #18-21 保存位圖寬度(以像素個數表示)* #22-25 保存位圖高度(以像素個數表示)*/imageStream.skip(18);byte[] widthByte = new byte[4];imageStream.read(widthByte);byte[] heightByte = new byte[4];imageStream.read(heightByte);int width = byte2Int(widthByte);int height = byte2Int(heightByte);int[] data = new int[width * height];/*
* 丟棄無用信息
* #54-x 位圖數據
*/imageStream.skip(28);int skipNum = (4 - width * 3 % 4) % 4;for(int i = height - 1; i >= 0; --i) {for(int j = 0; j < width; ++j) {int blue = imageStream.read() & 0xff;int green = imageStream.read() & 0xff;int red = imageStream.read() & 0xff;Color color = new Color(red, green, blue);data[i * width + j] = color.getRGB();}imageStream.skip(skipNum);}imageStream.close();return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(width, height, data, 0, width));}catch (Exception e) {e.printStackTrace();return null;}//return null;}public int byte2Int(byte[] tmp) {int t1 = tmp[3] & 0xff;int t2 = tmp[2] & 0xff;int t3 = tmp[1] & 0xff;int t4 = tmp[0] & 0xff;int num = t1 << 24 | t2 << 16 | t3 << 8 |t4;return num;}/** 根據二進制數據創建Image時可以使用API;* 把讀取彩色圖像轉換成灰度圖像;* 提取并且顯示彩色圖像各個色彩通道;* 把處理完的圖像保存為bmp格式圖像。* IImageIO必須實現myWrite函數
*/public Image myWrite(Image img, String filepath) throws IOException {try {File bmpFile = new File(filepath + ".bmp");BufferedImage bufferedImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);Graphics2D graph = bufferedImage.createGraphics();graph.drawImage(img, 0, 0, null);graph.dispose();ImageIO.write(bufferedImage, "bmp", bmpFile);return bufferedImage;} catch (Exception e) {e.printStackTrace();}return null;}}
此外,實訓博客也有一個比較有趣的任務,就是輸入色彩圖,轉換為紅藍綠灰圖,由于博客中已給出了紅藍綠圖和灰度圖的公式(見下面代碼的注釋),其核心算法即圖像處理算法的實現也比較簡單:
class ImplementImageProcessor implements IImageProcessor {public Image showGray(Image image) {try { return colorFilter(image, "gray");} catch (Exception e) {e.printStackTrace();}return null;}public Image showChanelB(Image image) {try { return colorFilter(image, "blue");} catch (Exception e) {e.printStackTrace();}return null;}public Image showChanelG(Image image) {try { return colorFilter(image, "green");} catch (Exception e) {e.printStackTrace();}return null;}public Image showChanelR(Image image) {try { return colorFilter(image, "red");} catch (Exception e) {e.printStackTrace();}return null;}private Image colorFilter(Image img, String color) {BufferedImage newImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);Graphics2D graph = newImage.createGraphics();graph.drawImage(img, 0, 0, null);graph.dispose();int width = img.getWidth(null);int height = img.getHeight(null);for(int i = 0; i < width; ++i) {for(int j = 0; j < height; ++j) {int rgb = newImage.getRGB(i, j);if(color == "red") {rgb = 0xffff0000 & rgb;}else if(color == "green") {rgb = 0xff00ff00 & rgb;}else if(color == "blue") {rgb = 0xff0000ff & rgb;}else {
/** 彩色圖像轉換成灰度圖像*將彩色圖轉換成灰度圖,建議采用NTSC推薦的彩色圖到灰度圖的轉換公式:*I = 0.299 * R + 0.587 * G + 0.114 *B,其中R,G,B分別為紅、綠、藍通道的顏色值。*然后將三個色彩通道的顏色值改為這個值即可。這樣,原來的彩色圖像就變成了灰度圖像了。*/int gray = (int)(((rgb & 0x00ff0000) >> 16) * 0.299 + ((rgb & 0x0000ff00) >> 8) * 0.587 + (rgb & 0x000000ff) * 0.114);rgb = (rgb & 0xff000000) + (gray << 16) + (gray << 8) + gray;}newImage.setRGB(i, j, rgb);}}return newImage;}}
2. imagereader的readme文件
This project can be run using Eclipse. Its introduction and running screenshots are listed below. We can also use Sonar-Qube for code analysis of project files.
The overall structure of the project is as follows:
(1)ImagaReaderRunner.java
This Java file is the entry to the program. Running this file calls the IImageIO and IImageProcessormethods.
(2)ImplementImageIO.java
The ImplementImageIO Implements the read and write operations on images in the IImageIO interface .
(3)ImplementImageProcessor.java
The ImplementImageProcessor implements the operation for image single-channel extraction in the IImageProcessor interface.
(4)ImageProcessorTest.java
After importing junit.jar, we run this file to test the correctness of the program’s four functions with two original images in the ImageReader\src\bmptest folder.
3. mazebug的工作
mazebug也是一個比較有趣的任務,它基于階段2所實現的girdworld,要求實現跑迷宮的功能,這個功能在筆者完成階段2的基本任務時完全沒有想到,一定程度上也讓筆者意識到簡單的框架也可以做出很多有趣的東西啊~
mazebug的代碼實現并不難,只是使用了基本的深度優先搜索算法,并在這基礎上添加了方向預測的特性,具體實現的代碼如下:
public class MazeBug extends Bug {
/** Location next 記錄下一步要行走到的位置* Location last 記錄上一步的位置,便于在走到死路盡頭時返回* Stack<ArrayList<Location>> crossLocation 記錄樹的節點的棧* Integer stepCount 記錄本迷宮走到出口所用的步數*/public Location next;public Location last;public boolean isEnd = false;public Stack<ArrayList<Location>> crossLocation = new Stack<ArrayList<Location>>();public Integer stepCount = 0;public boolean hasShown = false;/** boolean visit[][] 訪問矩陣* ArrayList<Location> branch 棧頂節點和已經訪問的路徑節點* int []weight 四個方向的權值*/public boolean visit[][];public ArrayList<Location> branch;public int []weight;/*** Constructs a box bug that traces a square of a given side length* * @param length* the side length*/public MazeBug() {setColor(Color.GREEN);last = new Location(0, 0);/** initialize property*/visit = new boolean[100][100];for(int i=0;i<100;i++){for(int j=0;j<100;j++){visit[i][j]=false;}}Location loc = getLocation();branch = new ArrayList<Location>();branch.add(loc);
/** 四個方向選擇次數默認都是1,如果第一個節點選擇向左,則向左次數加1,注意思考什么時候次數需要減少。*/weight = new int[4];for(int i=0;i<4;i++){weight[i]=1;}}/*** Moves to the next location of the square.*//** 選擇行走方向可以使用java的隨機數類Random。* 要注意什么時候該入棧,什么時候該出棧。* 存儲一個節點時,注意除了存儲”該節點位置”和”已訪問方向”外,還要存儲”進入節點的方向”,以便在前路不通時能成功返回。*/public void act() {boolean willMove = canMove();if (isEnd == true) {//to show step count when reach the goal if (hasShown == false) {String msg = stepCount.toString() + " steps";JOptionPane.showMessageDialog(null, msg);hasShown = true;}} else if (willMove) {visit[next.getRow()][next.getCol()]=true;move();//increase step count when move stepCount++;} else {if(branch.isEmpty()){branch = crossLocation.pop();Location loc=branch.get(branch.size()-1);int dir=getLocation().getDirectionToward(loc);/** 四個方向選擇次數默認都是1,如果第一個節點選擇向左,則向左次數加1* 注意思考什么時候次數需要減少。* 回溯的時候次數減1*/if(dir==Location.NORTH){weight[2]--;}else if(dir==Location.EAST){weight[1]--;}else if(dir==Location.SOUTH){weight[3]--;}else if(dir==Location.WEST){weight[0]--;}}next = branch.remove(branch.size()-1);move();stepCount++;}}/*** Find all positions that can be move to.* * @param loc* the location to detect.* @return List of positions.*/public ArrayList<Location> getValid(Location loc) {Grid<Actor> gr = getGrid();if (gr == null){return null;}ArrayList<Location> valid = new ArrayList<Location>();/** 蟲子的行走方向只有東南西北四個方向,* 且在碰到迷宮出口(紅石頭)時,蟲子會自動停下來。*/int[] direction ={ Location.NORTH, Location.WEST, Location.EAST, Location.SOUTH };for(int d:direction){Location neighbor= loc.getAdjacentLocation(getDirection() + d);if(gr.isValid(neighbor)){Actor a = gr.get(neighbor);if((a==null||a instanceof Flower)&&!visit[neighbor.getRow()][neighbor.getCol()]){valid.add(neighbor);}else if(a instanceof Rock){if(Color.RED.equals(a.getColor())){isEnd = true;} }}}return valid;}/** 增加方向的概率估計。五個評估成績的迷宮都有一定的方向偏向性,如圖四就有向上和向左的偏向性。* 在行走正確路徑時,對四個方向的選擇次數進行統計,從而控制隨機選擇時選擇某個方向的概率。增加方向的概率估計后能有效地提高走迷宮的效率。* 提示:四個方向選擇次數默認都是1,如果第一個節點選擇向左,則向左次數加1,注意思考什么時候次數需要減少。*/public Location directionPrediction(ArrayList<Location> locs){int dir=0;int westWeight=0,eastWeight=0,northWeight=0,southWeight=0;for(Location loc:locs){dir=getLocation().getDirectionToward(loc);if(dir==Location.NORTH){northWeight=weight[2];}else if(dir==Location.EAST){eastWeight=weight[1];}else if(dir==Location.SOUTH){southWeight=weight[3];}else if(dir==Location.WEST){westWeight=weight[0];}}int random= 1 + (int)(Math.random() * (westWeight + eastWeight + northWeight + southWeight));if (random <= westWeight) {dir = Location.WEST;weight[0]++;} else if (random <= (westWeight + eastWeight)) {dir = Location.EAST;weight[1]++;} else if (random <= (westWeight + eastWeight + northWeight)) {dir = Location.NORTH;weight[2]++;} else {dir =Location.SOUTH;weight[3]++;}Location next=null;for(Location loc:locs){if(dir==getLocation().getDirectionToward(loc)){next=loc;}}return next;}/*** Tests whether this bug can move forward into a location that is empty or* contains a flower.* * @return true if this bug can move.*/public boolean canMove() {ArrayList<Location> locs=getValid(getLocation());if(locs.isEmpty()){return false;}else{branch.add(getLocation());if(locs.size()>1){crossLocation.push(branch);branch = new ArrayList<Location>();next = directionPrediction(locs);}else{next=locs.get(0);}last = getLocation();}return true;}/*** Moves the bug forward, putting a flower into the location it previously* occupied.*//** 選擇行走方向可以使用java的隨機數類Random。* 要注意什么時候該入棧,什么時候該出棧。* 存儲一個節點時,注意除了存儲”該節點位置”和”已訪問方向”外,還要存儲”進入節點的方向”,以便在前路不通時能成功返回。*/public void move() {Grid<Actor> gr = getGrid();if (gr == null){return;}Location loc = getLocation();if (gr.isValid(next)) {setDirection(getLocation().getDirectionToward(next));moveTo(next);} else{removeSelfFromGrid();}Flower flower = new Flower(getColor());flower.putSelfInGrid(gr, loc);}
}
4. mazebug的readme文件
This project can be run using Eclipse. Its introduction and running screenshots are listed below. We can also use Sonar-Qube for code analysis of project files.
The overall structure of the project is as follows:
(1)MazeBugRunner.java
This Java file is the entry to the program. Running this file calls the ActorWorld methods.
In GridWorld, we can set the grid size and load the maze. And then, we can use the depth-first algorithm to get out of the maze.
(2)MazeBug.java
This file realizes the method of getting out of the maze based on depth first search and direction probability estimation.
(3)Examples
OneRoadMaze:
EasyMaze:
FinalMaze01:
FinalMaze02:
FinalMaze03;
FinalMaze04:
FinalMaze05;
5. jigsaw的工作
jigsaw(N-Puzzle)是筆者認為整個中級實訓最好玩的一個任務,筆者以外地發現可以在這個小項目中進行一些“煉丹工作者”的fine-tune操作。
筆者先根據要求在啟發式搜索算法的框架下,完成了簡單的廣度優先算法:
/***(實驗一)廣度優先搜索算法,求指定5*5拼圖(24-數碼問題)的最優解* 填充此函數,可在Solution類中添加其他函數,屬性* @param bNode - 初始狀態節點* @param eNode - 目標狀態節點* @return 搜索成功時為true,失敗為false*/public boolean BFSearch(JigsawNode bNode, JigsawNode eNode) {this.beginJNode = new JigsawNode(bNode);this.endJNode = new JigsawNode(eNode);this.currentJNode = null;this.visitedList = new HashSet<>(1000);this.exploreList = new LinkedList<JigsawNode>();Set<JigsawNode> recordHashSet = new HashSet<>(1000);this.searchedNodesNum = 0;super.reset();final int MAX_NODE_NUM = 29000;final int DIRS = 4;exploreList.add(this.beginJNode);recordHashSet.add(this.beginJNode);while (this.searchedNodesNum < MAX_NODE_NUM && !exploreList.isEmpty()) {this.currentJNode = exploreList.peek();if (this.currentJNode.equals(eNode)) {this.getPath();break;}this.exploreList.remove();recordHashSet.remove(currentJNode);this.visitedList.add(this.currentJNode);this.searchedNodesNum++;JigsawNode[] nextNodes = new JigsawNode[]{new JigsawNode(this.currentJNode), new JigsawNode(this.currentJNode),new JigsawNode(this.currentJNode), new JigsawNode(this.currentJNode)};for (int i = 0; i < DIRS; ++i) {if (nextNodes[i].move(i) && !recordHashSet.contains(nextNodes[i]) && !this.visitedList.contains(nextNodes[i])) {exploreList.add(nextNodes[i]);recordHashSet.add(nextNodes[i]);}}}System.out.println("Jigsaw BF Search 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:" + this.searchedNodesNum);System.out.println("Depth of the current node is:" + this.getCurrentJNode().getNodeDepth());return this.isCompleted();}
然后便是為啟發式搜索算法完善估價函數,在實訓博客中有如下定義:
估價函數f(n)用來估計節點n的重要性,表示為:從起始節點,經過節點n,到達目標節點的代價。f(n)越小,表示節點n越優良,應該優先訪問它的鄰接節點。可參考的估價方法有:
①所有 放錯位的數碼 個數
②所有 放錯位的數碼與其正確位置的距離 之和
③后續節點不正確的數碼個數
……
可以同時使用多個估價方法,f(n) = af1(n) + bf2(n) + … 通過適當調整權重(a,b,…),能夠加快搜索速度。
在博客的基礎上,筆者一共實現了四種估價參數,并通過手動設置不同的權值來進行fine-tune操作(下面代碼中的參數是手動測試得到的局部最優值):
/***(Demo+實驗二)計算并修改狀態節點jNode的代價估計值:f(n)* 如 f(n) = s(n). s(n)代表后續節點不正確的數碼個數* 此函數會改變該節點的estimatedValue屬性值* 修改此函數,可在Solution類中添加其他函數,屬性* @param jNode - 要計算代價估計值的節點*/public void estimateValue(JigsawNode jNode) { /** four cost*/int s = 0; int misplacedTiles = 0; int manHattan = 0; int euclidean = 0; int dimension = JigsawNode.getDimension();/** 1st cost: 后續節點不正確的個數*/for (int index = 1; index < dimension * dimension; index++) {if (jNode.getNodesState()[index] + 1 != jNode.getNodesState()[index + 1]) {s++;}}/** 2nd cost: 所有放錯位的數碼個數*/for(int index = 1; index <= dimension*dimension; index++){if(jNode.getNodesState()[index] != index)misplacedTiles++;}/** 3rh and 4th cost: 曼哈頓距離 & 歐幾里得距離*/for (int i = 1; i <= dimension * dimension; ++i) {if (jNode.getNodesState()[i] != 0) {for (int j = 1; j <= dimension * dimension; ++j) {if (jNode.getNodesState()[i] == this.endJNode.getNodesState()[j]) {int startX = (i - 1) / dimension;int startY = (i - 1) % dimension;int endX = (j - 1) / dimension;int endY = (j - 1) % dimension;manHattan += Math.abs(startX - endX) + Math.abs(startY - endY);euclidean += Math.pow(startX - endX, 2) + Math.pow(startY - endY, 2);break;}}}}//fine-tune to set the locally optimal weightsjNode.setEstimatedValue(8 * s + 4 * manHattan + 2 * euclidean + 1 * misplacedTiles);}
在筆者愉快的調參過程中,筆者經過多次測試調優后,發現該項目的最高得分score為7,最低得分score為3(權值均為0),調參過程果然還是挺有趣的~
四、總結和心得體會
在本次中級實訓臨近結束時,不禁感慨個人的實力還是有限的,一個人不可能在0經驗的情況下無師自通,還是得多多學習和多多請教。
與大一的初級實現相比,中級實訓的代碼量看似多了不少,但實際上難度應該不在于代碼的編寫,而在于項目框架的理解,在掌握了基本的工具與框架之后,代碼的編寫簡直是如魚得水。但與此同時,每個階段的問答題,還是讓我耗費了更多的精力,當然也因此認識到自己項目開發的不足,比如考慮問題的角度不夠細致,單元測試有所缺陷,上交TA檢查代碼前沒有做好充足的準備而突發BUG等等。這些都讓我對軟件開發流程有了更深刻的理解。
在整個中級實訓過程中,我最喜歡的是擴展任務部分,這讓我有一種真正學到一些有趣的知識的感覺,而不用像基本任務那樣“填鴨式”地完成編程任務。當然,這也有可能是因為不需要像之前那樣對著代碼完成問答題。但總而言之,在擴展任務部分進行飛馬行空的編程,寫寫DFS和BFS,寫寫方向預測和進行快樂的調參,這些經歷都是不可多得的。
最后,這次實訓總結得到了很多人的幫助,如TA、舍友和往屆師兄師姐等,希望這篇略長的實訓總結報告,能為往后的師弟師妹們獻上一分薄力。
謝謝堅持完成中級實訓任務的自己,日后還請多多加油。
總結
- 上一篇: CentOS Docker安装配置部署G
- 下一篇: 基于Golang的简单web服务程序开发